Dynamic Call Window Developer Guide
Overview
The Dynamic Call Window feature allows administrators to configure per-user calling time restrictions. This extends the existing TRAI-mandated 9 AM - 9 PM IST calling window with customizable windows for specific users.
Feature Summary
| Scenario | Behavior |
|---|---|
User with no custom config and no off_hours_campaign feature | Restricted to 9 AM - 9 PM IST (default TRAI window) |
User with off_hours_campaign feature enabled | Allowed to call 24/7 (legacy behavior) |
| User with custom call window enabled | Allowed to call only within their configured window (e.g., 6 AM - 10 PM IST) |
Priority order:
- Custom call window (if enabled) → takes precedence
off_hours_campaignfeature → allows 24/7- Default → 9 AM - 9 PM IST restriction
All Call Types Covered
The off-hours validation applies to all call types:
| Call Type | Where Validated | Function Used |
|---|---|---|
| Campaign calls | calling.service.ts → fillCallSlots() | isCallingAllowedForCampaign() |
| CRM API calls | crmApi.service.ts → triggerCall() | isCallingAllowedForUser() |
| Human calls (browser-to-phone) | crm.controller.ts → initiateHumanCallFromCrm() | isCallingAllowedForUser() |
| Phone-to-phone calls | crm.controller.ts → initiatePhoneToPhoneCallFromCrm() | isCallingAllowedForUser() |
Data Model
User Schema Update
Added callWindowConfig field to the User model:
interface ICallWindowConfig {
enabled: boolean; // Whether custom window is active
startTimeIST: string; // Start time in "HH:MM" format (24-hour, IST)
endTimeIST: string; // End time in "HH:MM" format (24-hour, IST)
}
// Default values
const DEFAULT_CALL_WINDOW_CONFIG: ICallWindowConfig = {
enabled: false,
startTimeIST: "09:00",
endTimeIST: "21:00",
};MongoDB Schema (models/User.ts):
callWindowConfig: {
type: {
enabled: { type: Boolean, default: false },
startTimeIST: {
type: String,
default: "09:00",
validate: {
validator: (value) => /^([01]\d|2[0-3]):([0-5]\d)$/.test(value),
message: "startTimeIST must be in HH:MM format (24-hour)",
},
},
endTimeIST: {
type: String,
default: "21:00",
validate: {
validator: (value) => /^([01]\d|2[0-3]):([0-5]\d)$/.test(value),
message: "endTimeIST must be in HH:MM format (24-hour)",
},
},
},
default: () => ({ ...DEFAULT_CALL_WINDOW_CONFIG }),
}Overnight Window Support
The system supports overnight windows (e.g., 10 PM - 6 AM).
Example configurations:
| Config | Meaning |
|---|---|
startTimeIST: "06:00", endTimeIST: "21:00" | 6 AM to 9 PM (same day) |
startTimeIST: "22:00", endTimeIST: "06:00" | 10 PM to 6 AM (overnight) |
startTimeIST: "00:00", endTimeIST: "23:59" | Effectively 24 hours |
Logic (in isTimeInWindow()):
// If start > end, it's an overnight window
if (startMinutes > endMinutes) {
// Allowed if: current >= start OR current < end
return currentMinutes >= startMinutes || currentMinutes < endMinutes;
} else {
// Normal daytime window: current >= start AND current < end
return currentMinutes >= startMinutes && currentMinutes < endMinutes;
}API Reference
Update Customer Call Window
Endpoint: PUT /api/admin/customers/:customerId
Auth: Admin JWT
Body:
{
"callWindowConfig": {
"enabled": true,
"startTimeIST": "06:00",
"endTimeIST": "22:00"
}
}Validation Rules:
enabledmust be a boolean- When
enabled: true, bothstartTimeISTandendTimeISTare required - Times must be in
HH:MMformat (24-hour) - Start and end times cannot be the same
- To reset to default, send
"callWindowConfig": null
Response (200):
{
"success": true,
"message": "Customer updated successfully",
"data": { /* customer object with updated callWindowConfig */ }
}Error Responses:
| Code | When |
|---|---|
| 400 | Invalid time format or missing required fields |
| 404 | Customer not found |
| 403 | Customer belongs to different org |
Service Functions
isCallingAllowedForUser(userUID, orgUID?)
Used for CRM API calls, human calls, and phone-to-phone calls.
Location: services/shared.service.ts
Parameters:
userUID: Customer/user UIDorgUID: Optional org UID (will be looked up from user if not provided)
Returns:
{
allowed: boolean;
reason: string;
windowStatus: {
isWithinDefaultWindow: boolean;
isWithinCustomWindow: boolean;
customWindowEnabled: boolean;
customWindowStart: string | null;
customWindowEnd: string | null;
currentTimeIST: string;
defaultWindowStart: string;
defaultWindowEnd: string;
};
}Example usage:
const check = await isCallingAllowedForUser(customerUID, orgUID);
if (!check.allowed) {
return res.status(403).json({
success: false,
code: "OUTSIDE_CALLING_WINDOW",
message: check.reason,
});
}isCallingAllowedForCampaign(campaignUID)
Used for campaign calls (via fillCallSlots()).
Location: services/shared.service.ts
Returns: Promise<boolean>
Check order:
- Within standard 9 AM - 9 PM IST? → allowed
- User has custom window enabled and within window? → allowed
- User has
off_hours_campaignfeature? → allowed - Otherwise → blocked
getCallingWindowStatusForUser(callWindowConfig?)
Returns detailed window status for UI display.
Location: services/shared.service.ts
Returns:
{
isWithinDefaultWindow: boolean;
isWithinCustomWindow: boolean;
customWindowEnabled: boolean;
customWindowStart: string | null;
customWindowEnd: string | null;
currentTimeIST: string; // e.g., "14:35"
defaultWindowStart: string; // "09:00"
defaultWindowEnd: string; // "21:00"
}shouldAutoStopCampaign(orgUID, userUID)
Determines if a campaign should be auto-stopped at 9 PM IST.
Location: services/shared.service.ts
Returns: Promise<boolean>
true→ auto-stop the campaignfalse→ keep running (user has extended window or off-hours feature)
Used by: scheduler.service.ts → TRAI auto-pause cron job
Frontend Integration
Customer Management Page
Location: app/src/pages/admin/CustomerManagementPage.tsx
Changes:
- Added "Call Window" column to customer table
- Added call window configuration UI in edit dialog
- Validates times when enabling custom window
UI Display in Table:
| User State | Display |
|---|---|
| Custom window enabled | 06:00-22:00 IST |
off_hours_campaign feature | 24/7 (green) |
| Default | 9:00-21:00 IST (muted) |
Backward Compatibility
Existing Users
No migration required. Existing users without callWindowConfig will:
- Have
undefinedcallWindowConfig (or default when accessed) - Follow existing behavior:
- With
off_hours_campaignfeature → 24/7 access - Without → 9 AM - 9 PM IST restriction
- With
The code safely handles missing callWindowConfig:
const userCallWindowConfig = (user as any).callWindowConfig as ICallWindowConfig | undefined;
if (userCallWindowConfig?.enabled) {
// Custom window logic
}Feature Flag
The off_hours_campaign feature in the org's plan is still required to:
- Enable the custom call window UI in customer management
- Allow the admin to configure custom windows for customers
Error Messages
| Code | Message | When |
|---|---|---|
OUTSIDE_CALLING_WINDOW | "Outside calling window (9AM-9PM IST) and no off-hours access configured" | Default restriction |
OUTSIDE_CALLING_WINDOW | "Outside custom calling window (HH:MM-HH:MM IST)" | Custom window violation |
INVALID_CALL_WINDOW_CONFIG | "callWindowConfig.enabled must be a boolean" | Invalid enabled field |
INVALID_CALL_WINDOW_CONFIG | "When enabling custom call window, both startTimeIST and endTimeIST are required" | Missing times |
INVALID_CALL_WINDOW_CONFIG | "startTimeIST must be in HH:MM format (24-hour, e.g., '06:00')" | Invalid format |
INVALID_CALL_WINDOW_CONFIG | "startTimeIST and endTimeIST cannot be the same" | Same times |
Files Changed
Backend
| File | Changes |
|---|---|
types/index.ts | Added ICallWindowConfig, DEFAULT_CALL_WINDOW_CONFIG |
models/User.ts | Added callWindowConfig field with validation |
services/shared.service.ts | Added getISTTime(), parseTimeToMinutes(), isTimeInWindow(), isWithinCustomCallWindow(), getCallingWindowStatusForUser(), isCallingAllowedForUser(); Updated isCallingAllowedForCampaign(), shouldAutoStopCampaign() |
services/crmApi.service.ts | Added off-hours check in triggerCall() |
controllers/admin.controller.ts | Added callWindowConfig validation in updateCustomer() |
controllers/crm.controller.ts | Added off-hours checks in initiateHumanCallFromCrm(), initiatePhoneToPhoneCallFromCrm() |
Frontend
| File | Changes |
|---|---|
types/auth.ts | Added CallWindowConfig interface, DEFAULT_CALL_WINDOW_CONFIG |
pages/admin/CustomerManagementPage.tsx | Added call window column and edit UI |
Testing
Manual Testing Checklist
Default restriction test:
- Create user without
off_hours_campaignfeature - Try API call outside 9 AM - 9 PM IST
- Expected: 403 with "Outside calling window" message
- Create user without
Legacy feature test:
- Enable
off_hours_campaignfeature for user - Try API call at any time
- Expected: Call proceeds
- Enable
Custom window test:
- Set
callWindowConfig: { enabled: true, startTimeIST: "06:00", endTimeIST: "22:00" } - Try API call at 7 AM IST
- Expected: Call proceeds
- Set
Outside custom window test:
- Same config as above
- Try API call at 5 AM IST
- Expected: 403 with "Outside custom calling window (06:00-22:00 IST)"
Overnight window test:
- Set
callWindowConfig: { enabled: true, startTimeIST: "22:00", endTimeIST: "06:00" } - Try API call at 11 PM IST
- Expected: Call proceeds
- Try API call at 10 AM IST
- Expected: 403 blocked
- Set
Campaign auto-stop with custom window:
- User with custom window ending at 22:00
- Start campaign at 8 PM
- At 9 PM: campaign should NOT auto-stop (custom window extends to 10 PM)
- At 10 PM: campaign should auto-stop