Domain Model
Tất cả schema do
@nx/coresở hữu. Taxation chỉ subclass repository. Root nguồn:packages/core/src/models/schemas/tax/và.../pricing/.
1. ERD đầy đủ
2. Entities
TaxGroup
| Thuộc tính | Giá trị |
|---|---|
| Bảng | tax.TaxGroup |
| Nguồn | packages/core/src/models/schemas/tax/tax-group/schema.ts |
| Soft-delete | có |
| Cột Owner ID | merchantId (nullable — null = template hệ thống) |
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Khóa chính |
identifier | text | ✓ | Snowflake | Code ổn định unique (vd VN_DEDUCTION_VAT10) |
name | jsonb | ✓ | — | i18n { default, en, vi } |
description | jsonb | — | i18n | |
taxMethod | text | ✓ | — | 000_DIRECT / 100_DEDUCTION |
status | text | ✓ | ACTIVATED | Xem enum bên dưới |
merchantId | text | — | Merchant sở hữu; null = system |
Enum status: ACTIVATED · DEACTIVATED · ARCHIVED (Statuses).
Index: UQ_TaxGroup_identifier, IDX_TaxGroup_merchantId, IDX_TaxGroup_merchantId_status.
Quan hệ: merchant (M:1 → Merchant.id), items (1:M → TaxGroupItem, relationName taxGroup).
TaxGroupItem
| Thuộc tính | Giá trị |
|---|---|
| Bảng | tax.TaxGroupItem |
| Nguồn | packages/core/src/models/schemas/tax/tax-group-item/schema.ts |
| Soft-delete | có |
| Cột Owner ID | — (sở hữu qua parent taxGroupId) |
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Khóa chính |
taxGroupId | text | ✓ | — | Group cha |
discriminationTypeId | text | ✓ | — | Phân loại thuế (VAT/PIT/...) qua DiscriminationType |
type | text | ✓ | 100_PERCENTAGE | TaxMode |
value | decimal(15,4) | 0 | Tỷ lệ hoặc số tiền | |
isInclusive | boolean | ✓ | false | Thuế nhúng trong giá |
priority | integer | 0 | Thứ tự áp dụng (tăng dần) | |
usage | text | ✓ | 000_SALE | 000_SALE / 100_PURCHASE |
chargeTarget | text | ✓ | 000_CUSTOMER | 000_CUSTOMER / 100_MERCHANT |
Index: IDX_TaxGroupItem_discriminationTypeId, IDX_TaxGroupItem_taxGroupId.
Quan hệ: taxGroup (M:1 → TaxGroup.id), discriminationType (M:1 → DiscriminationType.id).
TaxSet
| Thuộc tính | Giá trị |
|---|---|
| Bảng | pricing.TaxSet |
| Nguồn | packages/core/src/models/schemas/pricing/tax-set/schema.ts |
| Soft-delete | có |
| Cột Owner ID | — (đa hình principalId/principalType) |
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Khóa chính |
principalId | text | ✓ | — | id Product hoặc ProductVariant |
principalType | text | ✓ | — | Product (mặc định) / ProductVariant (override) |
name | jsonb | — | i18n (copy từ TaxGroup) | |
status | text | ✓ | ACTIVATED | Xem enum bên dưới |
sourceType | text | — | TaxGroup (auto) / null (thủ công) | |
sourceId | text | — | TaxGroup.id khi auto-provision |
Enum status: ACTIVATED · DEACTIVATED · ARCHIVED.
Index: partial unique UPI_TaxSet_principalId_principalType_status (where deletedAt IS NULL), IDX_TaxSet_sourceId, IDX_TaxSet_sourceType_sourceId, IDX_TaxSet_status.
Tax
| Thuộc tính | Giá trị |
|---|---|
| Bảng | pricing.Tax |
| Nguồn | packages/core/src/models/schemas/pricing/tax/schema.ts |
| Soft-delete | có |
| Cột Owner ID | — (sở hữu qua taxSetId) |
Trường (liên quan provisioning):
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Khóa chính |
name | jsonb | ✓ | — | i18n (từ DiscriminationType / group) |
type | text | ✓ | 100_PERCENTAGE | TaxMode |
value | decimal(15,4) | ✓ | — | Tỷ lệ hoặc số tiền |
effectiveFrom | timestamptz | ✓ | thời điểm provision | Khi thuế bắt đầu active |
effectiveTo | timestamptz | — | Hết hạn (null = mở) | |
priority | integer | 0 | Thứ tự áp dụng | |
isInclusive | boolean | ✓ | false | Nhúng trong giá |
shouldApplyOnDiscounted | boolean | ✓ | true | Áp trên base đã giảm giá |
isCompound | boolean | ✓ | true | Cộng dồn trên thuế trước |
usage | text | ✓ | 000_SALE | Sale/purchase |
status | text | ✓ | ACTIVATED | — |
chargeTarget | text | ✓ | 000_CUSTOMER | Customer/merchant |
taxSetId | text | ✓ | — | Set cha |
taxTypeId | text | — | FK legacy tới TaxType (dual-write, nullable) | |
discriminationTypeId | text | ✓ | — | Phân loại (authoritative) |
Index: IDX_Tax_discriminationTypeId, IDX_Tax_status, IDX_Tax_taxSetId, IDX_Tax_taxTypeId.
TaxType
| Thuộc tính | Giá trị |
|---|---|
| Bảng | pricing.TaxType |
| Nguồn | packages/core/src/models/schemas/pricing/tax-type/schema.ts |
| Soft-delete | có |
| Cột Owner ID | merchantId (nullable) |
Enum type (FixedTaxTypes): 000_VAT, 100_EXCISE, 200_ENVIRONMENTAL, 300_LUXURY, 400_PIT, 999_CUSTOM. chargeTarget: 000_CUSTOMER / 100_MERCHANT. Unique (type, merchantId). Phân loại legacy — DiscriminationType (scope tax_classification) giờ là authoritative; Tax.taxTypeId giữ làm fallback dual-write (ADR-0002).
Bảng tham chiếu VN (read-only)
| Bảng | Cột key | FK |
|---|---|---|
tax.VnAdministrativeUnit | codeName (UK), fullName, shortName (+ *_en) | — |
tax.VnProvince | code (UK), name, fullName, administrativeUnitCode | → VnAdministrativeUnit.codeName |
tax.VnWard | code (UK), name, provinceCode, administrativeUnitCode | → VnProvince.code, VnAdministrativeUnit.codeName |
Phục vụ read-only; không được seed bởi migration của package này (load bên ngoài).
3. Bất biến xuyên entity
| Bất biến | Cách thực thi |
|---|---|
Nhiều nhất một TaxSet ACTIVATED cho mỗi (principalId, principalType) | Partial unique index UPI_TaxSet_principalId_principalType_status (where not deleted) |
| Provision idempotent cho cùng source group | TaxProvisioningService skip khi TaxSet active có sourceType=TaxGroup và sourceId khớp |
| Chỉ TaxSet có nguồn TaxGroup bị deactivate khi (de)provision | _deactivateExistingTaxSet guard trên sourceType === TaxGroup |
Merchant taxMethod phải khớp TaxGroup.taxMethod | TaxGroupService.validateTaxGroupForMerchant (DIRECT ≠ DEDUCTION) |
Mỗi Tax được provision phản chiếu một TaxGroupItem | provisionForProduct lặp các item của group |
4. Hành vi Soft-delete
| Hành vi | Chi tiết |
|---|---|
| Đọc mặc định | deletedAt IS NULL (model defaultFilter) |
| Deprovision | Lật status thành DEACTIVATED — không phải soft-delete (dòng tích lũy) |
| Hard-delete | Không bao giờ theo mặc định |
| Restore | Re-provision tạo một TaxSet ACTIVATED mới; các dòng DEACTIVATED trước vẫn còn |