import mongoose from 'mongoose'; import { generateId } from '../../utils.js'; import { productModel } from './product.schema.js'; import { taxRateModel } from './taxrate.schema.js'; import { editObject, getObject } from '../../database.js'; import { amountWithTax, effectiveMarginPrice, resolveTaxRate, } from '../../tax.js'; const { Schema } = mongoose; const partSkuUsageSchema = new Schema({ part: { type: Schema.Types.ObjectId, ref: 'part', required: true }, partSku: { type: Schema.Types.ObjectId, ref: 'partSku', required: true }, quantity: { type: Number, required: true }, }); // Define the main product SKU schema const productSkuSchema = new Schema( { _reference: { type: String, default: () => generateId()() }, barcode: { type: String, required: false }, product: { type: Schema.Types.ObjectId, ref: 'product', required: true }, name: { type: String, required: true }, description: { type: String, required: false }, priceMode: { type: String, default: 'margin' }, price: { type: Number, required: false }, cost: { type: Number, required: false }, overrideCost: { type: Boolean, default: false }, overridePrice: { type: Boolean, default: false }, margin: { type: Number, required: false }, amount: { type: Number, required: false }, parts: { type: [partSkuUsageSchema], default: [] }, priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, priceWithTax: { type: Number, required: false }, costWithTax: { type: Number, required: false }, }, { timestamps: true } ); productSkuSchema.index({ name: 'text', barcode: 'text', description: 'text' }); // Add virtual id getter productSkuSchema.virtual('id').get(function () { return this._id; }); // Configure JSON serialization to include virtuals productSkuSchema.set('toJSON', { virtuals: true }); productSkuSchema.statics.recalculate = async function (productSku, user) { const parent = await getObject({ model: productModel, id: productSku.product?._id || productSku.product, cached: true, }); const taxUpdateData = {}; if (productSku.overrideCost) { const costTaxRate = await resolveTaxRate(productSku.costTaxRate, getObject, taxRateModel); if (productSku.cost != null) { taxUpdateData.costWithTax = amountWithTax(productSku.cost, costTaxRate); } } else if (parent?.costWithTax != null) { taxUpdateData.costWithTax = parent.costWithTax; } if (productSku.overridePrice) { const priceTaxRate = await resolveTaxRate( productSku.priceTaxRate ?? parent?.priceTaxRate, getObject, taxRateModel ); const cost = productSku.overrideCost ? productSku.cost : parent?.cost; const price = effectiveMarginPrice({ priceMode: productSku.priceMode ?? parent?.priceMode, price: productSku.price, cost, margin: productSku.margin ?? parent?.margin, }); if (price != null) { taxUpdateData.priceWithTax = amountWithTax(price, priceTaxRate); } } else if (parent?.priceWithTax != null) { taxUpdateData.priceWithTax = parent.priceWithTax; } if (Object.keys(taxUpdateData).length > 0) { await editObject({ model: this, id: productSku._id, updateData: taxUpdateData, user, recalculate: false, }); Object.assign(productSku, taxUpdateData); } const orderItemModel = mongoose.model('orderItem'); const skuId = productSku._id; const draftOrderItems = await orderItemModel .find({ 'state.type': 'draft', itemType: 'product', sku: skuId, }) .populate('order') .lean(); for (const orderItem of draftOrderItems) { await orderItemModel.recalculate(orderItem, user); } }; // Create and export the model export const productSkuModel = mongoose.model('productSku', productSkuSchema);