Domain Model
Nguồn schema:
packages/core/src/models/schemas/ledger/- package này không sở hữu schema Drizzle nào; nó re-export repository từ@nx/core. Mọi bảng nằm trong schema Postgresledger.
1. ERD đầy đủ
2. Entities
Ledger
| Thuộc tính | Giá trị |
|---|---|
| Table | ledger.Ledger |
| Source | core/src/models/schemas/ledger/ledger/schema.ts |
| Soft-delete | có |
| Owner ID column | merchantId |
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | PK |
type | text | ✓ | - | TLedgerIdentifierCode (S1a-HKD..S2e-HKD) |
status | text | ✓ | PENDING | Xem enum bên dưới |
period | text | ✓ | - | YYYY-MN / YYYY-QN / YYYY-Y (vd 2026-M3, 2026-Q1, 2026-Y) |
periodStart / periodEnd | timestamptz | ✓ | - | Biên kỳ |
merchantId | text | ✓ | - | Merchant sở hữu |
isCurrent | boolean | ✓ | true | Cờ version hiện tại |
version | numeric(_,1) | ✓ | 1.0 | Version revision |
previousVersionId | text | null | Version trước (đặt khi revise) | |
ledgerIdentifierId | text | ✓ | - | Soft ref tới LedgerIdentifier |
summary | jsonb | null | TLedgerSummary (tổng theo từng mẫu) | |
note | jsonb | null | note revision i18n { en, vi } |
Enum trạng thái (LedgerStatuses - tái dùng IGNIS Statuses):
| Giá trị | Mô tả |
|---|---|
PENDING | Mặc định; draft sửa được (bản ghi tạo mà chưa/chờ job) |
NEW | Bản ghi tạo kèm một job cần xử lý |
PROCESSING | Đang tạo |
COMPLETED | Đã tạo xong |
FAIL | Tạo thất bại |
SETTLED | Đã finalize; khoá version - phải revise để thay đổi |
ARCHIVED | Bị thay thế bởi bản tạo lại/revise; chỉ đọc |
Luồng active do người dùng điều khiển dùng
PENDING(draft) →SETTLED(finalize) →revise→PENDINGmới. Worker chỉ độngLedgerJob.status- phần ghiLedger.statusđã được comment trongLedgerWorkerService.
Index & ràng buộc:
| Tên | Cột | Loại |
|---|---|---|
PK_Ledger | id | Primary key |
UPQ_Ledger_* | merchantId, type, period, version | Unique partial (deleted_at IS NULL) |
UQ_Ledger_* | ledgerIdentifierId, merchantId, period, version | Unique |
IDX_Ledger_* | isCurrent · merchantId,period · merchantId,periodStart,periodEnd · merchantId,status · previousVersionId · status | Btree |
LedgerJob
| Thuộc tính | Giá trị |
|---|---|
| Table | ledger.LedgerJob |
| Source | core/src/models/schemas/ledger/ledger-job/schema.ts |
| Soft-delete | có |
| Owner ID column | - (qua ledgerId → Ledger) |
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | PK |
ledgerId | text | ✓ | - | Sổ sở hữu (soft ref) |
status | text | ✓ | PENDING | Xem enum bên dưới |
attemptCount | integer | ✓ | 0 | Số lần thử trọn đời; không reset khi retry |
processStartAt | timestamptz | - | Mốc phát hiện kẹt | |
processCompletedAt | timestamptz | - | - | |
failureReason | jsonb | - | { default, en?, vi?, errorCode } | |
enqueuedAt | timestamptz | ✓ | - | Lần enqueue đầu |
lastEnqueuedAt | timestamptz | - | Lần re-enqueue cuối |
Enum trạng thái (LedgerJobStatuses): DRAFT, PENDING, PROCESSING, COMPLETED, REJECTED (DRAFT = đã tạo nhưng chưa đẩy vào queue; luồng active chạy PENDING → PROCESSING → COMPLETED|REJECTED).
Index: IDX_LedgerJob_ledgerId, IDX_LedgerJob_status, IDX_LedgerJob_status_processStartAt (quét job kẹt), UPQ_LedgerJob_ledgerId (partial - một job đang chạy mỗi sổ khi status IN (PENDING, PROCESSING, DRAFT)).
LedgerSnapshot
| Thuộc tính | Giá trị |
|---|---|
| Table | ledger.LedgerSnapshot |
| Source | core/src/models/schemas/ledger/ledger-snapshot/schema.ts |
| Soft-delete | có (+ cột user-audit) |
| Owner ID column | - (qua ledgerId) |
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | PK |
ledgerId | text | ✓ | - | Sổ sở hữu; unique |
headerData | jsonb | - | TSnapshotHeaderData (businessName, taxCode, address…) | |
snapshotMeta | jsonb | - | Tổng hợp staleness theo loại (count, maxUpdatedAt) | |
pulledAt | timestamptz | ✓ | - | Thời điểm pull |
hasUnrecordedChange | boolean | ✓ | false | Cờ staleness (chặn finalize) |
lastChangeDetectedAt | timestamptz | - | - |
Index: UQ_LedgerSnapshot_ledgerId (một snapshot mỗi sổ).
LedgerSnapshotEntry
| Thuộc tính | Giá trị |
|---|---|
| Table | ledger.LedgerSnapshotEntry |
| Source | core/src/models/schemas/ledger/ledger-snapshot-entry/schema.ts |
| Soft-delete | có (+ cột user-audit) |
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | PK |
snapshotId | text | ✓ | - | Snapshot sở hữu |
rowIndex | integer | ✓ | - | Thứ tự hàng |
originalData | jsonb | null | Hàng nguồn; null với entry do người dùng thêm | |
currentData | jsonb | ✓ | - | Hàng đã sửa/hiệu lực |
Index: IDX_LedgerSnapshotEntry_snapshotId.
LedgerIdentifier
| Thuộc tính | Giá trị |
|---|---|
| Table | ledger.LedgerIdentifier |
| Source | core/src/models/schemas/ledger/ledger-identifier/schema.ts |
| Soft-delete | có |
Danh mục 6 mã mẫu sổ HKD. Seed một lần; được Ledger.type và Ledger.ledgerIdentifierId tham chiếu.
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | PK |
identifier | text | ✓ | - | TLedgerIdentifierCode (S1a-HKD..S2e-HKD); unique |
name | jsonb | ✓ | - | i18n { en, vi } |
description | jsonb | - | i18n |
Index: unique trên identifier.
TaxTier
| Thuộc tính | Giá trị |
|---|---|
| Table | ledger.TaxTier |
| Source | core/src/models/schemas/ledger/tax-tier/schema.ts |
| Soft-delete | có (+ cột user-audit) |
Nhóm dải doanh thu mà merchant thuộc về (TIRE_1..TIRE_4). Seed một lần.
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | PK |
code | text | ✓ | - | TTaxTierCode (100_TIRE_1..400_TIRE_4) |
name | jsonb | ✓ | - | i18n { en, vi } |
description | jsonb | - | i18n | |
status | text | ✓ | ACTIVATED | ACTIVATED / DEACTIVATED |
businessScale | text | ✓ | 000_HOUSEHOLD | TBusinessScaleType (000_HOUSEHOLD / 100_ENTERPRISE) |
revenueMin | decimal(15,4) | ✓ | 0 | Biên dưới của dải (VND) |
revenueMax | decimal(15,4) | null | Biên trên của dải (VND); null = không giới hạn |
Index: IDX_TaxTier_status, UQ_TaxTier_code (unique partial, deleted_at IS NULL).
LedgerTaxConfig
| Thuộc tính | Giá trị |
|---|---|
| Table | ledger.LedgerTaxConfig |
| Source | core/src/models/schemas/ledger/ledger-tax-config/schema.ts |
| Soft-delete | có (+ cột user-audit) |
Một phương pháp thuế khả dụng trong một bậc, kèm tập mẫu sổ mà nó yêu cầu. Seed một lần.
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | PK |
code | text | ✓ | - | TTaxApproachCode (100_EXEMPT, 201_PERCENTAGE_BOTH, 202_PERCENTAGE_VAT_INCOME_PIT) |
taxTierId | text | ✓ | - | Soft ref tới TaxTier |
name | jsonb | ✓ | - | i18n { en, vi } |
description | jsonb | - | i18n | |
requiredLedgers | jsonb | ✓ | [] | TLedgerIdentifierCode[] mà phương pháp này yêu cầu |
status | text | ✓ | ACTIVATED | ACTIVATED / DEACTIVATED |
metadata | jsonb | - | { isDefault, isSelectable } |
Index: IDX_LedgerTaxConfig_tax_tier_id, IDX_LedgerTaxConfig_status, UPQ_LedgerTaxConfig_tax_tier_id_code (unique partial, deleted_at IS NULL).
MerchantTaxConfig
| Thuộc tính | Giá trị |
|---|---|
| Table | ledger.MerchantTaxConfig |
| Source | core/src/models/schemas/ledger/merchant-tax-config/schema.ts |
| Soft-delete | có (+ cột user-audit) |
| Owner ID column | merchantId |
Lựa chọn onboarding thuế theo từng merchant theo từng năm. Schema và model đã được định nghĩa; package này chưa wire repository/service/controller.
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | PK |
merchantId | text | ✓ | - | Merchant sở hữu |
year | integer | ✓ | năm hiện tại | Năm config |
taxTierId | text | - | Soft ref tới TaxTier | |
ledgerTaxConfigId | text | - | Soft ref tới LedgerTaxConfig (phương pháp đã chọn) | |
filingPeriod | text | - | TPeriodType (MONTHLY / QUARTERLY / YEARLY) | |
declaredAnnualRevenue | decimal(15,4) | - | Doanh thu năm tự khai (VND) | |
onboardingStatus | text | ✓ | PENDING | PENDING / PARTIAL / COMPLETED |
disclaimerAcceptedAt | timestamptz | - | Thời điểm chấp nhận disclaimer | |
disclaimerAcceptedBy | text | - | Người chấp nhận (cột DB disclaimer_accepted_by) | |
disclaimerVersion | text | - | Phiên bản disclaimer đã chấp nhận | |
metadata | jsonb | - | { disclaimerHistory: { acceptedAt, version }[] } |
Index: IDX_MerchantTaxConfig_year, IDX_MerchantTaxConfig_merchant_id_onboarding_status_year, UPQ_MerchantTaxConfig_merchant_id_year (unique partial, deleted_at IS NULL).
3. Bất biến xuyên entity
| Bất biến | Cách thực thi |
|---|---|
Nhiều nhất một sổ current mỗi (merchantId, type, period, version) | Unique partial index + cờ isCurrent |
revise luôn tạo một hàng PENDING mới (version+1, isCurrent=false, đặt previousVersionId, yêu cầu nguồn SETTLED) | LedgerSnapshotService.revise |
| Đúng một snapshot mỗi sổ | UQ_LedgerSnapshot_ledgerId |
| Nhiều nhất một job đang chạy mỗi sổ | UPQ_LedgerJob_ledgerId (partial trên PENDING/PROCESSING/DRAFT) |
finalize bị chặn khi snapshot hasUnrecordedChange = true; đặt Ledger.status = SETTLED | guard LedgerSnapshotService.finalize |
Một phương pháp LedgerTaxConfig là duy nhất theo (taxTierId, code); requiredLedgers của nó quyết định các mẫu sổ merchant phải giữ | UPQ_LedgerTaxConfig_tax_tier_id_code + seed APPROACH_REQUIRED_LEDGERS |
Một MerchantTaxConfig mỗi (merchantId, year) | UPQ_MerchantTaxConfig_merchant_id_year |
Worker chỉ động LedgerJob.status - không bao giờ Ledger.status | LedgerWorkerService (ghi Ledger-status đã comment) |
4. Hành vi Soft-delete
| Hành vi | Chi tiết |
|---|---|
| Mặc định đọc | deletedAt IS NULL (mọi repo qua SoftDeletableRepository) |
| Hard-delete | Re-pull snapshot soft-delete entry + snapshot trước đó trước khi tạo lại |
| Unique index | Partial (WHERE deleted_at IS NULL) nên hàng đã soft-delete không chặn tạo lại |