# 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
