Skip to content

PRD: Vouchers & posting

ModuleFinance (CORE-12)PRD IDPRD-VCH-001
StatusIn-progressOwnerFinance squad
Date2026-05-22Versionv0.1
Packages@nx/finance · @nx/core · @nx/commerceURDVCH · WAL

TL;DR

Lets a merchant record every money event as a balanced double-entry voucher instead of a loose running total per wallet. Typed money accounts (cash, bank, QR, mobile-POS) plus internal control accounts are seeded per merchant, and a payment integration matrix routes a paid payment to the right account and auto-issues the correct voucher. The result: sales, purchases, and stock movements post into the books automatically, idempotently, and with a link back to their source document.

1. Context & Problem

A merchant's books must record every money event as a balanced bookkeeping document - not a single running total mutated per wallet. The legacy finance-wallet model could only hold a number per wallet: it could not express a receipt vs. a payment, could not balance debits against credits, and offered no internal control accounts for inventory value or cost of goods sold. That makes the books unauditable and blocks any reliable income/expense reporting for the HKD/SME bookkeeping KICKO targets.

Why now: the payment refactor needs a place for sale payments to land in the books. A typed account schema plus a payment integration routing layer turns each paid payment into a correctly-routed, balanced voucher. This is the foundation of the Finance module's double-entry model.

2. Goals & Non-Goals

Goals

  • Replace the legacy wallet model with a typed account schema (cash, bank, QR, mobile-POS) plus internal control accounts, seeded per merchant.
  • Record money events as balanced vouchers with debit/credit ledger lines through the voucher service.
  • Drive postings from the payment integration so a paid payment auto-issues the correct voucher against the routed account and category.
  • Link each voucher to its source document and keep automatic postings idempotent under repeated events.

Non-Goals

  • Budget tracking, P&L, and cash-flow forecasting (URD Non-Goals).
  • Recurring / scheduled expense automation.
  • Receipt-image capture / OCR.
  • Multi-currency conversion within a single voucher - single currency, default VND.

3. Success Metrics

MetricTarget / signal
Auto-posting coverage100% of succeeded sale payments produce a matching receipt voucher
Balance integrityEvery issued voucher balances (debits equal credits where both sides apply); zero unbalanced postings
IdempotencyA replayed payment event never creates a second voucher
TraceabilityEvery auto-issued voucher links back to its source document (sale order, PO, stock movement)
Account seedingEvery new merchant gets its default and internal control accounts

4. Personas & Use Cases

PersonaGoal in this feature
OwnerSee money events posted as balanced vouchers against the right accounts, with categories applied
Accountant / ManagerTrust the books to reflect sales and purchases without manual data entry
System (payment integration)Route a paid payment to the correct account/category and issue the voucher automatically

Core scenarios: a new merchant is seeded with typed default and control accounts → a sale payment succeeds → the payment integration routes it to the right account and category → a balanced receipt voucher is auto-issued, linked to the source order → a replayed event is ignored (idempotent).

5. User Stories

  • As an owner, I want each new merchant to start with its default and internal control accounts, so the books are ready before the first sale.
  • As an accountant, I want every money event recorded as a balanced voucher with debit/credit lines, so the books are auditable double-entry, not a running total.
  • As an owner, I want a paid sale to auto-issue a receipt voucher against the routed account, so I don't record routine revenue by hand.
  • As the system, I want a payment integration matrix to map a payment to the correct account and category, so postings route deterministically.
  • As an accountant, I want each voucher linked to its source document, so I can trace a posting back to its order or movement.
  • As the system, I want automatic postings to be idempotent, so a repeated payment event never double-posts.

6. Functional Requirements

#RequirementURD ref
FR-1Hold typed money accounts (cash, bank, QR, mobile-POS) plus internal control accounts, owned per merchantURD-WAL-001
FR-2Seed each merchant's default and internal control accounts on merchant creationURD-WAL-003
FR-3Maintain a default account per type per merchant for routing automatic postingsURD-WAL-002
FR-4Record money events as vouchers of type receipt, payment, transfer, or adjustmentURD-VCH-001
FR-5Every voucher is balanced - debits equal credits where both sides applyURD-VCH-002
FR-6Auto-issue a receipt voucher when a sale payment succeeds, classified as SaleURD-VCH-003
FR-7Auto-issue a payment voucher to the vendor when a purchase order is receivedURD-VCH-004
FR-8Auto-post cost of goods sold and stock adjustments to internal control accounts on stock movementURD-VCH-005
FR-9Automatic postings are idempotent - a repeated event never double-postsURD-VCH-006
FR-10Link each voucher to its source document (sale order, PO, stock movement, manual)URD-VCH-007
FR-11Number each issued voucher per merchant, per type, per monthURD-VCH-008
FR-12Support a manual lifecycle (draft → issue, delete a draft), account transfers, and void-by-reversalURD-VCH-009..011

