Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('mapLocations routes', () => {
console.log('Testing 401 unauthorized access...');

try {
const responses = await Promise.all([
await Promise.all([
agent.post('/api/mapLocations').send(reqBody).expect(401),
agent.get('/api/mapLocations/randomId').send(reqBody).expect(401),
agent.put(`/api/mapLocations/randomId`).send(reqBody).expect(401),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('PopupEditorBackups tests', () => {
console.log('Testing 401 unauthorized access...');

try {
const responses = await Promise.all([
await Promise.all([
agent.post('/api/popupEditorBackup').send(reqBody).expect(401),
agent.get('/api/popupEditorBackup/randomId').send(reqBody).expect(401),
agent.put(`/api/popupEditorBackup/randomId`).send(reqBody).expect(401),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('rolePreset routes', () => {
console.log('Testing 401 unauthorized access...');

try {
const responses = await Promise.all([
await Promise.all([
agent.post('/api/rolePreset').send(reqBody).expect(401),
agent.get('/api/rolePreset/randomRoleName').send(reqBody).expect(401),
agent.put(`/api/rolePreset/randomId`).send(reqBody).expect(401),
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/integration/wbsRoutes.integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('wbsRouter tests', () => {
console.log('Testing 401 unauthorized access...');

try {
const responses = await Promise.all([
await Promise.all([
agent.post('/api/wbs').send(reqBody).expect(401),
agent.get('/api/wbs/randomId').send(reqBody).expect(401),
agent.put(`/api/wbs/randomId`).send(reqBody).expect(401),
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/badgeController.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const UserProfile = require('../models/userProfile');
const helper = require('../utilities/permissions');
const escapeRegex = require('../utilities/escapeRegex');
const cacheClosure = require('../utilities/nodeCache');
//const userHelper = require('../helpers/userHelper')();
// const userHelper = require('../helpers/userHelper')();

const badgeController = function (Badge) {
/**
Expand Down Expand Up @@ -354,7 +354,7 @@ const badgeController = function (Badge) {
};

return {
//awardBadgesTest,
// awardBadgesTest,
getAllBadges,
assignBadges,
postBadge,
Expand Down
8 changes: 3 additions & 5 deletions src/controllers/bmdashboard/bmMaterialsController.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,11 +249,9 @@ const bmMaterialsController = function (BuildingMaterial) {
if (status === 'Approved') {
updateObject.$inc = { stockBought: quantity };
}
const updatedMaterial = await BuildingMaterial.findOneAndUpdate(
{ 'purchaseRecord._id': purchaseId },
updateObject,
{ new: true },
);
await BuildingMaterial.findOneAndUpdate({ 'purchaseRecord._id': purchaseId }, updateObject, {
new: true,
});
res.status(200).send(`Purchase ${status.toLowerCase()} successfully`);
} catch (error) {
res.status(500).send(error.message);
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/bmdashboard/bmProjectController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable prefer-destructuring */
const mongoose = require('mongoose');
const BuildingProject = require('../../models/bmdashboard/buildingProject');
// const BuildingProject = require('../../models/bmdashboard/buildingProject');
const Task = require('../../models/task');
// TODO: uncomment when executing auth checks
// const jwt = require('jsonwebtoken');
Expand Down
167 changes: 167 additions & 0 deletions src/controllers/bmdashboard/injuryCategoryController.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,170 @@ exports.getProjectsWithInjuries = async (req, res) => {
res.status(500).json({ error: 'Internal server error' });
}
};

// Returns monthly injury counts per severity between startDate and endDate for an optional projectId
// Response shape:
// {
// months: ['Jan', 'Feb', ...],
// serious: [..],
// medium: [..],
// low: [..]
// }
exports.getInjuryTrendData = async (req, res) => {
try {
const { projectId, startDate, endDate } = req.query || {};

// Build match using existing helpers for date parsing/validation
const { match, invalidDate } = buildMatch({ projectIds: projectId, startDate, endDate });
if (invalidDate)
return res
.status(400)
.json({ error: 'Invalid startDate or endDate (use YYYY-MM-DD or ISO)' });

// Defaults: last 12 months if no range provided
const now = new Date();
const defaultEnd = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1, 0, 0, 0, 0));
const defaultStart = new Date(
Date.UTC(defaultEnd.getUTCFullYear(), defaultEnd.getUTCMonth() - 11, 1, 0, 0, 0, 0),
);

const start = match.date?.$gte || defaultStart;
const endExclusive =
match.date?.$lt ||
new Date(Date.UTC(defaultEnd.getUTCFullYear(), defaultEnd.getUTCMonth() + 1, 1, 0, 0, 0, 0));

// Ensure match uses our computed range bounds
match.date = { $gte: start, $lt: endExclusive };

// Aggregate by year-month and severity
const agg = await InjuryCategory.aggregate([
{ $match: match },
{
$group: {
_id: {
y: { $year: '$date' },
m: { $month: '$date' },
s: '$severity',
},
c: { $sum: { $ifNull: ['$count', 0] } },
},
},
{
$project: {
_id: 0,
year: '$_id.y',
month: '$_id.m',
severity: '$_id.s',
count: '$c',
},
},
{ $sort: { year: 1, month: 1 } },
]).option({ allowDiskUse: true });

const monthNames = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];

// Build ordered list of months from start..endExclusive stepping by 1 month
const labels = [];
const monthKeys = [];
{
const d = new Date(start);
while (d < endExclusive) {
labels.push(monthNames[d.getUTCMonth()]);
monthKeys.push(`${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}`);
d.setUTCMonth(d.getUTCMonth() + 1);
}
}

// Map of key(year-month)->severity->count
const map = new Map();
agg.forEach((r) => {
const key = `${r.year}-${String(r.month).padStart(2, '0')}`;
if (!map.has(key)) map.set(key, {});
const m2 = map.get(key);
const sev = String(r.severity || '').toLowerCase();
m2[sev] = (m2[sev] || 0) + (Number(r.count) || 0);
});

const seriesSerious = [];
const seriesMedium = [];
const seriesLow = [];
monthKeys.forEach((k) => {
const entry = map.get(k) || {};
seriesSerious.push(entry.serious || 0);
seriesMedium.push(entry.medium || 0);
seriesLow.push(entry.low || 0);
});

res
.status(200)
.json({ months: labels, serious: seriesSerious, medium: seriesMedium, low: seriesLow });
} catch (err) {
console.error('[getInjuryTrendData] Error:', err);
res.status(500).json({ error: 'Internal server error' });
}
};

// Create injury records (production)
exports.createInjuries = async (req, res) => {
try {
const body = Array.isArray(req.body) ? req.body : [req.body];
if (!body.length) return res.status(400).json({ error: 'Empty payload' });

const allowedSeverity = new Map([
['serious', 'Serious'],
['medium', 'Medium'],
['low', 'Low'],
]);

const normalize = (x = {}) => {
const { projectId, projectName, date, injuryType, workerCategory, severity, count } = x;

if (!projectId || !mongoose.Types.ObjectId.isValid(projectId)) {
throw new Error('projectId is required and must be a valid ObjectId');
}

const d = parseDateFlexibleUTC(date);
if (!d) throw new Error('Invalid or missing date (use YYYY-MM-DD or ISO)');

const sevNorm = allowedSeverity.get(
String(severity || '')
.trim()
.toLowerCase(),
);
if (!sevNorm) throw new Error('severity must be one of: Serious | Medium | Low');

return {
projectId: new mongoose.Types.ObjectId(projectId),
projectName: projectName ? String(projectName) : undefined,
date: d,
injuryType: injuryType ? String(injuryType) : undefined,
workerCategory: workerCategory ? String(workerCategory) : undefined,
severity: sevNorm,
count: Number(count ?? 1),
};
};

const docs = body.map(normalize);
const result = await InjuryCategory.insertMany(docs, { ordered: false });
return res.status(201).json({ insertedCount: result.length, docs: result });
} catch (err) {
const msg = err?.message || 'Failed to create injuries';
console.error('[createInjuries] Error:', err);
// 400 for validation, 500 for others
if (/required|invalid|must be/i.test(msg)) return res.status(400).json({ error: msg });
return res.status(500).json({ error: 'Internal server error' });
}
};
19 changes: 9 additions & 10 deletions src/routes/bmdashboard/bmMaterialsRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,21 @@ const express = require('express');

const routes = function (buildingMaterial) {
const materialsRouter = express.Router();
const controller = require('../../controllers/bmdashboard/bmMaterialsController')(buildingMaterial);
materialsRouter.route('/materials')
const controller = require('../../controllers/bmdashboard/bmMaterialsController')(
buildingMaterial,
);
materialsRouter
.route('/materials')
.get(controller.bmMaterialsList)
.post(controller.bmPurchaseMaterials);

materialsRouter.route('/updateMaterialRecord')
.post(controller.bmPostMaterialUpdateRecord);
materialsRouter.route('/updateMaterialRecord').post(controller.bmPostMaterialUpdateRecord);

materialsRouter.route('/updateMaterialRecordBulk')
.post(controller.bmPostMaterialUpdateBulk);
materialsRouter.route('/updateMaterialRecordBulk').post(controller.bmPostMaterialUpdateBulk);

materialsRouter.route('/updateMaterialStatus')
.post(controller.bmupdatePurchaseStatus);
materialsRouter.route('/updateMaterialStatus').post(controller.bmupdatePurchaseStatus);

materialsRouter.route('/materials/:projectId')
.get(controller.bmGetMaterialSummaryByProject);
materialsRouter.route('/materials/:projectId').get(controller.bmGetMaterialSummaryByProject);

return materialsRouter;
};
Expand Down
14 changes: 5 additions & 9 deletions src/routes/bmdashboard/bmProjectRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,14 @@ const routes = function (buildingProject) {
const projectRouter = express.Router();
const controller = require('../../controllers/bmdashboard/bmProjectController')(buildingProject);

projectRouter.route('/projects')
.get(controller.fetchAllProjects);
projectRouter.route('/projects').get(controller.fetchAllProjects);

projectRouter.route('/project/:projectId')
.get(controller.fetchSingleProject);
projectRouter.route('/project/:projectId').get(controller.fetchSingleProject);

projectRouter.route('/projectsNames')
.get(controller.fetchProjectsNames);
projectRouter.route('/projectsNames').get(controller.fetchProjectsNames);

projectRouter.route('/project/:projectId/users').get(controller.fetchProjectMembers);

projectRouter.route('/project/:projectId/users')
.get(controller.fetchProjectMembers);

return projectRouter;
};

Expand Down
5 changes: 5 additions & 0 deletions src/routes/bmdashboard/injuryCategoryRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@ const {
getUniqueSeverities,
getUniqueInjuryTypes,
getProjectsWithInjuries,
getInjuryTrendData,
createInjuries,
} = require('../../controllers/bmdashboard/injuryCategoryController');

router.get('/category-breakdown', getCategoryBreakdown);
router.get('/injury-severities', getUniqueSeverities);
router.get('/injury-types', getUniqueInjuryTypes);
router.get('/project-injury', getProjectsWithInjuries);
router.get('/trend-data', getInjuryTrendData);
// Base path is '/api/bm/injuries' from startup/routes, so POST to '/api/bm/injuries'
router.post('/', createInjuries);

module.exports = router;
Loading