Product Variants Module
Overview
The product-variants module manages the variant system for FlowPOS retail products. A product can have multiple sellable SKUs (variants) defined by combinations of option types and option values (e.g. Size=M + Color=Red). Up to 3 option dimensions are supported per product.
Domain Concepts
| Concept | Description |
|---|---|
| Option Type | A dimension axis (e.g. Size, Color, Material). Business-scoped, reusable across products. |
| Option Value | A specific choice within an option type (e.g. S, M, L for Size). |
| Product Variant | A sellable SKU defined by a combination of option values. Carries its own barcode, price override, and inventory. |
| Variant Label | Auto-computed display string (e.g. "M / Red") from sorted option values. |
| Default Variant | Auto-created placeholder variant for non-variant products. Removed when real variants are added. |
Architecture
product-variants/
├── product-variants.module.ts # NestJS module definition
├── domain/
│ └── product-variants-repository.domain.ts # Repository port + domain interfaces
├── application/
│ └── product-variants.service.ts # Business logic / use cases
├── infrastructure/
│ └── product-variants.repository.ts # Kysely DB adapter
└── interfaces/
├── product-variants.controller.ts # HTTP routes
├── dtos/
│ ├── create-option-type.dto.ts
│ ├── update-option-type.dto.ts
│ ├── create-option-value.dto.ts
│ ├── update-option-value.dto.ts
│ ├── create-variant.dto.ts
│ ├── update-variant.dto.ts
│ ├── create-variants-bulk.dto.ts
│ └── assign-barcode.dto.ts
└── query/
├── list-option-types.query.ts
└── list-variants.query.ts
Dependencies: DatabaseModule, BarcodesModule
API Endpoints
Option Types
| Method | Path | Description |
|---|---|---|
GET | /product-option-types | List option types with their values |
POST | /product-option-types | Create an option type |
PATCH | /product-option-types/:id | Update an option type |
DELETE | /product-option-types/:id | Delete an option type (fails if values are in use) |
Option Values
| Method | Path | Description |
|---|---|---|
GET | /product-option-types/:typeId/values | List values for an option type |
POST | /product-option-types/:typeId/values | Create an option value |
PATCH | /product-option-values/:id | Update an option value |
DELETE | /product-option-values/:id | Delete an option value (fails if assigned to variants) |
Variants
| Method | Path | Description |
|---|---|---|
GET | /products/:productId/variants | List variants for a product |
POST | /products/:productId/variants | Create a single variant |
POST | /products/:productId/variants/bulk | Create variants in bulk |
GET | /product-variants/:id | Get variant by ID |
PATCH | /product-variants/:id | Update a variant |
DELETE | /product-variants/:id | Delete a variant (fails if it has transaction history) |
Barcode Management
| Method | Path | Description |
|---|---|---|
POST | /product-variants/generate-barcodes | Generate barcodes for variants without one |
GET | /product-variants/lookup | POS lookup by SKU or barcode |
PATCH | /product-variants/:id/barcode | Assign a barcode to a variant |
PATCH | /product-variants/:id/barcode/retire | Retire a variant's barcode |
PATCH | /product-variants/:id/barcode/reactivate | Reactivate a retired barcode |
Key Use Cases
Bulk Variant Creation
When creating variants in bulk (POST /products/:productId/variants/bulk):
- Validates no duplicate SKUs or barcodes in the batch.
- Optionally auto-generates internal barcodes (
generateMissingBarcodes: true). - Removes the default variant placeholder (with markdown conflict check).
- Sets
product.hasVariants = true. - Runs in a single transaction for atomicity.
Barcode Lifecycle
Barcodes follow a lifecycle: unassigned → active → retired → active (reactivated).
- Assign: Validates format, checks cross-entity uniqueness, retires old barcode if present.
- Retire: Marks assignment as retired in history; barcode value preserved on variant.
- Reactivate: Checks availability and creates new assignment record.
Internal barcodes follow the format: FP-{BUSINESS_SHORT_CODE}-{SKU} (with -N suffix on collision).
Deletion Guards
- Option types: Cannot delete if any values are assigned to existing variants.
- Option values: Cannot delete if assigned to any variant.
- Variants: Cannot delete if they have inventory or adjustment history; deactivate instead.
- Default variant removal: Blocked if product has an active markdown wave.
Database Tables
product_option_type— Option type definitions (business-scoped)product_option_value— Values within each option typeproduct_variant— Sellable SKU recordsproduct_variant_option— Join table linking variants to their option value assignmentsbarcode_assignment— Barcode lifecycle history
Permissions
Uses PolicyResource.ProductVariant with standard CRUD actions (Create, Read, Update, Delete).