Skip to content

Domain Model

Schema source: packages/core/src/models/schemas/finance/. PostgreSQL schema finance, 6 tables.

1. Full ERD

2. Entities

FinanceAccount

PropertyValue
Tablefinance.FinanceAccount
Sourcepackages/core/src/models/schemas/finance/finance-account/
Soft-deleteyes
Owner ID columnmerchantId

Fields:

FieldTypeRequiredDefaultDescription
idtextSnowflakePrimary key
merchantIdtextOwner merchant
statustextACTIVATEDSee status enum
namei18n{ default, en, vi }
typetextSee account-type enum
providertextBANABANA / VNPAY / e-wallet / bank
productCodetexte.g. QR_MMS, PHONE_POS, INTERNAL_ACCOUNT
accountNumber / accountHoldertextBank/e-wallet identity
detailsjsonbType-specific (branch, swift, last4, drawer…)
unittextVNDAccount currency
currentBalancenumeric(15,4)0Running balance maintained per posting
postingSequenceLastValuebigint0Monotonic per-account ledger cursor
environmenttextproductionEnv partition
metadatajsonbisDefault, isExcluded, isPayable, isInternal

Account-type enum (FinanceAccountTypes):

ValueMeaning
100_CASHPhysical cash drawer
200_BANKBank account
300_QR_CODEQR acceptance (e.g. VNPAY QR_MMS)
400_MOBILE_POSMobile/phone POS terminal (e.g. VNPAY PHONE_POS)
998_COGSInternal control — cost of goods sold (isInternal=true)
999_INVENTORYInternal control — inventory asset (isInternal=true)

Status enum: ACTIVATED · DEACTIVATED · ARCHIVED.

Key indexes:

NameColumnsType
UPI_FinanceAccount_…provider_productCode_accountNumber_environmentpartialUnique (live, provider+number present)
UPI_FinanceAccount_merchantId_type_environmentpartialUnique default per merchant/type (metadata.isDefault=true)
IDX_FinanceAccount_merchantId(_status / _provider_environment)Btree

FinanceVoucher

PropertyValue
Tablefinance.FinanceVoucher
Sourcepackages/core/src/models/schemas/finance/finance-voucher/
Soft-deleteyes
Owner ID columnmerchantId

Fields (selected):

FieldTypeRequiredDefaultDescription
typetextRECEIPT / PAYMENT / TRANSFER / ADJUSTMENT
statustextDRAFTDRAFT / ISSUED / VOIDED
voucherNumbertextAssigned on issue (PT/PC/PCK/PKT + seq)
amountdecimal(15,4)Denormalized header total
unittextVNDVoucher currency (all lines share it)
partyTypetextCUSTOMER / VENDOR / EMPLOYEE / INTERNAL / EXTERNAL
partyId / partyName / partyAddresstext/i18npartyName ✓Party snapshot at issue
reasoni18nNarrative
sourceTypetextMANUAL / SALE_ORDER / SALE_ORDER_REFUND / PURCHASE_ORDER / POS_SESSION / INVENTORY_ADJUSTMENT
sourceIdtextOriginating document id
sourceEventUidtextPer-event idempotency key (Kafka redelivery)
sessionIdtextPOS shift attribution
transactionDatetimestamptzAccounting date
reversalVoucherIdtextCounter voucher on void
issuedAt/issuedBy/voidedAt/voidedBy/voidReasonLifecycle audit
metadatajsonbdraftLines, adjustmentReason, inventoryIssuance, inventoryAdjustment, refundOf

Type / direction mapping:

Voucher typeLine direction ruleNumber prefix
RECEIPT (Phiếu thu)all lines DEBIT (inferred)PT
PAYMENT (Phiếu chi)all lines CREDIT (inferred)PC
TRANSFER (Phiếu chuyển khoản)explicit per line; DEBIT sum == CREDIT sumPCK
ADJUSTMENT (Phiếu kế toán)explicit per linePKT

Key indexes:

