PRD: Finance categories
| Module | Finance | PRD ID | PRD-CAT-001 |
| Status | Ready to dev | FEAT | CAT |
| Epic | — | Plane | BANA-1496 |
| Date | 2026-05-25 | Version | v1.0 |
| Packages | @nx/finance | URD | CAT |
| Surface | Client · Owner/MgrBO · Ops | ||
| Owner | Phát Nguyễn | ||
TL;DR
Read the books by income vs expense and by reason (Sale, Purchase, Rent, Salary…). A fixed catalog of platform-owned system categories applies automatically to every auto-generated voucher, and merchants can add their own categories nested under a parent through a management screen - so every money movement carries a consistent, reportable label, with no unlabelled vouchers and no ad-hoc tagging.
1. Context & Problem
Every money movement needs a classification so owners can read their books by income vs expense and by reason (Sale, Purchase, Rent, Salary…). Without a catalog, auto-generated vouchers - receipts on paid sales, payments on purchase-order receipt - have no consistent label, and merchants can't group or report their movements. That blocks the basic bookkeeping read HKD/SME owners expect.
This increment delivers the category axis of the double-entry model: a fixed set the platform owns and applies automatically, plus merchant-defined categories nested under a parent, managed from a client screen.
2. Goals & Non-Goals
Goals
- Provide a fixed catalog of system categories spanning income and expense, owned by the platform (not merchant-scoped).
- Type each category as income or expense.
- Apply a system category automatically to auto-generated vouchers (Sale, Purchase, etc.) via stable fixed identifiers.
- Let merchants add custom categories nested under a parent, managed via a client screen with live preview, parent picker, and icon/badge display.
Non-Goals
- Budget tracking and variance (URD Non-Goal).
- Recurring / scheduled expense automation (URD Non-Goal).
- Profit & loss and cash-flow forecasting (URD Non-Goal).
- The voucher and ledger-line posting mechanics themselves - owned by areas
VCHandTXN.
3. Success Metrics
| Metric | Target / signal |
|---|---|
| Classification coverage | 100% of auto-generated vouchers carry a system category |
| Catalog stability | Re-running the seeder never duplicates a system category (idempotent by identifier) |
| Custom adoption | Merchants who add at least one custom category trends up |
| Reporting readiness | Movements group cleanly by income/expense and by category in reports |
4. Personas & Use Cases
| Persona | Goal in this feature |
|---|---|
| Owner | Read the books by income vs expense and by reason; tailor categories to the business |
| Accountant / Manager | Classify movements consistently for reporting |
| Platform | Apply a stable, known category to every automatic posting |
Core scenarios: the platform seeds a fixed catalog at startup → auto-generated vouchers resolve a stable system category by fixed identifier → an owner adds custom categories nested under a parent through the management screen → all movements report by income/expense and by category.
5. User Stories
- As an owner, I want money movements classified as income or expense, so that I can read my books at a glance.
- As an owner, I want auto-generated vouchers to carry a consistent system category, so that sales and purchases group correctly without manual tagging.
- As an owner, I want to add my own categories nested under a parent, so that classification fits how my business thinks about money.
- As an owner, I want a management screen with a live preview and icon picker, so that creating and editing categories is clear.
- As the platform, I want a fixed set of system categories the seeder can re-run safely, so that automatic postings always resolve a stable category.
6. Functional Requirements
| # | Requirement | URD ref |
|---|---|---|
| FR-1 | Provide a fixed catalog of seeded system categories spanning income and expense, platform-owned (not tied to any merchant); the seeder is idempotent (create-or-update by identifier/type) | URD-CAT-001 |
| FR-2 | Each category is typed income or expense | URD-CAT-002 |
| FR-3 | System categories are keyed by stable fixed identifiers (Sale, Purchase, Refund, …) so auto-generated vouchers resolve a stable system category automatically | URD-CAT-003 |
| FR-4 | Merchants may add custom categories nested under a parent, with icon metadata | URD-CAT-004 |
| FR-5 | Category management confined to the user's own merchant, authorized via finance category permissions | URD-CAT-004 |
| FR-6 | A client management screen for create / edit / list, with name input, parent picker, live preview, badge + icon set, and bilingual labels | URD-CAT-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 | System categories carry stable fixed identifiers; auto-posting resolves by identifier, never by name |
| Idempotency | The seeder is create-or-update by identifier/type - re-running never duplicates a system category |
| Tenancy & authz | System categories are platform-owned (not tied to any merchant); custom categories are confined to the user's own merchant and permission-gated |
| Performance / scale | Category catalog is small and cacheable; resolution by fixed identifier is a constant-time lookup |
| i18n | Category names and screen labels are bilingual (English and Vietnamese) |
8. UX & Flows
Key screens (in the merchant app): category list, create, and edit - with name input, parent picker, live preview, and a category badge + icon set.
9. Data & Domain
| Concept | Role |
|---|---|
| Finance category | The category record - type (income/expense), parent (nesting), icon metadata, ownership (platform-owned when not tied to a merchant) |
| Fixed system-category identifiers | Stable fixed identifiers (Sale, Purchase, Refund, …) used by auto-posting to resolve a system category |
| Income / expense type | The classification each category carries |
| Category seeder | Idempotent startup process that seeds the fixed system catalog |
Conceptual only - full schema and invariants in the finance domain model.
10. Dependencies & Assumptions
Depends on
- Vouchers & Posting (URD-VCH) - auto-generated vouchers consume categories.
- Ledger Lines (URD-TXN) - a ledger line carries the optional category.
Assumptions
- The fixed system categories cover the income/expense reasons automatic postings emit (Sale, Purchase, refunds, etc.).
- A merchant exists before creating custom merchant-scoped categories.
11. Risks & Open Questions
| Risk / question | Mitigation / status |
|---|---|
| Auto-posting could fail to resolve a category | Resolution is by stable fixed identifier, seeded idempotently at startup |
| Re-running the seeder could duplicate categories | Seeder is create-or-update by identifier/type - never duplicates |
| Deep custom-category nesting could complicate reports | Single parent level is the documented model; revisit if multi-level demand appears |
| Renaming a system category by hand | System categories are platform-owned, not merchant-editable |
12. Release Plan & Launch Criteria
| Aspect | Plan |
|---|---|
| Phase | P1 (foundation) - see URD feature catalog |
| Rollout | All merchants; no feature flag |
| Migration | None for merchants; the fixed system catalog is seeded at startup (idempotent) |
| Launch criteria | 14 system categories seeded with correct income/expense typing; auto-generated vouchers resolve a system category by fixed identifier; custom category create/edit/list works merchant-scoped |
| Monitoring | Auto-posting category-resolution failures; custom-category create rate; seeder idempotency on restart |
13. FAQ
Are system categories editable by a merchant? No - the fixed system categories are platform-owned (not tied to any merchant) and applied automatically; merchants add their own custom categories instead.
How does an auto-generated voucher pick a category? By a stable fixed identifier (Sale, Purchase, Refund, …), so the same event always resolves the same system category.
Can a custom category be nested? Yes - a custom category nests under a parent, with icon metadata for display.
Does running the seeder twice create duplicates? No - the seeder is idempotent (create-or-update by identifier/type).
What classifies as income vs expense? Each category is typed income or expense; the seeded catalog covers both (e.g. Sales Revenue, Refund Received as income; Purchase, Rent & Utilities, Salaries as expense).
References
- URD: Finance - Categories
- Related PRDs: Vouchers & Posting · Accounts & Ledger
- Module: Finance - URD
- Developer: @nx/finance · domain model