2026-03-08 01:07:34 +00:00

294 lines
9.7 KiB
JavaScript

import mongoose from 'mongoose';
import { purchaseOrderModel } from './purchaseorder.schema.js';
import { salesOrderModel } from '../sales/salesorder.schema.js';
import { taxRateModel } from '../management/taxrate.schema.js';
import { filamentModel } from '../management/filament.schema.js';
import { filamentSkuModel } from '../management/filamentsku.schema.js';
import { partModel } from '../management/part.schema.js';
import { partSkuModel } from '../management/partsku.schema.js';
import { productModel } from '../management/product.schema.js';
import { productSkuModel } from '../management/productsku.schema.js';
import {
aggregateRollups,
aggregateRollupsHistory,
editObject,
getObject,
} from '../../database.js';
import { generateId } from '../../utils.js';
const { Schema } = mongoose;
const skuModelsByItemType = {
filament: filamentSkuModel,
part: partSkuModel,
product: productSkuModel,
};
const parentModelsByItemType = {
filament: filamentModel,
part: partModel,
product: productModel,
};
const orderItemSchema = new Schema(
{
_reference: { type: String, default: () => generateId()() },
orderType: { type: String, required: true },
name: { type: String, required: true },
state: {
type: { type: String, required: true, default: 'draft' },
},
order: { type: Schema.Types.ObjectId, refPath: 'orderType', required: true },
itemType: { type: String, required: true },
item: { type: Schema.Types.ObjectId, refPath: 'itemType', required: false },
sku: {
type: Schema.Types.ObjectId,
ref: function () {
return ['filament', 'part', 'product'].includes(this.itemType)
? this.itemType + 'Sku'
: null;
},
required: false,
},
syncAmount: { type: String, required: false, default: null },
itemAmount: { type: Number, required: true },
quantity: { type: Number, required: true },
totalAmount: { type: Number, required: true },
taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
totalAmountWithTax: { type: Number, required: true },
invoicedAmountWithTax: { type: Number, required: false, default: 0 },
invoicedAmount: { type: Number, required: false, default: 0 },
invoicedQuantity: { type: Number, required: false, default: 0 },
invoicedAmountRemaining: { type: Number, required: false, default: 0 },
invoicedAmountWithTaxRemaining: { type: Number, required: false, default: 0 },
invoicedQuantityRemaining: { type: Number, required: false, default: 0 },
timestamp: { type: Date, default: Date.now },
shipment: { type: Schema.Types.ObjectId, ref: 'shipment', required: false },
orderedAt: { type: Date, required: false },
receivedAt: { type: Date, required: false },
},
{ timestamps: true }
);
const rollupConfigs = [
{
name: 'shipped',
filter: { 'state.type': 'shipped' },
rollups: [{ name: 'shipped', property: 'state.type', operation: 'count' }],
},
{
name: 'received',
filter: { 'state.type': 'received' },
rollups: [{ name: 'received', property: 'state.type', operation: 'count' }],
},
];
orderItemSchema.statics.stats = async function () {
const results = await aggregateRollups({
model: this,
baseFilter: {},
rollupConfigs: rollupConfigs,
});
// Transform the results to match the expected format
return results;
};
orderItemSchema.statics.history = async function (from, to) {
const results = await aggregateRollupsHistory({
model: this,
startDate: from,
endDate: to,
rollupConfigs: rollupConfigs,
});
// Return time-series data array
return results;
};
orderItemSchema.statics.recalculate = async function (orderItem, user) {
if (orderItem.orderType !== 'purchaseOrder' && orderItem.orderType !== 'salesOrder') {
return;
}
const orderId = orderItem.order?._id || orderItem.order;
if (!orderId) {
return;
}
// If SKU present and syncAmount is set, check if override is on for the price mode and use that price instead
let effectiveItemAmount = orderItem.itemAmount;
const syncAmount = orderItem.syncAmount;
const skuId = orderItem.sku?._id || orderItem.sku;
const itemType = orderItem.itemType;
if (syncAmount && skuId && itemType && ['filament', 'part', 'product'].includes(itemType)) {
const skuModel = skuModelsByItemType[itemType];
const parentModel = parentModelsByItemType[itemType];
if (skuModel && parentModel) {
const sku = await getObject({
model: skuModel,
id: skuId,
cached: true,
});
if (sku) {
const parentId = sku.part?._id || sku.part || sku.product?._id || sku.product || sku.filament?._id || sku.filament;
if (syncAmount === 'itemCost') {
if (sku.overrideCost && sku.cost != null) {
effectiveItemAmount = sku.cost;
} else if (parentId) {
const parent = await getObject({
model: parentModel,
id: parentId,
cached: true,
});
if (parent && parent.cost != null) {
effectiveItemAmount = parent.cost;
}
}
} else if (syncAmount === 'itemPrice' && itemType !== 'filament') {
if (sku.overridePrice && sku.price != null) {
effectiveItemAmount = sku.price;
} else if (parentId) {
const parent = await getObject({
model: parentModel,
id: parentId,
cached: true,
});
if (parent && parent.price != null) {
effectiveItemAmount = parent.price;
}
}
}
}
}
}
let taxRate = orderItem.taxRate;
if (orderItem.taxRate?._id && Object.keys(orderItem.taxRate).length === 1) {
taxRate = await getObject({
model: taxRateModel,
id: orderItem.taxRate._id,
cached: true,
});
}
const orderTotalAmount = effectiveItemAmount * orderItem.quantity;
const orderTotalAmountWithTax = orderTotalAmount * (1 + (taxRate?.rate || 0) / 100);
const orderItemUpdateData = {
totalAmount: orderTotalAmount,
totalAmountWithTax: orderTotalAmountWithTax,
invoicedAmountRemaining: orderTotalAmount - orderItem.invoicedAmount,
invoicedAmountWithTaxRemaining: orderTotalAmountWithTax - orderItem.invoicedAmountWithTax,
invoicedQuantityRemaining: orderItem.quantity - orderItem.invoicedQuantity,
};
if (effectiveItemAmount !== orderItem.itemAmount) {
orderItemUpdateData.itemAmount = effectiveItemAmount;
orderItem.itemAmount = effectiveItemAmount;
}
await editObject({
model: this,
id: orderItem._id,
updateData: orderItemUpdateData,
user,
recalculate: false,
});
const rollupResults = await aggregateRollups({
model: this,
baseFilter: {
order: new mongoose.Types.ObjectId(orderId),
orderType: orderItem.orderType,
},
rollupConfigs: [
{
name: 'orderTotals',
rollups: [
{ name: 'totalAmount', property: 'totalAmount', operation: 'sum' },
{
name: 'totalAmountWithTax',
property: 'totalAmountWithTax',
operation: 'sum',
},
],
},
{
name: 'overallCount',
rollups: [{ name: 'overallCount', property: '_id', operation: 'count' }],
},
...rollupConfigs,
],
});
const totals = rollupResults.orderTotals || {};
const totalAmount = totals.totalAmount.sum?.toFixed(2) || 0;
const totalAmountWithTax = totals.totalAmountWithTax.sum?.toFixed(2) || 0;
const orderModel = orderItem.orderType === 'purchaseOrder' ? purchaseOrderModel : salesOrderModel;
const order = await getObject({
model: orderModel,
id: orderId,
cached: true,
});
const grandTotalAmount =
parseFloat(totalAmountWithTax || 0) + parseFloat(order.shippingAmountWithTax || 0);
var updateData = {
totalAmount: parseFloat(totalAmount).toFixed(2),
totalAmountWithTax: parseFloat(totalAmountWithTax).toFixed(2),
totalTaxAmount: parseFloat((totalAmountWithTax - totalAmount).toFixed(2)),
grandTotalAmount: parseFloat(grandTotalAmount).toFixed(2),
};
const overallCount = rollupResults.overallCount.count || 0;
const shippedCount = rollupResults.shipped.count || 0;
const receivedCount = rollupResults.received.count || 0;
if (orderItem.orderType === 'purchaseOrder') {
if (shippedCount > 0 && shippedCount < overallCount) {
updateData = { ...updateData, state: { type: 'partiallyShipped' } };
}
if (shippedCount > 0 && shippedCount == overallCount) {
updateData = { ...updateData, state: { type: 'shipped' } };
}
if (receivedCount > 0 && receivedCount < overallCount) {
updateData = { ...updateData, state: { type: 'partiallyReceived' } };
}
if (receivedCount > 0 && receivedCount == overallCount) {
updateData = { ...updateData, state: { type: 'received' } };
}
} else {
if (shippedCount > 0 && shippedCount < overallCount) {
updateData = { ...updateData, state: { type: 'partiallyShipped' } };
}
if (shippedCount > 0 && shippedCount == overallCount) {
updateData = { ...updateData, state: { type: 'shipped' } };
}
if (receivedCount > 0 && receivedCount < overallCount) {
updateData = { ...updateData, state: { type: 'partiallyDelivered' } };
}
if (receivedCount > 0 && receivedCount == overallCount) {
updateData = { ...updateData, state: { type: 'delivered' } };
}
}
await editObject({
model: orderModel,
id: orderId,
updateData: updateData,
user,
});
};
// Add virtual id getter
orderItemSchema.virtual('id').get(function () {
return this._id;
});
// Configure JSON serialization to include virtuals
orderItemSchema.set('toJSON', { virtuals: true });
// Create and export the model
export const orderItemModel = mongoose.model('orderItem', orderItemSchema);