Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Appendix H: Product & Variant Model

Understanding the Parent-Child Relationship

This appendix documents how products and variants work in the POS Platform, with specific attention to Shopify compatibility and the Option1/Option2/Option3 system.


Core Concept: Products vs Variants

The Hierarchy

┌─────────────────────────────────────────────────────────────────────────────────────┐
│                         PRODUCT-VARIANT HIERARCHY                                    │
└─────────────────────────────────────────────────────────────────────────────────────┘

                              PRODUCT (Parent)
                        ┌──────────────────────────┐
                        │  id: 12345               │
                        │  name: "Galaxy V-Neck"   │
                        │  description: "Soft..."  │
                        │  vendor: "Acme Apparel"  │
                        │  category: "Men's Tops"  │
                        │  tags: ["summer", "new"] │
                        │  status: "active"        │
                        │                          │
                        │  ┌──────────────────────┐│
                        │  │ OPTIONS              ││
                        │  │ 1. Color (Red, Blue) ││
                        │  │ 2. Size (S, M, L, XL)││
                        │  │ 3. (unused)          ││
                        │  └──────────────────────┘│
                        └─────────────┬────────────┘
                                      │
           ┌──────────────────────────┼──────────────────────────┐
           │              │              │              │        │
           ▼              ▼              ▼              ▼        ▼
    ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐
    │ VARIANT 1  │ │ VARIANT 2  │ │ VARIANT 3  │ │ VARIANT 8  │ ...
    │            │ │            │ │            │ │            │
    │ Red / S    │ │ Red / M    │ │ Red / L    │ │ Blue / XL  │
    │            │ │            │ │            │ │            │
    │ SKU:       │ │ SKU:       │ │ SKU:       │ │ SKU:       │
    │ NXJ1078-   │ │ NXJ1078-   │ │ NXJ1078-   │ │ NXJ1078-   │
    │ RED-S      │ │ RED-M      │ │ RED-L      │ │ BLU-XL     │
    │            │ │            │ │            │ │            │
    │ Barcode:   │ │ Barcode:   │ │ Barcode:   │ │ Barcode:   │
    │ 0657381... │ │ 0657381... │ │ 0657381... │ │ 0657381... │
    │            │ │            │ │            │ │            │
    │ Price:     │ │ Price:     │ │ Price:     │ │ Price:     │
    │ $29.00     │ │ $29.00     │ │ $29.00     │ │ $32.00     │
    │            │ │            │ │            │ │            │
    │ Inventory: │ │ Inventory: │ │ Inventory: │ │ Inventory: │
    │ GM: 5      │ │ GM: 8      │ │ GM: 3      │ │ GM: 2      │
    │ HM: 3      │ │ HM: 6      │ │ HM: 4      │ │ HM: 0      │
    │ LM: 7      │ │ LM: 4      │ │ LM: 5      │ │ LM: 3      │
    └────────────┘ └────────────┘ └────────────┘ └────────────┘

    Color × Size = 2 × 4 = 8 VARIANTS (Cartesian product)

What Lives Where

DataParent ProductVariant
Name/Title✅ “Galaxy V-Neck Tee”Derived: “Galaxy V-Neck - Red / S”
Description✅ Full HTML description❌ (inherits from parent)
Category✅ “Men’s Tops”❌ (inherits from parent)
Vendor✅ “Acme Apparel”❌ (inherits from parent)
Tags✅ [“summer”, “new”, “sale”]❌ (inherits from parent)
SEO/URL✅ /products/galaxy-vneck❌ (one URL per product)
Options✅ Color, Size definitionsValues: Red, S
SKU✅ Unique per variant
Barcode✅ Unique UPC per variant
PriceBase price (optional)✅ Actual selling price
Cost✅ Per-variant cost
Inventory✅ Per-variant, per-location
Images✅ Multiple product imagesSingle variant-specific image

The Option System: Option1, Option2, Option3

Shopify Compatibility

Shopify supports exactly 3 options per product. This is a hard limit that cannot be extended via configuration or apps.

