Allowance Matrix
Source Code Map
Exact files, methods, subscribers, commands, and tests that implement the allowance matrix.
Core files
| File | Responsibility |
|---|---|
app/src/Service/Payroll/AllowanceMatrixCalculator.php | Reads client/project/time-entry data and creates TimesheetAllowanceLine rows. |
app/src/Entity/ClientAllowance.php | Client-scoped allowance definition, trigger, amount unit, application mode, amount, status, and config JSON. |
app/src/Entity/TimesheetAllowanceLine.php | Calculated allowance output row attached to a timesheet and optionally a time entry. |
app/src/Enum/AllowanceAmountUnit.php | hourly, daily, weekly, daily_or_weekly, project_variable. |
app/src/Enum/AllowanceApplicationMode.php | auto, selectable_timesheet, manual. |
app/src/Enum/AllowanceTriggerType.php | All trigger condition names used by the calculator. |
app/src/EventSubscriber/TimesheetAllowanceCalculationSubscriber.php | Detects changes and queues recalculation after flush. |
app/src/Command/RecalculateTimesheetAllowancesCommand.php | Manual recalculation command. |
app/tests/Service/Payroll/AllowanceMatrixCalculatorTest.php | Scenario coverage for project overrides, trigger types, manual selections, distance, and daily-or-weekly behavior. |
Important public methods
| Method | Meaning |
|---|---|
refreshCalculatedLines(Timesheet $timesheet) | Deletes existing allowance lines for the timesheet and persists newly calculated lines. |
calculateLines(Timesheet $timesheet) | Returns calculated line objects without flushing. It is the main pure calculation entry point. |
Internal decision methods
| Method | Meaning |
|---|---|
isProjectApplicable() | Checks Project.allowances_data. Empty project rows mean all active enabled client allowances are eligible. A matching row with apply=false disables that allowance. |
calculateAllowanceLines() | Dispatches to hourly, daily, weekly, daily-or-weekly, or manual/project-variable behavior based on effective amount unit. |
resolveEffectiveAmountUnit() | Converts project_variable into a concrete unit using the matching project allowance type. |
resolveUnitRate() | Uses project allowance rate first, then falls back to ClientAllowance.amount. |
isEntryTriggered() | Applies application-mode gating and trigger-specific tests. |
buildLine() | Creates the output line with quantity, unit rate, amount, reason, taxable/billable flags, and source snapshot. |
Subscriber flow
Doctrine onFlush
-> inspect scheduled changes
-> collect affected timesheet ids
-> collect project/client allowance changes that affect submitted, accepted, or approved timesheets
Doctrine postFlush
-> recalculate allowance lines
-> persist new lines
-> allow award recalculation subscriber to react to changed allowance linesThe subscriber does not recalculate draft timesheets automatically. This avoids unexpected churn while workers are still editing drafts.
Test scenarios to keep
The existing test class covers the most important matrix behavior. Any future refactor should keep equivalent coverage for:
- inactive and disabled allowances are ignored
- project
apply=falseblocks allowance calculation - project rate overrides client amount
- project-variable type chooses hourly/daily/weekly behavior
- shift-hours-over uses strict greater-than threshold behavior
- manual selections match by name or label
- manual quantity reads
quantity,hours,days, orunits - vehicle type matches configured
vehicleType - work conditions match name, slug, or id
- distance can be read directly or calculated from coordinates
- living-away allowance bypasses distance metadata requirement
- overtime and normal-time hours use configured thresholds
- daily-or-weekly switches to weekly mode at threshold days and can use
weeklyAmount