Employee monthly schedules
Monthly work schedules drive working_days per employee per month. That count feeds attendance KPIs and other scorecard logic (see Where schedules are used).
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 |
Who can edit
| Access | Edit which months |
|---|---|
| Any authenticated | View all months |
| Level 3–4 | Any month that is not dashboard-finalized |
| Level 5 | Any month, including finalized (should re-generate the final report after corrections) |
| Level 1–2 | View only |
A month locks for levels 3–4 when the BSC dashboard month report status is final. Past draft months stay editable. Reopen on the BSC dashboard unlocks schedules again. Saves and bulk actions return HTTP 409 while locked.
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.
Where schedules are used
| Consumer | Notes |
|---|---|
| Working-days API | GET .../working-days/{employee_id} — per-employee resolved count |
| BSC attendance KPIs | resolve_working_days_for_employee in bsc_employee_compute |
| Task delivery calendar | is_task_delivery_day on day codes; task_delivery/workdays.py |
| Task delivery scorecard grid | Day code abbreviations; pending/missing highlights — Fiscal cycles § Task delivery |
| Dashboard / analytics | Not in the product yet — Roadmap |
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 (409 if dashboard month is final, unless level 5) |
| 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)