┌─────────────────────────────────────────────────────────────────────────────────────┐
│                           OPTION SYSTEM                                              │
└─────────────────────────────────────────────────────────────────────────────────────┘

PRODUCT LEVEL:
┌─────────────────────────────────────────────────────────────────────────────────────┐
│  options: [                                                                          │
│    { name: "Color",    position: 1, values: ["Red", "Blue", "Green", "Black"] },    │
│    { name: "Size",     position: 2, values: ["S", "M", "L", "XL", "XXL"] },         │
│    { name: "Material", position: 3, values: ["Cotton", "Polyester"] }               │
│  ]                                                                                   │
└─────────────────────────────────────────────────────────────────────────────────────┘

VARIANT LEVEL:
┌─────────────────────────────────────────────────────────────────────────────────────┐
│  {                                                                                   │
│    "sku": "SHIRT-RED-M-COT",                                                        │
│    "option1": "Red",         ← Maps to options[0] (Color)                           │
│    "option2": "M",           ← Maps to options[1] (Size)                            │
│    "option3": "Cotton",      ← Maps to options[2] (Material)                        │
│    "price": 29.99,                                                                   │
│    "barcode": "0657381512532"                                                       │
│  }                                                                                   │
└─────────────────────────────────────────────────────────────────────────────────────┘

Option Naming Conventions

IndustryOption1 (Primary)Option2 (Secondary)Option3 (Tertiary)
ApparelColorSizeMaterial / Fit
FootwearSizeWidthColor
ElectronicsCapacity (32GB)ColorConnectivity
FurnitureColorSizeMaterial
FoodFlavorSizePack Quantity

Variant Count Calculation

VARIANT FORMULA: Option1.values × Option2.values × Option3.values

Example 1: Simple T-Shirt
┌─────────────────────────────────────────────────┐
│  Color: Red, Blue, Black (3 values)             │
│  Size: S, M, L, XL (4 values)                   │
│  Material: (not used)                           │
│                                                 │
│  Total Variants: 3 × 4 = 12                     │
└─────────────────────────────────────────────────┘

Example 2: Premium Dress Shirt
┌─────────────────────────────────────────────────┐
│  Color: White, Blue, Pink, Lavender (4 values)  │
│  Size: 14.5-32, 15-32, 15-34, ... (10 sizes)   │
│  Fit: Slim, Regular (2 values)                  │
│                                                 │
│  Total Variants: 4 × 10 × 2 = 80                │
└─────────────────────────────────────────────────┘

Example 3: Maximum Complexity (WARNING)
┌─────────────────────────────────────────────────┐
│  Color: 10 colors                               │
│  Size: 10 sizes                                 │
│  Material: 3 materials                          │
│                                                 │
│  Total Variants: 10 × 10 × 3 = 300              │
│                                                 │
│  ⚠️  EXCEEDS SHOPIFY LIMIT OF 100!              │
│  (2,048 limit available with new API)           │
└─────────────────────────────────────────────────┘

Product Creation Flow

Step-by-Step Workflow

┌─────────────────────────────────────────────────────────────────────────────────────┐
│                        PRODUCT CREATION WORKFLOW                                     │
└─────────────────────────────────────────────────────────────────────────────────────┘

