---
id: STATE-ACC-BAT-004
title: Posting record
object: DOM-ACC-BAT-003
product_area: accounting
module: batches
revision: 1
status: draft
design_intent: confirmed
origin: >-
  Reporting D7 (Andrew, 2026-10-21: "Unposting a batch should delete subledger
  entries; so no such thing as 'reversals.' But if so, keep all mark
  reversals."), recorded at
  services-accounting:docs/accounting/batches.md#Unposting deletes the entries
implementation_evidence: observed
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
states:
  - name: current
    meaning: The unreversed generation associated with the batch.
  - name: reversed
    meaning: >-
      A retired generation retained to explain when and how its accounting was
      taken back.
transitions:
  - from: current
    to: reversed
    trigger: Ordinary recovery retires the active posting.
    guard: >-
      The generation passes guarded deletion and ordinary batch-unposting
      checks.
    side_effects:
      - Remove its ledger headers and lines through the deletion function.
      - Retire its Open revenue plans.
      - Restore its batch to Open.
    who: An administrator executing an unposting job
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/integration/services/posting/unpost-deletes-entries.integration.spec.js#keeps
      the posting history: the first posting reversed, the second current
  - from: current
    to: reversed
    trigger: Fiscal reopening retires a system generation.
    guard: >-
      The caller period is Closed or Reopening and the posting is a system
      generation.
    side_effects:
      - Append mirrored accounting lines while retaining the original entries.
      - Restore the fiscal system batch to Open.
    who: Fiscal reopening through Batch Management functions
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/integration/services/close/reverse-system-batch.integration.spec.js#writes
      a reversing entry for each entry, debits and credits swapped, and keeps
      the originals
invalid:
  - from: reversed
    to: current
    why: >-
      A later posting creates a new generation; it does not reactivate the
      retired history row.
concurrency: >-
  The partial unique index rejects a second unreversed generation for the same
  batch. Retirement occurs while the batch is locked by the caller transaction.
  Repeating the system reversal function for an already reversed posting reports
  no new reversal.
evidence:
  - >-
    services-accounting at f47cb263: modules/batches/db/batch-postings.table.ts;
    services/persistence/batch-postings.ts, subledger-entries.ts;
    services/engine/unpost-batches.ts; services/close/system-batches.ts;
    docs/accounting/batches.md
---

# Posting Record

## The Lifecycle

```mermaid
stateDiagram-v2
  [*] --> current: generation recorded
  current --> reversed: ordinary deletion recovery
  current --> reversed: fiscal mirror recovery
  reversed --> [*]
```

## Transitions Explained

Retirement preserves the generation history while ordinary and fiscal recovery treat ledger rows differently. The transition rows identify the removal or mirror path and its transaction guard.

## What Cannot Happen

A retired generation remains history. A fresh posting has its own generation record, and the guarded ordinary deletion path refuses a generation containing reversal entries.

## Concurrency and Timing

Batch locking and the current-generation uniqueness constraint protect retirement and replacement. The history row is not a substitute for the durable job outcome.
