Staff — Concepts & architecture
The Staff domain is mostly frontend display (Tuke/Topi) over people data, shift schedules, and leave. Below: core concepts and data rules.
Concepts
| Concept | Meaning |
|---|---|
| Employee | Staff record in one BU. One person may have multiple Employee records (cross-BU). |
| enterpriseUserId | Cross-BU person identity — used to dedupe (merge) the Employees of the same person. |
| Roster / Shift | Work schedule; each shift has a time interval. |
| Open Shift | A published shift with no employee assigned (EmployeeId = null) so staff can claim it. |
| Leave | A leave day with required Date, TimeStart, TimeEnd and a request→approve status lifecycle. |
| TrackingCode | Classification code; TypeId = Leave or Shift. Leave forms only show the Leave type. |
| Availability | The window staff can be scheduled to work; repeats per weekday within a date range. |
| Shifts Leave | Leave attached to an active published shift via ShiftId. |
| Open Leave | Leave with no ShiftId; not the same as Open Shift. |
| Timetable | Display timetable (Syncfusion ScheduleComponent). |
| Staff Medical | Medical field groups of the staff profile (Dietary / Neurotypes / Additional Information); show/hide and required per BU customization. |
Dedupe rule in Weekly Planner
One person may only have one row in the planner, even with multiple records:
- Person with both selected-BU and cross-BU records → keep the selected BU's
employeeIdas the canonical resource id. - Person missing both identities → still a separate row.
- Sort applies after role-filter + merge. Does not include archived staff (already excluded by the employee-link contract).
Invariant:
INV-ROSTER-WP-001 v3.
Staff Roster on the Admin Portal
Weekly Planner belongs to Admin Portal → Staff → Roster → Weekly Planner. The planner runs on the Tuke route (standalone or embedded in the admin host app_type=aoadmin).
- When embedded in the admin host, navigation back to the parent surface uses the existing
postMessagechannel. - The migration to Tuke keeps ordering, cross-BU dedupe, approved-only absence, and
On Leave.
Rule source: Staff Roster – Weekly Planner move to Tuke.
Availability and two ways to create Leave
Availability describes when staff can work, not the scheduled shift or leave time:
- A range has start/end dates and time slots per weekday.
- The leave policy only reads active available slots; it does not modify or link Leave with Availability.
Shifts: select one or more active published shifts of the right staff, BU and date.Open: enter time without attaching a shift; no published shift may be assigned the same day.- Two ranges that only touch at a boundary are not counted as overlapping.
Real-world example: Nam has a published
09:00–17:00shift and requests leave13:00–15:00. → Leave attaches to the scheduled shift. → Select Shifts; the system savesShiftId, leave interval within the shift.
Real-world example: Lan has no Wednesday shift and requests leave
13:00–15:00. → Leave is not attached to a shift. → Select Open; the system checks restriction and Availability before saving.
Leave Request Restrictions
Each Business Unit picks a policy in Timesheet settings:
| Setting | Staff create Open | Admin create Open |
|---|---|---|
Published Shifts Only | Not allowed | Not allowed |
Shifts & Availability | Must fit fully within an active availability slot | Outside availability gets a warning; can confirm |
No Restrictions | Availability does not block | Outside availability gets a warning; can confirm |
- A missing or null setting reads as
No Restrictions. Published Shifts Onlyonly renders the Shifts content; the API still blocks Open if the UI is bypassed.- Staff cannot use admin confirmation to bypass the policy.
- Create and edit both re-run the policy by actor and BU from the authenticated context.
- No record is written while a warning is not yet confirmed by admin.
Real-world example: An is available
09:00–17:00and requests Open Leave16:00–18:00underShifts & Availability. → The leave interval exceeds the slot. → Staff is blocked; admin confirms the warning.
Leave create and save rules
- Each
EmployeeAbsencerepresents exactly oneDate;TimeStart < TimeEndwithin the same day. - Each record carries its own
BusinessUnitId, set from the Employee at creation; it is the scope basis and cannot touch leaves of another BU. - The form has no
StartDate,EndDateorAll Day. - Shifts Leave must fit fully within the shift; the user can shorten the Leave Time.
- The Shifts filter defaults from the BU's current date to the next Friday.
- Staff only filters current/future date; admin can filter past/current/future date.
- Open Leave cannot overlap an active published shift; if it does, use Shifts.
- Preview shows each record, the total record count, total duration, and warnings.
- Save sends one atomic batch; one failing candidate means the whole batch is not written.
- An active leave slot duplicating the employee, date and time is blocked with
SLOT_DUPLICATE. - Edit only updates the selected record and re-runs all validation.
Real-world example: Admin selects three published shifts then Review & Save. → The second shift is unpublished. → None of the three candidates is written.
Invariant:
INV-EMPLOYEE-ABSENCE-001/003/004/006/008/010 v1,INV-LEAVE-RESTRICTION-002..005 v1.
Leave overlap display rule
When creating/editing a shift, the popup shows the Leave field (read-only) if leave overlaps the shift interval:
- Evaluated by date-time interval intersection, not same-date comparison.
- The field includes leave date/time + tracking code; hidden when there is no overlap.
Invariant:
INV-ROSTER-LEAVE-DISPLAY-001 v1. Applies to the Weekly Planner shift popup, Timesheet rows, and Timesheet exports.
Open Shift rule
Open Shift is an EmployeeShift that is published but has no employee assigned:
EmployeeId = nullandIsPublished = true→ Open Shift.EmployeeId = nullbut not yet published → an admin draft; staff cannot see it to claim.- A shift that already has
EmployeeId→ no longer an Open Shift. - A claim only succeeds if the shift is still unassigned at the moment of the claim.
Real-world example: Linh and Mai both claim the
07:00–09:00shift. → Avoid one shift being claimed by two people. → One claim succeeds; the other gets a conflict and the list refreshes.
Invariant:
INV-OPENSHIFT-001 v1,INV-OPENSHIFT-002 v1.
Tracking Code rule
- The admin form create/edit/approve only shows codes with
TrackingCode.TypeId = Leave. - Shift type codes are excluded from the leave selector.
- The staff form only shows Reason; it does not show or write Tracking Code and Notes.
- Notes only show for authorized admin; staff do not see Notes in leave detail.
Staff Medical customization rule
Each Business Unit configures the medical field groups of staff profiles:
| Field group | Customization keys | Meaning |
|---|---|---|
| Dietary | StaffDietary_Enabled / StaffDietary_Required | Diet |
| Neurotypes/Other Conditions | StaffLearningCondition_Enabled / StaffLearningCondition_Required | Neuro characteristics / other conditions |
| Additional Information | StaffAdditionalInformation_Enabled / StaffAdditionalInformation_Required | Additional information |
- Each group has an
Enabledtoggle (On/Off) and aRequired(Optional/Mandatory). - Default for every group is disabled + not required for every provider, unless the BU configures otherwise.
- When one/many customization rows are missing at resolve → each missing entry behaves as
false. - The form only renders a group when
Enabled = true; mandatory validation only applies when the group is enabled andRequired = true. - A disabled group with
Required = true→ still hidden and does not block submit. - A disabled group with saved data → not deleted; when re-enabled the data is available again.
Real-world example: Admin disables the Additional Information group after staff already entered notes. → Do not lose declared data when hiding a group. → Re-enable the group later; the old notes show as before.
Invariant:
INV-SMC-001,INV-SMC-002,INV-SMC-003 v1.
Archive employees and login status
Archiving (soft delete) a staff member disables login in the same operation:
- Sets
StatusId = ArchivedandLoginStatusId = Initialised(login disabled) at the same time. - An archived row does not show "Login Enabled"; it shows a red "Login Disabled" tag.
- Applies to both surfaces: Admin Portal
Employee(POST /api/employee/{id}/Archive) and Enterprise PortalEnterpriseUser(POST /api/enterprisestaff/{id}/Archive). - Archiving staff already at
LoginStatusId = Initialised→ stays unchanged. - Restore does not re-enable login automatically:
LoginStatusIdkeeps the value at archive time; enabling login is a separate explicit action.
Real-world example: Admin restores an archived staff member. → Do not unintentionally grant login again. →
Employee.StatusId = ApprovedbutLoginStatusIdis stillInitialised; login must be manually re-enabled.
Invariant:
INV-EMP-006,INV-EMP-007 v1.
Sort & display-only UI rules
Many Staff specs are display-only (no persistence/API change):
| Place | Rule |
|---|---|
| Availability time slots | Sort by ascending start time, per weekday. |
| Edit Time Slot popup templates | Sort by ascending start time (within the loaded set). |
| Timetable controls | One control set; the ScheduleComponent header is the source of truth for range/view. |
| Staff Home header | Title left; action area right in a fixed order. |
| Leave drawer | Compact paired date/time layout; collapses on a narrow viewport. |
Architecture (high level)
The frontend handles the composer, preview and display. Tux always recomputes shift, Availability, restriction, actor and BU before the atomic mutation; it never trusts preview data from the client.
Next: Common cases.