import mongoose from 'mongoose'; import { generateId } from '../../utils.js'; import { partModel } from './part.schema.js'; import { taxRateModel } from './taxrate.schema.js'; import { editObject, getObject } from '../../database.js'; import { amountWithTax, effectiveMarginPrice, resolveTaxRate, } from '../../tax.js'; const { Schema } = mongoose; // Define the main part SKU schema - pricing lives at SKU level const partSkuSchema = new Schema( { _reference: { type: String, default: () => generateId()() }, barcode: { type: String, required: false }, part: { type: Schema.Types.ObjectId, ref: 'part', 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 }, 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 } ); partSkuSchema.index({ name: 'text', barcode: 'text', description: 'text' }); // Add virtual id getter partSkuSchema.virtual('id').get(function () { return this._id; }); // Configure JSON serialization to include virtuals partSkuSchema.set('toJSON', { virtuals: true }); partSkuSchema.statics.recalculate = async function (partSku, user) { const parent = await getObject({ model: partModel, id: partSku.part?._id || partSku.part, cached: true, }); const taxUpdateData = {}; if (partSku.overrideCost) { const costTaxRate = await resolveTaxRate(partSku.costTaxRate, getObject, taxRateModel); if (partSku.cost != null) { taxUpdateData.costWithTax = amountWithTax(partSku.cost, costTaxRate); } } else if (parent?.costWithTax != null) { taxUpdateData.costWithTax = parent.costWithTax; } if (partSku.overridePrice) { const priceTaxRate = await resolveTaxRate( partSku.priceTaxRate ?? parent?.priceTaxRate, getObject, taxRateModel ); const cost = partSku.overrideCost ? partSku.cost : parent?.cost; const price = effectiveMarginPrice({ priceMode: partSku.priceMode ?? parent?.priceMode, price: partSku.price, cost, margin: partSku.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: partSku._id, updateData: taxUpdateData, user, recalculate: false, }); Object.assign(partSku, taxUpdateData); } const orderItemModel = mongoose.model('orderItem'); const skuId = partSku._id; const draftOrderItems = await orderItemModel .find({ 'state.type': 'draft', itemType: 'part', sku: skuId, }) .populate('order') .lean(); for (const orderItem of draftOrderItems) { await orderItemModel.recalculate(orderItem, user); } }; // Create and export the model export const partSkuModel = mongoose.model('partSku', partSkuSchema);