# Holiday Handling & EXPRESS Mode TAT Calculation ## Overview The TAT (Turn Around Time) system now supports: 1. **Holiday Exclusions** - Configured holidays are excluded from STANDARD priority TAT calculations 2. **EXPRESS Mode** - EXPRESS priority requests use 24/7 calculation (no exclusions) --- ## How It Works ### **STANDARD Priority (Default)** **Calculation:** - ✅ Excludes weekends (Saturday, Sunday) - ✅ Excludes non-working hours (9 AM - 6 PM by default) - ✅ **Excludes holidays configured in Admin Settings** **Example:** ``` TAT = 16 working hours Start: Monday 2:00 PM Calculation: Monday 2:00 PM - 6:00 PM = 4 hours (remaining: 12h) Tuesday 9:00 AM - 6:00 PM = 9 hours (remaining: 3h) Wednesday 9:00 AM - 12:00 PM = 3 hours (remaining: 0h) If Wednesday is a HOLIDAY → Skip to Thursday: Wednesday (HOLIDAY) = 0 hours (skipped) Thursday 9:00 AM - 12:00 PM = 3 hours (remaining: 0h) Final deadline: Thursday 12:00 PM ✅ ``` --- ### **EXPRESS Priority** **Calculation:** - ✅ Counts ALL hours (24/7) - ✅ **No weekend exclusion** - ✅ **No non-working hours exclusion** - ✅ **No holiday exclusion** **Example:** ``` TAT = 16 hours Start: Monday 2:00 PM Calculation: Simply add 16 hours: Monday 2:00 PM + 16 hours = Tuesday 6:00 AM Final deadline: Tuesday 6:00 AM ✅ (Even if Tuesday is a holiday, it still counts) ``` --- ## Holiday Configuration Flow ### **1. Admin Adds Holiday** ``` Settings Page → Holiday Manager → Add Holiday Name: "Christmas Day" Date: 2025-12-25 Type: Public Holiday ✅ Save ``` ### **2. Holiday Stored in Database** ```sql INSERT INTO holidays (holiday_date, holiday_name, holiday_type, is_active) VALUES ('2025-12-25', 'Christmas Day', 'PUBLIC_HOLIDAY', true); ``` ### **3. Holiday Cache Updated** ```typescript // Holidays are cached in memory for 6 hours await loadHolidaysCache(); // → holidaysCache = Set(['2025-12-25', '2025-01-01', ...]) ``` ### **4. TAT Calculation Uses Holiday Cache** ```typescript // When scheduling TAT jobs if (priority === 'STANDARD') { // Working hours calculation - checks holidays const threshold1 = await addWorkingHours(start, hours * 0.55); // → If date is in holidaysCache, it's skipped ✅ } else { // EXPRESS: 24/7 calculation - ignores holidays const threshold1 = addCalendarHours(start, hours * 0.55); // → Adds hours directly, no checks ✅ } ``` --- ## Implementation Details ### **Function: `addWorkingHours()` (STANDARD Mode)** ```typescript export async function addWorkingHours(start: Date, hoursToAdd: number): Promise { let current = dayjs(start); // Load holidays from database (cached) await loadHolidaysCache(); let remaining = hoursToAdd; while (remaining > 0) { current = current.add(1, 'hour'); // Check if current hour is working time if (isWorkingTime(current)) { // ✅ Checks holidays here remaining -= 1; } } return current; } function isWorkingTime(date: Dayjs): boolean { // Check weekend if (date.day() === 0 || date.day() === 6) return false; // Check working hours if (date.hour() < 9 || date.hour() >= 18) return false; // Check if holiday ✅ if (isHoliday(date)) return false; return true; } function isHoliday(date: Dayjs): boolean { const dateStr = date.format('YYYY-MM-DD'); return holidaysCache.has(dateStr); // ✅ Checks cached holidays } ``` --- ### **Function: `addCalendarHours()` (EXPRESS Mode)** ```typescript export function addCalendarHours(start: Date, hoursToAdd: number): Dayjs { // Simple addition - no checks ✅ return dayjs(start).add(hoursToAdd, 'hour'); } ``` --- ## TAT Scheduler Integration ### **Updated Method Signature:** ```typescript async scheduleTatJobs( requestId: string, levelId: string, approverId: string, tatDurationHours: number, startTime?: Date, priority: Priority = Priority.STANDARD // ✅ New parameter ): Promise ``` ### **Priority-Based Calculation:** ```typescript const isExpress = priority === Priority.EXPRESS; if (isExpress) { // EXPRESS: 24/7 calculation threshold1Time = addCalendarHours(now, hours * 0.55).toDate(); threshold2Time = addCalendarHours(now, hours * 0.80).toDate(); breachTime = addCalendarHours(now, hours).toDate(); logger.info('Using EXPRESS mode (24/7) - no holiday/weekend exclusions'); } else { // STANDARD: Working hours, exclude holidays const t1 = await addWorkingHours(now, hours * 0.55); const t2 = await addWorkingHours(now, hours * 0.80); const tBreach = await addWorkingHours(now, hours); threshold1Time = t1.toDate(); threshold2Time = t2.toDate(); breachTime = tBreach.toDate(); logger.info('Using STANDARD mode - excludes holidays, weekends, non-working hours'); } ``` --- ## Example Scenarios ### **Scenario 1: STANDARD with Holiday** ``` Request Details: - Priority: STANDARD - TAT: 16 working hours - Start: Monday 2:00 PM - Holiday: Wednesday (Christmas) Calculation: Monday 2:00 PM - 6:00 PM = 4 hours (12h remaining) Tuesday 9:00 AM - 6:00 PM = 9 hours (3h remaining) Wednesday (HOLIDAY) = SKIPPED ✅ Thursday 9:00 AM - 12:00 PM = 3 hours (0h remaining) TAT Milestones: - Threshold 1 (55%): Tuesday 4:40 PM (8.8 working hours) - Threshold 2 (80%): Thursday 10:48 AM (12.8 working hours) - Breach (100%): Thursday 12:00 PM (16 working hours) ``` --- ### **Scenario 2: EXPRESS with Holiday** ``` Request Details: - Priority: EXPRESS - TAT: 16 hours - Start: Monday 2:00 PM - Holiday: Wednesday (Christmas) - IGNORED ✅ Calculation: Monday 2:00 PM + 16 hours = Tuesday 6:00 AM TAT Milestones: - Threshold 1 (55%): Monday 10:48 PM (8.8 hours) - Threshold 2 (80%): Tuesday 2:48 AM (12.8 hours) - Breach (100%): Tuesday 6:00 AM (16 hours) Note: Even though Wednesday is a holiday, EXPRESS doesn't care ✅ ``` --- ### **Scenario 3: Multiple Holidays** ``` Request Details: - Priority: STANDARD - TAT: 40 working hours - Start: Friday 10:00 AM - Holidays: Monday (New Year), Tuesday (Day After) Calculation: Friday 10:00 AM - 6:00 PM = 8 hours (32h remaining) Saturday-Sunday = SKIPPED (weekend) Monday (HOLIDAY) = SKIPPED ✅ Tuesday (HOLIDAY) = SKIPPED ✅ Wednesday 9:00 AM - 6:00 PM = 9 hours (23h remaining) Thursday 9:00 AM - 6:00 PM = 9 hours (14h remaining) Friday 9:00 AM - 6:00 PM = 9 hours (5h remaining) Monday 9:00 AM - 2:00 PM = 5 hours (0h remaining) Final deadline: Next Monday 2:00 PM ✅ (Skipped 2 weekends + 2 holidays) ``` --- ## Holiday Cache Management ### **Cache Lifecycle:** ``` 1. Server Startup → initializeHolidaysCache() called → Holidays loaded into memory 2. Cache Valid for 6 Hours → holidaysCacheExpiry = now + 6 hours → Subsequent calls use cached data (fast) 3. Cache Expires After 6 Hours → Next TAT calculation reloads cache from DB → New cache expires in 6 hours 4. Manual Cache Refresh (Optional) → Admin adds/updates holiday → Call initializeHolidaysCache() to refresh immediately ``` ### **Cache Performance:** ``` Without Cache: - Every TAT calculation → DB query → SLOW ❌ - 100 requests/hour → 100 DB queries With Cache: - Load once per 6 hours → DB query → FAST ✅ - 100 requests/hour → 0 DB queries (use cache) - Cache refresh: Every 6 hours or on-demand ``` --- ## Priority Detection in Services ### **Workflow Service (Submission):** ```typescript // When submitting workflow const workflowPriority = (updated as any).priority || 'STANDARD'; await tatSchedulerService.scheduleTatJobs( requestId, levelId, approverId, tatHours, now, workflowPriority // ✅ Pass priority ); ``` ### **Approval Service (Next Level):** ```typescript // When moving to next approval level const workflowPriority = (wf as any)?.priority || 'STANDARD'; await tatSchedulerService.scheduleTatJobs( requestId, nextLevelId, nextApproverId, tatHours, now, workflowPriority // ✅ Pass priority ); ``` --- ## Database Schema ### **Holidays Table:** ```sql CREATE TABLE holidays ( holiday_id UUID PRIMARY KEY, holiday_date DATE NOT NULL, holiday_name VARCHAR(255) NOT NULL, holiday_type VARCHAR(50), description TEXT, is_active BOOLEAN DEFAULT true, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Example data INSERT INTO holidays (holiday_date, holiday_name, holiday_type) VALUES ('2025-12-25', 'Christmas Day', 'PUBLIC_HOLIDAY'), ('2025-01-01', 'New Year''s Day', 'PUBLIC_HOLIDAY'), ('2025-07-04', 'Independence Day', 'PUBLIC_HOLIDAY'); ``` ### **Workflow Request Priority:** ```sql -- WorkflowRequest table already has priority field SELECT request_id, priority, tat_hours FROM workflow_requests WHERE priority = 'EXPRESS'; -- 24/7 calculation -- OR WHERE priority = 'STANDARD'; -- Working hours + holiday exclusion ``` --- ## Testing Scenarios ### **Test 1: Add Holiday, Create STANDARD Request** ```bash # 1. Add holiday for tomorrow curl -X POST http://localhost:5000/api/v1/admin/holidays \ -H "Authorization: Bearer TOKEN" \ -d '{ "holidayDate": "2025-11-06", "holidayName": "Test Holiday", "holidayType": "PUBLIC_HOLIDAY" }' # 2. Create STANDARD request with 24h TAT curl -X POST http://localhost:5000/api/v1/workflows \ -d '{ "priority": "STANDARD", "tatHours": 24 }' # 3. Check scheduled TAT jobs in logs # → Should show deadline skipping the holiday ✅ ``` ### **Test 2: Same Holiday, EXPRESS Request** ```bash # 1. Holiday still exists (tomorrow) # 2. Create EXPRESS request with 24h TAT curl -X POST http://localhost:5000/api/v1/workflows \ -d '{ "priority": "EXPRESS", "tatHours": 24 }' # 3. Check scheduled TAT jobs in logs # → Should show deadline NOT skipping the holiday ✅ # → Exactly 24 hours from now (includes holiday) ``` ### **Test 3: Verify Holiday Exclusion** ```bash # Create request on Friday afternoon # With 16 working hours TAT # Should skip weekend and land on Monday/Tuesday # If Monday is a holiday: # → STANDARD: Should land on Tuesday ✅ # → EXPRESS: Should land on Sunday ✅ ``` --- ## Logging Examples ### **STANDARD Mode Log:** ``` [TAT Scheduler] Using STANDARD mode - excludes holidays, weekends, non-working hours [TAT Scheduler] Calculating TAT milestones for request REQ-123, level LEVEL-456 [TAT Scheduler] Priority: STANDARD, TAT Hours: 16 [TAT Scheduler] Start: 2025-11-05 14:00 [TAT Scheduler] Threshold 1 (55%): 2025-11-07 11:48 (skipped 1 holiday) [TAT Scheduler] Threshold 2 (80%): 2025-11-08 09:48 [TAT Scheduler] Breach (100%): 2025-11-08 14:00 ``` ### **EXPRESS Mode Log:** ``` [TAT Scheduler] Using EXPRESS mode (24/7) - no holiday/weekend exclusions [TAT Scheduler] Calculating TAT milestones for request REQ-456, level LEVEL-789 [TAT Scheduler] Priority: EXPRESS, TAT Hours: 16 [TAT Scheduler] Start: 2025-11-05 14:00 [TAT Scheduler] Threshold 1 (55%): 2025-11-05 22:48 (8.8 hours) [TAT Scheduler] Threshold 2 (80%): 2025-11-06 02:48 (12.8 hours) [TAT Scheduler] Breach (100%): 2025-11-06 06:00 (16 hours) ``` --- ## Summary ### **What Changed:** 1. ✅ Added `addCalendarHours()` for EXPRESS mode (24/7 calculation) 2. ✅ Updated `addWorkingHours()` to check holidays from admin settings 3. ✅ Added `priority` parameter to `scheduleTatJobs()` 4. ✅ Updated workflow/approval services to pass priority 5. ✅ Holiday cache for performance (6-hour expiry) ### **How Holidays Are Used:** | Priority | Calculation Method | Holidays | Weekends | Non-Working Hours | |----------|-------------------|----------|----------|-------------------| | **STANDARD** | Working hours only | ✅ Excluded | ✅ Excluded | ✅ Excluded | | **EXPRESS** | 24/7 calendar hours | ❌ Counted | ❌ Counted | ❌ Counted | ### **Benefits:** 1. ✅ **Accurate TAT for STANDARD** - Respects holidays, no false breaches 2. ✅ **Fast EXPRESS** - True 24/7 calculation for urgent requests 3. ✅ **Centralized Holiday Management** - Admin can add/edit holidays 4. ✅ **Performance** - Holiday cache prevents repeated DB queries 5. ✅ **Flexible** - Priority can be changed per request --- ## Files Modified 1. `Re_Backend/src/utils/tatTimeUtils.ts` - Added `addCalendarHours()` for EXPRESS mode 2. `Re_Backend/src/services/tatScheduler.service.ts` - Added priority parameter and logic 3. `Re_Backend/src/services/workflow.service.ts` - Pass priority when scheduling TAT 4. `Re_Backend/src/services/approval.service.ts` - Pass priority for next level TAT --- ## Configuration Keys | Config Key | Default | Description | |------------|---------|-------------| | `WORK_START_HOUR` | 9 | Working hours start (STANDARD mode only) | | `WORK_END_HOUR` | 18 | Working hours end (STANDARD mode only) | | `WORK_START_DAY` | 1 | Monday (STANDARD mode only) | | `WORK_END_DAY` | 5 | Friday (STANDARD mode only) | **Note:** EXPRESS mode ignores all these configurations and uses 24/7 calculation.