PRD: Finance accounts & ledger
| Module | Finance (CORE-12) | PRD ID | PRD-WAL-001 |
| Status | In-progress | Owner | Finance squad |
| Date | 2026-06-04 | Version | v0.1 |
| Packages | @nx/finance · @nx/ledger | URD | WAL · TXN |
TL;DR
Gives a merchant a first-class home for its money: typed accounts (cash, bank, QR, mobile-POS) that carry a running balance, plus the internal control accounts (Inventory, Cost of Goods Sold) the system maintains for itself. Every voucher posts balanced debit/credit ledger lines against those accounts, each line carrying a balance-before/after snapshot - so an owner can trace where money lives and how each posting moved it.
1. Context & Problem
The Finance module (URD §1) tracks money across accounts, records every movement as a balanced double-entry voucher, and posts most movements automatically. None of that has a place to live until the accounts and ledger lines exist. Without typed accounts there is no running balance to route automatic postings to, and without ledger lines there is no per-account, balanced audit of how each voucher moved money.
This work lays the foundation: the accounts (WAL) that hold money and carry a running balance, and the ledger lines (TXN) every voucher posts against them. It also introduces non-cash internal control accounts (Inventory, Cost of Goods Sold) the system maintains for itself, so cost-of-goods and stock-adjustment postings have somewhere to land.
2. Goals & Non-Goals
Goals
- Hold typed accounts (Cash, Bank, QR, Mobile-POS), each owned by one merchant, each with a running balance.
- Maintain exactly one default account per type per merchant for routing automatic postings.
- Seed each merchant's default accounts and the internal Inventory / Cost-of-Goods-Sold control accounts when the merchant is created.
- Post debit/credit ledger lines against accounts, each carrying its account, amount, and a balance-before/after snapshot, in the voucher's currency.
- Link accounts to payment integrations (referenced, not auto-included).
Non-Goals
- Payment-gateway processing - owned by the Payment module; finance only links to integrations.
- The full voucher lifecycle and auto-posting rules (
VCH) and category taxonomy (CAT) - tracked by their own features. - Budget tracking, profit & loss, and cash-flow forecasting.
- Multi-currency conversion within a single voucher (single currency per voucher, default VND).
3. Success Metrics
| Metric | Target / signal |
|---|---|
| Account coverage | Every merchant has its default and internal control accounts after creation |
| Default uniqueness | Exactly one default account per type per merchant; zero duplicates |
| Balance integrity | Account running balance equals the sum of its posted ledger lines |
| Snapshot completeness | Every ledger line carries a balance-before/after snapshot in the voucher's currency |
4. Personas & Use Cases
| Persona | Goal |
|---|---|
| Owner | See where money lives across cash, bank, QR, and mobile-POS accounts and reconcile balances |
| Accountant / Manager | Read the per-account ledger and trace each movement to its source document |
| System (worker) | Auto-seed accounts on new merchant and post balanced ledger lines for every voucher |
Core scenarios: a merchant is created → default and internal control accounts are seeded → vouchers post debit/credit ledger lines against those accounts → each account's running balance updates and each line records a balance-before/after snapshot.
5. User Stories
- As an owner, I want typed accounts for cash, bank, QR, and mobile-POS, so I can see where my money lives and its running balance.
- As an owner, I want one default account per type, so automatic postings route to a predictable account.
- As the system, I want a new merchant's default and internal control accounts seeded automatically, so cost-of-goods and stock postings have somewhere to land from day one.
- As an accountant, I want each voucher to post balanced ledger lines against accounts with a balance snapshot, so I can audit how every movement changed an account.
- As an owner, I want an account to reference its payment integration, so a QR / mobile-POS account ties back to its gateway without auto-pulling integration data.
6. Functional Requirements
| # | Requirement | URD ref |
|---|---|---|
| FR-1 | Hold accounts of types Cash, Bank, QR, and Mobile-POS, each owned by one merchant | URD-WAL-001 |
| FR-2 | Maintain a default account per type per merchant for routing automatic postings | URD-WAL-002 |
| FR-3 | Auto-create each merchant's default and internal control accounts (Inventory, Cost of Goods Sold) when the merchant is created | URD-WAL-003 |
| FR-4 | Track each account's running current balance as vouchers post | URD-WAL-004 |
| FR-5 | Allow account currency (default VND) | URD-WAL-005 |
| FR-6 | Restrict account visibility to merchants the user is granted; admins see all | URD-WAL-006 |
| FR-7 | Each voucher posts debit/credit ledger lines against accounts | URD-TXN-001 |
| FR-8 | A line carries the account it affects, an optional category, an amount, and a balance-before/after snapshot | URD-TXN-002 |
| FR-9 | Ledger lines share the voucher's currency | URD-TXN-003 |
| FR-10 | A line may reference its source document for traceability | URD-TXN-004 |
Full requirement text and acceptance criteria live in the Finance URD. This PRD references them rather than restating them.
7. Non-Functional Requirements
| Area | Requirement |
|---|---|
| Data integrity | An account's running balance and the ledger lines that move it are written together; balance equals the sum of posted lines |
| Default uniqueness | Exactly one default account per type per merchant, enforced at the model level |
| Tenancy & authz | Accounts are merchant-scoped (x-merchant-id); non-admins see only granted merchants, admins see all |
| Precision | Monetary math uses float(value, 4); balances stored with the same precision |
| Consistency | Account seeding and ledger posting are idempotent and transactional; a repeated merchant event never double-seeds |
| i18n | User-facing labels/types are bilingual ({ en, vi }) |
8. UX & Flows
Key screens (in apps/client): account list / filter / form (incl. bank type), voucher select-account-by-currency, and the ledger table / filter / show page with a PDF view.
9. Data & Domain
| Entity | Role |
|---|---|
Account (finance-account) | A money account - type (Cash/Bank/QR/Mobile-POS), owning merchant, running balance, currency, isDefault per type, optional payment-integration relation |
| Internal control account | A non-cash account (Inventory, Cost of Goods Sold) the system maintains for cost/stock postings |
LedgerLine | A debit/credit row - affected account, optional category, amount, balance-before/after snapshot, voucher currency, source-document reference |
| Ledger config / job | Per-merchant ledger settings and the draft ledger job (with websocket progress) |
Conceptual only - full schema and invariants in the finance domain model.
10. Dependencies & Assumptions
Depends on
- Commerce (Merchant) - a new merchant's accounts are reconciled automatically on creation.
- Vouchers & posting (URD-VCH) - vouchers are what post ledger lines against accounts.
- Payment (
@nx/payment) - accounts reference payment integrations (linked, not auto-included).
Assumptions
- A merchant-created event is delivered to the finance worker so accounts can be seeded.
- Internal control accounts exist before any cost-of-goods posting is attempted.
11. Risks & Open Questions
| Risk / question | Mitigation / status |
|---|---|
| Account balance and ledger lines could diverge | Written transactionally; balance equals the sum of posted lines, snapshotted per line |
| Repeated merchant event could double-seed accounts | Seeding is idempotent - a repeated event never creates a second default account |
isDefault per-type uniqueness | Enforced at the model level after initial template/model fixes |
| No multi-currency within a voucher | Out of scope; single currency per voucher (default VND) |
12. Release Plan & Launch Criteria
| Aspect | Plan |
|---|---|
| Phase | P1 (foundation) - see URD feature catalog |
| Rollout | All merchants; no feature flag |
| Migration | Seed migration for default finance accounts; internal control accounts seeded per merchant |
| Launch criteria | New merchant gets default + control accounts; ledger lines post balanced with balance snapshots; account balance equals the sum of its lines |
| Monitoring | Per-merchant account count, default-uniqueness checks, balance-vs-ledger-sum consistency |
13. FAQ
What account types exist? Cash, Bank, QR, and Mobile-POS - plus non-cash internal control accounts (Inventory, Cost of Goods Sold) the system maintains for itself.
What is an internal control account? A non-cash bookkeeping account - not the merchant's money - that gives cost-of-goods and stock-adjustment postings a place to land.
How does a default account get chosen? Exactly one account per type per merchant is flagged default; automatic postings route to it.
What does a ledger line record? The affected account, an optional category, an amount, the voucher's currency, a balance-before/after snapshot, and an optional source-document reference.
Does linking an account to a payment integration pull integration data in? No - the relation is a reference; integrations are not auto-included in the account.
References
- URD: Finance - Accounts (
WAL) · Ledger Lines (TXN) - Related: Vouchers & Posting (
VCH) · Categories (CAT) - Module: Finance - overview + traceability
- Developer: @nx/finance · domain model