At a Glance
Persona: Requester (Outlet Manager at consuming destination) · Module: store-requisition · Scenarios: ~29
Categories: Happy Path · Permission · Validation · Edge Case
E2E coverage: maps totests/701-sr.spec.ts(TC-SR-010001..010005, 020001..020003, 030001..030004, 040001..040005, 050001..050005) in../carmen-inventory-frontend-e2e/
This page captures the test scenarios that the Requester persona (the Outlet Manager at the consuming destination) directly drives in the store-requisition module. The Requester's involvement begins when a stock need is identified at the outlet (a planned production event, a par-level breach, a banquet event sheet, or a recipe-driven auto-create from [recipe](/en/inventory/recipe)) and ends when the SR leaves the requester zone — either through submit (handoff to Approver), withdrawal (SR_AUTH_004 → cancelled), or all-lines rejection by approver (SR_POST_004 tail). Scenarios are grouped into happy paths (draft walk: create / line entry / submit; auto-create from recipe; resubmit after send-back), RBAC (Requester not assigned to department, segregation-of-duties carry-over at the approve step), validation (negative tests against SR_VAL_001–SR_VAL_009 that the Requester can trigger at save or submit), and a small set of edge cases around soft vs hard source-availability mode, split cost-dimension lines, decimal precision on requested_qty, and concurrent submits on the same outlet. Cross-persona handoffs that pivot off the Requester (Scenarios 1, 2, 3, 4, 11, 12 in the parent overview) live in 04-test-scenarios.md, not here.
| # | Scenario | Pre-condition | Steps | Expected |
|---|---|---|---|---|
| REQ-HP-01 | Create SR draft against valid source / destination — single-line kitchen pull |
Outlet Manager purchase@blueledgers.com logged in; member of Department-Main-Kitchen; permitted between WH-Central (source, inventory) and Main-Kitchen (destination, direct); product Rice-1kg active with on-hand at source. |
1. Navigate to /store-operations/store-requisitions → click New SR. 2. Pick destination Main-Kitchen (auto-populated from home outlet). 3. Pick source WH-Central. 4. Pick sr_type = 'issue'. 5. Enter sr_date = today, expected_date = today + 2 days, description = "weekly replenishment". 6. Add line: product Rice-1kg, requested_qty = 25.000. 7. Click Save. |
tb_store_requisition row persisted at doc_status = draft; sr_no assigned per tenant policy; requestor_id / department_id set from user profile; one tb_store_requisition_detail row written (product_id = Rice-1kg, requested_qty = 25.000, approved_qty = 0, issued_qty = 0); no inventory or GL impact; SR appears in the list filtered by draft and is editable. Maps to TC-SR-010001. |
| REQ-HP-02 | Submit SR — draft → in_progress with all rules passing |
REQ-HP-01 state plus: source on-hand on every line ≥ requested_qty; tenant in soft SR_VAL_009 mode. |
1. Open the draft SR. 2. Confirm header and line data. 3. Click Submit for Approval. |
All submit-time rules pass (SR_VAL_001–SR_VAL_009); doc_status = draft → in_progress; workflow_current_stage advances to first approval stage; user_action.execute populated with first-stage approvers (Department Head bann@blueledgers.com); last_action = submitted, last_action_at_date = now(), last_action_by_id = requester; workflow_history records { stage: 'submitted', action: 'submit', by, at }; per-line history appended with submit entry; document leaves the Requester's hands. Maps to TC-SR-050001. |
| REQ-HP-03 | Add multiple lines, mixed UoM, with cost-dimension split | Outlet Manager logged in; products Rice-1kg, Flour-1kg, Sugar-1kg active; outlet allocates rice 60% Banquet / 40% A-la-carte (two cost-centres). |
1. Create draft SR (steps 1–5 of REQ-HP-01). 2. Add line: Rice-1kg, requested_qty = 15.000, dimension = [{cost_centre: 'Banquet'}]. 3. Add separate line: Rice-1kg, requested_qty = 10.000, dimension = [{cost_centre: 'A-la-carte'}]. 4. Add line: Flour-1kg, requested_qty = 12.000. 5. Add line: Sugar-1kg, requested_qty = 8.000. 6. Save. |
Four tb_store_requisition_detail rows written; the two Rice-1kg rows are distinct because their dimension JSON differs (SRT1_* unique index permits this — see 01-data-model.md §5 item 10); the four lines flow independently through approval and fulfilment; total of 4 rows × independent quantities. Maps to TC-SR-020001 + REQ-EDGE-02 variant. |
| REQ-HP-04 | Auto-create SR draft from recipe demand | [recipe](/en/inventory/recipe) module has computed ingredient demand for a banquet event at Main-Kitchen (200 covers); ingredients map to source WH-Central. |
1. Recipe module posts a draft SR with info.recipe_id = "EVENT-2026-05-15-001" and pre-populated lines. 2. Requester opens the auto-created draft from the queue. 3. Reviews quantities (may adjust downward; upward changes flagged as recipe-inconsistent). 4. Clicks Submit for Approval. |
The auto-created SR persists info.recipe_id as the back-reference end-to-end; line quantities reflect the recipe BoM × cover count; on submit the SR enters the normal approval flow; variance at period close against the recipe's computed demand is surfaced in the variance dashboard. Maps to manual / planned (no E2E coverage; recipe-driven creation lives in the recipe module). |
| REQ-HP-05 | Resubmit after send-back from Approver | An earlier SR was send-back by Approver bann@blueledgers.com with review_message = "please justify the 25-unit rice request — par level is 18" on the Rice line. SR is at doc_status = in_progress, workflow_current_stage = requester stage. |
1. Open the returned SR (queue shows it under "Action Required — your input"). 2. Reduce requested_qty from 25.000 to 18.000 on the Rice line. 3. Add a header description addendum acknowledging par level. 4. Click Resubmit. |
requested_qty = 18.000 written on the line; last_action = submitted; workflow advances back to the approval stage; per-line history appended with { status: 'submit', message: 'amended per review', by, at }; the Approver's earlier review_by_id / review_message are preserved for audit; previously approved lines on the SR (if any) remain approved (do not revert). Maps to TC-SR-040001 variant. |
| REQ-HP-06 | Withdraw own SR at first approval stage | Requester submitted an SR; workflow is at first approval stage but Approver has not yet acted (approved_by_id IS NULL on every line). |
1. Open the in_progress SR. 2. Click Withdraw. 3. Enter reason "duplicate of SR-2026-05-15-007; withdrawing this one". 4. Confirm. |
doc_status = in_progress → cancelled per SR_AUTH_004; reason text recorded on the SR header / activity log; no inventory or GL impact; Approver's queue no longer shows the SR; document is terminal. Past the first approval stage (after any approver action), withdrawal is blocked — only the Approver can reject. |
| # | Scenario | Expected behaviour (allow/deny + reason) |
|---|---|---|
| REQ-PERM-01 | Outlet Manager creates SR at doc_status = draft and submits |
Allow. Requester owns the editable draft → in_progress transition for their own department's SRs. New SR, line entry, attachment upload, and Submit are all enabled on a non-submitted SR. The Approver and Fulfiller see the document in in_progress after submit but it is no longer editable by the Requester. Maps to TC-SR-010001 + TC-SR-050001. |
| REQ-PERM-02 | User NOT assigned to department attempts to create SR for that department | Deny — RBAC. The requestor@blueledgers.com user is not a member of Department-Main-Kitchen. Attempting to create an SR with department_id = Department-Main-Kitchen is rejected at create with "You are not authorized to create requisitions for this department."; no tb_store_requisition row written. Per SR_AUTH_001. Maps to TC-SR-010002. |
| REQ-PERM-03 | Requester edits own draft SR |
Allow. Header and line edits, attachment upload, withdraw — all enabled while doc_status = draft. Per SR_AUTH_002. |
| REQ-PERM-04 | Requester attempts to edit own in_progress SR (after submit) |
Deny — document has left requester's hands. Edit endpoints reject with "SR at status in_progress is not editable by the requester; the document is under approval / fulfilment." Exception: if the workflow has routed the SR back to the requester stage (send-back from approver), editing is re-enabled at the requester stage only. Per SR_AUTH_002 second clause. |
| REQ-PERM-05 | Requester attempts to approve own SR | Deny — SoD. Even if the Requester also holds the Approver role, SR_AUTH_011 blocks the approve action on their own SR with "You raised this requisition; another user must approve it."; no approved_qty write; workflow does not advance. The Requester must wait for a different approver. |
| REQ-PERM-06 | Requester attempts to withdraw own SR past first approval stage | Deny — workflow advanced. Once any approver has acted on any line (approved_by_id IS NOT NULL on at least one line), the Withdraw button is hidden / disabled. Direct API call returns "Cannot withdraw an SR that has been actioned by an approver; ask the approver to reject if needed." Per SR_AUTH_004 second clause. |
| REQ-PERM-07 | Requester attempts to edit completed SR |
Deny — terminal state. Header and line edits blocked across all personas (not just Requester) on completed SRs; the only post-commit correction path is [inventory-adjustment](/en/inventory/inventory-adjustment). Maps to permission-denial across personas. |
| # | Scenario | Trigger | Expected error |
|---|---|---|---|
| REQ-VAL-01 | Missing sr_no at create |
Tenant numbering policy fails to assign sr_no (system issue); requester clicks Save. |
Reject at create — SR_VAL_001. Server returns "SR reference number is required and must be unique." (System / Sysadmin to investigate the numbering service.) |
| REQ-VAL-02 | Missing source / destination locations at submit | from_location_id or to_location_id left null after the requester rushed through the form; clicks Submit. |
Reject at submit — SR_VAL_002. Server returns "Source and destination locations are required and must differ." Same error if source = destination. Maps to TC-SR-050002. |
| REQ-VAL-03 | Movement-type / destination-type mismatch | Requester picks sr_type = 'issue' but destination is tb_location.location_type = 'inventory' (a warehouse, not a consuming location). |
Reject at save / submit — SR_VAL_003. Server returns "Movement type 'issue' requires a 'direct' destination; selected destination is 'inventory'." Requester must either change sr_type to 'transfer' or pick a different destination. |
| REQ-VAL-04 | Missing sr_date or expected_date < sr_date |
Requester left sr_date null or entered expected_date = sr_date − 1 day; clicks Save or Submit. |
Reject — SR_VAL_004. Server returns "Requisition date is required; expected date must be on or after requisition date." Maps to TC-SR-010004 (missing expected delivery date). |
| REQ-VAL-05 | Missing requestor_id / department_id at submit |
Submit fires without populating requester / department (system bug — but possible via API). | Reject at submit — SR_VAL_005. Server returns "Requester and requesting department are required and the requester must belong to that department." |
| REQ-VAL-06 | Empty SR (no lines) at submit | Requester forgot to add lines; clicks Submit. | Reject at submit — SR_VAL_006 cascaded. Server returns "SR must contain at least one line item before it can be submitted." Maps to TC-SR-050003 (empty line items). |
| REQ-VAL-07 | Inactive / discontinued product on a line | Requester picked a product that has since been marked inactive (active in the catalog cache but inactive in master). | Reject at save line / submit — SR_VAL_006. Server returns "Product is required and must be active for issue from the source location." |
| REQ-VAL-08 | Duplicate product+dimension on the same SR | Requester adds Rice-1kg with dimension = [{cost_centre: 'Banquet'}] twice (instead of editing the existing line). |
Reject at save line — SR_VAL_007. Server returns "This product is already on the SR with the same cost-dimension allocation; edit the existing line or use a different dimension to split." Per the SRT1_* unique index. |
| REQ-VAL-09 | requested_qty = 0 or negative on a line |
Requester left the quantity at default 0, or accidentally typed -25. |
Reject at submit — SR_VAL_008. Server returns "Quantities must satisfy 0 ≤ issued_qty ≤ approved_qty ≤ requested_qty; requested quantity must be greater than zero at submit." Save may be warn-only on 0; submit blocks. Maps to TC-SR-020002 (invalid quantity). |
| REQ-VAL-10 | Source on-hand insufficient (hard mode) | Tenant in hard SR_VAL_009 mode; requested_qty = 100; source on-hand = 60; available (on-hand minus reservations) = 50. |
Reject at submit — SR_VAL_009 hard. Server returns "Requested quantity 100 exceeds available stock 50 at source location WH-Central." Requester reduces requested_qty to 50 or below, or raises a separate SR against a different source. Maps to TC-SR-020003 (insufficient stock) and TC-SR-030002 (negative inventory). |
| # | Scenario | Condition | Expected |
|---|---|---|---|
| REQ-EDGE-01 | Soft-mode source-availability warning | Tenant in soft SR_VAL_009 mode; requested_qty = 100; source on-hand = 60; available = 50. |
Submit allowed with warning. The screen shows a soft warning ("Requested quantity exceeds available stock; the Approver may trim and the Fulfiller may need to short-fulfil"); the requester chooses to submit anyway. The Approver sees the warning at approval time and decides whether to trim or approve as-is. Variance is the expected outcome at fulfilment. |
| REQ-EDGE-02 | Split cost-dimension on the same product | Same-product two-row entry per REQ-HP-03; the two rows have distinct dimension JSON values. |
Both rows persist (the SRT1_* unique index includes dimension); they flow independently through approval (each can be trimmed / rejected separately) and fulfilment (each writes a separate inventory transaction); journal entries split the cost across the two cost-centres at commit. Edge: identical product+dimension is a duplicate per REQ-VAL-08. |
| REQ-EDGE-03 | Decimal precision on requested_qty |
Outlet manager enters requested_qty = 12.345 (3 decimal places, well within Decimal(20, 5)). |
Accepted at storage. Quantity stored at 5dp precision; display rounded half-up to 3dp ("12.345"); flows through approval and issue unchanged; the linked inventory transaction's received_base_qty / cost_per_unit use the same precision contract. Per SR_CALC_007. |
| REQ-EDGE-04 | Concurrent submits on the same outlet (two requesters, two SRs against the same source) | Outlet has two requesters (Outlet Manager and a deputy); both submit an SR against WH-Central at the same time, both requesting Rice-1kg quantity that together exceeds source on-hand. |
Both submits succeed individually. Each SR passes SR_VAL_009 on its own snapshot (the check is per-SR, not aggregated across concurrent submits). The race is resolved at approval (Approver sees both and can trim) and at fulfilment (SR_VAL_013 live check at commit catches the over-commitment). One SR commits in full; the other commits partial or hits a stock-out. Variance recorded on the partial. |
| REQ-EDGE-05 | Long description and many attachments |
Requester writes a 4,000-character description and attaches 10 PDF / image files via tb_store_requisition_comment.attachments. |
Accepted within Prisma limits. description is VarChar (no length cap); attachments JSONB array carries the 10 file refs ({originalName, fileToken, contentType}). Storage backend (S3) holds the files; the SR carries only the tokens. Approver / Fulfiller see the attachments in the activity log. |
| REQ-EDGE-06 | Auto-save during line entry | Requester is mid-line-entry; system auto-saves every 60 seconds (TC-SR-040003); requester closes browser without explicit save. | Draft persisted. Auto-save writes the in-progress line state to tb_store_requisition / tb_store_requisition_detail every 60 seconds while doc_status = draft; on reopen the requester sees their last auto-saved state. No data loss within the 60-second window; data within the window is lost on a hard crash. Maps to TC-SR-040003. |
issue), Scenario 2 (full happy path transfer), Scenario 3 (Approver trim and partial fulfilment), Scenario 4 (send-back / amend), Scenario 11 (admin void on pre-commit), Scenario 12 (recipe-driven auto-create).draft → submit), the decision branches (soft warning, hard block, split dimension, emergency, recipe-driven, send-back response, withdraw), and the exit handoffs.SR_VAL_001 (sr_no required + unique), SR_VAL_002 (source / destination required, must differ), SR_VAL_003 (movement-type ↔ destination-type compatibility), SR_VAL_004 (dates), SR_VAL_005 (requester / department + membership), SR_VAL_006 (product active), SR_VAL_007 (product+dimension unique), SR_VAL_008 (quantity invariant — submit-side: requested_qty > 0), SR_VAL_009 (source availability soft / hard).../carmen-inventory-frontend-e2e/tests/701-sr.spec.ts — canonical Playwright spec for the SR module. Requester-relevant test groups: TC-SR-010001..010005 (Create Requisition — REQ-HP-01, REQ-PERM-02, REQ-VAL-02, REQ-VAL-04, alternate flow Quick Create), TC-SR-020001..020003 (Add Items — REQ-HP-03, REQ-VAL-09, REQ-VAL-10), TC-SR-030001..030004 (Real-time Inventory — REQ-EDGE-01, REQ-VAL-10), TC-SR-040001..040005 (Save / Auto-save — REQ-HP-05, REQ-EDGE-06), TC-SR-050001..050005 (Submit — REQ-HP-02, REQ-VAL-02, REQ-VAL-06, REQ-PERM-02 variant). Permission-denial coverage uses the requestor@blueledgers.com fixture; the happy-path Requester uses purchase@blueledgers.com.draft SR for the Requester to review.SR_VAL_009) reads from tb_inventory_status; lot / cost-layer data is downstream of the Requester's role.