dss-server / src /controllers /managementController.js
yeshwanth-kr's picture
Upload 43 files
8c7b7ca verified
const { body, param, query } = require('express-validator');
const bcrypt = require('bcryptjs');
const {
User,
Vendor,
Employee,
Document,
SafetyInduction,
SafetyBadge,
GatePass,
RenewRequest
} = require('../models');
const validate = require('../utils/validation');
const { parseVendorCode } = require('../utils/vendorCode');
const allowedEmployeeStatuses = ['Pending', 'HR_Approved', 'Safety_Approved', 'Active', 'Rejected'];
const listVendorsValidators = [
query('search').optional().isString(),
validate
];
const updateVendorValidators = [
param('id').isMongoId().withMessage('valid vendor id is required'),
body('name').optional().notEmpty().withMessage('name cannot be empty'),
body('vendor_code').optional().notEmpty().withMessage('vendor_code cannot be empty'),
body('vendor_code').optional().custom((value) => {
if (!parseVendorCode(value)) {
throw new Error('vendor_code must be in format NAME4-DSS-YY-SEQ (e.g., YESH-DSS-26-001)');
}
return true;
}),
body('contact_name').optional().notEmpty().withMessage('contact_name cannot be empty'),
body('contact_email').optional().isEmail().withMessage('contact_email must be valid'),
body('contact_phone').optional().notEmpty().withMessage('contact_phone cannot be empty'),
body('pan').optional({ nullable: true }).isString(),
body('gstin').optional({ nullable: true }).isString(),
body('epf_code').optional({ nullable: true }).isString(),
body('esi_code').optional({ nullable: true }).isString(),
body('work_order_no').optional({ nullable: true }).isString(),
body('contract_start').optional({ nullable: true }).isDate().withMessage('contract_start must be valid date'),
body('contract_end').optional({ nullable: true }).isDate().withMessage('contract_end must be valid date'),
body('max_workers').optional({ nullable: true }).isInt({ min: 1 }).withMessage('max_workers must be at least 1'),
body('is_active').optional().isBoolean(),
validate
];
const setVendorStatusValidators = [
param('id').isMongoId().withMessage('valid vendor id is required'),
body('is_active').isBoolean().withMessage('is_active must be boolean'),
validate
];
const deleteVendorValidators = [
param('id').isMongoId().withMessage('valid vendor id is required'),
validate
];
const listEmployeesValidators = [
query('search').optional().isString(),
query('vendor_id').optional().isMongoId().withMessage('vendor_id must be a valid id'),
query('include_inactive').optional().isIn(['true', 'false']).withMessage('include_inactive must be true or false'),
validate
];
const updateEmployeeValidators = [
param('id').isMongoId().withMessage('valid employee id is required'),
body('name').optional().notEmpty().withMessage('name cannot be empty'),
body('aadhar_no').optional().notEmpty().withMessage('aadhar_no cannot be empty'),
body('designation').optional({ nullable: true }).isString(),
body('department').optional({ nullable: true }).isString(),
body('contact_number').optional({ nullable: true }).isString(),
body('uan_no').optional({ nullable: true }).isString(),
body('esi_no').optional({ nullable: true }).isString(),
body('bank_ifsc_code').optional({ nullable: true }).isString(),
body('bank_account_number').optional({ nullable: true }).isString(),
body('vehicle_no').optional({ nullable: true }).isString(),
body('vehicle_type').optional({ nullable: true }).isString(),
body('status').optional().isIn(allowedEmployeeStatuses).withMessage('invalid status'),
body('is_active').optional().isBoolean(),
validate
];
const setEmployeeStatusValidators = [
param('id').isMongoId().withMessage('valid employee id is required'),
body('is_active').isBoolean().withMessage('is_active must be boolean'),
validate
];
const deleteEmployeeValidators = [
param('id').isMongoId().withMessage('valid employee id is required'),
validate
];
const getEmployeeIdCardValidators = [
param('id').isMongoId().withMessage('valid employee id is required'),
validate
];
const resetUserPasswordValidators = [
body('email').isEmail().withMessage('valid email is required'),
validate
];
function generateTempPassword(length = 12) {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789@#$!';
let value = '';
for (let i = 0; i < length; i += 1) {
value += chars[Math.floor(Math.random() * chars.length)];
}
return value;
}
async function listVendors(req, res, next) {
try {
const search = String(req.query.search || '').trim().toLowerCase();
const vendors = await Vendor.find({}).sort({ created_at: -1 }).lean();
const vendorIds = vendors.map((v) => v._id);
const vendorUsers = await User.find({ vendor_id: { $in: vendorIds }, role: 'Vendor' })
.sort({ created_at: 1 })
.lean();
const employees = await Employee.find({ vendor_id: { $in: vendorIds } }, { vendor_id: 1, is_active: 1 }).lean();
const firstUserByVendor = new Map();
for (const user of vendorUsers) {
const key = String(user.vendor_id);
if (!firstUserByVendor.has(key)) firstUserByVendor.set(key, user);
}
const employeeStats = new Map();
for (const employee of employees) {
const key = String(employee.vendor_id);
if (!employeeStats.has(key)) employeeStats.set(key, { total: 0, active: 0 });
const stats = employeeStats.get(key);
stats.total += 1;
if (employee.is_active) stats.active += 1;
}
const rows = vendors
.map((vendor) => {
const user = firstUserByVendor.get(String(vendor._id));
const stats = employeeStats.get(String(vendor._id)) || { total: 0, active: 0 };
return {
id: String(vendor._id),
name: vendor.name,
vendor_code: vendor.vendor_code,
pan: vendor.pan,
gstin: vendor.gstin,
epf_code: vendor.epf_code,
esi_code: vendor.esi_code,
work_order_no: vendor.work_order_no,
contract_start: vendor.contract_start,
contract_end: vendor.contract_end,
max_workers: vendor.max_workers,
form1_path: vendor.form1_path,
form2_path: vendor.form2_path,
is_active: vendor.is_active,
vendor_user_id: user ? String(user._id) : null,
contact_name: user?.full_name || null,
contact_email: user?.email || null,
contact_phone: user?.phone || null,
employee_count: stats.total,
active_employee_count: stats.active
};
})
.filter((row) => {
if (!search) return true;
return String(row.name || '').toLowerCase().includes(search)
|| String(row.vendor_code || '').toLowerCase().includes(search)
|| String(row.contact_email || '').toLowerCase().includes(search);
});
return res.json(rows);
} catch (error) {
return next(error);
}
}
async function updateVendor(req, res, next) {
try {
const vendorId = req.params.id;
const vendor = await Vendor.findById(vendorId);
if (!vendor) {
return res.status(404).json({ message: 'Vendor not found' });
}
const vendorUser = await User.findOne({ vendor_id: vendorId, role: 'Vendor' }).sort({ created_at: 1 });
const nextVendorCodeRaw = req.body.vendor_code ?? vendor.vendor_code;
const parsedNextVendorCode = parseVendorCode(nextVendorCodeRaw);
if (!parsedNextVendorCode) {
return res.status(400).json({ message: 'vendor_code must be in format NAME4-DSS-YY-SEQ (e.g., YESH-DSS-26-001)' });
}
const nextVendorCode = parsedNextVendorCode.normalized;
const parsedCurrentVendorCode = parseVendorCode(vendor.vendor_code);
if (nextVendorCode !== vendor.vendor_code) {
const dupVendorCode = await Vendor.findOne({ vendor_code: nextVendorCode, _id: { $ne: vendorId } }).lean();
if (dupVendorCode) {
return res.status(409).json({ message: 'Vendor code is already in use' });
}
}
if (
parsedCurrentVendorCode
&& parsedCurrentVendorCode.name4 !== parsedNextVendorCode.name4
) {
const employeeExists = await Employee.exists({ vendor_id: vendorId });
if (employeeExists) {
return res.status(400).json({ message: 'Cannot change NAME4 prefix after employees are created for this vendor' });
}
}
const dupName4 = await Vendor.findOne({
_id: { $ne: vendorId },
vendor_code: { $regex: `^${parsedNextVendorCode.name4}-DSS-`, $options: 'i' }
}).lean();
if (dupName4) {
return res.status(409).json({ message: `NAME4 prefix '${parsedNextVendorCode.name4}' is already assigned to another vendor` });
}
if (vendorUser && req.body.contact_email && req.body.contact_email !== vendorUser.email) {
const dupEmail = await User.findOne({ email: req.body.contact_email.toLowerCase(), _id: { $ne: vendorUser._id } }).lean();
if (dupEmail) {
return res.status(409).json({ message: 'Contact email is already in use' });
}
}
const nextContractStart = req.body.contract_start ?? vendor.contract_start;
const nextContractEnd = req.body.contract_end ?? vendor.contract_end;
if (nextContractStart && nextContractEnd && new Date(nextContractEnd) < new Date(nextContractStart)) {
return res.status(400).json({ message: 'contract_end must be on or after contract_start' });
}
const nextEpf = (req.body.epf_code ?? vendor.epf_code ?? '').toString().trim();
const nextEsi = (req.body.esi_code ?? vendor.esi_code ?? '').toString().trim();
if (!nextEpf && !nextEsi) {
return res.status(400).json({ message: 'Either epf_code or esi_code is required' });
}
vendor.name = req.body.name ?? vendor.name;
vendor.vendor_code = nextVendorCode;
vendor.pan = req.body.pan ?? vendor.pan;
vendor.gstin = req.body.gstin ?? vendor.gstin;
vendor.epf_code = req.body.epf_code ?? vendor.epf_code;
vendor.esi_code = req.body.esi_code ?? vendor.esi_code;
vendor.work_order_no = req.body.work_order_no ?? vendor.work_order_no;
vendor.contract_start = nextContractStart ? new Date(nextContractStart) : null;
vendor.contract_end = nextContractEnd ? new Date(nextContractEnd) : null;
vendor.max_workers = req.body.max_workers ?? vendor.max_workers;
if (typeof req.body.is_active === 'boolean') vendor.is_active = req.body.is_active;
await vendor.save();
if (vendorUser) {
vendorUser.full_name = req.body.contact_name ?? vendorUser.full_name;
vendorUser.email = (req.body.contact_email ?? vendorUser.email).toLowerCase();
vendorUser.phone = req.body.contact_phone ?? vendorUser.phone;
if (typeof req.body.is_active === 'boolean') vendorUser.is_active = req.body.is_active;
await vendorUser.save();
}
return res.json({ message: 'Vendor updated successfully' });
} catch (error) {
return next(error);
}
}
async function setVendorStatus(req, res, next) {
try {
const vendorId = req.params.id;
const { is_active } = req.body;
const vendor = await Vendor.findById(vendorId);
if (!vendor) {
return res.status(404).json({ message: 'Vendor not found' });
}
vendor.is_active = is_active;
await vendor.save();
await User.updateMany({ vendor_id: vendorId, role: 'Vendor' }, { $set: { is_active } });
return res.json({ message: is_active ? 'Vendor activated' : 'Vendor deactivated' });
} catch (error) {
return next(error);
}
}
async function deleteVendor(req, res, next) {
try {
const vendorId = req.params.id;
const vendor = await Vendor.findById(vendorId).lean();
if (!vendor) {
return res.status(404).json({ message: 'Vendor not found' });
}
const employees = await Employee.find({ vendor_id: vendorId }, { _id: 1 }).lean();
const employeeIds = employees.map((e) => e._id);
const gatePasses = await GatePass.find({ employee_id: { $in: employeeIds } }, { _id: 1 }).lean();
const gatePassIds = gatePasses.map((g) => g._id);
await Document.deleteMany({ employee_id: { $in: employeeIds } });
await SafetyInduction.deleteMany({ employee_id: { $in: employeeIds } });
await SafetyBadge.deleteMany({ employee_id: { $in: employeeIds } });
await RenewRequest.deleteMany({ $or: [{ employee_id: { $in: employeeIds } }, { gate_pass_id: { $in: gatePassIds } }, { vendor_id: vendorId }] });
await GatePass.deleteMany({ employee_id: { $in: employeeIds } });
await Employee.deleteMany({ vendor_id: vendorId });
await User.deleteMany({ vendor_id: vendorId, role: 'Vendor' });
await Vendor.deleteOne({ _id: vendorId });
return res.json({ message: 'Vendor deleted successfully' });
} catch (error) {
return next(error);
}
}
async function listEmployees(req, res, next) {
try {
const search = String(req.query.search || '').trim().toLowerCase();
const vendorId = req.query.vendor_id || null;
const includeInactive = String(req.query.include_inactive || 'true') !== 'false';
const filter = {};
if (vendorId) filter.vendor_id = vendorId;
if (!includeInactive) filter.is_active = true;
const employees = await Employee.find(filter).sort({ created_at: -1 }).lean();
const vendorIds = [...new Set(employees.map((e) => String(e.vendor_id)))];
const vendors = await Vendor.find({ _id: { $in: vendorIds } }, { name: 1 }).lean();
const vendorMap = new Map(vendors.map((v) => [String(v._id), v.name]));
const employeeIds = employees.map((e) => e._id);
const docs = await Document.find({ employee_id: { $in: employeeIds } }).sort({ uploaded_at: -1 }).lean();
const docsByEmployee = new Map();
for (const doc of docs) {
const key = String(doc.employee_id);
if (!docsByEmployee.has(key)) docsByEmployee.set(key, []);
docsByEmployee.get(key).push({
id: String(doc._id),
employee_id: String(doc.employee_id),
type: doc.type,
file_path: doc.file_path,
pvc_validity_date: doc.pvc_validity_date,
hr_status: doc.hr_status,
hr_remarks: doc.hr_remarks,
verified_by_hr: doc.verified_by_hr,
uploaded_at: doc.uploaded_at
});
}
const rows = employees
.map((employee) => {
const key = String(employee._id);
const employeeDocs = docsByEmployee.get(key) || [];
return {
id: key,
employee_code: employee.employee_code,
vendor_id: String(employee.vendor_id),
vendor_name: vendorMap.get(String(employee.vendor_id)) || 'N/A',
name: employee.name,
aadhar_no: employee.aadhar_no,
designation: employee.designation,
department: employee.department,
contact_number: employee.contact_number,
uan_no: employee.uan_no,
esi_no: employee.esi_no,
bank_ifsc_code: employee.bank_ifsc_code,
bank_account_number: employee.bank_account_number,
vehicle_no: employee.vehicle_no,
vehicle_type: employee.vehicle_type,
status: employee.status,
due_date: employee.due_date,
profile_photo_path: employee.profile_photo_path,
is_active: employee.is_active,
created_at: employee.created_at,
total_docs_count: employeeDocs.length,
pending_docs_count: employeeDocs.filter((d) => d.hr_status === 'Pending').length,
approved_docs_count: employeeDocs.filter((d) => d.hr_status === 'Approved').length,
rejected_docs_count: employeeDocs.filter((d) => d.hr_status === 'Rejected').length,
documents: employeeDocs
};
})
.filter((row) => {
if (!search) return true;
return String(row.name || '').toLowerCase().includes(search)
|| String(row.vendor_name || '').toLowerCase().includes(search)
|| String(row.employee_code || '').toLowerCase().includes(search)
|| String(row.aadhar_no || '').toLowerCase().includes(search);
});
return res.json(rows);
} catch (error) {
return next(error);
}
}
async function updateEmployee(req, res, next) {
try {
const employeeId = req.params.id;
const employee = await Employee.findById(employeeId);
if (!employee) {
return res.status(404).json({ message: 'Employee not found' });
}
const nextAadhar = req.body.aadhar_no ?? employee.aadhar_no;
if (nextAadhar !== employee.aadhar_no) {
const dup = await Employee.findOne({ vendor_id: employee.vendor_id, aadhar_no: nextAadhar, _id: { $ne: employeeId } }).lean();
if (dup) {
return res.status(409).json({ message: 'aadhar_no already exists for this vendor' });
}
}
employee.name = req.body.name ?? employee.name;
employee.aadhar_no = nextAadhar;
employee.designation = req.body.designation ?? employee.designation;
employee.department = req.body.department ?? employee.department;
employee.contact_number = req.body.contact_number ?? employee.contact_number;
employee.uan_no = req.body.uan_no ?? employee.uan_no;
employee.esi_no = req.body.esi_no ?? employee.esi_no;
employee.bank_ifsc_code = req.body.bank_ifsc_code ?? employee.bank_ifsc_code;
employee.bank_account_number = req.body.bank_account_number ?? employee.bank_account_number;
employee.vehicle_no = req.body.vehicle_no ?? employee.vehicle_no;
employee.vehicle_type = req.body.vehicle_type ?? employee.vehicle_type;
employee.status = req.body.status ?? employee.status;
if (typeof req.body.is_active === 'boolean') employee.is_active = req.body.is_active;
await employee.save();
return res.json({ message: 'Employee updated successfully' });
} catch (error) {
return next(error);
}
}
async function setEmployeeStatus(req, res, next) {
try {
const employeeId = req.params.id;
const { is_active } = req.body;
const employee = await Employee.findById(employeeId);
if (!employee) {
return res.status(404).json({ message: 'Employee not found' });
}
employee.is_active = is_active;
await employee.save();
return res.json({ message: is_active ? 'Employee activated' : 'Employee deactivated' });
} catch (error) {
return next(error);
}
}
async function deleteEmployee(req, res, next) {
try {
const employeeId = req.params.id;
const employee = await Employee.findById(employeeId).lean();
if (!employee) {
return res.status(404).json({ message: 'Employee not found' });
}
const gatePasses = await GatePass.find({ employee_id: employeeId }, { _id: 1 }).lean();
const gatePassIds = gatePasses.map((g) => g._id);
await Document.deleteMany({ employee_id: employeeId });
await SafetyInduction.deleteMany({ employee_id: employeeId });
await SafetyBadge.deleteMany({ employee_id: employeeId });
await RenewRequest.deleteMany({ $or: [{ employee_id: employeeId }, { gate_pass_id: { $in: gatePassIds } }] });
await GatePass.deleteMany({ employee_id: employeeId });
await Employee.deleteOne({ _id: employeeId });
return res.json({ message: 'Employee deleted successfully' });
} catch (error) {
return next(error);
}
}
async function getEmployeeIdCard(req, res, next) {
try {
const employeeId = req.params.id;
const employee = await Employee.findById(employeeId).lean();
if (!employee) {
return res.status(404).json({ message: 'Employee not found' });
}
const vendor = await Vendor.findById(employee.vendor_id, { name: 1 }).lean();
const gatePass = await GatePass.findOne({ employee_id: employee._id })
.sort({ created_at: -1 })
.lean();
return res.json({
employee_id: String(employee._id),
employee_code: employee.employee_code || null,
name: employee.name,
vendor_name: vendor?.name || 'N/A',
aadhar_no: employee.aadhar_no || null,
designation: employee.designation || null,
department: employee.department || null,
contact_number: employee.contact_number || null,
status: employee.status,
profile_photo_path: employee.profile_photo_path || null,
barcode: gatePass?.barcode || null,
gate_pass_status: gatePass?.status || null,
gate_pass_issue_date: gatePass?.issue_date || null,
gate_pass_expiry_date: gatePass?.expiry_date || employee.due_date || null
});
} catch (error) {
return next(error);
}
}
async function resetUserPassword(req, res, next) {
try {
if (req.user.role !== 'Admin') {
return res.status(403).json({ message: 'Only Admin can reset user passwords' });
}
const email = String(req.body.email || '').trim().toLowerCase();
const user = await User.findOne({ email });
if (!user) {
return res.status(404).json({ message: 'User not found for this email' });
}
const temporaryPassword = generateTempPassword();
user.password_hash = await bcrypt.hash(temporaryPassword, 10);
user.must_reset_password = true;
user.password_changed_at = new Date();
await user.save();
return res.json({
message: 'Password reset successful. Share temporary password and request immediate password change.',
credentials: {
email: user.email,
role: user.role,
temporary_password: temporaryPassword
}
});
} catch (error) {
return next(error);
}
}
module.exports = {
listVendorsValidators,
updateVendorValidators,
setVendorStatusValidators,
deleteVendorValidators,
listEmployeesValidators,
updateEmployeeValidators,
setEmployeeStatusValidators,
deleteEmployeeValidators,
getEmployeeIdCardValidators,
resetUserPasswordValidators,
listVendors,
updateVendor,
setVendorStatus,
deleteVendor,
listEmployees,
updateEmployee,
setEmployeeStatus,
deleteEmployee,
getEmployeeIdCard,
resetUserPassword
};