The inventory ledger
Route: /org/inventory/ledger · Module: org_inv_ledger — 3 permissions
Permission to view: View Inventory Ledger
1. What the ledger is
Every stock movement recorded by Inventory, in one append-only list. The module's real name is Inventory Audit View, and that is what it is: a journal, not a stock report.
The button has no click handler. Nothing happens when you press it — no file, no error, no spinner.
The Export inventory ledger permission that appears to gate it is enforced nowhere either. The export feature does not exist in any form; there is no API endpoint behind it.
To get this data out, use the external API with the
inventory:stock:read scope.
2. The six transaction types
| Type | Sign | Written by |
|---|---|---|
PURCHASE | + | Goods receipts |
PRODUCTION_IN | + | Completing a production run |
CONSUMPTION_OUT | − | The same completion, one per ingredient |
SALE | − | Direct sales |
ADJUSTMENT | ± | Stock adjustments |
WASTAGE | − | Wastage |
A customer buying on the storefront produces no ledger row at all. Only the direct-sales screen
writes SALE.
So the ledger is a complete record of what Inventory did, and an incomplete record of what happened to your stock. See How stock is counted.
3. Reading the table
| Column | Notes |
|---|---|
| Date & time | When the row was written |
| Item details | Product name, SKU and variation — blank on raw-material rows |
| Transaction type | Colour-coded pill |
| Movement | Signed quantity with unit; @ ₹price on sale rows |
| Audit info | Always N/A / Ref: Manual |
| Remarks | Raw, undecoded |
Three things wrong with this table
Every PURCHASE, CONSUMPTION_OUT, ADJUSTMENT and WASTAGE row against a raw material has an
empty Item Details cell. The name is returned by the API — the page only ever renders the
product name, never the raw-material name.
Roughly half the audit view is therefore anonymous: you can see that 3.5 KG of something was wasted, but not what.
Sales and maintenance pack their metadata into the remarks field and decode it on their own screens. The ledger does not decode it, so rows read:
[CUST:Ritwika Basu] [PH:9836500415] [PAY:card] [DISC:5] [TAX:5]
[REASON:Expired stock] Past shelf life at morning check
On a tenant with real customers this column displays customer names and phone numbers in plain
text to anyone holding ledger.view. Treat that as a privacy consideration when granting the
permission.
N/AEvery row shows N/A and Ref: Manual, including rows posted by an authenticated user through the
UI. The created_by value is stored but never resolved to a name, so the audit view cannot tell you
who did anything.
4. Finding a movement
| Tool | Matches |
|---|---|
| Search | SKU, product or reference |
| Detailed Filters | Type, date range and item scope |
Receipts and sales carry a reference_no you chose. Production writes none — only a generated
remark containing the order's first 8 characters, such as Production Order #fb75ec9f completion.
Searching for a production run means searching for that fragment.
5. Ordering and pagination
Rows are returned newest first, 50 per page by default. The count in the footer is the total for the organisation, and the project filter is applied on top.
6. Permissions
| Action | Permission | Enforced? |
|---|---|---|
| View | ledger.view | ✅ |
| Menu | ledger.menu | ✅ |
| Export | ledger.export | ❌ — and there is nothing to enforce it on |
7. Troubleshooting
| Symptom | Cause |
|---|---|
| Empty ledger | No project selected, or no operations have been recorded |
| Export does nothing | Correct — the button has no handler |
| Half the rows have no item name | Raw-material rows do not render their name |
| Remarks full of square brackets | The ledger does not decode the encoding tags |
| A storefront order is missing | Storefront orders never write here |
Audit Info always N/A | created_by is never resolved to a user |
| Balance disagrees with the Stock column | They are different systems — see How stock is counted |