T1 Electrical SolutionsT1 Platform Docs
Allowance Matrix

Source Code Map

Exact files, methods, subscribers, commands, and tests that implement the allowance matrix.

Core files

FileResponsibility
app/src/Service/Payroll/AllowanceMatrixCalculator.phpReads client/project/time-entry data and creates TimesheetAllowanceLine rows.
app/src/Entity/ClientAllowance.phpClient-scoped allowance definition, trigger, amount unit, application mode, amount, status, and config JSON.
app/src/Entity/TimesheetAllowanceLine.phpCalculated allowance output row attached to a timesheet and optionally a time entry.
app/src/Enum/AllowanceAmountUnit.phphourly, daily, weekly, daily_or_weekly, project_variable.
app/src/Enum/AllowanceApplicationMode.phpauto, selectable_timesheet, manual.
app/src/Enum/AllowanceTriggerType.phpAll trigger condition names used by the calculator.
app/src/EventSubscriber/TimesheetAllowanceCalculationSubscriber.phpDetects changes and queues recalculation after flush.
app/src/Command/RecalculateTimesheetAllowancesCommand.phpManual recalculation command.
app/tests/Service/Payroll/AllowanceMatrixCalculatorTest.phpScenario coverage for project overrides, trigger types, manual selections, distance, and daily-or-weekly behavior.

Important public methods

MethodMeaning
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

MethodMeaning
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 lines

The 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=false blocks 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, or units
  • 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

On this page