Re_Backend/src/controllers/tat.controller.ts

316 lines
9.4 KiB
TypeScript

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'
});
}
};