Allowance Calculation Engine
Allowance Calculation Engine
The calculation engine is App\Service\Payroll\AllowanceMatrixCalculator.
High-level algorithm
calculateLines(Timesheet $timesheet) performs this flow:
1. Resolve project from timesheet.
2. Stop if project or client is missing.
3. Load client allowances for the project client, sorted by name.
4. Keep only active and enabled allowances.
5. For each allowance:
a. Check project applicability.
b. Resolve effective amount unit.
c. Dispatch to hourly, daily, weekly, daily_or_weekly, or project_variable calculation.
6. Return calculated TimesheetAllowanceLine objects.refreshCalculatedLines(Timesheet $timesheet) removes all existing allowance lines from the timesheet and EntityManager, then persists the newly calculated lines. This makes recalculation idempotent for the current state.
Amount-unit dispatch
| Unit | Method | Quantity behavior |
|---|---|---|
hourly | calculateHourlyLines | Sum matched entry hours, or use overtime/normal time aggregate quantities for those trigger types. |
daily | calculateDailyLines | Count unique worked dates that match the trigger. |
daily_or_weekly | calculateDailyOrWeeklyLine | Count unique worked dates until threshold, then use one weekly unit. Can use triggerConfig.weeklyAmount. |
weekly | calculateWeeklyLine | One unit if any entry matches. |
project_variable | calculateManualSelectionLines or project-resolved unit | If project type resolves to hourly/daily/weekly/daily_or_weekly, the calculator dispatches to that effective type. Otherwise manual quantity is read from entry selections. |
Effective amount unit
resolveEffectiveAmountUnit() keeps the client allowance unit unless it is project_variable. For project-variable allowances, it reads the matching project allowance row type and maps it to:
hourlydailyweeklydaily_or_weekly
If no valid project type exists, it remains project_variable and uses manual-selection style quantity.
Effective unit rate
resolveUnitRate() first checks project allowance rows for a matching name and numeric rate. It strips non-numeric/currency characters before parsing. If a project rate exists, it overrides ClientAllowance.amount. If not, the client allowance amount is used, defaulting to 0.
Line snapshot
Every output line stores a sourceSnapshot containing:
clientAllowanceIdtriggerTypeapplicationModeamountUnitconfiguredAmountUnitisEabtriggerConfig- extra context such as matched dates, matched entries, overtime hours, normal time hours, selected mode, or threshold values
This snapshot is critical because project/client rules may change later. The line should still explain what was calculated at the time it was generated.