Full requirement text and acceptance criteria live in the Finance URD. This PRD references them rather than restating them.

7. Non-Functional Requirements

AreaRequirement
Data integrityA voucher and its ledger lines are written together; every voucher balances (debits equal credits where both sides apply)
IdempotencyAutomatic postings are keyed off the source event so a replayed event never double-posts
ImmutabilityIssued vouchers are never hard-deleted; corrections happen by balanced reversal (void), preserving the original
Tenancy & authzAccounts and vouchers are merchant-scoped (x-merchant-id); non-admins see only granted merchants' accounts
PrecisionMonetary math uses float(value, 4); single currency per voucher, default VND
i18nUser-facing labels/types/categories are bilingual ({ en, vi })

8. UX & Flows

Posting is event-driven, not screen-driven: the merchant takes a payment in apps/client, and the receipt voucher is issued automatically by the finance worker. Manual account/voucher and transfer screens are emerging in P2.

9. Data & Domain

EntityRole
finance-accountA typed money account (cash, bank, QR, mobile-POS) or internal control account; holds a running balance
VoucherOne balanced bookkeeping document for a money event (receipt, payment, transfer, adjustment), linked to its source
LedgerLineA debit or credit row inside a voucher, carrying account, optional category, amount, and balance-before/after snapshot
payment-integrationThe routing matrix that maps a paid payment to the correct account and category
CategoryThe income/expense classification applied to a voucher

Conceptual only - full schema, enums, and invariants in the finance domain model.

10. Dependencies & Assumptions

Depends on

  • Payment (@nx/core payment integration) - a succeeded sale payment is the trigger that routes and posts the receipt voucher.
  • Commerce / Merchant (@nx/commerce) - a new merchant gets its default and internal control accounts reconciled automatically.
  • Inventory (@nx/inventory) - purchase-order receipts and stock movements drive payment/cost-of-goods vouchers.
  • Categories (URD-CAT) - system categories classify auto-generated vouchers.

Assumptions

  • The merchant's internal control accounts exist before cost-of-goods postings are attempted.
  • Exactly one default account per type per merchant exists for deterministic routing.
  • Payment events carry enough context (amount, method, source document) to route and link the voucher.

11. Risks & Open Questions

Risk / questionMitigation / status
A replayed payment event could double-postAutomatic postings are idempotent - keyed off the source event
Cost-of-goods postings before control accounts existControl accounts are seeded on merchant creation; posting requires them present
Reverting/voiding a voucher tied to an already-settled paymentVoid is by balanced reversal preserving the original; compensation path tracked as the feature matures
Single-currency onlyOut of scope; documented as a constraint (default VND)
PO-receipt, stock-movement auto-postings, transfers, and void are still maturingTracked under the same VCH feature as they land; account redesign + payment-driven posting are the foundation

12. Release Plan & Launch Criteria

AspectPlan
PhaseP1 (foundation) - see URD feature catalog
RolloutAll merchants; no feature flag
MigrationAccount schema redesign (finance-walletfinance-account); default and internal control accounts seeded per merchant
Launch criteriaPaid sale auto-issues a balanced, linked receipt voucher; replayed event does not double-post; new merchant gets its seeded accounts
MonitoringAuto-posting volume and error rate, unbalanced-voucher checks, idempotency-skip counts

13. FAQ

What replaced the legacy wallet model? A typed finance-account schema with account-type constants plus internal control accounts (inventory, cost of goods sold), seeded per merchant.

Does a merchant record sales revenue by hand? No - a succeeded sale payment auto-issues a receipt voucher, classified as Sale, against the routed account.

How does a payment know which account to post to? A payment-integration matrix routes a paid payment to the correct account and category; the finance worker then issues the voucher.

What stops a replayed event from double-posting? Automatic postings are idempotent - a repeated event never creates a second voucher.

Can an issued voucher be deleted? No - issued vouchers are voided by a balanced reversal that preserves the original for audit.

References

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