From 908bb1a3b8064be0bb0db385cc3f2675bed3dd6d Mon Sep 17 00:00:00 2001 From: Bhanu Anish Date: Thu, 22 Jan 2026 00:22:21 -0600 Subject: [PATCH 1/3] feat: add KIInventoryItems schema and add items --- .../KIInventoryController.js | 50 +++++++++++++++++++ .../kitchenandinventory/KIInventoryItems.js | 26 ++++++++++ .../kitchenandinventory/KIInventoryRouter.js | 11 ++++ src/startup/routes.js | 6 +++ 4 files changed, 93 insertions(+) create mode 100644 src/controllers/kitchenandinventory/KIInventoryController.js create mode 100644 src/models/kitchenandinventory/KIInventoryItems.js create mode 100644 src/routes/kitchenandinventory/KIInventoryRouter.js diff --git a/src/controllers/kitchenandinventory/KIInventoryController.js b/src/controllers/kitchenandinventory/KIInventoryController.js new file mode 100644 index 000000000..a813e4aba --- /dev/null +++ b/src/controllers/kitchenandinventory/KIInventoryController.js @@ -0,0 +1,50 @@ +const KIInventoryItem = require('../../models/kitchenandinventory/KIInventoryItems'); + +const KIInventoryController = () => { + const addItem = async (req, res) => { + const { + name, + storedQuantity, + unit, + type, + monthlyUsage, + category, + expiryDate, + location, + onsite, + reorderAt, + lastHarvestDate, + nextHarvestDate, + nextHarvestQuantity, + } = req.body; + + const newItem = new KIInventoryItem({ + name, + storedQuantity, + presentQuantity: storedQuantity, + unit, + type, + monthlyUsage, + category, + expiryDate, + location, + onsite, + reorderAt, + lastHarvestDate, + nextHarvestDate, + nextHarvestQuantity, + }); + + try { + const savedItem = await newItem.save(); + res.status(201).json({ + message: 'Inventory item added successfully', + data: savedItem, + }); + } catch (error) { + res.status(400).json({ message: error.message }); + } + }; + return { addItem }; +}; +module.exports = KIInventoryController; diff --git a/src/models/kitchenandinventory/KIInventoryItems.js b/src/models/kitchenandinventory/KIInventoryItems.js new file mode 100644 index 000000000..891661117 --- /dev/null +++ b/src/models/kitchenandinventory/KIInventoryItems.js @@ -0,0 +1,26 @@ +const mongoose = require('mongoose'); + +const KIInventoryItemSchema = new mongoose.Schema({ + name: { type: String, required: true, trim: true, minlength: 3, maxlength: 50 }, + presentQuantity: { type: Number, required: false }, + storedQuantity: { type: Number, required: true, min: 0 }, + unit: { type: String, required: true }, + type: { type: String, required: true, trim: true }, + monthlyUsage: { type: Number, required: true }, + reorderAt: { type: Number, required: true }, + category: { + type: String, + required: true, + enum: ['INGREDIENT', 'EQUIPEMENTANDSUPPLIES', 'SEEDS', 'CANNINGSUPPLIES', 'ANIMALSUPPLIES'], + }, + createdAt: { type: Date, default: Date.now }, + updatedAt: { type: Date, default: Date.now }, + expiryDate: { type: Date, required: true, min: Date.now }, + location: { type: String, optional: true }, + onsite: { type: Boolean, default: false }, + lastHarvestDate: { type: Date, optional: true, max: Date.now }, + nextHarvestDate: { type: Date, optional: true, min: Date.now }, + nextHarvestQuantity: { type: Number, optional: true }, +}); + +module.exports = mongoose.model('KIInventoryItem', KIInventoryItemSchema); diff --git a/src/routes/kitchenandinventory/KIInventoryRouter.js b/src/routes/kitchenandinventory/KIInventoryRouter.js new file mode 100644 index 000000000..d15cf2952 --- /dev/null +++ b/src/routes/kitchenandinventory/KIInventoryRouter.js @@ -0,0 +1,11 @@ +const express = require('express'); +const controller = require('../../controllers/kitchenandinventory/KIInventoryController')(); + +const router = function () { + const inventoryRouter = express.Router(); + + inventoryRouter.route('/items').post(controller.addItem); + return inventoryRouter; +}; + +module.exports = router; diff --git a/src/startup/routes.js b/src/startup/routes.js index 71526e7a1..9517dbd38 100644 --- a/src/startup/routes.js +++ b/src/startup/routes.js @@ -354,6 +354,9 @@ const badgeSystemRouter = require('../routes/educationPortal/badgeSystemRouter') const promotionDetailsRouter = require('../routes/promotionDetailsRouter'); +// Kitchen and Inventory portal routes +const kitchenInventoryRouter = require('../routes/kitchenandinventory/KIInventoryRouter')(); + module.exports = function (app) { app.use('/api', forgotPwdRouter); app.use('/api', loginRouter); @@ -543,4 +546,7 @@ module.exports = function (app) { app.use('/api/education', browsableLessonPlanRouter); app.use('/api/educator/reports', downloadReportRouter); + + // Kitchen and Inventory portal routes + app.use('/api/kitchenandinventory/inventory', kitchenInventoryRouter); }; From 296353c84d6b96c1cb906df652c255f48a69a5df Mon Sep 17 00:00:00 2001 From: Bhanu Anish Date: Fri, 23 Jan 2026 18:34:55 -0600 Subject: [PATCH 2/3] feat: create get items and update items endpoints --- .../KIInventoryController.js | 70 ++++++++++++++++++- .../kitchenandinventory/KIInventoryRouter.js | 9 ++- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/src/controllers/kitchenandinventory/KIInventoryController.js b/src/controllers/kitchenandinventory/KIInventoryController.js index a813e4aba..9671fb0c4 100644 --- a/src/controllers/kitchenandinventory/KIInventoryController.js +++ b/src/controllers/kitchenandinventory/KIInventoryController.js @@ -17,7 +17,6 @@ const KIInventoryController = () => { nextHarvestDate, nextHarvestQuantity, } = req.body; - const newItem = new KIInventoryItem({ name, storedQuantity, @@ -34,7 +33,6 @@ const KIInventoryController = () => { nextHarvestDate, nextHarvestQuantity, }); - try { const savedItem = await newItem.save(); res.status(201).json({ @@ -45,6 +43,72 @@ const KIInventoryController = () => { res.status(400).json({ message: error.message }); } }; - return { addItem }; + const getItems = async (req, res) => { + try { + const items = await KIInventoryItem.find(null, { __v: 0 }).lean().sort({ createdAt: -1 }); + res.status(200).json({ message: 'All Items fetched successfully.', data: items }); + } catch (err) { + res.status(400).json({ message: 'Something went wrong while fetching items.' }); + } + }; + const getItemsByCategory = async (req, res) => { + const { category } = req.params; + try { + const items = await KIInventoryItem.find({ category }, { __v: 0 }) + .lean() + .sort({ createdAt: -1 }); + res.status(200).json({ message: 'Items fetched successfully.', data: items }); + } catch (err) { + res.status(400).json({ message: 'Something went wrong while fetching items.' }); + } + }; + const updateOnUsage = async (req, res) => { + const { itemId, usedQuantity } = req.body; // Need to implement validation for usedQuantity + try { + const item = await KIInventoryItem.findById(itemId); + if (!item) { + return res.status(404).json({ message: 'Item not found.' }); + } + if (item.expiryDate < new Date()) { + return res.status(400).json({ message: `This item was expired on ${item.expiryDate}` }); + } + let present = item.presentQuantity; + present -= usedQuantity; + if (present < 0) { + present = 0; + } + item.presentQuantity = present; + item.updatedAt = new Date(); + await item.save(); + res.status(200).json({ message: 'Item usage updated successfully.', data: item }); + } catch (err) { + res.status(400).json({ message: err.message }); + } + }; + const updateStoredQuantity = async (req, res) => { + const { itemId, addedQuantity, newExpiry } = req.body; + try { + const item = await KIInventoryItem.findById(itemId); + if (!item) { + return res.status(404).json({ message: 'Item not found.' }); + } + if (item.presentQuantity === 0 || item.expiryDate < new Date()) { + item.storedQuantity = addedQuantity; + item.presentQuantity = 0; + } else { + item.storedQuantity += addedQuantity; + } + item.presentQuantity += addedQuantity; + if (newExpiry) { + item.expiryDate = newExpiry; + } + item.updatedAt = new Date(); + await item.save(); + res.status(200).json({ message: 'Stored quantity updated successfully.', data: item }); + } catch (err) { + res.status(400).json({ message: err.message }); + } + }; + return { addItem, getItems, getItemsByCategory, updateOnUsage, updateStoredQuantity }; }; module.exports = KIInventoryController; diff --git a/src/routes/kitchenandinventory/KIInventoryRouter.js b/src/routes/kitchenandinventory/KIInventoryRouter.js index d15cf2952..4cf4a46ef 100644 --- a/src/routes/kitchenandinventory/KIInventoryRouter.js +++ b/src/routes/kitchenandinventory/KIInventoryRouter.js @@ -3,8 +3,13 @@ const controller = require('../../controllers/kitchenandinventory/KIInventoryCon const router = function () { const inventoryRouter = express.Router(); - - inventoryRouter.route('/items').post(controller.addItem); + // Routes for inventory items + inventoryRouter.route('/items').post(controller.addItem); // Route to add a new inventory item + inventoryRouter.route('/items').get(controller.getItems); // Route to get all inventory items + inventoryRouter.route('/items/:category').get(controller.getItemsByCategory); // Route to get items by category + // Below update endpoints are non-idempotent and meant to be used for specific actions + inventoryRouter.route('/items/updateOnUsage').post(controller.updateOnUsage); // Route to update item on usage + inventoryRouter.route('/items/updateStoredQuantity').post(controller.updateStoredQuantity); // Route to update stored quantity return inventoryRouter; }; From 2812b72604d82108e3870ddfb192ed8ec1c3fd43 Mon Sep 17 00:00:00 2001 From: Bhanu Anish Date: Sat, 24 Jan 2026 16:54:07 -0600 Subject: [PATCH 3/3] feat: add getPreservedStock and updateNextHarvest --- .../KIInventoryController.js | 57 ++++++++++++++++++- .../kitchenandinventory/KIInventoryRouter.js | 6 +- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/controllers/kitchenandinventory/KIInventoryController.js b/src/controllers/kitchenandinventory/KIInventoryController.js index 9671fb0c4..ad79dac1c 100644 --- a/src/controllers/kitchenandinventory/KIInventoryController.js +++ b/src/controllers/kitchenandinventory/KIInventoryController.js @@ -62,8 +62,28 @@ const KIInventoryController = () => { res.status(400).json({ message: 'Something went wrong while fetching items.' }); } }; + const getPreservedStock = async (req, res) => { + const oneyearFromNow = new Date(); + oneyearFromNow.setFullYear(oneyearFromNow.getFullYear() + 1); + try { + const items = await KIInventoryItem.find( + { category: 'INGREDIENT', expiryDate: { $gte: oneyearFromNow } }, + { __v: 0 }, + ) + .lean() + .sort({ presentQuantity: -1 }); + res.status(200).json({ message: 'Preserved stock items fetched successfully.', data: items }); + } catch (err) { + res + .status(400) + .json({ message: 'Something went wrong while fetching preserved stock items.' }); + } + }; const updateOnUsage = async (req, res) => { - const { itemId, usedQuantity } = req.body; // Need to implement validation for usedQuantity + const { itemId, usedQuantity } = req.body; + if (usedQuantity <= 0) { + return res.status(400).json({ message: 'Used quantity must be greater than zero.' }); + } try { const item = await KIInventoryItem.findById(itemId); if (!item) { @@ -87,6 +107,12 @@ const KIInventoryController = () => { }; const updateStoredQuantity = async (req, res) => { const { itemId, addedQuantity, newExpiry } = req.body; + if (addedQuantity <= 0) { + return res.status(400).json({ message: 'Added quantity must be greater than zero.' }); + } + if (newExpiry && new Date(newExpiry) < new Date()) { + return res.status(400).json({ message: 'New expiry date must be a future date.' }); + } try { const item = await KIInventoryItem.findById(itemId); if (!item) { @@ -109,6 +135,33 @@ const KIInventoryController = () => { res.status(400).json({ message: err.message }); } }; - return { addItem, getItems, getItemsByCategory, updateOnUsage, updateStoredQuantity }; + const updateNextHarvest = async (req, res) => { + const { itemId, lastHarvestSuccess, nextHarvestDate, nextHarvestQuantity } = req.body; + try { + const item = await KIInventoryItem.findById(itemId); + if (!item) { + return res.status(404).json({ message: 'Item not found.' }); + } + if (lastHarvestSuccess) { + item.lastHarvestDate = item.nextHarvestDate; + } + item.nextHarvestDate = nextHarvestDate; + item.nextHarvestQuantity = nextHarvestQuantity; + item.updatedAt = new Date(); + await item.save(); + res.status(200).json({ message: 'Next harvest details updated successfully.', data: item }); + } catch (err) { + res.status(400).json({ message: err.message }); + } + }; + return { + addItem, + getItems, + getItemsByCategory, + getPreservedStock, + updateOnUsage, + updateStoredQuantity, + updateNextHarvest, + }; }; module.exports = KIInventoryController; diff --git a/src/routes/kitchenandinventory/KIInventoryRouter.js b/src/routes/kitchenandinventory/KIInventoryRouter.js index 4cf4a46ef..dc4fcf83f 100644 --- a/src/routes/kitchenandinventory/KIInventoryRouter.js +++ b/src/routes/kitchenandinventory/KIInventoryRouter.js @@ -7,9 +7,11 @@ const router = function () { inventoryRouter.route('/items').post(controller.addItem); // Route to add a new inventory item inventoryRouter.route('/items').get(controller.getItems); // Route to get all inventory items inventoryRouter.route('/items/:category').get(controller.getItemsByCategory); // Route to get items by category + inventoryRouter.route('/items/ingredients/preserved').get(controller.getPreservedStock); // Route to get preserved items // Below update endpoints are non-idempotent and meant to be used for specific actions - inventoryRouter.route('/items/updateOnUsage').post(controller.updateOnUsage); // Route to update item on usage - inventoryRouter.route('/items/updateStoredQuantity').post(controller.updateStoredQuantity); // Route to update stored quantity + inventoryRouter.route('/items/usage').post(controller.updateOnUsage); // Route to update item on usage + inventoryRouter.route('/items/storedQuantity').post(controller.updateStoredQuantity); // Route to update stored quantity + inventoryRouter.route('/items/nextHarvest').put(controller.updateNextHarvest); // Route to update next harvest details return inventoryRouter; };