Tom Butcher 6a730294b1 Refactor filter handling in various routes to include master filter support
This commit updates multiple routes across the management and inventory modules to enhance the handling of property value requests. The `/values` endpoints are modified to incorporate a filtering mechanism that allows for both a standard filter and an optional master filter, improving the flexibility and accuracy of data retrieval. Additionally, debug logging statements are added to assist in tracking the filter parameters during requests. This refactor aims to streamline the data fetching process and improve the overall user experience when interacting with the API.
2026-08-30 12:25:10 +01:00

522 lines
13 KiB
JavaScript

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,
searchObjects,
getPropertyValues,
getObjectNeighbors,
} 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', 'marketplace'],
});
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', 'marketplace'],
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 getSalesOrderPropertyValuesRouteHandler = async (
req,
res,
property,
filter,
masterFilter
) => {
const result = await getPropertyValues({
model: salesOrderModel,
property,
filter: { ...filter, ...masterFilter },
});
res.send(result);
};
export const searchSalesOrdersRouteHandler = async (req, res, search) => {
const result = await searchObjects({
model: salesOrderModel,
search,
});
res.send(result);
};
export const getSalesOrderRouteHandler = async (req, res) => {
const id = req.params.id;
const result = await getObject({
model: salesOrderModel,
id,
populate: ['client', 'marketplace'],
});
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,
marketplace: req.body.marketplace,
};
// Create audit log before updating
const result = await editObject({
model: salesOrderModel,
id,
updateData,
user: req.user,
populate: ['client', 'marketplace'],
});
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,
marketplace: update.marketplace,
}));
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,
marketplace: req.body.marketplace,
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);
};
export const getSalesOrderNeighborsRouteHandler = async (
req,
res,
property = '',
filter = {},
search = '',
sort = '',
order = 'ascend',
id
) => {
if (!id) {
return res.status(400).send({ error: 'Missing id parameter', code: 400 });
}
const result = await getObjectNeighbors({
model: salesOrderModel,
id,
filter,
search,
sort,
order,
});
if (result?.error) {
logger.error('Error fetching salesOrder neighbors.');
return res.status(result.code).send(result);
}
logger.debug(`Retrieved salesOrder neighbors for ID: ${id}`);
res.send(result);
};