# Inventory Reports Design

Audience: AI coding agents first.

**Date:** 2026-07-05
**Slug:** `inventory-reports`

## Scope

Task B12 adds a dedicated inventory reporting surface at `/inventory/reports` for invoice-enabled tenants. The page combines three accounting-facing workflows already implied by the inventory ledger:

1. current stock valuation from `stock_position`
2. period cost-of-goods-sold from negative `stock_movement` rows
3. direct export of the existing accountant inventory-count workbook

Rationale (2026-07-05 B14): the reporting surface now follows dedicated inventory RBAC so stock/report access can be granted independently of invoice list access.

## Contract

### App route

`/inventory/reports`

- Requires the same inventory access boundary as `/inventory`: authenticated user session with `inventory:read`
- Shows four KPI cards:
  - stock items
  - locations
  - inventory value
  - COGS in period
- Shows two report sections:
  - `Inventory valuation` table from current positive `stock_position` rows
  - `Cost of goods sold` table from negative `stock_movement` rows inside the selected period
- Exposes a `Count sheet date` control and `Export count sheet` action that reuses `GET /api/reports/accountant/inventory-count/xlsx?asOf=YYYY-MM-DD`
- Export button is visible on the page but only enabled for users with `accountant:export`

### API route

`GET /api/inventory/reports?from=YYYY-MM-DD&to=YYYY-MM-DD`

- Requires `inventory:read`
- Returns:

```ts
{
  summary: {
    skuCount: number
    locationCount: number
    qtyOnHand: number
    inventoryValue: number
    cogsTotal: number
  }
  valuationRows: InventoryListItem[]
  cogsRows: Array<{
    stockItemId: string
    productName: string
    qtySold: number
    cogsAmount: number
    lastSoldAt: string | null
  }>
}
```

- `valuationRows` reuse the stock-list query shape
- `cogsRows` are grouped by tracked item over negative stock movements in the selected period
- `cogsAmount` uses persisted `cogs_amount` when available and falls back to `abs(qty_delta * unit_cost)` when needed

### Direct inventory report exports

`GET /api/reports/inventory`, `/api/reports/inventory/export.csv`, and `/api/reports/inventory/export.xlsx` provide a finance-oriented row export alongside the canonical app report API.

- JSON requires `reports:read`; CSV and XLSX require `reports:export`.
- Query accepts optional `asOf`, `from`, `to`, and tenant-scoped `locationId`; invalid ranges return `400`.
- Rows combine current tracked `stock_position` balances with period `stock_movement.cogs_amount` grouped by item and location.
- CSV and XLSX cells MUST pass through shared spreadsheet-formula sanitization.
- Filenames use `inventory-count-YYYY-MM-DD`.

Rationale (2026-07-11 merge reconciliation): keep one canonical UI implementation while preserving the PR's distinct CSV/XLSX finance export contract.

## Rationale

The stock list and item ledger already expose current positions and per-SKU drill-down. B12 adds the accounting summary layer without introducing a second inventory ledger or a new export pipeline.

## Test Strategy

- API route test for `/api/inventory/reports`
- App route test for `/inventory/reports`
- UI render test covering valuation section, COGS section, and count-sheet export affordance
- Direct CSV/XLSX tests covering columns and spreadsheet-formula sanitization
