---
title: Supply chain report
order: 4
updated: 2026-07-12
---

# Supply chain report

The *Inventory & Supply Chain* tab on the dashboard is a firm-wide, movement-centric view of your product catalogue. It answers three questions at a glance: **what's moving**, **what's stuck**, and **what's actually making money**. It fuses the old standalone supply-chain report with the inventory overview into one page, served by a single call, so everything you see below arrives together.

Two clocks run on this page at once, and it matters which is which:

- **Period-scoped** (obeys the *From* / *To* date pickers): stock movement, monthly trend, ordered revenue, and FIFO profit/loss.
- **Point-in-time / all-time** (as of now, ignores the dates): current stock value, idle days, dead-stock, the pending arrival/dispatch pipeline, and the approval queues.

## Open the report

1. Open the *Dashboard* (the firm-overview page, not a records list).
2. In the tab strip, pick *Inventory & Supply Chain* (the boxes icon).
3. Set the window with the *From* and *To* date pickers in the dashboard header. The tab reloads automatically whenever the dates or the head filter change.

The tab only appears if you hold the *Supply Chain Report (detailed)* permission (`dashboard.supplyChainReport`), which itself depends on general dashboard access (`dashboard.view`). Both are **re-checked on the server** on every load and on every print — a forged request without them is rejected with `403`, not just hidden in the UI. See [Users & permissions](/docs/settings/users-and-permissions).

### Date range

The header shows two date fields, *From* and *To*, in `dd/mm/yy` order. When the dashboard first opens it uses your saved default range (set in your personal settings); if you never set one it falls back to the current financial year, and the raw store default is the current calendar month. Picking a *From* later than the current *To* snaps *To* forward to the same day (and vice-versa) so the range can never invert.

The backend re-validates the window before running anything:

- Both `start` and `end` must be positive, finite millisecond timestamps.
- `start` must be `≤ end`.
- The span may not exceed **3 years** (`end − start ≤ 366 × 3 days`).

A window that breaks any of these is rejected with `400 Invalid period` — nothing is computed.

### Filter by product head

A single *Filter by product head* selector (placeholder *All product heads (optional)*) sits at the top-left of the tab. Pick a head to narrow **every** KPI, chart, and list to that head's product codes; a small *✕ Clear* control above the box returns you to the firm-wide view.

- **Default:** empty — firm-wide.
- When a head is chosen, the scope header switches from a *Firm-wide* chip to a *Head view* chip showing the head's display name.
- The head id is **re-validated server-side**: a malformed id is rejected with `400`, and an id that doesn't resolve to a real head in your firm is rejected with `404` — a fake head id can't crash the pipeline.
- The head is resolved to its set of product codes once, and every metric is scoped to that set.

One deliberate exception: the **approval queues, supplier count, and low-stock alerts** at the bottom are always **firm-wide** and ignore the head filter (they answer "what does the firm need to action right now", not "how is this head doing"). Everything else honours the filter.

## Scope header

Just under the toolbar a one-line header summarises the current view:

- The head's name, or *All products* when unfiltered.
- A *Head view* / *Firm-wide* chip.
- A red *N dead-stock products* chip when any dead stock exists (see [Dead stock](#what-counts-as-dead-stock)).
- A muted subtext: *Net movement N units · purchase inflow ₹X* — units in minus units out over the period, and the total purchase value that arrived.

## KPI strip

The headline tiles, left to right. Currency tiles use a compact Indian format (e.g. `₹1.2L`, `₹3.4Cr`). Some tiles only render if you hold the matching permission, so your strip may be shorter.

### Stock value (ex-tax)

Current on-hand stock valued at **weighted-average cost, excluding tax**. The cost basis is built from all approved arrivals (at the PO-discounted net rate) plus any priced positive stock adjustments; on-hand quantity is arrivals − delivered (returns add back) + net adjustments. Point-in-time — it ignores the date range. Always shown.

### Stock value (incl tax)

The same on-hand stock valued **including tax**. Shown **only** if you hold *View inventory stock value* (`inventory.viewInventoryStockValue`); otherwise the tile is omitted entirely.

### Arrived qty

Units **received** via approved stock arrivals whose effective date falls in the period. The effective date is the invoice date, else the approval time, else the created time. Reverse entries (return-to-supplier) carry negative quantity, so they net down this figure automatically. Period-scoped.

### Dispatched qty

Units **shipped** via approved deliveries in the period. Returns are excluded (`isReturn: false`). Period-scoped.

### Est. profit

Ordered ex-tax revenue **minus** FIFO cost-of-goods, summed across products. See [How profit is calculated](#how-profit-is-calculated). Period-scoped.

### Margin

*Est. profit ÷ ordered ex-tax revenue*, shown as a whole-number percentage. Reads `0%` when there was no ordered revenue in the period.

### Pending arrival ₹

Total value still awaiting arrival from suppliers — the sum of the [pending arrival pipeline](#pending-arrival-pipeline). Point-in-time.

### Pending dispatch ₹

Total value of ordered goods not yet dispatched to customers — the sum of the [pending dispatch pipeline](#pending-dispatch-pipeline). Point-in-time.

### Suppliers

Total number of suppliers on record. Shown **only** if you hold *View supplier list* (`database.viewSupplierList`). Firm-wide, point-in-time. See [Suppliers](/docs/records/suppliers).

### Products in loss

How many products sold **below** their FIFO cost in the period (negative profit). Period-scoped.

> `Net movement`, `purchase inflow`, and the dead-stock count live in the scope header above the strip rather than as their own tiles.

## Charts

### Stock movement

A single card with **two stacked panes**, both plotted by month across the period:

- **Quantity** (top) — bars for units *arrived (in)* (green) vs *dispatched (out)* (amber), with an indigo *net (in − out)* line. A net above zero means stock is building up; below zero means it's drawing down.
- **Value (at cost)** (bottom) — the same shape in rupees. Arrivals are valued at their **net purchase price**; dispatches are valued at each code's **all-time weighted-average net cost** (not the sale price — this pane is about the cost of stock leaving the shelf, not revenue).

Months are filled densely across the window (every month between *From* and *To* appears even if it had zero movement). The card shows *No movement in this period.* when nothing arrived or shipped.

### Stock value by head

A donut of where your capital is tied up, by product head — the top 10 heads by current on-hand stock value (ex-tax). Heads with zero value are dropped.

## Detail tables

Every product table shares a common column set. All are firm-wide top-N lists (or head-scoped when the head filter is active).

### Column glossary

- *Product* / *Head* — the code's name and its head.
- *Stock* / *Stock ₹* — current on-hand units and their weighted-average ex-tax value.
- *Last dispatched* — idle time, rendered as `today`, `1 day ago`, `N days ago`, or *Never dispatched* for stock that has never shipped.
- *Dispatched* — units shipped in the period.
- *Revenue ₹* / *Profit ₹* / *Margin* — period ordered ex-tax revenue, FIFO profit, and margin percent for that code.

### Profit & loss

- *Highest profit* — up to **10** products ranked by FIFO profit (descending). Only products that had ordered revenue or cost in the period appear.
- *Loss-making products* — products whose FIFO profit is **negative**, up to **10**, worst first. Empty is good — it shows *No loss-making products — nice.*

### Stock movement & capital

- *Slowest / dead stock* — products currently **holding stock**, up to **15**, longest-idle first. Never-dispatched stock sorts to the very top (treated as infinitely idle). Columns show stock, stock value, and last-dispatched.
- *Fastest movers* — products with the most units dispatched this period, up to **10**, descending.
- *Capital tied up in stock* — products by current stock value, up to **10**, descending.

## Value pipeline

Money that has been committed but hasn't physically moved yet. **This is not an approval queue** — it's the rupee value in flight, regardless of who needs to click what.

### Pending arrival pipeline

Ordered from suppliers, awaiting stock. One row per open purchase-order line, columns *Product · Supplier · Remaining · Value ₹ · Age*.

- Source: purchase orders whose status is `sent` or `partiallyArrived` and that aren't deleted. The scan looks at up to the **200** most-recent such POs.
- *Remaining* = ordered quantity on the line − quantity already approved-arrived against that PO line (never negative).
- *Value* = remaining × the PO line's **gross** price-per-unit. Note this is the un-discounted PO rate, so this figure can read slightly higher than the discounted cost you'll actually book on arrival.
- *Age* = days since the PO date.
- Lines with nothing remaining are dropped; the rest are shown highest-value first, up to **50** rows.

See [Create a purchase order](/docs/procurement/create-purchase-order) and [Add a stock arrival](/docs/inventory/add-stock-arrival).

### Pending dispatch pipeline

Ordered by customers, awaiting delivery. Columns *Product · Customer · Pending · Value ₹ · Age*.

- Source: orders that are PO-arrived (`po.hasPoArrived`), not yet fully delivered, and not deleted — reduced to individual product lines.
- *Pending* = ordered line quantity − quantity already delivered (approved, non-return) against that exact order line.
- *Value* = pending × the line's **net** unit price (`pricePerUnit × (1 − discount%)`).
- *Age* = days since the order's PO date.
- Fully-delivered lines are dropped; the rest are shown highest-value first, up to **50** rows.

See [Orders](/docs/enquiry-bank/orders) and [Send a delivery receipt](/docs/enquiry-bank/send-delivery-receipt).

## Awaiting approval

Distinct from the value pipeline: these are **vouchers a manager must approve** — the click that turns a drafted arrival or delivery into real stock movement. Each block only appears if you hold the matching approval permission, and clicking a row jumps to the approval screen. Firm-wide.

### Arrivals awaiting approval

Shown only with *Approve add-stock request* (`inventory.approveAddStockRequest`). The **10** most recently created pending arrivals, columns *Invoice · Supplier · Date · Qty*. Rows and the *Open approvals* link go to `/stockAdd/approvals`. See [Add a stock arrival](/docs/inventory/add-stock-arrival).

### Deliveries awaiting approval

Shown only with *Approve deliver-stock request* (`delivery.approveDeliverStockRequest`). The **10** most recently created pending deliveries, columns *Enquiry · Customer · Type · Qty*, where *Type* flags each voucher as *Deliver* or *Return*. Rows and *Open approvals* go to `/stockDeliver/approvals`.

## Low-stock alerts

Shown only with *View inventory* (`inventory.viewInventory`). Product codes running low — any code with an on-hand quantity of **1 to 10** units (the low threshold is `10`; codes at exactly `0` are treated as out-of-stock, not low). The **10** lowest-quantity codes are listed, columns *Product code · Quantity left*, lowest first, with a warning icon. *View all* opens the inventory list. Firm-wide, point-in-time. See [Stock overview](/docs/inventory/stock-overview).

## How profit is calculated

Profit on this tab uses **FIFO** (first-in, first-out) costing:

> Each unit sold draws its cost from the **oldest remaining purchase layer**.

**Worked example.** You buy 100 pcs at ₹10 each, later 100 pcs at ₹20 each, then sell 1 pc for ₹15. FIFO consumes the ₹10 layer first, so that unit costs ₹10 → profit = 15 − 10 = **₹5**.

Because FIFO is order-dependent, the report replays each product's full purchase-and-sale history to know which layers a given period's orders consume. Pre-period sales still consume layers so that in-period sales see the correct remaining ones, but their cost is not counted toward the period.

- **Revenue** is the ordered ex-tax revenue: for orders that are PO-arrived with a PO date inside the period, each line contributes `quantity × pricePerUnit × (1 − discount%)`. Tax is excluded because it's pass-through.
- **Cost** is the FIFO cost of those ordered units.
- **Profit** = revenue − cost; **margin** = profit ÷ revenue.

**Purchase layers** come from approved stock arrivals (at the PO-discounted net rate) plus any priced **positive** stock adjustments used as opening/added layers. Return-to-supplier reverse entries are **not** treated as purchase layers.

**Caveat:** if recorded sales exceed recorded purchases for a product (e.g. opening stock that was never entered as an arrival), the **last known purchase cost is reused** for the shortfall so cost isn't lost.

The report's own footnote states this verbatim: *Profit uses FIFO cost (oldest purchase layer first); when sales exceed recorded purchases the last known cost is reused.*

> The [Product report](/docs/reports/product-report) uses a simpler **average-cost** profit estimate, so its profit figures can differ from this tab. Use this tab when you specifically want FIFO.

## Print report

The *Print report* button (top-right of the tab) generates a one-click PDF of the current view. It posts the same `{ period, productHeadId? }` you're viewing, so if a head filter is active the PDF is scoped to that head. The file downloads as `inventory-supply-chain-<start-date>.pdf` and works identically in the web and desktop apps.

The PDF re-runs the whole report server-side (re-checking your `dashboard.supplyChainReport` permission) and lays out:

- **Headline KPIs** — Stock value, Arrived qty, Dispatched qty, Est. profit ₹, Margin, Pending arrival ₹, Pending dispatch ₹, Products in loss.
- **Monthly movement** table — Month · Arrived qty · Disp qty · Net qty · Arrived ₹ · Dispatched ₹.
- **Stock value by head**.
- **Highest profit** and **Loss-making products**.
- **Slowest / dead stock**, **Fastest movers**, and **Capital tied up in stock**.
- **Pending arrivals** and **Pending dispatch**.

It carries your firm name and logo, a *PRINTED BY* block naming the user (name, username, and user id) with a generated-at timestamp, a confidential footer, and a faint tamper-trace watermark. If the PDF fails to generate the button simply re-enables — the on-screen page is never affected.

> The **approval queues, supplier count, and low-stock alerts** are on-screen only. They are **not** in the PDF, which mirrors the analytical report rather than the live action lists.

## Common questions

### Why don't the numbers match the Product report?

They use different cost methods. This tab is **FIFO** (oldest purchase layer first); the [Product report](/docs/reports/product-report) uses **average cost**. Same sales, different cost assignment, so profit and margin can differ. Neither is "booked" accounting profit.

### Why is "Stock value" different from "Pending arrival ₹"?

*Stock value* is what you already hold on the shelf, valued at cost. *Pending arrival ₹* is stock you've ordered from suppliers but haven't received yet. One is on the balance sheet now; the other is money committed and in transit.

### What counts as dead stock?

A product that is currently **holding stock** but has **no dispatch inside the selected period** — either it has never been dispatched, or its last dispatch predates the *From* date. The red chip in the scope header counts these; the *Slowest / dead stock* table lists them, never-dispatched first.

### The date range doesn't change my stock value — is that a bug?

No. Stock value, idle days, the pending pipeline, and the approval queues are **point-in-time / all-time** — they describe the situation right now. Only movement, the monthly trend, ordered revenue, and profit obey the date pickers.

### Why does the head filter not touch the approval or low-stock lists?

Those three lists (arrivals awaiting approval, deliveries awaiting approval, low-stock alerts) plus the supplier count are deliberately **firm-wide**. They're operational to-do lists for the whole firm, so narrowing them to one head would hide work that still needs doing.

### Pending-arrival value looks a bit high — why?

The pending arrival figure values remaining units at the **gross** PO price, before the PO's line discount. The discounted net rate is only applied when the stock actually arrives, so the real booked cost is usually a little lower.

### Why can't I see the tab / why did printing fail with a permission error?

The tab and the PDF both require *Supply Chain Report (detailed)* (`dashboard.supplyChainReport`) on top of general dashboard access (`dashboard.view`). The server enforces both independently, so hiding the tab and blocking the print are the same check. Ask an admin — see [Users & permissions](/docs/settings/users-and-permissions).

### Is there a CSV / Excel export?

Not on this tab — export is a **PDF** only. If you need the raw rows, the underlying records live in [Stock overview](/docs/inventory/stock-overview), [Orders](/docs/enquiry-bank/orders), and the [purchase orders](/docs/procurement/view-purchase-order) list.