STEP 1: CREATE PARENT PRODUCT
┌─────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                     │
│  Admin Portal → Products → [+ New Product]                                          │
│                                                                                     │
│  ┌─────────────────────────────────────────────────────────────────────────────┐   │
│  │  BASIC INFORMATION                                                           │   │
│  │                                                                               │   │
│  │  Product Name *         [Galaxy V-Neck Tee                               ]   │   │
│  │                                                                               │   │
│  │  Description            [────────────────────────────────────────────────]   │   │
│  │                         [Premium cotton v-neck with a modern fit.        ]   │   │
│  │                         [Perfect for everyday wear.                      ]   │   │
│  │                         [────────────────────────────────────────────────]   │   │
│  │                                                                               │   │
│  │  Vendor/Brand *         [Nexus Premier                              ▼ ]      │   │
│  │  Category *             [Men's Tops > T-Shirts                      ▼ ]      │   │
│  │  Product Type           [Apparel                                    ▼ ]      │   │
│  │                                                                               │   │
│  │  Tags                   [summer] [new arrival] [basics] [+ Add Tag]          │   │
│  │                                                                               │   │
│  └─────────────────────────────────────────────────────────────────────────────┘   │
│                                                                                     │
│                                                           [Save & Continue →]       │
└─────────────────────────────────────────────────────────────────────────────────────┘
                                         │
                                         ▼
STEP 2: DEFINE OPTIONS
┌─────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                     │
│  ┌─────────────────────────────────────────────────────────────────────────────┐   │
│  │  PRODUCT OPTIONS                                                             │   │
│  │                                                                               │   │
│  │  Does this product have variants (size, color, etc.)?                        │   │
│  │  ● Yes, this product has multiple options                                    │   │
│  │  ○ No, this is a simple product (single variant)                             │   │
│  │                                                                               │   │
│  │  ──────────────────────────────────────────────────────────────────────────  │   │
│  │                                                                               │   │
│  │  OPTION 1 *                                                                   │   │
│  │  Name:   [Color                                      ▼ ]                      │   │
│  │  Values: [Red    ] [Blue   ] [Navy   ] [Black  ] [+ Add]                     │   │
│  │                                                                               │   │
│  │  ──────────────────────────────────────────────────────────────────────────  │   │
│  │                                                                               │   │
│  │  OPTION 2                                                        [+ Add Option] │
│  │  Name:   [Size                                       ▼ ]                      │   │
│  │  Values: [S      ] [M      ] [L      ] [XL     ] [+ Add]                     │   │
│  │                                                                               │   │
│  │  ──────────────────────────────────────────────────────────────────────────  │   │
│  │                                                                               │   │
│  │  OPTION 3 (Optional)                                             [+ Add Option] │
│  │  Name:   [                                           ▼ ]                      │   │
│  │  Values:                                                                      │   │
│  │                                                                               │   │
│  │  ──────────────────────────────────────────────────────────────────────────  │   │
│  │                                                                               │   │
│  │  VARIANT PREVIEW                                                              │   │
│  │  Based on your options, 16 variants will be created:                         │   │
│  │                                                                               │   │
│  │  Red/S, Red/M, Red/L, Red/XL,                                                │   │
│  │  Blue/S, Blue/M, Blue/L, Blue/XL,                                            │   │
│  │  Navy/S, Navy/M, Navy/L, Navy/XL,                                            │   │
│  │  Black/S, Black/M, Black/L, Black/XL                                         │   │
│  │                                                                               │   │
│  └─────────────────────────────────────────────────────────────────────────────┘   │
│                                                                                     │
│                                                           [Save & Continue →]       │
└─────────────────────────────────────────────────────────────────────────────────────┘
                                         │
                                         ▼
STEP 3: CONFIGURE VARIANTS
┌─────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                     │
│  ┌─────────────────────────────────────────────────────────────────────────────┐   │
│  │  VARIANT DETAILS                                                 [Bulk Edit] │   │
│  │                                                                               │   │
│  │  ┌───────────────────────────────────────────────────────────────────────┐   │   │
│  │  │ Default Price: [$29.00    ]    Default Cost: [$12.00    ]             │   │   │
│  │  │ [✓] Apply to all variants    [✓] Apply to all variants               │   │   │
│  │  └───────────────────────────────────────────────────────────────────────┘   │   │
│  │                                                                               │   │
│  │  ┌─────────────────────────────────────────────────────────────────────────┐ │   │
│  │  │ Variant      │ SKU              │ Barcode       │ Price  │ Cost   │ ✓  │ │   │
│  │  ├──────────────┼──────────────────┼───────────────┼────────┼────────┼────┤ │   │
│  │  │ Red / S      │ NXJ1078-RED-S    │ 0657381512501 │ $29.00 │ $12.00 │ ✓  │ │   │
│  │  │ Red / M      │ NXJ1078-RED-M    │ 0657381512502 │ $29.00 │ $12.00 │ ✓  │ │   │
│  │  │ Red / L      │ NXJ1078-RED-L    │ 0657381512503 │ $29.00 │ $12.00 │ ✓  │ │   │
│  │  │ Red / XL     │ NXJ1078-RED-XL   │ 0657381512504 │ $32.00 │ $13.00 │ ✓  │ │   │
│  │  │ Blue / S     │ NXJ1078-BLU-S    │ 0657381512505 │ $29.00 │ $12.00 │ ✓  │ │   │
│  │  │ Blue / M     │ NXJ1078-BLU-M    │ 0657381512506 │ $29.00 │ $12.00 │ ✓  │ │   │
│  │  │ ...          │ ...              │ ...           │ ...    │ ...    │    │ │   │
│  │  └─────────────────────────────────────────────────────────────────────────┘ │   │
│  │                                                                               │   │
│  │  SKU PATTERN: [NXJ1078-{color:3}-{size}]              [Auto-Generate SKUs]   │   │
│  │                                                                               │   │
│  │  ⚠️  2 variants have higher prices (XL sizes)                                │   │
│  │  ⚠️  0 variants are missing barcodes                                         │   │
│  │                                                                               │   │
│  └─────────────────────────────────────────────────────────────────────────────┘   │
│                                                                                     │
│                                                           [Save & Continue →]       │
└─────────────────────────────────────────────────────────────────────────────────────┘
                                         │
                                         ▼
STEP 4: SET INVENTORY
┌─────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                     │
│  ┌─────────────────────────────────────────────────────────────────────────────┐   │
│  │  INVENTORY BY LOCATION                                                       │   │
│  │                                                                               │   │
│  │  Track inventory: [✓] Track quantity   [ ] Don't track (infinite stock)     │   │
│  │                                                                               │   │
│  │  ┌─────────────────────────────────────────────────────────────────────────┐ │   │
│  │  │ Variant      │ HQ (Warehouse) │ GM       │ HM       │ LM       │ NM     │ │   │
│  │  ├──────────────┼────────────────┼──────────┼──────────┼──────────┼────────┤ │   │
│  │  │ Red / S      │ [100         ] │ [5     ] │ [3     ] │ [4     ] │ [2   ] │ │   │
│  │  │ Red / M      │ [150         ] │ [8     ] │ [6     ] │ [7     ] │ [4   ] │ │   │
│  │  │ Red / L      │ [120         ] │ [6     ] │ [5     ] │ [5     ] │ [3   ] │ │   │
│  │  │ Red / XL     │ [80          ] │ [3     ] │ [2     ] │ [3     ] │ [2   ] │ │   │
│  │  │ Blue / S     │ [90          ] │ [4     ] │ [3     ] │ [3     ] │ [2   ] │ │   │
│  │  │ ...          │ ...            │ ...      │ ...      │ ...      │ ...    │ │   │
│  │  └─────────────────────────────────────────────────────────────────────────┘ │   │
│  │                                                                               │   │
│  │  TOTALS:                                                                      │   │
│  │  ┌─────────────────────────────────────────────────────────────────────────┐ │   │
│  │  │  HQ: 1,520 units  │  GM: 92  │  HM: 68  │  LM: 75  │  NM: 45           │ │   │
│  │  │                                                                         │ │   │
│  │  │  NETWORK TOTAL: 1,800 units                                             │ │   │
│  │  └─────────────────────────────────────────────────────────────────────────┘ │   │
│  │                                                                               │   │
│  └─────────────────────────────────────────────────────────────────────────────┘   │
│                                                                                     │
│                                                           [Save & Continue →]       │
└─────────────────────────────────────────────────────────────────────────────────────┘
                                         │
                                         ▼
STEP 5: ADD IMAGES
┌─────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                     │
│  ┌─────────────────────────────────────────────────────────────────────────────┐   │
│  │  PRODUCT IMAGES                                                              │   │
│  │                                                                               │   │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐            │   │
│  │  │             │ │             │ │             │ │             │            │   │
│  │  │   [MAIN]    │ │   [BACK]    │ │   [RED]     │ │   [BLUE]    │            │   │
│  │  │    📷      │ │    📷      │ │    📷      │ │    📷      │            │   │
│  │  │             │ │             │ │             │ │             │            │   │
│  │  └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘            │   │
│  │   Primary        Position 2       Color swatch    Color swatch              │   │
│  │                                   → Red variants  → Blue variants            │   │
│  │                                                                               │   │
│  │  [+ Upload Images]  [Link from URL]                                          │   │
│  │                                                                               │   │
│  │  ──────────────────────────────────────────────────────────────────────────  │   │
│  │                                                                               │   │
│  │  VARIANT IMAGE MAPPING (Optional)                                            │   │
│  │                                                                               │   │
│  │  ┌─────────────────────────────────────────────────────────────────────────┐ │   │
│  │  │  Color   │ Image                                                        │ │   │
│  │  ├──────────┼──────────────────────────────────────────────────────────────┤ │   │
│  │  │  Red     │ [red-vneck.jpg                                          ▼ ] │ │   │
│  │  │  Blue    │ [blue-vneck.jpg                                         ▼ ] │ │   │
│  │  │  Navy    │ [navy-vneck.jpg                                         ▼ ] │ │   │
│  │  │  Black   │ [black-vneck.jpg                                        ▼ ] │ │   │
│  │  └─────────────────────────────────────────────────────────────────────────┘ │   │
│  │                                                                               │   │
│  └─────────────────────────────────────────────────────────────────────────────┘   │
│                                                                                     │
│                                                           [Save & Continue →]       │
└─────────────────────────────────────────────────────────────────────────────────────┘
                                         │
                                         ▼
STEP 6: REVIEW & PUBLISH
┌─────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                     │
│  ┌─────────────────────────────────────────────────────────────────────────────┐   │
│  │  REVIEW PRODUCT                                                              │   │
│  │                                                                               │   │
│  │  ✓ Basic Information complete                                                │   │
│  │  ✓ 2 options defined (Color, Size)                                           │   │
│  │  ✓ 16 variants configured                                                    │   │
│  │  ✓ All variants have SKUs                                                    │   │
│  │  ⚠️  4 variants missing barcodes                                             │   │
│  │  ✓ Inventory set for all locations                                          │   │
│  │  ✓ 4 images uploaded                                                         │   │
│  │                                                                               │   │
│  │  ──────────────────────────────────────────────────────────────────────────  │   │
│  │                                                                               │   │
│  │  PUBLISH STATUS                                                               │   │
│  │                                                                               │   │
│  │  ○ Draft (not visible to customers)                                          │   │
│  │  ● Active (visible on storefront)                                            │   │
│  │  ○ Archived (hidden, preserved for records)                                  │   │
│  │                                                                               │   │
│  │  ──────────────────────────────────────────────────────────────────────────  │   │
│  │                                                                               │   │
│  │  SHOPIFY SYNC                                                                │   │
│  │                                                                               │   │
│  │  [✓] Sync to Shopify                                                         │   │
│  │      Product will be created/updated in Shopify store                       │   │
│  │                                                                               │   │
│  └─────────────────────────────────────────────────────────────────────────────┘   │
│                                                                                     │
│  [← Back to Edit]                                      [Save as Draft] [Publish]   │
│                                                                                     │
└─────────────────────────────────────────────────────────────────────────────────────┘

Database Schema

Products Table (Parent)

CREATE TABLE products (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL REFERENCES shared.tenants(id),

    -- Basic Information
    name            VARCHAR(255) NOT NULL,
    description     TEXT,
    handle          VARCHAR(255) NOT NULL,           -- URL slug
    vendor_id       UUID REFERENCES vendors(id),
    category_id     UUID REFERENCES categories(id),
    product_type    VARCHAR(100),

    -- Options Definition (up to 3)
    option1_name    VARCHAR(50),                     -- e.g., "Color"
    option1_values  TEXT[],                          -- e.g., {"Red", "Blue", "Black"}
    option2_name    VARCHAR(50),                     -- e.g., "Size"
    option2_values  TEXT[],                          -- e.g., {"S", "M", "L", "XL"}
    option3_name    VARCHAR(50),                     -- e.g., "Material"
    option3_values  TEXT[],                          -- e.g., {"Cotton", "Polyester"}

    -- Status
    status          VARCHAR(20) DEFAULT 'draft',     -- draft, active, archived

    -- Shopify Sync
    shopify_product_id  BIGINT,
    shopify_synced_at   TIMESTAMPTZ,

    -- Metadata
    tags            TEXT[],
    metadata        JSONB DEFAULT '{}',

    -- Audit
    created_at      TIMESTAMPTZ DEFAULT NOW(),
    updated_at      TIMESTAMPTZ DEFAULT NOW(),
    created_by      UUID,
    deleted_at      TIMESTAMPTZ,                     -- Soft delete

    -- Constraints
    UNIQUE (tenant_id, handle),
    CHECK (status IN ('draft', 'active', 'archived'))
);

-- Indexes
CREATE INDEX idx_products_tenant_status ON products(tenant_id, status);
CREATE INDEX idx_products_category ON products(category_id);
CREATE INDEX idx_products_vendor ON products(vendor_id);
CREATE INDEX idx_products_shopify ON products(shopify_product_id);
CREATE INDEX idx_products_tags ON products USING GIN(tags);

Product Variants Table (Children)

CREATE TABLE product_variants (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL REFERENCES shared.tenants(id),
    product_id      UUID NOT NULL REFERENCES products(id) ON DELETE CASCADE,

    -- Identifiers (CRITICAL - must be unique)
    sku             VARCHAR(100) NOT NULL,
    barcode         VARCHAR(50),                     -- UPC/EAN

    -- Option Values (maps to parent's option definitions)
    option1_value   VARCHAR(100),                    -- e.g., "Red"
    option2_value   VARCHAR(100),                    -- e.g., "M"
    option3_value   VARCHAR(100),                    -- e.g., "Cotton"

    -- Derived title (for display)
    title           VARCHAR(255) GENERATED ALWAYS AS (
                      COALESCE(option1_value, '') ||
                      CASE WHEN option2_value IS NOT NULL THEN ' / ' || option2_value ELSE '' END ||
                      CASE WHEN option3_value IS NOT NULL THEN ' / ' || option3_value ELSE '' END
                    ) STORED,

    -- Pricing
    price           DECIMAL(10,2) NOT NULL,
    compare_at_price DECIMAL(10,2),                  -- "Was" price for sales
    cost            DECIMAL(10,2),                   -- Cost of goods

    -- Physical
    weight          DECIMAL(8,2),
    weight_unit     VARCHAR(10) DEFAULT 'lb',
    requires_shipping BOOLEAN DEFAULT true,

    -- Tax
    taxable         BOOLEAN DEFAULT true,
    tax_code        VARCHAR(50),

    -- Status
    position        INTEGER DEFAULT 1,               -- Display order
    is_active       BOOLEAN DEFAULT true,

    -- Shopify Sync
    shopify_variant_id      BIGINT,
    shopify_inventory_item_id BIGINT,
    shopify_synced_at       TIMESTAMPTZ,

    -- Audit
    created_at      TIMESTAMPTZ DEFAULT NOW(),
    updated_at      TIMESTAMPTZ DEFAULT NOW(),

    -- Constraints
    UNIQUE (tenant_id, sku),
    UNIQUE (tenant_id, barcode) WHERE barcode IS NOT NULL
);

-- Indexes
CREATE INDEX idx_variants_product ON product_variants(product_id);
CREATE INDEX idx_variants_sku ON product_variants(sku);
CREATE INDEX idx_variants_barcode ON product_variants(barcode);
CREATE INDEX idx_variants_shopify ON product_variants(shopify_variant_id);

Inventory Levels Table (Per-Variant, Per-Location)

CREATE TABLE inventory_levels (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL REFERENCES shared.tenants(id),
    variant_id      UUID NOT NULL REFERENCES product_variants(id) ON DELETE CASCADE,
    location_id     UUID NOT NULL REFERENCES locations(id),

    -- Quantities
    quantity_on_hand    INTEGER NOT NULL DEFAULT 0,
    quantity_committed  INTEGER NOT NULL DEFAULT 0,  -- Reserved for orders
    quantity_available  INTEGER GENERATED ALWAYS AS (quantity_on_hand - quantity_committed) STORED,

    -- Reorder
    reorder_point       INTEGER,
    reorder_quantity    INTEGER,

    -- Shopify Sync
    shopify_inventory_level_id  BIGINT,

    -- Audit
    updated_at      TIMESTAMPTZ DEFAULT NOW(),

    -- Constraints
    UNIQUE (variant_id, location_id),
    CHECK (quantity_on_hand >= 0),
    CHECK (quantity_committed >= 0)
);

-- Indexes
CREATE INDEX idx_inventory_variant ON inventory_levels(variant_id);
CREATE INDEX idx_inventory_location ON inventory_levels(location_id);
CREATE INDEX idx_inventory_low_stock ON inventory_levels(quantity_available)
    WHERE quantity_available <= reorder_point;

API Endpoints

Create Product with Variants

POST /api/v1/products
Content-Type: application/json

{
  "name": "Galaxy V-Neck Tee",
  "description": "Premium cotton v-neck with a modern fit.",
  "vendorId": "vendor_abc123",
  "categoryId": "cat_mens_tops",
  "productType": "Apparel",
  "tags": ["summer", "new arrival", "basics"],

  "options": [
    { "name": "Color", "values": ["Red", "Blue", "Navy", "Black"] },
    { "name": "Size", "values": ["S", "M", "L", "XL"] }
  ],

  "variants": [
    {
      "sku": "NXJ1078-RED-S",
      "barcode": "0657381512501",
      "option1Value": "Red",
      "option2Value": "S",
      "price": 29.00,
      "cost": 12.00,
      "inventory": [
        { "locationCode": "HQ", "quantity": 100 },
        { "locationCode": "GM", "quantity": 5 },
        { "locationCode": "HM", "quantity": 3 }
      ]
    },
    {
      "sku": "NXJ1078-RED-M",
      "barcode": "0657381512502",
      "option1Value": "Red",
      "option2Value": "M",
      "price": 29.00,
      "cost": 12.00,
      "inventory": [
        { "locationCode": "HQ", "quantity": 150 },
        { "locationCode": "GM", "quantity": 8 }
      ]
    }
    // ... more variants
  ],

  "status": "active",
  "syncToShopify": true
}

Response

{
  "id": "prod_abc123",
  "name": "Galaxy V-Neck Tee",
  "handle": "galaxy-v-neck-tee",
  "status": "active",
  "shopifyProductId": 7891234567890,

  "options": [
    { "name": "Color", "position": 1, "values": ["Red", "Blue", "Navy", "Black"] },
    { "name": "Size", "position": 2, "values": ["S", "M", "L", "XL"] }
  ],

  "variants": [
    {
      "id": "var_001",
      "sku": "NXJ1078-RED-S",
      "title": "Red / S",
      "price": 29.00,
      "shopifyVariantId": 39332384801985,
      "totalInventory": 108
    },
    {
      "id": "var_002",
      "sku": "NXJ1078-RED-M",
      "title": "Red / M",
      "price": 29.00,
      "shopifyVariantId": 39332384802001,
      "totalInventory": 158
    }
  ],

  "totalVariants": 16,
  "totalInventory": 1800,

  "createdAt": "2025-01-15T10:30:00Z",
  "updatedAt": "2025-01-15T10:30:00Z"
}

SKU Generation Patterns

{BRAND}{STYLE}-{OPTION1}-{OPTION2}[-{OPTION3}]

Examples:
NXJ1078-RED-S         ← Nexus Clothing, Style 1078, Red, Small
NXP0892-KHK-32        ← Nexus Clothing, Pants 0892, Khaki, Size 32
BRT-BLU-155           ← Burton, Blue, 155cm

Auto-Generation Logic

function generateSku(product, variant) {
  const brand = product.vendorCode || 'GEN';  // 3-char brand code
  const style = product.styleNumber;           // 4-digit style

  let sku = `${brand}${style}`;

  if (variant.option1Value) {
    sku += `-${abbreviate(variant.option1Value, 3)}`;  // RED, BLU, BLK
  }
  if (variant.option2Value) {
    sku += `-${variant.option2Value}`;  // S, M, L, 32, 34
  }
  if (variant.option3Value) {
    sku += `-${abbreviate(variant.option3Value, 3)}`;  // COT, PLY
  }

  return sku.toUpperCase();
}

function abbreviate(value, length) {
  // Standard abbreviations
  const map = {
    'Red': 'RED', 'Blue': 'BLU', 'Black': 'BLK', 'White': 'WHT',
    'Navy': 'NAV', 'Green': 'GRN', 'Grey': 'GRY', 'Gray': 'GRY',
    'Cotton': 'COT', 'Polyester': 'PLY', 'Wool': 'WOL',
    'Small': 'S', 'Medium': 'M', 'Large': 'L', 'Extra Large': 'XL'
  };
  return map[value] || value.substring(0, length).toUpperCase();
}

Shopify Sync Mapping

Product Fields

POS FieldShopify FieldNotes
nametitleDirect mapping
descriptionbody_htmlMay include HTML
handlehandleURL slug, auto-generated
vendor.namevendorString, not ID
category.nameproduct_typeString, not ID
tags[]tagsComma-separated string
statusstatusactive/draft/archived
option1_nameoptions[0].namee.g., “Color”
option1_values[]options[0].values[]e.g., [“Red”, “Blue”]

Variant Fields

POS FieldShopify FieldNotes
skuskuPrimary sync key
barcodebarcodeUPC/EAN
option1_valueoption1e.g., “Red”
option2_valueoption2e.g., “M”
option3_valueoption3e.g., “Cotton”
pricepriceDecimal as string
compare_at_pricecompare_at_priceSale pricing
costN/AShopify uses inventory_item.cost
weightweightWith weight_unit
taxabletaxableBoolean
requires_shippingrequires_shippingBoolean

Inventory Sync

POS inventory_levels      →     Shopify InventoryLevel
─────────────────────────────────────────────────────────
variant.shopify_inventory_item_id  →  inventory_item_id
location.shopify_location_id       →  location_id
quantity_available                 →  available

POST /admin/api/2025-01/inventory_levels/set.json
{
  "location_id": 655441491,
  "inventory_item_id": 808950810,
  "available": 42
}

Variant Limits & Workarounds

Shopify Limits

LimitValueWorkaround
Max Options3Split into separate products
Max Variants100 (legacy) / 2,048 (new)Split into separate products
Max Images250 per productUse CDN for extras
SKU Length255 charsKeep under 100 recommended

When to Split Products

Split a product into multiple Shopify products when:

  1. More than 3 options needed (e.g., Size, Color, Material, Inseam)
  2. Variant count exceeds 100 (or 2,048 with new API)
  3. Variants need unique descriptions (Shopify shares description)
  4. SEO requires separate pages (different keywords per variant group)
SPLIT EXAMPLE:
─────────────────────────────────────────────────────
Original: "Dress Shirt" with Color × Size × Fit × Collar = 500+ variants

Split into:
├── "Dress Shirt - Slim Fit"     (Color × Size = 100 variants)
├── "Dress Shirt - Regular Fit"  (Color × Size = 100 variants)
├── "Dress Shirt - Classic Fit"  (Color × Size = 100 variants)
└── ... (grouped by Fit, which becomes implicit)

Link them with:
- Same "product_group" tag
- Cross-reference in descriptions
- Collection grouping

Implementation Checklist

  • Create products table with option columns
  • Create product_variants table with option values
  • Create inventory_levels table with per-location tracking
  • Implement SKU auto-generation
  • Build product creation wizard UI (6 steps)
  • Implement Shopify sync service for products
  • Implement Shopify sync service for variants
  • Implement Shopify sync service for inventory levels
  • Add bulk import from CSV
  • Add variant matrix editor for quick updates
  • Handle Shopify >100 variant split logic

Appendix H - Version 1.0 Product and Variant Model Documentation Last updated: December 2025