# EXPIRATION TRACKING — MIGRATION.md ## Migration Philosophy **No breaking changes to existing products or checkout flow.** All existing products must retain their current behavior after migration. ## Phase 0: Feature Flag Off The feature flag `expiration_tracking` starts as **off**. All code paths default to existing behavior. ## Phase 1: Database Migration (Zero-downtime safe) ```sql -- 1. Add column to products (nullable, default false) ALTER TABLE catalog_products ADD COLUMN expiration_tracking_enabled boolean NOT NULL DEFAULT false; -- 2. Create lots table CREATE TABLE inventory_lots ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), variant_id uuid NOT NULL REFERENCES catalog_variants(id) ON DELETE CASCADE, quantity integer NOT NULL DEFAULT 0 CHECK (quantity >= 0), expiration_date date, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX inventory_lots_variant_id_idx ON inventory_lots(variant_id); CREATE INDEX inventory_lots_expiration_idx ON inventory_lots(expiration_date) WHERE expiration_date IS NOT NULL; -- 3. Extend movements for lot traceability ALTER TABLE inventory_movements DROP CONSTRAINT IF EXISTS inventory_movements_operation_check; ALTER TABLE inventory_movements ADD CONSTRAINT inventory_movements_operation_check CHECK ( operation IN ( 'reserve', 'release', 'confirm', 'set_available', 'lot_create', 'lot_adjust', 'lot_delete' ) ); ALTER TABLE inventory_movements ADD COLUMN lot_id uuid REFERENCES inventory_lots(id) ON DELETE SET NULL; ``` All changes are additive. No existing data is modified or deleted. ## Phase 2: Seed Existing Stock as Lots (for tracking-enabled products only) For each existing product where `expiration_tracking_enabled = true`: - Query `inventory_stock` for each variant - Create one `inventory_lots` entry per variant with: - `quantity = inventory_stock.available` - `expiration_date = NULL` (admin must add expiration dates) **Products with `expiration_tracking_enabled = false`**: no lots created. Existing variant-level stock table continues to be the source of truth. ## Phase 3: Code Deployment Deploy code with: - Feature flag `expiration_tracking = off` - New domain classes (LotService, InventoryLotRepository, new routes) - All existing paths still work via the flag check ## Phase 4: Admin Onboarding Admin can now: 1. Enable expiration tracking on products 2. Create inventory lots with expiration dates 3. See lot-level stock view **Admin instruction**: When enabling expiration tracking for a product, the admin should create inventory lots and set expiration dates before the product goes on sale. Existing stock without lots is not tracked. ## Phase 5: Flip Feature Flag After admin confirms all required products have lots configured: ``` FLAG_EXPIRATION_TRACKING = true ``` ## Rollback Plan 1. Set `FLAG_EXPIRATION_TRACKING = false` 2. Existing data is preserved (lots table + new columns) 3. Code paths revert to variant-level behavior 4. No data loss ## What Breaks if We Skip Feature Flag If `expiration_tracking` is always on: - Existing products without expiration_tracking flag → still create lots with null expiry dates - Checkout continues to work (null expiry = always valid) - No user-facing breakage The flag is for gradual rollout and operational safety, not a hard architectural requirement. ## No-Tracking Products After Migration Products with `expiration_tracking_enabled = false`: - Continue using `inventory_stock` table - `GET /inventory/:variantId/availability` uses variant-level query - No lots are created or queried - Behavior is byte-for-byte identical to pre-migration