import mongoose from 'mongoose'; import { generateId } from '../../utils.js'; const { Schema } = mongoose; import { aggregateRollups, aggregateRollupsHistory, editObject } from '../../database.js'; const invoiceOrderItemSchema = new Schema( { orderItem: { type: Schema.Types.ObjectId, ref: 'orderItem', required: true }, taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, invoiceAmountWithTax: { type: Number, required: true, default: 0 }, invoiceAmount: { type: Number, required: true, default: 0 }, invoiceQuantity: { type: Number, required: true, default: 0 }, }, { timestamps: true } ); const invoiceShipmentSchema = new Schema( { shipment: { type: Schema.Types.ObjectId, ref: 'shipment', required: true }, taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, invoiceAmountWithTax: { type: Number, required: true, default: 0 }, invoiceAmount: { type: Number, required: true, default: 0 }, }, { timestamps: true } ); const invoiceSchema = new Schema( { _reference: { type: String, default: () => generateId()() }, totalAmount: { type: Number, required: true, default: 0 }, totalAmountWithTax: { type: Number, required: true, default: 0 }, shippingAmount: { type: Number, required: true, default: 0 }, shippingAmountWithTax: { type: Number, required: true, default: 0 }, grandTotalAmount: { type: Number, required: true, default: 0 }, totalTaxAmount: { type: Number, required: true, default: 0 }, from: { type: Schema.Types.ObjectId, ref: 'vendor', required: false }, to: { type: Schema.Types.ObjectId, ref: 'client', required: false }, state: { type: { type: String, required: true, default: 'draft' }, }, orderType: { type: String, required: true }, order: { type: Schema.Types.ObjectId, refPath: 'orderType', required: true }, issuedAt: { type: Date, required: false }, dueAt: { type: Date, required: false }, postedAt: { type: Date, required: false }, acknowledgedAt: { type: Date, required: false }, paidAt: { type: Date, required: false }, cancelledAt: { type: Date, required: false }, invoiceOrderItems: [invoiceOrderItemSchema], invoiceShipments: [invoiceShipmentSchema], }, { timestamps: true } ); const rollupConfigs = [ { name: 'draft', filter: { 'state.type': 'draft' }, rollups: [ { name: 'draftCount', property: 'state.type', operation: 'count' }, { name: 'draftGrandTotalAmount', property: 'grandTotalAmount', operation: 'sum' }, ], }, { name: 'sent', filter: { 'state.type': 'sent' }, rollups: [ { name: 'sentCount', property: 'state.type', operation: 'count' }, { name: 'sentGrandTotalAmount', property: 'grandTotalAmount', operation: 'sum' }, ], }, { name: 'acknowledged', filter: { 'state.type': 'acknowledged' }, rollups: [ { name: 'acknowledgedCount', property: 'state.type', operation: 'count' }, { name: 'acknowledgedGrandTotalAmount', property: 'grandTotalAmount', operation: 'sum' }, ], }, { name: 'partiallyPaid', filter: { 'state.type': 'partiallyPaid' }, rollups: [ { name: 'partiallyPaidCount', property: 'state.type', operation: 'count' }, { name: 'partiallyPaidGrandTotalAmount', property: 'grandTotalAmount', operation: 'sum' }, ], }, { name: 'paid', filter: { 'state.type': 'paid' }, rollups: [{ name: 'paidCount', property: 'state.type', operation: 'count' }], }, { name: 'overdue', filter: { 'state.type': 'overdue' }, rollups: [{ name: 'overdueCount', property: 'state.type', operation: 'count' }], }, { name: 'cancelled', filter: { 'state.type': 'cancelled' }, rollups: [{ name: 'cancelledCount', property: 'state.type', operation: 'count' }], }, ]; invoiceSchema.statics.stats = async function () { const results = await aggregateRollups({ model: this, rollupConfigs: rollupConfigs, }); // Transform the results to match the expected format return results; }; invoiceSchema.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; }; invoiceSchema.statics.recalculate = async function (invoice, user) { const invoiceId = invoice._id || invoice; if (!invoiceId) { return; } // Calculate totals from invoiceOrderItems let totalAmount = 0; for (const item of invoice.invoiceOrderItems || []) { totalAmount += Number.parseFloat(item.invoiceAmount) || 0; } let totalAmountWithTax = 0; for (const item of invoice.invoiceOrderItems || []) { totalAmountWithTax += Number.parseFloat(item.invoiceAmountWithTax) || 0; } // Calculate shipping totals from invoiceShipments let shippingAmount = 0; for (const item of invoice.invoiceShipments || []) { shippingAmount += Number.parseFloat(item.invoiceAmount) || 0; } let shippingAmountWithTax = 0; for (const item of invoice.invoiceShipments || []) { shippingAmountWithTax += Number.parseFloat(item.invoiceAmountWithTax) || 0; } // Calculate grand total and tax amount const grandTotalAmount = parseFloat(totalAmountWithTax) + parseFloat(shippingAmountWithTax); const totalTaxAmount = parseFloat(totalAmountWithTax) - parseFloat(totalAmount) + (parseFloat(shippingAmountWithTax) - parseFloat(shippingAmount)); const updateData = { totalAmount: parseFloat(totalAmount).toFixed(2), totalAmountWithTax: parseFloat(totalAmountWithTax).toFixed(2), shippingAmount: parseFloat(shippingAmount).toFixed(2), shippingAmountWithTax: parseFloat(shippingAmountWithTax).toFixed(2), grandTotalAmount: parseFloat(grandTotalAmount).toFixed(2), totalTaxAmount: parseFloat(totalTaxAmount).toFixed(2), }; await editObject({ model: this, id: invoiceId, updateData, user, recalculate: false, }); }; // Add virtual id getter invoiceSchema.virtual('id').get(function () { return this._id; }); // Configure JSON serialization to include virtuals invoiceSchema.set('toJSON', { virtuals: true }); // Create and export the model export const invoiceModel = mongoose.model('invoice', invoiceSchema);