Overview
What the payroll engine does
The Tidex payroll engine computes wage earnings for work shifts. It takes shift data (date, start/end time) and wage settings (hourly rate, supplements, tax, break deductions) to produce deterministic earnings values.
Inputs
- Shift data: shift_date (ISO), start_time (HH:MM), end_time (HH:MM), optional custom supplements, job_id
- Wage snapshot: Hourly wage, supplement rules, tax settings, break deduction settings — scoped to a job
- Job: Name, color, payroll_day, half_tax_month, monthly_goal — primary source for payroll configuration
- User settings: Global preferences; payroll_day/half_tax_month/monthly_goal kept as legacy fallback during compatibility window
Outputs
- computeShift output: durationHours, paidHours, basePay, supplementPay, gross, wagePeriods, originalWagePeriods, breakAudit
- Downstream totals output: taxAmount and net (calculated outside computeShift)
Key invariants
- Deterministic: Same inputs always produce same outputs
- Pure computation: Zero I/O, all inputs explicit
- Cross-midnight support: Shifts spanning midnight are calculated as continuous time
- Dual-date snapshot logic: Wage/supplements/breaks use shift date; tax uses payout date
- Precision: 3 decimal places for hours, 2 decimal places for currency
- Job-scoped snapshots: Each shift uses wage snapshots belonging to the same job; legacy (job-less) snapshots serve as fallback
- Job-scoped payroll day: payroll_day is resolved from the shift's job first, then falls back to user settings
Definitions
| Term | Definition |
|---|---|
| Shift | A stored work period with date, start time, end time |
| Virtual shift | A computed occurrence from a recurring shift template (not persisted) |
| Wage snapshot | Point-in-time capture of wage, supplement, tax, and break settings — scoped to a job |
| Baseline snapshot | Snapshot with from_date = NULL, serves as fallback within its job bucket |
| Supplement window | Time-of-day range when a supplement rate applies |
| Payout date | Date when wages are paid (typically month after work + payroll day) |
| Payroll period | The calendar month whose earnings are grouped for a payout |
| Month grouping | Shifts worked in month M are paid in month M+1 |
| Job | An employer/workplace entity that groups shifts and wage snapshots; owns payroll_day, half_tax_month, monthly_goal |
| Default job | Each user has exactly one active default job; shifts without an explicit job_id are assigned here |
| Legacy snapshot | A wage_snapshot with job_id = NULL; used as fallback when no job-specific snapshot exists |
Entry points
The payroll engine can be called in two ways. The optional job parameter is reserved for future use — payroll day resolution is performed upstream in ShiftsService before calling computeShift.
// Pure function (core calculation)
computeShift(shift, settings, presetRules, snapshot, job?) // from @/lib/payroll/calc.ts
// Effect-wrapped (with validation)
computeShift(shift, settings, presetRules, snapshot, job?) // from @/lib/payroll/effect.ts