Payroll / Timesheet — Common cases
Payroll tab missing in Finance Settings
Check STAFF_ROSTER for the current BU. The tab only renders when the BU enables Staff Roster; route /settings/finance/payroll is protected by the same rule.
Real-world example: Admin does not see the Payroll tab after Subsidy Settings. → BU has not enabled Staff Roster / lacks
STAFF_ROSTER. → The tab not rendering is correct config; do not change the UI.
Saving payroll settings with the wrong value
Fixed backend label mapping: Rostered Hours = 0, Actual Hours = 1. Saving a value other than 0/1 → rejected/normalized; a raw label is never stored.
Real-world example: Admin picks
Actual Hours. → Store the value1, not the label. →DefaultPaidHoursOptionId = 1; reloading still showsActual Hours.
Export is CSV only, no xlsx/PDF
Export is always a single CSV; the system offers no other variant. The browser does not assemble the file — every format is built by the backend + jsReport.
Real-world example: User downloads the export and gets CSV instead of the old xlsx. → New behavior of server-side rendering. → CSV downloads, filename from the date range, content from the backend dataset.
Export rejected for id outside scope
A row id (shift or leave-only) outside the caller's BU/org scope → entire request rejected, results are not narrowed. Missing Timesheet permission also rejects, no rows marked.
Real-world example: Caller sends an id from another BU's org. → Do not return a file missing rows. → The whole request fails, no row marked exported.
Render fails but nothing marked exported
exported is marked only after a successful render; Reporting Server error → request fails, every row stays as-is. Un-export still clears the flag so rows can be selected again.
Real-world example: jsReport errors mid-render. → Don't let rows be "exported" without a file. → No row is marked; retry once the server is stable.
Leave Hours column from the wrong source
Leave Hours = overlap between the leave interval and shift interval — not the absence total hours. Leave Tracking Code = the leave's tracking code, not the shift's code.
Real-world example: Shift
09:00–17:00, leave07:00–11:00(total 4h, codeAL). →Leave Hours = 2, codeAL. → If4orOTshows → wrong source, do not fix via UI.
Multiple leaves overlapping one shift
Export merges into the same shift row, values keep overlap start-time order; the shift row is not duplicated.
Real-world example: A shift overlaps 2 leaves (hours
1/2, codesAL/SICK). → Export 1 row for the shift. →Leave Hourscell =1and2; leaveTracking Code=ALandSICK.
Leave-only row missing columns
A leave-only row uses the exact column set of a shift row: Date/Name have values, Org empty, shift-hour columns empty, Leave Hours/Tracking Code from the absence itself.
Real-world example: A leave-only
09:00–13:00codeAL. → The file hasLeave Hours = 4.00, codeAL,Organd shift-hour columns empty. → The row still parses to the same field count as a shift row.
Approving a leave-only row drags in leave approval
Timesheet status is independent of the leave approval lifecycle: approving a row on the Timesheet does not change the leave's status/approver/time, and vice versa.
Real-world example: Admin approves a draft leave-only row. → Must not turn the leave request into "approved". → Only
EmployeeAbsence.TimesheetStatusIdchanges; the leave lifecycle stays unchanged.
Leave overlapping a shift does not create a separate row
Leave overlapping a rostered shift → no leave-only row; it sits in the shift row's leave column, not approved/exported independently.
Real-world example: Staff has a shift and approved leave on the same day at overlapping hours. → Don't duplicate into 2 rows. → Only shown in the shift row's leave column; approve/export follows the shift row.
Grouped mode merging same-name staff
Groups by staff identity, not display name; 2 staff with the same name are still 2 separate records, hours never summed together.
Real-world example: Two employees both named "Hoa" are in the same selection. → Do not merge their hours. → The CSV has 2 separate records, each summing exactly its own hours, sorted by name.
Leave-only row locked from selection when exported
An exported row cannot be selected; the un-export action clears the flag and allows re-selection.
Real-world example: A leave-only row exported last period still shows in the grid. → Don't export it again. → Selection control disabled; choose Un-export to unlock and re-select.