From 4bd289838083729263d3d0b038c31e59a05a3324 Mon Sep 17 00:00:00 2001 From: Ram-blip Date: Fri, 27 Mar 2026 16:36:02 +0530 Subject: [PATCH 1/3] fix(bm-dashboard): separate reusable inventory type create flow from tools --- .../bmdashboard/bmInventoryTypeController.js | 227 +++++++++--------- src/models/bmdashboard/buildingUnit.js | 10 + .../bmdashboard/bmInventoryTypeRouter.js | 17 +- src/startup/routes.js | 2 + 4 files changed, 137 insertions(+), 119 deletions(-) create mode 100644 src/models/bmdashboard/buildingUnit.js diff --git a/src/controllers/bmdashboard/bmInventoryTypeController.js b/src/controllers/bmdashboard/bmInventoryTypeController.js index 4f59f06d1..357331384 100644 --- a/src/controllers/bmdashboard/bmInventoryTypeController.js +++ b/src/controllers/bmdashboard/bmInventoryTypeController.js @@ -1,14 +1,12 @@ -const fs = require('fs'); -const path = require('path'); - -const filename = 'BuildingUnits.json'; -const currentFilePath = __filename; -const rootPath = path.resolve(path.dirname(currentFilePath), '../../../'); // Go up three levels to the root -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, + BuildingUnit, +) { async function fetchMaterialTypes(req, res) { try { MatType.find() @@ -68,23 +66,10 @@ function bmInventoryTypeController(InvType, MatType, ConsType, ReusType, ToolTyp const fetchInvUnitsFromJson = async (req, res) => { try { - console.log(__dirname, filepath); - readFile(filepath, 'utf8', (err, data) => { - if (err) { - console.error('Error reading file:', err); - res.status(500).send(err); - } - - try { - const jsonData = JSON.parse(data); - res.status(200).send(jsonData); - } catch (parseError) { - console.error('Error parsing JSON:', parseError); - res.status(500).send(parseError); - } - }); + const units = await BuildingUnit.find().exec(); + res.status(200).send(units); } catch (err) { - res.json(err); + res.status(500).json({ error: err.message }); } }; @@ -109,31 +94,19 @@ function bmInventoryTypeController(InvType, MatType, ConsType, ReusType, ToolTyp createdBy: requestorId, }; MatType.create(newDoc) - .then((results) => { + .then(async (results) => { res.status(201).send(results); if (req.body.customUnit) { try { - // Add new unit to json file : src\controllers\bmdashboard\BuildingUnits.json - const newItem = { unit: req.body.customUnit, category: 'Material' }; - const newItemString = JSON.stringify(newItem, null, 2); - readFile(filepath, 'utf8', (err, data) => { - if (err) { - console.error('Error reading file:', err); - return; - } - // Remove the last array bracket and comma - const updatedContent = data.trim().replace(/\s*]$/, ''); - - // Add a comma and newline if the file is not empty - const separator = updatedContent !== '' ? ',\n' : ''; - const updatedFileContent = `${updatedContent}${separator}${newItemString}\n]`; - - writeFile(filepath, updatedFileContent, 'utf8', (error) => { - if (error) { - console.error('Error writing to file:', error); - } - }); + const exists = await BuildingUnit.findOne({ + unit: { $regex: new RegExp(`^${req.body.customUnit}$`, 'i') }, }); + if (!exists) { + await BuildingUnit.create({ + unit: req.body.customUnit, + category: 'Material', + }); + } } catch (e) { console.log(e); } @@ -266,6 +239,74 @@ function bmInventoryTypeController(InvType, MatType, ConsType, ReusType, ToolTyp } } + async function addReusableType(req, res) { + const { + name, + description, + invoice, + purchaseRental, + fromDate, + toDate, + condition, + phoneNumber, + quantity, + currency, + unitPrice, + shippingFee, + taxes, + totalPriceWithShipping, + images, + link, + requestor: { requestorId }, + } = req.body; + + try { + ReusType.find({ name }) + .then((result) => { + if (result.length) { + res.status(409).send('Oops!! Reusable already exists!'); + } else { + const newDoc = { + category: 'Reusable', + name, + description, + invoice, + purchaseRental, + fromDate, + toDate, + condition, + phoneNumber, + quantity, + currency, + unitPrice, + shippingFee, + taxes, + totalPriceWithShipping, + images, + link, + createdBy: requestorId, + }; + ReusType.create(newDoc) + .then((results) => { + res.status(201).send(results); + }) + .catch((error) => { + if (error._message.includes('validation failed')) { + res.status(400).send(error.errors.unit.message); + } else { + res.status(500).send(error); + } + }); + } + }) + .catch((error) => { + res.status(500).send(error); + }); + } catch (error) { + res.status(500).send(error); + } + } + async function fetchInventoryByType(req, res) { const { type } = req.params; let SelectedType = InvType; @@ -432,7 +473,6 @@ function bmInventoryTypeController(InvType, MatType, ConsType, ReusType, ToolTyp } }; - // POST - Add a new unit to the JSON file const addInventoryUnit = async (req, res) => { try { const { unit, category } = req.body; @@ -441,87 +481,43 @@ function bmInventoryTypeController(InvType, MatType, ConsType, ReusType, ToolTyp return res.status(400).json({ error: 'Unit is required' }); } - readFile(filepath, 'utf8', (err, data) => { - if (err) { - console.error('Error reading file:', err); - return res.status(500).json({ error: 'Error reading units file' }); - } - - try { - const jsonData = JSON.parse(data); - - // Check if unit already exists - const exists = jsonData.some( - (item) => item.unit.toLowerCase() === unit.toLowerCase(), - ); - if (exists) { - return res.status(409).json({ error: 'Unit already exists' }); - } - - // Add new unit - const newUnit = { unit, category: category || 'Material' }; - jsonData.push(newUnit); - - writeFile(filepath, JSON.stringify(jsonData, null, 2), 'utf8', (writeErr) => { - if (writeErr) { - console.error('Error writing to file:', writeErr); - return res.status(500).json({ error: 'Error saving unit' }); - } - res.status(201).json(newUnit); - }); - } catch (parseError) { - console.error('Error parsing JSON:', parseError); - res.status(500).json({ error: 'Error parsing units file' }); - } + const exists = await BuildingUnit.findOne({ + unit: { $regex: new RegExp(`^${unit}$`, 'i') }, }); + if (exists) { + return res.status(409).json({ error: 'Unit already exists' }); + } + + const newUnit = await BuildingUnit.create({ unit, category: category || 'Material' }); + res.status(201).json(newUnit); } catch (error) { res.status(500).json({ error: error.message }); } }; - // DELETE - Remove a unit from the JSON file by unit name const deleteInventoryUnit = async (req, res) => { try { const { unitName } = req.params; if (!unitName) { - return res.status(400).json({ error: 'Unit name is required' }); + return res.status(400).json({ error: 'Unit identifier is required' }); } - readFile(filepath, 'utf8', (err, data) => { - if (err) { - console.error('Error reading file:', err); - return res.status(500).json({ error: 'Error reading units file' }); - } - - try { - const jsonData = JSON.parse(data); - - // Find index of unit to delete - const decodedUnitName = decodeURIComponent(unitName); - const unitIndex = jsonData.findIndex( - (item) => item.unit.toLowerCase() === decodedUnitName.toLowerCase(), - ); + let deleted; + if (unitName.match(/^[0-9a-fA-F]{24}$/)) { + deleted = await BuildingUnit.findByIdAndDelete(unitName); + } else { + const decodedUnitName = decodeURIComponent(unitName); + deleted = await BuildingUnit.findOneAndDelete({ + unit: { $regex: new RegExp(`^${decodedUnitName}$`, 'i') }, + }); + } - if (unitIndex === -1) { - return res.status(404).json({ error: 'Unit not found' }); - } + if (!deleted) { + return res.status(404).json({ error: 'Unit not found' }); + } - // Remove the unit - jsonData.splice(unitIndex, 1); - - writeFile(filepath, JSON.stringify(jsonData, null, 2), 'utf8', (writeErr) => { - if (writeErr) { - console.error('Error writing to file:', writeErr); - return res.status(500).json({ error: 'Error deleting unit' }); - } - res.status(200).json({ message: 'Unit deleted successfully' }); - }); - } catch (parseError) { - console.error('Error parsing JSON:', parseError); - res.status(500).json({ error: 'Error parsing units file' }); - } - }); + res.status(200).json({ message: 'Unit deleted successfully' }); } catch (error) { res.status(500).json({ error: error.message }); } @@ -539,6 +535,7 @@ function bmInventoryTypeController(InvType, MatType, ConsType, ReusType, ToolTyp addMaterialType, addConsumableType, addToolType, + addReusableType, fetchInvUnitsFromJson, fetchInventoryByType, updateInventoryType, diff --git a/src/models/bmdashboard/buildingUnit.js b/src/models/bmdashboard/buildingUnit.js new file mode 100644 index 000000000..33ff82bd1 --- /dev/null +++ b/src/models/bmdashboard/buildingUnit.js @@ -0,0 +1,10 @@ +const mongoose = require('mongoose'); + +const { Schema } = mongoose; + +const buildingUnitSchema = new Schema({ + unit: { type: String, required: true, unique: true }, + category: { type: String, default: 'Material' }, +}); + +module.exports = mongoose.model('buildingUnit', buildingUnitSchema, 'buildingUnits'); diff --git a/src/routes/bmdashboard/bmInventoryTypeRouter.js b/src/routes/bmdashboard/bmInventoryTypeRouter.js index db68d0a8b..df32f8226 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, + buildingUnit, +) { 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, + buildingUnit, ); // Route for fetching all material types @@ -22,6 +31,8 @@ const routes = function (baseInvType, matType, consType, reusType, toolType, equ inventoryTypeRouter.route('/tools').post(controller.addToolType); + inventoryTypeRouter.route('/invtypes/reusable').post(controller.addReusableType); + inventoryTypeRouter.route('/invtypes/tools').get(controller.fetchToolTypes); inventoryTypeRouter.route('/invtypes/equipment').post(controller.addEquipmentType); @@ -54,9 +65,7 @@ const routes = function (baseInvType, matType, consType, reusType, toolType, equ .get(controller.fetchInvUnitsFromJson) .post(controller.addInventoryUnit); - inventoryTypeRouter - .route('/inventoryUnits/:unitName') - .delete(controller.deleteInventoryUnit); + inventoryTypeRouter.route('/inventoryUnits/:unitName').delete(controller.deleteInventoryUnit); return inventoryTypeRouter; }; diff --git a/src/startup/routes.js b/src/startup/routes.js index 7daec51a6..60593d19f 100644 --- a/src/startup/routes.js +++ b/src/startup/routes.js @@ -86,6 +86,7 @@ const { buildingTool, buildingEquipment, } = require('../models/bmdashboard/buildingInventoryItem'); +const buildingUnit = require('../models/bmdashboard/buildingUnit'); const dashboardMetrics = require('../models/bmdashboard/dashboardMetrics'); const bmTimeLog = require('../models/bmdashboard/buildingTimeLogger'); @@ -222,6 +223,7 @@ const bmInventoryTypeRouter = require('../routes/bmdashboard/bmInventoryTypeRout reusableType, toolType, equipmentType, + buildingUnit, ); const toolAvailabilityRoutes = require('../routes/bmdashboard/bmToolAvailabilityRoutes'); From 845387acb7caf5928746daea1f8abbcbc4fd43ec Mon Sep 17 00:00:00 2001 From: Ram-blip Date: Sat, 11 Apr 2026 14:00:01 +0530 Subject: [PATCH 2/3] fix(test): mock playwrightUtil instead of missing puppeteer in checkPersonalMax spec --- src/helpers/__tests__/checkPersonalMax.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/__tests__/checkPersonalMax.spec.js b/src/helpers/__tests__/checkPersonalMax.spec.js index 81c74d871..83bc6c388 100644 --- a/src/helpers/__tests__/checkPersonalMax.spec.js +++ b/src/helpers/__tests__/checkPersonalMax.spec.js @@ -34,7 +34,7 @@ jest.mock('../../utilities/nodeCache', () => () => ({ jest.mock('../../startup/logger', () => ({ logException: jest.fn(), })); -jest.mock('puppeteer', () => ({})); +jest.mock('../../utilities/playwrightUtil', () => jest.fn()); jest.mock('sharp', () => ({})); /* ======================= From f7f17694f0b270bae513527ae357e2a09209df85 Mon Sep 17 00:00:00 2001 From: Ram-blip Date: Wed, 6 May 2026 19:13:28 +0530 Subject: [PATCH 3/3] fix(bm-dashboard): resolve sonarcloud security and duplication issues in inventory type controller --- .../bmdashboard/bmInventoryTypeController.js | 100 ++++-------------- 1 file changed, 21 insertions(+), 79 deletions(-) diff --git a/src/controllers/bmdashboard/bmInventoryTypeController.js b/src/controllers/bmdashboard/bmInventoryTypeController.js index 7416c8aef..18852f6c7 100644 --- a/src/controllers/bmdashboard/bmInventoryTypeController.js +++ b/src/controllers/bmdashboard/bmInventoryTypeController.js @@ -101,12 +101,13 @@ function bmInventoryTypeController( res.status(201).send(results); if (req.body.customUnit) { try { + const trimmedUnit = req.body.customUnit.trim(); const exists = await BuildingUnit.findOne({ - unit: { $regex: new RegExp(`^${req.body.customUnit}$`, 'i') }, - }); + unit: trimmedUnit, + }).collation({ locale: 'en', strength: 2 }); if (!exists) { await BuildingUnit.create({ - unit: req.body.customUnit, + unit: trimmedUnit, category: 'Material', }); } @@ -174,7 +175,7 @@ function bmInventoryTypeController( } } - async function addToolType(req, res) { + async function addPurchasableItemType(Model, category, req, res) { const { name, description, @@ -196,13 +197,13 @@ function bmInventoryTypeController( } = req.body; try { - ToolType.find({ name }) + Model.find({ name }) .then((result) => { if (result.length) { - res.status(409).send('Oops!! Tool already exists!'); + res.status(409).send(`Oops!! ${category} already exists!`); } else { const newDoc = { - category: 'Tool', + category, name, description, invoice, @@ -221,7 +222,7 @@ function bmInventoryTypeController( link, createdBy: requestorId, }; - ToolType.create(newDoc) + Model.create(newDoc) .then((results) => { res.status(201).send(results); }) @@ -242,72 +243,12 @@ function bmInventoryTypeController( } } - async function addReusableType(req, res) { - const { - name, - description, - invoice, - purchaseRental, - fromDate, - toDate, - condition, - phoneNumber, - quantity, - currency, - unitPrice, - shippingFee, - taxes, - totalPriceWithShipping, - images, - link, - requestor: { requestorId }, - } = req.body; + async function addToolType(req, res) { + return addPurchasableItemType(ToolType, 'Tool', req, res); + } - try { - ReusType.find({ name }) - .then((result) => { - if (result.length) { - res.status(409).send('Oops!! Reusable already exists!'); - } else { - const newDoc = { - category: 'Reusable', - name, - description, - invoice, - purchaseRental, - fromDate, - toDate, - condition, - phoneNumber, - quantity, - currency, - unitPrice, - shippingFee, - taxes, - totalPriceWithShipping, - images, - link, - createdBy: requestorId, - }; - ReusType.create(newDoc) - .then((results) => { - res.status(201).send(results); - }) - .catch((error) => { - if (error._message.includes('validation failed')) { - res.status(400).send(error.errors.unit.message); - } else { - res.status(500).send(error); - } - }); - } - }) - .catch((error) => { - res.status(500).send(error); - }); - } catch (error) { - res.status(500).send(error); - } + async function addReusableType(req, res) { + return addPurchasableItemType(ReusType, 'Reusable', req, res); } async function fetchInventoryByType(req, res) { @@ -504,15 +445,16 @@ function bmInventoryTypeController( const addInventoryUnit = async (req, res) => { try { - const { unit, category } = req.body; + const { category } = req.body; + const unit = req.body.unit?.trim(); if (!unit) { return res.status(400).json({ error: 'Unit is required' }); } const exists = await BuildingUnit.findOne({ - unit: { $regex: new RegExp(`^${unit}$`, 'i') }, - }); + unit, + }).collation({ locale: 'en', strength: 2 }); if (exists) { return res.status(409).json({ error: 'Unit already exists' }); } @@ -536,10 +478,10 @@ function bmInventoryTypeController( if (unitName.match(/^[0-9a-fA-F]{24}$/)) { deleted = await BuildingUnit.findByIdAndDelete(unitName); } else { - const decodedUnitName = decodeURIComponent(unitName); + const decodedUnitName = decodeURIComponent(unitName).trim(); deleted = await BuildingUnit.findOneAndDelete({ - unit: { $regex: new RegExp(`^${decodedUnitName}$`, 'i') }, - }); + unit: decodedUnitName, + }).collation({ locale: 'en', strength: 2 }); } if (!deleted) {