avg cycle time, my request stat mismatch resolved
This commit is contained in:
parent
0435159e2f
commit
7fd5d58080
@ -1,2 +1,2 @@
|
||||
import{a as t}from"./index-C-Pt4yOr.js";import"./radix-vendor-BP4rDxsU.js";import"./charts-vendor-Cji9-Yri.js";import"./utils-vendor-DHm03ykU.js";import"./ui-vendor-DB5PynB_.js";import"./socket-vendor-TjCxX7sJ.js";import"./router-vendor-1fSSvDCY.js";async function l(n){return(await t.post(`/conclusions/${n}/generate`)).data.data}async function m(n,o){return(await t.post(`/conclusions/${n}/finalize`,{finalRemark:o})).data.data}async function d(n){return(await t.get(`/conclusions/${n}`)).data.data}export{m as finalizeConclusion,l as generateConclusion,d as getConclusion};
|
||||
//# sourceMappingURL=conclusionApi-DaHT8E_1.js.map
|
||||
import{a as t}from"./index-BJC2x1CB.js";import"./radix-vendor-BP4rDxsU.js";import"./charts-vendor-Cji9-Yri.js";import"./utils-vendor-DHm03ykU.js";import"./ui-vendor-DB5PynB_.js";import"./socket-vendor-TjCxX7sJ.js";import"./router-vendor-1fSSvDCY.js";async function l(n){return(await t.post(`/conclusions/${n}/generate`)).data.data}async function m(n,o){return(await t.post(`/conclusions/${n}/finalize`,{finalRemark:o})).data.data}async function d(n){return(await t.get(`/conclusions/${n}`)).data.data}export{m as finalizeConclusion,l as generateConclusion,d as getConclusion};
|
||||
//# sourceMappingURL=conclusionApi-BNkt5Ttj.js.map
|
||||
@ -1 +1 @@
|
||||
{"version":3,"file":"conclusionApi-DaHT8E_1.js","sources":["../../src/services/conclusionApi.ts"],"sourcesContent":["import apiClient from './authApi';\r\n\r\nexport interface ConclusionRemark {\r\n conclusionId: string;\r\n requestId: string;\r\n aiGeneratedRemark: string | null;\r\n aiModelUsed: string | null;\r\n aiConfidenceScore: number | null;\r\n finalRemark: string | null;\r\n editedBy: string | null;\r\n isEdited: boolean;\r\n editCount: number;\r\n approvalSummary: any;\r\n documentSummary: any;\r\n keyDiscussionPoints: string[];\r\n generatedAt: string | null;\r\n finalizedAt: string | null;\r\n createdAt: string;\r\n updatedAt: string;\r\n}\r\n\r\n/**\r\n * Generate AI-powered conclusion remark\r\n */\r\nexport async function generateConclusion(requestId: string): Promise<{\r\n conclusionId: string;\r\n aiGeneratedRemark: string;\r\n keyDiscussionPoints: string[];\r\n confidence: number;\r\n generatedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/generate`);\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Update conclusion remark (edit by initiator)\r\n */\r\nexport async function updateConclusion(requestId: string, finalRemark: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.put(`/conclusions/${requestId}`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Finalize conclusion and close request\r\n */\r\nexport async function finalizeConclusion(requestId: string, finalRemark: string): Promise<{\r\n conclusionId: string;\r\n requestNumber: string;\r\n status: string;\r\n finalRemark: string;\r\n finalizedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/finalize`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Get conclusion for a request\r\n */\r\nexport async function getConclusion(requestId: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.get(`/conclusions/${requestId}`);\r\n return response.data.data;\r\n}\r\n\r\n"],"names":["generateConclusion","requestId","apiClient","finalizeConclusion","finalRemark","getConclusion"],"mappings":"0PAwBA,eAAsBA,EAAmBC,EAMtC,CAED,OADiB,MAAMC,EAAU,KAAK,gBAAgBD,CAAS,WAAW,GAC1D,KAAK,IACvB,CAaA,eAAsBE,EAAmBF,EAAmBG,EAMzD,CAED,OADiB,MAAMF,EAAU,KAAK,gBAAgBD,CAAS,YAAa,CAAE,YAAAG,EAAa,GAC3E,KAAK,IACvB,CAKA,eAAsBC,EAAcJ,EAA8C,CAEhF,OADiB,MAAMC,EAAU,IAAI,gBAAgBD,CAAS,EAAE,GAChD,KAAK,IACvB"}
|
||||
{"version":3,"file":"conclusionApi-BNkt5Ttj.js","sources":["../../src/services/conclusionApi.ts"],"sourcesContent":["import apiClient from './authApi';\r\n\r\nexport interface ConclusionRemark {\r\n conclusionId: string;\r\n requestId: string;\r\n aiGeneratedRemark: string | null;\r\n aiModelUsed: string | null;\r\n aiConfidenceScore: number | null;\r\n finalRemark: string | null;\r\n editedBy: string | null;\r\n isEdited: boolean;\r\n editCount: number;\r\n approvalSummary: any;\r\n documentSummary: any;\r\n keyDiscussionPoints: string[];\r\n generatedAt: string | null;\r\n finalizedAt: string | null;\r\n createdAt: string;\r\n updatedAt: string;\r\n}\r\n\r\n/**\r\n * Generate AI-powered conclusion remark\r\n */\r\nexport async function generateConclusion(requestId: string): Promise<{\r\n conclusionId: string;\r\n aiGeneratedRemark: string;\r\n keyDiscussionPoints: string[];\r\n confidence: number;\r\n generatedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/generate`);\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Update conclusion remark (edit by initiator)\r\n */\r\nexport async function updateConclusion(requestId: string, finalRemark: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.put(`/conclusions/${requestId}`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Finalize conclusion and close request\r\n */\r\nexport async function finalizeConclusion(requestId: string, finalRemark: string): Promise<{\r\n conclusionId: string;\r\n requestNumber: string;\r\n status: string;\r\n finalRemark: string;\r\n finalizedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/finalize`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Get conclusion for a request\r\n */\r\nexport async function getConclusion(requestId: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.get(`/conclusions/${requestId}`);\r\n return response.data.data;\r\n}\r\n\r\n"],"names":["generateConclusion","requestId","apiClient","finalizeConclusion","finalRemark","getConclusion"],"mappings":"0PAwBA,eAAsBA,EAAmBC,EAMtC,CAED,OADiB,MAAMC,EAAU,KAAK,gBAAgBD,CAAS,WAAW,GAC1D,KAAK,IACvB,CAaA,eAAsBE,EAAmBF,EAAmBG,EAMzD,CAED,OADiB,MAAMF,EAAU,KAAK,gBAAgBD,CAAS,YAAa,CAAE,YAAAG,EAAa,GAC3E,KAAK,IACvB,CAKA,eAAsBC,EAAcJ,EAA8C,CAEhF,OADiB,MAAMC,EAAU,IAAI,gBAAgBD,CAAS,EAAE,GAChD,KAAK,IACvB"}
|
||||
File diff suppressed because one or more lines are too long
1
build/assets/index-BJC2x1CB.js.map
Normal file
1
build/assets/index-BJC2x1CB.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -52,7 +52,7 @@
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/index-C-Pt4yOr.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-BJC2x1CB.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/charts-vendor-Cji9-Yri.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/radix-vendor-BP4rDxsU.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/utils-vendor-DHm03ykU.js">
|
||||
|
||||
@ -133,15 +133,42 @@ export class DashboardService {
|
||||
search?: string,
|
||||
slaCompliance?: string
|
||||
) {
|
||||
const range = this.parseDateRange(dateRange, startDate, endDate);
|
||||
// Check if date range should be applied
|
||||
const applyDateRange = dateRange !== undefined && dateRange !== null;
|
||||
const range = applyDateRange ? this.parseDateRange(dateRange, startDate, endDate) : null;
|
||||
|
||||
// Check if user is admin or management (has broader access)
|
||||
const user = await User.findByPk(userId);
|
||||
const isAdmin = user?.hasManagementAccess() || false;
|
||||
|
||||
// Build filter conditions (excluding status - stats should show all statuses)
|
||||
// Build filter conditions
|
||||
let filterConditions = '';
|
||||
const replacements: any = { start: range.start, end: range.end, userId };
|
||||
const replacements: any = { userId };
|
||||
|
||||
// Add date range to replacements if date range is applied
|
||||
if (applyDateRange && range) {
|
||||
replacements.start = range.start;
|
||||
replacements.end = range.end;
|
||||
}
|
||||
|
||||
// Status filter
|
||||
if (status && status !== 'all') {
|
||||
const statusUpper = status.toUpperCase();
|
||||
if (statusUpper === 'PENDING') {
|
||||
// Pending includes both PENDING and IN_PROGRESS
|
||||
filterConditions += ` AND (wf.status = 'PENDING' OR wf.status = 'IN_PROGRESS')`;
|
||||
} else if (statusUpper === 'CLOSED') {
|
||||
filterConditions += ` AND wf.status = 'CLOSED'`;
|
||||
} else if (statusUpper === 'REJECTED') {
|
||||
filterConditions += ` AND wf.status = 'REJECTED'`;
|
||||
} else if (statusUpper === 'APPROVED') {
|
||||
filterConditions += ` AND wf.status = 'APPROVED'`;
|
||||
} else {
|
||||
// Fallback: use the uppercase value as-is
|
||||
filterConditions += ` AND wf.status = :status`;
|
||||
replacements.status = statusUpper;
|
||||
}
|
||||
}
|
||||
|
||||
// Priority filter
|
||||
if (priority && priority !== 'all') {
|
||||
@ -223,26 +250,42 @@ export class DashboardService {
|
||||
|
||||
// Organization Level: Admin/Management see ALL requests across organization
|
||||
// Personal Level: Regular users see only requests they INITIATED
|
||||
// Note: For pending/open requests, count ALL pending requests regardless of creation date
|
||||
// For approved/rejected, count only those submitted in date range (use submission_date, not created_at)
|
||||
let whereClauseForDateRange = `
|
||||
WHERE wf.submission_date BETWEEN :start AND :end
|
||||
// Note: If dateRange is provided, filter by submission_date. Otherwise, show all requests.
|
||||
// For pending/open requests, if no date range, count ALL pending requests regardless of creation date
|
||||
// For approved/rejected/closed, if date range is provided, count only those submitted in date range
|
||||
const dateFilterClause = applyDateRange
|
||||
? `wf.submission_date BETWEEN :start AND :end AND wf.submission_date IS NOT NULL`
|
||||
: `1=1`; // No date filter - show all requests
|
||||
|
||||
let whereClauseForAllRequests = `
|
||||
WHERE ${dateFilterClause}
|
||||
AND wf.is_draft = false
|
||||
AND (wf.is_deleted IS NULL OR wf.is_deleted = false)
|
||||
AND wf.submission_date IS NOT NULL
|
||||
${!isAdmin ? `AND wf.initiator_id = :userId` : ''}
|
||||
${filterConditions}
|
||||
`;
|
||||
|
||||
let whereClauseForAllPending = `
|
||||
WHERE wf.is_draft = false
|
||||
// For pending requests, if no date range is applied, don't filter by date at all
|
||||
// This ensures pending requests are always counted regardless of submission date
|
||||
const pendingDateFilterClause = applyDateRange
|
||||
? `wf.submission_date BETWEEN :start AND :end AND wf.submission_date IS NOT NULL`
|
||||
: `1=1`; // No date filter for pending requests
|
||||
|
||||
let whereClauseForPending = `
|
||||
WHERE ${pendingDateFilterClause}
|
||||
AND wf.is_draft = false
|
||||
AND (wf.is_deleted IS NULL OR wf.is_deleted = false)
|
||||
AND (wf.status = 'PENDING' OR wf.status = 'IN_PROGRESS')
|
||||
${!isAdmin ? `AND wf.initiator_id = :userId` : ''}
|
||||
${filterConditions}
|
||||
${filterConditions.replace(/AND \(wf\.status = 'PENDING' OR wf\.status = 'IN_PROGRESS'\)|AND wf\.status = 'PENDING'|AND wf\.status = 'IN_PROGRESS'/g, '').trim()}
|
||||
`;
|
||||
|
||||
// Get total, approved, rejected, and closed requests created in date range
|
||||
// Clean up any double ANDs
|
||||
whereClauseForPending = whereClauseForPending.replace(/\s+AND\s+AND/g, ' AND');
|
||||
|
||||
// Get total, approved, rejected, and closed requests
|
||||
// If date range is applied, only count requests submitted in that range
|
||||
// If no date range, count all requests matching other filters
|
||||
const result = await sequelize.query(`
|
||||
SELECT
|
||||
COUNT(*)::int AS total_requests,
|
||||
@ -250,19 +293,20 @@ export class DashboardService {
|
||||
COUNT(CASE WHEN wf.status = 'REJECTED' THEN 1 END)::int AS rejected_requests,
|
||||
COUNT(CASE WHEN wf.status = 'CLOSED' THEN 1 END)::int AS closed_requests
|
||||
FROM workflow_requests wf
|
||||
${whereClauseForDateRange}
|
||||
${whereClauseForAllRequests}
|
||||
`, {
|
||||
replacements,
|
||||
type: QueryTypes.SELECT
|
||||
});
|
||||
|
||||
// Get ALL pending/open requests (regardless of creation date)
|
||||
// Get ALL pending/open requests
|
||||
// Organization Level (Admin): All pending requests across organization
|
||||
// Personal Level (Regular User): Only pending requests they initiated
|
||||
// If no date range, count all pending requests regardless of submission date
|
||||
const pendingResult = await sequelize.query(`
|
||||
SELECT COUNT(*)::int AS open_requests
|
||||
FROM workflow_requests wf
|
||||
${whereClauseForAllPending}
|
||||
${whereClauseForPending}
|
||||
`, {
|
||||
replacements,
|
||||
type: QueryTypes.SELECT
|
||||
@ -311,11 +355,11 @@ export class DashboardService {
|
||||
|
||||
// For regular users: only their initiated requests
|
||||
// For admin: all requests
|
||||
// Include requests that were COMPLETED (APPROVED, REJECTED, or CLOSED) within the date range
|
||||
// CLOSED status represents approved requests that were finalized with a conclusion remark
|
||||
// Include only CLOSED requests (ignore APPROVED and REJECTED)
|
||||
// CLOSED status represents requests that were finalized with a conclusion remark
|
||||
// This ensures we capture all requests that finished during the period, regardless of when they started
|
||||
let whereClause = `
|
||||
WHERE wf.status IN ('APPROVED', 'REJECTED', 'CLOSED')
|
||||
WHERE wf.status = 'CLOSED'
|
||||
AND wf.is_draft = false
|
||||
AND wf.submission_date IS NOT NULL
|
||||
AND (
|
||||
@ -325,7 +369,7 @@ export class DashboardService {
|
||||
${!isAdmin ? `AND wf.initiator_id = :userId` : ''}
|
||||
`;
|
||||
|
||||
// Get completed requests with their submission and closure dates
|
||||
// Get closed requests with their submission and closure dates
|
||||
const completedRequests = await sequelize.query(`
|
||||
SELECT
|
||||
wf.request_id,
|
||||
@ -340,11 +384,11 @@ export class DashboardService {
|
||||
type: QueryTypes.SELECT
|
||||
});
|
||||
|
||||
// Calculate cycle time using working hours for each request
|
||||
// Calculate cycle time using working hours for each request, grouped by priority
|
||||
const { calculateElapsedWorkingHours } = await import('@utils/tatTimeUtils');
|
||||
const cycleTimes: number[] = [];
|
||||
const priorityCycleTimes = new Map<string, number[]>();
|
||||
|
||||
logger.info(`[Dashboard] Calculating cycle time for ${completedRequests.length} completed requests`);
|
||||
logger.info(`[Dashboard] Calculating cycle time for ${completedRequests.length} closed requests`);
|
||||
|
||||
for (const req of completedRequests as any) {
|
||||
const submissionDate = req.submission_date;
|
||||
@ -362,7 +406,13 @@ export class DashboardService {
|
||||
completionDate,
|
||||
priority
|
||||
);
|
||||
cycleTimes.push(elapsedHours);
|
||||
|
||||
// Group by priority
|
||||
if (!priorityCycleTimes.has(priority)) {
|
||||
priorityCycleTimes.set(priority, []);
|
||||
}
|
||||
priorityCycleTimes.get(priority)!.push(elapsedHours);
|
||||
|
||||
logger.info(`[Dashboard] Request ${req.request_id} (${priority}): ${elapsedHours.toFixed(2)}h (submission: ${submissionDate}, completion: ${completionDate})`);
|
||||
} catch (error) {
|
||||
logger.error(`[Dashboard] Error calculating cycle time for request ${req.request_id}:`, error);
|
||||
@ -376,6 +426,31 @@ export class DashboardService {
|
||||
// This ensures consistency between Dashboard and All Requests screen
|
||||
}
|
||||
|
||||
// Calculate average per priority
|
||||
const expressCycleTimes = priorityCycleTimes.get('express') || [];
|
||||
const standardCycleTimes = priorityCycleTimes.get('standard') || [];
|
||||
|
||||
const expressAvg = expressCycleTimes.length > 0
|
||||
? Math.round((expressCycleTimes.reduce((sum, hours) => sum + hours, 0) / expressCycleTimes.length) * 100) / 100
|
||||
: 0;
|
||||
|
||||
const standardAvg = standardCycleTimes.length > 0
|
||||
? Math.round((standardCycleTimes.reduce((sum, hours) => sum + hours, 0) / standardCycleTimes.length) * 100) / 100
|
||||
: 0;
|
||||
|
||||
// Calculate overall average as average of EXPRESS and STANDARD averages
|
||||
// This is the average of the two priority averages (not weighted by count)
|
||||
let avgCycleTimeHours = 0;
|
||||
if (expressAvg > 0 && standardAvg > 0) {
|
||||
avgCycleTimeHours = Math.round(((expressAvg + standardAvg) / 2) * 100) / 100;
|
||||
} else if (expressAvg > 0) {
|
||||
avgCycleTimeHours = expressAvg;
|
||||
} else if (standardAvg > 0) {
|
||||
avgCycleTimeHours = standardAvg;
|
||||
}
|
||||
|
||||
logger.info(`[Dashboard] Cycle time calculation: EXPRESS=${expressAvg.toFixed(2)}h (${expressCycleTimes.length} requests), STANDARD=${standardAvg.toFixed(2)}h (${standardCycleTimes.length} requests), Overall=${avgCycleTimeHours.toFixed(2)}h`);
|
||||
|
||||
// Count ALL requests (pending, in-progress, approved, rejected, closed) that have currently breached TAT
|
||||
// Use the same logic as Requests screen: check currentLevelSLA status using calculateSLAStatus
|
||||
// This ensures delayedWorkflows matches what users see when filtering for "breached" in All Requests screen
|
||||
@ -396,12 +471,12 @@ export class DashboardService {
|
||||
FROM workflow_requests wf
|
||||
LEFT JOIN approval_levels al ON al.request_id = wf.request_id
|
||||
AND al.level_number = wf.current_level
|
||||
AND (al.status = 'IN_PROGRESS' OR (wf.status IN ('APPROVED', 'REJECTED', 'CLOSED') AND al.status = 'APPROVED'))
|
||||
AND (al.status = 'IN_PROGRESS' OR (wf.status = 'CLOSED' AND al.status = 'APPROVED'))
|
||||
WHERE wf.is_draft = false
|
||||
AND wf.submission_date IS NOT NULL
|
||||
AND (
|
||||
-- Completed requests: must be completed in date range
|
||||
(wf.status IN ('APPROVED', 'REJECTED', 'CLOSED')
|
||||
-- Completed requests: must be CLOSED in date range (ignore APPROVED and REJECTED)
|
||||
(wf.status = 'CLOSED'
|
||||
AND (
|
||||
(wf.closure_date IS NOT NULL AND wf.closure_date BETWEEN :start AND :end)
|
||||
OR (wf.closure_date IS NULL AND wf.updated_at BETWEEN :start AND :end)
|
||||
@ -439,7 +514,7 @@ export class DashboardService {
|
||||
let recalculatedCompliantCount = 0;
|
||||
|
||||
for (const req of allRequestsBreached as any) {
|
||||
const isCompleted = req.status === 'APPROVED' || req.status === 'REJECTED' || req.status === 'CLOSED';
|
||||
const isCompleted = req.status === 'CLOSED';
|
||||
|
||||
// Check current level SLA (same logic as Requests screen)
|
||||
let isBreached = false;
|
||||
@ -493,27 +568,20 @@ export class DashboardService {
|
||||
// Total delayed workflows = completed breached + currently pending/in-progress breached
|
||||
const totalDelayedWorkflows = finalBreachedCount + pendingBreachedCount;
|
||||
|
||||
// Compliant workflows = all completed requests (APPROVED, REJECTED, CLOSED) that did NOT breach TAT
|
||||
// Compliant workflows = all CLOSED requests that did NOT breach TAT
|
||||
// This includes:
|
||||
// - Approved requests that were approved within TAT
|
||||
// - Closed requests that were closed within TAT
|
||||
// - Rejected requests that were rejected within TAT (before TAT was exceeded)
|
||||
// - Closed requests that were closed within TAT
|
||||
// Use recalculated compliant count from above which uses same logic as Requests screen
|
||||
// Note: Only counting CLOSED requests now (APPROVED and REJECTED are ignored)
|
||||
const totalCompleted = recalculatedBreachedCount + recalculatedCompliantCount;
|
||||
const compliantCount = recalculatedCompliantCount;
|
||||
|
||||
// Compliance percentage = (compliant / total completed) * 100
|
||||
// This shows what percentage of completed requests (approved/closed/rejected) were completed within TAT
|
||||
// This shows what percentage of CLOSED requests were completed within TAT
|
||||
const compliancePercent = totalCompleted > 0 ? Math.round((compliantCount / totalCompleted) * 100) : 0;
|
||||
|
||||
// Calculate average cycle time (rounded to 2 decimal places for accuracy)
|
||||
const sum = cycleTimes.reduce((sum, hours) => sum + hours, 0);
|
||||
const avgCycleTimeHours = cycleTimes.length > 0
|
||||
? Math.round((sum / cycleTimes.length) * 100) / 100
|
||||
: 0;
|
||||
|
||||
logger.info(`[Dashboard] Cycle time calculation: ${cycleTimes.length} requests included, sum: ${sum.toFixed(2)}h, average: ${avgCycleTimeHours.toFixed(2)}h`);
|
||||
logger.info(`[Dashboard] Compliance calculation: ${totalCompleted} total completed (APPROVED/REJECTED/CLOSED), ${finalBreachedCount} breached, ${compliantCount} compliant`);
|
||||
// Average cycle time is already calculated above from priority averages
|
||||
logger.info(`[Dashboard] Compliance calculation: ${totalCompleted} total completed (CLOSED), ${finalBreachedCount} breached, ${compliantCount} compliant`);
|
||||
logger.info(`[Dashboard] Breached requests (using Requests screen logic): ${finalBreachedCount} completed breached + ${pendingBreachedCount} pending/in-progress breached = ${totalDelayedWorkflows} total delayed`);
|
||||
|
||||
return {
|
||||
@ -1650,12 +1718,15 @@ export class DashboardService {
|
||||
type: QueryTypes.SELECT
|
||||
});
|
||||
|
||||
// Get only COMPLETED requests for cycle time calculation
|
||||
// Get only CLOSED requests for cycle time calculation (ignore APPROVED and REJECTED)
|
||||
let whereClauseCompleted = `
|
||||
WHERE wf.submission_date BETWEEN :start AND :end
|
||||
AND wf.status IN ('APPROVED', 'REJECTED')
|
||||
WHERE wf.status = 'CLOSED'
|
||||
AND wf.is_draft = false
|
||||
AND wf.submission_date IS NOT NULL
|
||||
AND (
|
||||
(wf.closure_date IS NOT NULL AND wf.closure_date BETWEEN :start AND :end)
|
||||
OR (wf.closure_date IS NULL AND wf.updated_at BETWEEN :start AND :end)
|
||||
)
|
||||
${!isAdmin ? `AND wf.initiator_id = :userId` : ''}
|
||||
`;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user