Finance / Payroll
The Finance app is the EUSRR adapter around the project-independent
payroll_core package. The dependency points in one direction:
Django models, permissions, workflow and API
|
v
immutable payroll_core DTOs
|
v
deterministic calculation result
The core does not know about Django, users, databases, Excel or EUSRR. The adapter resolves approved inputs, persists snapshots and implements the company workflow.
Implemented slice
- effective-dated, revisioned employee rates;
- period roster, point facts and legacy Excel control totals;
- typed bonuses, corrections, one-time accruals, deductions and advances;
- maker-checker approval for rates and input records;
- deterministic period calculation with input, ruleset and result hashes;
- immutable run revisions and employee statements;
- review, approval, publication, correction return and republication;
- native payroll workspace API for periods, rates, work records, input lines and run workflow actions;
- employee-only statement API and idempotent receipt acknowledgement;
- payroll audit events and dedicated Django permissions.
The June 2026 example is covered by an integration test:
base 80,000 + bonus 15,000 + correction 20,000 = payable 115,000 RUB
target points 110, actual points 110, point amount 0
This proves the photographed row only. It does not prove the formula for employees above or below the point target.
| Spreadsheet column | Finance source |
|---|---|
| Оклад | approved EmployeePayRate |
| Норма баллов / баллы | approved PayrollWorkRecord |
| Отпускные / премия / коррекция / разовая | typed PayrollInputLine |
| Аванс | payment input line, reducing payable |
| Итого / выработка / перерасчёт / к выплате | reconciliation checkpoints |
Checkpoint columns can block review on mismatch but never feed money back into the calculation.
Workflow
Input records and rates are created as drafts and normally approved by another
actor. An approver cannot approve their own record or edit another user's draft
in the admin. A deliberately assigned self-approval override can waive only
the first restriction; the actor must still hold the ordinary input or run
approval permission. Every use persists self_approval_overridden=true on the
approved object and in audit metadata. Database constraints reject both an
unflagged self-approval and a flag without an actual self-approval.
The approval screen captures the exact draft version the checker reviewed; any concurrent maker edit increments that version and makes the approval fail closed until the checker reviews it again.
inputs: draft -> approved
run: calculated -> review -> approved -> published
| |
+-> returned <-+
|
calculate new revision
Every revision after the first requires a reason. Publishing a new revision supersedes the previous published run without changing its snapshots. A legacy Excel mismatch blocks submission for review.
Permissions
Use the custom Finance permissions rather than ordinary Django model access:
finance.manage_payroll_inputs— create and maintain own drafts;finance.approve_payroll_inputs— approve another actor's inputs;finance.calculate_payroll— calculate, submit or withdraw a calculated run;finance.approve_payroll— return, review and approve runs;finance.override_payroll_approval— additionally allow an input or run approver to approve an object they created themselves; this permission grants no workspace access and never replaces either ordinary approval permission;finance.publish_payroll— publish employee statements;finance.view_all_payroll— see payroll data for all employees;finance.audit_payroll— inspect the audit journal.
Ordinary employees need none of these. The self-service API always filters by the authenticated employee and by the currently published run:
GET /api/v1/finance/payroll/me/statements/
GET /api/v1/finance/payroll/me/statements/<public_id>/
POST /api/v1/finance/payroll/me/statements/<public_id>/acknowledge/
Payroll operators use the permission-gated native workspace under
/api/v1/finance/payroll/admin/. It exposes explicit commands rather than
generic model CRUD: draft edits require the version that the operator opened,
approvals require a different actor unless the explicit audited override is
used, and run transitions call the payroll services. The portal entry point is
/finances/payroll/manage; Django Admin is not required for the normal payroll
workflow.
Responses contain no internal source references and are marked
Cache-Control: private, no-store.
Configuration
Rules live in FINANCE_PAYROLL and can be supplied through the
PAYROLL_* environment variables documented in .env.example. Increment
PAYROLL_RULESET_VERSION whenever calculation semantics change.
The point policy is disabled by default. excess_only is available for a
controlled shadow run and computes max(actual - target, 0) * point_rate, but
must not be enabled for publication until the company confirms it on a
representative workbook.
The adapter intentionally accepts only a 0.01 money quantum and nonnegative
payable amounts because the persisted schema stores two decimal places.
Attendance work import
The EUSRR host adapter can prepare PayrollWorkRecord drafts from stored
attendance through the period-scoped preview/apply command. This integration
does not live in payroll_core: it translates verified portal attendance into
the versioned payroll input contract.
Policy hours_as_work_units_v1 maps one verified attendance hour to one work
unit. It is accepted only while the payroll point policy is disabled, so
hours cannot accidentally become paid production points. Missing days,
technical issues, open shifts, unverified remote work and work outside the
effective schedule block the affected employee. The apply command never
auto-approves: it creates a draft, updates only the operator's own draft, or
creates a draft revision that leaves the approved record intact until the
normal maker-checker approval succeeds.
Operational boundaries
- PostgreSQL is required in production. SQLite is suitable only for single-process development/tests because it does not provide the row locks used by the payroll workflow.
- An acknowledgement means the employee confirmed receipt of the statement; it is not evidence that a bank transfer completed.
- Model/admin protections are defense in depth for normal application paths.
Database administrators and direct
QuerySet.update()calls can bypass model validation, so production DB access must remain restricted. - Successful workflow changes and detailed employee views are audited. Failed command attempts are not yet written to a separate append-only command log; add that external audit sink before treating this module as the sole compliance record.
- Overlapping periods are rejected by application validation. A PostgreSQL exclusion constraint should be added before periods can be written by more than one integration.
- Linked reversal records are intentionally disabled in this slice. Use an explicit debit/credit correction with a reason until reversal semantics are approved and implemented atomically.
- Taxes, statutory deductions, bank files, 1C export/import and disputes are deliberately outside this first slice.
Rollout
- Import rates and several representative spreadsheet periods as drafts.
- Reconcile every employee in shadow mode; do not publish mismatched runs.
- Confirm point, correction, deduction and rounding rules with accounting.
- Freeze and version the approved ruleset.
- Grant separated operator, checker, publisher and auditor roles.
- Run one parallel payroll cycle before making the portal authoritative.