# inventory-visibility

## Overview

Provides read-only visibility into current stock levels across all sites. Users open an inventory list of `StockLevel` rows and see the quantity columns `onHand`, `reserved`, `blocked`, plus a derived `available` (`available = onHand - reserved - blocked`) computed on the client. Users narrow the list by Item (SKU or item name, partial match) or by Site.

**Site/StorageLocation assumption.** This application is configured so that each Site has exactly one StorageLocation (Site : StorageLocation = 1 : 1). Under this assumption, `StockLevel` rows are already at the Item × Site grain — no roll-up is needed. The implementation therefore relies on the TailorDB-auto-generated `stockLevels` collection query directly (no custom server-side aggregation resolver). If a future Site grows multiple StorageLocations, a custom aggregation query will need to be introduced; the auto-generated `stockLevels` collection's input / output shape would be the contract such a query should mirror.

Item × Site combinations without an existing `StockLevel` row are **excluded** from the list — no synthetic zero rows are produced. This flow does not include any inventory mutations (no adjustments, transfers, or cycle counts) and does not yet enforce per-actor site scoping — every authorized actor sees every site in the MVP.

## Actors Involved

- [Inventory Manager](../../actor/inventory-manager.md) — viewer (primary)
- [Store Staff](../../actor/store-staff.md) — viewer
- [Purchaser](../../actor/purchaser.md) — viewer (consulted during purchase planning)
- [Approver](../../actor/approver.md) — viewer (consulted during PO approval)
- [Admin](../../actor/admin.md) — viewer

## Flow Diagram

```mermaid
sequenceDiagram
    participant U as Authorized Viewer
    participant S as System
    Note over U: Any of: Inventory Manager / Store Staff / Purchaser / Approver / Admin

    U->>S: Open inventory list
    Note over S: Site : StorageLocation = 1 : 1 in this app — one StockLevel row per (Item, Site)
    S->>S: Fetch existing StockLevel rows (auto-generated stockLevels collection)
    Note over S: available = onHand - reserved - blocked is computed on the client per row
    S-->>U: Default list (one row per existing StockLevel, paginated, server-side cursor)

    opt Filter by Item
        U->>S: Enter SKU or item name (partial match)
        S->>S: Resolve matching itemIds via items(query) with a case-insensitive StringFilter.regex ("(?i)<term>") on sku OR name (the StringFilter.contains operator is case-sensitive, so the regex form is used instead)
        S-->>U: stockLevels(query: itemId.in = matched ids)
    end

    opt Filter by Site
        U->>S: Pick a Site tab (Site picker writes the underlying storageLocationId since 1:1)
        S-->>U: stockLevels(query: storageLocationId.eq = chosen id)
    end

    alt Filtered result is empty
        S-->>U: Empty state shown (filters preserved)
    end

    U->>S: (Optional) Sort visible rows (auto-gen supports onHand / reserved / blocked / createdAt / updatedAt)
    Note over S: SKU / item name / site name are NOT sort-able in MVP (out of StockLevelOrderFieldEnum)
    S-->>U: Sorted view
```

## Stories

- [Browse Inventory List](./story/inventory-manager--browse-inventory-list.md)
- [Filter Inventory By Item](./story/inventory-manager--filter-inventory-by-item.md)
- [Filter Inventory By Site](./story/inventory-manager--filter-inventory-by-site.md)
- [Check Item Availability](./story/store-staff--check-item-availability.md)
