Skip to content

Domain Model

All schemas are defined in @nx/core/src/models/schemas/{public,allocation}/ and re-exported. Catalog tables live in the public schema; allocation tables in the allocation schema. Polymorphic columns use generatePrincipalColumnDefs; common columns via generateCommonColumnDefs<TMetadata>().

1. Full ERD

2. Common Columns

ColumnTypeNotes
idtextPK, Snowflake
identifiertextHuman code with prefix (P, PV, M, …)
createdAt / modifiedAttimestamptz
createdBy / modifiedBytextUser audit
deletedAttimestamptzSoft-delete
metadatajsonbTyped extension bag ($type<TMetadata>())

3. Entities

3.1 Product

PropertyValue
TableProduct (public)
Sourcecore/.../public/product/schema.ts
Soft-deleteyes
FieldTypeRequiredDescription
slugtextUnique partial per merchantId
statustextProductStatuses; default ACTIVATED
merchantIdtextOwner
taxGroupIdtextTax group ref (consumed by taxation)
parentIdtextHierarchy / variant-group
referenceIdtextExternal / sync reference

3.2 ProductVariant

PropertyValue
TableProductVariant (public)
Sourcecore/.../public/product-variant/schema.ts
FieldTypeRequiredDescription
slugtext
isDefaultbooleanDefault variant of the product (default false)
dateFrom / dateTotimestamptzValidity window
statustextProductVariantStatuses; default ACTIVATED
typetextProductVariantTypes; default STORABLE — structural discriminator
uomjsonbIUomRole { base, purchase, sale }
productIdtextParent product

type drives stocking & bundling — see §4.1. Pricing is not stored here (lives in @nx/pricing).

3.3 ProductInfo / ProductIdentifier

ProductInfo (polymorphic generatePrincipalColumnDefs — principal = Product or ProductVariant): name (i18n), description (i18n).

ProductIdentifier (polymorphic): scheme (TProductIdentifierSchemeSYSTEM / SKU / BARCODE / …), identifier (text). SYSTEM scheme is auto-generated on aggregate create.

3.4 ProductOption / ProductOptionValue / ProductVariantOption

TableKey fields
ProductOptionproductId, key, name (i18n), sequence
ProductOptionValueoptionId, value, name (i18n), sequence
ProductVariantOptionproductVariantId, optionId, optionValueId — links a variant to one value per option

3.5 ProductBundler

PropertyValue
TableProductBundler (public)
Sourcecore/.../public/product-bundler/{schema,constants}.ts
FieldTypeRequiredDescription
typetextProductBundlerTypes COMBO / ADDON / FBT; default COMBO
leadVariantIdtextHost / combo variant
relatedVariantIdtextComponent / addon / suggestion
quantitynumericDefault 1
basistextProductBundlerBasises — FBT price override; null on COMBO/ADDON
basisValuenumericOverride amount/percent
sequenceintOrdering (default 0)

Unique partial on (type, leadVariantId, relatedVariantId). This single table expresses COMBO/ADDON/FBT relations — they are not variant types. See ADR-0003.

3.6 Merchant

PropertyValue
TableMerchant (public)
Sourcecore/.../public/merchant/{schema,constants}.ts
FieldTypeRequiredDescription
slugtextUnique partial per organizerId
name / descriptioni18nname ✓
statustextMerchantStatuses; default ACTIVATED
currencytextDefault VND
businessTypetextBusinessTypes; default HOUSEHOLD
industrytextMerchantIndustry; default FNB
isHeadquarterbooleanDefault false
locationjsonblocation() helper
taxMethodtextTaxMethods (e.g. DIRECT)
parentIdtextHierarchy
organizerIdtextOwner
metadata.taxjsonb{ taxCode, cityCode?, districtCode?, wardsCode?, fullName?, addressLine? } — MST capture (CDC → TaxInfo)
metadata.eInvoice[]jsonbe-invoice provider creds
metadata.finance.accounts[]jsonbseeded finance accounts
metadata.onboardingjsonbPartial<Record<TMerchantOnboardingStep, boolean>>

3.7 Organizer

slug (unique), identifier, status (OrganizerStatuses; default ACTIVATED), name (i18n), description (i18n), location, parentId, headquarterMerchantId.

3.8 SaleChannel / SaleChannelProduct

SaleChannel: slug, identifier, status (default ACTIVATED), name (i18n), description (i18n), merchantId, parentId.

