diff --git a/src/controllers/bmdashboard/bmInventoryTypeController.js b/src/controllers/bmdashboard/bmInventoryTypeController.js index d52ce6c77..86182d91d 100644 --- a/src/controllers/bmdashboard/bmInventoryTypeController.js +++ b/src/controllers/bmdashboard/bmInventoryTypeController.js @@ -8,7 +8,15 @@ const filepath = path.join(rootPath, filename); const { readFile } = fs; const { writeFile } = fs; -function bmInventoryTypeController(InvType, MatType, ConsType, ReusType, ToolType, EquipType) { +function bmInventoryTypeController( + InvType, + MatType, + ConsType, + ReusType, + ToolType, + EquipType, + invTypeHistory, +) { async function fetchMaterialTypes(req, res) { try { MatType.find() @@ -362,32 +370,95 @@ function bmInventoryTypeController(InvType, MatType, ConsType, ReusType, ToolTyp const updateNameAndUnit = async (req, res) => { try { const { invtypeId } = req.params; - const { name, unit } = req.body; + const { + name, + unit, + requestor: { requestorId }, + } = req.body; + // 1. Fetch existing document + const invType = await MatType.findById(invtypeId); + if (!invType) { + return res.status(404).send('invType Material not found check Id'); + } + + // 2. Name uniqueness check + if (name && name !== invType.name) { + const existingInvType = await MatType.findOne({ + name, + _id: { $ne: invtypeId }, + }); + + if (existingInvType) { + return res.status(404).send('Material name already exists'); + } + } + + const historyDocs = []; const updateData = {}; - if (name) { + // 3. Track name change + if (name && name !== invType.name) { + historyDocs.push({ + invtypeId, + field: 'name', + oldValue: invType.name, + newValue: name, + editedBy: requestorId, + }); updateData.name = name; } - if (unit) { + // 4. Track unit change + if (unit && unit !== invType.unit) { + historyDocs.push({ + invtypeId, + field: 'unit', + oldValue: invType.unit, + newValue: unit, + editedBy: requestorId, + }); updateData.unit = unit; } - const updatedInvType = await InvType.findByIdAndUpdate(invtypeId, updateData, { + // 5. Save history (if any) + if (historyDocs.length > 0) { + await invTypeHistory.insertMany(historyDocs); + } + + // 6. Update main document + const updatedInvType = await MatType.findByIdAndUpdate(invtypeId, updateData, { new: true, runValidators: true, }); - if (!updatedInvType) { - return res.status(404).json({ error: 'invType Material not found check Id' }); - } - res.status(200).json(updatedInvType); } catch (error) { + console.error(error); res.status(500).send(error); } }; + + const fetchInvTypeHistory = async (req, res) => { + try { + const { invtypeId } = req.params; + + if (!invtypeId || !invtypeId.match(/^[0-9a-fA-F]{24}$/)) { + return res.status(400).json({ message: 'Invalid inventory type id' }); + } + + const history = await invTypeHistory + .find({ invtypeId }) + .populate('editedBy', '_id firstName lastName email') + .sort({ editedAt: -1 }) + .lean(); + + res.status(200).json(history); + } catch (error) { + console.error('Fetch history error:', error); + res.status(500).json({ message: 'Failed to fetch inventory history' }); + } + }; return { fetchMaterialTypes, fetchConsumableTypes, @@ -402,6 +473,7 @@ function bmInventoryTypeController(InvType, MatType, ConsType, ReusType, ToolTyp addToolType, fetchInvUnitsFromJson, fetchInventoryByType, + fetchInvTypeHistory, }; } diff --git a/src/models/bmdashboard/buildingInventoryType.js b/src/models/bmdashboard/buildingInventoryType.js index 7dcaa38dc..ff4a0298c 100644 --- a/src/models/bmdashboard/buildingInventoryType.js +++ b/src/models/bmdashboard/buildingInventoryType.js @@ -23,10 +23,13 @@ const invTypeBase = mongoose.model('invTypeBase', invTypeBaseSchema, 'buildingIn // ex: sand, stone, brick, lumber -const materialType = invTypeBase.discriminator('material_type', new mongoose.Schema({ - category: { type: String, enum: ['Material'] }, - unit: { type: String, required: true }, // unit of measurement -})); +const materialType = invTypeBase.discriminator( + 'material_type', + new mongoose.Schema({ + category: { type: String, enum: ['Material'] }, + unit: { type: String, required: true }, // unit of measurement + }), +); //--------------------------- // CONSUMABLE TYPE @@ -34,11 +37,14 @@ const materialType = invTypeBase.discriminator('material_type', new mongoose.Sch // ex: screws, nails, staples -const consumableType = invTypeBase.discriminator('consumable_type', new mongoose.Schema({ - category: { type: String, enum: ['Consumable'] }, - unit: { type: String, required: true }, - size: { type: String, required: false }, -})); +const consumableType = invTypeBase.discriminator( + 'consumable_type', + new mongoose.Schema({ + category: { type: String, enum: ['Consumable'] }, + unit: { type: String, required: true }, + size: { type: String, required: false }, + }), +); //--------------------------- // REUSABLE TYPE @@ -46,9 +52,12 @@ const consumableType = invTypeBase.discriminator('consumable_type', new mongoose // ex: gloves, brushes, hammers, screwdrivers -const reusableType = invTypeBase.discriminator('reusable_type', new mongoose.Schema({ - category: { type: String, enum: ['Reusable'] }, -})); +const reusableType = invTypeBase.discriminator( + 'reusable_type', + new mongoose.Schema({ + category: { type: String, enum: ['Reusable'] }, + }), +); //--------------------------- // TOOL TYPE @@ -56,26 +65,29 @@ const reusableType = invTypeBase.discriminator('reusable_type', new mongoose.Sch // ex: shovels, wheelbarrows, power drills, jackhammers -const toolType = invTypeBase.discriminator('tool_type', new mongoose.Schema({ - category: { type: String, enum: ['Tool'] }, - invoice: String, - purchaseRental: String, - fromDate: Date, - toDate:Date, - condition: String, - phoneNumber: String, - quantity: Number, - currency: String, - unitPrice: Number, - shippingFee: Number, - taxes: Number, - totalPriceWithShipping: Number, - images: String, - link: String, - - // isPowered: { type: Boolean, required: true }, - // powerSource: { type: String, required: () => this.isPowered }, // required if isPowered = true (syntax?) -})); +const toolType = invTypeBase.discriminator( + 'tool_type', + new mongoose.Schema({ + category: { type: String, enum: ['Tool'] }, + invoice: String, + purchaseRental: String, + fromDate: Date, + toDate: Date, + condition: String, + phoneNumber: String, + quantity: Number, + currency: String, + unitPrice: Number, + shippingFee: Number, + taxes: Number, + totalPriceWithShipping: Number, + images: String, + link: String, + + // isPowered: { type: Boolean, required: true }, + // powerSource: { type: String, required: () => this.isPowered }, // required if isPowered = true (syntax?) + }), +); //--------------------------- // EQUIPMENT TYPE @@ -83,10 +95,49 @@ const toolType = invTypeBase.discriminator('tool_type', new mongoose.Schema({ // ex: tractors, excavators -const equipmentType = invTypeBase.discriminator('equipment_type', new mongoose.Schema({ - category: { type: String, enum: ['Equipment'] }, - fuelType: { type: String, enum: ['Diesel', 'Biodiesel', 'Gasoline', 'Natural Gas', 'Ethanol'], required: true }, -})); +const equipmentType = invTypeBase.discriminator( + 'equipment_type', + new mongoose.Schema({ + category: { type: String, enum: ['Equipment'] }, + fuelType: { + type: String, + enum: ['Diesel', 'Biodiesel', 'Gasoline', 'Natural Gas', 'Ethanol'], + required: true, + }, + }), +); +/* ========================= + INVENTORY TYPE HISTORY +========================= */ + +const invTypeHistorySchema = new Schema({ + invtypeId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'invTypeBase', + required: true, + }, + field: { + type: String, + required: true, + }, + oldValue: { + type: Schema.Types.Mixed, + }, + newValue: { + type: Schema.Types.Mixed, + }, + editedBy: { + type: mongoose.Schema.Types.ObjectId, + ref: 'userProfile', + required: true, + }, + editedAt: { + type: Date, + default: Date.now, + }, +}); + +const invTypeHistory = mongoose.model('invTypeHistory', invTypeHistorySchema); module.exports = { invTypeBase, @@ -95,4 +146,5 @@ module.exports = { reusableType, toolType, equipmentType, + invTypeHistory, }; diff --git a/src/routes/bmdashboard/bmInventoryTypeRouter.js b/src/routes/bmdashboard/bmInventoryTypeRouter.js index 2f57105e5..0d62c061c 100644 --- a/src/routes/bmdashboard/bmInventoryTypeRouter.js +++ b/src/routes/bmdashboard/bmInventoryTypeRouter.js @@ -1,6 +1,14 @@ const express = require('express'); -const routes = function (baseInvType, matType, consType, reusType, toolType, equipType) { +const routes = function ( + baseInvType, + matType, + consType, + reusType, + toolType, + equipType, + invTypeHistory, +) { const inventoryTypeRouter = express.Router(); const controller = require('../../controllers/bmdashboard/bmInventoryTypeController')( baseInvType, @@ -9,6 +17,7 @@ const routes = function (baseInvType, matType, consType, reusType, toolType, equ reusType, toolType, equipType, + invTypeHistory, ); // Route for fetching all material types @@ -40,6 +49,7 @@ const routes = function (baseInvType, matType, consType, reusType, toolType, equ .put(controller.updateNameAndUnit); inventoryTypeRouter.route('/inventoryUnits').get(controller.fetchInvUnitsFromJson); + inventoryTypeRouter.route('/invtypes/:invtypeId/history').get(controller.fetchInvTypeHistory); return inventoryTypeRouter; }; diff --git a/src/startup/routes.js b/src/startup/routes.js index 71526e7a1..a78294e6c 100644 --- a/src/startup/routes.js +++ b/src/startup/routes.js @@ -73,6 +73,7 @@ const { reusableType, toolType, equipmentType, + invTypeHistory, } = require('../models/bmdashboard/buildingInventoryType'); const { buildingConsumable, @@ -211,6 +212,7 @@ const bmInventoryTypeRouter = require('../routes/bmdashboard/bmInventoryTypeRout reusableType, toolType, equipmentType, + invTypeHistory, ); const toolAvailabilityRoutes = require('../routes/bmdashboard/bmToolAvailabilityRoutes');