payroll

Delete Payroll Run

Discard a draft or calculated run — 422 from posted onwards.

ZCT-540 — the one payroll entity with no reachable delete. runs_service has shipped :func:delete_run since SP6 (R5 iii) and the whole codebase reasons about it — ZET-154's post gate exists to make "post N, then delete N−1" impossible by construction — but no route ever exposed it, so no MCP operation existed either (the tool surface mirrors this API 1:1). That left the product telling the truth about the only two exits and offering neither: a year-level operation refuses with "N unposted run(s) overlap … — post or delete them before issuing" (:mod:invoiage_core.payroll.reports.inclusion ⇒ :func:payroll.lohnausweis.service.issue), while a draft created out of period order can no longer be posted at all — ytd_basis_out_of_order correctly refuses to calculate it (ZET-153), and that guard is not the defect. One abandoned draft therefore blocked Lohnausweis issuance for the rest of the year, with the only escape being to reverse and redo every later run.

Nothing is orphaned. delete_run releases the SP9 expense-claim feed markers via the inputs_discarded hook, clears results, QST details, run lines and the snapshot, and the two child tables the clear does not name (payroll_payslip, payroll_payment_batch) are ON DELETE CASCADE and cannot exist before posted anyway. No posted figure can move: only draft/calculated runs are deletable, and ZET-154's _refuse_unfinalized_ytd_basis refuses to post a run while any contributor to its year-to-date basis is still unposted, so a deletable run is never a contributor to an immutable one.

Deliberately not the ticket's second suggestion (let year-level operations ignore empty drafts): an unposted run genuinely does make a year incomplete, the same partition drives the declarations and the reconciliation, and "ignore it if it looks empty" would hide a run someone still means to calculate. The exit the message already promises is the one that was missing.

The deletion is audited. delete_run appends a payroll_master_audit row (entity_type="run", action=deleted) inside the same transaction, carrying who, when, which run and which period, plus the lifecycle stamps the header held — approved → calculated → DELETE is reachable, so the run that vanished may have been approved by someone (owner ruling 2026-09-03; this replaces the interim log.info, which is not what an audit reads). It is not a GeBüV matter — a draft/calculated run has journal_entry_id IS NULL, so nothing was ever booked, unlike ZET-220's posted entry — but it is a payroll-audit one, and this route is the first thing to make it reachable, over MCP included.

⚠️ The trail is written but not yet readable over the API: the only route over payroll_master_audit is GET …/employees/{employee_id}/audit, and a run row is not employee-scoped (employee_id IS NULL). It is a durable, queryable row where there was none; a read surface for it is a separate ticket.

delete/companies/{company_id}/payroll/runs/{run_id}

Request

  • Base URL: https://app.zeno-works.ch (the document declares no server; this is the origin it was published from)
  • URL: https://app.zeno-works.ch/companies/{company_id}/payroll/runs/{run_id}
  • Auth: none declared

Path parameters

company_idstring required
run_idstring required

Headers

authorizationstring nullable

Cookies

access_tokenstring nullable

Response

Successful Response

okboolean

Changes

No recorded changes to this operation across all 1 revision of this API.