Domain Model
All schemas are owned by
@nx/core. Taxation only subclasses repositories. Source roots:packages/core/src/models/schemas/tax/and.../pricing/.
1. Full ERD
2. Entities
TaxGroup
| Property | Value |
|---|---|
| Table | tax.TaxGroup |
| Source | packages/core/src/models/schemas/tax/tax-group/schema.ts |
| Soft-delete | yes |
| Owner ID column | merchantId (nullable — null = system template) |
Fields:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Primary key |
identifier | text | ✓ | Snowflake | Unique stable code (e.g. VN_DEDUCTION_VAT10) |
name | jsonb | ✓ | — | i18n { default, en, vi } |
description | jsonb | — | i18n | |
taxMethod | text | ✓ | — | 000_DIRECT / 100_DEDUCTION |
status | text | ✓ | ACTIVATED | See enum below |
merchantId | text | — | Owner merchant; null = system |
Status enum: ACTIVATED · DEACTIVATED · ARCHIVED (Statuses).
Indexes: UQ_TaxGroup_identifier, IDX_TaxGroup_merchantId, IDX_TaxGroup_merchantId_status.
Relations: merchant (M:1 → Merchant.id), items (1:M → TaxGroupItem, relationName taxGroup).
TaxGroupItem
| Property | Value |
|---|---|
| Table | tax.TaxGroupItem |
| Source | packages/core/src/models/schemas/tax/tax-group-item/schema.ts |
| Soft-delete | yes |
| Owner ID column | — (owned via parent taxGroupId) |
Fields:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Primary key |
taxGroupId | text | ✓ | — | Parent group |
discriminationTypeId | text | ✓ | — | Classifies the tax (VAT/PIT/...) via DiscriminationType |
type | text | ✓ | 100_PERCENTAGE | TaxMode |
value | decimal(15,4) | 0 | Rate or amount | |
isInclusive | boolean | ✓ | false | Tax embedded in price |
priority | integer | 0 | Application order (ascending) | |
usage | text | ✓ | 000_SALE | 000_SALE / 100_PURCHASE |
chargeTarget | text | ✓ | 000_CUSTOMER | 000_CUSTOMER / 100_MERCHANT |
Indexes: IDX_TaxGroupItem_discriminationTypeId, IDX_TaxGroupItem_taxGroupId.
Relations: taxGroup (M:1 → TaxGroup.id), discriminationType (M:1 → DiscriminationType.id).
TaxSet
| Property | Value |
|---|---|
| Table | pricing.TaxSet |
| Source | packages/core/src/models/schemas/pricing/tax-set/schema.ts |
| Soft-delete | yes |
| Owner ID column | — (polymorphic principalId/principalType) |
Fields:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Primary key |
principalId | text | ✓ | — | Product or ProductVariant id |
principalType | text | ✓ | — | Product (default) / ProductVariant (override) |
name | jsonb | — | i18n (copied from TaxGroup) | |
status | text | ✓ | ACTIVATED | See enum below |
sourceType | text | — | TaxGroup (auto) / null (manual) | |
sourceId | text | — | TaxGroup.id when auto-provisioned |
Status enum: ACTIVATED · DEACTIVATED · ARCHIVED.
Indexes: partial unique UPI_TaxSet_principalId_principalType_status (where deletedAt IS NULL), IDX_TaxSet_sourceId, IDX_TaxSet_sourceType_sourceId, IDX_TaxSet_status.
Tax
| Property | Value |
|---|---|
| Table | pricing.Tax |
| Source | packages/core/src/models/schemas/pricing/tax/schema.ts |
| Soft-delete | yes |
| Owner ID column | — (owned via taxSetId) |
Fields (provisioning-relevant):
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Primary key |
name | jsonb | ✓ | — | i18n (from DiscriminationType / group) |
type | text | ✓ | 100_PERCENTAGE | TaxMode |
value | decimal(15,4) | ✓ | — | Rate or amount |
effectiveFrom | timestamptz | ✓ | provision time | When tax becomes active |
effectiveTo | timestamptz | — | Expiry (null = open) | |
priority | integer | 0 | Application order | |
isInclusive | boolean | ✓ | false | Embedded in price |
shouldApplyOnDiscounted | boolean | ✓ | true | Apply on discounted base |
isCompound | boolean | ✓ | true | Compounds on prior taxes |
usage | text | ✓ | 000_SALE | Sale/purchase |
status | text | ✓ | ACTIVATED | — |
chargeTarget | text | ✓ | 000_CUSTOMER | Customer/merchant |
taxSetId | text | ✓ | — | Parent set |
taxTypeId | text | — | Legacy FK to TaxType (dual-write, nullable) | |
discriminationTypeId | text | ✓ | — | Classification (authoritative) |
Indexes: IDX_Tax_discriminationTypeId, IDX_Tax_status, IDX_Tax_taxSetId, IDX_Tax_taxTypeId.
TaxType
| Property | Value |
|---|---|
| Table | pricing.TaxType |
| Source | packages/core/src/models/schemas/pricing/tax-type/schema.ts |
| Soft-delete | yes |
| Owner ID column | merchantId (nullable) |
type enum (FixedTaxTypes): 000_VAT, 100_EXCISE, 200_ENVIRONMENTAL, 300_LUXURY, 400_PIT, 999_CUSTOM. chargeTarget: 000_CUSTOMER / 100_MERCHANT. Unique (type, merchantId). Legacy classification — DiscriminationType (scope tax_classification) is now authoritative; Tax.taxTypeId is kept as a dual-write fallback (ADR-0002).
VN reference tables (read-only)
| Table | Key columns | 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 |
Served read-only; not seeded by this package's migrations (loaded externally).
3. Cross-entity Invariants
| Invariant | Enforcement |
|---|---|
At most one ACTIVATED TaxSet per (principalId, principalType) | Partial unique index UPI_TaxSet_principalId_principalType_status (where not deleted) |
| Provision is idempotent for the same source group | TaxProvisioningService skips when active TaxSet has sourceType=TaxGroup and matching sourceId |
| Only TaxGroup-sourced TaxSets are deactivated on (de)provision | _deactivateExistingTaxSet guards on sourceType === TaxGroup |
Merchant taxMethod must match TaxGroup.taxMethod | TaxGroupService.validateTaxGroupForMerchant (DIRECT ≠ DEDUCTION) |
Each provisioned Tax mirrors one TaxGroupItem | provisionForProduct loops group items |
4. Soft-delete Behavior
| Behavior | Detail |
|---|---|
| Read default | deletedAt IS NULL (model defaultFilter) |
| Deprovision | Status flip to DEACTIVATED — not a soft-delete (rows accumulate) |
| Hard-delete | Never by default |
| Restore | Re-provisioning creates a fresh ACTIVATED TaxSet; prior DEACTIVATED rows remain |