Catalog — variant sell unit and pack
Executive summary
Section titled “Executive summary”One product (items) can have many variations (item_variations / JSON variations[]). Retail vs wholesale pricing is driven by price channel on orders, not by duplicating catalog rows. Sell unit defaults from items.unit_id; each variation may override with its own unit_id or inherit when null. Pack size is stored per variation as units_per_pack (for example 24 for a case). An optional base variation link (base_variation_id, exposed as base_variant key in API payloads) supports reporting and conversion when pack size is known. Stock remains per variation; the platform does not auto-convert inventory between pack and base SKU.
Database and migration
Section titled “Database and migration”| Item | Detail |
|---|---|
| Migration | ItemVariationUnitAndPack1783000000000 — apps/backend/src/database/migrations/1783000000000-ItemVariationUnitAndPack.ts |
| Table | item_variations (prefixed table name via tp('item_variations') where the codebase applies table prefixes) |
New columns (legacy field prefix fp(...)) | unit_id — nullable; references sell unit id; null means inherit product unit · units_per_pack — nullable decimal multiplier vs base SKU · base_variation_id — nullable self-FK |
| Constraint | FK_item_variations_base_variation — ON DELETE SET NULL |
Run pending migrations for apps/backend against the target database. If the item_variations table does not exist, the migration skips column changes safely.
Entity and mapping code
Section titled “Entity and mapping code”- Entity:
apps/backend/src/database/entities/item-variation.entity.ts—unitId,unitsPerPack,baseVariationId, optionalbaseVariationrelation. - API row builder:
apps/backend/src/common/item-variation-unit.util.ts—mapItemVariationEntityToApiRow,resolveVariationSellUnitId,variationBaseQuantity, payload parsers. - Sync from payloads:
apps/backend/src/common/item-variation-sync.util.ts— readsunit_id,units_per_pack,base_variant/base_variation_keyfrom each variation payload; persists columns; resolvesbase_variation_idfrom the base variant key in a follow-up pass.
API contract (variations array)
Section titled “API contract (variations array)”Each object in variations on product detail (admin + vendor rebuild paths) includes fields such as:
| Field | Type (typical) | Meaning |
|---|---|---|
variant | string | Stable variation key / label |
sku_code, sku_name, barcode | string | Identifiers |
price, wholesale_price, stock | number | Existing catalog fields |
unit_id | string | null | Override unit id; null → use product unit_id |
units_per_pack | number | null | Pack multiplier; null if unset |
base_variation_id | number | null | Persisted FK after sync |
base_variant | string | null | Key of linked base variation row (round-trip / forms) |
Write payload (create / update)
Section titled “Write payload (create / update)”Clients may send per variation row:
unit_id— optional string or empty / omitted to inheritunits_per_pack— optional positive number or null to clearbase_variant— optional string matching another row’svariantkey
Exact request bodies remain defined in OpenAPI (npm run openapi:export) — see API endpoints catalog.
Backend consumers
Section titled “Backend consumers”| Area | Implementation notes |
|---|---|
| Vendor catalog detail | apps/backend/src/modules/vendor/vendor-products.service.ts — resolveVariationsForVendorDetail uses mapItemVariationEntityToApiRow. |
| Admin catalog detail / export | apps/backend/src/modules/admin/admin-products.service.ts — same mapping; export builds from full ItemVariationEntity rows where required. |
| POS item variants | apps/backend/src/modules/pos/pos.service.ts — variant rows expose unit_id, units_per_pack, base_variation_id where loaded from ItemVariationEntity. |
Frontend coverage
Section titled “Frontend coverage”Vendor web (apps/vendor-web)
Section titled “Vendor web (apps/vendor-web)”- Routes:
/dashboard/products/create,/dashboard/products/:id/edit— variations tables include sell unit, units / pack, and base SKU (multi-variant only) viaProductVariationUnitPackCells. - Files:
product-form-page.tsx,product-form.schema.ts,components/dashboard/products/ProductVariationUnitPackCells.tsx. - Copy:
packages/shared/src/locales/vendor-product-form.ts(variantsTablekeys for unit, inherit label, units per pack, base variant).
Admin web (apps/admin-web)
Section titled “Admin web (apps/admin-web)”- Admin product form exposes product-level
unit_id; per-variation units per pack and base variant editors are not implemented yet. APIs still return variation fields — use vendor edit or raw API JSON as needed until UI parity lands.
Product detail vs edit
Section titled “Product detail vs edit”Vendor detail screens may omit some variation breakdown fields; authoritative editing is on create / edit.
Optional client follow-up
Section titled “Optional client follow-up”If POS or tooling reads catalog only from JSON blobs, extend packages/utils normalization so unit_id / units_per_pack propagate consistently alongside entity-backed paths.
Automated tests
Section titled “Automated tests”apps/backend/src/common/item-variation-sync.util.spec.tsapps/backend/src/common/item-variation-unit.util.spec.ts