3.7 KiB
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)
-- 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_stockfor each variant - Create one
inventory_lotsentry per variant with:quantity = inventory_stock.availableexpiration_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:
- Enable expiration tracking on products
- Create inventory lots with expiration dates
- 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
- Set
FLAG_EXPIRATION_TRACKING = false - Existing data is preserved (lots table + new columns)
- Code paths revert to variant-level behavior
- 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_stocktable GET /inventory/:variantId/availabilityuses variant-level query- No lots are created or queried
- Behavior is byte-for-byte identical to pre-migration