At a Glance
Persona: Purchaser (read-only catalogue consumer) · Module: product · Scenarios: ~33
Categories: Happy Path · Permission · Validation · Edge Case
E2E coverage: indirect — exercised through upstream module specs (300-pr.spec.ts,400-po.spec.ts) in../carmen-inventory-frontend-e2e/
This page captures the test scenarios the Purchaser persona drives in the product module. They are read-only consumers of the catalogue — they search, filter, view, and pick products for PR / PO composition; reference standard cost, last-receiving cost (derived), unit conversions, and vendor mapping; and post comments for stale entries or new-product requests. Because the persona is lookup-only, the scenarios concentrate on search / picker behaviour (filtering, scoping, sorting), read-side RBAC (what they can and cannot see), validation rules they encounter as a consumer (rejected attempts to pick inactive products, rejected attempts to use unconfigured units), and the comment / feedback paths that route their concerns back to the Product Administrator. There are no CRUD scenarios for this persona; their transactional work (composing PRs and POs) lives in purchase-request and purchase-order. Cross-persona handoffs that pivot off the Purchaser (Scenarios 1, 8, 10, 16 in the parent overview) live in 04-test-scenarios.md, not here.
| # | Scenario | Pre-condition | Steps | Expected |
|---|---|---|---|---|
| PR-HP-01 | Search and pick a product on a PR line by code | Purchaser purchase@blueledgers.com logged in; composing a new PR; product COF-001 (Arabica Beans Premium) exists, active, with at least one order-unit conversion (1 CASE = 5 KG) and at least one vendor mapping. |
1. From purchase-request line entry → click product field → picker opens. 2. Type COF-001 in search. 3. Picker shows the row with code, name, base unit (KG), standard cost, last-receiving cost (derived per PRD_CALC_008), vendor count. 4. Click the row. |
PR line populated with product_id = COF-001.id, base unit KG, default order-unit CASE (from is_default = true conversion). Maps to parent Scenario 1. Picker closed; PR line continues with qty / unit-price entry. |
| PR-HP-02 | Search and pick by barcode | Same pre-condition; product has barcode = '8851234567890'. |
1. Open picker. 2. Type 885123 in search. 3. Picker matches against barcode. |
Same as PR-HP-01 — picker resolves and line is populated. Barcode-search is a non-default path on PR but available for cross-checking. |
| PR-HP-03 | Filter by classification | Composing PR; need to find all coffee products. | 1. Open picker. 2. Apply classification filter: category = Beverages → sub-category = Hot → item-group = Coffee Beans. |
Picker shows all live products under that item-group (typically 5–15 rows). Sort by code or by last-receiving cost as needed. Per PRD_AUTH_005 Purchaser has read scope across all live products. |
| PR-HP-04 | Filter by vendor scope on PO line | Composing a PO against vendor "Coffee Imports Co.". | 1. From purchase-order line entry → product picker. 2. Picker scopes to products with tb_product_tb_vendor mapping to this vendor per PRD_AUTH_006. |
Picker shows only products mapped to Coffee Imports Co. — typically a smaller subset. If the product the Purchaser wants is not in the list, they post a comment requesting vendor mapping (decision branch handled by parent Scenario 10 / 16). |
| PR-HP-05 | Inspect product detail before selection | Need to verify standard cost and last-receiving cost before adding a high-value item to PR. | 1. Open picker → search → find product. 2. Click "View details" instead of "Select". 3. Detail view shows: classification path, inherited tax profile, deviation tolerances, all defined unit conversions, vendor mappings, per-location stock policy (informational), and the "Latest Purchase" tab with last 5 GRN receipts (date / vendor / unit cost). | All values render correctly per PRD_CALC_001 (classification path), PRD_CALC_002 (tax profile), PRD_CALC_003 (deviation tolerances), PRD_CALC_008 (last-receiving cost — derived from tb_inventory_transaction_cost_layer). Purchaser closes detail and returns to picker to select. |
| PR-HP-06 | Override default order-unit on PR line | Product has order-unit conversions 1 CASE = 5 KG (default) and 1 BAG = 10 KG; Purchaser wants to order in BAG. |
1. Pick product on PR line; line defaults to CASE. 2. On the line, change the unit dropdown to BAG. 3. Enter qty 2 BAG. |
Line stores qty = 2 in unit BAG; system computes base-unit equivalent 2 × 10 = 20 KG per PRD_CALC_005. Both values surfaced on the line for reference. |
| PR-HP-07 | Post comment for stale standard-cost | Encountered product COF-001 with standard_cost = ฿450; current vendor quote is ฿520; deviation tolerance is 10% (so 10% × 450 = ฿45, vendor quote is ฿70 above standard — exceeds tolerance). |
1. Open product detail → Comments tab. 2. New comment: "Standard cost out of date — vendor Coffee Imports quoted ฿520 today; standard is ฿450." Attach vendor quote screenshot. 3. Save. | tb_product_comment row inserted with user_id = purchase@blueledgers.com, type = user, message, attachments. Product Administrator picks up from comments queue (per parent Scenario 8 / 17). Purchaser may continue with the PR line (deviation will flag at submit for above-threshold approval) or hold the line pending Product Administrator update. |
| PR-HP-08 | Browse catalogue for procurement planning | Quarterly menu refresh; Purchaser wants to confirm all "Hot Beverages" items have at least one active vendor mapping. | 1. Products → list view → filter by classification "Hot Beverages". 2. Sort by "Vendor Count" column ascending. 3. Identify products with vendor count = 0. | Read-only browse per PRD_AUTH_005. Products with zero vendor mappings surfaced; Purchaser exports the list and routes it to Product Administrator for vendor-mapping setup (typically via comment on each affected product or a bulk request channel). |
| PR-HP-09 | Last-receiving cost vs standard-cost comparison | Composing PR; Purchaser wants to benchmark against last actual receipt. | 1. Pick product; expand the Latest Purchase tab on detail. 2. View last 5 GRN receipts with date / vendor / unit cost. | Per PRD_CALC_008: last-receiving cost is derived from tb_inventory_transaction_cost_layer (most recent inbound). Shown alongside standard_cost for comparison. Purchaser uses both — standard_cost as budget reference; last-receiving cost as market reference (per product/03-user-flow-purchaser decision branch). |
| # | Scenario | Expected behaviour (allow/deny + reason) |
|---|---|---|
| PR-PERM-01 | Purchaser searches / filters / views catalogue | Allow. Per PRD_AUTH_005, read-only scope across all live products. Picker filters to product_status_type = active by default; toggle available for inactive view (read-only). |
| PR-PERM-02 | Purchaser views tb_product_tb_vendor and pricelist joins |
Allow. Per PRD_AUTH_006, read-only. Used for vendor-scope filtering on PO and for pricelist-driven unit-price defaults. |
| PR-PERM-03 | Purchaser attempts to create a new product | Deny — Product Administrator required. Per PRD_AUTH_001, create authority is on Product Administrator. Picker has no "create new" affordance for Purchaser. Direct API submission returns "Product creation requires the Product Administrator role." 403 Forbidden. The Purchaser's path is to post a new-product-request comment per parent Scenario 8. |
| PR-PERM-04 | Purchaser attempts to edit tb_product fields (e.g. update standard cost from a high-velocity supplier feed) |
Deny. Same as above — write authority belongs to Product Administrator. Direct API PATCH /product/<id> returns 403 Forbidden. |
| PR-PERM-05 | Purchaser attempts to add a tb_unit_conversion row |
Deny — Product Administrator required. Conversion-factor authority is Product Administrator per PRD_AUTH_002. The Purchaser's path is to post a comment requesting the conversion be added per parent Scenario 10. |
| PR-PERM-06 | Purchaser attempts to edit tb_product_location numeric policy |
Deny. Per PRD_AUTH_004 / INV_AUTH_004, policy edits are Inventory Controller authority. The Purchaser's path is to escalate via Store Keeper feedback channel (rare — typically not a Purchaser concern). |
| PR-PERM-07 | Purchaser posts a comment on a product | Allow. Per PRD_XMOD_011, all roles can post comments. Comment routes to Product Administrator for review and response. |
| PR-PERM-08 | Purchaser views activity log on a product | Allow read-only per PRD_XMOD_011. Sees creates, edits, status transitions, soft-deletes (where applicable) on the product master. |
| PR-PERM-09 | Purchaser views soft-deleted products | Allow read if "show inactive / deleted" filter is toggled — but cannot select them on a new line per PRD_XMOD_001. Used for historical reference / research only. |
| PR-PERM-10 | Purchaser exports the catalogue | Permitted at the role level for non-sensitive views (basic product list, classification). Export of sensitive fields (standard_cost, vendor mapping) may be restricted to Product Administrator / Auditor depending on tenant configuration. |
| # | Scenario | Trigger | Expected error |
|---|---|---|---|
| PR-VAL-01 | Pick inactive product on new PR line (PRD_XMOD_001) |
Purchaser toggles "show inactive" filter and tries to select an inactive product. | Reject at line save — "Product <code> is inactive or deleted and cannot be added to new transactions." 400 Bad Request. Picker normally greys out inactive rows when the toggle is on; direct API submission re-checks. Maps to inactive-product handling. |
| PR-VAL-02 | Pick a unit not configured for the product (PRD_XMOD_006) |
Product has order-unit conversions 1 CASE = 5 KG only; Purchaser attempts to enter qty in BAG via API. |
Reject at line save — "No conversion factor defined for unit BAG → KG on product COF-001." 400 Bad Request. Picker normally shows only configured units; this is a defensive backend check. Resolution: post comment requesting the conversion per parent Scenario 10. |
| PR-VAL-03 | Pick a product not mapped to the PO's selected vendor | PO scoped to vendor V1; product P1 has no tb_product_tb_vendor row for V1. |
Reject at line save — "Product P1 is not mapped to vendor V1." 400 Bad Request. (Convention; not a hard schema rule — application-enforced.) Resolution: change the PO's vendor, or post a comment requesting the mapping per parent Scenario 16. |
| PR-VAL-04 | Submit PR line with unit-price exceeding price_deviation_limit |
Product has price_deviation_limit = 10, standard_cost = ฿100; Purchaser enters unit-price = ฿120 (20% above standard, exceeds 10%). |
Soft-block — the line is accepted but flagged for above-threshold approval per purchase-request's validation rules (the product-side rule per PRD_CALC_003 feeds the gating). The flag is visible on the PR's approval workflow. Per PRD_XMOD_007, the deviation tolerance is the gate. |
| PR-VAL-05 | Comment with malformed attachment | Purchaser posts comment with attachment metadata missing fileToken. |
Reject at submit — comment attachment shape is enforced per the tenant-comment convention; malformed attachments return 400 Bad Request. |
| # | Scenario | Condition | Expected |
|---|---|---|---|
| PR-EDGE-01 | Picker shows product but vendor scope is empty | Product is active and has tb_product_tb_vendor mappings to vendors V1 and V2 only. PO scoped to V3. |
Picker filters per PRD_AUTH_006; product does not appear. Purchaser sees the empty-result state with prompt "Product not mapped to V3 — request mapping via comment." Path forward: switch PO vendor or request mapping per Scenario 16. |
| PR-EDGE-02 | Standard cost just updated mid-session | Purchaser opens picker; concurrently Product Administrator commits a standard-cost change to a product the Purchaser is viewing. | Picker reads on demand; the next picker open shows the new standard_cost. The current detail view may show stale data until refresh. No conflict — read-side staleness is acceptable. |
| PR-EDGE-03 | Multi-level deep classification filter | Filter by category = Beverages (depth 1); 5 sub-categories, 20 item-groups, 200 products beneath. | Picker shows all 200 products. Pagination kicks in per TR-API-004. Performance: indexed query on item-group path; sub-second response expected. |
| PR-EDGE-04 | Decimal precision on unit-price | Product's standard cost is ฿11.33333 (5dp from weighted-average ledger derivation); Purchaser enters unit-price ฿11.33. |
Deviation check at line submit per PRD_CALC_003: (11.33 vs 11.33333) → within 0.003% deviation → well within typical tolerance. No flag. Display rounded to 2dp. |
| PR-EDGE-05 | New product just created and not yet on cached picker | Product Administrator creates new product at T; Purchaser opens picker at T+50ms — frontend cache may not yet have the new row. | Frontend picker typically debounces / caches results for 30s–5min depending on tenant config. The Purchaser may need to refresh / search explicitly to see the new product. No state corruption — the product exists in DB; the picker is just behind. |
| PR-EDGE-06 | Last-receiving cost is null (product never received) | Product P1 is active but no GRN has ever been posted for it. | Per PRD_CALC_008: last-receiving cost is null (no inbound cost-layer to derive from). Detail view shows "—" or "No purchase history". Standard cost is the only reference value. |
| PR-EDGE-07 | Same product different lots have different costs | Product is FIFO-costed (per business-unit calculation_method = fifo); has LOT-1 at ฿10 and LOT-2 at ฿14 on hand. |
Last-receiving cost per PRD_CALC_008 returns the most recent inbound, e.g. LOT-2 at ฿14. FIFO-vs-WA-cost-pick is irrelevant on the read side (Purchaser sees last-receiving regardless of method). |
| PR-EDGE-08 | Product moved to a different item-group mid-PR-composition | Purchaser has PR draft with product P1's line populated (snapshot tax-profile and deviation values); Product Administrator re-classifies P1 to a different item-group with different inherited tax profile. | Per PRD_LIFE_010: classification change is prospective. The Purchaser's existing PR line keeps its snapshot. New PR lines for P1 after the re-classification read the new effective tax profile. |
| PR-EDGE-09 | Vendor mapping deleted while Purchaser is mid-PO | Purchaser opens PO line picker scoped to V1; product P1 was just unmapped from V1 (tb_product_tb_vendor row soft-deleted). |
Picker re-fetch returns the updated set excluding P1. If Purchaser has already populated the line and saves, the application revalidates per PR-VAL-03 and rejects with "Product P1 is not mapped to vendor V1." Edge case; rare. |
PRD_CALC_001 / PRD_CALC_002 / PRD_CALC_003 / PRD_CALC_005 / PRD_CALC_008 referenced in Section 1; authorization PRD_AUTH_005 (Purchaser read-only), PRD_AUTH_006 (vendor-mapping read), PRD_AUTH_009 (picker filter); cross-module PRD_XMOD_001 (inactive product rejected on new line), PRD_XMOD_006 (order-unit conversion required), PRD_XMOD_007 (deviation tolerance gates PR / GRN approval). The validation rules per PR_VAL_* on purchase-request are the line-side counterparts.purchase@blueledgers.com.tb_inventory_transaction_cost_layer.cost_per_unit which is what PRD_CALC_008 reads back to surface last-receiving cost.