dodil/erp-brick-tax

DODIL ERP brick: tax

src/brick_tax/application/process.yaml

# The brick's process — module.yaml's workflows (v4), copied (pattern.md §15). TAX.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 is frozen once signed: it is the BPMN id. Signed 2026-10-22 (tax v4).
id: tax
name: Tax (VAT)
version: 6
actors:
- id: tax_admin
  name: Whoever is responsible for VAT (the owner, the bookkeeper, the accountant)
  does: records the VAT registration, keeps tax codes, rates, categories, treatments and the rules that join them
  sees: everything
  permissions:
  - tax:code:read
  - tax:code:write
  - tax:code:approve
  - tax:category:write
  - tax:registration:write
  - tax:party_tax:write
  - tax:document_tax:approve_override
  - tax:settings:write
  - tax:vat_return:read
  - tax:vat_return:prepare
  - tax:vat_return:submit
  - tax:vat_adjustment:write
  - tax:bad_debt:claim
  - tax:cis:write
  - tax:cis_return:submit
  - tax:trade_declaration:write
  - tax:import_vat:write
  - tax:partial_exemption:write
- id: owner
  name: The owner or a finance controller
  does: sets the controls on whoever keeps VAT (whether a rate, rule or registration change waits for a second person, who approves an override that lowers tax, the business time zone)
  sees: everything
  permissions:
  - tax:code:read
  - tax:code:approve
  - tax:settings:controls
  - tax:hmrc:connect
  - tax:vat_return:read
  - tax:vat_return:submit
  - tax:cis_return:submit
  note: 'v4: the owner also connects the business to HMRC (TAX-26) and may submit a VAT or CIS return; submitting is a legal declaration a person makes, never a service client'
- id: biller
  name: Whoever raises orders and invoices
  does: sees the tax worked out on a document, records a customer's VAT number, overrides a line's code with a reason
  sees: tax codes, a document's tax
  permissions:
  - tax:code:read
  - tax:party_tax:write
  - tax:document_tax:override
- id: system
  name: The system
  does: loads the country's codes, determines and computes tax for a document in its direction (sale or purchase), suggests a code where no rule covers a line, checks its contents, freezes it at issue, runs the one-off Odoo migration; v4 - reads HMRC obligations, liabilities and payments, checks VAT numbers, answers whether a tax point is filed, computes a CIS deduction and records one suffered, records import entries, finds bad debts eligible for relief
  sees: everything, through its own service account (one service client per calling brick, each with only its exact grants)
  permissions:
  - tax:code:read
  - tax:code:seed
  - tax:document_tax:compute
  - tax:document_tax:suggest
  - tax:history:import
  - tax:migration:read
  - tax:obligation:sync
  - tax:vat_number:check
  - tax:tax_point:read
  - tax:cis:deduct
  - tax:cis:record_suffered
  - tax:import_vat:record
  - tax:bad_debt:suggest
  - tax:cis:read
workflows:
- entity: registration
  states:
  - not_registered
  - registered
  - deregistered
  terminal:
  - deregistered
  transitions:
  - key: record_not_registered
    name: Record that we are not registered
    from: none
    to: not_registered
    by: tax_admin
    gate: 'the first registration row (it may carry any date: nothing has used it)'
    effect: documents charge no VAT and are not titled VAT invoice
  - key: register
    name: Record our VAT registration
    from: not_registered
    to: registered
    by: tax_admin
    requires: vat_number
    gate: a VAT number in the GB format; effective_from after today; a second person's approval where tax_changes_need_second_person
    effect: tax points from that date charge VAT
  - key: record_registered
    name: Record an existing registration
    from: none
    to: registered
    by: tax_admin
    requires: vat_number
    gate: 'the first registration row (a new installation or the migration): it carries the date HMRC registered us'
  - key: change_vat_scheme
    name: Change the accounting basis or the flat rate scheme (v4)
    from: registered
    to: registered
    by: tax_admin
    requires: scheme
    gate: 'from the first day of a VAT period after today; eligibility from the dated tax_statutory limits (cash accounting: expected taxable turnover at most the joining limit; flat rate: at most its joining limit, a sector and its rate); a second person''s approval where tax_changes_need_second_person'
    effect: 'a new registration row: accounting_basis invoice | cash, flat_rate with sector, rate and first_year_until; returns of later periods compute under it (TAX-36, TAX-37)'
  - key: deregister
    name: Record deregistration
    from: registered
    to: deregistered
    by: tax_admin
    requires: effective_date
    approval: true
    gate: effective_date after today; the approver is not the author
    effect: tax points from that date charge no VAT
