Website : rimsha.abasa.com
backdoor
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
var
/
www
/
mudeerapi.abasa.com
/
nodetest
/
src
/
controllers
/
Filename :
department.controller.js
back
Copy
import mongoose from 'mongoose'; import jwt from 'jsonwebtoken'; import { Department } from '../models/department.model.js'; import { user } from '../models/user.models.js'; import UserStatus from '../models/userStatus.model.js'; import attendance from '../models/attendance.model.js'; import { invalidateCache } from '../utils/invalidationHelper.js'; import { blacklistUserTokens } from '../utils/tokenBlacklist.js'; import { notifyRoleChange } from '../services/realtimeNotificationService.js'; import { sendNotificationService } from '../services/notificationService.js'; import { roleChangeNotificationTemplate } from '../template/notificationTemplates.js'; // Helper function to check if user is already in any department const checkUserDepartmentStatus = async (userId) => { const userDoc = await user.findById(userId); if (!userDoc) { throw new Error('User not found'); } // Check if user already has a department assigned if (userDoc.department) { const existingDept = await Department.findById(userDoc.department); return { hasExistingDepartment: true, departmentName: existingDept?.name || 'Unknown' }; } return { hasExistingDepartment: false }; }; // Helper function to check if user is already head of another department const checkUserHeadStatus = async (userId) => { const existingHeadDept = await Department.findOne({ head: userId }); return { isHeadElsewhere: !!existingHeadDept, departmentName: existingHeadDept?.name || null }; }; export const createDepartment = async (req, res) => { try { const { name, employees = [] } = req.body; // Check if department name already exists const existingDept = await Department.findOne({ name }); if (existingDept) { return res.status(400).json({ message: 'Department with this name already exists' }); } let validatedEmployees = []; // Validate employees if provided for (const employeeId of employees) { const employee = await user.findById(employeeId); if (!employee) { return res.status(404).json({ message: `Employee with ID ${employeeId} not found` }); } if (employee.isActive === false) { return res.status(400).json({ message: `Cannot add deactivated user to department` }); } const empStatus = await checkUserDepartmentStatus(employeeId); if (empStatus.hasExistingDepartment) { return res.status(400).json({ message: `Employee is already assigned to department: ${empStatus.departmentName}` }); } validatedEmployees.push(new mongoose.Types.ObjectId(employeeId)); } const newDepartment = new Department({ _id: new mongoose.Types.ObjectId(), name, employees: validatedEmployees }); const result = await newDepartment.save(); // Update user documents with department reference for (const empId of employees) { await user.findByIdAndUpdate(empId, { department: result._id }); } res.status(201).json({ department: result }); } catch (error) { console.error(error); res.status(500).json({ message: 'Error creating department', error: error.message }); } }; export const updateDepartment = async (req, res) => { try { const { name, head, employees } = req.body; // Find the department by name const department = await Department.findOne({ name }); if (!department) { return res.status(404).json({ message: 'Department not found' }); } // Update the head if provided if (head) { // Check if it's the same head (no validation needed) if (department.head?.toString() !== head) { const incomingHeadId = head; // Check if user is active const headUser = await user.findById(head); if (!headUser) { return res.status(404).json({ message: 'Head user not found' }); } if (headUser.isActive === false) { return res.status(400).json({ message: 'Cannot assign deactivated user as department head' }); } const headStatus = await checkUserDepartmentStatus(head); if (headStatus.hasExistingDepartment) { return res.status(400).json({ message: `Head user is already assigned to department: ${headStatus.departmentName}` }); } const headOfDeptStatus = await checkUserHeadStatus(head); if (headOfDeptStatus.isHeadElsewhere) { return res.status(400).json({ message: `User is already head of department: ${headOfDeptStatus.departmentName}` }); } // Demote old head to employee (keep department assignment) if (department.head) { const oldHeadId = department.head; await user.findByIdAndUpdate(oldHeadId, { role: 'employee' }); // Keep old head visible as a normal member. // Because we enforce "head not in employees[]", a head-only user would disappear otherwise. department.employees = (department.employees || []) .filter((empId) => empId.toString() !== incomingHeadId.toString()); if (!department.employees.some((empId) => empId.toString() === oldHeadId.toString())) { department.employees.push(new mongoose.Types.ObjectId(oldHeadId)); } // Send system notification const demoteTemplate = roleChangeNotificationTemplate('demoted', 'department_head', 'employee', department.name); await sendNotificationService({ ...demoteTemplate, recipientIds: [oldHeadId], sendFCM: true }); // Blacklist tokens and send real-time notification await blacklistUserTokens(oldHeadId); await notifyRoleChange(oldHeadId, { action: 'demoted', oldRole: 'department_head', newRole: 'employee', department: department.name }); // Invalidate cache for old head await invalidateCache.user(oldHeadId); } department.head = new mongoose.Types.ObjectId(head); await user.findByIdAndUpdate(head, { department: department._id, role: 'department_head' }); // Enforce invariant: head must never also be in employees[] department.employees = (department.employees || []).filter( (empId) => empId.toString() !== head.toString() ); // Send system notification for new head const promoteTemplate = roleChangeNotificationTemplate('promoted', 'employee', 'department_head', department.name); await sendNotificationService({ ...promoteTemplate, recipientIds: [head], sendFCM: true }); // Blacklist tokens and send real-time notification await blacklistUserTokens(head); await notifyRoleChange(head, { action: 'promoted', oldRole: 'employee', newRole: 'department_head', department: department.name }); // Invalidate cache for new head await invalidateCache.user(head); } } // Update the employees if provided if (employees && employees.length > 0) { // Remove department reference from old employees, but never from the current head (they must keep this department) if (department.employees?.length > 0) { const headIdStr = department.head?.toString(); const employeeIdsToUnset = headIdStr ? department.employees.filter(emp => emp.toString() !== headIdStr) : department.employees; if (employeeIdsToUnset.length > 0) { await user.updateMany( { _id: { $in: employeeIdsToUnset } }, { $unset: { department: 1 } } ); } } const validatedEmployees = []; for (const employeeId of employees) { // Never store head inside employees[] if (department.head && department.head.toString() === employeeId) { continue; } // Skip validation if employee is already in this department if (department.employees?.some(emp => emp.toString() === employeeId)) { validatedEmployees.push(new mongoose.Types.ObjectId(employeeId)); continue; } // Check if user is active const employee = await user.findById(employeeId); if (!employee) { return res.status(404).json({ message: `Employee with ID ${employeeId} not found` }); } if (employee.isActive === false) { return res.status(400).json({ message: `Cannot add deactivated user to department` }); } const empStatus = await checkUserDepartmentStatus(employeeId); if (empStatus.hasExistingDepartment) { return res.status(400).json({ message: `Employee is already assigned to department: ${empStatus.departmentName}` }); } validatedEmployees.push(new mongoose.Types.ObjectId(employeeId)); } department.employees = validatedEmployees; // Update user documents with new department reference for (const empId of employees) { await user.findByIdAndUpdate(empId, { department: department._id }); } } const updatedDepartment = await department.save(); res.status(200).json({ department: updatedDepartment }); } catch (error) { console.error(error); res.status(500).json({ message: 'Error updating department', error: error.message }); } }; export const getAllDepartments = async (req, res) => { try { const token = req.headers.authorization.split(" ")[1]; const decodedToken = jwt.verify(token, "this is dummy text"); let query = {}; // Handle different roles if (decodedToken.role === 'department_head') { if (!decodedToken.department) { return res.status(400).json({ message: "Department head has no department assigned" }); } // Department heads can only see their own department query = { _id: new mongoose.Types.ObjectId(decodedToken.department) }; } else if (decodedToken.role === 'employee') { if (!decodedToken.department) { return res.status(400).json({ message: "Employee has no department assigned" }); } query = { _id: new mongoose.Types.ObjectId(decodedToken.department) }; } else if (decodedToken.role === 'super_admin' || decodedToken.role === 'admin') { // Admins and super admins can see all departments query = {}; } else { return res.status(403).json({ message: "Access denied" }); } const departments = await Department.find(query).populate([ { path: 'head', match: { isActive: { $ne: false } } }, { path: 'employees', match: { isActive: { $ne: false } } } ]); const departmentIds = departments.map((d) => d._id); // Presents today (local) + Activity% based on presents/total // Client should send: // - day=YYYY-MM-DD (local day) // - tzOffsetMinutes=Date.getTimezoneOffset() (minutes to add to local -> UTC; can be negative) const day = typeof req.query.day === 'string' ? req.query.day : null; const tzOffsetMinutesRaw = typeof req.query.tzOffsetMinutes === 'string' ? req.query.tzOffsetMinutes : null; const tzOffsetMinutes = tzOffsetMinutesRaw != null && !Number.isNaN(Number(tzOffsetMinutesRaw)) ? Number(tzOffsetMinutesRaw) : 0; let presentByDept = new Map(); if (day && /^\d{4}-\d{2}-\d{2}$/.test(day)) { const [y, m, d] = day.split('-').map((n) => Number(n)); const startUtcMs = Date.UTC(y, m - 1, d) + tzOffsetMinutes * 60 * 1000; const startUtc = new Date(startUtcMs); const endUtc = new Date(startUtcMs + 24 * 60 * 60 * 1000); const presentAgg = await attendance.aggregate([ { $match: { department: { $in: departmentIds }, attendanceStatus: { $in: ['Present', 'Late'] }, createdAt: { $gte: startUtc, $lt: endUtc }, } }, { $group: { _id: '$department', presentToday: { $sum: 1 } } } ]); presentByDept = new Map( presentAgg.map((row) => [row._id.toString(), row.presentToday ?? 0]) ); } const departmentsWithActivity = departments.map((dept) => { const doc = dept.toObject ? dept.toObject() : { ...dept }; const deptId = dept._id.toString(); const presentToday = presentByDept.get(deptId) ?? 0; const activeMemberIds = new Set(); for (const e of Array.isArray(doc.employees) ? doc.employees : []) { if (e && e._id && e.isActive !== false) { activeMemberIds.add(e._id.toString()); } } if (doc.head && doc.head._id && doc.head.isActive !== false) { activeMemberIds.add(doc.head._id.toString()); } const totalPeople = activeMemberIds.size; doc.presentToday = presentToday; doc.totalPeople = totalPeople; doc.activityPercentage = totalPeople > 0 ? Math.round((presentToday / totalPeople) * 100) : 0; return doc; }); res.status(200).json({ departments: departmentsWithActivity, }); } catch (error) { console.error(error); res.status(500).json({ message: 'Error fetching departments', error }); } }; export const addEmployeeToDepartment = async (req, res) => { try { const department = await Department.findById(req.params.id); if (!department) { return res.status(404).json({ message: 'Department not found' }); } const { employeeId } = req.body; // Enforce invariant: head must never also be in employees[] if (department.head && department.head.toString() === employeeId) { return res.status(400).json({ message: 'Department head cannot be added as an employee entry' }); } // Check if employee is already in this department if (department.employees.some(emp => emp.toString() === employeeId)) { return res.status(400).json({ message: 'Employee is already in this department' }); } // Check if user is active const employee = await user.findById(employeeId); if (!employee) { return res.status(404).json({ message: 'Employee not found' }); } if (employee.isActive === false) { return res.status(400).json({ message: 'Cannot add deactivated user to department' }); } // Check if employee is in another department const empStatus = await checkUserDepartmentStatus(employeeId); if (empStatus.hasExistingDepartment) { return res.status(400).json({ message: `Employee is already assigned to department: ${empStatus.departmentName}` }); } department.employees.push(new mongoose.Types.ObjectId(employeeId)); await department.save(); // Update user document with department reference await user.findByIdAndUpdate(employeeId, { department: department._id }); res.status(200).json({ message: 'Employee added to department', department }); } catch (error) { console.error(error); res.status(500).json({ message: 'Error adding employee to department', error: error.message }); } }; export const getDepartment = async (req, res) => { try { const token = req.headers.authorization.split(" ")[1]; const decodedToken = jwt.verify(token, "this is dummy text"); if (decodedToken.role === 'department_head' || decodedToken.role === 'employee') { if (!decodedToken.department) { return res.status(400).json({ message: "User has no department assigned" }); } if (req.params.id !== decodedToken.department) { return res.status(403).json({ message: "Access denied. You can only view your own department" }); } } // admin and super_admin can access any department (no additional checks needed) const department = await Department.findById(req.params.id).populate([ { path: 'head', match: { isActive: { $ne: false } } }, { path: 'employees', match: { isActive: { $ne: false } } } ]); if (!department) { return res.status(404).json({ message: 'Department not found' }); } res.status(200).json({ department }); } catch (error) { console.error(error); res.status(500).json({ message: 'Error fetching department', error }); } }; export const deleteDepartment = async (req, res) => { try { const department = await Department.findById(req.params.id); if (!department) { return res.status(404).json({ message: 'Department not found' }); } // Collect all affected user IDs for cache invalidation const affectedUserIds = []; // Remove department reference from all employees if (department.employees && department.employees.length > 0) { affectedUserIds.push(...department.employees); await user.updateMany( { _id: { $in: department.employees } }, { $unset: { department: 1 } } ); } // Remove department reference from head and demote to employee if (department.head) { affectedUserIds.push(department.head); await user.findByIdAndUpdate(department.head, { $unset: { department: 1 }, role: 'employee' }); } // Invalidate cache for all affected users if (affectedUserIds.length > 0) { await Promise.all(affectedUserIds.map(userId => invalidateCache.user(userId))); } // Delete the department await Department.findByIdAndDelete(req.params.id); res.status(200).json({ message: 'Department deleted successfully' }); } catch (error) { console.error(error); res.status(500).json({ message: 'Error deleting department', error }); } }; export const removeEmployeeFromDepartment = async (req, res) => { try { const department = await Department.findById(req.params.id); if (!department) { return res.status(404).json({ message: 'Department not found' }); } const employeeId = req.body.employeeId; if (!department.employees.some(emp => emp.toString() === employeeId)) { return res.status(404).json({ message: 'Employee not found in this department' }); } department.employees = department.employees.filter( id => id.toString() !== employeeId.toString() ); await department.save(); // Remove department reference from user await user.findByIdAndUpdate(employeeId, { $unset: { department: 1 } }); res.status(200).json({ message: 'Employee removed from department', department }); } catch (error) { console.error(error); res.status(500).json({ message: 'Error removing employee from department', error: error.message }); } }; export const addHead = async (req, res) => { try { const department = await Department.findById(req.params.id); if (!department) { return res.status(404).json({ message: 'Department not found' }); } const headId = req.body.employeeId; // Check if user is active const headUser = await user.findById(headId); if (!headUser) { return res.status(404).json({ message: 'User not found' }); } if (headUser.isActive === false) { return res.status(400).json({ message: 'Cannot assign deactivated user as department head' }); } // Check if user is already in another department const headStatus = await checkUserDepartmentStatus(headId); if (headStatus.hasExistingDepartment) { // Check if the user is already in THIS department - if so, allow promotion const userDoc = await user.findById(headId); if (userDoc.department && userDoc.department.toString() !== req.params.id) { return res.status(400).json({ message: `User is already assigned to department: ${headStatus.departmentName}` }); } } // Check if user is already head of another department const headOfDeptStatus = await checkUserHeadStatus(headId); if (headOfDeptStatus.isHeadElsewhere) { return res.status(400).json({ message: `User is already head of department: ${headOfDeptStatus.departmentName}` }); } // Automatically demote old head if exists if (department.head) { const oldHeadId = department.head; await user.findByIdAndUpdate(oldHeadId, { role: 'employee' }); // Keep old head as a normal member (swap head -> employee). if (!department.employees.some((empId) => empId.toString() === oldHeadId.toString())) { department.employees.push(new mongoose.Types.ObjectId(oldHeadId)); } // Send system notification const demoteTemplate = roleChangeNotificationTemplate('demoted', 'department_head', 'employee', department.name); await sendNotificationService({ ...demoteTemplate, recipientIds: [oldHeadId], sendFCM: true }); // Blacklist tokens and send real-time notification await blacklistUserTokens(oldHeadId); await notifyRoleChange(oldHeadId, { action: 'demoted', oldRole: 'department_head', newRole: 'employee', department: department.name }); // Invalidate cache for old head await invalidateCache.user(oldHeadId); } department.head = new mongoose.Types.ObjectId(headId); // Enforce invariant: head must never also be in employees[] department.employees = (department.employees || []).filter( (empId) => empId.toString() !== headId.toString() ); await department.save(); // Update user document with department reference and role await user.findByIdAndUpdate(headId, { department: department._id, role: 'department_head' }); // Send system notification const template = roleChangeNotificationTemplate('promoted', 'employee', 'department_head', department.name); await sendNotificationService({ ...template, recipientIds: [headId], sendFCM: true }); // Blacklist tokens and send real-time notification await blacklistUserTokens(headId); await notifyRoleChange(headId, { action: 'promoted', oldRole: 'employee', newRole: 'department_head', department: department.name }); // Invalidate cache for the promoted user await invalidateCache.user(headId); res.status(200).json({ message: 'Head added to department', department }); } catch (error) { console.error(error); res.status(500).json({ message: 'Error adding head to department', error: error.message }); } }; export const removeHead = async (req, res) => { try { const department = await Department.findById(req.params.id); if (!department) { return res.status(404).json({ message: 'Department not found' }); } if (!department.head) { return res.status(404).json({ message: 'Department does not have a head' }); } const headId = department.head; department.head = undefined; await department.save(); // Change role back to employee (keep department assignment) await user.findByIdAndUpdate(headId, { role: 'employee' }); // Send system notification const template = roleChangeNotificationTemplate('demoted', 'department_head', 'employee', department.name); await sendNotificationService({ ...template, recipientIds: [headId], sendFCM: true }); // Blacklist tokens and send real-time notification await blacklistUserTokens(headId); await notifyRoleChange(headId, { action: 'demoted', oldRole: 'department_head', newRole: 'employee', department: department.name }); // Invalidate cache for the demoted user await invalidateCache.user(headId); res.status(200).json({ message: 'Department head removed', department }); } catch (error) { console.error(error); res.status(500).json({ message: 'Error removing department head', error: error.message }); } };