Draft a run, compute every payslip, get a different person to approve, pay — then notify each member, all on the immutable trail.
The whole flow, end to end
Payslips compute, they are not typed.gross = base + commission − deductions per member, in integer minor units, with commission injected from the commission sub-store.
Self-approval is impossible. The four-eyes gate (approver <> created_by) is enforced by a code-level GovernanceError 403 AND a database CHECK constraint — the maker can never sign off their own run.
One run per period. A duplicate-run guard plus a UNIQUE constraint stop a second non-voided run for the same entity and month; a draft can be recomputed and re-audited before submit.
Mail = email notification Bell = in-app alert (fail-soft) Amber diamond = automatic check Teal diamond = a person decides (four-eyes)
Self-approval is blocked twice — a code-level GovernanceError and a database CHECK constraint (approver <> created_by). Commission is injected from the commission sub-store; a draft can be recomputed and re-audited before submit. Paying notifies every member fail-soft.
What the payroll run guarantees
Every state change — create, recompute, submit, approve, pay, void — writes one immutable audit row via the atomic transition seam (fail-closed).
Maker and checker are config-driven roles — no hardcoded role literals; the writeRoles gate makes, the approveRoles gate checks.
Paying the run notifies every member with a payslip — fail-soft, so a delivery miss never reverses a recorded payment.