URD: Finance
| Module | CORE-12 | Version | v0.6 |
|---|---|---|---|
| Status | In-progress | Date | 2026-06-15 |
Business documentation. This URD is Finance's feature list - each feature below is one Functional Area (
<AREA>). The same<AREA>keys the feature's PRDs (PRD-<AREA>-NNN) and tests (TC-<AREA>-NNN), and each feature is listed in the Delivery feature catalog. See the Feature Spine convention.
1. Purpose
Define the user-facing requirements for Finance - the merchant's books. The module must track money across accounts (cash, bank, QR, mobile-POS), record every movement as a balanced double-entry voucher, classify income and expense by category, and create most postings automatically in response to sales, purchases, and stock movements so owners do not record routine money flows by hand.
2. Scope
| Included | Excluded |
|---|---|
| Money accounts / wallets (Cash, Bank, QR, Mobile-POS) | Payment-gateway processing (Payment module) |
| Internal control accounts (Inventory, Cost of Goods Sold) | Tax invoice issuance (Invoice module) |
| Vouchers: receipt, payment, transfer, adjustment | Stock quantity & costing math (Inventory module) |
| Ledger lines (debit / credit) per voucher | Budget tracking, P&L, cash-flow forecast (future) |
| Income / expense categories (seeded + custom) | Recurring / scheduled expenses (future) |
| Auto posting from sale payment, PO receipt, stock movement | Receipt-image capture / OCR (future) |
| Voucher void by balanced reversal | Multi-currency conversion (single currency per voucher) |
3. Definitions
| Term | Definition |
|---|---|
| Account (wallet) | A place money lives - a cash drawer, bank account, QR acceptance account, or mobile-POS terminal. Holds a running balance. |
| Internal control account | A non-cash bookkeeping account (Inventory, Cost of Goods Sold) the system maintains automatically; not user money. |
| Voucher | One bookkeeping document recording a money event: a receipt (money in), payment (money out), transfer (between accounts), or adjustment. |
| Ledger line | One debit or credit row inside a voucher. A voucher always balances: total debits equal total credits where both sides apply. |
| Category | The income/expense classification on a movement (e.g. Sale, Purchase, Rent). |
| Default account | The account a given type of automatic posting routes to by default - one per type per merchant. |
| Void | Cancelling an issued voucher by posting an equal-and-opposite reversal, preserving the original for audit. |
4. Conceptual Model
Conceptual only - the full schema, enums, and invariants live in the finance developer domain model.
5. Feature Catalog
The feature list of this module. Each row is one feature (a Functional Area). Detail in §6. Mirrored in the Delivery feature catalog.
| Feature ID | Feature | Phase | Status | Priority |
|---|---|---|---|---|
WAL | Accounts | P1 | In-progress | High |
VCH | Vouchers & Posting | P1 | In-progress | High |
EVT | Event-Driven Posting | P1 | Built | High |
TXN | Ledger Lines | P1 | Built | High |
CAT | Categories | P1 | Built | High |
LDG | Partner ledger & P&L | P2 | Planned | High |
Status: live from Plane where mapped, otherwise registry-declared. Vocabulary mirrors Plane (state-group / phase).
6. Features
One sub-section per feature, in catalog order. Each feature keeps its description, requirements, and acceptance together. Priority = MoSCoW (Must / Should / Could / Won't).
WAL - Accounts In-progress
Feature ID: expenses/WAL · Phase: P1 · PRDs: - · Dev: @nx/finance
What it does for users: merchants hold money across cash drawers, bank, QR, and mobile-POS accounts - each with a running balance and a default per type - while the system auto-creates default and internal control accounts for every new merchant.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-WAL-001 | M | Hold accounts of types Cash, Bank, QR, and Mobile-POS, each owned by one merchant |
| URD-WAL-002 | M | Maintain a default account per type per merchant for routing automatic postings |
| URD-WAL-003 | M | Auto-create each merchant's default and internal control accounts when the merchant is created |
| URD-WAL-004 | M | Track each account's running current balance as vouchers post |
| URD-WAL-005 | S | Allow account currency (default VND) |
| URD-WAL-006 | S | Restrict account visibility to merchants the user is granted (admins see all) |
Acceptance
AC-WAL-01: Default & control accounts on new merchant
| Given | When | Then |
|---|---|---|
| A merchant is created | The merchant event is reconciled | The merchant's default accounts are seeded |
| Internal Inventory and Cost-of-Goods-Sold control accounts exist |
VCH - Vouchers & Posting In-progress
Feature ID: expenses/VCH · Phase: P1 · PRDs: - · Dev: @nx/finance
What it does for users: every money event is recorded as a balanced voucher - receipts auto-issued when sales are paid, payments when purchase orders are received, and cost-of-goods postings on stock movement - with manual transfers, draft lifecycle, and void-by-reversal for owners.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-VCH-001 | M | Record money events as vouchers of type receipt, payment, transfer, or adjustment |
| URD-VCH-002 | M | Every voucher is balanced - debits equal credits where both sides apply |
| URD-VCH-003 | M | Auto-issue a receipt voucher when a sale payment succeeds, classified as Sale |
| URD-VCH-004 | M | Auto-issue a payment voucher to the vendor when a purchase order is received |
| URD-VCH-005 | M | Auto-post cost of goods sold and stock adjustments to internal control accounts on stock movement |
| URD-VCH-006 | M | Automatic postings are idempotent - a repeated event never double-posts |
| URD-VCH-007 | M | Link each voucher to its source document (sale order, purchase order, stock movement, manual) |
| URD-VCH-008 | S | Number each issued voucher per merchant, per type, per month |
| URD-VCH-009 | S | Support a manual lifecycle: draft → issue, and delete a draft before issue |
| URD-VCH-010 | S | Transfer money between two accounts as a single balanced transfer voucher |
| URD-VCH-011 | S | Void an issued voucher via a balanced reversal; the original is preserved |
| URD-VCH-012 | C | Attribute a voucher to a POS shift session for shift cash movements |
Acceptance
AC-VCH-01: Auto receipt on paid sale
| Given | When | Then |
|---|---|---|
| A sale order is paid | The payment-succeeded event arrives | A receipt voucher is issued, classified as Sale, linked to the sale order |
| The receiving account's balance goes up by the amount | ||
| The same event arrives again | Processed a second time | No second voucher is created (idempotent) |
AC-VCH-02: Auto payment on purchase-order receipt
| Given | When | Then |
|---|---|---|
| A purchase order is received | The PO-received event arrives | A payment voucher is issued to the vendor, linked to the purchase order |
| Inventory value is positive | The voucher balances a stock asset leg against the cash leg(s) |
AC-VCH-03: Transfer between accounts
| Given | When | Then |
|---|---|---|
| Two accounts of the merchant | Owner transfers an amount A → B | One transfer voucher posts a debit and a credit of equal value |
| Account A goes down and Account B goes up by the same amount |
AC-VCH-04: Void by reversal
| Given | When | Then |
|---|---|---|
| An issued voucher | Owner voids it | A balanced reversal voucher is posted; the original is preserved |
| Affected account balances return to their pre-voucher state |
EVT - Event-Driven Posting Built
Feature ID: finance/EVT · Phase: P1 · PRDs: PRD-EVT-001 · Dev: @nx/finance
What it does for users: Finance keeps the books complete without manual entry by reacting to what already happens - a paid sale, a received purchase order, stock issued for a sale, an inventory value change, a new merchant - and posting the matching voucher off a message stream, asynchronously. Taking a payment never waits on the books; a momentary Finance outage catches up rather than loses entries; and a replayed event never double-posts.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-EVT-001 | M | Finance consumes operational events asynchronously; the originating sale / purchase / stock action is never blocked on a posting |
| URD-EVT-002 | M | Subscribe to sale payment succeeded, purchase order received, inventory issued for sale, inventory adjusted, and merchant created |
| URD-EVT-003 | M | A succeeded sale payment posts a receipt voucher, plus a change-return payment voucher when cash tendered exceeds the order total |
| URD-EVT-004 | M | A received purchase order posts a vendor payment voucher, with an inventory asset leg when received value is positive |
| URD-EVT-005 | M | Stock issued for a sale posts a cost-of-goods voucher when the cost basis is positive |
| URD-EVT-006 | M | An operator-driven inventory value change posts a single-line inventory adjustment voucher |
| URD-EVT-007 | M | A new merchant seeds its default and internal control accounts from the merchant lifecycle event |
| URD-EVT-008 | M | Every automatic posting is idempotent - a redelivered event replays the existing voucher, never a second one |
| URD-EVT-009 | M | An event is acknowledged only after its posting commits; a failed handler leaves the event for redelivery (at-least-once) |
| URD-EVT-010 | S | Events carry only identifiers; the worker resolves the full source and party identity (vendor, PO code, accounts) before posting |
| URD-EVT-011 | S | Postings short-circuit with no voucher when there is nothing to book (no finance routing, zero cost basis, zero value delta, no change due) |
| URD-EVT-012 | S | The consumer drains in-flight work and closes cleanly on a shutdown signal |
Acceptance
AC-EVT-01: Async posting does not block the sale
| Given | When | Then |
|---|---|---|
| A sale order is paid | The payment-succeeded event is published | The sale completes immediately without waiting on Finance |
| The Finance consumer reads the event | A balanced receipt voucher is posted, linked to the sale order |
AC-EVT-02: Idempotent redelivery
| Given | When | Then |
|---|---|---|
| An event already produced its voucher | The same event is delivered again | The existing voucher is replayed - no second voucher is created |
AC-EVT-03: At-least-once on handler failure
| Given | When | Then |
|---|---|---|
| A money-relevant event is delivered | The handler fails before the posting commits | The event is not acknowledged and is redelivered |
| The event is redelivered | The handler succeeds | The voucher is posted exactly once |
AC-EVT-04: Cost-of-goods on stock issued
| Given | When | Then |
|---|---|---|
| Stock is issued for a sale with a positive cost basis | The inventory-issued event arrives | A cost-of-goods voucher posts against the inventory control account |
| The cost basis is zero | The event arrives | No voucher is posted; the skip is logged |
AC-EVT-05: Clean short-circuit when nothing to book
| Given | When | Then |
|---|---|---|
| A payment carries no finance routing, or change due is zero | The event arrives | No voucher is posted and no error is raised |
| An inventory change has zero value delta | The event arrives | No voucher is posted; the skip is logged |
TXN - Ledger Lines Built
Feature ID: expenses/TXN · Phase: P1 · PRDs: - · Dev: @nx/finance
What it does for users: each voucher posts debit and credit ledger lines against accounts, carrying the affected account, an optional category, an amount, a balance-before/after snapshot, and a traceability link to the source document.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-TXN-001 | M | Each voucher posts debit/credit ledger lines against accounts |
| URD-TXN-002 | M | A line carries the account it affects, an optional category, an amount, and balance-before/after snapshot |
| URD-TXN-003 | M | Lines share the voucher's currency |
| URD-TXN-004 | S | A line may reference its source document for traceability |
Acceptance
AC-TXN-01: Balanced ledger lines
| Given | When | Then |
|---|---|---|
| A voucher is posted | Ledger lines are written | Each line carries its account, amount, and balance-before/after snapshot |
| Lines share the voucher's currency |
CAT - Categories Built
Feature ID: expenses/CAT · Phase: P1 · PRDs: - · Dev: @nx/finance
What it does for users: money movements are classified as income or expense using 14 seeded system categories applied automatically to auto-generated vouchers, with merchants free to add custom categories nested under a parent.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-CAT-001 | M | Provide 14 seeded system categories spanning income and expense |
| URD-CAT-002 | M | Categories are typed income or expense |
| URD-CAT-003 | M | Apply a system category automatically to auto-generated vouchers (e.g. Sale, Purchase) |
| URD-CAT-004 | S | Merchants may add custom categories, nested under a parent |
Acceptance
AC-CAT-01: System categories on auto vouchers
| Given | When | Then |
|---|---|---|
| An auto-generated voucher (e.g. Sale, Purchase) | It is issued | A matching system category is applied automatically |
| A merchant adds a custom category under a parent | Saved | It is available for classifying movements |
LDG - Partner Ledger & P&L Planned
Feature ID: finance/LDG · Phase: P2 (Jul-Aug) · PRDs: PRD-LDG-001 · Dev: @nx/finance
What it does for users: merchants see who owes them and whom they owe - one partner ledger, receivables (customers) and payables (vendors) - and whether they actually made money, via a P&L for any period.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-LDG-001 | M | Customer receivables are tracked (a charge sale raises debt; a receipt settles it) |
| URD-LDG-002 | M | Vendor payables work end-to-end (raised on PO receipt; settled by payment voucher) |
| URD-LDG-003 | M | One partner-ledger view: by partner, both directions, running balances |
| URD-LDG-004 | M | A P&L report for a period (revenue − COGS − expenses) |
| URD-LDG-005 | S | Receivables/payables settle via finance vouchers |
| URD-LDG-006 | S | Ledger filters by partner type and period |
Acceptance
AC-LDG-01: Two-way ledger + P&L
| Given | When | Then |
|---|---|---|
| A B2B sale on credit and a received PO | Owner opens the partner ledger | Customer debt and vendor debt both show with correct balances |
| A month of trading | Owner opens P&L | Revenue, COGS and expenses reconcile to the accounts |
7. Constraints & Non-Goals
Constraints
| ID | Constraint |
|---|---|
| C-01 | Every voucher must balance (debits equal credits where both sides apply) |
| C-02 | Exactly one default account per type per merchant |
| C-03 | Automatic postings must be idempotent under repeated events |
| C-04 | All ledger lines in a voucher share the voucher's currency (single-currency; default VND) |
| C-05 | The 14 seeded system categories are not merchant-owned |
| C-06 | Records use soft-delete; issued vouchers are never hard-deleted - they are voided |
| C-07 | Internal control accounts must exist before cost-of-goods postings |
Non-Goals
- Budget tracking and variance
- Recurring / scheduled expense automation
- Profit & loss statement and cash-flow forecasting
- Receipt-image capture / OCR
- Multi-currency conversion within a single voucher
8. Version History
| Date | Author | Description | Ver |
|---|---|---|---|
| 2026-02-26 | P. Do - Product Owner | Initial user stories | v0.1 |
| 2026-04-16 | P. Do - Product Owner | Wallets, transactions, categories scope | v0.3 |
| 2026-05-30 | Docs migration | Restructured to verified double-entry voucher model (accounts / vouchers / ledger lines / categories); corrected account types and transaction model | v0.4 |
| 2026-06-04 | Claude (AI pair) | Reorganize by feature (Feature Spine) | v0.5 |
| 2026-06-15 | Finance squad | Add EVT Event-Driven Posting feature (sale → GL seam) | v0.6 |