- entity: tax_code
  states:
  - draft
  - active
  - retired
  terminal:
  - retired
  transitions:
  - key: load_pack
    name: Load the country's codes
    from: none
    to: draft
    by: system
    gate: a new installation with no codes, and no migration planned
    effect: 'the UK pack: S, R, Z, E, OS, RC, NV, with their rates from their real dates'
  - key: add_code
    name: Add a tax code
    from: none
    to: draft
    by: tax_admin
    gate: the code is not in use
  - key: activate_code
    name: Activate the code
    from: draft
    to: active
    by: tax_admin
    gate: 'a treatment, a first rate (any date: no document has used the code), and the document wording where the treatment needs one (reverse charge)'
  - key: schedule_rate
    name: Schedule a rate change
    from: active
    to: active
    by: tax_admin
    gate: valid_from after today; no other rate for the code on that date; a second person's approval where tax_changes_need_second_person
    effect: a dated rate row; issued documents unchanged
  - key: withdraw_rate
    name: Withdraw a rate not yet in effect
    from: active
    to: active
    by: tax_admin
    gate: its valid_from is after today
    effect: the row is marked withdrawn, with who and when; it never takes effect
  - key: retire_code
    name: Retire the code
    from: active
    to: retired
    by: tax_admin
    gate: no rule in effect today or later points at it
    effect: no new determinations; documents keep it
  note: a code's treatment and reporting_box are fixed once a document has used it (a different meaning is a new code); its description and wording may change, because each issued document keeps the wording it froze
- entity: document_tax
  states:
  - computed
  - frozen
  terminal:
  - frozen
  transitions:
  - key: determine
    name: Determine each line's code
    from: none
    to: computed
    by: system
    gate: 'a rule of the document''s direction (sale | purchase) valid on the tax point covers every (category, treatment) pair, except a line pinned to an earlier frozen document (v4, TAX-47), which takes that document''s code and rate; the party has a treatment: the customer''s for a sale, the SUPPLIER''s for a purchase (v6, TAX-53: or, with no bp_id, the treatment_code the document names; neither refuses with gate party_or_treatment); v6, TAX-51: a line''s consumption (eat_in | takeaway) selects among rules that distinguish it'
    effect: code, rate, VAT per line; the per-rate summary; the total; for a purchase, VAT split into reclaimable and non-reclaimable by each code's reclaimable_pct
  - key: ai_suggest_code
    name: AI suggests a code for a line no rule covers (suggest)
    from: computed
    to: computed
    by: system
    gate: a line has no rule, or no product, and the step is on
    effect: a proposed code with its reason; nothing is applied
  - key: accept_tax_suggestion
    name: Accept or reject the suggestion
    from: computed
    to: computed
    by: biller
    gate: a suggestion is open on the line
    effect: 'accepted: recorded as an override with the suggestion, the model and who accepted; rejected: the line stays refused until a person chooses'
  - key: override_line
    name: Override a line's code
    from: computed
    to: computed
    by: biller
    requires: reason
    gate: the biller disagrees with the rule's code
  - key: approve_override
    name: Approve an override that lowers tax
    from: computed
    to: computed
    by: tax_admin
    approval: true
    gate: an override lowers the tax; the approver is not the person who overrode, unless override_approval is self_with_reason
  - key: check_contents
    name: Check the invoice contents
    from: computed
    to: computed
    by: system
    gate: the document is about to issue (a sale) or be approved (a purchase)
    effect: 'every para 16.3.1 gate as {gate, met, reason, fix}; for a purchase the supplier is the SUPPLIER (its name, address and VAT number on the bill) and we are the customer; v6, TAX-50: a purchase at or under simplified_invoice_threshold (gross including VAT) is a less detailed invoice (VAT Notice 700 para 16.6.1, VAT Regulations 1995 reg. 16): our name and address are not required and the title says so'
  - key: freeze
    name: Freeze the tax at issue
    from: computed
    to: frozen
    by: system
    gate: 'every readiness gate met: contents complete, no open suggestion, no override waiting for approval'
    effect: never recomputed
  note: the document (an order, an invoice, a credit note) owns its own copy of the result; this lifecycle is the tax on it