NameColumnsType
UPI_…merchantId_voucherNumberpartialUnique number per merchant (live)
UPI_…merchantId_type_sourceType_sourceIdpartialPer-source dedup (excludes MANUAL/POS_SESSION/SALE_ORDER)
UPI_…sourceType_sourceEventUidpartialPer-event dedup (live, non-void)

FinanceTransaction (ledger line)

PropertyValue
Tablefinance.FinanceTransaction
Sourcepackages/core/src/models/schemas/finance/finance-transaction/
Soft-deleteyes
Owner ID columnmerchantId

Fields (selected):

FieldTypeRequiredDefaultDescription
typetext100_DEBIT (balance up) / 200_CREDIT (balance down)
statustextCOMPLETEDPENDING / COMPLETED / CANCELLED
amountdecimal(15,4)Line amount
unittextVNDCurrency (= account unit)
exchangeRatedecimal(19,6)1Reserved (single-currency, always 1)
financeAccountIdtextAccount affected
financeCategoryIdtextOptional classification
financeVoucherIdtextParent voucher
lineNumberintegerPosition within voucher
balanceBefore / balanceAfterdecimal(15,4)Snapshot at post time
postingSequencebigintPer-account monotonic posting cursor
referenceType / referenceIdtextPolymorphic source link (mirrors voucher source)

Key index: UPI_FinanceTransaction_financeAccountId_postingSequence (partial unique — one line per account posting cursor).

FinanceCategory

PropertyValue
Tablefinance.FinanceCategory
Sourcepackages/core/src/models/schemas/finance/finance-category/
Soft-deleteyes
Owner ID columnmerchantId (null = system category)

Fields: identifier (unique), type (100_INCOME / 200_EXPENSE), name/description (i18n), parentId (hierarchy), status. See Configuration → Seeded Data for the 14 system categories.

FinanceVoucherSequence

PropertyValue
Tablefinance.FinanceVoucherSequence
Sourcepackages/core/src/models/schemas/finance/finance-voucher-sequence/
Unique(merchantId, type, yearMonth)

Per-merchant / per-type / per-month counter. lastValue (bigint) is incremented under lock to mint the next voucher number.

PaymentIntegration

PropertyValue
Tablefinance.PaymentIntegration
Sourcepackages/core/src/models/schemas/finance/payment-integration/
Soft-deleteyes
Owner ID columnprincipalId (Merchant / SaleChannel)

Fields (selected): financeAccountId, principalType (Merchant/SaleChannel), provider, productCode, integrationType (100_PAYMENT_PROVIDER / 200_MERCHANT_PAYMENT_CONFIG / 300_TERMINAL_PAYMENT_CONFIG), credential (encrypted), maskedValue, config (jsonb), status (ACTIVATED / ARCHIVED). A partial unique index enforces one ACTIVATED row per full credential coordinate.

3. Cross-entity Invariants

InvariantEnforcement
A voucher is balanced: TRANSFER Σdebit == Σcredit; PAYMENT-with-asset-leg balancesFinanceVoucherService.assertLineDirectionForVoucherType (1e-4 tolerance)
Every voucher line shares the voucher's currencyassertLinesCurrencyConsistent + per-line check in postLines
account.currentBalance == running sum of its line signed amountspostLines computes balanceAfter; adjustAccountState persists
postingSequenceLastValue strictly increases per accountadjustAccountState guard + partial UPI on (financeAccountId, postingSequence)
One default account per (merchant, type, environment)Partial unique index where metadata.isDefault=true
Auto vouchers are idempotent under Kafka redeliveryPer-source / per-event partial unique indexes + tryIdempotentReplay
INVENTORY + COGS control accounts exist before COGS posting_ensureInternalControlAccounts on merchant CDC; handler throws if missing

4. Soft-delete Behavior

BehaviorDetail
Read defaultdeletedAt IS NULL (model defaultFilter)
Hard-deleteOnly deleteDraft removes a DRAFT voucher; issued vouchers are never deleted (void instead)
RestoreNot exposed
Dedup indexesAll scoped WHERE deletedAt IS NULL so a soft-deleted row never blocks re-insert

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