# Inventory Management B8 Design

**Date:** 2026-07-05
**Slug:** `inventory-management-b8`

## Scope

Task B8 closes two accountant-export gaps:

1. **Form 6111 stock-line composition**
   Expense receipts carrying `sourceMetadata.stockLines` represent inventory received into stock, not ordinary operating expense. Their ex-VAT net amount must flow to a dedicated inventory / cost-of-sales source in Form 6111 composition instead of inflating the expense category bucket.

2. **Inventory count workbook (`מפקד מלאי`)**
   Accountants need a year-end physical count sheet that reconciles current on-hand stock to the annual filing. Zync should generate a direct XLSX workbook from `stock_position`, enriched with product and location labels, with blank counted/variance columns for manual counting.

3. **Inventory item movement history (`/inventory/:itemId`)**
   Operators need a stock-ledger view for tracked products showing current stock, location balances, and recent movement rows pulled from `stock_position` and `stock_movement`.

## Recommended Approach

**Approach 1: Reuse accountant export seams with one new source kind and one direct XLSX route**

| Dimension | Assessment |
|-----------|------------|
| Robustness | Reuses existing Form 6111 aggregation and shared XLSX sanitization helpers; smallest blast radius. |
| Long-term | Keeps inventory-aware tax behavior explicit via `inventory_stock` instead of overloading ordinary expense categories. |
| Scalability | Direct workbook generation is cheap because it reads current `stock_position` only; no async job/R2 path needed yet. |
| Performance | Single query + in-memory workbook generation; bounded by tracked SKUs/locations. |
| Reversibility | Two-way door; if inventory exports later need job history, the direct route can be wrapped in the existing `accountant_export_jobs` flow. |

**Weakness:** direct inventory-count downloads do not appear in the recent-exports job list.

**Recommended: Approach 1** — it fixes the statutory/accountant-facing gap without expanding schema or job types. The only new accounting seam is `inventory_stock`, which remains editable through the existing CoA mapping UI.

## Contract

### Form 6111 composition

- Add source kind `inventory_stock`.
- Default CoA seed maps `inventory_stock -> account 4900`, with `form6111_code = 2000`.
- Expense aggregation:
  - compute ex-VAT deductible expense amount exactly as before;
  - detect stock lines from `sourceMetadata.stockLines`, `stock_lines`, `inventory.stockLines`, or `receiveIntoStock.lines`;
  - resolve each stock line’s net amount from explicit net fields first, then gross-minus-VAT fallbacks;
  - subtract the stock-line net amount from the ordinary category bucket;
  - add the same amount to `inventory_stock`.

### Inventory count workbook

`GET /api/reports/accountant/inventory-count/xlsx?asOf=YYYY-MM-DD`

- Requires `accountant:export`.
- Reads positive `stock_position` rows for the tenant.
- Joins `products.stock_item_id` for product name and `stock_locations` for location labels.
- Workbook columns:
  - running number
  - location code
  - location name
  - product
  - stock item id
  - quantity in books
  - average unit cost
  - book value
  - counted quantity (blank)
  - quantity variance (blank)
  - notes (blank)

### Inventory item movement history

`GET /api/products/:id/inventory-history`

- Requires `inventory:read`.
- Returns `404` when the product does not exist for the tenant or is not a tracked stock item.
- Response includes:
  - `product`
  - `summary`: `qtyOnHand`, `inventoryValue`, `locationCount`, `lastMovementAt`
  - `locations[]`: `locationId`, `locationName`, `locationCode`, `qtyOnHand`
  - `movements[]`: `id`, `kind`, `qtyDelta`, `unitCost`, `occurredAt`, `locationId`, `locationName`, `locationCode`, `holderRef`

`/inventory/:itemId`

- Loads the API above inside the app shell.
- Renders KPI cards for current stock, inventory value, locations, and last movement.
- Renders a location list and a newest-first movement table.
- Empty state copy for a tracked product with no movement rows: `No stock movements yet.`

Rationale: B10 exposes the persisted stock ledger already maintained by receipts and invoice issue flows, so warehouse/accounting users can inspect one SKU without exporting reports. (2026-07-05 B14: the by-id history route now uses the dedicated inventory read permission instead of invoice read.)

## Test Strategy

- Pure unit test for expense partitioning with stock lines.
- Pure unit test for composed Form 6111 rows mapping `inventory_stock` to code `2000`.
- XLSX round-trip test for the inventory count workbook structure and RTL flag.