- entity: party_tax
  states:
  - recorded
  terminal: []
  transitions:
  - key: set_party_tax
    name: Record a customer's or supplier's treatment and VAT number
    from: none
    to: recorded
    by: biller
    gate: a treatment that needs a VAT number has one; the first row of a party holds from the start
    effect: the party's first tax row (TAX-05)
  - key: change_party_tax
    name: Change a party's treatment, number or end-user notice (v4)
    from: recorded
    to: recorded
    by: biller
    gate: valid_from after today; a construction end-user or intermediary notice carries its reference and date
    effect: a new dated row; documents not yet issued with a tax point from then use it (TAX-05, TAX-46)
  - key: check_party_vat_number
    name: Check the VAT number with HMRC (v4)
    from: recorded
    to: recorded
    by: system
    gate: a treatment that needs a VAT number was saved, or its last check is older than vat_number_check_days before a reverse-charge document issues
    effect: check_result valid | invalid | could_not_check, HMRC's name and address and consultation number stored on the row (TAX-13)
  - key: confirm_party_vat_number
    name: Confirm a number HMRC could not check (v4)
    from: recorded
    to: recorded
    by: tax_admin
    requires: reason
    gate: the last check is could_not_check (a non-GB number, or HMRC unavailable)
    effect: recorded as confirmed by a person, with the reason
  note: 'v4: the party''s tax rows were a table only; drawing them makes the VAT-number check and the end-user notice visible in the signed process'
- entity: vat_return
  states:
  - open
  - draft
  - locked
  - submitted
  terminal:
  - submitted
  transitions:
  - key: sync_vat_obligations
    name: Read our obligations from HMRC
    from: none
    to: open
    by: system
    gate: an HMRC connection for the VRN
    effect: one vat_obligation per periodKey with HMRC's dates; a period HMRC shows filed is marked so (TAX-27)
    timer: daily
  - key: prepare_vat_return
    name: Prepare the return
    from: open
    to: draft
    by: tax_admin
    gate: the obligation is open at HMRC
    effect: 'boxes 1-9 from frozen document tax not yet in a return (claimed per [document_ref, tax_code]), under the period''s scheme: invoice or cash basis, flat rate, partial exemption, bad debt claims and repayments, PVA entries, adjustments (TAX-28, TAX-35..TAX-37, TAX-48)'
  - key: refresh_vat_return
    name: Prepare the draft again
    from: draft
    to: draft
    by: tax_admin
    effect: this draft's entries replaced; entries of other returns untouched
  - key: record_vat_adjustment
    name: Correct an earlier period's error on this return
    from: draft
    to: draft
    by: tax_admin
    requires: reason
    gate: net errors within the error-correction threshold and the error within four years
    effect: an adjustment row naming the period it corrects, in box 1 or 4 (TAX-29)
  - key: record_vat_error_notice
    name: Record an error notified to HMRC (VAT652)
    from: draft
    to: draft
    by: tax_admin
    requires: reference
    gate: an error above the threshold
    effect: the notice kept with its date, periods and amount; it is not on the return
  - key: ai_review_vat_return
    name: AI reviews the draft return (suggest)
    from: draft
    to: draft
    by: system
    gate: the step is on
    effect: findings (a box moving far from the last four periods, a document with an unusual code) listed for the tax admin; nothing changes
  - key: lock_vat_return
    name: Lock the return for submission
    from: draft
    to: locked
    by: tax_admin
    gate: the period has ended (its end before today in business_timezone); the period's postponed-import statements reconciled; flat-rate eligibility answered; no adjustment above the threshold
    effect: boxes and entries frozen; a new document with a tax point in the period is refused (tax_point_open)
  - key: reopen_vat_return
    name: Reopen a locked return
    from: locked
    to: draft
    by: tax_admin
    requires: reason
    gate: not submitted
  - key: submit_vat_return
    name: Submit to HMRC with the declaration
    from: locked
    to: submitted
    by: owner
    approval: true
    requires: declaration
    gate: the HMRC connection is valid; the person ticked the legal declaration; a second person where vat_return_needs_second_person (not the identity that locked it, by email or sub); every fraud-prevention header present; HMRC still shows the obligation open
    effect: HMRC's receipt stored; the return immutable; its tax points filed (TAX-30, TAX-31)
  - key: settle_vat_return
    name: Post the return's settlement to the ledger
    from: submitted
    to: submitted
    by: system
    gate: the ledger is installed
    effect: each tax code's VAT against vat_control, journal id derived from the return; once (TAX-32)
  - key: refresh_vat_liability
    name: Read HMRC's liabilities and payments
    from: submitted
    to: submitted
    by: system
    gate: an HMRC connection
    effect: liability, paid and outstanding per period as HMRC holds them (TAX-32)
    timer: daily
  note: a submission that times out is settled by GET /organisations/vat/{vrn}/returns/{periodKey} before any retry, so a return is never filed twice; a correction after submission is an adjustment on a later return or a VAT652 notice, never an edit
