294 lines
9.7 KiB
JavaScript
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);
|