import { Request, Response } from 'express'; import { TatAlert } from '@models/TatAlert'; import { ApprovalLevel } from '@models/ApprovalLevel'; import { User } from '@models/User'; import { WorkflowRequest } from '@models/WorkflowRequest'; import logger from '@utils/logger'; import { sequelize } from '@config/database'; import { QueryTypes } from 'sequelize'; import { activityService } from '@services/activity.service'; import { getRequestMetadata } from '@utils/requestUtils'; import type { AuthenticatedRequest } from '../types/express'; /** * Get TAT alerts for a specific request */ export const getTatAlertsByRequest = async (req: Request, res: Response) => { try { const { requestId } = req.params; const alerts = await TatAlert.findAll({ where: { requestId }, include: [ { model: ApprovalLevel, as: 'level', attributes: ['levelNumber', 'levelName', 'approverName', 'status'] }, { model: User, as: 'approver', attributes: ['userId', 'displayName', 'email', 'department'] } ], order: [['alertSentAt', 'ASC']] }); res.json({ success: true, data: alerts }); } catch (error) { logger.error('[TAT Controller] Error fetching TAT alerts:', error); res.status(500).json({ success: false, error: 'Failed to fetch TAT alerts' }); } }; /** * Get TAT alerts for a specific approval level */ export const getTatAlertsByLevel = async (req: Request, res: Response) => { try { const { levelId } = req.params; const alerts = await TatAlert.findAll({ where: { levelId }, order: [['alertSentAt', 'ASC']] }); res.json({ success: true, data: alerts }); } catch (error) { logger.error('[TAT Controller] Error fetching TAT alerts by level:', error); res.status(500).json({ success: false, error: 'Failed to fetch TAT alerts' }); } }; /** * Get TAT compliance summary */ export const getTatComplianceSummary = async (req: Request, res: Response) => { try { const { startDate, endDate } = req.query; let dateFilter = ''; if (startDate && endDate) { dateFilter = `AND alert_sent_at BETWEEN '${startDate}' AND '${endDate}'`; } const summary = await sequelize.query(` SELECT COUNT(*) as total_alerts, COUNT(CASE WHEN alert_type = 'TAT_50' THEN 1 END) as alerts_50, COUNT(CASE WHEN alert_type = 'TAT_75' THEN 1 END) as alerts_75, COUNT(CASE WHEN alert_type = 'TAT_100' THEN 1 END) as breaches, COUNT(CASE WHEN was_completed_on_time = true THEN 1 END) as completed_on_time, COUNT(CASE WHEN was_completed_on_time = false THEN 1 END) as completed_late, ROUND( COUNT(CASE WHEN was_completed_on_time = true THEN 1 END) * 100.0 / NULLIF(COUNT(CASE WHEN was_completed_on_time IS NOT NULL THEN 1 END), 0), 2 ) as compliance_percentage FROM tat_alerts WHERE 1=1 ${dateFilter} `, { type: QueryTypes.SELECT }); res.json({ success: true, data: summary[0] || {} }); } catch (error) { logger.error('[TAT Controller] Error fetching TAT compliance summary:', error); res.status(500).json({ success: false, error: 'Failed to fetch TAT compliance summary' }); } }; /** * Get TAT breach report */ export const getTatBreachReport = async (req: Request, res: Response) => { try { const breaches = await sequelize.query(` SELECT ta.alert_id, ta.request_id, w.request_number, w.title as request_title, w.priority, al.level_number, al.approver_name, ta.tat_hours_allocated, ta.tat_hours_elapsed, ta.alert_sent_at, ta.completion_time, ta.was_completed_on_time, CASE WHEN ta.completion_time IS NULL THEN 'Still Pending' WHEN ta.was_completed_on_time = false THEN 'Completed Late' ELSE 'Completed On Time' END as completion_status FROM tat_alerts ta JOIN workflow_requests w ON ta.request_id = w.request_id JOIN approval_levels al ON ta.level_id = al.level_id WHERE ta.is_breached = true ORDER BY ta.alert_sent_at DESC LIMIT 100 `, { type: QueryTypes.SELECT }); res.json({ success: true, data: breaches }); } catch (error) { logger.error('[TAT Controller] Error fetching TAT breach report:', error); res.status(500).json({ success: false, error: 'Failed to fetch TAT breach report' }); } }; /** * Update breach reason for a TAT alert */ export const updateBreachReason = async (req: Request, res: Response) => { try { const { levelId } = req.params; const { breachReason } = req.body; const userId = (req as AuthenticatedRequest).user?.userId; const requestMeta = getRequestMetadata(req); if (!userId) { return res.status(401).json({ success: false, error: 'Unauthorized' }); } if (!breachReason || typeof breachReason !== 'string' || breachReason.trim().length === 0) { return res.status(400).json({ success: false, error: 'Breach reason is required' }); } // Get the approval level to verify permissions const level = await ApprovalLevel.findByPk(levelId); if (!level) { return res.status(404).json({ success: false, error: 'Approval level not found' }); } // Get user to check role const user = await User.findByPk(userId); if (!user) { return res.status(404).json({ success: false, error: 'User not found' }); } const userRole = (user as any).role; const approverId = (level as any).approverId; // Check permissions: ADMIN, MANAGEMENT, or the approver const hasPermission = userRole === 'ADMIN' || userRole === 'MANAGEMENT' || approverId === userId; if (!hasPermission) { return res.status(403).json({ success: false, error: 'You do not have permission to update breach reason' }); } // Get user details for activity logging const userDisplayName = (user as any).displayName || (user as any).email || 'Unknown User'; const isUpdate = !!(level as any).breachReason; // Check if this is an update or first time const levelNumber = (level as any).levelNumber; const approverName = (level as any).approverName || 'Unknown Approver'; // Update breach reason directly in approval_levels table await level.update({ breachReason: breachReason.trim() }); // Reload to get updated data await level.reload(); // Log activity for the request const userRoleLabel = userRole === 'ADMIN' ? 'Admin' : userRole === 'MANAGEMENT' ? 'Management' : 'Approver'; await activityService.log({ requestId: level.requestId, type: 'comment', // Using comment type for breach reason entry user: { userId: userId, name: userDisplayName, email: (user as any).email }, timestamp: new Date().toISOString(), action: isUpdate ? 'Updated TAT breach reason' : 'Added TAT breach reason', details: `${userDisplayName} (${userRoleLabel}) ${isUpdate ? 'updated' : 'added'} TAT breach reason for ${approverName} (Level ${levelNumber}): "${breachReason.trim()}"`, metadata: { levelId: level.levelId, levelNumber: levelNumber, approverName: approverName, breachReason: breachReason.trim(), updatedByRole: userRole }, ipAddress: requestMeta.ipAddress, userAgent: requestMeta.userAgent }); logger.info(`[TAT Controller] Breach reason ${isUpdate ? 'updated' : 'added'} for level ${levelId} by user ${userId} (${userRole})`); return res.json({ success: true, message: `Breach reason ${isUpdate ? 'updated' : 'added'} successfully`, data: { levelId: level.levelId, breachReason: breachReason.trim() } }); } catch (error) { logger.error('[TAT Controller] Error updating breach reason:', error); return res.status(500).json({ success: false, error: 'Failed to update breach reason' }); } }; /** * Get approver TAT performance */ export const getApproverTatPerformance = async (req: Request, res: Response) => { try { const { approverId } = req.params; const performance = await sequelize.query(` SELECT COUNT(DISTINCT ta.level_id) as total_approvals, COUNT(CASE WHEN ta.alert_type = 'TAT_50' THEN 1 END) as alerts_50_received, COUNT(CASE WHEN ta.alert_type = 'TAT_75' THEN 1 END) as alerts_75_received, COUNT(CASE WHEN ta.is_breached = true THEN 1 END) as breaches, AVG(ta.tat_hours_elapsed) as avg_hours_taken, ROUND( COUNT(CASE WHEN ta.was_completed_on_time = true THEN 1 END) * 100.0 / NULLIF(COUNT(CASE WHEN ta.was_completed_on_time IS NOT NULL THEN 1 END), 0), 2 ) as compliance_rate FROM tat_alerts ta WHERE ta.approver_id = :approverId `, { replacements: { approverId }, type: QueryTypes.SELECT }); res.json({ success: true, data: performance[0] || {} }); } catch (error) { logger.error('[TAT Controller] Error fetching approver TAT performance:', error); res.status(500).json({ success: false, error: 'Failed to fetch approver TAT performance' }); } };