Finance Service
@nx/finance is the bookkeeping engine. It owns accounts (wallets) and a double-entry ledger built from journal vouchers (RECEIPT / PAYMENT / TRANSFER / ADJUSTMENT), each posting balanced DEBIT/CREDIT FinanceTransaction lines. It is event-driven first: most postings are created automatically by reacting to Kafka topics from sale, inventory, and merchant CDC — direct REST is reserved for manual vouchers, account management, and reporting.
1. Quick Reference
| Property | Value |
|---|---|
| Package | @nx/finance |
| Code | SVC-00040-FINANCE |
| Type | Microservice |
| Runtime | Bun |
| Base Class | VerifierApplication |
| Location | packages/finance |
| Base Path | /v1/api/finance |
| Dev Port | 31040 |
| Container Port | 3000 (external 31040) |
| Snowflake ID | 4 |
| DB Schema | finance (6 tables) |
| Binding Namespace | @nx/finance |
2. Purpose & Scope
| Included | Excluded |
|---|---|
| Accounts / wallets (CASH, BANK, QR_CODE, MOBILE_POS) + internal control accounts (INVENTORY, COGS) | Payment provider/gateway processing (owned by @nx/mq-pay) |
| Double-entry vouchers (RECEIPT / PAYMENT / TRANSFER / ADJUSTMENT) with DEBIT/CREDIT ledger lines | Sale-order lifecycle (owned by @nx/sale) |
Per-merchant/type voucher numbering (PT/PC/PCK/PKT) | Stock quantities / costing math (owned by @nx/inventory) |
| Income/expense category hierarchy (14 seeded system categories) | Tax invoice issuance (owned by @nx/invoice) |
Auto-posting from PAYMENT_SUCCESS, PURCHASE_ORDER_RECEIVED, INVENTORY_ISSUED_FOR_SALE, INVENTORY_ADJUSTED | Loyalty / customer points (owned by @nx/sale) |
| Default-account reconciliation on merchant CDC + onboarding step | Multi-currency conversion (single-currency per voucher; exchangeRate reserved) |
| Payment-integration credential storage (masked, per-merchant/terminal) | — |
| Voucher void via balanced reversal | — |
3. Tech Stack
External:
| Library | Purpose |
|---|---|
@venizia/ignis | IoC container, DI, BaseService, BaseController, ControllerFactory |
@venizia/ignis-helpers | Logger, KafkaProducerHelper / KafkaConsumerHelper, WebSocketEmitter, float() |
@platformatic/kafka | Kafka client (producer + consumer) |
hono | HTTP server (via IGNIS) |
@hono/zod-openapi | OpenAPI generator from Zod schemas |
@scalar/hono-api-reference | OpenAPI explorer at /doc |
drizzle-orm | DB access via PostgresCoreDataSource |
pg | PostgreSQL driver |
lodash | Utilities |
Internal:
| Package | Purpose |
|---|---|
@nx/core | Schemas, repositories (re-exported), VerifierApplication, FinanceVoucherService, FinanceAccountService, PaymentIntegrationService, KafkaTopics/CDCKafkaTopics, all finance constants |
@nx/asset | ApplicationAssetBanksVNComponent — Vietnamese bank reference data |
4. Project Structure
packages/finance/
├── src/
│ ├── application.ts # VerifierApplication subclass
│ ├── index.ts # bootstrapApplication()
│ ├── migrate.ts # bootstrapMigration()
│ ├── common/
│ │ ├── keys.ts # BindingKeys (Kafka producer/consumer, Redis cache)
│ │ ├── rest-paths.ts # 5 RestPaths
│ │ └── types.ts # local types
│ ├── components/
│ │ ├── kafka/ # ApplicationKafkaComponent (producer + 5-topic consumer)
│ │ └── websocket/ # ApplicationWebSocketComponent + FinanceSocketEventService
│ ├── controllers/ # 5 controller folders (account, category, transaction, voucher, payment-integration)
│ ├── datasources/ # PostgresCoreDataSource
│ ├── migrations/processes/ # 3 migrations (categories, permissions, role-permissions)
│ ├── models/ # zod request/response schemas
│ ├── repositories/ # re-exports from @nx/core
│ ├── resources/ # app-info.json, banner.txt
│ └── services/ # FinanceWorkerService, FinanceIntegrationService
├── package.json
└── tsconfig.json5. Architecture
Detail: see Architecture.
6. Domain Snapshot
Full ERD + per-entity tables: see Domain Model.
7. Surface Summary
REST controllers — full reference rendered live from /v1/api/finance/doc/openapi.json (live spec — Scalar viewer at /doc, gateway portal):
| Controller | Base path | Notes |
|---|---|---|
FinanceAccountController | /finance-accounts | CRUD + overview; merchant-scoped via Casbin GROUP policies (non-admins see only granted merchants) |
FinanceCategoryController | /finance-categories | CRUD (read-oriented; system + custom categories) |
FinanceTransactionController | /finance-transactions | CRUD (ledger lines; mostly read) |
FinanceVoucherController | /finance-vouchers | Read-only CRUD (count/find/findById/findOne) + custom actions: createDraft, updateDraft, deleteDraft, approve, issueDirect, voidVoucher, overview |
PaymentIntegrationController | /payment-integrations | Provider / merchant-config / terminal-config; credentials masked in responses |
Async surface — full reference in API Events:
| Direction | Channel | Count |
|---|---|---|
| Inbound | Kafka (4 domain + 1 CDC) | 5 topics |
| Outbound | Kafka | 0 (producer initialized for future use; nothing published) |
| Outbound | WebSocket | emitter registered (finance-ws-emitter); no domain topics wired yet |
8. Components
| Component | File | Purpose |
|---|---|---|
ApplicationKafkaComponent | src/components/kafka/component.ts | Idempotent producer (acks=ALL, lz4) + consumer (autocommit off, fallback latest) subscribed to 5 topics, dispatched to FinanceWorkerService |
ApplicationWebSocketComponent | src/components/websocket/component.ts | Redis-backed WebSocketEmitter (single or cluster) + FinanceSocketEventService |
ApplicationAssetBanksVNComponent | from @nx/asset | Vietnamese bank reference data |
| Cache Redis | useCacheRedis() (parent) | Authorization permission cache |
9. Services
| Service | File | One-liner |
|---|---|---|
FinanceWorkerService | src/services/finance-worker.service.ts | Kafka event handlers — turns sale/inventory/merchant events into vouchers; reconciles default + control accounts on merchant CDC |
FinanceIntegrationService | src/services/finance-integration.service.ts | Masks payment-integration credentials in account responses; builds/attaches integration bundles |
FinanceVoucherService | @nx/core | Double-entry posting engine: issueDirect, draft lifecycle, void (balanced reversal), getOverview, balance/posting-sequence math |
FinanceAccountService | @nx/core | Account create-with-opening-balance, update, getOverview |
PaymentIntegrationService | @nx/core | Payment-integration CRUD + credential encryption |
10. Repositories
All re-exported from
@nx/core(src/repositories/index.ts). Owned tables in thefinanceschema; the rest are cross-package reads.
| Repository | Table / Source | Notes |
|---|---|---|
FinanceAccountRepository | finance.FinanceAccount | + assertAccountsOwnedByMerchant |
FinanceTransactionRepository | finance.FinanceTransaction | ledger lines |
FinanceCategoryRepository | finance.FinanceCategory | system + custom |
FinanceVoucherRepository | finance.FinanceVoucher | + next-number lookups, findExistingForSource(Event), getOverview |
FinanceVoucherSequenceRepository | finance.FinanceVoucherSequence | + next (per merchant/type/yearMonth) |
PaymentIntegrationRepository | finance.PaymentIntegration | masked credentials |
ConfigurationRepository | @nx/core | reads DEFAULT_FINANCE_ACCOUNTS system config |
MerchantRepository / OrganizerRepository | @nx/core | merchant CDC reconciliation + onboarding step |
PurchaseOrderRepository / VendorRepository | @nx/core | resolve PO + vendor party for PAYMENT voucher |
PermissionRepository / RoleRepository / PolicyDefinitionRepository | @nx/core | seeds + merchant-scoped account filtering |
11. Entry Points
| File | Purpose |
|---|---|
src/index.ts | Service entry → bootstrapApplication() |
src/migrate.ts | Migration entry → bootstrapMigration() |
src/application.ts | Application extends VerifierApplication |
12. Configuration
Env vars + seeded data: see Configuration.
13. Operations
Deployment + observability + security + runbook: see Operations.
14. Related Pages
Concepts — why/how:
- Architecture
- Domain Model
- Integration — sale / inventory / commerce CDC / identity contracts
Reference — lookup:
- API Events
- Configuration
- Operations
- REST endpoints — live OpenAPI at
/v1/api/finance/doc/openapi.json(live spec — Scalar viewer at/doc, gateway portal)
Decisions: