# 06 — Phase 1 Implementation Plan: Retail Inventory

**Goal:** turn the existing quantity ledger into a retail-electronics inventory, by **extending** `app/Inventory` and `app/Menu`, not replacing them.

**Gate:** start only after Phase 0 (`05`) is approved and done: isolation, clean database, `techpos_test`, and a green baseline test run.

Every increment ships with:
- a migration;
- service + controller + routes;
- permissions in `app/Authorization/PermissionCatalog.php` plus a backfill migration (the existing pattern, e.g. `2026_08_18_110005_backfill_inventory_permissions.php`);
- audit events;
- feature tests in `tests/Feature/Inventory/*`;
- frontend pages and drawers with Vitest tests.

---

## Increment 1.0: Defect fixes and hardening (small, do first)

| # | Change | Files |
|---|---|---|
| 1 | Fail closed at commit when a mapped item has no stock row at the location (`not_configured`), instead of auto-creating it | `app/Inventory/InventoryMovementService.php` (`lockStock`, `applyConsumptionAs`) |
| 2 | Reject movements and consumption for archived items; revoke mappings on archive | `InventoryMovementService.php`, `InventoryItemService::archive`, `InventoryAvailabilityResolver.php` |
| 3 | Implement `stockAvailable()` as a dry-run through `InventoryAvailabilityResolver` | `app/Payments/OrderFinalizationCoordinator.php:177` |
| 4 | Make SKU generation location-unique (reserve against all variant SKUs at the location) | `app/Menu/Variants/VariantSkuGenerator.php`, `VariantBulkService.php` |
| 5 | Correct the stale "not wired" docblocks | `InventoryMovementService.php`, `InventoryDeductionService.php`, `InventoryConsumptionResolver.php` |

**Tests:** new regression tests for each fix. The full Inventory, Orders and Storefront suites must stay green.

## Increment 1.1: Item model extension and barcode scan

- **Migration on `inventory_items`:**
  - widen the `unit` CHECK (each, box, pack, metre, …);
  - add `tracking_mode` (`quantity`|`serialized`, default `quantity`);
  - add `costing_method` (`average`|`specific`), `default_cost_minor`, `currency`;
  - add `category`/`brand`/`model` (optional, or keep these on the product).
- **Migration on `product_variants`:** add `barcode` (unique per tenant+location when present).
- **Endpoint:** `GET api/v1/inventory/lookup?code=` does an exact match on variant SKU/barcode, item SKU/barcode and (after 1.2) unit serial/IMEI.
- **Variant presets:** `storage`, `colour`, `carrier/lock`, `condition`, `grade` in `config/menu_variant_presets.php`.
- **Import:** add `sku`, `barcode`, `opening_stock`, `cost` columns to `app/Menu/Import/MenuImportParser.php`.

## Increment 1.2: Serialized units (IMEI / serial, condition, acquisition cost)

- **Tables:** `inventory_units` and `inventory_unit_events` (schema in `03` §3).
  - Unique per tenant: `serial_number`, `imei_1`, `imei_2` (nullable).
  - Validate IMEI with a Luhn check plus a 15-digit rule.
- **Service:** `app/Inventory/InventoryUnitService.php`.
  - Operations: register unit(s) on receipt or adjustment, change condition/grade, write-off, list/search, history.
  - All operations go through `InventoryMovementService` so `on_hand` stays equal to the count of active units under the same lock.
- **Order integration:**
  - `order_line_units` binds units to lines.
  - `OrderInventoryBridge` requires unit selection for serialized items at commit and marks units `sold`.
  - Cancel/restock returns units to `in_stock` (or `returned` for re-grade).
- **Cost snapshot:** add `unit_cost_minor` and `cost_total_minor` to `order_lines`, filled at commit (unit cost for serialized items, average cost for quantity items).
- **UI:**
  - unit list on the inventory item detail page;
  - "Register units" drawer (scan-friendly, bulk paste);
  - unit detail with history;
  - POS unit picker/scanner on the cart line.
