Files
mercadodevida/project/specs/expiration-tracking/MIGRATION.md
2026-08-17 22:23:10 +02:00

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_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