- entity: bad_debt_claim
  states:
  - eligible
  - claimed
  - dismissed
  terminal:
  - claimed
  - dismissed
  transitions:
  - key: suggest_bad_debt_relief
    name: List invoices eligible for relief
    from: none
    to: eligible
    by: system
    gate: an invoice six months past the later of its due and supply dates, written off, its VAT in a submitted return
    effect: one eligible row per invoice
    timer: when a return is prepared
  - key: claim_bad_debt_relief
    name: Claim the relief on this return
    from: eligible
    to: claimed
    by: tax_admin
    gate: every eligibility gate met; within four years and six months
    effect: the VAT fraction of the unpaid amount in box 4 (TAX-34)
  - key: dismiss_bad_debt_relief
    name: Do not claim
    from: eligible
    to: dismissed
    by: tax_admin
    requires: reason
  - key: repay_bad_debt_relief
    name: Repay relief on a later receipt
    from: claimed
    to: claimed
    by: system
    gate: a receipt allocated to the invoice after the claim
    effect: its VAT fraction in box 1 of the next return, once per allocation (TAX-35)
- entity: cis_subcontractor
  states:
  - unverified
  - verified
  terminal: []
  transitions:
  - key: add_cis_subcontractor
    name: Add a subcontractor
    from: none
    to: unverified
    by: tax_admin
    requires: utr
    gate: a business partner with a UTR (and a company number or NI number)
  - key: verify_cis_subcontractor
    name: Verify with HMRC
    from: unverified
    to: verified
    by: tax_admin
    gate: an HMRC connection, or a verification number a person read from HMRC
    effect: status gross | net | unmatched, rate and verification number from today (TAX-38)
  - key: reverify_cis_subcontractor
    name: Verify again
    from: verified
    to: verified
    by: tax_admin
    effect: a new dated row; deductions from then use it
- entity: cis_return
  states:
  - open
  - prepared
  - submitted
  terminal:
  - submitted
  transitions:
  - key: compute_cis_deduction
    name: Compute the deduction on a subcontractor's payment
    from: open
    to: open
    by: system
    gate: the subcontractor is verified, or was paid in this or the last two tax years
    effect: a deduction row keyed [payment_ref, bp_id] in the payment's tax month (TAX-39)
  - key: reverse_cis_deduction
    name: Reverse a deduction when the payment is rejected (v4)
    from: open
    to: open
    by: system
    gate: the deduction's tax month is not submitted (cis_return_open); a repeat answers the same
    effect: a reversing deduction row keyed [payment_ref, bp_id]; the month's return and the subcontractor's statement no longer count it
  - key: prepare_cis_return
    name: Prepare the monthly CIS return
    from: open
    to: prepared
    by: tax_admin
    gate: the tax month (6th to 5th) has ended
    effect: the CIS300 lines per subcontractor, or a nil return
  - key: submit_cis_return
    name: Submit the CIS return
    from: prepared
    to: submitted
    by: tax_admin
    approval: true
    requires: declaration
    gate: the employment-status and verification declarations confirmed by a person; an HMRC connection
    effect: HMRC's receipt kept (TAX-40)
  - key: send_cis_statements
    name: Send each subcontractor its statement
    from: submitted
    to: submitted
    by: tax_admin
    effect: a payment and deduction statement per subcontractor, the send recorded
- entity: trade_declaration
  states:
  - draft
  - filed
  terminal:
  - filed
  transitions:
  - key: prepare_trade_declaration
    name: Prepare the EC sales list or Intrastat declaration
    from: none
    to: draft
    by: tax_admin
    gate: ni_goods registered; for Intrastat, the flow's threshold passed
    effect: lines from ar's (and purchasing's) goods documents with EU partners; the upload file (TAX-42, TAX-43)
  - key: record_trade_declaration_filed
    name: Record that it was filed with HMRC
    from: draft
    to: filed
    by: tax_admin
    requires: submission_ref
    gate: the EC sales list ties to box 8 for its months; every Intrastat line has a commodity code
    effect: filed with the reference and date; an amendment is a new version
- entity: pva_statement
  states:
  - imported
  - reconciled
  terminal:
  - reconciled
  transitions:
  - key: import_pva_statement
    name: Upload the month's postponed import VAT statement
    from: none
    to: imported
    by: tax_admin
    gate: one statement per month
    effect: statement lines kept, the file kept as an object
  - key: match_pva_statement
    name: Match statement lines to import entries
    from: imported
    to: imported
    by: system
    effect: each line matched by MRN, with any difference
  - key: explain_pva_difference
    name: Explain a difference
    from: imported
    to: imported
    by: tax_admin
    requires: reason
  - key: reconcile_pva_statement
    name: Reconcile the statement
    from: imported
    to: reconciled
    by: tax_admin
    gate: every line matched or explained
    effect: the return uses the reconciled figures (TAX-45)