---
id: STATE-ACC-TRX-003
title: Write-off lifecycle
object: DOM-ACC-TRX-001
product_area: accounting
module: transactions
revision: 1
status: draft
design_intent: confirmed
origin: >-
  Andrew’s D19 as recorded in
  services-accounting:docs/architecture/accounting-2.0/02-decisions.md#M1-6
  (partial and reversible by a negating write-off)
implementation_evidence: observed
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
states:
  - name: not created
    meaning: No write-off has been saved.
  - name: issued
    meaning: >-
      Selected outstanding debt has been abandoned using the chosen accounting
      method.
  - name: reversed
    meaning: A retained opposite document has restored the abandoned value.
transitions:
  - from: not created
    to: issued
    trigger: abandon debt
    guard: >-
      The issued real invoice must own the selected lines and have sufficient
      balance; a supplied allowance account must belong to its unit.
    side_effects:
      - assign the write-off number
      - insert write-off ledger lines
      - append abandonment facts
    who: Staff with write-off create permission.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/write-offs/write-off-commands.ts;
      services-accounting:modules/transactions/db/write-offs.table.ts;
      services-accounting:modules/transactions/tests/integration/services/write-offs/write-offs.integration.spec.js#it
      writes nothing: no write-off, no ledger line, no fact, no event
  - from: issued
    to: issued
    trigger: correct metadata or batching
    guard: Batch moves and date changes require unposted batching.
    side_effects:
      - append history for changed details
    who: Staff with write-off update permission.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/write-offs/write-off-commands.ts;
      services-accounting:modules/transactions/db/write-offs.table.ts;
      services-accounting:modules/transactions/tests/integration/services/document-edits/reassign-and-edit.integration.spec.js#it
      negates the facts on the old accounting date and restates them on the new
      one, the originals untouched
  - from: issued
    to: reversed
    trigger: restore the debt
    guard: >-
      A reason is required and the original must neither be reversed nor itself
      be an opposite document.
    side_effects:
      - create today's opposite write-off
      - restore line debt
      - append restoration facts
    who: Staff with write-off update permission.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/write-offs/write-off-commands.ts;
      services-accounting:modules/transactions/db/write-offs.table.ts;
      services-accounting:modules/transactions/tests/integration/services/write-offs/write-offs.integration.spec.js#it
      answers the reversal: a second write-off, numbered next, its lines
      negating the first’s
invalid:
  - from: reversed
    to: issued
    why: Another abandonment requires a new write-off after restoration.
  - from: issued
    to: reversed
    why: The opposite document cannot itself initiate another reversal.
concurrency: >-
  Ordered invoice-ledger locks serialize competing changes, and the database
  rejects a write-off that exceeds remaining debt.
invariants:
  - INV-DOM-ACC-TRX-001-15
evidence:
  - >-
    services-accounting at f47cb263: services/write-offs/*;
    db/write-offs.table.ts; docs/architecture/accounting-2.0/02-decisions.md
    M1-6, A-2, §2.12 D-5 and D-7.
---
# Write-off lifecycle

## The Lifecycle

A restoration is another financial document linked to its original. Both records remain available to explain the customer’s debt.

```mermaid
stateDiagram-v2
  direction LR
  state "not created" as s0
  state "issued" as s1
  state "reversed" as s2
  s0 --> s1 : abandon debt
  s1 --> s1 : correct metadata or batching
  s1 --> s2 : restore the debt
```

## Transitions Explained

The transition rows identify the initiating action, eligibility checks, resulting records and responsible caller. They retain the pinned evidence for each path.

## What Cannot Happen

The invalid-transition rows describe final-state protection and unsupported paths; command eligibility remains distinct from the declared state vocabulary.

## Concurrency and Timing

Ordered invoice-ledger locks serialize competing changes, and the database rejects a write-off that exceeds remaining debt. No asynchronous worker advances this lifecycle.
