import config from '../../config.js'; import { purchaseOrderModel } from '../../database/schemas/inventory/purchaseorder.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('Purchase Orders'); logger.level = config.server.logLevel; export const listPurchaseOrdersRouteHandler = async ( req, res, page = 1, limit = 25, property = '', filter = {}, search = '', sort = '', order = 'ascend' ) => { const result = await listObjects({ model: purchaseOrderModel, page, limit, property, filter, search, sort, order, populate: ['vendor'], }); if (result?.error) { logger.error('Error listing purchase orders.'); res.status(result.code).send(result); return; } logger.debug(`List of purchase orders (Page ${page}, Limit ${limit}). Count: ${result.length}`); res.send(result); }; export const listPurchaseOrdersByPropertiesRouteHandler = async ( req, res, properties = '', filter = {}, masterFilter = {} ) => { const result = await listObjectsByProperties({ model: purchaseOrderModel, properties, filter, populate: ['vendor'], masterFilter, }); if (result?.error) { logger.error('Error listing purchase orders.'); res.status(result.code).send(result); return; } logger.debug(`List of purchase orders. Count: ${result.length}`); res.send(result); }; export const getPurchaseOrderRouteHandler = async (req, res) => { const id = req.params.id; const result = await getObject({ model: purchaseOrderModel, id, populate: ['vendor'], }); if (result?.error) { logger.warn(`Purchase Order not found with supplied id.`); return res.status(result.code).send(result); } logger.debug(`Retreived purchase order with ID: ${id}`); res.send(result); }; export const editPurchaseOrderRouteHandler = async (req, res) => { // Get ID from params const id = new mongoose.Types.ObjectId(req.params.id); logger.trace(`Purchase Order with ID: ${id}`); const checkStatesResult = await checkStates({ model: purchaseOrderModel, id, states: ['draft'] }); if (checkStatesResult.error) { logger.error('Error checking purchase order states:', checkStatesResult.error); res.status(checkStatesResult.code).send(checkStatesResult); return; } if (checkStatesResult === false) { logger.error('Purchase order is not in draft state.'); res.status(400).send({ error: 'Purchase order is not in draft state.', code: 400 }); return; } const updateData = { updatedAt: new Date(), vendor: req.body.vendor, }; // Create audit log before updating const result = await editObject({ model: purchaseOrderModel, id, updateData, user: req.user, }); if (result.error) { logger.error('Error editing purchase order:', result.error); res.status(result).send(result); return; } logger.debug(`Edited purchase order with ID: ${id}`); res.send(result); }; export const editMultiplePurchaseOrdersRouteHandler = async (req, res) => { const updates = req.body.map((update) => ({ _id: update._id, vendor: update.vendor, })); if (!Array.isArray(updates)) { return res.status(400).send({ error: 'Body must be an array of updates.', code: 400 }); } const result = await editObjects({ model: purchaseOrderModel, updates, user: req.user, }); if (result.error) { logger.error('Error editing purchase orders:', result.error); res.status(result.code || 500).send(result); return; } logger.debug(`Edited ${updates.length} purchase orders`); res.send(result); }; export const newPurchaseOrderRouteHandler = async (req, res) => { const newData = { updatedAt: new Date(), vendor: req.body.vendor, totalAmount: 0, totalAmountWithTax: 0, totalTaxAmount: 0, }; const result = await newObject({ model: purchaseOrderModel, newData, user: req.user, }); if (result.error) { logger.error('No purchase order created:', result.error); return res.status(result.code).send(result); } logger.debug(`New purchase order with ID: ${result._id}`); res.send(result); }; export const deletePurchaseOrderRouteHandler = async (req, res) => { // Get ID from params const id = new mongoose.Types.ObjectId(req.params.id); logger.trace(`Purchase Order with ID: ${id}`); const result = await deleteObject({ model: purchaseOrderModel, id, user: req.user, }); if (result.error) { logger.error('No purchase order deleted:', result.error); return res.status(result.code).send(result); } logger.debug(`Deleted purchase order with ID: ${result._id}`); res.send(result); }; export const getPurchaseOrderStatsRouteHandler = async (req, res) => { const result = await getModelStats({ model: purchaseOrderModel }); if (result?.error) { logger.error('Error fetching purchase order stats:', result.error); return res.status(result.code).send(result); } logger.trace('Purchase order stats:', result); res.send(result); }; export const getPurchaseOrderHistoryRouteHandler = async (req, res) => { const from = req.query.from; const to = req.query.to; const result = await getModelHistory({ model: purchaseOrderModel, from, to }); if (result?.error) { logger.error('Error fetching purchase order history:', result.error); return res.status(result.code).send(result); } logger.trace('Purchase order history:', result); res.send(result); }; export const postPurchaseOrderRouteHandler = async (req, res) => { const id = new mongoose.Types.ObjectId(req.params.id); logger.trace(`Purchase Order with ID: ${id}`); const checkStatesResult = await checkStates({ model: purchaseOrderModel, id, states: ['draft'] }); if (checkStatesResult.error) { logger.error('Error checking purchase order states:', checkStatesResult.error); res.status(checkStatesResult.code).send(checkStatesResult); return; } if (checkStatesResult === false) { logger.error('Purchase order is not in draft state.'); res.status(400).send({ error: 'Purchase order is not in draft state.', code: 400 }); return; } const orderItemsResult = await listObjects({ model: orderItemModel, filter: { order: id, orderType: 'purchaseOrder' }, pagination: false, }); const shipmentsResult = await listObjects({ model: shipmentModel, filter: { order: id, orderType: 'purchaseOrder' }, 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: purchaseOrderModel, id, updateData, user: req.user, }); if (result.error) { logger.error('Error posting purchase order:', result.error); res.status(result.code).send(result); return; } logger.debug(`Posted purchase order with ID: ${id}`); res.send(result); }; export const acknowledgePurchaseOrderRouteHandler = async (req, res) => { const id = new mongoose.Types.ObjectId(req.params.id); logger.trace(`Purchase Order with ID: ${id}`); const checkStatesResult = await checkStates({ model: purchaseOrderModel, id, states: ['sent'] }); if (checkStatesResult.error) { logger.error('Error checking purchase order states:', checkStatesResult.error); res.status(checkStatesResult.code).send(checkStatesResult); return; } if (checkStatesResult === false) { logger.error('Purchase order is not in sent state.'); res.status(400).send({ error: 'Purchase order is not in sent state.', code: 400 }); return; } const updateData = { updatedAt: new Date(), state: { type: 'acknowledged' }, acknowledgedAt: new Date(), }; const result = await editObject({ model: purchaseOrderModel, id, updateData, user: req.user, }); if (result.error) { logger.error('Error acknowledging purchase order:', result.error); res.status(result.code).send(result); return; } logger.debug(`Acknowledged purchase order with ID: ${id}`); res.send(result); }; export const cancelPurchaseOrderRouteHandler = async (req, res) => { const id = new mongoose.Types.ObjectId(req.params.id); logger.trace(`Purchase Order with ID: ${id}`); const checkStatesResult = await checkStates({ model: purchaseOrderModel, id, states: ['sent', 'acknowledged', 'partiallyShipped', 'shipped', 'partiallyReceived'], }); if (checkStatesResult.error) { logger.error('Error checking purchase order states:', checkStatesResult.error); res.status(checkStatesResult.code).send(checkStatesResult); return; } if (checkStatesResult === false) { logger.error('Purchase order is not in a cancellable state.'); res.status(400).send({ error: 'Purchase order is not in a cancellable state (must be draft, sent, or acknowledged).', code: 400, }); return; } const orderItemsResult = await listObjects({ model: orderItemModel, filter: { order: id, orderType: 'purchaseOrder' }, pagination: false, }); const shipmentsResult = await listObjects({ model: shipmentModel, filter: { order: id, orderType: 'purchaseOrder' }, 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: purchaseOrderModel, id, updateData, user: req.user, }); if (result.error) { logger.error('Error cancelling purchase order:', result.error); res.status(result.code).send(result); return; } logger.debug(`Cancelled purchase order with ID: ${id}`); res.send(result); };