Goods receipts
Route: /org/inventory/procurement · Module: org_inv_procurement — 4 permissions
Permission to view: View Inventory Procurements
1. What a goods receipt is
The record that stock arrived from a vendor. One receipt is a batch — many lines posted together under one reference number, typically one delivery or one invoice.
| Raw material receipt | Finished goods receipt | |
|---|---|---|
| You are | Buying inputs to make things | Buying ready-made products to resell |
| Lines key on | raw_material_id | variation_id |
| Typical for | A manufacturer | A trader |
| The chooser calls it | Default | The alternative |
2. How a receipt moves through the module
3. Step by step
Step 1 · Choose what you are receiving
Add Receipt Batch does not open the entry form. It asks a question first.
Raw Materials carries a Default badge and is described as the manufacturer's path.
Finished Goods is for buying products you resell without making.
The choice decides which master the lines are drawn from, and it cannot be changed once you are in the grid — go back and start again.
Step 2 · Fill the grid
The grid lists every material — 160 on this tenant — and the counter reads 0 of 160 filled. A
row becomes part of the receipt only when it has a quantity.
| Column | Notes |
|---|---|
| Material | Read-only |
| UOM | From the material. Not editable here |
| Current | The material's current_stock today |
| Last cost | last_purchase_price |
| Qty * | Required per filled line. Must be > 0 |
| Unit cost * | Required. Pre-filled from Last cost; may be 0 but not negative |
| Batch # | Optional. A vendor lot number |
| Expiry | Optional date |
Above the grid, Reference / Invoice # and Remarks apply to the whole batch.
Step 3 · Save
Save Receipt posts one PURCHASE ledger row per filled line, all sharing a generated batch id
and your reference number.
Every line writes a ledger row. Nothing writes current_stock. Come back to this screen after
saving and the Current column reads exactly what it did before.
This is the module's most confusing behaviour and it is not a bug you can configure away. See How stock is counted.
4. Validation — the exact strings
| Condition | Message |
|---|---|
| No line has a quantity | Add a quantity to at least one item |
| A filled line has qty ≤ 0, or a negative/non-numeric unit cost | Each filled line needs a positive quantity and non-negative unit cost |
| Save succeeds | Receipt saved — N items |
| Save fails | Failed to save receipt batch |
5. Permissions — and a gap
| Action | Permission | Enforced? |
|---|---|---|
| See the screen | procurement.view | ✅ |
| Menu entry | procurement.menu | ✅ |
| Post a receipt | procurement.create | ✅ at the API |
| Approve a receipt | procurement.approve | ❌ never checked anywhere |
procurement.approve does nothing, and the buttons are not hiddenThere is no approval step in the product. The permission exists, can be granted, and is read by no route and no component.
Worse, this screen performs no permission checks in the UI at all — Add Receipt Batch renders
for anyone holding procurement.view. A user without procurement.create can fill in a whole batch
and only discover the problem when Save fails. See Permissions.
6. The admin contract
| Must be true | Or else |
|---|---|
| A project is selected | The list is empty |
| Raw materials (or products) exist | The grid has no rows to fill |
last_purchase_price is set | Unit cost pre-fills as 0 |
7. Downstream
| Effect | Where |
|---|---|
One PURCHASE row per line | Ledger |
| Batch grouping | The list view groups by batch id |
| Cost captured | The row's unit_cost, used for valuation |
current_stock | Unchanged |
8. Troubleshooting
| Symptom | Cause |
|---|---|
| Add Receipt Batch opens a dialog, not a form | Correct — the type chooser comes first |
| Unit cost pre-fills as 0 | The material's last_purchase_price is 0 |
| Save refused with a validation toast | A filled line has qty ≤ 0 or a negative unit cost |
| Stock did not go up | Expected. Receipts write the ledger only — see §3 |
| A colleague can open the form but not save | They hold procurement.view but not .create; the UI does not hide the button |