# Rule: Inventory & Stock Valuation

How UNO stores stock, and how the inventory tools value it. (Phase 2.)

## The model
- **Item = two levels:** `stock` (product) → `StockBar` (the SKU/barcode/UOM variant). The universal key is **`stkbar_id`**.
- **On-hand is a cached balance** held per `stkbar_id` × warehouse in `StockCalculateValue` (`qun_onhand`, `qun_reserved`, `qun_available`, `qun_on_order`). Do **not** re-sum movements to get current on-hand.
- **Cost is weighted-average**, stored per bar as `stk_Cost_LL` / `stk_Cost_USD` / `stk_Cost_FC`.
- **Stock value = qun_onhand × stk_Cost.** Reported in BOTH LBP and USD — never add the two currencies.
- The convenient report view **`vwStockListRpt`** already joins product + bar + on-hand + cost + warehouse + category. The tools build on it.

## Valuation rules the tools apply
- **Company-scoped**, and **services excluded** (`IsService = 0` — services hold no stock).
- **All stock bars are summed.** On-hand is tracked independently per bar (a pack-UOM bar and a base-UOM bar each carry their own real balance), so summing all bars is the true total. Do NOT filter to base-UOM only — that would drop real stock held in pack units.
- Sentinel "unbalanced" probe rows (`qun_onhand` ≈ −1e14) are filtered out.
- Verified company total ≈ **9.08B LBP / $6.02M** (a bakery/food manufacturer).

## Data-quality flags (report these as findings)
- **On-hand with zero cost** → valuation gap; the stock value is understated for those items.
- **Negative stock** (`qun_onhand < 0`) → oversold or a data issue; real in this DB, flag it, don't hide it.

## Reconciling to the books
Inventory value is a *stock-module* number; it won't always equal the inventory/stock **GL** account in `trial_balance` (postings can lag). Compare the two, but treat a gap as expected rather than an error.

## Movements, sales & profit
- Stock movements are invoice lines in **`vwInvoice`** (line grain, everything joined). Direction = `int_inout` ('I' in / 'O' out / 'N' non-stock like orders & quotations).
- **A "sale" is a specific set of document types**, not just any OUT: sales OUT = `SA` (Sales Invoice), `SL` (Sales Local), `DN` (Delivery Note), `CS` (cash sale). Purchases IN = `PU`, `PL`, `GR`. Returns = `SR`/`RS` (sales), `PR`/`RP` (purchase). Transfers (`TI`/`TO`), damage (`DA`), assembly (`AI`/`AO`), unpacking (`UP`) are NOT sales — the sales tools exclude them.
- **Cost of goods sold** = each sale line's FROZEN cost `inc_cost_LL` / `inc_cost_USD` (stored at the moment of sale, both currencies) × qty — not today's cost.
- **Revenue currency:** `inc_price` / `inc_lineTotal` are in the **document currency** (`inp_acc_curr`: 01=LBP, 02=USD, 03+=FC), NOT always LBP. The tools convert revenue to LBP and USD using the invoice rate (`inp_rate` = LBP/USD, `inp_rateFC` = FC/USD). Never compare raw `inc_lineTotal` to `inc_cost_LL` — that mixes currencies.

## Tools
- **`inventory_value`** — total stock value (LBP & USD), `by=warehouse|category`, data-quality flags.
- **`stock_on_hand`** — per-item qty + value, availability, `by_warehouse`, `below_reorder`, search/filter.
- **`negative_stock`** / **`stockouts`** — oversold items / out-of-stock (optionally selling_only).
- **`stock_movements`** — stock card for one item (signed in/out, price, cost, party).
- **`top_selling_items`** — best/worst sellers by revenue, qty or gross profit; by item or category.
- **`product_profitability`** — revenue, COGS, gross profit & margin % per item/category (LBP & USD), loss-making flag.
- **`inventory_turnover`** — COGS ÷ inventory value + days of inventory (DIO), overall or by category.
- **`slow_moving_stock`** — items not sold in N days and the cash value locked up.
- **`reorder_suggestions`** — velocity-based (item reorder points are usually unset): avg daily sales → days of cover → suggested order qty.
- **`item_price`** — selling price vs cost, markup %, below-cost flag.
- (`expiring_stock` not yet built — expiry is lot-level, needs a lot-balance model.)

See `docs/PHASE2_INVENTORY_REFERENCE.md` for the full schema and SQL recipes.