SaleChannelProduct (join): productId, saleChannelId. Cascade-deleted by DeletionPolicyService when a channel is removed.

3.9 Category

FieldTypeRequiredDescription
name / descriptioni18nname ✓
statustextCategoryStatuses
discriminationTypetextTCategoryDiscriminationType (FE grouping label)
identifiertext
merchantIdtextnull ⇒ SYSTEM category (shared by businessType)
parentIdtextSelf-ref hierarchy

Category.type is FE-grouping only — it does not drive inventory/sale behavior.

3.10 Configuration

FieldTypeRequiredDescription
principalType / principalIdtextOwner scope (null ⇒ SYSTEM)
codetextConfig code; provider-integration format {type}:{provider}:{action}:{credentialType}
grouptextConfigurationGroups
statustextConfigurationStatuses
environmenttextPer-environment scoping
credentialtextAES-256-GCM ciphertext (masked in responses)

Unique partial on (group, code, principalId, principalType, environment).

3.11 Device / DiscriminationType / ReceiptTemplate / Setting

TableKey fields
Devicestatus (default NEW), name (i18n), code, type, merchantId, hardwareInfo/softwareInfo (jsonb), pin, vendor
DiscriminationTypescope, status, name (i18n), type, parentId, merchantId
ReceiptTemplatepolymorphic principal, name, locale, paperWidth, fontSize, isDefault, content (jsonb TReceiptContent)
Settingpolymorphic principal, status, typed metadata value bag

3.12 Allocation (allocation schema)

TableKey fields
AllocationLayouttop-level floor plan
AllocationZonename (i18n), layoutId, parentId (self-ref), style (jsonb)
AllocationUnitname (i18n), zoneId, placement/style (jsonb), capacity, status

AllocationUsage (occupancy) is owned and mutated by @nx/sale; commerce owns only the static layout.

4. Status & Type Enums

4.1 ProductVariantTypes

Source: core/.../public/product-variant/constants.ts.

ValueConstStockableBOM
000_STORABLESTORABLE
100_CONSUMABLECONSUMABLE
200_SERVICESERVICE
300_KITKIT✓ (via Material)
301_COMBOCOMBO✓ (via ProductVariant)
400_MANUFACTUREDMANUFACTURED

Helper sets: STOCKABLE_SET = {STORABLE, MANUFACTURED}, BOMABLE_SET = {KIT, COMBO, MANUFACTURED}.

4.2 ProductBundlerTypes

ValueConstMeaning
000_COMBOCOMBOrelatedVariantId is a component of the combo
100_ADDONADDONfree addon attached to host variant
200_FBTFBT"frequently bought together" suggestion (directional)

4.3 Lifecycle statuses

EnumValues
ProductStatuses / ProductVariantStatusesDRAFT, ACTIVATED, DEACTIVATED, ARCHIVED
MerchantStatuses / OrganizerStatuses / SaleChannelStatusesreuse Statuses incl. ACTIVATED (default)
MerchantOnboardingStepsBUSINESS, FINANCE_ACCOUNT (300), TAX_INFO (400), PRODUCT

5. Cross-entity Invariants

InvariantEnforcement
Product aggregate is atomic (Product + Info + SYSTEM identifier + channel links + default variant)Single TX in ProductCreateService
Merchant aggregate atomic (merchant + policy + identifier + categories + channels)Single TX in MerchantService
Onboarding creates Organizer + Merchant + 2 PolicyDefinitions + default SaleChannel atomicallyOrganizerService.onBoarding TX
Product.slug unique per merchantId; Merchant.slug unique per organizerIdDB unique partial (deletedAt IS NULL)
Multi-merchant product replication runs only when syncMerchantIds.length > 0ProductAggregate*Listener.pushJobToQueue guard
Merchant tax info lives in metadata.tax, not a column; TaxInfo is authoritative downstreamMerchantService metadata merge → CDC
Provider credentials never returned in plaintextEncryptService + masked display value
Configuration uniqueness(group, code, principalId, principalType, environment) unique partial
ProductBundler row unique per (type, leadVariantId, relatedVariantId)DB unique partial

6. Soft-delete Behavior

EntitySoft-deleteNotes
All commerce entitiesdeletedAt marker; unique partials exclude soft-deleted rows
Deletion guardsDeletionPolicyService blocks delete when sale history exists / strict policy set; archive instead

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