Refactor invoice routes and handlers for improved clarity and functionality
- Renamed and updated route handlers for sending and marking invoices to acknowledge and post invoices, respectively. - Changed filter parameters from 'customer' to 'client' and added 'order' and 'orderType' filters for better invoice management. - Enhanced invoice handling logic to include order items and shipments, improving the overall invoicing process. - Updated the population fields in invoice queries to reflect new schema relationships. - Adjusted state checks and logging for better error handling and clarity in invoice processing.
This commit is contained in:
parent
d3cbea45c5
commit
a7e35c279e
@ -13,9 +13,9 @@ import {
|
||||
listInvoicesByPropertiesRouteHandler,
|
||||
getInvoiceStatsRouteHandler,
|
||||
getInvoiceHistoryRouteHandler,
|
||||
sendInvoiceRouteHandler,
|
||||
markInvoicePaidRouteHandler,
|
||||
acknowledgeInvoiceRouteHandler,
|
||||
cancelInvoiceRouteHandler,
|
||||
postInvoiceRouteHandler,
|
||||
} from '../../services/finance/invoices.js';
|
||||
|
||||
// list of invoices
|
||||
@ -23,12 +23,13 @@ router.get('/', isAuthenticated, (req, res) => {
|
||||
const { page, limit, property, search, sort, order } = req.query;
|
||||
const allowedFilters = [
|
||||
'vendor',
|
||||
'customer',
|
||||
'invoiceType',
|
||||
'client',
|
||||
'state',
|
||||
'value',
|
||||
'vendor._id',
|
||||
'customer._id',
|
||||
'client._id',
|
||||
'order',
|
||||
'order._id',
|
||||
'orderType',
|
||||
];
|
||||
const filter = getFilter(req.query, allowedFilters);
|
||||
listInvoicesRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
||||
@ -38,12 +39,13 @@ router.get('/properties', isAuthenticated, (req, res) => {
|
||||
let properties = convertPropertiesString(req.query.properties);
|
||||
const allowedFilters = [
|
||||
'vendor',
|
||||
'customer',
|
||||
'invoiceType',
|
||||
'client',
|
||||
'orderType',
|
||||
'order',
|
||||
'state.type',
|
||||
'value',
|
||||
'vendor._id',
|
||||
'customer._id',
|
||||
'client._id',
|
||||
];
|
||||
const filter = getFilter(req.query, allowedFilters, false);
|
||||
var masterFilter = {};
|
||||
@ -84,12 +86,12 @@ router.delete('/:id', isAuthenticated, async (req, res) => {
|
||||
deleteInvoiceRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.post('/:id/send', isAuthenticated, async (req, res) => {
|
||||
sendInvoiceRouteHandler(req, res);
|
||||
router.post('/:id/post', isAuthenticated, async (req, res) => {
|
||||
postInvoiceRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.post('/:id/markpaid', isAuthenticated, async (req, res) => {
|
||||
markInvoicePaidRouteHandler(req, res);
|
||||
router.post('/:id/acknowledge', isAuthenticated, async (req, res) => {
|
||||
acknowledgeInvoiceRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.post('/:id/cancel', isAuthenticated, async (req, res) => {
|
||||
|
||||
@ -14,6 +14,8 @@ import {
|
||||
getModelHistory,
|
||||
checkStates,
|
||||
} from '../../database/database.js';
|
||||
import { orderItemModel } from '../../database/schemas/inventory/orderitem.schema.js';
|
||||
import { shipmentModel } from '../../database/schemas/inventory/shipment.schema.js';
|
||||
|
||||
const logger = log4js.getLogger('Invoices');
|
||||
logger.level = config.server.logLevel;
|
||||
@ -29,7 +31,11 @@ export const listInvoicesRouteHandler = async (
|
||||
sort = '',
|
||||
order = 'ascend'
|
||||
) => {
|
||||
const populateFields = ['vendor', 'customer', 'relatedOrder'];
|
||||
const populateFields = [
|
||||
{ path: 'to', strictPopulate: false, ref: 'client' },
|
||||
{ path: 'from', strictPopulate: false, ref: 'vendor' },
|
||||
{ path: 'order' },
|
||||
];
|
||||
const result = await listObjects({
|
||||
model: invoiceModel,
|
||||
page,
|
||||
@ -59,7 +65,7 @@ export const listInvoicesByPropertiesRouteHandler = async (
|
||||
filter = {},
|
||||
masterFilter = {}
|
||||
) => {
|
||||
const populateFields = ['vendor', 'customer', 'relatedOrder'];
|
||||
const populateFields = ['to', 'from', 'order'];
|
||||
const result = await listObjectsByProperties({
|
||||
model: invoiceModel,
|
||||
properties,
|
||||
@ -80,7 +86,15 @@ export const listInvoicesByPropertiesRouteHandler = async (
|
||||
|
||||
export const getInvoiceRouteHandler = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
const populateFields = ['vendor', 'customer', 'relatedOrder'];
|
||||
const populateFields = [
|
||||
{ path: 'to', strictPopulate: false },
|
||||
{ path: 'from', strictPopulate: false },
|
||||
{ path: 'order' },
|
||||
{ path: 'invoiceOrderItems.taxRate' },
|
||||
{ path: 'invoiceShipments.taxRate' },
|
||||
{ path: 'invoiceOrderItems.orderItem' },
|
||||
{ path: 'invoiceShipments.shipment' },
|
||||
];
|
||||
const result = await getObject({
|
||||
model: invoiceModel,
|
||||
id,
|
||||
@ -117,12 +131,17 @@ export const editInvoiceRouteHandler = async (req, res) => {
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
vendor: req.body.vendor,
|
||||
customer: req.body.customer,
|
||||
client: req.body.client,
|
||||
invoiceType: req.body.invoiceType,
|
||||
invoiceDate: req.body.invoiceDate,
|
||||
dueDate: req.body.dueDate,
|
||||
relatedOrderType: req.body.relatedOrderType,
|
||||
relatedOrder: req.body.relatedOrder,
|
||||
dueAt: req.body.dueDate,
|
||||
issuedAt: req.body.issuedAt,
|
||||
orderType: req.body.orderType,
|
||||
order: req.body.order,
|
||||
invoiceOrderItems: req.body.invoiceOrderItems,
|
||||
invoiceShipments: req.body.invoiceShipments,
|
||||
from: req.body.from,
|
||||
to: req.body.to,
|
||||
};
|
||||
// Create audit log before updating
|
||||
const result = await editObject({
|
||||
@ -147,7 +166,7 @@ export const editMultipleInvoicesRouteHandler = async (req, res) => {
|
||||
const updates = req.body.map((update) => ({
|
||||
_id: update._id,
|
||||
vendor: update.vendor,
|
||||
customer: update.customer,
|
||||
client: update.client,
|
||||
invoiceType: update.invoiceType,
|
||||
}));
|
||||
|
||||
@ -173,21 +192,90 @@ export const editMultipleInvoicesRouteHandler = async (req, res) => {
|
||||
};
|
||||
|
||||
export const newInvoiceRouteHandler = async (req, res) => {
|
||||
const orderItems = await listObjects({
|
||||
model: orderItemModel,
|
||||
filter: { order: req.body.order, orderType: req.body.orderType },
|
||||
});
|
||||
|
||||
const shipments = await listObjects({
|
||||
model: shipmentModel,
|
||||
filter: { order: req.body.order, orderType: req.body.orderType },
|
||||
});
|
||||
|
||||
if (orderItems.error) {
|
||||
logger.error('Error getting order items:', orderItems.error);
|
||||
return res.status(orderItems.code).send(orderItems);
|
||||
}
|
||||
|
||||
if (shipments.error) {
|
||||
logger.error('Error getting shipments:', shipments.error);
|
||||
return res.status(shipments.code).send(shipments);
|
||||
}
|
||||
|
||||
const invoiceOrderItems = orderItems
|
||||
.map((orderItem) => {
|
||||
const invoicedAmountWithTax = orderItem.invoicedAmountWithTax || 0;
|
||||
const totalAmountWithTax = orderItem.totalAmountWithTax || 0;
|
||||
const invoicedAmount = orderItem.invoicedAmount || 0;
|
||||
const totalAmount = orderItem.totalAmount || 0;
|
||||
const quantity = (orderItem.quantity || 0) - (orderItem.invoicedQuantity || 0);
|
||||
const taxRate = orderItem?.taxRate?._id;
|
||||
|
||||
if (invoicedAmountWithTax >= totalAmountWithTax || invoicedAmount >= totalAmount) {
|
||||
return null;
|
||||
}
|
||||
var finalQuantity = quantity;
|
||||
if (finalQuantity <= 0) {
|
||||
finalQuantity = 1;
|
||||
}
|
||||
return {
|
||||
orderItem: orderItem._id,
|
||||
taxRate: taxRate,
|
||||
invoiceAmountWithTax: totalAmountWithTax - invoicedAmountWithTax,
|
||||
invoiceAmount: totalAmount - invoicedAmount,
|
||||
invoiceQuantity: finalQuantity,
|
||||
};
|
||||
})
|
||||
.filter((item) => item !== null);
|
||||
|
||||
const invoiceShipments = shipments
|
||||
.map((shipment) => {
|
||||
const invoicedAmount = shipment.invoicedAmount || 0;
|
||||
const amountWithTax = shipment.amountWithTax || 0;
|
||||
const invoicedAmountWithTax = shipment.invoicedAmountWithTax || 0;
|
||||
const amount = shipment.amount || 0;
|
||||
const taxRate = shipment?.taxRate || null;
|
||||
|
||||
if (invoicedAmountWithTax >= amountWithTax || invoicedAmount >= amount) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
shipment: shipment._id,
|
||||
taxRate: taxRate,
|
||||
invoiceAmountWithTax: amountWithTax - invoicedAmountWithTax,
|
||||
invoiceAmount: amount - invoicedAmount,
|
||||
};
|
||||
})
|
||||
.filter((item) => item !== null);
|
||||
|
||||
const newData = {
|
||||
updatedAt: new Date(),
|
||||
vendor: req.body.vendor,
|
||||
customer: req.body.customer,
|
||||
invoiceType: req.body.invoiceType || 'sales',
|
||||
invoiceDate: req.body.invoiceDate || new Date(),
|
||||
dueDate: req.body.dueDate,
|
||||
relatedOrderType: req.body.relatedOrderType,
|
||||
relatedOrder: req.body.relatedOrder,
|
||||
client: req.body.client,
|
||||
issuedAt: req.body.issuedAt || new Date(),
|
||||
dueAt: req.body.dueAt || new Date(),
|
||||
orderType: req.body.orderType,
|
||||
order: req.body.order,
|
||||
totalAmount: 0,
|
||||
totalAmountWithTax: 0,
|
||||
totalTaxAmount: 0,
|
||||
grandTotalAmount: 0,
|
||||
shippingAmount: 0,
|
||||
shippingAmountWithTax: 0,
|
||||
invoiceOrderItems: invoiceOrderItems,
|
||||
invoiceShipments: invoiceShipments,
|
||||
from: req.body.from,
|
||||
to: req.body.to,
|
||||
};
|
||||
const result = await newObject({
|
||||
model: invoiceModel,
|
||||
@ -247,7 +335,48 @@ export const getInvoiceHistoryRouteHandler = async (req, res) => {
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const sendInvoiceRouteHandler = async (req, res) => {
|
||||
export const acknowledgeInvoiceRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`Invoice with ID: ${id}`);
|
||||
|
||||
const checkStatesResult = await checkStates({ model: invoiceModel, id, states: ['sent'] });
|
||||
|
||||
if (checkStatesResult.error) {
|
||||
logger.error('Error checking invoice states:', checkStatesResult.error);
|
||||
res.status(checkStatesResult.code).send(checkStatesResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkStatesResult === false) {
|
||||
logger.error('Invoice is not in sent state.');
|
||||
res.status(400).send({ error: 'Invoice is not in sent state.', code: 400 });
|
||||
return;
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
state: { type: 'acknowledged' },
|
||||
acknowledgedAt: new Date(),
|
||||
};
|
||||
const result = await editObject({
|
||||
model: invoiceModel,
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error acknowledging invoice:', result.error);
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Acknowledged invoice with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const postInvoiceRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`Invoice with ID: ${id}`);
|
||||
@ -266,10 +395,98 @@ export const sendInvoiceRouteHandler = async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const invoice = await getObject({
|
||||
model: invoiceModel,
|
||||
id,
|
||||
});
|
||||
|
||||
const invoiceOrderItems = invoice.invoiceOrderItems;
|
||||
const invoiceShipments = invoice.invoiceShipments;
|
||||
|
||||
for (const invoiceOrderItem of invoiceOrderItems) {
|
||||
const orderItem = await getObject({
|
||||
model: orderItemModel,
|
||||
id: invoiceOrderItem.orderItem._id,
|
||||
});
|
||||
if (orderItem.error) {
|
||||
logger.error('Error getting order item:', orderItem.error);
|
||||
return res.status(orderItem.code).send(orderItem);
|
||||
}
|
||||
const invoiceQuantity = invoiceOrderItem.invoiceQuantity || 0;
|
||||
const invoiceAmount = invoiceOrderItem.invoiceAmount || 0;
|
||||
const invoiceAmountWithTax = invoiceOrderItem.invoiceAmountWithTax || 0;
|
||||
const invoicedQuantity = orderItem.invoicedQuantity || 0;
|
||||
const invoicedAmount = orderItem.invoicedAmount || 0;
|
||||
const invoicedAmountWithTax = orderItem.invoicedAmountWithTax || 0;
|
||||
var quantity = (orderItem.invoiceQuantity || 0) + invoiceQuantity;
|
||||
if (quantity <= orderItem.quantity) {
|
||||
quantity = orderItem.quantity;
|
||||
}
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
invoicedQuantity: invoicedQuantity + invoiceQuantity,
|
||||
invoicedAmount: invoicedAmount + invoiceAmount,
|
||||
invoicedAmountWithTax: invoicedAmountWithTax + invoiceAmountWithTax,
|
||||
};
|
||||
const result = await editObject({
|
||||
model: orderItemModel,
|
||||
id: orderItem._id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
});
|
||||
if (result.error) {
|
||||
logger.error('Error updating order item:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.debug(`Updated order item with ID: ${orderItem._id}`);
|
||||
}
|
||||
|
||||
for (const invoiceShipment of invoiceShipments) {
|
||||
const shipmentId = invoiceShipment.shipment._id || invoiceShipment.shipment;
|
||||
const shipment = await getObject({
|
||||
model: shipmentModel,
|
||||
id: shipmentId,
|
||||
});
|
||||
if (shipment.error) {
|
||||
logger.error('Error getting shipment:', shipment.error);
|
||||
return res.status(shipment.code).send(shipment);
|
||||
}
|
||||
const invoiceAmount = invoiceShipment.invoiceAmount || 0;
|
||||
const invoiceAmountWithTax = invoiceShipment.invoiceAmountWithTax || 0;
|
||||
const invoicedAmount = shipment.invoicedAmount || 0;
|
||||
const invoicedAmountWithTax = shipment.invoicedAmountWithTax || 0;
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
invoicedAmount: invoicedAmount + invoiceAmount,
|
||||
invoicedAmountWithTax: invoicedAmountWithTax + invoiceAmountWithTax,
|
||||
};
|
||||
const result = await editObject({
|
||||
model: shipmentModel,
|
||||
id: shipment._id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
});
|
||||
if (result.error) {
|
||||
logger.error('Error updating shipment:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.debug(`Updated shipment with ID: ${shipment._id}`);
|
||||
}
|
||||
|
||||
if (invoiceOrderItems.error) {
|
||||
logger.error('Error getting invoice order items:', invoiceOrderItems.error);
|
||||
return res.status(invoiceOrderItems.code).send(invoiceOrderItems);
|
||||
}
|
||||
|
||||
if (invoiceShipments.error) {
|
||||
logger.error('Error getting invoice shipments:', invoiceShipments.error);
|
||||
return res.status(invoiceShipments.code).send(invoiceShipments);
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
state: { type: 'sent' },
|
||||
sentAt: new Date(),
|
||||
postedAt: new Date(),
|
||||
};
|
||||
const result = await editObject({
|
||||
model: invoiceModel,
|
||||
@ -279,57 +496,12 @@ export const sendInvoiceRouteHandler = async (req, res) => {
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error sending invoice:', result.error);
|
||||
logger.error('Error posting invoice:', result.error);
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
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}`);
|
||||
logger.debug(`Posted invoice with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user