dss-server / src /controllers /safetyController.js
yeshwanth-kr's picture
Upload 43 files
8c7b7ca verified
const { body, param } = require('express-validator');
const dayjs = require('dayjs');
const {
SafetyInduction,
Employee,
Vendor,
SafetyBadge,
User
} = require('../models');
const { sendEmail, sendWhatsApp } = require('../services/notificationService');
const validate = require('../utils/validation');
const scheduleValidators = [
param('id').isMongoId().withMessage('valid employee id is required'),
body('scheduled_date').isDate().withMessage('scheduled_date must be valid'),
body('notes').optional().isString(),
validate
];
const issueBadgeValidators = [
param('id').isMongoId().withMessage('valid employee id is required'),
body('induction_date').isDate().withMessage('induction_date must be valid'),
body('badge_no').notEmpty().withMessage('badge_no is required'),
validate
];
const submitTestResultValidators = [
param('id').isMongoId().withMessage('valid induction id is required'),
body('result').isIn(['Pass', 'Fail']).withMessage('result must be Pass or Fail'),
body('remarks').optional({ nullable: true }).isLength({ max: 255 }).withMessage('remarks must be at most 255 characters'),
validate
];
function computeExpiry(inductionDate) {
const dt = new Date(inductionDate);
dt.setDate(dt.getDate() + 365);
return dt;
}
async function getCalendar(req, res, next) {
try {
const rows = await SafetyInduction.find({ test_status: 'Pending' })
.sort({ scheduled_date: 1 })
.populate({ path: 'employee_id', model: 'Employee', select: 'name vendor_id is_active' })
.lean();
const employeeIds = rows.map((row) => row.employee_id?._id).filter(Boolean);
const badges = await SafetyBadge.find({ employee_id: { $in: employeeIds } }, { employee_id: 1, badge_no: 1, expiry_date: 1 }).lean();
const badgeByEmployee = new Map(badges.map((b) => [String(b.employee_id), b]));
const payload = [];
for (const row of rows) {
const employee = row.employee_id;
if (!employee || !employee.is_active) continue;
const vendor = await Vendor.findById(employee.vendor_id, { name: 1, is_active: 1 }).lean();
if (!vendor || !vendor.is_active) continue;
const badge = badgeByEmployee.get(String(employee._id));
payload.push({
id: String(row._id),
employee_id: String(employee._id),
employee_code: employee.employee_code,
employee_name: employee.name,
vendor_name: vendor.name,
scheduled_date: row.scheduled_date,
completed_date: row.completed_date,
test_status: row.test_status,
test_date: row.test_date,
failure_remarks: row.failure_remarks,
retest_requested: row.retest_requested,
retest_requested_at: row.retest_requested_at,
notes: row.notes,
badge_no: badge?.badge_no || null,
expiry_date: badge?.expiry_date || null
});
}
return res.json(payload);
} catch (error) {
return next(error);
}
}
async function scheduleInduction(req, res, next) {
try {
const employeeId = req.params.id;
const { scheduled_date, notes } = req.body;
const employee = await Employee.findById(employeeId, { status: 1, is_active: 1, vendor_id: 1 });
if (!employee) {
return res.status(404).json({ message: 'Employee not found' });
}
const vendor = await Vendor.findById(employee.vendor_id, { is_active: 1 }).lean();
if (!vendor || !vendor.is_active || !employee.is_active) {
return res.status(400).json({ message: 'Employee or vendor is inactive' });
}
if (employee.status !== 'HR_Approved') {
return res.status(400).json({ message: 'Employee must be HR_Approved before induction scheduling' });
}
await SafetyInduction.findOneAndUpdate(
{ employee_id: employeeId },
{
$set: {
scheduled_date: new Date(scheduled_date),
notes: notes || null,
created_by: req.user.id,
completed_date: null,
test_status: 'Pending',
test_date: null,
failure_remarks: null,
retest_requested: false,
retest_requested_at: null
}
},
{ upsert: true, new: true, setDefaultsOnInsert: true }
);
return res.json({ message: 'Safety induction scheduled' });
} catch (error) {
return next(error);
}
}
async function issueBadge(req, res, next) {
try {
const employeeId = req.params.id;
const { induction_date, badge_no } = req.body;
const employee = await Employee.findById(employeeId);
if (!employee) {
return res.status(404).json({ message: 'Employee not found' });
}
if (!['HR_Approved', 'Safety_Approved', 'Active'].includes(employee.status)) {
return res.status(400).json({ message: 'Employee is not eligible for safety badge issuance' });
}
await SafetyBadge.findOneAndUpdate(
{ employee_id: employeeId },
{
$set: {
induction_date: new Date(induction_date),
expiry_date: computeExpiry(induction_date),
badge_no,
issued_by: req.user.id
}
},
{ upsert: true, new: true, setDefaultsOnInsert: true }
);
await SafetyInduction.updateOne(
{ employee_id: employeeId },
{ $set: { completed_date: new Date(induction_date) } }
);
employee.status = 'Safety_Approved';
employee.safety_completed_at = new Date();
await employee.save();
return res.json({ message: 'HSE card issued (valid for 1 year)' });
} catch (error) {
return next(error);
}
}
function generateBadgeNo(employeeId, testDateISO) {
return `HSE-${employeeId}-${dayjs(testDateISO).format('YYYYMMDD')}-${String(Date.now()).slice(-5)}`;
}
async function submitTestResult(req, res, next) {
try {
const inductionId = req.params.id;
const { result, remarks } = req.body;
const effectiveTestDate = dayjs().format('YYYY-MM-DD');
const cleanRemarks = String(remarks || '').trim();
const induction = await SafetyInduction.findById(inductionId);
if (!induction) {
return res.status(404).json({ message: 'Induction record not found' });
}
const employee = await Employee.findById(induction.employee_id);
if (!employee) {
return res.status(404).json({ message: 'Employee not found' });
}
const vendor = await Vendor.findById(employee.vendor_id, { name: 1, is_active: 1 }).lean();
if (!vendor || !vendor.is_active || !employee.is_active) {
return res.status(400).json({ message: 'Employee or vendor is inactive' });
}
if (result === 'Pass') {
if (!['HR_Approved', 'Safety_Approved', 'Active'].includes(String(employee.status))) {
return res.status(400).json({ message: 'Employee is not eligible for test pass processing' });
}
const badgeNo = generateBadgeNo(String(employee._id), effectiveTestDate);
await SafetyBadge.findOneAndUpdate(
{ employee_id: employee._id },
{
$set: {
induction_date: new Date(effectiveTestDate),
expiry_date: computeExpiry(effectiveTestDate),
badge_no: badgeNo,
issued_by: req.user.id
}
},
{ upsert: true, new: true, setDefaultsOnInsert: true }
);
induction.completed_date = new Date(effectiveTestDate);
induction.test_status = 'Passed';
induction.test_date = new Date(effectiveTestDate);
induction.failure_remarks = null;
induction.retest_requested = false;
induction.retest_requested_at = null;
induction.vendor_notified_at = null;
await induction.save();
employee.status = 'Safety_Approved';
employee.safety_completed_at = new Date();
await employee.save();
return res.json({ message: 'Test passed. HSE card issued automatically for 1 year.', badge_no: badgeNo });
}
induction.completed_date = null;
induction.test_status = 'Failed';
induction.test_date = new Date(effectiveTestDate);
induction.failure_remarks = cleanRemarks || null;
induction.retest_requested = false;
induction.retest_requested_at = null;
induction.vendor_notified_at = new Date();
await induction.save();
employee.status = 'HR_Approved';
employee.safety_completed_at = null;
await employee.save();
const vendorUser = await User.findOne(
{ vendor_id: vendor._id, role: 'Vendor', is_active: true },
{ email: 1, phone: 1 }
).lean();
const adminUsers = await User.find({ role: 'Admin', is_active: true }, { email: 1, phone: 1 }).lean();
const prettyDate = dayjs(effectiveTestDate).format('DD/MM/YYYY');
const failReason = cleanRemarks ? ` Remarks: ${cleanRemarks}` : '';
const text = `${employee.name} (${vendor.name}) failed safety test on ${prettyDate}.${failReason} Vendor can apply for retest from dashboard.`;
try {
if (vendorUser?.email) await sendEmail(vendorUser.email, `Safety Test Failed - ${employee.name}`, text);
if (vendorUser?.phone) await sendWhatsApp(`whatsapp:${vendorUser.phone}`, text);
for (const adminUser of adminUsers) {
if (adminUser.email) await sendEmail(adminUser.email, `Safety Test Failed - ${employee.name}`, text);
if (adminUser.phone) await sendWhatsApp(`whatsapp:${adminUser.phone}`, text);
}
} catch (_notifyError) {
// Non-blocking notification path.
}
return res.json({ message: 'Test marked as failed. Vendor has been notified for retest.' });
} catch (error) {
return next(error);
}
}
module.exports = {
scheduleValidators,
issueBadgeValidators,
submitTestResultValidators,
getCalendar,
scheduleInduction,
issueBadge,
submitTestResult
};