Run the quarterly tax filing

Close a quarter: check liability, generate the package, approve it, and mail it.

Owned by Admin · 8 steps · about 25 minutes

Why this exists

Tax is the part of the platform with a deadline attached to a physical envelope, so it is built to be boring and repeatable rather than clever.

The important design idea is that the filing is derived, never typed. Every taxable sale accrues its tax at the moment of the sale, line by line, with the rate snapshotted onto the accrual — so a rate change next year cannot retroactively alter last quarter's numbers. Refunds post negative reversal rows, prorated and capped so cumulative reversals can never exceed the original accrual. The quarterly package is simply the sum of those rows.

The second idea is the approval gate. A generated package is a draft with a watermark. It becomes a real filing only when an authorised approver approves it — from the dashboard or from a Telegram card — and only then is it rendered, hash-sealed and handed to the mail adapter. The stored document bytes and their hash never change afterwards; the watermark is applied when the document is served, not when it is stored.

This entire workflow is admin-only. A venue manager gets a 403 on every route in it, including the read-only ones.

Before you start

  • An admin session — every route in this workflow is admin-gated.
  • Accrued tax in the period you are filing (the seed ships some).
  • The four agency records configured, since a package allocates across exactly four.

Practise with

PersonaEmailPasswordNote
adminadmin@club.test admin123the only persona that can open any of these routes

Steps 1–8 — Admin

their manual →
  1. 1
    Open the tax office and read the quarter-to-date liability tiles and the reconciliation badge.
    Expected result QTD liability by jurisdiction plus a reconciliation status.
    Watch out for If reconciliation is red, stop. It means the accrual rows and the ledger disagree, and filing on top of that just posts the disagreement to an agency. QUALIFIED 2026-08-30: on every install today it is ALREADY red for one known reason — payments credits the tax liability accounts on every sale and writes no accrual row, so the whole ledger movement reads as drift. So 'stop' here means read reconcile-tax-accruals first and learn that baseline; it does not mean this platform can never file. What you must not do is treat a red badge as normal without knowing which red it is. Plan 680 closes the gap so the badge can mean something again.
  2. 2
    Pull the same liability figures from the API and check them against the ledger's tax liability accounts.
    Expected result Liability broken out by period and agency.
  3. 3
    Run the filing scheduler for the quarter you are closing (in training and demo environments — in production the scheduled tick does this).
    Expected result A filing package generated for the period, in draft.
    Watch out for It is idempotent per period: running it again returns the existing package rather than making a second one. Debug routes like this one are admin-only and vanish entirely when debug endpoints are disabled.
  4. 4
    Open the period and read the generated package: the totals, the documents, and the audit trail.
    /admin/tax/periods/{period_id} tax
    Expected result The package with its state-gated actions and DRAFT-watermarked documents.
  5. 5
    Set the allocation across the four agencies.
    /api/tax/packages/{package_id}/allocations PUT tax
    Expected result The allocation saved against the package.
    Watch out for Exactly four agencies, each at least zero. The endpoint rejects anything else rather than filing a package that does not add up.
  6. 6
    Send the package for approval. A Telegram card goes out with inline approve and reject buttons.
    /api/tax/packages/{package_id}/send-approval POST tax
    Expected result The package moves to awaiting approval and the outbound call is recorded.
    Watch out for The Telegram route is a convenience, not a second authority: an unauthorised approver pressing the button is answered with 'Not authorized' and nothing changes.
  7. 7
    Approve the package.
    /api/tax/packages/{package_id}/approve POST tax
    Expected result Documents are rendered and hash-sealed, and physical mail is submitted for each agency with a tracking number.
    Watch out for This is the irreversible one. Approving mails the filing. If the mail adapter partially fails you get a 502 and the package is left in mail_submitted — retry the mailing, do not re-approve; Retry mail sends only the envelopes that failed, so nobody gets two. Added 2026-08-30: know the way OUT before you press it, because this step is the last moment there is one. While the package is draft or awaiting approval the period page carries a red Cancel, which stops the filing dead, and Regenerate, which supersedes this revision and compiles a fresh one — and a cancelled package can still be regenerated, so cancelling is not the end of the quarter. Regenerating for a numbers change is reconcile-tax-accruals; both controls are gone the moment you approve.
  8. 8
    Close the loop: check the tax liability accounts on the trial balance against what you just filed.
    /admin/ledger ledger
    Expected result A balanced trial balance whose liability accounts match the filed figures.
    Watch out for If they do not match, the answer is a compensating ledger entry and a note — never an edit. The ledger refuses updates at the database level.