# EXPIRATION TRACKING — TASKS.md ## Backend ### EXP-BE-001 **ID**: EXP-BE-001 **Title**: Product expiration_tracking_enabled column **Goal**: Add boolean column to catalog_products **Why**: Products need to declare whether they require expiration tracking **Dependencies**: None **Applications**: Backend **Modules**: catalog_products table, Product domain, ProductRepository **Database impact**: ALTER TABLE catalog_products ADD COLUMN expiration_tracking_enabled boolean NOT NULL DEFAULT false **API contracts**: GET /products/:id returns field; PATCH /products/:id accepts field **Permissions**: admin **Implementation**: Add to NewProduct interface, ProductPatch type, pg-product-repository **Tests**: Unit — default false; Integration — column exists with correct default **Migration**: See MIGRATION.md Phase 1 **Expected blast radius**: Low — only affects new product queries **Definition of Done**: Column exists, defaults to false, persists on create/update ### EXP-BE-002 **ID**: EXP-BE-002 **Title**: InventoryLot domain and repository **Goal**: New InventoryLot entity and InventoryLotRepository port + PgInventoryLotRepository **Why**: Core data model for lot-level stock **Dependencies**: EXP-BE-001 **Applications**: Backend **Modules**: inventory/domain, inventory/infrastructure **Database impact**: CREATE TABLE inventory_lots (see DESIGN.md) **API contracts**: None yet (repository only) **Permissions**: N/A **Implementation**: InventoryLot interface, CreateLotCommand, InventoryLotRepository port, PgInventoryLotRepository **Tests**: Unit — CRUD operations; Integration — table constraints, FK **Expected blast radius**: Low — new table, no existing data touched **Definition of Done**: Lot CRUD works, FK to catalog_variants enforced, CHECK constraint on expiration_date ### EXP-BE-003 **ID**: EXP-BE-003 **Title**: LotService — create, update, delete, FEFO allocation **Goal**: Domain service handling lot business rules **Why**: Encapsulates expiration validation and FEFO logic **Dependencies**: EXP-BE-002 **Applications**: Backend **Modules**: inventory/application **API contracts**: None (domain service) **Permissions**: N/A **Implementation**: LotService class with createLot (validates expiry required), allocateLots (FEFO), adjustQuantity **Tests**: Unit — missing expiry rejected; past expiry rejected; FEFO order; partial lot allocation **Expected blast radius**: Low **Definition of Done**: LotService methods have correct business rules; FEFO allocates from earliest expiry first ### EXP-BE-004 **ID**: EXP-BE-004 **Title**: InventoryLots API routes **Goal**: CRUD endpoints for lots + availability query with filter **Why**: Admin needs to manage lots; frontend needs to display them **Dependencies**: EXP-BE-003 **Applications**: Backend **Modules**: inventory/api **API contracts**: GET /inventory/lots, POST /inventory/lots, PATCH /inventory/lots/:id, DELETE /inventory/lots/:id (all admin) **Permissions**: admin role required **Implementation**: New route registrations in inventory module; serialize LotService results **Tests**: Integration — CRUD round-trip; filter=expiring|expired|all **Expected blast radius**: Low — new routes **Definition of Done**: All 5 routes respond correctly; filter parameters work; auth enforced ### EXP-BE-005 **ID**: EXP-BE-005 **Title**: InventoryService — integrate LotService for expiration products **Goal**: Extend existing InventoryService to delegate to LotService when expiry is enabled **Why**: Preserve existing InventoryServicePort contract while adding expiration support **Dependencies**: EXP-BE-001, EXP-BE-003 **Applications**: Backend, Checkout **Modules**: inventory/application, checkout **API contracts**: Existing InventoryServicePort contract unchanged **Permissions**: N/A **Implementation**: In InventoryService, check product.expiration_tracking_enabled; if true, use LotService.getAvailableStock and LotService.allocateLots; feature flag gates behavior **Tests**: Unit — delegation to LotService for expiry products; existing path for non-expiry products **Expected blast radius**: Checkout uses InventoryServicePort — must not break **Definition of Done**: Checkout reserve/confirm still works for both expiry and non-expiry products; FEFO used for expiry products ### EXP-BE-006 **ID**: EXP-BE-006 **Title**: Extend inventory_movements with lot operation types **Goal**: Track lot_create, lot_adjust, lot_delete in movement audit log **Why**: Full auditability of lot changes **Dependencies**: EXP-BE-002 **Applications**: Backend **Modules**: inventory/infrastructure, security **Database impact**: ALTER TABLE inventory_movements — new operation types + optional lot_id FK **API contracts**: Movement audit reflects lot operations **Permissions**: N/A **Implementation**: Add operation types in pg-inventory-repository insertMovement calls **Tests**: Integration — movements logged with correct operation type **Expected blast radius**: Low **Definition of Done**: Lot mutations produce audit entries ## Database ### EXP-DB-001 **ID**: EXP-DB-001 **Title**: Run expiration tracking migrations **Goal**: Apply all DB changes from MIGRATION.md Phase 1 **Why**: Infrastructure for lot model **Dependencies**: EXP-BE-001 (column on catalog_products), EXP-BE-002 (lots table) **Applications**: Database **Modules**: N/A **Database impact**: See MIGRATION.md Phase 1 **API contracts**: N/A **Permissions**: DBA **Implementation**: Add migration file or run raw SQL against dev DB; apply via docker-compose migration pipeline **Tests**: Verify schema after migration **Expected blast radius**: Low — additive changes **Definition of Done**: Migration runs without error; new columns/tables exist with correct constraints ## Admin ### EXP-ADM-001 **ID**: EXP-ADM-001 **Title**: Product Editor — expiration tracking toggle **Goal**: Add checkbox to General tab: "Track expiration dates" **Why**: Admin configures per-product policy **Dependencies**: EXP-BE-001 **Applications**: Admin **Modules**: ProductEditor, GeneralSection **Database impact**: None (uses EXP-BE-001) **API contracts**: PATCH /catalog/products/:id **Permissions**: products.write **Implementation**: Add toggle to GeneralSection; saves { expiration_tracking_enabled: boolean } on save **Tests**: Toggle saves correctly; shows/hides expiration UI based on state **Expected blast radius**: Low **Definition of Done**: Admin can enable/disable expiry tracking per product; toggle persists ### EXP-ADM-002 **ID**: EXP-ADM-002 **Title**: Inventory — lot-level stock view **Goal**: Show lots table for expiry-tracking products in inventory page **Why**: Operational visibility into expiration state **Dependencies**: EXP-BE-004 **Applications**: Admin **Modules**: InventorySection, inventory page **Database impact**: None **API contracts**: GET /inventory/lots?variant_id=X **Permissions**: inventory.read **Implementation**: Extend InventorySection to show lots when product has expiry enabled; compute status (VALID/NEAR_EXPIRY/EXPIRED) client-side from FLAG_EXPIRY_WARNING_DAYS **Tests**: Lot table renders correctly; status computed from dates **Expected blast radius**: Low **Definition of Done**: Lots displayed with correct quantity, date, and status badge ### EXP-ADM-003 **ID**: EXP-ADM-003 **Title**: Inventory — lot create/edit/delete **Goal**: Inline lot management in inventory section **Why**: Admin must be able to add/update/remove lots **Dependencies**: EXP-BE-004 **Applications**: Admin **Modules**: InventorySection **Database impact**: None **API contracts**: POST/PATCH/DELETE /inventory/lots/:id **Permissions**: inventory.write **Implementation**: Add lot form (quantity, expiration date); inline edit on lot row; delete confirmation **Tests**: Create lot with required expiry date; edit quantity; delete lot; 422 shown for missing expiry **Expected blast radius**: Low **Definition of Done**: Admin can fully manage lots; validation errors shown correctly ### EXP-ADM-004 **ID**: EXP-ADM-004 **Title**: Inventory filters — expiring, expired, all, no-expiry **Goal**: Filter inventory page by expiration status **Why**: Operational efficiency for stock management **Dependencies**: EXP-BE-004 **Applications**: Admin **Modules**: inventory page **Database impact**: None **API contracts**: GET /inventory/lots?filter=expiring|expired|all|no-expiry **Permissions**: inventory.read **Implementation**: Add filter tabs/dropdown to inventory page; calls API with filter param **Tests**: Each filter returns correct lot subset **Expected blast radius**: Low **Definition of Done**: Filters work; filter state reflected in URL or UI ## QA ### EXP-QA-001 **ID**: EXP-QA-001 **Title**: Expiration tracking regression tests **Goal**: Ensure existing checkout flow is unbroken **Why**: No regressions on existing products **Dependencies**: EXP-BE-005, EXP-DB-001 **Applications**: QA **Modules**: E2E tests **Tests**: - Normal product (non-expiry) still checks out correctly - Expiry product with no lots: unavailable - Expiry product with valid lot: available and reservable - FEFO: earliest expiry lot consumed first - Expired lot: contributes zero sellable units **Expected blast radius**: N/A **Definition of Done**: All regression tests pass ### EXP-QA-002 **ID**: EXP-QA-002 **Title**: Expiration tracking unit/integration tests **Goal**: Comprehensive test coverage for all new domain code **Why**: Business rules must be correct **Dependencies**: EXP-BE-003, EXP-BE-004, EXP-BE-005 **Applications**: QA **Modules**: Backend test suite **Tests**: See TESTS.md **Expected blast radius**: N/A **Definition of Done**: 100% pass rate on expiration-specific tests --- ## Task Summary Table | Task | Layer | Feature | Depends On | Risk | Parallel | |------|-------|---------|-----------|------|---------| | EXP-BE-001 | Backend | Product expiry column | — | Low | * | | EXP-BE-002 | Backend | Lot model + repository | — | Low | * | | EXP-BE-003 | Backend | LotService domain | EXP-BE-002 | Low | * | | EXP-BE-004 | Backend | Lot API routes | EXP-BE-003 | Low | * | | EXP-BE-005 | Backend | InventoryService + LotService | EXP-BE-001, EXP-BE-003 | Medium | * | | EXP-BE-006 | Backend | Movement audit for lots | EXP-BE-002 | Low | EXP-BE-004 | | EXP-DB-001 | DB | Run migrations | EXP-BE-001, EXP-BE-002 | Low | * | | EXP-ADM-001 | Admin | Product expiry toggle | EXP-BE-001 | Low | * | | EXP-ADM-002 | Admin | Lot stock view | EXP-BE-004 | Low | * | | EXP-ADM-003 | Admin | Lot CRUD | EXP-BE-004 | Low | * | | EXP-ADM-004 | Admin | Expiry filters | EXP-BE-004 | Low | * | | EXP-QA-001 | QA | Checkout regression | EXP-BE-005, EXP-DB-001 | Medium | After backend | | EXP-QA-002 | QA | Domain unit tests | All BE tasks | Low | With backend | **Parallel group**: EXP-BE-001 and EXP-BE-002 can run in parallel. BE-003 depends on BE-002. BE-004 and BE-006 depend on BE-003. BE-005 depends on BE-001 + BE-003. **Recommended order**: 1. EXP-BE-001 + EXP-BE-002 (parallel, no dependencies) 2. EXP-BE-003 (depends on BE-002) 3. EXP-BE-004 + EXP-BE-006 (depend on BE-003, parallel) 4. EXP-DB-001 (run after BE-001 + BE-002 code is deployed) 5. EXP-BE-005 (depends on BE-001 + BE-003) 6. EXP-ADM-001 (depends on BE-001) 7. EXP-ADM-002 + EXP-ADM-003 + EXP-ADM-004 (depend on BE-004, parallel) 8. EXP-QA-001 + EXP-QA-002 (after all backend + admin) **High-risk tasks**: EXP-BE-005 (changes InventoryService contract internal behavior, affects checkout — thorough regression testing required). **MVP boundary**: EXP-BE-001 through EXP-BE-005 + EXP-DB-001 + EXP-ADM-001 through EXP-ADM-004. EXP-BE-006 (audit) is low priority for MVP. EXP-QA-002 is bundled with implementation. EXP-QA-001 is blocking go-live.