Skip to content

ADR-0001. Double-entry voucher + ledger model

FieldValue
StatusAccepted
Date2026-04-22
Decidersfinance-team
Supersedes

Context

  • The earlier model recorded money movement as flat FinanceTransaction rows with an INCOME/EXPENSE/TRANSFER type and a single account — there was no document grouping, no counter-leg, and no audit-ready slip.
  • Vietnamese POS bookkeeping expects recognizable documents: Phiếu thu (receipt), Phiếu chi (payment), Phiếu chuyển khoản (transfer), Phiếu kế toán (adjustment), each with a per-merchant running number.
  • COGS and inventory-asset movements need a balanced counter-leg (DEBIT one account, CREDIT another) — impossible with single-sided transactions.
  • Account balances must be reconstructable and tamper-evident.

Decision

We will model finance as double-entry: a FinanceVoucher header (type RECEIPT / PAYMENT / TRANSFER / ADJUSTMENT) owns N FinanceTransaction lines, each a DEBIT (100_DEBIT, balance up) or CREDIT (200_CREDIT, balance down) against one FinanceAccount.

Line direction is inferred for RECEIPT (all DEBIT) and PAYMENT (all CREDIT), and explicit + balanced for TRANSFER (Σdebit == Σcredit) and ADJUSTMENT. Numbers are minted per (merchantId, type, yearMonth) via FinanceVoucherSequence with prefixes PT/PC/PCK/PKT. Each posted line snapshots balanceBefore/balanceAfter and a monotonic postingSequence; the account row is FOR UPDATE-locked for the whole post.

Consequences

ProsCons
Audit-ready documents with stable per-merchant numbersMore tables + a posting engine to maintain
Balanced counter-legs enable COGS / inventory-asset accountingCallers of TRANSFER/ADJUSTMENT must supply balanced lines
Per-line balance snapshot + posting sequence make the ledger tamper-evidentVoucher posting is a multi-row transaction (lock contention under load)
Voids become balanced reversals, never destructive editsSingle-currency enforced per voucher (multi-currency deferred)

Alternatives Considered

OptionProsConsWhy rejected
Flat single-sided transactions (prior model)SimplestNo documents, no counter-leg, no COGS supportCannot represent balanced postings
Full general-ledger with chart-of-accountsAccounting-completeOverkill for SMB POS; steep operator UXToo heavy for current scope
Event-sourced ledger (append events, project balances)Strong auditNew infra; balance projection latencypostingSequence + snapshot already gives audit without the rebuild cost

References

  • packages/core/src/services/finance/finance-voucher.service.ts (_performIssue, postLines, resolveLineDirection, computeHeaderAmount)
  • packages/core/src/models/schemas/finance/finance-voucher/, finance-transaction/, finance-voucher-sequence/
  • Domain Model

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.