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); };