At a Glance
Persona: Inventory Controller · Module: inventory-adjustment · Workflow stages: Above-Store-Keeper-threshold queue — review atin_progress; approve tocompleted(below Finance threshold) or escalate to Finance; reject back todraft; cancel inin_progress(ADJ_AUTH_007); count-rollup commit; pre-period-end variance sign-off · Key permissions: approve below Finance threshold (ADJ_AUTH_004); new-lot stock-in approval (ADJ_AUTH_003); commit count-rollup
What this persona does: Reviews above-threshold adjustments and new-lot stock-ins, posts to ledger, and clears variance before Finance closes the period.
The Inventory Controller is the approval authority between the auto-approve threshold and the Finance threshold. The matrix below uses a two-stage axis (Controller-band approval and count-rollup commit) from the Controller's perspective. Rows are derived from Section 2 (Entry Point and Primary Flow) of this file; rule citations refer to inventory-adjustment/02-business-rules § 4 (Authorization Rules) and § 5 (Posting Rules).
| Action | Controller-band approval (฿500–฿10,000) |
Count-rollup commit |
|---|---|---|
Review in_progress documents in approval queue |
✅ (ADJ_AUTH_004) |
✅ (variance lines review) |
Approve stock-in / stock-out (in_progress → completed) |
✅ (ADJ_AUTH_004) — fires posting per ADJ_POST_002 |
✅ auto-advance per ADJ_POST_006 |
| Approve new-lot stock-in (any cost impact) | ✅ (ADJ_AUTH_003) — validates lot identity + cost defensibility |
— |
Reject document (in_progress → draft) |
✅ (ADJ_AUTH_004) — rejection reason in workflow_history |
✅ (can reject count before commit) |
Cancel in_progress document (→ cancelled) |
✅ (ADJ_AUTH_007) |
— |
| Forward above-threshold to Finance | ❌ (out of band — see above-band note) | — |
| Commit count variances (Physical Count / Spot Check) | — | ✅ (ADJ_POST_006) — auto-creates tb_stock_in (overage) + tb_stock_out (shortage) |
Direct-create tb_stock_in / tb_stock_out |
✅ (ADJ_AUTH_001 — same scope as Store Keeper) |
— |
Void completed document (compensating reversal) |
✅ (ADJ_AUTH_007, ADJ_POST_004) |
— |
Monitor variance dashboard (ADJ_CALC_010 period impact) |
✅ (ADJ_CALC_008 variance %) |
✅ |
Edit lines on completed document |
❌ (ADJ_VAL_013 — immutable after posting) |
❌ |
Configure tb_adjustment_type reason codes / thresholds |
❌ (System Administrator per ADJ_AUTH_008) |
❌ |
| Approve above-Controller-threshold documents | ❌ (Finance per ADJ_AUTH_005) |
❌ |
ℹ️ Department Manager review responsibility: The Department Manager role (cost-centre oversight, comment / flag capability) is folded into the Inventory Controller persona group in this wiki. Department Managers receive notifications on documents affecting their cost-centre (resolved via the document's
dimension.department) but do not approve — they comment and escalate to the Controller or Finance.
The Inventory Controller persona (folded with Inventory Manager in the carmen/docs source, and absorbing the Department Manager review responsibility) is the governance authority above the auto-approve threshold and the balance-accuracy owner for the property. Within the inventory-adjustment module the Controller holds:
in_progress tb_stock_in / tb_stock_out documents within the Controller threshold band (฿500–฿10,000 typical, between Store Keeper auto-approve and Finance) per ADJ_AUTH_004. Approval fires in_progress → completed which posts the inventory transaction and GL entry per ADJ_POST_002.ADJ_AUTH_003, where the Controller validates lot identity and cost-per-unit defensibility.ADJ_CALC_008 variance-% calc and drives corrective process changes (training, recount, reason-code reconfiguration request to Sysadmin).ADJ_POST_006 auto-rollup tb_stock_in / tb_stock_out creation; cross-references inventory INV_XMOD_003 / INV_XMOD_004.tb_stock_in / tb_stock_out directly (e.g. when an investigation reveals a discrepancy that the Store Keeper hasn't yet reported, or when SoD per ADJ_AUTH_010 blocks the Store Keeper from raising a write-off for a lot they received).ADJ_POST_004, moves the original to voided after the compensating post.in_progress documents per ADJ_AUTH_007 — when a recount or investigation concludes the adjustment isn't warranted.dimension JSON), notification subscription, comment / flag capability for escalation to Finance.The Controller does not have above-Controller-threshold approval authority (Finance per ADJ_AUTH_005), does not configure reason codes / thresholds (Sysadmin per ADJ_AUTH_008), and does not run the period-end close (Finance Manager per inventory INV_AUTH_006).
The Controller's adjustment-module ownership begins when a document submits at or above threshold (or when a count commits) and ends at one of the boundaries enumerated in Section 4.
Entry points: Five doors into a Controller-driven action on adjustments.
in_progress documents within the Controller's scope (filterable by location, reason, requester, age). Documents enter this queue from Store Keeper submit at or above auto-approve threshold or for new-lot stock-in. This is the primary daily entry.ADJ_CALC_010 period-impact aggregation.ADJ_POST_006 auto-rollup. This is the count-driven entry point.Primary flow (review and approve an above-threshold stock-out, 9 steps — illustrative of the approval pattern):
tb_stock_out at doc_status = in_progress with reason BREAKAGE, total cost ฿2,500 (above auto-approve threshold, below Controller threshold), creator, age (time in queue).dimension), the lines (product, qty, lot-pick-preview / FIFO preview, cost preview), the comments / attachments (damage photo, supervisor sign-off), and the workflow_history (Store Keeper submitted at <timestamp>).BREAKAGE) against the attached photo and the line description (e.g. "5 bottles dropped during transfer from pallet"). Reasons that don't match the evidence (e.g. claiming EXPIRY_WRITE_OFF on a non-expired lot) are flagged for follow-up.BREAKAGE for P-1 at LOC-A this month — process problem, not a one-off").฿2,500 from LOT-1 at ฿10.00 × 250 units, say) — verify the cost is reasonable given recent vendor prices. WA products show the moving average; outlier averages flag for investigation.INV_VAL_005 (no negative balance) live at the moment of approval. If on-hand at the picked lot has changed since submit (e.g. another posting consumed from the same lot), the approval re-validates; rejection here returns the document to draft with a "stock no longer available, please re-pick lot" message to the Store Keeper.in_progress → completed per ADJ_POST_002. Inventory transaction posts; cost-layer rows write; GL journal generates. workflow_history records {stage: 'completed', action: 'approved', by: <controller_id>}. Activity log shows the Controller as the approver.draft; Store Keeper sees the rejection in their queue with the reason in workflow_history; can edit and re-submit or cancel.in_progress; Store Keeper attaches the requested evidence via comment and re-submits to re-trigger approval review.in_progress document per ADJ_AUTH_007. doc_status = cancelled with reason text; terminal; no inventory effect.INV_POST_002: tb_inventory_transaction, tb_inventory_transaction_detail (qty < 0 for stock-out), one or more tb_inventory_transaction_cost_layer rows (FIFO multi-row or WA single per ADJ_CALC_006 / ADJ_CALC_007), GL journal (Dr Breakage Expense ฿2,500 / Cr Inventory ฿2,500). The detail's inventory_transaction_id is stamped.The count-variance commit flow follows a slightly different shape:
tb_count_stock.status = completed. Variance lines are staged: per (location, product, lot), the difference between physical and system qty.ADJ_POST_006:
tb_stock_in with reason COUNT_OVERAGE for all overage lines.tb_stock_out with reason COUNT_SHORTAGE for all shortage lines.completed under the Controller's authority (skipping the explicit approval queue).ADJ_POST_002.tb_count_stock.status = completed_posted.info.countId = <count_uuid> on each rollup document).The direct-create flow follows the Store Keeper's primary flow (Section 2 of 03-user-flow-store-keeper.md) with the Controller as created_by_id. Above-Controller-threshold direct-create routes to Finance per ADJ_AUTH_005.
ADJ_CALC_008 variance threshold (e.g. > 5% of on-hand at a product / location) trigger a deeper investigation: cross-check with physical-count last-count results, check the Store Keeper's recent variance history, walk the floor. Investigation may conclude with a comment-only "approved with note" or with a rejection and recount request.ADJ_VAL_009; (b) the cost-per-unit is defensible (typically read from vendor-pricelist last-price or set to zero with explanatory note); (c) the new lot creation is consistent with the reason (e.g. VENDOR_FREE_REPLACEMENT justifies a zero-cost new lot; bare FOUND_STOCK of an unknown-cost lot raises eyebrows). The default cost-per-unit for true-found-stock with no prior reference is typically zero with a DATA_FIX reason — to avoid inflating inventory valuation on guesswork.฿10,000 cost impact — large recall write-offs, large damage write-offs, large theft write-offs) cannot be approved by the Controller alone. The Controller reviews and either rejects, or forwards to Finance by re-submitting with a Finance approval annotation. Finance picks up from the Finance flow.ADJ_POST_006. For counts with oversize aggregate variance (e.g. > 10% net cost impact), the Controller may instead choose to reject the count and request a recount before committing — preventing the auto-rollup from posting an unjustifiably large adjustment.The Controller's involvement on a given adjustment ends at one of five boundaries:
doc_status = completed; inventory transaction posted. Controller's work on this document ends; no further persona handoff. Activity log records the Controller as approver.doc_status = draft with rejection reason in workflow_history. Store Keeper re-engages.doc_status = cancelled with reason. No inventory effect. Terminal from the Controller's side.in_progress; Finance picks up the review.ADJ_POST_006 auto-rollup; rollup documents auto-advance to completed; count document at completed_posted. Controller's work on the count's adjustment side ends.tb_adjustment_type, thresholds, and integration; Auditor who reads the Controller's approval history and SoD compliance.tb_stock_in / tb_stock_out workflow / last_action columns the Controller reads at approval; tb_adjustment_type reason and info.glAccount the Controller validates against.ADJ_AUTH_003 (new-lot Controller gate), ADJ_AUTH_004 (Controller approval), ADJ_AUTH_007 (cancel / void), ADJ_POST_002 (post fan-out fired by Controller approve), ADJ_POST_004 (void via compensating reversal), ADJ_POST_006 (count-rollup auto-post), ADJ_CALC_008 (variance %), ADJ_CALC_010 (period impact); cross-module ADJ_XMOD_002 / ADJ_XMOD_003 (count rollup).INV_AUTH_003 (Controller as second signature in inventory hierarchy), INV_POST_001 / INV_POST_002 (post effects), INV_XMOD_003 / INV_XMOD_004 (count-variance posting path).