Domain Model
Nguồn schema:
packages/core/src/models/schemas/finance/. PostgreSQL schemafinance, 6 bảng.
1. ERD đầy đủ
2. Entities
FinanceAccount
| Thuộc tính | Giá trị |
|---|---|
| Bảng | finance.FinanceAccount |
| Nguồn | packages/core/src/models/schemas/finance/finance-account/ |
| Soft-delete | có |
| Cột Owner ID | merchantId |
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Khóa chính |
merchantId | text | ✓ | — | Merchant sở hữu |
status | text | ✓ | ACTIVATED | Xem enum status |
name | i18n | ✓ | — | { default, en, vi } |
type | text | ✓ | — | Xem enum loại tài khoản |
provider | text | ✓ | BANA | BANA / VNPAY / e-wallet / bank |
productCode | text | — | vd QR_MMS, PHONE_POS, INTERNAL_ACCOUNT | |
accountNumber / accountHolder | text | — | Định danh bank/e-wallet | |
details | jsonb | — | Theo từng loại (branch, swift, last4, drawer…) | |
unit | text | ✓ | VND | Tiền tệ tài khoản |
currentBalance | numeric(15,4) | ✓ | 0 | Số dư chạy duy trì theo mỗi hạch toán |
postingSequenceLastValue | bigint | ✓ | 0 | Con trỏ ledger đơn điệu theo từng tài khoản |
environment | text | ✓ | production | Phân vùng env |
metadata | jsonb | — | isDefault, isExcluded, isPayable, isInternal |
Enum loại tài khoản (FinanceAccountTypes):
| Giá trị | Ý nghĩa |
|---|---|
100_CASH | Ngăn kéo tiền mặt vật lý |
200_BANK | Tài khoản ngân hàng |
300_QR_CODE | Chấp nhận QR (vd VNPAY QR_MMS) |
400_MOBILE_POS | Terminal POS di động/điện thoại (vd VNPAY PHONE_POS) |
998_COGS | Kiểm soát nội bộ — giá vốn hàng bán (isInternal=true) |
999_INVENTORY | Kiểm soát nội bộ — tài sản tồn kho (isInternal=true) |
Enum status: ACTIVATED · DEACTIVATED · ARCHIVED.
Index chính:
| Tên | Cột | Loại |
|---|---|---|
UPI_FinanceAccount_…provider_productCode_accountNumber_environment | partial | Unique (live, có provider+number) |
UPI_FinanceAccount_merchantId_type_environment | partial | Unique default theo merchant/type (metadata.isDefault=true) |
IDX_FinanceAccount_merchantId(_status / _provider_environment) | — | Btree |
FinanceVoucher
| Thuộc tính | Giá trị |
|---|---|
| Bảng | finance.FinanceVoucher |
| Nguồn | packages/core/src/models/schemas/finance/finance-voucher/ |
| Soft-delete | có |
| Cột Owner ID | merchantId |
Trường (chọn lọc):
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
type | text | ✓ | — | RECEIPT / PAYMENT / TRANSFER / ADJUSTMENT |
status | text | ✓ | DRAFT | DRAFT / ISSUED / VOIDED |
voucherNumber | text | — | Gán khi issue (PT/PC/PCK/PKT + seq) | |
amount | decimal(15,4) | ✓ | — | Tổng header denormalized |
unit | text | ✓ | VND | Tiền tệ phiếu (mọi dòng dùng chung) |
partyType | text | ✓ | — | CUSTOMER / VENDOR / EMPLOYEE / INTERNAL / EXTERNAL |
partyId / partyName / partyAddress | text/i18n | partyName ✓ | — | Snapshot party khi issue |
reason | i18n | — | Diễn giải | |
sourceType | text | ✓ | — | MANUAL / SALE_ORDER / SALE_ORDER_REFUND / PURCHASE_ORDER / POS_SESSION / INVENTORY_ADJUSTMENT |
sourceId | text | — | id chứng từ gốc | |
sourceEventUid | text | — | Idempotency key theo từng-event (Kafka redeliver) | |
sessionId | text | — | Quy gán ca POS | |
transactionDate | timestamptz | ✓ | — | Ngày kế toán |
reversalVoucherId | text | — | Phiếu đối ứng khi void | |
issuedAt/issuedBy/voidedAt/voidedBy/voidReason | — | — | Audit vòng đời | |
metadata | jsonb | — | draftLines, adjustmentReason, inventoryIssuance, inventoryAdjustment, refundOf |
Map type / direction:
| Loại phiếu | Quy tắc hướng dòng | Tiền tố số |
|---|---|---|
RECEIPT (Phiếu thu) | mọi dòng DEBIT (suy ra) | PT |
PAYMENT (Phiếu chi) | mọi dòng CREDIT (suy ra) | PC |
TRANSFER (Phiếu chuyển khoản) | explicit theo dòng; Σ DEBIT == Σ CREDIT | PCK |
ADJUSTMENT (Phiếu kế toán) | explicit theo dòng | PKT |
Index chính:
| Tên | Cột | Loại |
|---|---|---|
UPI_…merchantId_voucherNumber | partial | Unique số theo merchant (live) |
UPI_…merchantId_type_sourceType_sourceId | partial | Dedup theo từng-source (loại trừ MANUAL/POS_SESSION/SALE_ORDER) |
UPI_…sourceType_sourceEventUid | partial | Dedup theo từng-event (live, không void) |
FinanceTransaction (dòng ledger)
| Thuộc tính | Giá trị |
|---|---|
| Bảng | finance.FinanceTransaction |
| Nguồn | packages/core/src/models/schemas/finance/finance-transaction/ |
| Soft-delete | có |
| Cột Owner ID | merchantId |
Trường (chọn lọc):
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
type | text | ✓ | — | 100_DEBIT (balance lên) / 200_CREDIT (balance xuống) |
status | text | ✓ | COMPLETED | PENDING / COMPLETED / CANCELLED |
amount | decimal(15,4) | ✓ | — | Số tiền dòng |
unit | text | ✓ | VND | Tiền tệ (= unit tài khoản) |
exchangeRate | decimal(19,6) | ✓ | 1 | Dự trữ (một tiền tệ, luôn 1) |
financeAccountId | text | ✓ | — | Tài khoản bị ảnh hưởng |
financeCategoryId | text | — | Phân loại tùy chọn | |
financeVoucherId | text | — | Phiếu cha | |
lineNumber | integer | — | Vị trí trong phiếu | |
balanceBefore / balanceAfter | decimal(15,4) | — | Snapshot tại thời điểm post | |
postingSequence | bigint | — | Con trỏ post đơn điệu theo từng tài khoản | |
referenceType / referenceId | text | — | Liên kết source đa hình (phản chiếu source phiếu) |
Index chính: UPI_FinanceTransaction_financeAccountId_postingSequence (partial unique — một dòng cho mỗi con trỏ post của tài khoản).
FinanceCategory
| Thuộc tính | Giá trị |
|---|---|
| Bảng | finance.FinanceCategory |
| Nguồn | packages/core/src/models/schemas/finance/finance-category/ |
| Soft-delete | có |
| Cột Owner ID | merchantId (null = danh mục hệ thống) |
Trường: identifier (unique), type (100_INCOME / 200_EXPENSE), name/description (i18n), parentId (phân cấp), status. Xem Configuration → Dữ liệu Seed cho 14 danh mục hệ thống.
FinanceVoucherSequence
| Thuộc tính | Giá trị |
|---|---|
| Bảng | finance.FinanceVoucherSequence |
| Nguồn | packages/core/src/models/schemas/finance/finance-voucher-sequence/ |
| Unique | (merchantId, type, yearMonth) |
Counter theo từng merchant / từng type / từng tháng. lastValue (bigint) được tăng dưới lock để tạo số phiếu tiếp theo.
PaymentIntegration
| Thuộc tính | Giá trị |
|---|---|
| Bảng | finance.PaymentIntegration |
| Nguồn | packages/core/src/models/schemas/finance/payment-integration/ |
| Soft-delete | có |
| Cột Owner ID | principalId (Merchant / SaleChannel) |
Trường (chọn lọc): 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). Một partial unique index thực thi một dòng ACTIVATED cho mỗi tọa độ credential đầy đủ.
3. Bất biến xuyên entity
| Bất biến | Cách thực thi |
|---|---|
Phiếu cân bằng: TRANSFER Σdebit == Σcredit; PAYMENT-with-asset-leg cân bằng | FinanceVoucherService.assertLineDirectionForVoucherType (sai số 1e-4) |
| Mọi dòng phiếu dùng chung tiền tệ của phiếu | assertLinesCurrencyConsistent + kiểm tra theo dòng trong postLines |
account.currentBalance == tổng chạy số tiền có dấu của các dòng của nó | postLines tính balanceAfter; adjustAccountState lưu |
postingSequenceLastValue tăng nghiêm ngặt theo tài khoản | guard adjustAccountState + partial UPI trên (financeAccountId, postingSequence) |
Một tài khoản mặc định cho mỗi (merchant, type, environment) | Partial unique index nơi metadata.isDefault=true |
| Phiếu tự động idempotent khi Kafka redeliver | Partial unique index theo từng-source / từng-event + tryIdempotentReplay |
| Tài khoản kiểm soát INVENTORY + COGS tồn tại trước khi hạch toán COGS | _ensureInternalControlAccounts khi merchant CDC; handler throw nếu thiếu |
4. Hành vi Soft-delete
| Hành vi | Chi tiết |
|---|---|
| Đọc mặc định | deletedAt IS NULL (model defaultFilter) |
| Hard-delete | Chỉ deleteDraft xóa một phiếu DRAFT; phiếu đã issue không bao giờ bị xóa (void thay thế) |
| Restore | Không expose |
| Index dedup | Tất cả scope WHERE deletedAt IS NULL để một dòng soft-delete không bao giờ chặn re-insert |