- **Permissions:** `inventory.units.manage`, `inventory.cost.view` (hide cost from sales staff).

## Increment 1.3: Cost on quantity items

- **Columns:**
  - `inventory_movements`: `unit_cost_minor`, `total_cost_minor`;
  - `inventory_location_stock`: `avg_unit_cost_minor`.
- **Average cost:** a weighted average recalculated on every receipt, under the stock lock.
- **Valuation endpoint:** `GET api/v1/inventory/valuation?location=` returns quantity × cost, plus the sum of serialized unit costs.

## Increment 1.4: Suppliers, purchase orders, goods receiving

- **Tables:**
  - `suppliers` (+ `supplier_contacts`);
  - `purchase_orders` and `purchase_order_lines` (draft → ordered → partially_received → received / cancelled);
  - `goods_receipts` and `goods_receipt_lines` (with serial capture for serialized lines).
- **Posting:** a receipt posts `receiving` movements with cost and creates units with `acquisition_source=purchase`.
- **Permissions:** `suppliers.manage`, `purchasing.manage`, reuse `inventory.receive`.
- **UI:** Suppliers, Purchase Orders, Receive against PO.

## Increment 1.5: Used-device intake (trade-in / buy from customer)

- **Form:** customer (CRM link), device details, IMEI/serial, condition/grade, agreed price.
- **Result:** creates a unit with `acquisition_source=trade_in|buy_from_customer` and `acquisition_cost_minor`.
- **Payout:** an offline payout record or a store-credit placeholder for Phase 3.
- **Compliance:** capture the seller's ID if required, as a CRM custom field or an intake field.

## Increment 1.6: Transfers

- **Tables:** `stock_transfers` and `stock_transfer_lines` (draft → in_transit → received / cancelled).
- **Posting:** writes the existing `transfer_out`/`transfer_in` movement types. Units move location and are `in_transit` while shipped.

## Increment 1.7: Reservations

- **Model:**
  - `inventory_reservations` (item or unit, qty, `source_type`/`source_id`, `expires_at`, `status`);
  - `inventory_location_stock.reserved`;
  - availability = `on_hand − reserved`.
- **Sources:** draft POS orders (optional), online checkout quotes/orders (fixes oversell), future repair tickets, layaway.
- **Expiry:** a scheduled sweep releases expired reservations.

## Increment 1.8: Reports and audit history

- Stock on hand, valuation, movement ledger (tenant-wide, filterable), unit history, margin by product/category/period, slow movers, reorder suggestions (from `reorder_quantity`).
- Audit-log viewer for inventory events (`audit_logs.view`).

## Increment 1.9: WordPress stock sync (after the connector source is available)

- `updated_since` on storefront product endpoints.
- Quantity exposure per connection policy.
- Outbox table and signed outbound webhook on stock/price changes.
- Integer quantity validation for serialized goods.

---

## Suggested order and estimates

| Increment | Depends on | Relative size |
|---|---|---|
| 0 (isolation + baseline) | owner approval | S |
| 1.0 defects | 0 | S |
| 1.1 item model + scan | 1.0 | S–M |
| 1.2 serialized units | 1.1 | **L** (core) |
| 1.3 costing | 1.1 | M |
| 1.4 purchasing/receiving | 1.2, 1.3 | L |
| 1.5 trade-in intake | 1.2, CRM | M |
| 1.6 transfers | 1.2 | M |
| 1.7 reservations | 1.2 | M |
| 1.8 reports | 1.3 | M |
| 1.9 WordPress sync | connector source, 1.7 | M |

## Definition of done (each increment)

- Migrations are additive and reversible, and do not break existing data.
- Backend feature tests (including concurrency where stock changes) and frontend tests pass against `techpos_test`.
- PHPStan level 5 is clean; Pint is clean; ESLint has 0 errors.
- Permissions and role templates are updated; audit events are emitted; idempotency keys are honoured.
- Docs are updated in `docs/techtitans/`.
