dodil/erp-brick-payroll
DODIL ERP brick: payroll
src/brick_payroll/application/process.yaml
# The brick's process — module.yaml's workflows, copied (pattern.md §15). BRICK.declare_process serves
# it as BPMN 2.0 at GET /process/bpmn; it is identical to `erp-compose bpmn --from module.yaml`.
# A transition's key (or its derived <entity>_<from>_to_<to>) is frozen once signed: it is the BPMN id.
id: payroll
name: Payroll
version: 1
actors:
- id: payroll_admin
name: Payroll administrator
- id: payroll_manager
name: Payroll manager / finance director
- id: employee
name: Anyone on the payroll
- id: owner
name: The owner (or whoever the owner names)
- id: system
name: The system
workflows:
- entity: pay_run
states:
- open
- calculated
- approved
- submitted
- paid
- closed
- cancelled
terminal:
- closed
- cancelled
transitions:
- key: open_pay_run
name: Open the period
from: none
to: open
by:
- system
- payroll_admin
gate: the schedule's previous regular run is approved; the period claim (company, schedule, tax year, period, regular)
free
effect: pay_run_id <company>-<schedule>-<tax year>-<period>; the hr extract produced (HR-60)
- key: open_off_cycle_run
name: Open an off-cycle run
from: none
to: open
by: payroll_manager
requires: reason
gate: the tax period's regular run approved
effect: pay_run_id <regular>-X<n>, kind off_cycle
- key: calculate_pay_run
name: Calculate
from: open
to: calculated
by: payroll_admin
gate: every employee in it has a tax code and an NI category
effect: payslips derived from hr's extract, time-off, timesheets and the rows in force on the pay date
- key: recalculate_pay_run
name: Recalculate
from: calculated
to: calculated
by: payroll_admin
effect: the draft payslips replaced; stale flags cleared
- key: approve_pay_run
name: Approve the run
from: calculated
to: approved
by: payroll_manager
approval: true
gate: not_stale; nmw_met; net_not_negative; bacs_lead; the approver is not the preparer (email AND sub); own_payslip_by_peer
(the approver's own payslip, if in the run, already approved by a peer)
effect: 'after commit, from pending rows: extract sent (HR-60), journal (PRL-16), payouts (PRL-12), FPS (PRL-13), pension
file (PRL-15); payslips frozen'
- key: approve_approver_payslip
name: Approve the approver's own payslip
from: calculated
to: calculated
by: payroll_manager
approval: true
gate: a payroll manager who is neither the payslip's person nor the run's preparer (email AND sub)
effect: that payslip approved; the run may then be approved
- key: record_fps_accepted
name: HMRC accepted the FPS
from: approved
to: submitted
by: system
gate: the capability's RESULT accepted, or the admin's manual task done with HMRC's reference
effect: the correlation id kept
- key: confirm_pay_run_paid
name: Every payout settled
from: submitted
to: paid
by: system
gate: every payout paid or returned (payments, PRL-28)
- key: close_pay_run
name: Close the period
from: paid
to: closed
by:
- system
- payroll_admin
gate: the journal posted; the pension file sent or its manual task done
effect: year to date frozen; payslips published on the pay date
- key: cancel_pay_run
name: Cancel the run
from:
- open
- calculated
to: cancelled
by: payroll_manager
requires: reason
effect: the period claim released; nothing was sent
readiness:
- transition: calculate_pay_run
gate: tax_details
reason: '{person} has no tax code or NI category'
fix: 'Employees: complete the record (P45 or starter declaration)'
- transition: approve_pay_run
gate: not_stale
reason: 'payslips changed under the run: named with their cause'
fix: recalculate
- transition: approve_pay_run
gate: nmw_met
reason: '{person} is paid {rate} an hour, below the {band} minimum of {minimum}'
fix: correct the rate in hr
- transition: approve_pay_run
gate: net_not_negative
reason: '{person}''s net pay is below 0.00'
fix: carry the deduction to the next period
- transition: approve_pay_run
gate: bacs_lead
reason: the pay date is fewer than {n} working days away
fix: approve now and ask finance for a faster payment, or move the pay date
- transition: approve_pay_run
gate: second_person
reason: you calculated this run
fix: another payroll manager approves it
- transition: approve_pay_run
gate: own_payslip_by_peer
reason: your own payslip is in this run and no peer has approved it
fix: another payroll manager approves your payslip first
- transition: record_fps_accepted
gate: fps_on_time
reason: the FPS is due on or before {pay_date}
fix: send it now; a late FPS needs a late reporting reason
note: 'approved never returns to calculated: a mistake found after approval is corrected in the next period or by an off-cycle
run, so no state is ever re-entered'
- entity: payroll_employee
states:
- pending_details
- active
- left
terminal:
- left
transitions:
- key: take_starter
name: Take a starter from hr
from: none
to: pending_details
by: system
gate: a starter on hr's extract with an employee worker type
effect: payroll_id <person_id>-<n>; enrolment assessed in the first run
- key: complete_payroll_details
name: Complete the tax details
from: pending_details
to: active
by: payroll_admin
requires: tax_code
gate: a P45 or a starter declaration (A, B or C), else the emergency code with a warning; the admin is not the person
(email AND sub)
- key: issue_p45
name: Issue the P45
from: active
to: left
by: system
gate: the leaver's final payslip approved
effect: P45 document; the leaving date on the FPS
- entity: pension_membership
states:
- enrolled
- opted_out
- ceased
terminal:
- opted_out
- ceased
transitions:
- key: enrol_pension
name: Enrol
from: none
to: enrolled
by:
- system
- payroll_admin
gate: assessed eligible jobholder, or an entitled or non-eligible worker who asked to join
effect: the enrolment date; the opt-out window (one month) starts when the scheme confirms
- key: opt_out_pension
name: Opt out
from: enrolled
to: opted_out
by:
- employee
- payroll_admin
gate: within the opt-out window
effect: contributions refunded in the next run; the scheme told
- key: cease_pension
name: Cease membership
from: enrolled
to: ceased
by:
- employee
- payroll_admin
- system
gate: after the window (no refund), or the person left
effect: contributions stop from the next period
- entity: prl_ytd_run
states:
- loaded
- reconciled
- signed
- discarded
terminal:
- signed
- discarded
transitions:
- key: load_prl_ytd
name: Load the previous provider's year to date
from: none
to: loaded
by: system
gate: hr's migration signed; the provider's last FPS file attached
- key: reconcile_prl_migration
name: Reconcile against the provider's FPS
from: loaded
to: reconciled
by: system
- key: sign_prl_migration
name: Sign the reconciliation
from: reconciled
to: signed
by: owner
approval: true
gate: every total equal to the penny
- key: discard_prl_migration
name: Discard the load
from:
- loaded
- reconciled
to: discarded
by: owner
requires: reason