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