At a Glance
Persona: Procurement Manager (high-value approval + configurational rule-tuning) · Module: purchase-order · Scenarios: ~29
Categories: Happy Path · Permission · Validation · Edge Case
E2E coverage: maps to401-po.spec.ts,403-po-approver-journey.spec.tsin../carmen-inventory-frontend-e2e/
This page captures the test scenarios that the Procurement Manager persona drives in the purchase-order module. Unlike the Purchaser — who owns the document end-to-end — the Procurement Manager engages with the PO across two distinct surfaces (03-user-flow-procurement-manager.md Section 1). The transactional surface is the high-value approval gate that the Purchaser cannot self-clear under PO_AUTH_004: POs whose tb_purchase_order.total_amount exceeds the tenant threshold (or whose pricelist deviation exceeds tolerance per PO_XMOD_006) escalate to the Manager's My Approvals queue, where the Manager runs Approve & Transmit (PO_POST_004), Return to Buyer (PO_POST_005), or Reject (PO_POST_010). The configurational surface is the rule-tuning workbench — vendor ranking and allocation, Convert-to-PO (vendor_id, currency_id) grouping, unit conversion factors, pricelist tolerance band, and the high-value threshold itself — whose edits affect new POs only because in-flight POs retain their snapshot of the prior rule version. The Manager additionally holds the override authorities the Purchaser cannot exercise: soft-delete-in-draft (PO_AUTH_005), void from any non-terminal state (PO_AUTH_007, PO_POST_010), and early-close from partial → closed (PO_AUTH_008, PO_POST_011). Scenarios below mirror the same five-section shape as the Purchaser file: the happy paths trace both the transactional escalation cycle and a configurational rule edit; the permission section pins down PO_AUTH_004–PO_AUTH_007 against neighbouring stages; the validation section covers threshold mismatches, snapshot preservation, and rule-edit integrity; and the edge cases cover boundary-amount POs, in-flight rule changes, bulk failures, and delegation timing.
| # | Scenario | Pre-condition | Steps | Expected |
|---|---|---|---|---|
| PM-HP-01 | Receive an escalated high-value PO in My Approvals and open the detail page | Procurement Manager fc@blueledgers.com logged in with enum_stage_role = approve; one PO at po_status = in_progress seeded via submitPOAsPurchaser() whose total_amount exceeds the tenant high-value threshold so workflow_current_stage lands on the Manager and the Manager's user-id is in user_action.execute (TC-PO-070101..TC-PO-070103) |
1. Open Sidebar → Procurement → My Approvals. 2. Click the PO filter tab (TC-PO-070102) — the queue lists POs at draft + in_progress awaiting the Manager. 3. Click the PO row that matches the seeded po_no (TC-PO-070103) — URL navigates to /procurement/purchase-order/<ref>. 4. Confirm the status badge reads IN PROGRESS (TC-PO-070201) and the header fields are read-only at this stage (TC-PO-070202). |
Detail page loads with the IN PROGRESS badge; header (vendor, currency, exchange rate, credit term, order/delivery date) is rendered read-only per PO_VAL_016 — the Manager is in approve mode, not edit mode; Edit + Comment buttons present in the action toolbar (TC-PO-070203); the items table, attachments, and comments tabs are visible; deviation indicator (where PO_XMOD_006 force-routed the PO here) is shown next to affected lines. |
| PM-HP-02 | Approve all items on an escalated PO and drive in_progress → sent with transmit |
PM is on the detail page of a in_progress PO from PM-HP-01 with at least one line; all Section 2 validations have passed and no deviation flag is outstanding |
1. Click Edit (TC-PO-070301) — enters edit mode. 2. Tick the row checkbox on the first item — the item-action toolbar (Approve / Review / Reject) appears (TC-PO-070301). 3. Click Approve in the item toolbar — a green Approved badge appears on the row (TC-PO-070302). 4. Repeat for all remaining line items. 5. Click Approve PO in the footer (TC-PO-070305) — confirmation dialog opens with "Once approved, PO will be sent to vendor" (TC-PO-070306). 6. Click Confirm (TC-PO-070307). |
po_status transitions in_progress → sent via PO_POST_004; approval_date = now(), last_action = approved, last_action_by_id = PM user-id; the transmit handler fires under PO_AUTH_006 and the system emails / EDI-posts / portal-posts the PO to the vendor; tb_purchase_order.email is set; status badge updates to APPROVED / SENT (TC-PO-070307); the soft budget commitment hardens into a vendor liability; history appended with the approve event and the workflow chain created → submitted → approved → sent is visible on the timeline. |
| PM-HP-03 | Send back an escalated PO to the buyer for revision (in_progress → draft) |
PM is on the detail page of an in_progress PO; one line has a price the Manager wants the buyer to renegotiate |
1. Click Edit → tick the row of the disputed line. 2. Click Review in the item toolbar — an amber Review badge appears on the row and the footer Send Back button becomes visible (TC-PO-070303). 3. Click Send Back in the footer — the Send Back dialog opens with the stage selector and a per-item reason field (TC-PO-070308). 4. Fill reason = "Please verify pricing" and confirm (TC-PO-070309). |
po_status transitions in_progress → draft via PO_POST_005; workflow_current_stage resets to the start; last_action = rejected; the reason is appended to tb_purchase_order_comment with type system; URL stays at /procurement/purchase-order/<ref> (TC-PO-070309) and the PO surfaces on the Purchaser's Returned queue; the Manager's My Approvals queue drops the PO from the pending list. |
| PM-HP-04 | Reject an escalated PO to terminate at voided |
PM is on the detail page of an in_progress PO that the Manager has determined should not happen at all (e.g. duplicate of an active PO, or vendor flagged after submit) |
1. Click Edit → tick the row of any item. 2. Click Reject in the item toolbar — the row shows a Reject badge and the footer Reject button becomes visible (TC-PO-070304). 3. Click Reject in the footer — the Reject dialog opens with the optional reason field (TC-PO-070310). 4. Fill reason = "Vendor pricing exceeds budget" and confirm (TC-PO-070311). |
po_status transitions in_progress → voided via PO_POST_010; is_active = false, last_action = rejected, last_action_at_date = now(); the workflow terminates (no further stages); any soft budget commitment is reversed; status badge updates to REJECTED (TC-PO-070311); voided is terminal — re-submission is not possible on this po_no; the buyer is notified and may use the reason to re-raise a corrected PO under a new po_no. |
| PM-HP-05 | Void a sent PO via the non-draft override path (PO_AUTH_007) |
PM is on the detail page of a PO at po_status = sent (no GRN posted yet); the vendor has cancelled the order from their side |
1. Open the PO detail page. 2. From the action toolbar click Void — the Void dialog opens with a mandatory reason field. 3. Fill reason = "Vendor cancelled order from their side" and confirm. |
po_status transitions sent → voided via PO_POST_010; is_active = false, last_action_at_date = now(); the reason is recorded in tb_purchase_order_comment (type system) and in the activity log; vendor-side notification is reversed where the channel supports it; the budget soft-commitment is released; voided is terminal. The Purchaser cannot trigger this action under PO_AUTH_007 — only the Manager. |
| PM-HP-06 | Early-close a partial PO when the vendor cannot supply the remainder (PO_POST_011) |
PO at po_status = partial; line L1 has order_qty = 10, received_qty = 6, cancelled_qty = 0; the vendor has confirmed they cannot supply the outstanding 4 units |
1. Open the PO detail page. 2. From the action toolbar click Close — the Close dialog opens with a mandatory reason field. 3. Fill reason = "Vendor cannot supply remainder" and confirm. |
po_status transitions partial → closed via PO_POST_011; on L1 the application writes back cancelled_qty = order_qty − received_qty = 10 − 6 = 4 so received_qty + cancelled_qty = order_qty; the reason is recorded in tb_purchase_order_comment; status badge updates to CLOSED; closed is terminal — distinct from completed (full receipt) per PO_POST_007; AP-side accruals already raised by prior GRN postings are not touched (AP's responsibility per PO_XMOD_007). The shared authority with the Inventory Manager / Receiver via PO_AUTH_008 permits either role to perform this action. |
| PM-HP-07 | Update the Vendor Ranking & Allocation rule from the configuration workbench | PM logged in; Sidebar → Procurement → Configuration → Vendor Ranking & Allocation loads with the current rule set; one in-flight PO exists at po_status = in_progress that snapshots the prior ranking |
1. Open the Vendor Ranking & Allocation screen. 2. Re-rank vendor V1 up (e.g. from #3 to #1) on the basis of on-time delivery score; adjust scoring weights if the screen exposes them (on-time-delivery, three-way-match success rate, deviation rate). 3. Click Save. 4. Acknowledge the in-flight-PO warning. |
A new rule version is written with effective_from = now(); the rule's version counter increments; the change is recorded in the configuration audit log with the Manager's user-id, the prior value, and the new value; new POs created from this point consume the new ranking; the in-flight PO retains its snapshot per 03-user-flow-procurement-manager.md Section 4 (Configurational handoff — Rule change saved → effective for new POs); a configuration-change notification fires to Purchasers ("Vendor ranking updated"). |
| PM-HP-08 | Tune the Convert-to-PO Grouping rule and the Unit Conversion factor | PM logged in; no active Convert-to-PO run is in progress; one product P1 has a stale order_unit_conversion_factor |
1. Open Procurement → Configuration → Convert-to-PO Grouping. 2. Confirm the primary grouping key (vendor_id, currency_id) and toggle on a secondary key (e.g. delivery_location_id) if the tenant has enabled it. 3. Save. 4. Open Unit Conversion for product P1. 5. Update order_unit_conversion_factor from 1.000 to 1.250 (positive value satisfies PO_VAL_009). 6. Save. |
Both rule sets write new versions with effective_from = now(); audit-log entries written; the grouping change affects the next Convert-to-PO run only — any current draft PO whose lines were already grouped under the old key retains its grouping; the unit-conversion change affects new PO lines only — existing PO lines that already stored order_unit_conversion_factor on the line snapshot continue to use the prior factor for PO_CALC_006 (base_qty = order_qty × order_unit_conversion_factor) and the recompute in PO_CALC_011; configuration-change notifications fire to Purchasers ("Convert-to-PO grouping rule updated", "Unit conversion factor changed for product P1"). |
| PM-HP-09 | Bulk-void stuck POs from a vendor that has gone out of business | PM logged in; vendor V_old has 12 active POs split across sent (8) and partial (4) statuses; some completed POs also exist but are out-of-scope |
1. Open the PO list. 2. Filter by vendor_id = V_old and status sent + partial. 3. Multi-select all 12 rows. 4. Click Bulk Void in the toolbar — the dialog opens with a mandatory reason field. 5. Fill reason = "Vendor out of business — bulk void" and confirm. |
Per PO_AUTH_007 and PO_POST_010, all 12 POs transition to voided; each row records the reason in its own tb_purchase_order_comment and activity log; for the 4 partial POs the received_qty posted by prior GRNs remains intact — only the unfulfilled remainder is voided; the completed POs on the vendor are skipped (terminal states cannot be re-transitioned per PO_POST_010); a summary toast confirms 12 of 12 voided; the Purchaser community is notified and the soft commitments are released. |
| # | Scenario | Expected behaviour (allow/deny + reason) |
|---|---|---|
| PM-PERM-01 | PM approves a PO at the escalated high-value stage where total_amount > threshold and the PM's user-id is in user_action.execute for the current workflow_current_stage |
Allow per PO_AUTH_004 + PO_AUTH_011. The Approve PO action drives in_progress → sent via PO_POST_004 (TC-PO-070305..TC-PO-070307); Send Back drives in_progress → draft via PO_POST_005 (TC-PO-070308..TC-PO-070309); Reject drives in_progress → voided via PO_POST_010 (TC-PO-070310..TC-PO-070311). |
| PM-PERM-02 | PM attempts to approve a PO where total_amount <= threshold and the workflow has not routed the high-value stage to the Manager (Purchaser may self-approve under PO_AUTH_004) |
Deny at the workflow layer. The PO does not appear in the Manager's My Approvals queue because user_action.execute for its current stage does not contain the Manager's user-id. A direct API call to advance the stage is rejected by PO_AUTH_011 with "You are not in the authorised approver set for this stage". The Manager may still observe / read the PO under the read-grant but cannot drive the transition. |
| PM-PERM-03 | PM edits a vendor-ranking entry / a Convert-to-PO grouping key / a unit-conversion factor / the high-value threshold from the configuration workbench | Allow. The configuration screens are gated by the Procurement Manager role. Save writes a new rule version, audit-logs the change, and fires a configuration-change notification. Purchasers have read-only visibility on the same screens (per 03-user-flow-procurement-manager.md Section 2). |
| PM-PERM-04 | PM attempts to edit a Convert-to-PO grouping rule while a Convert-to-PO wizard run is in progress (rule lock) | Deny — the system holds a short-lived rule lock for the duration of an active Convert-to-PO transaction so that the snapshot taken at the start of the run is not invalidated mid-flight. The Save button is disabled with the hover hint "Rule is locked — a Convert-to-PO run is in progress"; the API call returns "Configuration rule is locked while a Convert-to-PO transaction is in flight. Retry once the run completes." |
| PM-PERM-05 | PM runs Bulk Void on 12 POs from a single vendor (multi-select on the PO list) | Allow per-PO under PO_AUTH_007. Each selected PO is evaluated individually: rows at voided, completed, or closed are skipped (terminal); rows at draft, in_progress, sent, or partial transition to voided via PO_POST_010. A single reason text is required by the dialog and is written once per PO in the activity log. The bulk action is logged as N distinct events, one per PO. |
| PM-PERM-06 | PM attempts to delegate configurational authority to a Purchaser temporarily (e.g. while the Manager is on leave) | Deny. Configuration-screen authority is bound to the enum_stage_role = approve of the Procurement Manager role and cannot be delegated to a Purchaser through the PO module's runtime UI. Delegation must be handled at the RBAC layer by the System Administrator (see 03-user-flow-audit-config.md). The Manager may temporarily reassign the transactional approval stage to a backup approver via the workflow definition, but the configuration screens themselves remain gated by the Procurement Manager role. |
| PM-PERM-07 | PM voids a PO at po_status = sent or partial via the override path |
Allow per PO_AUTH_007. From any non-terminal status (draft, in_progress, sent, partial) the Void action drives the PO to voided via PO_POST_010. The Purchaser is denied the same action (covered as PUR-PERM-07 in 04-test-scenarios-purchaser.md). The early-close action (partial → closed) is shared with the Inventory Manager / Receiver under PO_AUTH_008 and exercised via PO_POST_011. |
| # | Scenario | Trigger | Expected error |
|---|---|---|---|
| PM-VAL-01 | PM attempts to approve a PO at the high-value stage whose total_amount no longer exceeds the threshold (a Purchaser-side edit reduced the amount after submit) |
Open the PO; click Approve PO in the footer | Reject — the workflow layer detects the threshold mismatch when re-evaluating the stage at confirm time. The dialog displays "This PO no longer requires high-value approval — the buyer will self-approve on resubmit." and the PO is automatically routed back to draft for re-submission (or held at the current stage depending on the tenant's workflow policy). No status change to sent is performed; PO_POST_004 is not fired. |
| PM-VAL-02 | PM saves a configuration rule edit where the new value is identical to the current value (no diff) | Open Vendor Ranking & Allocation → tweak a sort field then revert to the original value → Save | Reject — the save handler computes the diff and refuses to write a no-op version. Server returns "Rule change rejected — new value is identical to the current version." No new rule version is written; no audit-log entry; no configuration-change notification fires. |
| PM-VAL-03 | PM updates the high-value threshold while a borderline PO is sitting at in_progress on the high-value stage — confirms that the in-flight PO is not re-evaluated against the new threshold |
Open Workflow Threshold; raise the threshold from ฿100k to ฿150k; the borderline PO at total_amount = ฿120k is sitting at in_progress on the high-value stage. Save. |
Accepted — the new threshold takes effect for new POs only. The in-flight PO continues through the high-value stage on the old threshold per the snapshot principle (see 03-user-flow-procurement-manager.md Section 3 — "Save and apply going forward"). Any attempt to re-route the in-flight PO is gated behind the tenant's "Save and re-route in-flight" option which most tenants disable for audit safety. Server records the rule change with effective_from = now() and the audit log captures the in-flight-PO warning that was shown. |
| PM-VAL-04 | PM updates Vendor Ranking scoring weights that do not sum to 1.0 (or 100 %) | Open Vendor Ranking & Allocation → adjust the on-time-delivery weight to 0.5, three-way-match-success to 0.3, deviation-rate to 0.3 (sum = 1.1). Save. | Reject — server returns "Scoring weights must sum to 1.0 (or 100 %)." No new rule version is written. UI may also enforce the constraint inline via a live-sum readout. |
| PM-VAL-05 | PM updates a unit-conversion factor to 0 or a negative value |
Open Unit Conversion for product P1; set order_unit_conversion_factor = 0 or -0.5. Save. |
Reject — the validation re-uses PO_VAL_009 (order_unit_conversion_factor > 0). Server returns "Order UoM must have a positive conversion factor to base UoM." No new rule version is written. Any new PO created against P1 continues to use the prior factor. |
| PM-VAL-06 | PM attempts to Send Back or Reject a PO without providing a reason | Open an in_progress PO → mark items as Review (or Reject) → click footer Send Back (or Reject) → leave the reason field blank → confirm |
Reject — the dialog's confirm button stays disabled while the reason field is empty (for the Send-Back path, where the reason field is mandatory per 03-user-flow-procurement-manager.md Section 2). For the Reject path the reason is documented as optional in TC-PO-070310 but a server-side guard accepts an empty reason and records an empty tb_purchase_order_comment entry. Tenants that require a reason on Reject configure the field as mandatory via the workflow definition. |
| PM-VAL-07 | PM attempts any action (Approve / Send Back / Reject / Void / Close) on a PO already at a terminal state (voided, completed, closed) |
Open a voided PO; the toolbar buttons are hidden / disabled; force a direct API call to advance the status |
Reject — per PO_VAL_015 ("Status transitions follow the state machine in Section 5; out-of-order transitions are blocked"). Server returns "Invalid status transition from voided to sent" (or the analogous from-state). The terminal-state guard is enforced at the API layer regardless of the UI button-disable state. |
| PM-VAL-08 | PM attempts to early-close a partial PO where all lines are already fully received (received_qty = order_qty − cancelled_qty on every line) |
Open a partial PO whose every line satisfies the full-receipt condition; click Close |
Reject — the close handler detects that the PO should be at completed via PO_POST_007 (the GRN-driven transition has not yet run, or has run but a race left the status at partial). Server returns "PO is fully received — please refresh; the document will move to completed." Alternatively the system auto-corrects to completed on the next read. PO_POST_011 does not fire because there is no remainder to write back to cancelled_qty. |
| # | Scenario | Condition | Expected |
|---|---|---|---|
| PM-EDGE-01 | High-value threshold boundary — total_amount is exactly equal to the tenant threshold |
Tenant threshold = ฿100,000.00; PO at total_amount = ฿100,000.00 exactly; PO submitted to in_progress |
Tenant policy decides. Default interpretation per PO_AUTH_004 is total_amount > threshold (strict greater-than) routes to the Manager; total_amount = threshold may self-approve by the Purchaser. The workflow definition's stage rule encodes the comparator (> vs >=). Whichever path is taken, the post-conditions are correct: routed to PM ⇒ Manager's user_action.execute is populated; routed to Purchaser ⇒ Purchaser's user_action.execute is populated. There must be no ambiguity at the comparator — the threshold is single-sided. |
| PM-EDGE-02 | In-flight PO snapshot vs. new rule version boundary | PO A was submitted at T0 and snapshotted vendor-ranking version v1, then sat in the high-value queue. At T1 the PM saves vendor-ranking v2. PO B is submitted at T2 > T1. |
PO A retains its v1 snapshot through approve / send-back / reject — even when the Manager re-opens the detail page, v1 is the version of record for the vendor-allocation context shown on the page. PO B consumes v2. The same principle applies to the conversion factor on lines — tb_purchase_order_detail.order_unit_conversion_factor is denormalised onto the line at line-save time and a subsequent rule edit does not retroactively update existing PO lines (see also PUR-EDGE-02 in 04-test-scenarios-purchaser.md). |
| PM-EDGE-03 | Bulk action with partial failure — some POs in the multi-selection are at terminal states or were just modified by another user | PM selects 12 POs for Bulk Void: 9 at sent, 2 at partial, 1 at completed (terminal). One of the 9 sent POs was just transitioned to partial by a GRN posting in another session. |
The bulk-action handler processes each PO independently. Result: the 1 completed PO is skipped (terminal — PO_POST_010 rejects); the 1 sent-now-partial PO is voided from its current partial state (still non-terminal); the other 10 are voided successfully. The summary toast reports 11 of 12 voided, 1 skipped and a row-level breakdown is available on click. No single failure aborts the bulk operation — each PO is its own transaction. |
| PM-EDGE-04 | Workflow approval-stage delegation expires mid-decision | A backup approver was granted temporary delegation on the high-value stage with valid_until = T_end. The Manager opens the PO detail page at T_end − 30s, marks items Approved, clicks Approve PO at T_end + 10s. |
The stage check at confirm time re-evaluates user_action.execute against the current time. If the delegation has expired, the backup approver is no longer in the set and the API call returns "You are not in the authorised approver set for this stage" per PO_AUTH_011. The page-level state on the client (items marked Approved) is preserved but the document-level transition does not fire; the user must either refresh and retry (if the delegation was renewed) or escalate the PO to the primary approver. |
| PM-EDGE-05 | Save vs. submit race on a configuration rule — two Managers edit the same rule in parallel | Manager A and Manager B both open Vendor Ranking & Allocation at version v3. A saves a re-rank at T (rule advances to v4). B saves a tolerance-band change at T + 1s using the stale v3 baseline. |
First save wins: A's re-rank is persisted as v4. B's save is rejected by the optimistic-concurrency guard with "Configuration rule was modified by another user. Please refresh and re-apply your changes." B's client refreshes to v4, B re-applies the tolerance-band change against v4, and a third save produces v5. The audit log shows both v4 and v5 (A and B respectively) with the rejected race recorded as a metric, not as a rule version. No double-write or lost-update is created. |
X-PO-05 (amendment cycle on Sent PO, where the Manager's override path may be triggered), X-PO-10 (send-back during approval), plus the void/close handoff scenarios that escalate from the Receiver or Finance side.PO_AUTH_004 (high-value approval threshold and Purchaser self-approval), PO_AUTH_005 (Manager-only soft-delete in draft), PO_AUTH_006 (Purchaser or Manager transmit), PO_AUTH_007 (Manager-only void from any non-terminal state), PO_AUTH_008 (Inventory Manager / shared early-close from partial → closed), PO_AUTH_010 (segregation of duties), PO_AUTH_011 (workflow-stage authorisation derived from user_action.execute); Section 5 — PO_POST_002 (submit → in_progress), PO_POST_003 (approve within in_progress), PO_POST_004 (final approval in_progress → sent + transmit), PO_POST_005 (send-back in_progress → draft), PO_POST_010 (void from any non-terminal state), PO_POST_011 (early-close partial → closed), PO_POST_012 (soft-delete in draft).PO_XMOD_005 / PO_XMOD_006 (vendor-pricelist snapshot and deviation-driven routing to the high-value stage), PO_XMOD_007 (three-way-match interaction on void / early-close).../carmen-inventory-frontend-e2e/tests/403-po-approver-journey.spec.ts — persona-journey spec for the FC / Procurement Manager. Covers TC-PO-070101..TC-PO-070103 (Step 1 — My Approval dashboard, PO filter tab, click pending PO row), TC-PO-070201..TC-PO-070203 (Step 2 — PO Detail in IN PROGRESS view, header read-only, Edit + Comment buttons visible), TC-PO-070301..TC-PO-070304 (Step 3 — item-level Approve / Review / Reject marking), TC-PO-070305..TC-PO-070307 (Document Approve flow with dialog and APPROVED/SENT transition), TC-PO-070308..TC-PO-070309 (Document Send Back flow with reason and IN_PROGRESS → DRAFT return), TC-PO-070310..TC-PO-070311 (Document Reject flow with REJECTED status), TC-PO-070312 (Edit-mode cancel without saving), TC-PO-070901 (Golden Journey full FC flow). Shared / mixed-persona coverage in ../carmen-inventory-frontend-e2e/tests/401-po.spec.ts includes budget-gated approval scenarios (approve PO ... งบประมาณไม่เพียงพอ) and three-way-match negatives that interact with the Manager's void / close override paths.PO_XMOD_006 feeds the high-value stage.(vendor_id, currency_id) grouping rule the Manager configures (PM-HP-08).sent → partial → completed; the Manager observes these transitions on the dashboard and intervenes only via early-close (PO_POST_011) or void (PO_POST_010).