Compare commits
No commits in common. "a7e35c279eb1071b37c137e706120098c823b7e5" and "dea6a90b68bdcf3d144e18ebe59a5b618e19eeb5" have entirely different histories.
a7e35c279e
...
dea6a90b68
@ -1,28 +1,7 @@
|
||||
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 }
|
||||
);
|
||||
import { aggregateRollups, aggregateRollupsHistory } from '../../database.js';
|
||||
|
||||
const invoiceSchema = new Schema(
|
||||
{
|
||||
@ -33,21 +12,21 @@ 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 },
|
||||
from: { type: Schema.Types.ObjectId, ref: 'vendor', required: false },
|
||||
to: { type: Schema.Types.ObjectId, ref: 'client', required: false },
|
||||
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 },
|
||||
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 },
|
||||
sentAt: { type: Date, required: false },
|
||||
paidAt: { type: Date, required: false },
|
||||
cancelledAt: { type: Date, required: false },
|
||||
invoiceOrderItems: [invoiceOrderItemSchema],
|
||||
invoiceShipments: [invoiceShipmentSchema],
|
||||
overdueAt: { type: Date, required: false },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
@ -56,49 +35,32 @@ const rollupConfigs = [
|
||||
{
|
||||
name: 'draft',
|
||||
filter: { 'state.type': 'draft' },
|
||||
rollups: [
|
||||
{ name: 'draftCount', property: 'state.type', operation: 'count' },
|
||||
{ name: 'draftGrandTotalAmount', property: 'grandTotalAmount', operation: 'sum' },
|
||||
],
|
||||
rollups: [{ name: 'draft', property: 'state.type', operation: 'count' }],
|
||||
},
|
||||
{
|
||||
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' },
|
||||
],
|
||||
rollups: [{ name: 'sent', property: 'state.type', operation: 'count' }],
|
||||
},
|
||||
{
|
||||
name: 'partiallyPaid',
|
||||
filter: { 'state.type': 'partiallyPaid' },
|
||||
rollups: [
|
||||
{ name: 'partiallyPaidCount', property: 'state.type', operation: 'count' },
|
||||
{ name: 'partiallyPaidGrandTotalAmount', property: 'grandTotalAmount', operation: 'sum' },
|
||||
],
|
||||
rollups: [{ name: 'partiallyPaid', property: 'state.type', operation: 'count' }],
|
||||
},
|
||||
{
|
||||
name: 'paid',
|
||||
filter: { 'state.type': 'paid' },
|
||||
rollups: [{ name: 'paidCount', property: 'state.type', operation: 'count' }],
|
||||
rollups: [{ name: 'paid', property: 'state.type', operation: 'count' }],
|
||||
},
|
||||
{
|
||||
name: 'overdue',
|
||||
filter: { 'state.type': 'overdue' },
|
||||
rollups: [{ name: 'overdueCount', property: 'state.type', operation: 'count' }],
|
||||
rollups: [{ name: 'overdue', property: 'state.type', operation: 'count' }],
|
||||
},
|
||||
{
|
||||
name: 'cancelled',
|
||||
filter: { 'state.type': 'cancelled' },
|
||||
rollups: [{ name: 'cancelledCount', property: 'state.type', operation: 'count' }],
|
||||
rollups: [{ name: 'cancelled', property: 'state.type', operation: 'count' }],
|
||||
},
|
||||
];
|
||||
|
||||
@ -124,57 +86,6 @@ 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;
|
||||
|
||||
@ -1,105 +0,0 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { generateId } from '../../utils.js';
|
||||
const { Schema } = mongoose;
|
||||
import { aggregateRollups, aggregateRollupsHistory, editObject } from '../../database.js';
|
||||
|
||||
const paymentSchema = new Schema(
|
||||
{
|
||||
_reference: { type: String, default: () => generateId()() },
|
||||
amount: { type: Number, required: true, default: 0 },
|
||||
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: false },
|
||||
client: { type: Schema.Types.ObjectId, ref: 'client', required: false },
|
||||
invoice: { type: Schema.Types.ObjectId, ref: 'invoice', required: true },
|
||||
state: {
|
||||
type: { type: String, required: true, default: 'draft' },
|
||||
},
|
||||
paymentDate: { type: Date, required: false },
|
||||
postedAt: { type: Date, required: false },
|
||||
cancelledAt: { type: Date, required: false },
|
||||
paymentMethod: { type: String, required: false },
|
||||
notes: { type: String, required: false },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
const rollupConfigs = [
|
||||
{
|
||||
name: 'draft',
|
||||
filter: { 'state.type': 'draft' },
|
||||
rollups: [
|
||||
{ name: 'draftCount', property: 'state.type', operation: 'count' },
|
||||
{ name: 'draftAmount', property: 'amount', operation: 'sum' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'posted',
|
||||
filter: { 'state.type': 'posted' },
|
||||
rollups: [
|
||||
{ name: 'postedCount', property: 'state.type', operation: 'count' },
|
||||
{ name: 'postedAmount', property: 'amount', operation: 'sum' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'cancelled',
|
||||
filter: { 'state.type': 'cancelled' },
|
||||
rollups: [
|
||||
{ name: 'cancelledCount', property: 'state.type', operation: 'count' },
|
||||
{ name: 'cancelledAmount', property: 'amount', operation: 'sum' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
paymentSchema.statics.stats = async function () {
|
||||
const results = await aggregateRollups({
|
||||
model: this,
|
||||
rollupConfigs: rollupConfigs,
|
||||
});
|
||||
|
||||
// Transform the results to match the expected format
|
||||
return results;
|
||||
};
|
||||
|
||||
paymentSchema.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;
|
||||
};
|
||||
|
||||
paymentSchema.statics.recalculate = async function (payment, user) {
|
||||
const paymentId = payment._id || payment;
|
||||
if (!paymentId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For payments, the amount is set directly
|
||||
const amount = payment.amount || 0;
|
||||
|
||||
const updateData = {
|
||||
amount: parseFloat(amount).toFixed(2),
|
||||
};
|
||||
|
||||
await editObject({
|
||||
model: this,
|
||||
id: paymentId,
|
||||
updateData,
|
||||
user,
|
||||
recalculate: false,
|
||||
});
|
||||
};
|
||||
|
||||
// Add virtual id getter
|
||||
paymentSchema.virtual('id').get(function () {
|
||||
return this._id;
|
||||
});
|
||||
|
||||
// Configure JSON serialization to include virtuals
|
||||
paymentSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
// Create and export the model
|
||||
export const paymentModel = mongoose.model('payment', paymentSchema);
|
||||
@ -14,7 +14,6 @@ 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' },
|
||||
},
|
||||
@ -27,12 +26,6 @@ const orderItemSchema = new Schema(
|
||||
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 },
|
||||
@ -104,9 +97,6 @@ orderItemSchema.statics.recalculate = async function (orderItem, user) {
|
||||
model: orderItemModel,
|
||||
id: orderItem._id,
|
||||
updateData: {
|
||||
invoicedAmountRemaining: orderTotalAmount - orderItem.invoicedAmount,
|
||||
invoicedAmountWithTaxRemaining: orderTotalAmountWithTax - orderItem.invoicedAmountWithTax,
|
||||
invoicedQuantityRemaining: orderItem.quantity - orderItem.invoicedQuantity,
|
||||
totalAmount: orderTotalAmount,
|
||||
totalAmountWithTax: orderTotalAmountWithTax,
|
||||
},
|
||||
|
||||
@ -15,10 +15,6 @@ const shipmentSchema = new Schema(
|
||||
amount: { type: Number, required: true },
|
||||
amountWithTax: { type: Number, required: true },
|
||||
taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
|
||||
invoicedAmount: { type: Number, required: false, default: 0 },
|
||||
invoicedAmountWithTax: { type: Number, required: false, default: 0 },
|
||||
invoicedAmountRemaining: { type: Number, required: false, default: 0 },
|
||||
invoicedAmountWithTaxRemaining: { type: Number, required: false, default: 0 },
|
||||
shippedAt: { type: Date, required: false },
|
||||
expectedAt: { type: Date, required: false },
|
||||
deliveredAt: { type: Date, required: false },
|
||||
@ -54,16 +50,12 @@ shipmentSchema.statics.recalculate = async function (shipment, user) {
|
||||
});
|
||||
}
|
||||
|
||||
const amountWithTax = parseFloat(
|
||||
(shipment.amount || 0) * (1 + (taxRate?.rate || 0) / 100)
|
||||
).toFixed(2);
|
||||
const amountWithTax = shipment.amount * (1 + (taxRate?.rate || 0) / 100);
|
||||
await editObject({
|
||||
model: shipmentModel,
|
||||
id: shipment._id,
|
||||
updateData: {
|
||||
amountWithTax: amountWithTax,
|
||||
invoicedAmountRemaining: shipment.amount - (shipment.invoicedAmount || 0),
|
||||
invoicedAmountWithTaxRemaining: amountWithTax - (shipment.invoicedAmountWithTax || 0),
|
||||
},
|
||||
user,
|
||||
recalculate: false,
|
||||
|
||||
@ -28,8 +28,6 @@ import { taxRateModel } from './management/taxrate.schema.js';
|
||||
import { taxRecordModel } from './management/taxrecord.schema.js';
|
||||
import { shipmentModel } from './inventory/shipment.schema.js';
|
||||
import { invoiceModel } from './finance/invoice.schema.js';
|
||||
import { clientModel } from './sales/client.schema.js';
|
||||
import { salesOrderModel } from './sales/salesorder.schema.js';
|
||||
|
||||
// Map prefixes to models and id fields
|
||||
export const models = {
|
||||
@ -104,6 +102,4 @@ export const models = {
|
||||
TXD: { model: taxRecordModel, idField: '_id', type: 'taxRecord', referenceField: '_reference' },
|
||||
SHP: { model: shipmentModel, idField: '_id', type: 'shipment', referenceField: '_reference' },
|
||||
INV: { model: invoiceModel, idField: '_id', type: 'invoice', referenceField: '_reference' },
|
||||
CLI: { model: clientModel, idField: '_id', type: 'client', referenceField: '_reference' },
|
||||
SOR: { model: salesOrderModel, idField: '_id', type: 'salesOrder', referenceField: '_reference' },
|
||||
};
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { generateId } from '../../utils.js';
|
||||
|
||||
const addressSchema = new mongoose.Schema({
|
||||
building: { required: false, type: String },
|
||||
addressLine1: { required: false, type: String },
|
||||
addressLine2: { required: false, type: String },
|
||||
city: { required: false, type: String },
|
||||
state: { required: false, type: String },
|
||||
postcode: { required: false, type: String },
|
||||
country: { required: false, type: String },
|
||||
});
|
||||
|
||||
const clientSchema = new mongoose.Schema(
|
||||
{
|
||||
_reference: { type: String, default: () => generateId()() },
|
||||
name: { required: true, type: String },
|
||||
email: { required: false, type: String },
|
||||
phone: { required: false, type: String },
|
||||
country: { required: false, type: String },
|
||||
active: { required: true, type: Boolean, default: true },
|
||||
address: { required: false, type: addressSchema },
|
||||
tags: [{ required: false, type: String }],
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
clientSchema.virtual('id').get(function () {
|
||||
return this._id;
|
||||
});
|
||||
|
||||
clientSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
export const clientModel = mongoose.model('client', clientSchema);
|
||||
@ -1,107 +0,0 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { generateId } from '../../utils.js';
|
||||
const { Schema } = mongoose;
|
||||
import { aggregateRollups, aggregateRollupsHistory } from '../../database.js';
|
||||
|
||||
const salesOrderSchema = 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 },
|
||||
timestamp: { type: Date, default: Date.now },
|
||||
client: { type: Schema.Types.ObjectId, ref: 'client', required: true },
|
||||
state: {
|
||||
type: { type: String, required: true, default: 'draft' },
|
||||
},
|
||||
postedAt: { type: Date, required: false },
|
||||
confirmedAt: { type: Date, required: false },
|
||||
cancelledAt: { type: Date, required: false },
|
||||
completedAt: { type: Date, required: false },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
const rollupConfigs = [
|
||||
{
|
||||
name: 'draft',
|
||||
filter: { 'state.type': 'draft' },
|
||||
rollups: [{ name: 'draft', property: 'state.type', operation: 'count' }],
|
||||
},
|
||||
{
|
||||
name: 'sent',
|
||||
filter: { 'state.type': 'sent' },
|
||||
rollups: [{ name: 'sent', property: 'state.type', operation: 'count' }],
|
||||
},
|
||||
{
|
||||
name: 'confirmed',
|
||||
filter: { 'state.type': 'confirmed' },
|
||||
rollups: [{ name: 'confirmed', property: 'state.type', operation: 'count' }],
|
||||
},
|
||||
{
|
||||
name: 'partiallyShipped',
|
||||
filter: { 'state.type': 'partiallyShipped' },
|
||||
rollups: [{ name: 'partiallyShipped', property: 'state.type', operation: 'count' }],
|
||||
},
|
||||
{
|
||||
name: 'shipped',
|
||||
filter: { 'state.type': 'shipped' },
|
||||
rollups: [{ name: 'shipped', property: 'state.type', operation: 'count' }],
|
||||
},
|
||||
{
|
||||
name: 'partiallyDelivered',
|
||||
filter: { 'state.type': 'partiallyDelivered' },
|
||||
rollups: [{ name: 'partiallyDelivered', property: 'state.type', operation: 'count' }],
|
||||
},
|
||||
{
|
||||
name: 'delivered',
|
||||
filter: { 'state.type': 'delivered' },
|
||||
rollups: [{ name: 'delivered', property: 'state.type', operation: 'count' }],
|
||||
},
|
||||
{
|
||||
name: 'cancelled',
|
||||
filter: { 'state.type': 'cancelled' },
|
||||
rollups: [{ name: 'cancelled', property: 'state.type', operation: 'count' }],
|
||||
},
|
||||
{
|
||||
name: 'completed',
|
||||
filter: { 'state.type': 'completed' },
|
||||
rollups: [{ name: 'completed', property: 'state.type', operation: 'count' }],
|
||||
},
|
||||
];
|
||||
|
||||
salesOrderSchema.statics.stats = async function () {
|
||||
const results = await aggregateRollups({
|
||||
model: this,
|
||||
rollupConfigs: rollupConfigs,
|
||||
});
|
||||
|
||||
// Transform the results to match the expected format
|
||||
return results;
|
||||
};
|
||||
|
||||
salesOrderSchema.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;
|
||||
};
|
||||
|
||||
// Add virtual id getter
|
||||
salesOrderSchema.virtual('id').get(function () {
|
||||
return this._id;
|
||||
});
|
||||
|
||||
// Configure JSON serialization to include virtuals
|
||||
salesOrderSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
// Create and export the model
|
||||
export const salesOrderModel = mongoose.model('salesOrder', salesOrderSchema);
|
||||
@ -38,9 +38,6 @@ import {
|
||||
taxRateRoutes,
|
||||
taxRecordRoutes,
|
||||
invoiceRoutes,
|
||||
paymentRoutes,
|
||||
clientRoutes,
|
||||
salesOrderRoutes,
|
||||
} from './routes/index.js';
|
||||
import path from 'path';
|
||||
import * as fs from 'fs';
|
||||
@ -144,9 +141,6 @@ app.use('/courierservices', courierServiceRoutes);
|
||||
app.use('/taxrates', taxRateRoutes);
|
||||
app.use('/taxrecords', taxRecordRoutes);
|
||||
app.use('/invoices', invoiceRoutes);
|
||||
app.use('/payments', paymentRoutes);
|
||||
app.use('/clients', clientRoutes);
|
||||
app.use('/salesorders', salesOrderRoutes);
|
||||
app.use('/notes', noteRoutes);
|
||||
|
||||
// Start the application
|
||||
|
||||
@ -13,9 +13,9 @@ import {
|
||||
listInvoicesByPropertiesRouteHandler,
|
||||
getInvoiceStatsRouteHandler,
|
||||
getInvoiceHistoryRouteHandler,
|
||||
acknowledgeInvoiceRouteHandler,
|
||||
sendInvoiceRouteHandler,
|
||||
markInvoicePaidRouteHandler,
|
||||
cancelInvoiceRouteHandler,
|
||||
postInvoiceRouteHandler,
|
||||
} from '../../services/finance/invoices.js';
|
||||
|
||||
// list of invoices
|
||||
@ -23,13 +23,12 @@ router.get('/', isAuthenticated, (req, res) => {
|
||||
const { page, limit, property, search, sort, order } = req.query;
|
||||
const allowedFilters = [
|
||||
'vendor',
|
||||
'client',
|
||||
'customer',
|
||||
'invoiceType',
|
||||
'state',
|
||||
'value',
|
||||
'vendor._id',
|
||||
'client._id',
|
||||
'order',
|
||||
'order._id',
|
||||
'orderType',
|
||||
'customer._id',
|
||||
];
|
||||
const filter = getFilter(req.query, allowedFilters);
|
||||
listInvoicesRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
||||
@ -39,13 +38,12 @@ router.get('/properties', isAuthenticated, (req, res) => {
|
||||
let properties = convertPropertiesString(req.query.properties);
|
||||
const allowedFilters = [
|
||||
'vendor',
|
||||
'client',
|
||||
'orderType',
|
||||
'order',
|
||||
'customer',
|
||||
'invoiceType',
|
||||
'state.type',
|
||||
'value',
|
||||
'vendor._id',
|
||||
'client._id',
|
||||
'customer._id',
|
||||
];
|
||||
const filter = getFilter(req.query, allowedFilters, false);
|
||||
var masterFilter = {};
|
||||
@ -86,12 +84,12 @@ router.delete('/:id', isAuthenticated, async (req, res) => {
|
||||
deleteInvoiceRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.post('/:id/post', isAuthenticated, async (req, res) => {
|
||||
postInvoiceRouteHandler(req, res);
|
||||
router.post('/:id/send', isAuthenticated, async (req, res) => {
|
||||
sendInvoiceRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.post('/:id/acknowledge', isAuthenticated, async (req, res) => {
|
||||
acknowledgeInvoiceRouteHandler(req, res);
|
||||
router.post('/:id/markpaid', isAuthenticated, async (req, res) => {
|
||||
markInvoicePaidRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.post('/:id/cancel', isAuthenticated, async (req, res) => {
|
||||
|
||||
@ -1,96 +0,0 @@
|
||||
import express from 'express';
|
||||
import { isAuthenticated } from '../../keycloak.js';
|
||||
import { getFilter, convertPropertiesString } from '../../utils.js';
|
||||
|
||||
const router = express.Router();
|
||||
import {
|
||||
listPaymentsRouteHandler,
|
||||
getPaymentRouteHandler,
|
||||
editPaymentRouteHandler,
|
||||
editMultiplePaymentsRouteHandler,
|
||||
newPaymentRouteHandler,
|
||||
deletePaymentRouteHandler,
|
||||
listPaymentsByPropertiesRouteHandler,
|
||||
getPaymentStatsRouteHandler,
|
||||
getPaymentHistoryRouteHandler,
|
||||
postPaymentRouteHandler,
|
||||
cancelPaymentRouteHandler,
|
||||
} from '../../services/finance/payments.js';
|
||||
|
||||
// list of payments
|
||||
router.get('/', isAuthenticated, (req, res) => {
|
||||
const { page, limit, property, search, sort, order } = req.query;
|
||||
const allowedFilters = [
|
||||
'vendor',
|
||||
'client',
|
||||
'state',
|
||||
'vendor._id',
|
||||
'client._id',
|
||||
'invoice',
|
||||
'invoice._id',
|
||||
];
|
||||
const filter = getFilter(req.query, allowedFilters);
|
||||
listPaymentsRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
||||
});
|
||||
|
||||
router.get('/properties', isAuthenticated, (req, res) => {
|
||||
let properties = convertPropertiesString(req.query.properties);
|
||||
const allowedFilters = [
|
||||
'vendor',
|
||||
'client',
|
||||
'invoice',
|
||||
'state.type',
|
||||
'value',
|
||||
'vendor._id',
|
||||
'client._id',
|
||||
'invoice._id',
|
||||
];
|
||||
const filter = getFilter(req.query, allowedFilters, false);
|
||||
var masterFilter = {};
|
||||
if (req.query.masterFilter) {
|
||||
masterFilter = JSON.parse(req.query.masterFilter);
|
||||
}
|
||||
listPaymentsByPropertiesRouteHandler(req, res, properties, filter, masterFilter);
|
||||
});
|
||||
|
||||
router.post('/', isAuthenticated, (req, res) => {
|
||||
newPaymentRouteHandler(req, res);
|
||||
});
|
||||
|
||||
// get payment stats
|
||||
router.get('/stats', isAuthenticated, (req, res) => {
|
||||
getPaymentStatsRouteHandler(req, res);
|
||||
});
|
||||
|
||||
// get payment history
|
||||
router.get('/history', isAuthenticated, (req, res) => {
|
||||
getPaymentHistoryRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.get('/:id', isAuthenticated, (req, res) => {
|
||||
getPaymentRouteHandler(req, res);
|
||||
});
|
||||
|
||||
// update multiple payments
|
||||
router.put('/', isAuthenticated, async (req, res) => {
|
||||
editMultiplePaymentsRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.put('/:id', isAuthenticated, async (req, res) => {
|
||||
editPaymentRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.delete('/:id', isAuthenticated, async (req, res) => {
|
||||
deletePaymentRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.post('/:id/post', isAuthenticated, async (req, res) => {
|
||||
postPaymentRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.post('/:id/cancel', isAuthenticated, async (req, res) => {
|
||||
cancelPaymentRouteHandler(req, res);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@ -30,9 +30,6 @@ import courierServiceRoutes from './management/courierservice.js';
|
||||
import taxRateRoutes from './management/taxrates.js';
|
||||
import taxRecordRoutes from './management/taxrecords.js';
|
||||
import invoiceRoutes from './finance/invoices.js';
|
||||
import paymentRoutes from './finance/payments.js';
|
||||
import clientRoutes from './sales/clients.js';
|
||||
import salesOrderRoutes from './sales/salesorders.js';
|
||||
import noteRoutes from './misc/notes.js';
|
||||
|
||||
export {
|
||||
@ -69,7 +66,4 @@ export {
|
||||
taxRateRoutes,
|
||||
taxRecordRoutes,
|
||||
invoiceRoutes,
|
||||
paymentRoutes,
|
||||
clientRoutes,
|
||||
salesOrderRoutes,
|
||||
};
|
||||
|
||||
@ -19,7 +19,6 @@ import {
|
||||
router.get('/', isAuthenticated, (req, res) => {
|
||||
const { page, limit, property, search, sort, order } = req.query;
|
||||
const allowedFilters = [
|
||||
'name',
|
||||
'itemType',
|
||||
'item',
|
||||
'item._id',
|
||||
@ -36,7 +35,6 @@ router.get('/', isAuthenticated, (req, res) => {
|
||||
router.get('/properties', isAuthenticated, (req, res) => {
|
||||
let properties = convertPropertiesString(req.query.properties);
|
||||
const allowedFilters = [
|
||||
'name',
|
||||
'itemType',
|
||||
'item',
|
||||
'item._id',
|
||||
|
||||
@ -1,59 +0,0 @@
|
||||
import express from 'express';
|
||||
import { isAuthenticated } from '../../keycloak.js';
|
||||
import { getFilter, convertPropertiesString } from '../../utils.js';
|
||||
|
||||
const router = express.Router();
|
||||
import {
|
||||
listClientsRouteHandler,
|
||||
getClientRouteHandler,
|
||||
editClientRouteHandler,
|
||||
newClientRouteHandler,
|
||||
deleteClientRouteHandler,
|
||||
listClientsByPropertiesRouteHandler,
|
||||
getClientStatsRouteHandler,
|
||||
getClientHistoryRouteHandler,
|
||||
} from '../../services/sales/clients.js';
|
||||
|
||||
// list of clients
|
||||
router.get('/', isAuthenticated, (req, res) => {
|
||||
const { page, limit, property, search, sort, order } = req.query;
|
||||
const allowedFilters = ['country', 'active', 'createdAt', 'updatedAt'];
|
||||
const filter = getFilter(req.query, allowedFilters);
|
||||
listClientsRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
||||
});
|
||||
|
||||
router.get('/properties', isAuthenticated, (req, res) => {
|
||||
let properties = convertPropertiesString(req.query.properties);
|
||||
const allowedFilters = ['country', 'active', 'createdAt', 'updatedAt'];
|
||||
const filter = getFilter(req.query, allowedFilters, false);
|
||||
listClientsByPropertiesRouteHandler(req, res, properties, filter);
|
||||
});
|
||||
|
||||
router.post('/', isAuthenticated, (req, res) => {
|
||||
newClientRouteHandler(req, res);
|
||||
});
|
||||
|
||||
// get client stats
|
||||
router.get('/stats', isAuthenticated, (req, res) => {
|
||||
getClientStatsRouteHandler(req, res);
|
||||
});
|
||||
|
||||
// get clients history
|
||||
router.get('/history', isAuthenticated, (req, res) => {
|
||||
getClientHistoryRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.get('/:id', isAuthenticated, (req, res) => {
|
||||
getClientRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.put('/:id', isAuthenticated, async (req, res) => {
|
||||
editClientRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.delete('/:id', isAuthenticated, async (req, res) => {
|
||||
deleteClientRouteHandler(req, res);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@ -1,84 +0,0 @@
|
||||
import express from 'express';
|
||||
import { isAuthenticated } from '../../keycloak.js';
|
||||
import { getFilter, convertPropertiesString } from '../../utils.js';
|
||||
|
||||
const router = express.Router();
|
||||
import {
|
||||
listSalesOrdersRouteHandler,
|
||||
getSalesOrderRouteHandler,
|
||||
editSalesOrderRouteHandler,
|
||||
editMultipleSalesOrdersRouteHandler,
|
||||
newSalesOrderRouteHandler,
|
||||
deleteSalesOrderRouteHandler,
|
||||
listSalesOrdersByPropertiesRouteHandler,
|
||||
getSalesOrderStatsRouteHandler,
|
||||
getSalesOrderHistoryRouteHandler,
|
||||
postSalesOrderRouteHandler,
|
||||
confirmSalesOrderRouteHandler,
|
||||
cancelSalesOrderRouteHandler,
|
||||
} from '../../services/sales/salesorders.js';
|
||||
|
||||
// list of sales orders
|
||||
router.get('/', isAuthenticated, (req, res) => {
|
||||
const { page, limit, property, search, sort, order } = req.query;
|
||||
const allowedFilters = ['client', 'state', 'value', 'client._id'];
|
||||
const filter = getFilter(req.query, allowedFilters);
|
||||
listSalesOrdersRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
||||
});
|
||||
|
||||
router.get('/properties', isAuthenticated, (req, res) => {
|
||||
let properties = convertPropertiesString(req.query.properties);
|
||||
const allowedFilters = ['client', 'state.type', 'value', 'client._id'];
|
||||
const filter = getFilter(req.query, allowedFilters, false);
|
||||
var masterFilter = {};
|
||||
if (req.query.masterFilter) {
|
||||
masterFilter = getFilter(JSON.parse(req.query.masterFilter), allowedFilters, true);
|
||||
}
|
||||
listSalesOrdersByPropertiesRouteHandler(req, res, properties, filter, masterFilter);
|
||||
});
|
||||
|
||||
router.post('/', isAuthenticated, (req, res) => {
|
||||
newSalesOrderRouteHandler(req, res);
|
||||
});
|
||||
|
||||
// get sales order stats
|
||||
router.get('/stats', isAuthenticated, (req, res) => {
|
||||
getSalesOrderStatsRouteHandler(req, res);
|
||||
});
|
||||
|
||||
// get sales orders history
|
||||
router.get('/history', isAuthenticated, (req, res) => {
|
||||
getSalesOrderHistoryRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.get('/:id', isAuthenticated, (req, res) => {
|
||||
getSalesOrderRouteHandler(req, res);
|
||||
});
|
||||
|
||||
// update multiple sales orders
|
||||
router.put('/', isAuthenticated, async (req, res) => {
|
||||
editMultipleSalesOrdersRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.put('/:id', isAuthenticated, async (req, res) => {
|
||||
editSalesOrderRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.delete('/:id', isAuthenticated, async (req, res) => {
|
||||
deleteSalesOrderRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.post('/:id/post', isAuthenticated, async (req, res) => {
|
||||
postSalesOrderRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.post('/:id/confirm', isAuthenticated, async (req, res) => {
|
||||
confirmSalesOrderRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.post('/:id/cancel', isAuthenticated, async (req, res) => {
|
||||
cancelSalesOrderRouteHandler(req, res);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@ -14,8 +14,6 @@ import {
|
||||
getModelHistory,
|
||||
checkStates,
|
||||
} from '../../database/database.js';
|
||||
import { orderItemModel } from '../../database/schemas/inventory/orderitem.schema.js';
|
||||
import { shipmentModel } from '../../database/schemas/inventory/shipment.schema.js';
|
||||
|
||||
const logger = log4js.getLogger('Invoices');
|
||||
logger.level = config.server.logLevel;
|
||||
@ -31,11 +29,7 @@ export const listInvoicesRouteHandler = async (
|
||||
sort = '',
|
||||
order = 'ascend'
|
||||
) => {
|
||||
const populateFields = [
|
||||
{ path: 'to', strictPopulate: false, ref: 'client' },
|
||||
{ path: 'from', strictPopulate: false, ref: 'vendor' },
|
||||
{ path: 'order' },
|
||||
];
|
||||
const populateFields = ['vendor', 'customer', 'relatedOrder'];
|
||||
const result = await listObjects({
|
||||
model: invoiceModel,
|
||||
page,
|
||||
@ -65,7 +59,7 @@ export const listInvoicesByPropertiesRouteHandler = async (
|
||||
filter = {},
|
||||
masterFilter = {}
|
||||
) => {
|
||||
const populateFields = ['to', 'from', 'order'];
|
||||
const populateFields = ['vendor', 'customer', 'relatedOrder'];
|
||||
const result = await listObjectsByProperties({
|
||||
model: invoiceModel,
|
||||
properties,
|
||||
@ -86,15 +80,7 @@ export const listInvoicesByPropertiesRouteHandler = async (
|
||||
|
||||
export const getInvoiceRouteHandler = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
const populateFields = [
|
||||
{ path: 'to', strictPopulate: false },
|
||||
{ path: 'from', strictPopulate: false },
|
||||
{ path: 'order' },
|
||||
{ path: 'invoiceOrderItems.taxRate' },
|
||||
{ path: 'invoiceShipments.taxRate' },
|
||||
{ path: 'invoiceOrderItems.orderItem' },
|
||||
{ path: 'invoiceShipments.shipment' },
|
||||
];
|
||||
const populateFields = ['vendor', 'customer', 'relatedOrder'];
|
||||
const result = await getObject({
|
||||
model: invoiceModel,
|
||||
id,
|
||||
@ -131,17 +117,12 @@ export const editInvoiceRouteHandler = async (req, res) => {
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
vendor: req.body.vendor,
|
||||
client: req.body.client,
|
||||
customer: req.body.customer,
|
||||
invoiceType: req.body.invoiceType,
|
||||
invoiceDate: req.body.invoiceDate,
|
||||
dueAt: req.body.dueDate,
|
||||
issuedAt: req.body.issuedAt,
|
||||
orderType: req.body.orderType,
|
||||
order: req.body.order,
|
||||
invoiceOrderItems: req.body.invoiceOrderItems,
|
||||
invoiceShipments: req.body.invoiceShipments,
|
||||
from: req.body.from,
|
||||
to: req.body.to,
|
||||
dueDate: req.body.dueDate,
|
||||
relatedOrderType: req.body.relatedOrderType,
|
||||
relatedOrder: req.body.relatedOrder,
|
||||
};
|
||||
// Create audit log before updating
|
||||
const result = await editObject({
|
||||
@ -166,7 +147,7 @@ export const editMultipleInvoicesRouteHandler = async (req, res) => {
|
||||
const updates = req.body.map((update) => ({
|
||||
_id: update._id,
|
||||
vendor: update.vendor,
|
||||
client: update.client,
|
||||
customer: update.customer,
|
||||
invoiceType: update.invoiceType,
|
||||
}));
|
||||
|
||||
@ -192,90 +173,21 @@ export const editMultipleInvoicesRouteHandler = async (req, res) => {
|
||||
};
|
||||
|
||||
export const newInvoiceRouteHandler = async (req, res) => {
|
||||
const orderItems = await listObjects({
|
||||
model: orderItemModel,
|
||||
filter: { order: req.body.order, orderType: req.body.orderType },
|
||||
});
|
||||
|
||||
const shipments = await listObjects({
|
||||
model: shipmentModel,
|
||||
filter: { order: req.body.order, orderType: req.body.orderType },
|
||||
});
|
||||
|
||||
if (orderItems.error) {
|
||||
logger.error('Error getting order items:', orderItems.error);
|
||||
return res.status(orderItems.code).send(orderItems);
|
||||
}
|
||||
|
||||
if (shipments.error) {
|
||||
logger.error('Error getting shipments:', shipments.error);
|
||||
return res.status(shipments.code).send(shipments);
|
||||
}
|
||||
|
||||
const invoiceOrderItems = orderItems
|
||||
.map((orderItem) => {
|
||||
const invoicedAmountWithTax = orderItem.invoicedAmountWithTax || 0;
|
||||
const totalAmountWithTax = orderItem.totalAmountWithTax || 0;
|
||||
const invoicedAmount = orderItem.invoicedAmount || 0;
|
||||
const totalAmount = orderItem.totalAmount || 0;
|
||||
const quantity = (orderItem.quantity || 0) - (orderItem.invoicedQuantity || 0);
|
||||
const taxRate = orderItem?.taxRate?._id;
|
||||
|
||||
if (invoicedAmountWithTax >= totalAmountWithTax || invoicedAmount >= totalAmount) {
|
||||
return null;
|
||||
}
|
||||
var finalQuantity = quantity;
|
||||
if (finalQuantity <= 0) {
|
||||
finalQuantity = 1;
|
||||
}
|
||||
return {
|
||||
orderItem: orderItem._id,
|
||||
taxRate: taxRate,
|
||||
invoiceAmountWithTax: totalAmountWithTax - invoicedAmountWithTax,
|
||||
invoiceAmount: totalAmount - invoicedAmount,
|
||||
invoiceQuantity: finalQuantity,
|
||||
};
|
||||
})
|
||||
.filter((item) => item !== null);
|
||||
|
||||
const invoiceShipments = shipments
|
||||
.map((shipment) => {
|
||||
const invoicedAmount = shipment.invoicedAmount || 0;
|
||||
const amountWithTax = shipment.amountWithTax || 0;
|
||||
const invoicedAmountWithTax = shipment.invoicedAmountWithTax || 0;
|
||||
const amount = shipment.amount || 0;
|
||||
const taxRate = shipment?.taxRate || null;
|
||||
|
||||
if (invoicedAmountWithTax >= amountWithTax || invoicedAmount >= amount) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
shipment: shipment._id,
|
||||
taxRate: taxRate,
|
||||
invoiceAmountWithTax: amountWithTax - invoicedAmountWithTax,
|
||||
invoiceAmount: amount - invoicedAmount,
|
||||
};
|
||||
})
|
||||
.filter((item) => item !== null);
|
||||
|
||||
const newData = {
|
||||
updatedAt: new Date(),
|
||||
vendor: req.body.vendor,
|
||||
client: req.body.client,
|
||||
issuedAt: req.body.issuedAt || new Date(),
|
||||
dueAt: req.body.dueAt || new Date(),
|
||||
orderType: req.body.orderType,
|
||||
order: req.body.order,
|
||||
customer: req.body.customer,
|
||||
invoiceType: req.body.invoiceType || 'sales',
|
||||
invoiceDate: req.body.invoiceDate || new Date(),
|
||||
dueDate: req.body.dueDate,
|
||||
relatedOrderType: req.body.relatedOrderType,
|
||||
relatedOrder: req.body.relatedOrder,
|
||||
totalAmount: 0,
|
||||
totalAmountWithTax: 0,
|
||||
totalTaxAmount: 0,
|
||||
grandTotalAmount: 0,
|
||||
shippingAmount: 0,
|
||||
shippingAmountWithTax: 0,
|
||||
invoiceOrderItems: invoiceOrderItems,
|
||||
invoiceShipments: invoiceShipments,
|
||||
from: req.body.from,
|
||||
to: req.body.to,
|
||||
};
|
||||
const result = await newObject({
|
||||
model: invoiceModel,
|
||||
@ -335,48 +247,7 @@ export const getInvoiceHistoryRouteHandler = async (req, res) => {
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const acknowledgeInvoiceRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`Invoice with ID: ${id}`);
|
||||
|
||||
const checkStatesResult = await checkStates({ model: invoiceModel, id, states: ['sent'] });
|
||||
|
||||
if (checkStatesResult.error) {
|
||||
logger.error('Error checking invoice states:', checkStatesResult.error);
|
||||
res.status(checkStatesResult.code).send(checkStatesResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkStatesResult === false) {
|
||||
logger.error('Invoice is not in sent state.');
|
||||
res.status(400).send({ error: 'Invoice is not in sent state.', code: 400 });
|
||||
return;
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
state: { type: 'acknowledged' },
|
||||
acknowledgedAt: new Date(),
|
||||
};
|
||||
const result = await editObject({
|
||||
model: invoiceModel,
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error acknowledging invoice:', result.error);
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Acknowledged invoice with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const postInvoiceRouteHandler = async (req, res) => {
|
||||
export const sendInvoiceRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`Invoice with ID: ${id}`);
|
||||
@ -395,98 +266,10 @@ export const postInvoiceRouteHandler = async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const invoice = await getObject({
|
||||
model: invoiceModel,
|
||||
id,
|
||||
});
|
||||
|
||||
const invoiceOrderItems = invoice.invoiceOrderItems;
|
||||
const invoiceShipments = invoice.invoiceShipments;
|
||||
|
||||
for (const invoiceOrderItem of invoiceOrderItems) {
|
||||
const orderItem = await getObject({
|
||||
model: orderItemModel,
|
||||
id: invoiceOrderItem.orderItem._id,
|
||||
});
|
||||
if (orderItem.error) {
|
||||
logger.error('Error getting order item:', orderItem.error);
|
||||
return res.status(orderItem.code).send(orderItem);
|
||||
}
|
||||
const invoiceQuantity = invoiceOrderItem.invoiceQuantity || 0;
|
||||
const invoiceAmount = invoiceOrderItem.invoiceAmount || 0;
|
||||
const invoiceAmountWithTax = invoiceOrderItem.invoiceAmountWithTax || 0;
|
||||
const invoicedQuantity = orderItem.invoicedQuantity || 0;
|
||||
const invoicedAmount = orderItem.invoicedAmount || 0;
|
||||
const invoicedAmountWithTax = orderItem.invoicedAmountWithTax || 0;
|
||||
var quantity = (orderItem.invoiceQuantity || 0) + invoiceQuantity;
|
||||
if (quantity <= orderItem.quantity) {
|
||||
quantity = orderItem.quantity;
|
||||
}
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
invoicedQuantity: invoicedQuantity + invoiceQuantity,
|
||||
invoicedAmount: invoicedAmount + invoiceAmount,
|
||||
invoicedAmountWithTax: invoicedAmountWithTax + invoiceAmountWithTax,
|
||||
};
|
||||
const result = await editObject({
|
||||
model: orderItemModel,
|
||||
id: orderItem._id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
});
|
||||
if (result.error) {
|
||||
logger.error('Error updating order item:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.debug(`Updated order item with ID: ${orderItem._id}`);
|
||||
}
|
||||
|
||||
for (const invoiceShipment of invoiceShipments) {
|
||||
const shipmentId = invoiceShipment.shipment._id || invoiceShipment.shipment;
|
||||
const shipment = await getObject({
|
||||
model: shipmentModel,
|
||||
id: shipmentId,
|
||||
});
|
||||
if (shipment.error) {
|
||||
logger.error('Error getting shipment:', shipment.error);
|
||||
return res.status(shipment.code).send(shipment);
|
||||
}
|
||||
const invoiceAmount = invoiceShipment.invoiceAmount || 0;
|
||||
const invoiceAmountWithTax = invoiceShipment.invoiceAmountWithTax || 0;
|
||||
const invoicedAmount = shipment.invoicedAmount || 0;
|
||||
const invoicedAmountWithTax = shipment.invoicedAmountWithTax || 0;
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
invoicedAmount: invoicedAmount + invoiceAmount,
|
||||
invoicedAmountWithTax: invoicedAmountWithTax + invoiceAmountWithTax,
|
||||
};
|
||||
const result = await editObject({
|
||||
model: shipmentModel,
|
||||
id: shipment._id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
});
|
||||
if (result.error) {
|
||||
logger.error('Error updating shipment:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.debug(`Updated shipment with ID: ${shipment._id}`);
|
||||
}
|
||||
|
||||
if (invoiceOrderItems.error) {
|
||||
logger.error('Error getting invoice order items:', invoiceOrderItems.error);
|
||||
return res.status(invoiceOrderItems.code).send(invoiceOrderItems);
|
||||
}
|
||||
|
||||
if (invoiceShipments.error) {
|
||||
logger.error('Error getting invoice shipments:', invoiceShipments.error);
|
||||
return res.status(invoiceShipments.code).send(invoiceShipments);
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
state: { type: 'sent' },
|
||||
postedAt: new Date(),
|
||||
sentAt: new Date(),
|
||||
};
|
||||
const result = await editObject({
|
||||
model: invoiceModel,
|
||||
@ -496,12 +279,57 @@ export const postInvoiceRouteHandler = async (req, res) => {
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error posting invoice:', result.error);
|
||||
logger.error('Error sending invoice:', result.error);
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Posted invoice with ID: ${id}`);
|
||||
logger.debug(`Sent invoice with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const markInvoicePaidRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`Invoice with ID: ${id}`);
|
||||
|
||||
const checkStatesResult = await checkStates({
|
||||
model: invoiceModel,
|
||||
id,
|
||||
states: ['sent', 'partiallyPaid'],
|
||||
});
|
||||
|
||||
if (checkStatesResult.error) {
|
||||
logger.error('Error checking invoice states:', checkStatesResult.error);
|
||||
res.status(checkStatesResult.code).send(checkStatesResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkStatesResult === false) {
|
||||
logger.error('Invoice is not in sent or partially paid state.');
|
||||
res.status(400).send({ error: 'Invoice is not in sent or partially paid state.', code: 400 });
|
||||
return;
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
state: { type: 'paid' },
|
||||
paidAt: new Date(),
|
||||
};
|
||||
const result = await editObject({
|
||||
model: invoiceModel,
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error marking invoice as paid:', result.error);
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Marked invoice as paid with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
|
||||
@ -1,373 +0,0 @@
|
||||
import config from '../../config.js';
|
||||
import { paymentModel } from '../../database/schemas/finance/payment.schema.js';
|
||||
import log4js from 'log4js';
|
||||
import mongoose from 'mongoose';
|
||||
import {
|
||||
deleteObject,
|
||||
listObjects,
|
||||
getObject,
|
||||
editObject,
|
||||
editObjects,
|
||||
newObject,
|
||||
listObjectsByProperties,
|
||||
getModelStats,
|
||||
getModelHistory,
|
||||
checkStates,
|
||||
} from '../../database/database.js';
|
||||
import { invoiceModel } from '../../database/schemas/finance/invoice.schema.js';
|
||||
|
||||
const logger = log4js.getLogger('Payments');
|
||||
logger.level = config.server.logLevel;
|
||||
|
||||
export const listPaymentsRouteHandler = async (
|
||||
req,
|
||||
res,
|
||||
page = 1,
|
||||
limit = 25,
|
||||
property = '',
|
||||
filter = {},
|
||||
search = '',
|
||||
sort = '',
|
||||
order = 'ascend'
|
||||
) => {
|
||||
const populateFields = ['vendor', 'client', 'invoice'];
|
||||
const result = await listObjects({
|
||||
model: paymentModel,
|
||||
page,
|
||||
limit,
|
||||
property,
|
||||
filter,
|
||||
search,
|
||||
sort,
|
||||
order,
|
||||
populate: populateFields,
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
logger.error('Error listing payments.');
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`List of payments (Page ${page}, Limit ${limit}). Count: ${result.length}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const listPaymentsByPropertiesRouteHandler = async (
|
||||
req,
|
||||
res,
|
||||
properties = '',
|
||||
filter = {},
|
||||
masterFilter = {}
|
||||
) => {
|
||||
const populateFields = ['vendor', 'client', 'invoice'];
|
||||
const result = await listObjectsByProperties({
|
||||
model: paymentModel,
|
||||
properties,
|
||||
filter,
|
||||
populate: populateFields,
|
||||
masterFilter,
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
logger.error('Error listing payments.');
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`List of payments. Count: ${result.length}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getPaymentRouteHandler = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
const populateFields = [
|
||||
{ path: 'vendor' },
|
||||
{ path: 'client' },
|
||||
{ path: 'invoice' },
|
||||
];
|
||||
const result = await getObject({
|
||||
model: paymentModel,
|
||||
id,
|
||||
populate: populateFields,
|
||||
});
|
||||
if (result?.error) {
|
||||
logger.warn(`Payment not found with supplied id.`);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.debug(`Retrieved payment with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const editPaymentRouteHandler = async (req, res) => {
|
||||
// Get ID from params
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`Payment with ID: ${id}`);
|
||||
|
||||
const checkStatesResult = await checkStates({ model: paymentModel, id, states: ['draft'] });
|
||||
|
||||
if (checkStatesResult.error) {
|
||||
logger.error('Error checking payment states:', checkStatesResult.error);
|
||||
res.status(checkStatesResult.code).send(checkStatesResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkStatesResult === false) {
|
||||
logger.error('Payment is not in draft state.');
|
||||
res.status(400).send({ error: 'Payment is not in draft state.', code: 400 });
|
||||
return;
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
vendor: req.body.vendor,
|
||||
client: req.body.client,
|
||||
invoice: req.body.invoice,
|
||||
amount: req.body.amount,
|
||||
paymentDate: req.body.paymentDate,
|
||||
paymentMethod: req.body.paymentMethod,
|
||||
notes: req.body.notes,
|
||||
};
|
||||
// Create audit log before updating
|
||||
const result = await editObject({
|
||||
model: paymentModel,
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error editing payment:', result.error);
|
||||
res.status(result).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Edited payment with ID: ${id}`);
|
||||
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const editMultiplePaymentsRouteHandler = async (req, res) => {
|
||||
const updates = req.body.map((update) => ({
|
||||
_id: update._id,
|
||||
vendor: update.vendor,
|
||||
client: update.client,
|
||||
invoice: update.invoice,
|
||||
}));
|
||||
|
||||
if (!Array.isArray(updates)) {
|
||||
return res.status(400).send({ error: 'Body must be an array of updates.', code: 400 });
|
||||
}
|
||||
|
||||
const result = await editObjects({
|
||||
model: paymentModel,
|
||||
updates,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error editing payments:', result.error);
|
||||
res.status(result.code || 500).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Edited ${updates.length} payments`);
|
||||
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const newPaymentRouteHandler = async (req, res) => {
|
||||
// Get invoice to populate vendor/client
|
||||
const invoice = await getObject({
|
||||
model: invoiceModel,
|
||||
id: req.body.invoice,
|
||||
populate: [{ path: 'vendor' }, { path: 'client' }],
|
||||
});
|
||||
|
||||
if (invoice.error) {
|
||||
logger.error('Error getting invoice:', invoice.error);
|
||||
return res.status(invoice.code).send(invoice);
|
||||
}
|
||||
|
||||
const newData = {
|
||||
updatedAt: new Date(),
|
||||
vendor: invoice.vendor?._id || req.body.vendor,
|
||||
client: invoice.client?._id || req.body.client,
|
||||
invoice: req.body.invoice,
|
||||
amount: req.body.amount || 0,
|
||||
paymentDate: req.body.paymentDate || new Date(),
|
||||
paymentMethod: req.body.paymentMethod,
|
||||
notes: req.body.notes,
|
||||
};
|
||||
const result = await newObject({
|
||||
model: paymentModel,
|
||||
newData,
|
||||
user: req.user,
|
||||
});
|
||||
if (result.error) {
|
||||
logger.error('No payment created:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
|
||||
logger.debug(`New payment with ID: ${result._id}`);
|
||||
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const deletePaymentRouteHandler = async (req, res) => {
|
||||
// Get ID from params
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`Payment with ID: ${id}`);
|
||||
|
||||
const result = await deleteObject({
|
||||
model: paymentModel,
|
||||
id,
|
||||
user: req.user,
|
||||
});
|
||||
if (result.error) {
|
||||
logger.error('No payment deleted:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
|
||||
logger.debug(`Deleted payment with ID: ${result._id}`);
|
||||
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getPaymentStatsRouteHandler = async (req, res) => {
|
||||
const result = await getModelStats({ model: paymentModel });
|
||||
if (result?.error) {
|
||||
logger.error('Error fetching payment stats:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.trace('Payment stats:', result);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getPaymentHistoryRouteHandler = async (req, res) => {
|
||||
const from = req.query.from;
|
||||
const to = req.query.to;
|
||||
const result = await getModelHistory({ model: paymentModel, from, to });
|
||||
if (result?.error) {
|
||||
logger.error('Error fetching payment history:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.trace('Payment history:', result);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const postPaymentRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`Payment with ID: ${id}`);
|
||||
|
||||
const checkStatesResult = await checkStates({ model: paymentModel, id, states: ['draft'] });
|
||||
|
||||
if (checkStatesResult.error) {
|
||||
logger.error('Error checking payment states:', checkStatesResult.error);
|
||||
res.status(checkStatesResult.code).send(checkStatesResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkStatesResult === false) {
|
||||
logger.error('Payment is not in draft state.');
|
||||
res.status(400).send({ error: 'Payment is not in draft state.', code: 400 });
|
||||
return;
|
||||
}
|
||||
|
||||
const payment = await getObject({
|
||||
model: paymentModel,
|
||||
id,
|
||||
populate: [{ path: 'invoice' }],
|
||||
});
|
||||
|
||||
if (payment.error) {
|
||||
logger.error('Error getting payment:', payment.error);
|
||||
return res.status(payment.code).send(payment);
|
||||
}
|
||||
|
||||
// Update invoice paid amounts if needed
|
||||
if (payment.invoice) {
|
||||
const invoice = await getObject({
|
||||
model: invoiceModel,
|
||||
id: payment.invoice._id || payment.invoice,
|
||||
});
|
||||
|
||||
if (!invoice.error) {
|
||||
// You can add logic here to update invoice paid amounts
|
||||
// This is a simplified version - adjust based on your business logic
|
||||
}
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
state: { type: 'posted' },
|
||||
postedAt: new Date(),
|
||||
};
|
||||
const result = await editObject({
|
||||
model: paymentModel,
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error posting payment:', result.error);
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Posted payment with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const cancelPaymentRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`Payment with ID: ${id}`);
|
||||
|
||||
const checkStatesResult = await checkStates({
|
||||
model: paymentModel,
|
||||
id,
|
||||
states: ['draft', 'posted'],
|
||||
});
|
||||
|
||||
if (checkStatesResult.error) {
|
||||
logger.error('Error checking payment states:', checkStatesResult.error);
|
||||
res.status(checkStatesResult.code).send(checkStatesResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkStatesResult === false) {
|
||||
logger.error('Payment is not in a cancellable state.');
|
||||
res.status(400).send({
|
||||
error: 'Payment is not in a cancellable state (must be draft or posted).',
|
||||
code: 400,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
state: { type: 'cancelled' },
|
||||
cancelledAt: new Date(),
|
||||
};
|
||||
const result = await editObject({
|
||||
model: paymentModel,
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error cancelling payment:', result.error);
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Cancelled payment with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
@ -135,7 +135,6 @@ export const editOrderItemRouteHandler = async (req, res) => {
|
||||
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
name: req.body.name,
|
||||
itemType: req.body.itemType,
|
||||
item: req.body.item,
|
||||
orderType: req.body.orderType,
|
||||
@ -170,7 +169,6 @@ export const editOrderItemRouteHandler = async (req, res) => {
|
||||
export const editMultipleOrderItemsRouteHandler = async (req, res) => {
|
||||
const updates = req.body.map((update) => ({
|
||||
_id: update._id,
|
||||
name: update.name,
|
||||
itemType: update.itemType,
|
||||
item: update.item,
|
||||
orderType: update.orderType,
|
||||
@ -208,7 +206,6 @@ export const editMultipleOrderItemsRouteHandler = async (req, res) => {
|
||||
export const newOrderItemRouteHandler = async (req, res) => {
|
||||
const newData = {
|
||||
updatedAt: new Date(),
|
||||
name: req.body.name,
|
||||
purchaseOrder: req.body.purchaseOrder,
|
||||
state: { type: 'draft' },
|
||||
itemType: req.body.itemType,
|
||||
|
||||
@ -1,189 +0,0 @@
|
||||
import config from '../../config.js';
|
||||
import { clientModel } from '../../database/schemas/sales/client.schema.js';
|
||||
import log4js from 'log4js';
|
||||
import mongoose from 'mongoose';
|
||||
import {
|
||||
deleteObject,
|
||||
listObjects,
|
||||
getObject,
|
||||
editObject,
|
||||
newObject,
|
||||
listObjectsByProperties,
|
||||
getModelStats,
|
||||
getModelHistory,
|
||||
} from '../../database/database.js';
|
||||
const logger = log4js.getLogger('Clients');
|
||||
logger.level = config.server.logLevel;
|
||||
|
||||
export const listClientsRouteHandler = async (
|
||||
req,
|
||||
res,
|
||||
page = 1,
|
||||
limit = 25,
|
||||
property = '',
|
||||
filter = {},
|
||||
search = '',
|
||||
sort = '',
|
||||
order = 'ascend'
|
||||
) => {
|
||||
const result = await listObjects({
|
||||
model: clientModel,
|
||||
page,
|
||||
limit,
|
||||
property,
|
||||
filter,
|
||||
search,
|
||||
sort,
|
||||
order,
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
logger.error('Error listing clients.');
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`List of clients (Page ${page}, Limit ${limit}). Count: ${result.length}.`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const listClientsByPropertiesRouteHandler = async (
|
||||
req,
|
||||
res,
|
||||
properties = '',
|
||||
filter = {}
|
||||
) => {
|
||||
const result = await listObjectsByProperties({
|
||||
model: clientModel,
|
||||
properties,
|
||||
filter,
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
logger.error('Error listing clients.');
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`List of clients. Count: ${result.length}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getClientRouteHandler = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
const result = await getObject({
|
||||
model: clientModel,
|
||||
id,
|
||||
});
|
||||
if (result?.error) {
|
||||
logger.warn(`Client not found with supplied id.`);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.debug(`Retreived client with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const editClientRouteHandler = async (req, res) => {
|
||||
// Get ID from params
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`Client with ID: ${id}`);
|
||||
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
country: req.body.country,
|
||||
name: req.body.name,
|
||||
phone: req.body.phone,
|
||||
email: req.body.email,
|
||||
address: req.body.address,
|
||||
active: req.body.active,
|
||||
tags: req.body.tags,
|
||||
};
|
||||
// Create audit log before updating
|
||||
const result = await editObject({
|
||||
model: clientModel,
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error editing client:', result.error);
|
||||
res.status(result).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Edited client with ID: ${id}`);
|
||||
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const newClientRouteHandler = async (req, res) => {
|
||||
const newData = {
|
||||
updatedAt: new Date(),
|
||||
country: req.body.country,
|
||||
name: req.body.name,
|
||||
phone: req.body.phone,
|
||||
email: req.body.email,
|
||||
address: req.body.address,
|
||||
active: req.body.active,
|
||||
tags: req.body.tags,
|
||||
};
|
||||
const result = await newObject({
|
||||
model: clientModel,
|
||||
newData,
|
||||
user: req.user,
|
||||
});
|
||||
if (result.error) {
|
||||
logger.error('No client created:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
|
||||
logger.debug(`New client with ID: ${result._id}`);
|
||||
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const deleteClientRouteHandler = async (req, res) => {
|
||||
// Get ID from params
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`Client with ID: ${id}`);
|
||||
|
||||
const result = await deleteObject({
|
||||
model: clientModel,
|
||||
id,
|
||||
user: req.user,
|
||||
});
|
||||
if (result.error) {
|
||||
logger.error('No client deleted:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
|
||||
logger.debug(`Deleted client with ID: ${result._id}`);
|
||||
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getClientStatsRouteHandler = async (req, res) => {
|
||||
const result = await getModelStats({ model: clientModel });
|
||||
if (result?.error) {
|
||||
logger.error('Error fetching client stats:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.trace('Client stats:', result);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getClientHistoryRouteHandler = async (req, res) => {
|
||||
const from = req.query.from;
|
||||
const to = req.query.to;
|
||||
const result = await getModelHistory({ model: clientModel, from, to });
|
||||
if (result?.error) {
|
||||
logger.error('Error fetching client history:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.trace('Client history:', result);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
@ -1,460 +0,0 @@
|
||||
import config from '../../config.js';
|
||||
import { salesOrderModel } from '../../database/schemas/sales/salesorder.schema.js';
|
||||
import log4js from 'log4js';
|
||||
import mongoose from 'mongoose';
|
||||
import {
|
||||
deleteObject,
|
||||
listObjects,
|
||||
getObject,
|
||||
editObject,
|
||||
editObjects,
|
||||
newObject,
|
||||
listObjectsByProperties,
|
||||
getModelStats,
|
||||
getModelHistory,
|
||||
checkStates,
|
||||
} from '../../database/database.js';
|
||||
import { orderItemModel } from '../../database/schemas/inventory/orderitem.schema.js';
|
||||
import { shipmentModel } from '../../database/schemas/inventory/shipment.schema.js';
|
||||
|
||||
const logger = log4js.getLogger('Sales Orders');
|
||||
logger.level = config.server.logLevel;
|
||||
|
||||
export const listSalesOrdersRouteHandler = async (
|
||||
req,
|
||||
res,
|
||||
page = 1,
|
||||
limit = 25,
|
||||
property = '',
|
||||
filter = {},
|
||||
search = '',
|
||||
sort = '',
|
||||
order = 'ascend'
|
||||
) => {
|
||||
const result = await listObjects({
|
||||
model: salesOrderModel,
|
||||
page,
|
||||
limit,
|
||||
property,
|
||||
filter,
|
||||
search,
|
||||
sort,
|
||||
order,
|
||||
populate: ['client'],
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
logger.error('Error listing sales orders.');
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`List of sales orders (Page ${page}, Limit ${limit}). Count: ${result.length}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const listSalesOrdersByPropertiesRouteHandler = async (
|
||||
req,
|
||||
res,
|
||||
properties = '',
|
||||
filter = {},
|
||||
masterFilter = {}
|
||||
) => {
|
||||
const result = await listObjectsByProperties({
|
||||
model: salesOrderModel,
|
||||
properties,
|
||||
filter,
|
||||
populate: ['client'],
|
||||
masterFilter,
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
logger.error('Error listing sales orders.');
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`List of sales orders. Count: ${result.length}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getSalesOrderRouteHandler = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
const result = await getObject({
|
||||
model: salesOrderModel,
|
||||
id,
|
||||
populate: ['client'],
|
||||
});
|
||||
if (result?.error) {
|
||||
logger.warn(`Sales Order not found with supplied id.`);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.debug(`Retreived sales order with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const editSalesOrderRouteHandler = async (req, res) => {
|
||||
// Get ID from params
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`Sales Order with ID: ${id}`);
|
||||
|
||||
const checkStatesResult = await checkStates({ model: salesOrderModel, id, states: ['draft'] });
|
||||
|
||||
if (checkStatesResult.error) {
|
||||
logger.error('Error checking sales order states:', checkStatesResult.error);
|
||||
res.status(checkStatesResult.code).send(checkStatesResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkStatesResult === false) {
|
||||
logger.error('Sales order is not in draft state.');
|
||||
res.status(400).send({ error: 'Sales order is not in draft state.', code: 400 });
|
||||
return;
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
client: req.body.client,
|
||||
};
|
||||
// Create audit log before updating
|
||||
const result = await editObject({
|
||||
model: salesOrderModel,
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error editing sales order:', result.error);
|
||||
res.status(result).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Edited sales order with ID: ${id}`);
|
||||
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const editMultipleSalesOrdersRouteHandler = async (req, res) => {
|
||||
const updates = req.body.map((update) => ({
|
||||
_id: update._id,
|
||||
client: update.client,
|
||||
}));
|
||||
|
||||
if (!Array.isArray(updates)) {
|
||||
return res.status(400).send({ error: 'Body must be an array of updates.', code: 400 });
|
||||
}
|
||||
|
||||
const result = await editObjects({
|
||||
model: salesOrderModel,
|
||||
updates,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error editing sales orders:', result.error);
|
||||
res.status(result.code || 500).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Edited ${updates.length} sales orders`);
|
||||
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const newSalesOrderRouteHandler = async (req, res) => {
|
||||
const newData = {
|
||||
updatedAt: new Date(),
|
||||
client: req.body.client,
|
||||
totalAmount: 0,
|
||||
totalAmountWithTax: 0,
|
||||
totalTaxAmount: 0,
|
||||
};
|
||||
const result = await newObject({
|
||||
model: salesOrderModel,
|
||||
newData,
|
||||
user: req.user,
|
||||
});
|
||||
if (result.error) {
|
||||
logger.error('No sales order created:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
|
||||
logger.debug(`New sales order with ID: ${result._id}`);
|
||||
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const deleteSalesOrderRouteHandler = async (req, res) => {
|
||||
// Get ID from params
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`Sales Order with ID: ${id}`);
|
||||
|
||||
const result = await deleteObject({
|
||||
model: salesOrderModel,
|
||||
id,
|
||||
user: req.user,
|
||||
});
|
||||
if (result.error) {
|
||||
logger.error('No sales order deleted:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
|
||||
logger.debug(`Deleted sales order with ID: ${result._id}`);
|
||||
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getSalesOrderStatsRouteHandler = async (req, res) => {
|
||||
const result = await getModelStats({ model: salesOrderModel });
|
||||
if (result?.error) {
|
||||
logger.error('Error fetching sales order stats:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.trace('Sales order stats:', result);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getSalesOrderHistoryRouteHandler = async (req, res) => {
|
||||
const from = req.query.from;
|
||||
const to = req.query.to;
|
||||
const result = await getModelHistory({ model: salesOrderModel, from, to });
|
||||
if (result?.error) {
|
||||
logger.error('Error fetching sales order history:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.trace('Sales order history:', result);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const postSalesOrderRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`Sales Order with ID: ${id}`);
|
||||
|
||||
const checkStatesResult = await checkStates({ model: salesOrderModel, id, states: ['draft'] });
|
||||
|
||||
if (checkStatesResult.error) {
|
||||
logger.error('Error checking sales order states:', checkStatesResult.error);
|
||||
res.status(checkStatesResult.code).send(checkStatesResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkStatesResult === false) {
|
||||
logger.error('Sales order is not in draft state.');
|
||||
res.status(400).send({ error: 'Sales order is not in draft state.', code: 400 });
|
||||
return;
|
||||
}
|
||||
|
||||
const orderItemsResult = await listObjects({
|
||||
model: orderItemModel,
|
||||
filter: { order: id, orderType: 'salesOrder' },
|
||||
pagination: false,
|
||||
});
|
||||
|
||||
const shipmentsResult = await listObjects({
|
||||
model: shipmentModel,
|
||||
filter: { order: id, orderType: 'salesOrder' },
|
||||
pagination: false,
|
||||
});
|
||||
|
||||
for (const orderItem of orderItemsResult) {
|
||||
if (orderItem.state.type != 'draft') {
|
||||
logger.warn(`Order item ${orderItem._id} is not in draft state.`);
|
||||
return res
|
||||
.status(400)
|
||||
.send({ error: `Order item ${orderItem._reference} not in draft state.`, code: 400 });
|
||||
}
|
||||
if (!orderItem?.shipment || orderItem?.shipment == null) {
|
||||
logger.warn(`Order item ${orderItem._id} does not have a shipment.`);
|
||||
return res
|
||||
.status(400)
|
||||
.send({ error: `Order item ${orderItem._reference} does not have a shipment.`, code: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
for (const shipment of shipmentsResult) {
|
||||
if (shipment.state.type != 'draft') {
|
||||
logger.warn(`Shipment ${shipment._id} is not in draft state.`);
|
||||
return res
|
||||
.status(400)
|
||||
.send({ error: `Shipment ${shipment._reference} not in draft state.`, code: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
for (const orderItem of orderItemsResult) {
|
||||
await editObject({
|
||||
model: orderItemModel,
|
||||
id: orderItem._id,
|
||||
updateData: {
|
||||
state: { type: 'ordered' },
|
||||
orderedAt: new Date(),
|
||||
},
|
||||
user: req.user,
|
||||
});
|
||||
}
|
||||
|
||||
for (const shipment of shipmentsResult) {
|
||||
await editObject({
|
||||
model: shipmentModel,
|
||||
id: shipment._id,
|
||||
updateData: {
|
||||
state: { type: 'planned' },
|
||||
},
|
||||
user: req.user,
|
||||
});
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
state: { type: 'sent' },
|
||||
postedAt: new Date(),
|
||||
};
|
||||
const result = await editObject({
|
||||
model: salesOrderModel,
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error posting sales order:', result.error);
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Posted sales order with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const confirmSalesOrderRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`Sales Order with ID: ${id}`);
|
||||
|
||||
const checkStatesResult = await checkStates({ model: salesOrderModel, id, states: ['sent'] });
|
||||
|
||||
if (checkStatesResult.error) {
|
||||
logger.error('Error checking sales order states:', checkStatesResult.error);
|
||||
res.status(checkStatesResult.code).send(checkStatesResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkStatesResult === false) {
|
||||
logger.error('Sales order is not in sent state.');
|
||||
res.status(400).send({ error: 'Sales order is not in sent state.', code: 400 });
|
||||
return;
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
state: { type: 'confirmed' },
|
||||
confirmedAt: new Date(),
|
||||
};
|
||||
const result = await editObject({
|
||||
model: salesOrderModel,
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error confirming sales order:', result.error);
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Confirmed sales order with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const cancelSalesOrderRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`Sales Order with ID: ${id}`);
|
||||
|
||||
const checkStatesResult = await checkStates({
|
||||
model: salesOrderModel,
|
||||
id,
|
||||
states: ['sent', 'confirmed', 'partiallyShipped', 'shipped', 'partiallyDelivered'],
|
||||
});
|
||||
|
||||
if (checkStatesResult.error) {
|
||||
logger.error('Error checking sales order states:', checkStatesResult.error);
|
||||
res.status(checkStatesResult.code).send(checkStatesResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkStatesResult === false) {
|
||||
logger.error('Sales order is not in a cancellable state.');
|
||||
res.status(400).send({
|
||||
error: 'Sales order is not in a cancellable state (must be sent, confirmed, partiallyShipped, shipped, or partiallyDelivered).',
|
||||
code: 400,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const orderItemsResult = await listObjects({
|
||||
model: orderItemModel,
|
||||
filter: { order: id, orderType: 'salesOrder' },
|
||||
pagination: false,
|
||||
});
|
||||
|
||||
const shipmentsResult = await listObjects({
|
||||
model: shipmentModel,
|
||||
filter: { order: id, orderType: 'salesOrder' },
|
||||
pagination: false,
|
||||
});
|
||||
|
||||
const allowedOrderItemStates = ['ordered', 'shipped'];
|
||||
const allowedShipmentStates = ['shipped', 'planned'];
|
||||
|
||||
for (const orderItem of orderItemsResult) {
|
||||
if (allowedOrderItemStates.includes(orderItem.state.type)) {
|
||||
await editObject({
|
||||
model: orderItemModel,
|
||||
id: orderItem._id,
|
||||
updateData: {
|
||||
state: { type: 'cancelled' },
|
||||
},
|
||||
user: req.user,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const shipment of shipmentsResult) {
|
||||
if (allowedShipmentStates.includes(shipment.state.type)) {
|
||||
await editObject({
|
||||
model: shipmentModel,
|
||||
id: shipment._id,
|
||||
updateData: {
|
||||
state: { type: 'cancelled' },
|
||||
},
|
||||
user: req.user,
|
||||
});
|
||||
}
|
||||
}
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
state: { type: 'cancelled' },
|
||||
cancelledAt: new Date(),
|
||||
};
|
||||
const result = await editObject({
|
||||
model: salesOrderModel,
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error cancelling sales order:', result.error);
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Cancelled sales order with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user