# 03 — Inventory Gap Analysis (TechTitans priority #1)

**Business need:** TechTitans sells new and used computers, phones, laptops and tablets, plus accessories, and holds repair parts. It needs:

- per-unit (IMEI/serial) tracking with condition and acquisition cost;
- quantity tracking for accessories and parts;
- purchasing, receiving, transfers and reservations;
- profit reporting;
- WordPress stock sync.

Paths are relative to `backend/`.

---

## 1. What exists today

### Data model

| Table | Purpose | Notes |
|---|---|---|
| `inventory_items` | Tenant-wide stock item: `name`, `sku`, `barcode`, `unit`, `status` | `sku` and `barcode` unique per tenant. `unit` is **CHECK-limited to `'each'`**. **No cost columns** |
| `inventory_location_stock` | One row per (location, item): `on_hand` DECIMAL(12,3), `low_stock_threshold`, `reorder_quantity`, `prevent_sale_when_out_of_stock` | `on_hand` may go negative. **No `reserved` column** |
| `inventory_movements` | Append-only ledger: `sequence_no`, `quantity_delta`, `balance_after`, `movement_type`, polymorphic `reference_type/id`, `reason_code`, `note`, actor snapshot, `request_id`, `original_consumption_event_id` | Unique `(tenant, location, item, sequence_no)`. **No cost** |
| `inventory_stock_alerts` | Low/out-of-stock episodes, one active per type | — |
| `variant_inventory_consumptions` | Variant to item mapping with `quantity_per_unit` (recipe/BOM layer) | Retail use is 1 variant → 1 item × 1.000 |
| `modifier_inventory_consumptions` | Modifier to item mapping | Restaurant |

Migrations are under `database/migrations/2026_08_18_1100**` and `2026_08_23_12000*`.

**Movement types** (`app/Models/InventoryMovement.php:33`, DB CHECK `chk_inv_mov_type`):

- `sale`, `component_consumption`, `modifier_consumption`
- `refund_restock`, `order_restock`
- `receiving`, `manual_adjustment`, `waste`, `damage`, `staff_meal`
- `transfer_in`, `transfer_out`
- `physical_count_correction`

Manual types are `receiving`, `manual_adjustment`, `waste`, `damage` and `staff_meal`. **`transfer_in`, `transfer_out`, `refund_restock` and `sale` have no writer.**

### Services and APIs

- **Services** in `app/Inventory/`:
  - `InventoryMovementService` handles manual, count, consumption and restock moves. It uses row locks, sorted lock order, bcmath and idempotency.
  - The other services are `InventoryStockService`, `InventoryItemService`, `InventoryWorkspaceService`, `InventoryAvailabilityResolver`, `InventoryConsumptionService` and `Alerts/*`.
- **Endpoints** are in `routes/api.php:1105–1139` under `api/v1/menu/inventory/*` and `api/v1/menu/locations/{location}/inventory/*`. Item CRUD and archive, location stock settings, movements GET/POST, consumption mappings, availability, plus `dashboard/inventory-alerts`.
- **Permissions:** `inventory.view`, `inventory.manage`, `inventory.receive`, `inventory.adjust`, `inventory.count`.
- **Order integration** goes through `app/Orders/OrderInventoryBridge.php`:
  - POS commit: `OrderWriteController.php:519`
  - online finalize: `OrderFinalizationCoordinator.php:198`
  - cancel restock: `OrderCancellationService.php:108`
- **Frontend:**
  - `src/pages/menu/InventoryListPage.tsx` and `InventoryDetailPage.tsx`
  - drawers for receive, adjust, count, settings and mapping in `src/components/menu/inventory/*`
  - `InventoryAttentionCard` on the dashboard
- **Tests:** `tests/Feature/Inventory` has 17 files and about 185 tests, including concurrency tests. There are also `tests/Feature/Orders/OrderInventoryBridgeTest.php` and `OrderCancellationRestockTest.php`.

## 2. Requirement-by-requirement gap

