Employee monthly schedules
Status: Implemented (API + UI).
Last updated: July 11, 2026
Monthly work schedules drive working_days per employee per month. That count feeds attendance KPIs and other scorecard logic once wired (see Next integrations).
Collections
| Collection | Purpose |
|---|---|
tbl_scorecard_config | Day codes document _id: schedule-day-codes (field codes[]) |
tbl_employee_schedules | One document per employee per month |
Schedule document id: {employee_id}:{YYYY}-{MM}
Fields:
| Field | Description |
|---|---|
employee_id, year, month | Identity |
working_days | Cached count of days whose code_id maps to is_working_day: true |
days[] | Per calendar day: day, code_id, time_ranges[] |
Legacy documents may still store code (abbreviation) on a day; the API migrates to code_id on read when possible.
Day codes (catalog)
Each catalog row has:
| Field | Description |
|---|---|
id | Stable ID (employee cells reference this, not the abbreviation) |
code | Short label on the grid (e.g. WD, OFF) |
description | Long label |
is_working_day | When true, the day counts toward working_days (attendance KPI) |
is_task_delivery_day | When true, the day is a task-delivery workday (BSC quantity KPI + scorecard grid). Defaults to is_working_day when omitted on legacy rows. VL and SL default to is_task_delivery_day: true but is_working_day: false. |
sort_order | Config UI ordering |
Empty cells (no code_id) are non-working and have no times.
Configured at Scorecard → Configurations → Schedule Day Codes.
Time ranges
Optional shift times per day (time_ranges[]):
| Field | Description |
|---|---|
start_minutes | Minutes from midnight (0 = 12:00 AM) |
end_minutes | Exclusive end (e.g. 5:00 AM → 300) |
- Up to 8 non-overlapping ranges per day.
- Same-day shift:
end_minutes > start_minutes(e.g. 8:00 AM–5:00 PM). - Overnight shift:
end_minutes <= start_minutes— end is the next morning, exclusive (e.g. 8:00 PM–5:00 AM →start_minutes=1200,end_minutes=300). Legacy data may still use two segments (0–300and1200–1440); the UI merges those for display.
Default weekday template
Used for auto-fill and Apply weekday template:
| Day | Code | Times |
|---|---|---|
| Mon–Fri | WD | 8:00 PM – 5:00 AM (single overnight range) |
| Sat–Sun | OFF | (none) |
Defined in api/app/services/schedule_time_ranges.py (default_weekday_time_ranges()).
Employees in the grid
- Active employees (
status: true), regardless ofbsc_status. - Grid includes employee name, department, cluster (primary department), and WD (working-day count).
- Default scope: departments of the signed-in user.
- Level 3+:
scope=allordepartment_ids(comma-separated) to widen the list.
Auto-fill
When loading the month grid for the current month or next month (one month ahead):
- Any active employee without a saved schedule gets the weekday template above.
- Existing saved schedules are never overwritten by auto-fill.
Manual bulk actions (level 3+, audit-logged):
| Action | Behavior |
|---|---|
| Copy from last month | Copies prior month day-by-day; weekday template if prior month missing |
| Apply weekday template | Overwrites the whole month for filtered employees |
Month locking
| Access | Edit which months |
|---|---|
| Any authenticated | View all months |
| Level 3–4 | Current month and future only |
| Level 5 | Any month (including past) |
Past months return month_locked: true for levels 3–4; saves and bulk actions return HTTP 403.
Working days for attendance
text
resolve_working_days_for_employee(db, employee_id, year, month) → int- Saved schedule in
tbl_employee_schedules→working_dayson that document (recomputed on save from day codes). - Else → Mon–Fri count in the calendar month (
weekdays_in_calendar_month).
HTTP: GET /v1/scorecard/employee-schedules/working-days/{employee_id}?year=&month=
Attendance KPI formula (when working_days is used):
text
(working_days − violation_count) / working_days × 100See also BSC metrics.
Next integrations
| Consumer | Status |
|---|---|
GET .../working-days/{employee_id} | Done — per-employee resolved count |
| BSC attendance KPI calculation | Done — resolve_working_days_for_employee in bsc_employee_compute |
| Task delivery workdays | Done — is_task_delivery_day on day codes; task_delivery/workdays.py resolves TD calendar per employee-month |
| Task delivery scorecard grid | Done — day code abbreviations on off/non-WD cells; pending/missing highlights — see Fiscal cycles § Task delivery |
| Dashboard / analytics | Planned (Phase 2) |
API
| Method | Path | Who |
|---|---|---|
| GET | /v1/scorecard/schedule-day-codes | Authenticated |
| PUT | /v1/scorecard/schedule-day-codes | Level ≥ 3 |
| GET | /v1/scorecard/employee-schedules/month?year=&month=&search=&scope=&department_ids= | Authenticated (auto-fill on load; default scope=my) |
| PUT | /v1/scorecard/employee-schedules/month | Level ≥ 3 (respects month lock) |
| POST | /v1/scorecard/employee-schedules/month/copy-from-previous | Level ≥ 3 |
| POST | /v1/scorecard/employee-schedules/month/apply-template | Level ≥ 3 |
| GET | /v1/scorecard/employee-schedules/{employee_id}/{year}/{month} | Authenticated |
| GET | /v1/scorecard/employee-schedules/working-days/{employee_id}?year=&month= | Authenticated |
Audit logging
Creates/updates write to tbl_logs with resources employee_schedule and schedule_day_codes (same pattern as SBS/ATW entries).
UI
| Route | Description |
|---|---|
/organization/schedules | Monthly grid: frozen identity columns, scrollable days, filters, bulk actions, save month |
/organization | Chart + list (schedules are a separate nav link) — see Organization |
| Scorecard → Configurations → Schedule Day Codes | Code catalog |
Grid behavior (summary):
- Sort default: employee → department → cluster; column headers toggle primary sort.
- Today column: US Eastern (
America/New_York) date; header highlight only. - Day headers:
May 27+ weekday line; weekend headers use opaque styling. - Status line:
Showing N employees · May 2026 · {active filter summary}.
Key frontend files:
web/src/pages/OrganizationSchedulesPage.tsxweb/src/pages/organization_components/EmployeeSchedulesPanel.tsxweb/src/pages/organization_components/scheduleFilterUtils.tsweb/src/pages/organization_components/scheduleTimeUtils.ts
Key backend files:
api/app/services/employee_schedules.pyapi/app/services/schedule_day_codes.pyapi/app/services/schedule_time_ranges.pyapi/app/services/employee_schedule_policy.py
Not in v1
- CSV import/export for schedules
- Schedule data on Organization chart/list pages (schedules are only on
/organization/schedules) - Pagination (acceptable for <200 employees per filter today)
- Auto-migrating legacy two-segment overnight rows to one range on save (optional future)
