At a Glance
Persona: Purchaser (Procurement Officer + Department Manager) · Module: good-receive-note · Scenarios: ~20
Categories: Happy Path · Permission · Validation · Edge Case
E2E coverage: maps to501-grn.spec.tsin../carmen-inventory-frontend-e2e/
This page captures the test scenarios that the Purchaser persona (the Purchaser / Procurement Officer who raised the upstream PO, plus the Department Manager subset who owns the cost-centre that a GRN posts against) directly drives in the good-receive-note module. The Purchaser is a review-only participant on the GRN — they do not create the GRN at the dock (Receiver path), do not save line entries, do not post the irrevocable commit (Inventory Manager path), and do not edit the GRN document at any state. Their work is bounded by three activities: (1) react to the notification fired by a Receiver draft → saved (variance-flagged) or Inventory Manager saved → committed transition on a GRN whose source PO they own, (2) review the receipt versus the PO they raised — received_qty vs pending_qty, accepted_qty vs received_qty, lot / expiry on the linked tb_inventory_transaction_detail, attached packing slips, and price drift against [vendor-pricelist](/en/inventory/vendor-pricelist) — and (3) own the vendor-side resolution for every variance (chase short-ships, negotiate credit / replacement for damage, raise PO amendments, renegotiate price drift). The Department Manager subset performs the same review scoped to lines posting to their department's cost-centre and signs off on budget impact. Segregation of duties (PO_AUTH_010, carried over from the upstream PO module) explicitly forbids the user who raised or transmitted the PO from posting the GRN against it; the test set below verifies that boundary on every permission row. Scenarios are grouped into happy paths (notification consumption, clean-receipt close-out, short-ship chase, price-variance escalation, Department Manager cost-centre signoff, PO amendment for over-receipt resolution), permission (view scope by PO ownership, SoD on modify / create / commit, Department Manager cost-centre read scope), validation (denial of state-changing actions that belong to Receiver / Inventory Manager, denial of cost-centre override on the GRN document, SoD self-receipt block), and edge cases (notification timing on commit, variance tolerance boundary driving the chase decision, multi-PO consolidation review, cross-cost-centre Department Manager review). Cross-persona handoffs that pivot off the Purchaser (Scenarios 4, 6 of the parent overview — quality-rejection handoff and price-variance handoff) live in 04-test-scenarios.md, not here.
| # | Scenario | Pre-condition | Steps | Expected |
|---|---|---|---|---|
| PUR-HP-01 | Receive notification on a saved / committed GRN against an own PO |
Purchaser purchase@blueledgers.com is the buyer_id (or last_action_by_id on the sent transition) of PO PO-X at po_status ∈ {sent, partial}; Receiver has just driven a GRN against PO-X from draft → saved with a variance comment on at least one line, OR Inventory Manager has driven the GRN saved → committed. |
1. Receiver writes variance comment and clicks Save (or Inventory Manager clicks Commit). 2. Purchaser refreshes the in-app activity log / notification tray. 3. Click the notification entry. | A notification row is persisted against tb_user.id = purchaser, carrying the GRN number, the source PO number, and a summary flag (clean / short / over / quality-reject / price-variance / wrong-item); the click deep-links to the GRN read view scoped to the lines whose purchase_order_detail_id belongs to PO-X; the GRN document is not mutated by the Purchaser's open action; activity log records { event: 'notify_po_owner', recipient_id: purchaser, grn_id: <id> }. Maps to TC-GRN-010001 (list-view read access for procurement role). |
| PUR-HP-02 | Review a clean receipt and close the review ticket — no vendor contact | A committed GRN against own PO PO-X where every line satisfies received_qty = pending_qty, accepted_qty = received_qty, GRN unit price within tenant pricelist tolerance, and the Receiver wrote no variance comment. |
1. Open the GRN from the notification (PUR-HP-01) or from the PO module's Receiving History tab. 2. Compare the line-level received_qty / accepted_qty / pending_qty columns. 3. Open the pricelist-deviation panel and confirm every line is within tolerance. 4. Close the Purchaser review ticket with a "clean" disposition comment on the GRN activity log. |
No vendor-side action is fired; no PO amendment raised; no Finance handoff; the GRN document itself is not edited (no header / line / lot mutation); the activity log accumulates a Purchaser-side comment { event: 'purchaser_review_clean', actor_id: purchaser }; the PO line transitions naturally on commit (sent → partial → completed) per the upstream PO_POST_007 (driven by Receiver, not Purchaser). Maps to the read-only view side of TC-GRN-010001. |
| PUR-HP-03 | Review a short receipt and chase the vendor | committed GRN against own PO PO-Y (order_qty = 10) with received_qty = 6, accepted_qty = 6 on the line; Receiver wrote variance comment "vendor delivered partial — balance expected next week"; po_status = sent → partial. |
1. Open the GRN from the notification. 2. Confirm received_qty (6) < pending_qty (10) on the line. 3. Read the Receiver's variance comment. 4. Contact the vendor outside the GRN document (email / vendor portal) and confirm the balance ETA. 5. Write a Purchaser-side comment on the GRN activity log recording the vendor response and replacement-shipment ETA. |
Vendor chase fires off-document; the GRN itself stays committed (read-only, unchanged) — no header / line / lot mutation by the Purchaser; the activity log records { event: 'purchaser_vendor_chase', vendor_response: '<ETA>', actor_id: purchaser }; source PO stays at po_status = partial with pending = 4 (no PO amendment required); when the vendor's follow-up shipment arrives, the Receiver creates a second committed GRN against the same PO which advances received_qty to 10 and flips po_status → completed per the upstream module. Maps to the partial-receipt review side of TC-GRN-110004. |
| PUR-HP-04 | Review a price variance and contact the vendor for renegotiation | committed GRN against own PO PO-P where the GRN unit price on a line exceeds the active [vendor-pricelist](/en/inventory/vendor-pricelist) price by more than the tenant tolerance; Receiver wrote variance comment "invoice unit price drifted from pricelist — confirm with Purchaser"; no quantity variance. |
1. Open the GRN from the notification. 2. Open the pricelist-deviation panel — the GRN line's effective unit price is rendered beside the active pricelist price for the (vendor, item, receipt-date) window; the deviation flag is lit. 3. Cross-check the deviation against the tenant tolerance. 4. Contact the vendor and resolve — either secure a credit note for the gap (sub-path a) or accept that the pricelist is stale and raise a pricelist amendment (sub-path b). 5. Log the resolution on the GRN activity log. | Sub-path (a) — credit note: Purchaser writes credit-note reference on the GRN activity log and hands off to Finance for AP offset against the committed GRN; no PO amendment, no GRN mutation; Finance picks up the credit-note booking on the three-way match path (03-user-flow-finance.md). Sub-path (b) — pricelist amendment: Purchaser raises an amendment on [vendor-pricelist](/en/inventory/vendor-pricelist) so future POs price correctly; the current GRN price stands; no AP offset, no GRN mutation. Either sub-path leaves the GRN document at committed, unchanged, with the activity-log audit trail. Maps to the price-variance handoff side of Scenario 6 in 04-test-scenarios.md. |
| PUR-HP-05 | Department Manager reviews cost-centre allocation and signs off | A committed GRN posts inventory to a location whose default cost-centre belongs to the Department Manager's department; line-level cost_centre_id (resolved from the location / department mapping) is present on the inventory transaction; price is within tolerance; quantities match the PO. |
1. Department Manager opens the GRN from the notification (or via the GRN list filtered by cost_centre_id). 2. Confirm the goods received match what was ordered for the department (PO line ↔ GRN line cross-check). 3. Validate the inventory location / cost-centre allocation against the department's mapping. 4. Cross-check the pricelist-deviation panel scoped to the lines on the department's cost-centre. 5. Write a sign-off comment on the GRN activity log recording the budget impact. |
No GRN document mutation (Department Manager has no cost-centre override authority on the GRN itself — see PUR-VAL-03); the activity log records { event: 'cost_centre_signoff', cost_centre_id: <cc>, budget_impact: <amount>, actor_id: dept_manager }; if the Department Manager disagrees with the cost-centre allocation, the disagreement handoffs to Finance for journal re-allocation (the GRN itself stays committed, unedited). Maps to the cost-centre signoff side of Scenario 1's Finance handoff in 04-test-scenarios.md. |
| PUR-HP-06 | Request a PO amendment after over-receipt variance | committed GRN against own PO PO-O (order_qty = 10, tenant tolerance 5%) where received_qty = 10.5, accepted_qty = 10.5 — within tolerance per GRN_VAL_009 so the receipt was accepted, but the Purchaser wants the over-quantity commercially reflected on the PO for the three-way match. |
1. Open the GRN from the notification. 2. Confirm the over-receipt is within tolerance (no validation block fired at commit). 3. Contact the vendor and confirm the over-quantity is correctly priced. 4. Pivot to the upstream [purchase-order](/en/inventory/purchase-order) module — raise a PO line amendment (order_qty 10 → 10.5) so the PO matches the GRN. 5. Write a comment on the GRN activity log linking to the PO amendment. |
No GRN mutation by the Purchaser; the PO amendment is created on the upstream module (separate document state machine — see the [purchase-order](/en/inventory/purchase-order) module's amendment flow); on PO amendment commit, tb_purchase_order_detail.order_qty advances 10 → 10.5, the previously over-received GRN now matches order_qty exactly, and the three-way match (Finance) reconciles cleanly; the GRN document itself stays at committed, unchanged; activity log records { event: 'po_amendment_raised', po_id: <id>, new_order_qty: 10.5, actor_id: purchaser }. Maps to the over-receipt-within-tolerance branch of 03-user-flow-receiver.md Section 3 (Receiver-side accept) plus the Purchaser-side amendment follow-up. |
| # | Scenario | Expected behaviour (allow/deny + reason) |
|---|---|---|
| PUR-PERM-01 | Purchaser opens a GRN in read mode where they own the source PO | Allow. The GRN read endpoint is open to the user identified by tb_purchase_order.buyer_id (or last_action_by_id on the sent transition) for every line on the GRN that carries a purchase_order_detail_id pointing to a PO they own. The notification deep-link, the PO module's Receiving History tab, and the GRN list's "My POs" filter all resolve into the same read-only view. Header, line, lot, attachment, and activity log are all visible; no edit, save, commit, or void affordance is rendered. Maps to TC-GRN-010001 (view list as authenticated procurement user). |
| PUR-PERM-02 | Purchaser attempts to open a GRN whose source PO they do not own | Deny — scope. When every line on the GRN references a purchase_order_detail_id belonging to a PO owned by a different buyer_id, the read endpoint returns 403 / redirects to the GRN list with the row filtered out. A direct deep-link to the GRN detail URL returns "You do not have permission to view this GRN — the source PO is owned by a different buyer."; for mixed-ownership multi-PO GRNs (rare, multi-PO consolidation), only the lines whose PO the user owns are rendered, all other lines are masked. Maps to TC-GRN-010003 (View GRN List with Insufficient Permissions) for the scoped-deny case. |
| PUR-PERM-03 | Purchaser attempts to modify a GRN header / line / lot / accepted_qty | Deny — Receiver only (SoD). Per PO_AUTH_010 carrying over from the upstream PO module, the Purchaser (PO owner) cannot mutate the GRN they own the source PO for. The header edit, line add / edit / delete, lot edit, and accepted_qty mutation endpoints all return "This action requires the Receiver / Store Keeper role; PO owners cannot modify a GRN against their own PO (segregation of duties — PO_AUTH_010).". The UI hides every edit affordance for the Purchaser role on the GRN detail view. Symmetric to RCV-PERM-05 in 04-test-scenarios-receiver.md. Maps to TC-GRN-080005 (Edit Line Item with insufficient permission) for the Purchaser role. |
| PUR-PERM-04 | Purchaser attempts to create a GRN (New GRN button) | Deny — Receiver / Inventory Manager only. The New GRN button is hidden / disabled for the Purchaser role on the GRN list and on the PO module's Receiving History tab. A direct API call to the create endpoint returns "GRN creation requires the Receiver (Store Keeper) or Inventory Manager role."; no tb_good_received_note row is written. The Purchaser cannot bootstrap a GRN even against their own PO — they coordinate the dock-side process via the Receiver path. |
| PUR-PERM-05 | Purchaser attempts to commit a saved GRN (saved → committed) |
Deny — Inventory Manager only. Per the lifecycle (Section 4 of 03-user-flow.md), the irrevocable post is reserved to the Inventory Manager. The Commit button is hidden / disabled for the Purchaser role; a direct API call to the commit endpoint returns "Commit from status saved requires the Inventory Manager role."; no inventory or PO effects; the document stays at saved. Mirrors RCV-PERM-03 in 04-test-scenarios-receiver.md for the Purchaser-attempt variant. Maps to TC-GRN-110002 (No Permission to Commit). |
| PUR-PERM-06 | Department Manager opens a GRN posting to their department's cost-centre | Allow — cost-centre scope. The GRN read endpoint is open to the user with the Department Manager role when at least one GRN line resolves to a cost_centre_id belonging to the department's mapping (typically via the inventory location's default cost-centre). The Department Manager sees the lines on their cost-centre plus enough header context (vendor, currency, receipt date) to validate the receipt; cross-cost-centre lines (multi-department GRNs) are masked. The pricelist-deviation panel renders only for the in-scope lines. No edit / commit / void affordance is rendered (read-only by role, not just by document state). |
| # | Scenario | Trigger | Expected error |
|---|---|---|---|
| PUR-VAL-01 | Purchaser cannot trigger commit on a saved GRN they own the source PO for |
A saved GRN sits against own PO PO-X awaiting Inventory Manager review; Purchaser opens the GRN read view from a notification; tries to call the commit endpoint directly (curl / replayed request from a previous IM session). |
Reject — role check. Server returns "Commit from status saved requires the Inventory Manager role." doc_status stays saved; no inventory write; no PO advance; no AP accrual; activity log records { event: 'commit_denied', actor_id: purchaser, reason: 'role' }. Reinforces PUR-PERM-05 and PO_AUTH_010. Maps to TC-GRN-110002. |
| PUR-VAL-02 | Purchaser cannot mutate accepted_qty on a GRN line they own the source PO for |
saved GRN against own PO with received_qty = 10, accepted_qty = 10; Purchaser disagrees with the dock-side acceptance (wants accepted_qty = 8 to flag 2 units for vendor return); tries to PATCH the line's accepted_qty field. |
Reject — role check. Server returns "Accepted quantity is editable only by the Receiver / Store Keeper; PO owners cannot adjust line acceptance (segregation of duties — PO_AUTH_010)." The line stays received_qty = 10, accepted_qty = 10. The Purchaser's correct path is to comment on the GRN activity log and ask the Receiver to re-evaluate (the Receiver may save a corrected accepted_qty while the GRN is still saved); on committed GRNs, no accepted_qty change is possible by any role except via post-commit reversal (RCV-PERM-06). Reinforces PUR-PERM-03. |
| PUR-VAL-03 | Department Manager cannot override the cost-centre allocation on a GRN line | committed GRN posts to cost-centre CC-A (resolved from inventory location's default mapping); Department Manager disagrees with the resolved allocation and wants CC-B; tries to PATCH the inventory transaction's cost_centre_id field. |
Reject — terminal state + role check. Server returns "GRN at status committed is locked; cost-centre re-allocation must be performed by Finance via journal adjustment." The cost-centre stays CC-A; the GRN is unchanged; the Department Manager's correct path is to write a sign-off comment on the GRN activity log flagging the disagreement and hand off to Finance for the journal re-allocation (cost-centre move via a compensating GL entry, not by mutating the GRN document). Reinforces PUR-HP-05 boundary. |
| PUR-VAL-04 | Purchaser attempts to create / commit a GRN against their own PO (SoD self-receipt) | Purchaser purchase@blueledgers.com is the buyer_id / last_action_by_id on the sent transition of PO-X; same user tries the create-GRN flow against PO-X (or attempts to commit a saved GRN against PO-X they somehow have a role context for). |
Reject — PO_AUTH_010. Server returns "You created or transmitted this PO; another user must post the GRN against it." at the PO-pick step on the create flow, and "Commit from status saved requires the Inventory Manager role AND a different user than the PO buyer / transmitter." on the commit attempt. No tb_good_received_note row written on create; no doc_status advance on commit; no inventory, PO, or AP effects. Symmetric to RCV-PERM-05 from the Purchaser side. |
| # | Scenario | Condition | Expected |
|---|---|---|---|
| PUR-EDGE-01 | Notification timing — Purchaser receives notification at the moment of commit, not before | Receiver drives a clean GRN draft → saved → committed in one shift. The Receiver path fires a notification on the draft → saved transition only if a variance comment is present on at least one line; the Inventory Manager saved → committed transition fires unconditionally. |
For a clean receipt (no variance comment), the Purchaser receives one notification at saved → committed carrying the flag clean; the draft → saved transition fires no Purchaser notification. For a variance-flagged receipt, the Purchaser receives two notifications — one on saved (flag e.g. short) so vendor chase can start while the GRN is uncommitted, and one on committed confirming the posting; both notifications deep-link to the same GRN with state evolved between the two clicks. No duplicate notifications, no missed notifications (idempotency guard on tb_user_notification keyed by (grn_id, doc_status_to, recipient_id)). |
| PUR-EDGE-02 | Variance threshold at the exact boundary of the chase-decision rule | committed GRN against own PO where received_qty = pending_qty × (1 − tenant_short_tolerance) exactly — at the boundary of "short receipt acceptable" vs "chase vendor"; tenant short tolerance is 5%; order_qty = 10, so the boundary is received_qty = 9.5. |
At the boundary (received_qty = 9.5): the GRN's variance summary flag may render as clean (under one tenant configuration: short-within-tolerance) or short (under stricter configuration: any shortfall triggers chase). The Purchaser's decision branch in 03-user-flow-purchaser.md Section 3 ("Clean receipt" vs "Short receipt") follows the tenant configuration — at exactly the boundary, the chase is at the Purchaser's discretion and the activity-log comment reflects it. One unit below the boundary (received_qty = 9.49): unambiguously short, chase mandatory. One unit above (received_qty = 9.51): unambiguously clean, no chase. The PO po_status advance is driven independently by PO_POST_007 and is not affected by this decision. |
| PUR-EDGE-03 | Multi-PO consolidation review — single GRN spanning two of the Purchaser's POs | Same vendor delivers in one truck covering PO-A (one line, pending 4) and PO-B (two lines, pending 6 each); Receiver consolidates into one GRN with three lines per RCV-EDGE-06; both POs are owned by the same Purchaser; one line is short by 1 unit, the other two are clean. |
The Purchaser receives one notification per GRN state transition (not one per PO), carrying both PO numbers and a per-line variance breakdown. Opening the GRN renders all three lines (cross-PO ownership scope is unified for the read view since the Purchaser owns both POs); the variance summary flag is short (any line short flips the whole-GRN flag); the chase is scoped to the one short line and identifies which PO it sits against (PO-A line L1, or PO-B line L1 / L2); the clean lines need no action. The pricelist-deviation panel renders per line, per vendor / item / receipt-date window. Resolution (chase on the short line) is logged once on the GRN activity log with the PO / line reference; no PO amendment required if the next shipment will close the balance. Maps to TC-GRN-040002, TC-GRN-040004 from the Purchaser-side review. |
| PUR-EDGE-04 | Department Manager review spanning a multi-cost-centre GRN | A committed GRN consolidates a single vendor delivery across two inventory locations whose default cost-centres belong to two different Department Managers (CC-A owned by Dept Mgr A, CC-B owned by Dept Mgr B); three lines total. |
Each Department Manager receives a notification scoped to their cost-centre's lines only; opening the GRN renders the lines on their cost-centre fully (with header context) and masks the lines on the other department's cost-centre (per PUR-PERM-06). The pricelist-deviation panel renders only for the in-scope lines. Each Department Manager signs off independently on their cost-centre's lines (PUR-HP-05) — sign-offs are recorded as separate activity-log entries with cost_centre_id and actor_id distinguishing them. The GRN itself is not mutated by either signoff (PUR-VAL-03); cross-cost-centre disagreement on a shared line is impossible (each line has exactly one resolved cost_centre_id). |
[vendor-pricelist](/en/inventory/vendor-pricelist) — credit-note vs pricelist amendment), and the Department Manager cost-centre signoff path embedded in Scenario 1.PO_AUTH_010 (segregation of duties — PO owner ≠ GRN poster) carried over from the upstream PO module and enforced in PUR-PERM-03, PUR-PERM-05, PUR-VAL-01, PUR-VAL-02, PUR-VAL-04; the price-variance tolerance and pricelist-check rules referenced in PUR-HP-04 and PUR-EDGE-02.pending_qty, po_status, and the activity log that captures vendor-side resolution; PUR-HP-06 (PO amendment for over-receipt) and PUR-HP-03's chase against a partial PO both fire on this module's amendment / activity-log endpoints.../carmen-inventory-frontend-e2e/tests/501-grn.spec.ts — canonical Playwright spec for the GRN module. Purchaser-relevant test groups: TC-GRN-010001 (View GRN List — PUR-PERM-01 read scope), TC-GRN-010003 (View GRN List with Insufficient Permissions — PUR-PERM-02 scoped-deny), TC-GRN-080005 (Edit Line Item in RECEIVED status — PUR-PERM-03 / PUR-VAL-02 role denial), TC-GRN-110002 (No Permission to Commit — PUR-PERM-05 / PUR-VAL-01), TC-GRN-040002 / 040004 (Create from Multiple POs — PUR-EDGE-03 multi-PO consolidation review). The permission-denial fixtures use requestor@blueledgers.com for the unauthorised-read case; happy-path procurement reads use purchase@blueledgers.com which doubles as the Purchaser role identity in the fixture set.