From 826c0eedea78371ce8d96a14bac5c5821824d418 Mon Sep 17 00:00:00 2001 From: laxmanhalaki Date: Fri, 7 Nov 2025 20:50:20 +0530 Subject: [PATCH] TAT issue in dashboard scren for upcoming deadline --- src/services/dashboard.service.ts | 162 ++++++++++++++++++------------ 1 file changed, 96 insertions(+), 66 deletions(-) diff --git a/src/services/dashboard.service.ts b/src/services/dashboard.service.ts index c83e2e1..b4cffd8 100644 --- a/src/services/dashboard.service.ts +++ b/src/services/dashboard.service.ts @@ -10,6 +10,7 @@ import { Op, QueryTypes } from 'sequelize'; import { sequelize } from '@config/database'; import dayjs from 'dayjs'; import logger from '@utils/logger'; +import { calculateSLAStatus } from '@utils/tatTimeUtils'; interface DateRangeFilter { start: Date; @@ -226,7 +227,7 @@ export class DashboardService { FROM approval_levels al JOIN workflow_requests wf ON al.request_id = wf.request_id WHERE al.approver_id = :userId - AND al.status = 'PENDING' + AND al.status = 'IN_PROGRESS' AND wf.status IN ('PENDING', 'IN_PROGRESS') AND wf.is_draft = false AND al.level_number = wf.current_level @@ -461,7 +462,7 @@ export class DashboardService { WHERE al.request_id = wf.request_id AND al.approver_id = :userId AND al.level_number = wf.current_level - AND al.status = 'PENDING' + AND al.status = 'IN_PROGRESS' ) )` : ''} `; @@ -484,23 +485,19 @@ export class DashboardService { AND ta.is_breached = true ) AS breach_count, ( - SELECT SUM(al.tat_hours) - FROM approval_levels al - WHERE al.request_id = wf.request_id - ) AS total_allocated_tat, - -- Calculate current level's remaining TAT dynamically - ( - SELECT - CASE - WHEN al.level_start_time IS NOT NULL AND al.tat_hours IS NOT NULL THEN - GREATEST(0, al.tat_hours - (EXTRACT(EPOCH FROM (NOW() - al.level_start_time)) / 3600.0)) - ELSE al.tat_hours - END + SELECT al.tat_hours FROM approval_levels al WHERE al.request_id = wf.request_id AND al.level_number = wf.current_level LIMIT 1 - ) AS current_level_remaining_hours + ) AS current_level_tat_hours, + ( + SELECT al.level_start_time + FROM approval_levels al + WHERE al.request_id = wf.request_id + AND al.level_number = wf.current_level + LIMIT 1 + ) AS current_level_start_time FROM workflow_requests wf ${whereClause} AND ( @@ -523,19 +520,40 @@ export class DashboardService { type: QueryTypes.SELECT }); - return criticalRequests.map((req: any) => ({ - requestId: req.request_id, - requestNumber: req.request_number, - title: req.title, - priority: (req.priority || '').toLowerCase(), - status: (req.status || '').toLowerCase(), - currentLevel: req.current_level, - totalLevels: req.total_levels, - submissionDate: req.submission_date, - totalTATHours: parseFloat(req.current_level_remaining_hours) || 0, // Use current level remaining - breachCount: req.breach_count || 0, - isCritical: req.breach_count > 0 || req.priority === 'EXPRESS' + // Calculate working hours TAT for each critical request's current level + const criticalWithSLA = await Promise.all(criticalRequests.map(async (req: any) => { + const priority = (req.priority || 'standard').toLowerCase(); + const currentLevelTatHours = parseFloat(req.current_level_tat_hours) || 0; + const currentLevelStartTime = req.current_level_start_time; + + let currentLevelRemainingHours = currentLevelTatHours; + + if (currentLevelStartTime && currentLevelTatHours > 0) { + try { + // Use working hours calculation for current level + const slaData = await calculateSLAStatus(currentLevelStartTime, currentLevelTatHours, priority); + currentLevelRemainingHours = slaData.remainingHours; + } catch (error) { + logger.error(`[Dashboard] Error calculating SLA for critical request ${req.request_id}:`, error); + } + } + + return { + requestId: req.request_id, + requestNumber: req.request_number, + title: req.title, + priority, + status: (req.status || '').toLowerCase(), + currentLevel: req.current_level, + totalLevels: req.total_levels, + submissionDate: req.submission_date, + totalTATHours: currentLevelRemainingHours, // Current level remaining hours + breachCount: req.breach_count || 0, + isCritical: req.breach_count > 0 || req.priority === 'EXPRESS' + }; })); + + return criticalWithSLA; } /** @@ -546,14 +564,14 @@ export class DashboardService { const user = await User.findByPk(userId); const isAdmin = (user as any)?.isAdmin || false; - // For regular users: only show levels where they are approver OR requests they initiated + // For regular users: only show CURRENT LEVEL where they are the approver + // For admins: show all current active levels let whereClause = ` - WHERE al.status IN ('PENDING', 'IN_PROGRESS') + WHERE wf.status IN ('PENDING', 'IN_PROGRESS') AND wf.is_draft = false - ${!isAdmin ? `AND ( - al.approver_id = :userId - OR wf.initiator_id = :userId - )` : ''} + AND al.status = 'IN_PROGRESS' + AND al.level_number = wf.current_level + ${!isAdmin ? `AND al.approver_id = :userId` : ''} `; const deadlines = await sequelize.query(` @@ -568,49 +586,61 @@ export class DashboardService { wf.request_number, wf.title AS request_title, wf.priority, - -- Calculate elapsed hours dynamically - CASE - WHEN al.level_start_time IS NOT NULL THEN - EXTRACT(EPOCH FROM (NOW() - al.level_start_time)) / 3600.0 - ELSE 0 - END AS elapsed_hours, - -- Calculate remaining hours dynamically - CASE - WHEN al.level_start_time IS NOT NULL AND al.tat_hours IS NOT NULL THEN - GREATEST(0, al.tat_hours - (EXTRACT(EPOCH FROM (NOW() - al.level_start_time)) / 3600.0)) - ELSE al.tat_hours - END AS remaining_hours, - -- Calculate percentage used dynamically - CASE - WHEN al.level_start_time IS NOT NULL AND al.tat_hours IS NOT NULL AND al.tat_hours > 0 THEN - LEAST(100, ((EXTRACT(EPOCH FROM (NOW() - al.level_start_time)) / 3600.0) / al.tat_hours) * 100) - ELSE 0 - END AS tat_percentage_used + wf.current_level, + wf.total_levels FROM approval_levels al JOIN workflow_requests wf ON al.request_id = wf.request_id ${whereClause} - ORDER BY tat_percentage_used DESC, remaining_hours ASC + ORDER BY al.level_start_time ASC LIMIT :limit `, { replacements: { userId, limit }, type: QueryTypes.SELECT }); - return deadlines.map((d: any) => ({ - levelId: d.level_id, - requestId: d.request_id, - requestNumber: d.request_number, - requestTitle: d.request_title, - levelNumber: d.level_number, - approverName: d.approver_name, - approverEmail: d.approver_email, - tatHours: parseFloat(d.tat_hours) || 0, - elapsedHours: parseFloat(d.elapsed_hours) || 0, - remainingHours: parseFloat(d.remaining_hours) || 0, - tatPercentageUsed: parseFloat(d.tat_percentage_used) || 0, - levelStartTime: d.level_start_time, - priority: (d.priority || '').toLowerCase() + // Calculate working hours TAT for each deadline + const deadlinesWithSLA = await Promise.all(deadlines.map(async (d: any) => { + const priority = (d.priority || 'standard').toLowerCase(); + const tatHours = parseFloat(d.tat_hours) || 0; + const levelStartTime = d.level_start_time; + + let elapsedHours = 0; + let remainingHours = tatHours; + let tatPercentageUsed = 0; + + if (levelStartTime && tatHours > 0) { + try { + // Use working hours calculation (same as RequestDetail screen) + const slaData = await calculateSLAStatus(levelStartTime, tatHours, priority); + elapsedHours = slaData.elapsedHours; + remainingHours = slaData.remainingHours; + tatPercentageUsed = slaData.percentageUsed; + } catch (error) { + logger.error(`[Dashboard] Error calculating SLA for level ${d.level_id}:`, error); + } + } + + return { + levelId: d.level_id, + requestId: d.request_id, + requestNumber: d.request_number, + requestTitle: d.request_title, + levelNumber: d.level_number, + currentLevel: d.current_level, + totalLevels: d.total_levels, + approverName: d.approver_name, + approverEmail: d.approver_email, + tatHours, + elapsedHours, + remainingHours, + tatPercentageUsed, + levelStartTime, + priority + }; })); + + // Sort by TAT percentage used (descending) and return + return deadlinesWithSLA.sort((a, b) => b.tatPercentageUsed - a.tatPercentageUsed); } /**