| Requirement | Today | Verdict | What to do |
|---|---|---|---|
| Products, variants, SKU | Products, variants with option matrix (`product_variant_options*`, `CombinationKey`), `SkuPolicy`, `VariantSkuGenerator`, three price layers | **Reuse** | Add retail variant presets (storage, colour, carrier, grade) in `config/menu_variant_presets.php`. Fix the SKU-generator uniqueness bug |
| Barcode | `inventory_items.barcode` stored and LIKE-searchable. **No** barcode on product or variant, **no** scan lookup | **Extend** | Add `barcode`/`upc` to variants and an exact-match `GET …/lookup?code=` endpoint covering SKU, barcode, IMEI and serial |
| Individual IMEI / serial tracking | None anywhere | **New** | New `inventory_units` table (see §3) |
| Condition: new / used / refurbished (+ grade) | None | **New** | Per-unit `condition` and `grade`. Optionally a variant option for sellable "condition" SKUs |
| Individual acquisition cost (used devices) | No cost data at all | **New** | `inventory_units.acquisition_cost_minor` plus source (purchase order, trade-in, buy-from-customer) |
| Cost for quantity items (accessories, parts) | None | **New** | Weighted-average cost on `inventory_location_stock` (`avg_unit_cost_minor`); `unit_cost_minor` and `total_cost_minor` on movements |
| Inventory locations | Per-location stock exists. Locations are tenant-level | **Reuse** | Optional sub-locations (bins or shelves) later |
| Suppliers | None | **New** | `suppliers` table plus contacts. Could reuse CRM patterns |
| Purchase orders | None | **New** | `purchase_orders`, `purchase_order_lines` |
| Goods receiving | Only a manual `receiving` movement | **Extend + New** | `goods_receipts` and lines (partial receipts, serial capture at receipt), posting `receiving` movements with cost |
| Transfers | Movement types exist, no workflow | **Extend + New** | `stock_transfers` and lines (draft, in transit, received), posting `transfer_out` and `transfer_in`. Units change location |
| Adjustments / counts | `applyManual`, `applyCount` | **Reuse** | Add serial-level count (scan session). Retire `staff_meal` from the retail UI |
| Stock reservations | None (deducted only at commit or finalize) | **New** | `inventory_reservations` (source: draft order, repair ticket, online quote, layaway) with expiry. Available = on_hand − reserved. Unit `status=reserved` |
| Cost and profit | None | **New** | Snapshot `unit_cost_minor` and `cost_total_minor` on `order_lines` at commit (FIFO or specific-unit for serialized, average for quantity). Margin reports |
| Inventory audit history | Quantity ledger plus `audit_logs` events | **Extend** | Per-unit event history (`inventory_unit_events`), a tenant-wide movement ledger page and an audit-log viewer |
| WordPress stock sync | Pull-only `stock_status` and `purchasable` (no quantities, no delta) | **Extend** | `updated_since` delta and quantity exposure per connection policy. Outbound webhook or outbox on stock change (see `04`) |
| Returns / RMA | Restock reasons exist (`refund`, `void`), but no refund flow | **New (with POS)** | Return document links unit and line, then restocks with condition re-grade |
| Repair parts consumption (future) | The BOM mapping pattern exists | **Reuse later** | Repair ticket consumes items or units through the same movement service |

## 3. Proposed architecture (extend, do not replace)

Keep `inventory_items`, `inventory_location_stock` and `inventory_movements` as the **quantity source of truth**. Add a **unit layer** for serialized goods. The ledger stays authoritative for counts, and units give identity.

```
products ─< product_variants ──(variant_inventory_consumptions 1:1)──> inventory_items
                                                                         │ tracking_mode: quantity | serialized
                                                                         │ default_cost / costing_method
inventory_location_stock (on_hand, reserved, avg_unit_cost)  <───────────┤
inventory_movements (+ unit_cost_minor, total_cost_minor, document ref) <┤
inventory_units (serialized only) ───────────────────────────────────────┘
   id, tenant_id, inventory_item_id, location_id,
   serial_number, imei_1, imei_2, (unique per tenant)
   condition (new|used|refurbished|open_box|for_parts), grade (A|B|C…),
   status (in_stock|reserved|sold|in_transit|in_repair|returned|rma|written_off),
   acquisition_cost_minor, currency, acquisition_source (purchase|trade_in|buy_from_customer|transfer),
   source_document (goods_receipt_line | trade_in | …), supplier_id / customer_id (seller),
   order_line_id (when sold), warranty_until, notes, custom attributes json
inventory_unit_events  (append-only per-unit history, mirrors audit style)
inventory_reservations (item or unit, qty, source_type/id, expires_at, status)
suppliers, purchase_orders(+lines), goods_receipts(+lines), stock_transfers(+lines)
order_lines  + unit_cost_minor, cost_total_minor ; order_line_units (line ↔ unit binding)
```

**Rules:**
- For a serialized item, `on_hand` must equal the count of its units with status in_stock or reserved at that location. Enforce this inside the same locked transaction as the movement.
- Selling a serialized item requires picking or scanning a specific unit. The bridge binds the unit to the order line and posts the movement with that unit's cost.
- Every write goes through `InventoryMovementService` (extended) so the locking, idempotency, audit and alert behaviour is inherited.

**Future-module readiness:**
- `inventory_units.customer_id` and the `in_repair` status, plus reservations sourced from `repair_ticket`, prepare for repairs.
- Customer-owned devices should live in a separate `customer_devices` table that can be linked to a unit when sold.
- IPTV and phone plans are non-stock services: model them as `product_type=service` with no inventory mapping.

## 4. Components: reuse / extend / new

**Reuse as is:**
- the movement ledger engine (locks, sequence_no, idempotency, audit);
- per-location stock settings and the alert pipeline;
- the availability resolver;
- `OrderInventoryBridge` and cancel restock;
- products, variants, SKU, pricing and tax;
- media and catalogues;
- the CSV import framework;
- the permission split;
- the frontend inventory list, detail and drawers.

**Extend:**
- `inventory_items`: widen `unit`; add `tracking_mode` and default cost.
- `inventory_location_stock`: add `reserved`.
- `inventory_movements`: add cost columns and document references.
- Barcode on variants, plus a lookup endpoint.
- Import: add SKU, barcode, stock, cost and serial columns.
- Storefront: stock delta and quantities.
- Fix defects 1–4 (`02`).
- Rename `/menu/inventory` to `/inventory` in the UI.

**New:**
- the units, unit events and reservations tables;
- suppliers, purchase orders, goods receipts and transfers;
- cost of goods sold (COGS) on order lines;
- valuation and margin reports;
- trade-in / buy-from-customer intake;
- the unit picker and scanner in the POS;
- returns/RMA;
- outbound stock sync.
