Skip to content

Architecture

1. System Context (C4 L1)

2. Container View (C4 L2)

3. Component View (C4 L3) — Internal Layering

LayerResponsibility
ComponentsKafka producer/consumer, WebSocket emitter, VN bank assets
ControllersAuth + Casbin gate + DTO mapping; voucher custom actions; account merchant-scoping
WorkerFinanceWorkerService — maps inbound events to issueDirect calls
Core servicesFinanceVoucherService (posting engine), FinanceAccountService, PaymentIntegrationService
RepositoriesDrizzle queries, soft-delete, balance/posting-sequence persistence (all from @nx/core)

4. State Machines Index

EntityStatesDiagram
FinanceVoucherDRAFT, ISSUED, VOIDED→ jump
FinanceTransactionPENDING, COMPLETED, CANCELLED→ jump
FinanceAccountACTIVATED, DEACTIVATED, ARCHIVED→ jump

FinanceVoucher

FromEventToGuards
createDraftDRAFTmanual source; lines buffered in metadata.draftLines
issueDirectISSUEDlines posted immediately; idempotent replay on dedup key
DRAFTapprove (issueDraft)ISSUEDdraft must own ≥1 line; promotes draft, assigns number
DRAFTdeleteDraftremovedonly while DRAFT
ISSUEDvoidVOIDEDnot COGS/INVENTORY_ADJUSTMENT; opening-balance only if no later activity; issues counter voucher

FinanceTransaction (ledger line)

In practice voucher lines are created directly COMPLETED. PENDING/CANCELLED exist in the enum for future flows.

FinanceAccount

5. Runtime Scenarios

5.1 Sale payment → RECEIPT voucher

StepDetail
1-2Idempotency via sourceEventUid = attempt.uid (SALE_ORDER per-event dedup)
3RECEIPT ⇒ line direction inferred DEBIT; balance increases
4Whole posting is a single DB transaction with SELECT … FOR UPDATE on the account

5.2 Purchase order received → PAYMENT voucher (with asset leg)

5.3 Inventory issued for sale → COGS posting

5.4 Merchant CDC → reconcile accounts + onboarding

6. Crosscutting Concerns

ConcernHow this service handles it
AuthNJWT (Issuer = identity), JWKS verified per request (VerifierApplication)
AuthZCasbin; account list/count filtered by per-merchant GROUP policies (assertMerchantAccess on single-entity routes)
i18njsonb columns, { default, en, vi } shape (account/category name, voucher party/reason)
LoggingStructured key-value (key: %s); @logged() on worker handlers
IdempotencyVoucher dedup: per-source (merchantId, type, sourceType, sourceId) and per-event (sourceType, sourceEventUid) partial unique indexes; issueDirect replays existing voucher on hit
Posting integritypostingSequenceLastValue strictly increases per account; balance computed in-tx with float(_, 4); account row FOR UPDATE locked
Soft-deleteSoftDeletableRepository (deletedAt)
IDsSnowflake via IdGenerator, worker 4

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