Re_Backend/DYNAMIC_WORKING_HOURS.md

563 lines
14 KiB
Markdown

# Dynamic Working Hours Configuration
## Overview
Working hours for TAT (Turn Around Time) calculations are now **dynamically configurable** through the admin settings interface. Admins can change these settings at any time, and the changes will be reflected in all future TAT calculations.
---
## What's Configurable
### **Working Hours Settings:**
| Setting | Description | Default | Example |
|---------|-------------|---------|---------|
| `WORK_START_HOUR` | Working day starts at (hour) | 9 | 8 (8:00 AM) |
| `WORK_END_HOUR` | Working day ends at (hour) | 18 | 19 (7:00 PM) |
| `WORK_START_DAY` | First working day of week | 1 (Monday) | 1 (Monday) |
| `WORK_END_DAY` | Last working day of week | 5 (Friday) | 6 (Saturday) |
**Days:** 0 = Sunday, 1 = Monday, 2 = Tuesday, ..., 6 = Saturday
---
## How It Works
### **1. Admin Changes Working Hours**
```
Settings → System Configuration → Working Hours
- Work Start Hour: 9:00 → Change to 8:00
- Work End Hour: 18:00 → Change to 20:00
✅ Save
```
### **2. Backend Updates Database**
```sql
UPDATE admin_configurations
SET config_value = '8'
WHERE config_key = 'WORK_START_HOUR';
UPDATE admin_configurations
SET config_value = '20'
WHERE config_key = 'WORK_END_HOUR';
```
### **3. Cache is Cleared Automatically**
```typescript
// In admin.controller.ts
clearConfigCache(); // Clear general config cache
clearWorkingHoursCache(); // Clear TAT working hours cache
```
### **4. Next TAT Calculation Uses New Values**
```typescript
// TAT calculation loads fresh values
await loadWorkingHoursCache();
// → Reads: startHour=8, endHour=20 from database
// Applies new working hours
if (hour >= 8 && hour < 20) {
// This hour counts as working time ✅
}
```
---
## Cache Management
### **Working Hours Cache:**
**Cache Duration:** 5 minutes (shorter than holidays since it's more critical)
**Why Cache?**
- Performance: Avoids repeated database queries
- Speed: TAT calculations can happen hundreds of times per hour
- Efficiency: Reading from memory is ~1000x faster than DB query
**Cache Lifecycle:**
```
1. First TAT Calculation:
→ loadWorkingHoursCache() called
→ Database query: SELECT config_value WHERE config_key IN (...)
→ Store in memory: workingHoursCache = { startHour: 9, endHour: 18, ... }
→ Set expiry: now + 5 minutes
2. Next 5 Minutes (Cache Valid):
→ All TAT calculations use cached values
→ No database queries ✅ FAST
3. After 5 Minutes (Cache Expired):
→ Next TAT calculation reloads from database
→ New cache created with 5-minute expiry
4. Admin Updates Config:
→ clearWorkingHoursCache() called immediately
→ Cache invalidated
→ Next calculation loads fresh values ✅
```
---
## Example Scenarios
### **Scenario 1: Extend Working Hours**
**Before:**
```
Working Hours: 9:00 AM - 6:00 PM (9 hours/day)
```
**Admin Changes To:**
```
Working Hours: 8:00 AM - 8:00 PM (12 hours/day)
```
**Impact on TAT:**
```
Request: STANDARD Priority, 24 working hours
Created: Monday 9:00 AM
OLD Calculation (9 hours/day):
Monday 9 AM - 6 PM = 9 hours (15h remaining)
Tuesday 9 AM - 6 PM = 9 hours (6h remaining)
Wednesday 9 AM - 3 PM = 6 hours (0h remaining)
Deadline: Wednesday 3:00 PM
NEW Calculation (12 hours/day):
Monday 9 AM - 8 PM = 11 hours (13h remaining)
Tuesday 8 AM - 8 PM = 12 hours (1h remaining)
Wednesday 8 AM - 9 AM = 1 hour (0h remaining)
Deadline: Wednesday 9:00 AM ✅ FASTER!
```
---
### **Scenario 2: Include Saturday as Working Day**
**Before:**
```
Working Days: Monday - Friday (1-5)
```
**Admin Changes To:**
```
Working Days: Monday - Saturday (1-6)
```
**Impact on TAT:**
```
Request: STANDARD Priority, 16 working hours
Created: Friday 2:00 PM
OLD Calculation (Mon-Fri only):
Friday 2 PM - 6 PM = 4 hours (12h remaining)
Saturday-Sunday = SKIPPED
Monday 9 AM - 6 PM = 9 hours (3h remaining)
Tuesday 9 AM - 12 PM = 3 hours (0h remaining)
Deadline: Tuesday 12:00 PM
NEW Calculation (Mon-Sat):
Friday 2 PM - 6 PM = 4 hours (12h remaining)
Saturday 9 AM - 6 PM = 9 hours (3h remaining) ✅ Saturday counts!
Sunday = SKIPPED
Monday 9 AM - 12 PM = 3 hours (0h remaining)
Deadline: Monday 12:00 PM ✅ EARLIER!
```
---
### **Scenario 3: Reduce Working Hours (After-Hours Emergency)**
**Before:**
```
Working Hours: 9:00 AM - 6:00 PM
```
**Admin Changes To:**
```
Working Hours: 9:00 AM - 10:00 PM (extended for emergency)
```
**Impact:**
```
Request created at 7:00 PM (after old hours but within new hours)
OLD System:
7:00 PM → Not working time
First working hour: Tomorrow 9:00 AM
TAT starts counting from tomorrow ❌
NEW System:
7:00 PM → Still working time! ✅
TAT starts counting immediately
Faster response for urgent requests ✅
```
---
## Implementation Details
### **Configuration Reader Service**
```typescript
// Re_Backend/src/services/configReader.service.ts
export async function getWorkingHours(): Promise<{ startHour: number; endHour: number }> {
const startHour = await getConfigNumber('WORK_START_HOUR', 9);
const endHour = await getConfigNumber('WORK_END_HOUR', 18);
return { startHour, endHour };
}
```
### **TAT Time Utils (Working Hours Cache)**
```typescript
// Re_Backend/src/utils/tatTimeUtils.ts
let workingHoursCache: WorkingHoursConfig | null = null;
let workingHoursCacheExpiry: Date | null = null;
async function loadWorkingHoursCache(): Promise<void> {
// Check if cache is still valid
if (workingHoursCacheExpiry && new Date() < workingHoursCacheExpiry) {
return; // Use cached values
}
// Load from database
const { getWorkingHours, getConfigNumber } = await import('../services/configReader.service');
const hours = await getWorkingHours();
const startDay = await getConfigNumber('WORK_START_DAY', 1);
const endDay = await getConfigNumber('WORK_END_DAY', 5);
// Store in cache
workingHoursCache = {
startHour: hours.startHour,
endHour: hours.endHour,
startDay: startDay,
endDay: endDay
};
// Set 5-minute expiry
workingHoursCacheExpiry = dayjs().add(5, 'minute').toDate();
console.log(`[TAT Utils] Loaded working hours: ${hours.startHour}:00-${hours.endHour}:00`);
}
function isWorkingTime(date: Dayjs): boolean {
// Use cached working hours (with fallback to defaults)
const config = workingHoursCache || {
startHour: 9,
endHour: 18,
startDay: 1,
endDay: 5
};
const day = date.day();
const hour = date.hour();
// Check based on configured values
if (day < config.startDay || day > config.endDay) return false;
if (hour < config.startHour || hour >= config.endHour) return false;
if (isHoliday(date)) return false;
return true;
}
```
### **Admin Controller (Cache Invalidation)**
```typescript
// Re_Backend/src/controllers/admin.controller.ts
export const updateConfiguration = async (req: Request, res: Response): Promise<void> => {
// ... update database ...
// Clear config cache
clearConfigCache();
// If working hours config was updated, also clear TAT cache
const workingHoursKeys = ['WORK_START_HOUR', 'WORK_END_HOUR', 'WORK_START_DAY', 'WORK_END_DAY'];
if (workingHoursKeys.includes(configKey)) {
clearWorkingHoursCache(); // ✅ Immediate cache clear
logger.info(`Working hours config '${configKey}' updated - cache cleared`);
}
res.json({ success: true });
};
```
---
## Priority Behavior
### **STANDARD Priority**
**Uses configured working hours**
- Respects `WORK_START_HOUR` and `WORK_END_HOUR`
- Respects `WORK_START_DAY` and `WORK_END_DAY`
- Excludes holidays
**Example:**
```
Config: 9:00 AM - 6:00 PM, Monday-Friday
TAT: 16 working hours
→ Only hours between 9 AM - 6 PM on Mon-Fri count
→ Weekends and holidays are skipped
```
### **EXPRESS Priority**
**Ignores working hours configuration**
- Counts ALL 24 hours per day
- Counts ALL 7 days per week
- Counts holidays
**Example:**
```
Config: 9:00 AM - 6:00 PM (ignored)
TAT: 16 hours
→ Simply add 16 hours to start time
→ No exclusions
```
---
## Testing Scenarios
### **Test 1: Change Working Hours, Create Request**
```bash
# 1. Check current working hours
curl http://localhost:5000/api/v1/admin/configurations \
| grep WORK_START_HOUR
# → Returns: "configValue": "9"
# 2. Update working hours to start at 8:00 AM
curl -X PUT http://localhost:5000/api/v1/admin/configurations/WORK_START_HOUR \
-H "Authorization: Bearer TOKEN" \
-d '{"configValue": "8"}'
# → Response: "Configuration updated successfully"
# 3. Check logs
# → Should see: "Working hours configuration 'WORK_START_HOUR' updated - cache cleared"
# 4. Create new STANDARD request
curl -X POST http://localhost:5000/api/v1/workflows \
-d '{"priority": "STANDARD", "tatHours": 16}'
# 5. Check TAT calculation logs
# → Should see: "Loaded working hours: 8:00-18:00" ✅
# → Deadline calculation uses new hours ✅
```
### **Test 2: Verify Cache Expiry**
```bash
# 1. Create request (loads working hours into cache)
# → Cache expires in 5 minutes
# 2. Wait 6 minutes
# 3. Create another request
# → Should see log: "Loaded working hours: ..." (cache reloaded)
# 4. Create third request immediately
# → No log (uses cached values)
```
### **Test 3: Extend to 6-Day Week**
```bash
# 1. Update end day to Saturday
curl -X PUT http://localhost:5000/api/v1/admin/configurations/WORK_END_DAY \
-d '{"configValue": "6"}'
# 2. Create request on Friday afternoon
# → Deadline should include Saturday ✅
# → Sunday still excluded ✅
```
---
## Database Configuration
### **Configuration Keys:**
```sql
SELECT config_key, config_value, display_name
FROM admin_configurations
WHERE config_key IN (
'WORK_START_HOUR',
'WORK_END_HOUR',
'WORK_START_DAY',
'WORK_END_DAY'
);
-- Example results:
-- WORK_START_HOUR | 9 | Work Start Hour
-- WORK_END_HOUR | 18 | Work End Hour
-- WORK_START_DAY | 1 | Work Start Day (Monday)
-- WORK_END_DAY | 5 | Work End Day (Friday)
```
### **Update Example:**
```sql
-- Change working hours to 8 AM - 8 PM
UPDATE admin_configurations
SET config_value = '8', updated_at = NOW()
WHERE config_key = 'WORK_START_HOUR';
UPDATE admin_configurations
SET config_value = '20', updated_at = NOW()
WHERE config_key = 'WORK_END_HOUR';
-- Include Saturday as working day
UPDATE admin_configurations
SET config_value = '6', updated_at = NOW()
WHERE config_key = 'WORK_END_DAY';
```
---
## Logging Examples
### **Configuration Update:**
```
[Admin] Working hours configuration 'WORK_START_HOUR' updated - cache cleared
[ConfigReader] Configuration cache cleared
[TAT Utils] Working hours cache cleared
```
### **TAT Calculation:**
```
[TAT Utils] Loaded working hours: 8:00-20:00, Days: 1-6
[TAT Scheduler] Using STANDARD mode - excludes holidays, weekends, non-working hours
[TAT Scheduler] Calculating TAT milestones for request REQ-2025-001
[TAT Scheduler] Priority: STANDARD, TAT Hours: 16
[TAT Scheduler] Start: 2025-11-05 09:00
[TAT Scheduler] Threshold 1 (55%): 2025-11-05 17:48 (using 8-20 working hours)
[TAT Scheduler] Threshold 2 (80%): 2025-11-06 10:48
[TAT Scheduler] Breach (100%): 2025-11-06 15:00
```
---
## Migration from Hardcoded Values
### **Before (Hardcoded):**
```typescript
// ❌ Hardcoded in code
const WORK_START_HOUR = 9;
const WORK_END_HOUR = 18;
const WORK_START_DAY = 1;
const WORK_END_DAY = 5;
// To change: Need code update + deployment
```
### **After (Dynamic):**
```typescript
// ✅ Read from database
const config = await getWorkingHours();
// config = { startHour: 9, endHour: 18 }
// To change: Just update in admin UI
// No code changes needed ✅
// No deployment needed ✅
```
---
## Benefits
### **1. Flexibility**
- ✅ Change working hours anytime without code changes
- ✅ No deployment needed
- ✅ Takes effect within 5 minutes
### **2. Global Organizations**
- ✅ Adjust for different time zones
- ✅ Support 24/5 or 24/6 operations
- ✅ Extended hours for urgent periods
### **3. Seasonal Adjustments**
- ✅ Extend hours during busy seasons
- ✅ Reduce hours during slow periods
- ✅ Special hours for events
### **4. Performance**
- ✅ Cache prevents repeated DB queries
- ✅ Fast lookups (memory vs database)
- ✅ Auto-refresh every 5 minutes
### **5. Consistency**
- ✅ All TAT calculations use same values
- ✅ Immediate cache invalidation on update
- ✅ Fallback to defaults if DB unavailable
---
## Summary
| Aspect | Details |
|--------|---------|
| **Configurable** | ✅ Working hours, working days |
| **Admin UI** | ✅ Settings → System Configuration |
| **Cache Duration** | 5 minutes |
| **Cache Invalidation** | Automatic on config update |
| **Applies To** | STANDARD priority only |
| **Express Mode** | Ignores working hours (24/7) |
| **Performance** | Optimized with caching |
| **Fallback** | Uses TAT_CONFIG defaults if DB fails |
---
## Files Modified
1. `Re_Backend/src/utils/tatTimeUtils.ts` - Dynamic working hours loading
2. `Re_Backend/src/controllers/admin.controller.ts` - Cache invalidation on update
3. `Re_Backend/src/services/configReader.service.ts` - `getWorkingHours()` function
---
## Configuration Flow Diagram
```
Admin Updates Working Hours (8:00 AM - 8:00 PM)
Database Updated (admin_configurations table)
clearConfigCache() + clearWorkingHoursCache()
Caches Invalidated (both config and working hours)
Next TAT Calculation
loadWorkingHoursCache() called
Read from Database (startHour=8, endHour=20)
Store in Memory (5-minute cache)
TAT Calculation Uses New Hours ✅
All Future Requests (for 5 min) Use Cached Values
After 5 Minutes → Reload from Database
```
---
Working hours are now fully dynamic and admin-controlled! 🎉