From 866c29f33f903fea88f0ebb94e6f2f2eaed6261a Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sat, 7 Mar 2026 20:37:41 +0000 Subject: [PATCH] Set default state as 'draft' and introduced 'postedAt' field. Implemented postProductStock route handler to transition product stock from draft to posted state, including state validation checks. Updated related service functions for consistency. --- .../schemas/inventory/productstock.schema.js | 13 +++- src/routes/inventory/productstocks.js | 5 ++ src/services/inventory/productstocks.js | 72 ++++++++++++++++++- 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/src/database/schemas/inventory/productstock.schema.js b/src/database/schemas/inventory/productstock.schema.js index ce3d2e5..88bffe0 100644 --- a/src/database/schemas/inventory/productstock.schema.js +++ b/src/database/schemas/inventory/productstock.schema.js @@ -14,9 +14,10 @@ const productStockSchema = new Schema( { _reference: { type: String, default: () => generateId()() }, state: { - type: { type: String, required: true }, + type: { type: String, required: true, default: 'draft' }, progress: { type: Number, required: false }, }, + postedAt: { type: Date, required: false }, product: { type: mongoose.Schema.Types.ObjectId, ref: 'product', required: true }, currentQuantity: { type: Number, required: true }, partStocks: [partStockUsageSchema], @@ -30,6 +31,16 @@ const rollupConfigs = [ filter: {}, rollups: [{ name: 'totalCurrentQuantity', property: 'currentQuantity', operation: 'sum' }], }, + { + name: 'draft', + filter: { 'state.type': 'draft' }, + rollups: [{ name: 'draft', property: 'state.type', operation: 'count' }], + }, + { + name: 'posted', + filter: { 'state.type': 'posted' }, + rollups: [{ name: 'posted', property: 'state.type', operation: 'count' }], + }, ]; productStockSchema.statics.stats = async function () { diff --git a/src/routes/inventory/productstocks.js b/src/routes/inventory/productstocks.js index 967a549..bd5c669 100644 --- a/src/routes/inventory/productstocks.js +++ b/src/routes/inventory/productstocks.js @@ -10,6 +10,7 @@ import { editMultipleProductStocksRouteHandler, newProductStockRouteHandler, deleteProductStockRouteHandler, + postProductStockRouteHandler, listProductStocksByPropertiesRouteHandler, getProductStockStatsRouteHandler, getProductStockHistoryRouteHandler, @@ -61,4 +62,8 @@ router.delete('/:id', isAuthenticated, async (req, res) => { deleteProductStockRouteHandler(req, res); }); +router.post('/:id/post', isAuthenticated, async (req, res) => { + postProductStockRouteHandler(req, res); +}); + export default router; diff --git a/src/services/inventory/productstocks.js b/src/services/inventory/productstocks.js index 2e309a3..5587437 100644 --- a/src/services/inventory/productstocks.js +++ b/src/services/inventory/productstocks.js @@ -12,6 +12,7 @@ import { listObjectsByProperties, getModelStats, getModelHistory, + checkStates, } from '../../database/database.js'; import { productModel } from '../../database/schemas/management/product.schema.js'; const logger = log4js.getLogger('Product Stocks'); @@ -95,6 +96,20 @@ export const editProductStockRouteHandler = async (req, res) => { logger.trace(`Product Stock with ID: ${id}`); + const checkStatesResult = await checkStates({ model: productStockModel, id, states: ['draft'] }); + + if (checkStatesResult.error) { + logger.error('Error checking product stock states:', checkStatesResult.error); + res.status(checkStatesResult.code).send(checkStatesResult); + return; + } + + if (checkStatesResult === false) { + logger.error('Product stock is not in draft state.'); + res.status(400).send({ error: 'Product stock is not in draft state.', code: 400 }); + return; + } + const updateData = { partStocks: req.body?.partStocks?.map((partStock) => ({ quantity: partStock.quantity, @@ -156,7 +171,7 @@ export const newProductStockRouteHandler = async (req, res) => { updatedAt: new Date(), currentQuantity: req.body.currentQuantity, product: req.body.product, - state: req.body.state, + state: req.body.state ?? { type: 'draft' }, partStocks: product.parts.map((part) => ({ part: part.part, quantity: part.quantity, @@ -183,6 +198,20 @@ export const deleteProductStockRouteHandler = async (req, res) => { logger.trace(`Product Stock with ID: ${id}`); + const checkStatesResult = await checkStates({ model: productStockModel, id, states: ['draft'] }); + + if (checkStatesResult.error) { + logger.error('Error checking product stock states:', checkStatesResult.error); + res.status(checkStatesResult.code).send(checkStatesResult); + return; + } + + if (checkStatesResult === false) { + logger.error('Product stock is not in draft state.'); + res.status(400).send({ error: 'Product stock is not in draft state.', code: 400 }); + return; + } + const result = await deleteObject({ model: productStockModel, id, @@ -219,3 +248,44 @@ export const getProductStockHistoryRouteHandler = async (req, res) => { logger.trace('Product stock history:', result); res.send(result); }; + +export const postProductStockRouteHandler = async (req, res) => { + const id = new mongoose.Types.ObjectId(req.params.id); + + logger.trace(`Product Stock with ID: ${id}`); + + const checkStatesResult = await checkStates({ model: productStockModel, id, states: ['draft'] }); + + if (checkStatesResult.error) { + logger.error('Error checking product stock states:', checkStatesResult.error); + res.status(checkStatesResult.code).send(checkStatesResult); + return; + } + + if (checkStatesResult === false) { + logger.error('Product stock is not in draft state.'); + res.status(400).send({ error: 'Product stock is not in draft state.', code: 400 }); + return; + } + + const updateData = { + updatedAt: new Date(), + state: { type: 'posted' }, + postedAt: new Date(), + }; + const result = await editObject({ + model: productStockModel, + id, + updateData, + user: req.user, + }); + + if (result.error) { + logger.error('Error posting product stock:', result.error); + res.status(result.code).send(result); + return; + } + + logger.debug(`Posted product stock with ID: ${id}`); + res.send(result); +};