# Reporting & KPI Foundation v44

v44 introduces the dedicated `reporting` read model.

Transactional modules remain the business source of truth. Reporting tables are derived,
rebuildable projections and must never silently repair Sales, Inventory, Cash, Purchasing,
Payables or CRM source data.

## Daily projections

### reporting.daily_sales_metrics
Per tenant / branch / business date / currency:
- completed sale count
- posted return count
- customer count
- sales, discount, returns and net sales
- sale COGS
- restock return COGS reversal
- net COGS
- gross profit
- captured payments
- completed refunds

Sales use `sales.sales.business_date`.

Return and provider refund activity currently use the UTC calendar date of their posted /
resolved timestamp because return-specific branch business-date persistence is not yet
available. This is an explicit known boundary, not a hidden timezone assumption.

### reporting.daily_product_metrics
Per variant / branch / business date / currency:
- sold quantity
- returned quantity
- net quantity
- sales / return revenue
- COGS
- gross profit

COGS is derived from the immutable Inventory movement ledger:
- `sale_issue` contributes COGS
- `sale_reversal` reverses COGS only when stock was actually restored

This intentionally means damaged / waste / no-stock returns do not pretend inventory cost
was restored.

## KPI dictionary

`reporting.kpi_definitions` creates stable semantic definitions for management KPIs,
including:
- net sales
- gross profit
- gross margin %
- average ticket
- return rate %
- inventory value
- overdue payables
- cash balance
- shift variance
- active customers
- approved supplier spend

## Management APIs

- GET `/api/v1/reporting/kpis`
- GET `/api/v1/reporting/dashboard?from=&to=&branch_id=`
- GET `/api/v1/reporting/branches?from=&to=`
- GET `/api/v1/reporting/products?from=&to=&branch_id=&limit=`
- GET `/api/v1/reporting/products/fast-moving?...`
- GET `/api/v1/reporting/products/slow-moving?...`
- GET `/api/v1/reporting/inventory?from=&to=&warehouse_id=`
- GET `/api/v1/reporting/inventory/expiry?from=&to=&warehouse_id=&days=`
- GET `/api/v1/reporting/purchasing?from=&to=`
- GET `/api/v1/reporting/cash?from=&to=`
- GET `/api/v1/reporting/customers?from=&to=&limit=`
- POST `/api/v1/reporting/refresh`

## Inventory aging boundary

v44 exposes **activity aging** based on current on-hand records and `last_movement_at`.
It is not represented as FIFO inventory-layer age.

True inventory-layer aging requires cost/receipt layer allocation and belongs in a later
inventory costing/reporting enhancement. The API names the field `activity_aging` to avoid
misrepresenting the accounting meaning.

Expiry outlook is exact for tracked lots because it uses current lot balances and lot
expiry dates.

## Refresh

Reporting projections are rebuildable:

`php artisan reporting:refresh {tenant_id} --from=YYYY-MM-DD --to=YYYY-MM-DD`

The command and API both call the same explicit application action.

Maximum refresh window per request: 367 calendar days.

In production this action can later be invoked by queue/outbox consumers after business
events, while periodic rebuild remains a reconciliation safety net.
