diff --git a/src/database/schemas/finance/invoice.schema.js b/src/database/schemas/finance/invoice.schema.js index 90627ee..f703c79 100644 --- a/src/database/schemas/finance/invoice.schema.js +++ b/src/database/schemas/finance/invoice.schema.js @@ -1,7 +1,28 @@ import mongoose from 'mongoose'; import { generateId } from '../../utils.js'; const { Schema } = mongoose; -import { aggregateRollups, aggregateRollupsHistory } from '../../database.js'; +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( { @@ -12,21 +33,20 @@ const invoiceSchema = new Schema( shippingAmountWithTax: { type: Number, required: true, default: 0 }, grandTotalAmount: { type: Number, required: true, default: 0 }, totalTaxAmount: { type: Number, required: true, default: 0 }, - timestamp: { type: Date, default: Date.now }, - invoiceDate: { type: Date, required: false }, - dueDate: { type: Date, required: false }, vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: false }, - customer: { type: Schema.Types.ObjectId, ref: 'customer', required: false }, - invoiceType: { type: String, required: true, default: 'sales', enum: ['sales', 'purchase'] }, - relatedOrderType: { type: String, required: false }, - relatedOrder: { type: Schema.Types.ObjectId, refPath: 'relatedOrderType', required: false }, + client: { type: Schema.Types.ObjectId, ref: 'client', required: false }, state: { type: { type: String, required: true, default: 'draft' }, }, - sentAt: { type: Date, required: false }, + 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 }, paidAt: { type: Date, required: false }, cancelledAt: { type: Date, required: false }, - overdueAt: { type: Date, required: false }, + invoiceOrderItems: [invoiceOrderItemSchema], + invoiceShipments: [invoiceShipmentSchema], }, { timestamps: true } ); @@ -35,32 +55,49 @@ const rollupConfigs = [ { name: 'draft', filter: { 'state.type': 'draft' }, - rollups: [{ name: 'draft', property: 'state.type', operation: 'count' }], + rollups: [ + { name: 'draftCount', property: 'state.type', operation: 'count' }, + { name: 'draftGrandTotalAmount', property: 'grandTotalAmount', operation: 'sum' }, + ], }, { name: 'sent', filter: { 'state.type': 'sent' }, - rollups: [{ name: 'sent', property: 'state.type', operation: 'count' }], + 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: 'partiallyPaid', property: 'state.type', operation: 'count' }], + rollups: [ + { name: 'partiallyPaidCount', property: 'state.type', operation: 'count' }, + { name: 'partiallyPaidGrandTotalAmount', property: 'grandTotalAmount', operation: 'sum' }, + ], }, { name: 'paid', filter: { 'state.type': 'paid' }, - rollups: [{ name: 'paid', property: 'state.type', operation: 'count' }], + rollups: [{ name: 'paidCount', property: 'state.type', operation: 'count' }], }, { name: 'overdue', filter: { 'state.type': 'overdue' }, - rollups: [{ name: 'overdue', property: 'state.type', operation: 'count' }], + rollups: [{ name: 'overdueCount', property: 'state.type', operation: 'count' }], }, { name: 'cancelled', filter: { 'state.type': 'cancelled' }, - rollups: [{ name: 'cancelled', property: 'state.type', operation: 'count' }], + rollups: [{ name: 'cancelledCount', property: 'state.type', operation: 'count' }], }, ]; @@ -86,6 +123,57 @@ invoiceSchema.statics.history = async function (from, to) { 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;