At a Glance
Persona: Finance (Officer / Cost Controller + Finance Manager) · Module: costing · Scenarios: ~34
Categories: Happy Path · Permission · Validation · Edge Case
E2E coverage: maps to601-cn.spec.ts,900-period-end.spec.ts,501-grn.spec.tsin../carmen-inventory-frontend-e2e/
This page captures the test scenarios that the Finance persona (Finance Officer / Cost Controller for day-to-day policy and reconciliation, plus Finance Manager for period-lock authority) directly drives in the costing module. Finance is the valuation authority — the role that owns the tb_business_unit.calculation_method policy, the enum_physical_count_costing_method selection, the tb_product.standard_cost cadence, the reconciliation tolerance, the credit-note-amount revaluation approval per COST_AUTH_005, the sub-ledger ↔ GL reconciliation per COST_XMOD_009, the period-end valuation orchestration per COST_POST_007 / COST_POST_008, and (at the Finance Manager level) the closed → locked advance per COST_AUTH_006. Finance does not edit cost_per_unit directly per COST_AUTH_010; cost revaluation flows through credit-notes, compensating stock-in / stock-out, or the period-end rollforward. Their costing-module ownership begins when (a) a Controller escalates a cost-anomaly, (b) a credit-note arrives for approval, (c) reconciliation surfaces a variance, (d) the period-end run is due, or (e) a method-change config request lands, and ends when (a) the policy is applied, (b) the credit-note revaluation posts, (c) variance resolves, (d) the period transitions open → closed → (audit window) → locked. Scenarios are grouped into happy paths (credit-note approval, reconciliation pass, period close + open-next, period lock, method change after drain), RBAC (Finance Officer vs Finance Manager vs Sysadmin), validation (reconciliation above tolerance, close-with-open-credit-notes, method-change-with-on-hand, credit-note revaluation drives lot cost negative), and edge cases (FX revaluation at period end, multi-period reconciliation, locked-period restatement, standard-cost cadence boundary).
| # | Scenario | Pre-condition | Steps | Expected |
|---|---|---|---|---|
| FIN-HP-01 | Approve credit-note-amount revaluation (vendor concession) | committed GRN exists with LOT-X at cost_per_unit = ฿14.00, qty = 50, total_cost = ฿700.00; vendor concedes a ฿100.00 reduction post-receipt; credit-note tb_credit_note raised at pending; Finance Officer finance@blueledgers.com logged in. |
1. Open the credit-note approval queue. 2. Open the specific credit-note; review evidence (vendor email, signed memo). 3. Verify the post-revaluation cost preview: new_cost_per_unit = (700 + (−100)) / 50 = ฿12.00. 4. Click Approve. |
Credit-note doc_status = approved; INV_POST_007 fires; COST_POST_003 writes a new cost-layer row at (LOT-X): in_qty = 0, out_qty = 0, diff_amount = −฿100.00, transaction_type = credit_note_amount. LOT-X's cost_per_unit recalculated to ฿12.00 per COST_CALC_005. GL: Dr AP ฿100.00 / Cr Inventory ฿100.00. Subsequent FIFO consumption from LOT-X picks up ฿12.00. Maps to parent Scenario 3. |
| FIN-HP-02 | Sub-ledger ↔ GL reconciliation pass within tolerance | Current open period 2026-05; tolerance = ฿1.00 per location-cost-centre. Sub-ledger sum for LOC-A: Σ in_qty × cost_per_unit + Σ diff_amount − Σ out_qty × cost_per_unit = ฿100,234.56; GL Inventory net change for LOC-A = ฿100,234.00 (variance ฿0.56). |
1. Open the reconciliation dashboard. 2. Review per-location summary. 3. Confirm LOC-A variance ฿0.56 ≤ ฿1.00 tolerance. 4. Click Mark reconciliation clean for 2026-05 (LOC-A). |
Reconciliation clean recorded; activity log: { event: 'reconciliation_clean', period: '2026-05', location: 'LOC-A', variance: '฿0.56' }. No journal posted. |
| FIN-HP-03 | Reconciliation variance above tolerance — missed credit-note revaluation GL leg | LOC-A variance ฿100.00; drill identifies a credit_note_amount revaluation that posted to cost-layer correctly but whose corresponding GL journal Dr AP / Cr Inventory ฿100.00 was missed due to brief integration outage. |
1. Open the reconciliation drill for LOC-A. 2. Identify the affected credit-note CN-X. 3. Post compensating GL journal: Dr Inventory ฿100.00 / Cr GRN Clearing ฿100.00 (or the missed leg). 4. Re-run reconciliation; variance drops below tolerance. 5. Mark clean. |
Compensating GL journal posted; reconciliation passes; activity log records resolution. Cost-layer ledger unchanged (it was correct). Maps to parent Scenario 14. |
| FIN-HP-04 | Period-end close — FIFO business unit | Closing period 2026-04; FIFO business unit with residual lots at multiple (location, product) keys; Controller signed off variance; reconciliation clean; all source documents (incl. credit-notes) terminal. |
1. Open the period-end orchestration dashboard for 2026-04. 2. Verify pre-close checklist clear. 3. Verify cost-layer-to-snapshot rollup preview. 4. Click Close period 2026-04. |
INV_POST_009 + COST_POST_007 run: tb_period_snapshot rows written per (period_id, location_id, product_id, lot_no, lot_index) with closing_qty / closing_cost_per_unit / closing_total_cost per COST_CALC_006; close_period cost-layer rows write tying closing cost to anchor. Chained COST_POST_008 writes open_period cost-layer rows for 2026-05 carrying lot_seq_no from closed lots (FIFO sequence preserved per COST_CALC_007). tb_period.status = open → closed on 2026-04. Maps to parent Scenario 5. |
| FIN-HP-05 | Period-end close — WA business unit | Same as FIN-HP-04 but WA business unit. | Same steps. | Snapshot row per (location, product) carrying closing_cost_per_unit = current_running_average; single open_period cost-layer row per (location, product) opening 2026-05; next period's first outbound at (location, product) reads from this anchor. Maps to parent Scenario 6. |
| FIN-HP-06 | Period close blocked by open credit-note | Closing period 2026-04; one tb_credit_note at pending with a transaction date in 2026-04. |
1. Open the period-end dashboard. 2. Verify pre-close checklist (red flag on the open credit-note). 3. Click Close period 2026-04. | Reject at close — server returns "Cannot close period 2026-04: 1 credit-note remains at pending. Resolve before closing." Period stays open. Finance approves / rejects the credit-note; re-runs close. |
| FIN-HP-07 | Finance Manager locks closed period | 2026-03 at tb_period.status = closed; 60-day audit window passed; external audit signed off; Finance Manager logged in. |
1. Open period-lock dashboard. 2. Filter to 2026-03. 3. Verify audit sign-off. 4. Click Lock period 2026-03. |
tb_period.status = closed → locked; tb_period_snapshot.closing_cost_per_unit / closing_total_cost permanently immutable for 2026-03; no re-open path. Maps to parent Scenario 5 + 6 lock leg. |
| FIN-HP-08 | Standard-cost monthly cadence update | Tenant's monthly standard-cost batch; N products with cost updates; tenant has enum_physical_count_costing_method = standard. |
1. Open the standard-cost batch interface. 2. Upload / enter the updated standard_cost per product. 3. Save. |
tb_product.standard_cost updated per product; configuration-history written; no cost-layer effect per COST_POST_010. Subsequent count-variance posts pick the new value per COST_CALC_008; recipe baseline refreshes on next access. Maps to parent Scenario 9. |
| FIN-HP-09 | Calculation-method change after drain | Business unit BU-A at calculation_method = average with non-zero on-hand; Finance coordinates drain (transfer to a parallel business unit, write-offs, or migration); confirmed zero on-hand; Sysadmin re-attempts the change to fifo. |
1. Finance verifies drain complete (impact preview shows zero on-hand). 2. Sysadmin saves calculation_method = fifo. 3. Configuration-history entry. |
tb_business_unit.calculation_method = fifo persisted; subsequent inbound assigns lot_seq_no; subsequent outbound consumes FIFO. Existing cost-layer rows (from the pre-change WA period) preserve their cost_per_unit (terminal per COST_AUTH_010). Maps to parent Scenario 8. |
| FIN-HP-10 | Period re-open for audit-flagged revaluation in closed period | Period 2026-04 at closed within audit window; external audit identifies a vendor credit-note that should have revalued LOT-Y in 2026-04 but was missed; Finance Manager re-opens. |
1. Open period orchestration for 2026-04. 2. Click Re-open period 2026-04. 3. Enter justification ("External audit identified missed revaluation on LOT-Y"). 4. Confirm. |
tb_period.status = closed → open; Finance approves the credit-note with transaction date in 2026-04; COST_POST_003 writes the revaluation; LOT-Y cost recalculated; Finance re-runs close → re-writes tb_period_snapshot with corrected closing_cost_per_unit / closing_total_cost; period re-closes. Maps to parent Scenario 15. |
| # | Scenario | Expected behaviour (allow/deny + reason) |
|---|---|---|
| FIN-PERM-01 | Finance Officer approves credit-note-amount revaluation | Allow per COST_AUTH_005. Finance approves; engine fires the revaluation under system context. |
| FIN-PERM-02 | Finance Officer runs reconciliation and posts compensating GL journals | Allow per COST_XMOD_009. Reconciliation read scope + GL journal post (against the inventory control account) are Finance-Officer rights. |
| FIN-PERM-03 | Finance Officer attempts to lock period | Deny — Finance Manager only per COST_AUTH_006. The Lock button is hidden for Finance Officer; API returns "Period lock requires the Finance Manager role." |
| FIN-PERM-04 | Finance Manager re-opens a closed period | Allow with audit log per COST_AUTH_006 / INV_AUTH_006. Justification required; re-open audit-logged. Locked periods cannot be re-opened. |
| FIN-PERM-05 | Anyone attempts to re-open a locked period | Deny per COST_AUTH_006. Server returns "Period <YYMM> is locked; locked periods cannot be re-opened by any role." Corrections post as current-period restatement. |
| FIN-PERM-06 | Finance Officer attempts to edit cost_per_unit on a posted cost-layer row |
Deny — immutable per COST_AUTH_010. API call returns "Cost-layer rows are immutable. Use credit-note-amount or compensating adjustment for cost corrections." |
| FIN-PERM-07 | Finance Officer attempts to change tb_business_unit.calculation_method |
Deny — Sysadmin only per COST_AUTH_001. Finance is the requester / coordinator; Sysadmin executes. The console may surface the request flow but the save is Sysadmin-scoped. |
| FIN-PERM-08 | Finance Officer updates tb_product.standard_cost (in-app batch interface) |
Allow per Finance / cost-control authority — the underlying RBAC for tb_product writes is configured to grant Finance the standard_cost write scope. (COST_AUTH_003 says typically Finance owns this in coordination with Sysadmin / product-config; the actual write authority depends on tenant RBAC.) |
| # | Scenario | Trigger | Expected error |
|---|---|---|---|
| FIN-VAL-01 | Approve credit-note revaluation that would drive lot cost negative | Credit-note diff_amount = −฿1,000.00 on LOT-Z (original total_cost = ฿700, qty 50); revaluation would produce new_cost_per_unit = (700 − 1000)/50 = −฿6.00. |
Reject at approve per COST_VAL_004 (cost non-negative): server returns "Credit-note-amount revaluation would drive cost_per_unit below zero (calculated: −฿6.00). Reject or reduce diff_amount." Credit-note returns to originator. |
| FIN-VAL-02 | Reconciliation marked clean with above-tolerance variance | Finance Officer clicks Mark clean on LOC-A variance of ฿250.00 (above ฿1.00 tolerance). |
Reject at mark-clean — server returns "Variance ฿250.00 exceeds tolerance ฿1.00; resolve via compensating journal or corrective adjustment before marking clean." |
| FIN-VAL-03 | Period close attempted with in_progress source documents (incl. credit-notes) |
Period 2026-04 close attempted while a tb_credit_note is at pending. |
Reject at close per FIN-HP-06 logic — server returns "Cannot close period 2026-04: <N> source documents at non-terminal state." |
| FIN-VAL-04 | Period close without Controller variance sign-off | Same as inventory module — Finance clicks close while Controller hasn't signed off. | Reject at close — server returns "Inventory Controller has not signed off variance review." Mirrors inventory FIN-HP-05. |
| FIN-VAL-05 | Period close with unresolved cost-layer-to-snapshot rollup gap | Cost-layer rollup preview shows a key with null closing_cost_per_unit (e.g. a corrupted state from a partial integration). |
Reject at close per COST_VAL_008 — server returns "Cannot rollforward period <YYMM>: closing snapshot for <N> (location, product, lot) keys is missing or has null closing_cost_per_unit." Period stays open. |
| FIN-VAL-06 | Method change submitted with non-zero on-hand | Finance / Sysadmin attempts calculation_method change on a business unit with Σ on-hand > 0. |
Reject at impact preview per COST_VAL_009 — server returns "Cannot change calculation_method on business unit <code>: <N> products have non-zero on-hand. Drain stock or run elevated migration." Maps to parent Scenario 7. |
| FIN-VAL-07 | Standard-cost update entered as negative | Finance enters standard_cost = −฿5.00 for a product. |
Reject at save — shape validation "standard_cost must be non-negative." |
| FIN-VAL-08 | Lock attempted with open audit-correction requests | Finance Manager attempts to lock 2026-04 (closed) while one open audit-correction request remains. |
Reject at lock — server returns "Cannot lock period 2026-04: 1 open audit-correction request remains." |
| # | Scenario | Condition | Expected |
|---|---|---|---|
| FIN-EDGE-01 | Credit-note revaluation drives lot cost exactly to zero | Credit-note diff_amount = −฿700 on LOT-Z (original total_cost = ฿700, qty 50); revaluation produces cost_per_unit = ฿0.00. |
Allow — boundary inclusive. ≥ 0 passes COST_VAL_004. Lot's cost is ฿0.00; subsequent FIFO consumption from the lot is at ฿0.00 (free goods); GL impact: vendor concession matches receipt cost exactly. Defensible if documented. |
| FIN-EDGE-02 | Reconciliation variance exactly at tolerance | Variance ฿1.00 exactly equals tolerance ฿1.00. |
Boundary inclusive — clean. ≤ tolerance is clean. Mirrors inventory FIN-EDGE-01. |
| FIN-EDGE-03 | FX revaluation at period end (multi-currency inventory) | Foreign-currency-sourced lot (USD-vendor product P-USD); FX rate at period close differs from receipt-time FX. | Snapshot uses receipt-time cost; FX revaluation is a separate GL journal. tb_period_snapshot.closing_cost_per_unit uses the cost-layer's cost_per_unit (recorded in base currency at receipt-time FX). FX revaluation is a Finance journal to FX Gain/Loss; the cost-layer ledger preserves the original cost. Mirrors inventory FIN-EDGE-03. |
| FIN-EDGE-04 | Multi-period reconciliation (prior period still open) | 2026-03 and 2026-04 both open (Q1 audit hold delayed 2026-03 close). |
Allow reconciliation on current period; close order enforced. Sub-ledger sums per period scoped on at_period (YYMM); 2026-03 close must run before 2026-04 close (else 2026-04 opening can't read from a valid 2026-03 closing). |
| FIN-EDGE-05 | Locked-period correction via current-period restatement | 2026-03 is locked; auditor identifies a missed cost-impact; Finance posts a corrective adjustment with current date in 2026-05 referencing 2026-03. |
Posts cleanly in 2026-05. Transaction date is 2026-05; note field links back to 2026-03; restatement disclosure is a Finance reporting concern. The costing module just posts the cost-layer movement in 2026-05. Mirrors inventory FIN-EDGE-05. |
| FIN-EDGE-06 | Concurrent credit-note approvals on different lots of the same product | Finance Officer A approves credit-note CN-1 for LOT-X at T; Officer B approves CN-2 for LOT-Y at T+200ms; same product, different lots. |
Both succeed; no contention. Each revaluation writes a distinct cost-layer row; both lots' cost_per_unit updated independently; running WA shadow refreshes sequentially under FIFO maintenance. |
| FIN-EDGE-07 | Standard-cost update at period boundary | Finance updates tb_product.standard_cost on the last day of 2026-04 at 23:55; a count-variance at 2026-04-30 23:59 posts. |
Snapshot rule — count-variance reads standard_cost at post time. enum_physical_count_costing_method = standard resolves to the value in force at the post timestamp (the new value if the update saved before the count-variance post). Period boundary doesn't shift the rule. |
| FIN-EDGE-08 | Decimal precision in diff_amount aggregation |
Credit-note revaluation diff_amount = −฿0.00001 (5dp full precision); aggregation across thousands of revaluation rows. |
Per-row 5dp; aggregate rounded to 2dp; absorbed into reconciliation tolerance per COST_CALC_010. Mirrors inventory FIN-EDGE-06. |
COST_AUTH_001 / COST_AUTH_003 (policy ownership), COST_AUTH_005 (credit-note approval), COST_AUTH_006 (Finance Manager lock), COST_VAL_004 (cost non-negative — FIN-VAL-01), COST_VAL_006 (credit-note revaluation gate), COST_VAL_008 (period gate at close — FIN-VAL-05), COST_VAL_009 (method change drain — FIN-VAL-06), COST_CALC_005 (revaluation arithmetic — FIN-HP-01), COST_CALC_006 / COST_CALC_007 (period snapshot + rollforward — FIN-HP-04 / FIN-HP-05), COST_POST_003 (revaluation cost-layer write), COST_POST_007 / COST_POST_008 (close / open), COST_XMOD_009 (Finance / GL period-end).900-period-end.spec.ts — canonical period-end for FIN-HP-04 / FIN-HP-05 / FIN-HP-07 / FIN-HP-10; 601-cn.spec.ts — credit-note approval and inventory-side revaluation effect for FIN-HP-01; 501-grn.spec.ts — Stock Movements describe block as supporting context for cost-layer write assertions.credit_note_amount); Finance is the approval authority for the cost-side effect.