Compare commits
No commits in common. "a7e35c279eb1071b37c137e706120098c823b7e5" and "dea6a90b68bdcf3d144e18ebe59a5b618e19eeb5" have entirely different histories.
a7e35c279e
...
dea6a90b68
@ -1,28 +1,7 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
import { generateId } from '../../utils.js';
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
import { aggregateRollups, aggregateRollupsHistory, editObject } from '../../database.js';
|
import { aggregateRollups, aggregateRollupsHistory } 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(
|
const invoiceSchema = new Schema(
|
||||||
{
|
{
|
||||||
@ -33,21 +12,21 @@ const invoiceSchema = new Schema(
|
|||||||
shippingAmountWithTax: { type: Number, required: true, default: 0 },
|
shippingAmountWithTax: { type: Number, required: true, default: 0 },
|
||||||
grandTotalAmount: { type: Number, required: true, default: 0 },
|
grandTotalAmount: { type: Number, required: true, default: 0 },
|
||||||
totalTaxAmount: { type: Number, required: true, default: 0 },
|
totalTaxAmount: { type: Number, required: true, default: 0 },
|
||||||
from: { type: Schema.Types.ObjectId, ref: 'vendor', required: false },
|
timestamp: { type: Date, default: Date.now },
|
||||||
to: { type: Schema.Types.ObjectId, ref: 'client', required: false },
|
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: {
|
state: {
|
||||||
type: { type: String, required: true, default: 'draft' },
|
type: { type: String, required: true, default: 'draft' },
|
||||||
},
|
},
|
||||||
orderType: { type: String, required: true },
|
sentAt: { type: Date, required: false },
|
||||||
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 },
|
paidAt: { type: Date, required: false },
|
||||||
cancelledAt: { type: Date, required: false },
|
cancelledAt: { type: Date, required: false },
|
||||||
invoiceOrderItems: [invoiceOrderItemSchema],
|
overdueAt: { type: Date, required: false },
|
||||||
invoiceShipments: [invoiceShipmentSchema],
|
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
);
|
);
|
||||||
@ -56,49 +35,32 @@ const rollupConfigs = [
|
|||||||
{
|
{
|
||||||
name: 'draft',
|
name: 'draft',
|
||||||
filter: { 'state.type': 'draft' },
|
filter: { 'state.type': 'draft' },
|
||||||
rollups: [
|
rollups: [{ name: 'draft', property: 'state.type', operation: 'count' }],
|
||||||
{ name: 'draftCount', property: 'state.type', operation: 'count' },
|
|
||||||
{ name: 'draftGrandTotalAmount', property: 'grandTotalAmount', operation: 'sum' },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'sent',
|
name: 'sent',
|
||||||
filter: { 'state.type': 'sent' },
|
filter: { 'state.type': 'sent' },
|
||||||
rollups: [
|
rollups: [{ name: 'sent', property: 'state.type', operation: 'count' }],
|
||||||
{ 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',
|
name: 'partiallyPaid',
|
||||||
filter: { 'state.type': 'partiallyPaid' },
|
filter: { 'state.type': 'partiallyPaid' },
|
||||||
rollups: [
|
rollups: [{ name: 'partiallyPaid', property: 'state.type', operation: 'count' }],
|
||||||
{ name: 'partiallyPaidCount', property: 'state.type', operation: 'count' },
|
|
||||||
{ name: 'partiallyPaidGrandTotalAmount', property: 'grandTotalAmount', operation: 'sum' },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'paid',
|
name: 'paid',
|
||||||
filter: { 'state.type': 'paid' },
|
filter: { 'state.type': 'paid' },
|
||||||
rollups: [{ name: 'paidCount', property: 'state.type', operation: 'count' }],
|
rollups: [{ name: 'paid', property: 'state.type', operation: 'count' }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'overdue',
|
name: 'overdue',
|
||||||
filter: { 'state.type': 'overdue' },
|
filter: { 'state.type': 'overdue' },
|
||||||
rollups: [{ name: 'overdueCount', property: 'state.type', operation: 'count' }],
|
rollups: [{ name: 'overdue', property: 'state.type', operation: 'count' }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'cancelled',
|
name: 'cancelled',
|
||||||
filter: { 'state.type': '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;
|
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
|
// Add virtual id getter
|
||||||
invoiceSchema.virtual('id').get(function () {
|
invoiceSchema.virtual('id').get(function () {
|
||||||
return this._id;
|
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()() },
|
_reference: { type: String, default: () => generateId()() },
|
||||||
orderType: { type: String, required: true },
|
orderType: { type: String, required: true },
|
||||||
name: { type: String, required: true },
|
|
||||||
state: {
|
state: {
|
||||||
type: { type: String, required: true, default: 'draft' },
|
type: { type: String, required: true, default: 'draft' },
|
||||||
},
|
},
|
||||||
@ -27,12 +26,6 @@ const orderItemSchema = new Schema(
|
|||||||
totalAmount: { type: Number, required: true },
|
totalAmount: { type: Number, required: true },
|
||||||
taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
|
taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
|
||||||
totalAmountWithTax: { type: Number, required: true },
|
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 },
|
timestamp: { type: Date, default: Date.now },
|
||||||
shipment: { type: Schema.Types.ObjectId, ref: 'shipment', required: false },
|
shipment: { type: Schema.Types.ObjectId, ref: 'shipment', required: false },
|
||||||
orderedAt: { type: Date, required: false },
|
orderedAt: { type: Date, required: false },
|
||||||
@ -104,9 +97,6 @@ orderItemSchema.statics.recalculate = async function (orderItem, user) {
|
|||||||
model: orderItemModel,
|
model: orderItemModel,
|
||||||
id: orderItem._id,
|
id: orderItem._id,
|
||||||
updateData: {
|
updateData: {
|
||||||
invoicedAmountRemaining: orderTotalAmount - orderItem.invoicedAmount,
|
|
||||||
invoicedAmountWithTaxRemaining: orderTotalAmountWithTax - orderItem.invoicedAmountWithTax,
|
|
||||||
invoicedQuantityRemaining: orderItem.quantity - orderItem.invoicedQuantity,
|
|
||||||
totalAmount: orderTotalAmount,
|
totalAmount: orderTotalAmount,
|
||||||
totalAmountWithTax: orderTotalAmountWithTax,
|
totalAmountWithTax: orderTotalAmountWithTax,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -15,10 +15,6 @@ const shipmentSchema = new Schema(
|
|||||||
amount: { type: Number, required: true },
|
amount: { type: Number, required: true },
|
||||||
amountWithTax: { type: Number, required: true },
|
amountWithTax: { type: Number, required: true },
|
||||||
taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
|
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 },
|
shippedAt: { type: Date, required: false },
|
||||||
expectedAt: { type: Date, required: false },
|
expectedAt: { type: Date, required: false },
|
||||||
deliveredAt: { type: Date, required: false },
|
deliveredAt: { type: Date, required: false },
|
||||||
@ -54,16 +50,12 @@ shipmentSchema.statics.recalculate = async function (shipment, user) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const amountWithTax = parseFloat(
|
const amountWithTax = shipment.amount * (1 + (taxRate?.rate || 0) / 100);
|
||||||
(shipment.amount || 0) * (1 + (taxRate?.rate || 0) / 100)
|
|
||||||
).toFixed(2);
|
|
||||||
await editObject({
|
await editObject({
|
||||||
model: shipmentModel,
|
model: shipmentModel,
|
||||||
id: shipment._id,
|
id: shipment._id,
|
||||||
updateData: {
|
updateData: {
|
||||||
amountWithTax: amountWithTax,
|
amountWithTax: amountWithTax,
|
||||||
invoicedAmountRemaining: shipment.amount - (shipment.invoicedAmount || 0),
|
|
||||||
invoicedAmountWithTaxRemaining: amountWithTax - (shipment.invoicedAmountWithTax || 0),
|
|
||||||
},
|
},
|
||||||
user,
|
user,
|
||||||
recalculate: false,
|
recalculate: false,
|
||||||
|
|||||||
@ -28,8 +28,6 @@ import { taxRateModel } from './management/taxrate.schema.js';
|
|||||||
import { taxRecordModel } from './management/taxrecord.schema.js';
|
import { taxRecordModel } from './management/taxrecord.schema.js';
|
||||||
import { shipmentModel } from './inventory/shipment.schema.js';
|
import { shipmentModel } from './inventory/shipment.schema.js';
|
||||||
import { invoiceModel } from './finance/invoice.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
|
// Map prefixes to models and id fields
|
||||||
export const models = {
|
export const models = {
|
||||||
@ -104,6 +102,4 @@ export const models = {
|
|||||||
TXD: { model: taxRecordModel, idField: '_id', type: 'taxRecord', referenceField: '_reference' },
|
TXD: { model: taxRecordModel, idField: '_id', type: 'taxRecord', referenceField: '_reference' },
|
||||||
SHP: { model: shipmentModel, idField: '_id', type: 'shipment', referenceField: '_reference' },
|
SHP: { model: shipmentModel, idField: '_id', type: 'shipment', referenceField: '_reference' },
|
||||||
INV: { model: invoiceModel, idField: '_id', type: 'invoice', 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,
|
taxRateRoutes,
|
||||||
taxRecordRoutes,
|
taxRecordRoutes,
|
||||||
invoiceRoutes,
|
invoiceRoutes,
|
||||||
paymentRoutes,
|
|
||||||
clientRoutes,
|
|
||||||
salesOrderRoutes,
|
|
||||||
} from './routes/index.js';
|
} from './routes/index.js';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
@ -144,9 +141,6 @@ app.use('/courierservices', courierServiceRoutes);
|
|||||||
app.use('/taxrates', taxRateRoutes);
|
app.use('/taxrates', taxRateRoutes);
|
||||||
app.use('/taxrecords', taxRecordRoutes);
|
app.use('/taxrecords', taxRecordRoutes);
|
||||||
app.use('/invoices', invoiceRoutes);
|
app.use('/invoices', invoiceRoutes);
|
||||||
app.use('/payments', paymentRoutes);
|
|
||||||
app.use('/clients', clientRoutes);
|
|
||||||
app.use('/salesorders', salesOrderRoutes);
|
|
||||||
app.use('/notes', noteRoutes);
|
app.use('/notes', noteRoutes);
|
||||||
|
|
||||||
// Start the application
|
// Start the application
|
||||||
|
|||||||
@ -13,9 +13,9 @@ import {
|
|||||||
listInvoicesByPropertiesRouteHandler,
|
listInvoicesByPropertiesRouteHandler,
|
||||||
getInvoiceStatsRouteHandler,
|
getInvoiceStatsRouteHandler,
|
||||||
getInvoiceHistoryRouteHandler,
|
getInvoiceHistoryRouteHandler,
|
||||||
acknowledgeInvoiceRouteHandler,
|
sendInvoiceRouteHandler,
|
||||||
|
markInvoicePaidRouteHandler,
|
||||||
cancelInvoiceRouteHandler,
|
cancelInvoiceRouteHandler,
|
||||||
postInvoiceRouteHandler,
|
|
||||||
} from '../../services/finance/invoices.js';
|
} from '../../services/finance/invoices.js';
|
||||||
|
|
||||||
// list of invoices
|
// list of invoices
|
||||||
@ -23,13 +23,12 @@ router.get('/', isAuthenticated, (req, res) => {
|
|||||||
const { page, limit, property, search, sort, order } = req.query;
|
const { page, limit, property, search, sort, order } = req.query;
|
||||||
const allowedFilters = [
|
const allowedFilters = [
|
||||||
'vendor',
|
'vendor',
|
||||||
'client',
|
'customer',
|
||||||
|
'invoiceType',
|
||||||
'state',
|
'state',
|
||||||
|
'value',
|
||||||
'vendor._id',
|
'vendor._id',
|
||||||
'client._id',
|
'customer._id',
|
||||||
'order',
|
|
||||||
'order._id',
|
|
||||||
'orderType',
|
|
||||||
];
|
];
|
||||||
const filter = getFilter(req.query, allowedFilters);
|
const filter = getFilter(req.query, allowedFilters);
|
||||||
listInvoicesRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
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);
|
let properties = convertPropertiesString(req.query.properties);
|
||||||
const allowedFilters = [
|
const allowedFilters = [
|
||||||
'vendor',
|
'vendor',
|
||||||
'client',
|
'customer',
|
||||||
'orderType',
|
'invoiceType',
|
||||||
'order',
|
|
||||||
'state.type',
|
'state.type',
|
||||||
'value',
|
'value',
|
||||||
'vendor._id',
|
'vendor._id',
|
||||||
'client._id',
|
'customer._id',
|
||||||
];
|
];
|
||||||
const filter = getFilter(req.query, allowedFilters, false);
|
const filter = getFilter(req.query, allowedFilters, false);
|
||||||
var masterFilter = {};
|
var masterFilter = {};
|
||||||
@ -86,12 +84,12 @@ router.delete('/:id', isAuthenticated, async (req, res) => {
|
|||||||
deleteInvoiceRouteHandler(req, res);
|
deleteInvoiceRouteHandler(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/:id/post', isAuthenticated, async (req, res) => {
|
router.post('/:id/send', isAuthenticated, async (req, res) => {
|
||||||
postInvoiceRouteHandler(req, res);
|
sendInvoiceRouteHandler(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/:id/acknowledge', isAuthenticated, async (req, res) => {
|
router.post('/:id/markpaid', isAuthenticated, async (req, res) => {
|
||||||
acknowledgeInvoiceRouteHandler(req, res);
|
markInvoicePaidRouteHandler(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/:id/cancel', isAuthenticated, async (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 taxRateRoutes from './management/taxrates.js';
|
||||||
import taxRecordRoutes from './management/taxrecords.js';
|
import taxRecordRoutes from './management/taxrecords.js';
|
||||||
import invoiceRoutes from './finance/invoices.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';
|
import noteRoutes from './misc/notes.js';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -69,7 +66,4 @@ export {
|
|||||||
taxRateRoutes,
|
taxRateRoutes,
|
||||||
taxRecordRoutes,
|
taxRecordRoutes,
|
||||||
invoiceRoutes,
|
invoiceRoutes,
|
||||||
paymentRoutes,
|
|
||||||
clientRoutes,
|
|
||||||
salesOrderRoutes,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import {
|
|||||||
router.get('/', isAuthenticated, (req, res) => {
|
router.get('/', isAuthenticated, (req, res) => {
|
||||||
const { page, limit, property, search, sort, order } = req.query;
|
const { page, limit, property, search, sort, order } = req.query;
|
||||||
const allowedFilters = [
|
const allowedFilters = [
|
||||||
'name',
|
|
||||||
'itemType',
|
'itemType',
|
||||||
'item',
|
'item',
|
||||||
'item._id',
|
'item._id',
|
||||||
@ -36,7 +35,6 @@ router.get('/', isAuthenticated, (req, res) => {
|
|||||||
router.get('/properties', isAuthenticated, (req, res) => {
|
router.get('/properties', isAuthenticated, (req, res) => {
|
||||||
let properties = convertPropertiesString(req.query.properties);
|
let properties = convertPropertiesString(req.query.properties);
|
||||||
const allowedFilters = [
|
const allowedFilters = [
|
||||||
'name',
|
|
||||||
'itemType',
|
'itemType',
|
||||||
'item',
|
'item',
|
||||||
'item._id',
|
'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,
|
getModelHistory,
|
||||||
checkStates,
|
checkStates,
|
||||||
} from '../../database/database.js';
|
} 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');
|
const logger = log4js.getLogger('Invoices');
|
||||||
logger.level = config.server.logLevel;
|
logger.level = config.server.logLevel;
|
||||||
@ -31,11 +29,7 @@ export const listInvoicesRouteHandler = async (
|
|||||||
sort = '',
|
sort = '',
|
||||||
order = 'ascend'
|
order = 'ascend'
|
||||||
) => {
|
) => {
|
||||||
const populateFields = [
|
const populateFields = ['vendor', 'customer', 'relatedOrder'];
|
||||||
{ path: 'to', strictPopulate: false, ref: 'client' },
|
|
||||||
{ path: 'from', strictPopulate: false, ref: 'vendor' },
|
|
||||||
{ path: 'order' },
|
|
||||||
];
|
|
||||||
const result = await listObjects({
|
const result = await listObjects({
|
||||||
model: invoiceModel,
|
model: invoiceModel,
|
||||||
page,
|
page,
|
||||||
@ -65,7 +59,7 @@ export const listInvoicesByPropertiesRouteHandler = async (
|
|||||||
filter = {},
|
filter = {},
|
||||||
masterFilter = {}
|
masterFilter = {}
|
||||||
) => {
|
) => {
|
||||||
const populateFields = ['to', 'from', 'order'];
|
const populateFields = ['vendor', 'customer', 'relatedOrder'];
|
||||||
const result = await listObjectsByProperties({
|
const result = await listObjectsByProperties({
|
||||||
model: invoiceModel,
|
model: invoiceModel,
|
||||||
properties,
|
properties,
|
||||||
@ -86,15 +80,7 @@ export const listInvoicesByPropertiesRouteHandler = async (
|
|||||||
|
|
||||||
export const getInvoiceRouteHandler = async (req, res) => {
|
export const getInvoiceRouteHandler = async (req, res) => {
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
const populateFields = [
|
const populateFields = ['vendor', 'customer', 'relatedOrder'];
|
||||||
{ path: 'to', strictPopulate: false },
|
|
||||||
{ path: 'from', strictPopulate: false },
|
|
||||||
{ path: 'order' },
|
|
||||||
{ path: 'invoiceOrderItems.taxRate' },
|
|
||||||
{ path: 'invoiceShipments.taxRate' },
|
|
||||||
{ path: 'invoiceOrderItems.orderItem' },
|
|
||||||
{ path: 'invoiceShipments.shipment' },
|
|
||||||
];
|
|
||||||
const result = await getObject({
|
const result = await getObject({
|
||||||
model: invoiceModel,
|
model: invoiceModel,
|
||||||
id,
|
id,
|
||||||
@ -131,17 +117,12 @@ export const editInvoiceRouteHandler = async (req, res) => {
|
|||||||
const updateData = {
|
const updateData = {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
vendor: req.body.vendor,
|
vendor: req.body.vendor,
|
||||||
client: req.body.client,
|
customer: req.body.customer,
|
||||||
invoiceType: req.body.invoiceType,
|
invoiceType: req.body.invoiceType,
|
||||||
invoiceDate: req.body.invoiceDate,
|
invoiceDate: req.body.invoiceDate,
|
||||||
dueAt: req.body.dueDate,
|
dueDate: req.body.dueDate,
|
||||||
issuedAt: req.body.issuedAt,
|
relatedOrderType: req.body.relatedOrderType,
|
||||||
orderType: req.body.orderType,
|
relatedOrder: req.body.relatedOrder,
|
||||||
order: req.body.order,
|
|
||||||
invoiceOrderItems: req.body.invoiceOrderItems,
|
|
||||||
invoiceShipments: req.body.invoiceShipments,
|
|
||||||
from: req.body.from,
|
|
||||||
to: req.body.to,
|
|
||||||
};
|
};
|
||||||
// Create audit log before updating
|
// Create audit log before updating
|
||||||
const result = await editObject({
|
const result = await editObject({
|
||||||
@ -166,7 +147,7 @@ export const editMultipleInvoicesRouteHandler = async (req, res) => {
|
|||||||
const updates = req.body.map((update) => ({
|
const updates = req.body.map((update) => ({
|
||||||
_id: update._id,
|
_id: update._id,
|
||||||
vendor: update.vendor,
|
vendor: update.vendor,
|
||||||
client: update.client,
|
customer: update.customer,
|
||||||
invoiceType: update.invoiceType,
|
invoiceType: update.invoiceType,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -192,90 +173,21 @@ export const editMultipleInvoicesRouteHandler = async (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const newInvoiceRouteHandler = 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 = {
|
const newData = {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
vendor: req.body.vendor,
|
vendor: req.body.vendor,
|
||||||
client: req.body.client,
|
customer: req.body.customer,
|
||||||
issuedAt: req.body.issuedAt || new Date(),
|
invoiceType: req.body.invoiceType || 'sales',
|
||||||
dueAt: req.body.dueAt || new Date(),
|
invoiceDate: req.body.invoiceDate || new Date(),
|
||||||
orderType: req.body.orderType,
|
dueDate: req.body.dueDate,
|
||||||
order: req.body.order,
|
relatedOrderType: req.body.relatedOrderType,
|
||||||
|
relatedOrder: req.body.relatedOrder,
|
||||||
totalAmount: 0,
|
totalAmount: 0,
|
||||||
totalAmountWithTax: 0,
|
totalAmountWithTax: 0,
|
||||||
totalTaxAmount: 0,
|
totalTaxAmount: 0,
|
||||||
grandTotalAmount: 0,
|
grandTotalAmount: 0,
|
||||||
shippingAmount: 0,
|
shippingAmount: 0,
|
||||||
shippingAmountWithTax: 0,
|
shippingAmountWithTax: 0,
|
||||||
invoiceOrderItems: invoiceOrderItems,
|
|
||||||
invoiceShipments: invoiceShipments,
|
|
||||||
from: req.body.from,
|
|
||||||
to: req.body.to,
|
|
||||||
};
|
};
|
||||||
const result = await newObject({
|
const result = await newObject({
|
||||||
model: invoiceModel,
|
model: invoiceModel,
|
||||||
@ -335,48 +247,7 @@ export const getInvoiceHistoryRouteHandler = async (req, res) => {
|
|||||||
res.send(result);
|
res.send(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const acknowledgeInvoiceRouteHandler = async (req, res) => {
|
export const sendInvoiceRouteHandler = 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) => {
|
|
||||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||||
|
|
||||||
logger.trace(`Invoice with ID: ${id}`);
|
logger.trace(`Invoice with ID: ${id}`);
|
||||||
@ -395,98 +266,10 @@ export const postInvoiceRouteHandler = async (req, res) => {
|
|||||||
return;
|
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 = {
|
const updateData = {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
state: { type: 'sent' },
|
state: { type: 'sent' },
|
||||||
postedAt: new Date(),
|
sentAt: new Date(),
|
||||||
};
|
};
|
||||||
const result = await editObject({
|
const result = await editObject({
|
||||||
model: invoiceModel,
|
model: invoiceModel,
|
||||||
@ -496,12 +279,57 @@ export const postInvoiceRouteHandler = async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
logger.error('Error posting invoice:', result.error);
|
logger.error('Error sending invoice:', result.error);
|
||||||
res.status(result.code).send(result);
|
res.status(result.code).send(result);
|
||||||
return;
|
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);
|
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 = {
|
const updateData = {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
name: req.body.name,
|
|
||||||
itemType: req.body.itemType,
|
itemType: req.body.itemType,
|
||||||
item: req.body.item,
|
item: req.body.item,
|
||||||
orderType: req.body.orderType,
|
orderType: req.body.orderType,
|
||||||
@ -170,7 +169,6 @@ export const editOrderItemRouteHandler = async (req, res) => {
|
|||||||
export const editMultipleOrderItemsRouteHandler = async (req, res) => {
|
export const editMultipleOrderItemsRouteHandler = async (req, res) => {
|
||||||
const updates = req.body.map((update) => ({
|
const updates = req.body.map((update) => ({
|
||||||
_id: update._id,
|
_id: update._id,
|
||||||
name: update.name,
|
|
||||||
itemType: update.itemType,
|
itemType: update.itemType,
|
||||||
item: update.item,
|
item: update.item,
|
||||||
orderType: update.orderType,
|
orderType: update.orderType,
|
||||||
@ -208,7 +206,6 @@ export const editMultipleOrderItemsRouteHandler = async (req, res) => {
|
|||||||
export const newOrderItemRouteHandler = async (req, res) => {
|
export const newOrderItemRouteHandler = async (req, res) => {
|
||||||
const newData = {
|
const newData = {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
name: req.body.name,
|
|
||||||
purchaseOrder: req.body.purchaseOrder,
|
purchaseOrder: req.body.purchaseOrder,
|
||||||
state: { type: 'draft' },
|
state: { type: 'draft' },
|
||||||
itemType: req.body.itemType,
|
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