TAT issue in dashboard scren for upcoming deadline
This commit is contained in:
parent
c7c9b62358
commit
826c0eedea
@ -10,6 +10,7 @@ import { Op, QueryTypes } from 'sequelize';
|
|||||||
import { sequelize } from '@config/database';
|
import { sequelize } from '@config/database';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import logger from '@utils/logger';
|
import logger from '@utils/logger';
|
||||||
|
import { calculateSLAStatus } from '@utils/tatTimeUtils';
|
||||||
|
|
||||||
interface DateRangeFilter {
|
interface DateRangeFilter {
|
||||||
start: Date;
|
start: Date;
|
||||||
@ -226,7 +227,7 @@ export class DashboardService {
|
|||||||
FROM approval_levels al
|
FROM approval_levels al
|
||||||
JOIN workflow_requests wf ON al.request_id = wf.request_id
|
JOIN workflow_requests wf ON al.request_id = wf.request_id
|
||||||
WHERE al.approver_id = :userId
|
WHERE al.approver_id = :userId
|
||||||
AND al.status = 'PENDING'
|
AND al.status = 'IN_PROGRESS'
|
||||||
AND wf.status IN ('PENDING', 'IN_PROGRESS')
|
AND wf.status IN ('PENDING', 'IN_PROGRESS')
|
||||||
AND wf.is_draft = false
|
AND wf.is_draft = false
|
||||||
AND al.level_number = wf.current_level
|
AND al.level_number = wf.current_level
|
||||||
@ -461,7 +462,7 @@ export class DashboardService {
|
|||||||
WHERE al.request_id = wf.request_id
|
WHERE al.request_id = wf.request_id
|
||||||
AND al.approver_id = :userId
|
AND al.approver_id = :userId
|
||||||
AND al.level_number = wf.current_level
|
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
|
AND ta.is_breached = true
|
||||||
) AS breach_count,
|
) AS breach_count,
|
||||||
(
|
(
|
||||||
SELECT SUM(al.tat_hours)
|
SELECT 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
|
|
||||||
FROM approval_levels al
|
FROM approval_levels al
|
||||||
WHERE al.request_id = wf.request_id
|
WHERE al.request_id = wf.request_id
|
||||||
AND al.level_number = wf.current_level
|
AND al.level_number = wf.current_level
|
||||||
LIMIT 1
|
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
|
FROM workflow_requests wf
|
||||||
${whereClause}
|
${whereClause}
|
||||||
AND (
|
AND (
|
||||||
@ -523,19 +520,40 @@ export class DashboardService {
|
|||||||
type: QueryTypes.SELECT
|
type: QueryTypes.SELECT
|
||||||
});
|
});
|
||||||
|
|
||||||
return criticalRequests.map((req: any) => ({
|
// Calculate working hours TAT for each critical request's current level
|
||||||
requestId: req.request_id,
|
const criticalWithSLA = await Promise.all(criticalRequests.map(async (req: any) => {
|
||||||
requestNumber: req.request_number,
|
const priority = (req.priority || 'standard').toLowerCase();
|
||||||
title: req.title,
|
const currentLevelTatHours = parseFloat(req.current_level_tat_hours) || 0;
|
||||||
priority: (req.priority || '').toLowerCase(),
|
const currentLevelStartTime = req.current_level_start_time;
|
||||||
status: (req.status || '').toLowerCase(),
|
|
||||||
currentLevel: req.current_level,
|
let currentLevelRemainingHours = currentLevelTatHours;
|
||||||
totalLevels: req.total_levels,
|
|
||||||
submissionDate: req.submission_date,
|
if (currentLevelStartTime && currentLevelTatHours > 0) {
|
||||||
totalTATHours: parseFloat(req.current_level_remaining_hours) || 0, // Use current level remaining
|
try {
|
||||||
breachCount: req.breach_count || 0,
|
// Use working hours calculation for current level
|
||||||
isCritical: req.breach_count > 0 || req.priority === 'EXPRESS'
|
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 user = await User.findByPk(userId);
|
||||||
const isAdmin = (user as any)?.isAdmin || false;
|
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 = `
|
let whereClause = `
|
||||||
WHERE al.status IN ('PENDING', 'IN_PROGRESS')
|
WHERE wf.status IN ('PENDING', 'IN_PROGRESS')
|
||||||
AND wf.is_draft = false
|
AND wf.is_draft = false
|
||||||
${!isAdmin ? `AND (
|
AND al.status = 'IN_PROGRESS'
|
||||||
al.approver_id = :userId
|
AND al.level_number = wf.current_level
|
||||||
OR wf.initiator_id = :userId
|
${!isAdmin ? `AND al.approver_id = :userId` : ''}
|
||||||
)` : ''}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const deadlines = await sequelize.query(`
|
const deadlines = await sequelize.query(`
|
||||||
@ -568,49 +586,61 @@ export class DashboardService {
|
|||||||
wf.request_number,
|
wf.request_number,
|
||||||
wf.title AS request_title,
|
wf.title AS request_title,
|
||||||
wf.priority,
|
wf.priority,
|
||||||
-- Calculate elapsed hours dynamically
|
wf.current_level,
|
||||||
CASE
|
wf.total_levels
|
||||||
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
|
|
||||||
FROM approval_levels al
|
FROM approval_levels al
|
||||||
JOIN workflow_requests wf ON al.request_id = wf.request_id
|
JOIN workflow_requests wf ON al.request_id = wf.request_id
|
||||||
${whereClause}
|
${whereClause}
|
||||||
ORDER BY tat_percentage_used DESC, remaining_hours ASC
|
ORDER BY al.level_start_time ASC
|
||||||
LIMIT :limit
|
LIMIT :limit
|
||||||
`, {
|
`, {
|
||||||
replacements: { userId, limit },
|
replacements: { userId, limit },
|
||||||
type: QueryTypes.SELECT
|
type: QueryTypes.SELECT
|
||||||
});
|
});
|
||||||
|
|
||||||
return deadlines.map((d: any) => ({
|
// Calculate working hours TAT for each deadline
|
||||||
levelId: d.level_id,
|
const deadlinesWithSLA = await Promise.all(deadlines.map(async (d: any) => {
|
||||||
requestId: d.request_id,
|
const priority = (d.priority || 'standard').toLowerCase();
|
||||||
requestNumber: d.request_number,
|
const tatHours = parseFloat(d.tat_hours) || 0;
|
||||||
requestTitle: d.request_title,
|
const levelStartTime = d.level_start_time;
|
||||||
levelNumber: d.level_number,
|
|
||||||
approverName: d.approver_name,
|
let elapsedHours = 0;
|
||||||
approverEmail: d.approver_email,
|
let remainingHours = tatHours;
|
||||||
tatHours: parseFloat(d.tat_hours) || 0,
|
let tatPercentageUsed = 0;
|
||||||
elapsedHours: parseFloat(d.elapsed_hours) || 0,
|
|
||||||
remainingHours: parseFloat(d.remaining_hours) || 0,
|
if (levelStartTime && tatHours > 0) {
|
||||||
tatPercentageUsed: parseFloat(d.tat_percentage_used) || 0,
|
try {
|
||||||
levelStartTime: d.level_start_time,
|
// Use working hours calculation (same as RequestDetail screen)
|
||||||
priority: (d.priority || '').toLowerCase()
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user