Book 2 · Chapter 7 — Item Master & Pricing¶
Bizwiz Module Guide — Inventory & Stock. The "Maintain Item" area of the inventory navigation.
1. Purpose¶
Every physical thing Bizwiz can buy, count, break down, price, promote, or sell has to exist first as a catalogue record. This chapter covers the part of Bizwiz that creates and maintains that catalogue — the item master — and everything that controls how much an item costs the business and how much it sells for.
In plain terms, this area lets a distributor:
- Create and describe products — give each item a stock code, name, category, subcategory, pack size, tax setting, and picture; define the units it's sold in (e.g. a piece, a dozen, a carton) and how those units convert into each other.
- Set and change prices — the cost the business paid, the selling price, and the profit margin — either one item at a time, in bulk ("batch price change"), or through reusable price lists attached to sales routes, branches, or specific customers.
- Control the money-sensitive bits carefully — because prices directly affect profit, most changes route through an approval workflow. Someone requests a change; a manager approves or rejects it; and every change is recorded in a price history.
- Reserve, block, promote, and retire items — freeze stock for a specific purpose (reserved stocks), stop an item from being sold or repriced (blocked items), run promotions and hampers (gift bundles), and retire items that are no longer stocked.
- Track true cost — the weighted average cost of an item rises and falls as new stock arrives at different prices; this area records that history.
The recurring theme is governance: creating an item, editing its critical fields, changing its price, blocking it, retiring it at a branch, or changing its stock levels almost always creates a request that a privileged user must approve before it takes effect. This chapter maps each of those request→approval loops.
A note on model naming. Almost every table and model in this area is prefixed
Wa/wa_(for exampleWaInventoryItem,wa_inventory_items). "WA" stands for Weighted Average — the current-generation inventory schema. Older, non-Watables still exist in places and are flagged as legacy where found.
---¶
2. Users & roles¶
Access is gated by permission strings of the form module___action (three underscores). The nav parent "Maintain Item" opens when a user holds maintain-items___view or is the superadmin role (config('app.allowed_role'), role_id 1). Below are the permissions that gate each function in this chapter.
| Function | Permission string(s) | Source |
|---|---|---|
| View item list / "Manage Items" | maintain-items___view |
sidebar:255, controller index:142 |
| Add new item | maintain-items___add |
InventoryItemController:1409 |
| Edit item | maintain-items___edit |
InventoryItemController:2592 |
| View QOH across branches | maintain-items___view-per-branch |
InventoryItemController:166 |
| Hide QOH from a user | maintain-items___restrict-qoh-visibility |
InventoryItemController:154 |
| Item approval (new/edit/rejected/history/stock-status) | maintain-items___item-approval |
sidebar:455; controller:5410 |
| Approve branch retire | maintain-items___approve-branch-retire |
ItemBranchStatusController:135,175 |
| Request branch retire | maintain-items___request-branch-retire |
ItemBranchStatusController:29 |
| Item block approval | maintain-items___item-block-approval |
sidebar:496 |
| View blocked items | blocked-items___view-blocked-items |
sidebar:313 |
| Manage standard cost | maintain-items___manage-standard-cost |
PriceChangeController; sidebar:280 |
| Change cost price | maintain-items___change-cost-price |
sidebar:281 |
| Change selling price | maintain-items___change-selling-price |
sidebar:282 |
| Reserved stocks | reserved-items___view / ___create / ___invoice / ___destroy / ___release / ___revert |
Permissions/Inventory.php |
| Subbin items | item-subbins___view / ___update |
sidebar:270 (gated by activate-subbins setting) |
| Weighted averages | weighted-average-history___view |
sidebar:449 |
| Promotions | promotions___view / ___add / ___edit |
sidebar:345 |
| Hampers | hampers___view / ___create / ___edit |
sidebar (Promotions submenu) |
| Sales basket | basket___view / ___prompt-create-basket / ___create-basket / ___populate-basket / ___share-basket / ___retire-basket |
Permissions/Inventory.php |
| Price lists | price-lists___view, price-lists___attach_route_branch |
sidebar:339; PriceListController |
| Additional cost price lists | additional-cost-price-lists view (via can('view', ...)) |
sidebar:307 |
| Transport sell price requests | maintain-items___manage-standard-cost |
InventoryTransportPriceChangeRequestController |
Typical human roles (inferred from the permission split):
- Storekeeper / branch inventory clerk — views items and QOH for their branch, may request retire/stock-status/price changes, populates sales baskets.
- Category / pricing manager — creates items, changes prices, builds price lists and promotions.
- Approver / supervisor — holds
item-approval,approve-branch-retire,item-block-approval; sits on the receiving end of the request queues. - Superadmin (role_id 1) — bypasses all permission gates and can override price-below-minimum guards.
---¶
3. Processes¶
3.1 Item master — create / edit with approval¶
Trigger: A user opens Inventory → Maintain Item → Manage Items (maintain-items.index) and clicks Create, or edits an existing item.
The item list is built by InventoryItemController@index. QOH ("qty on hand") is computed live by summing wa_stock_moves.qauntity [sic] for the item, optionally scoped to the user's branch unless they hold maintain-items___view-per-branch; qty-on-order sums approved, un-GRN'd purchase-order lines (InventoryItemController.php:161–189). Whether the form is a 2-step or 3-step wizard depends on the INVENTORY_SETUP administration setting: value UOM BASED enables a third UOM step (InventoryItemController.php:149).
The critical branch is the approval gate, controlled by administration setting REQUIRE_APPROVAL_FOR_NEW_OR_EDITED_INVENTORY_ITEMS (defaults to true when unset — InventoryItemController.php:1820, 3108):
stateDiagram-v2
[*] --> Draft: user fills form
Draft --> PendingNewApproval: store() & approval required
Draft --> Approved: store() & approval NOT required (direct insert)
PendingNewApproval --> Approved: item_new_approval_approve()
PendingNewApproval --> Rejected: item_new_approval_reject()
Approved --> PendingEditApproval: update() changes a critical field & approval required
Approved --> Approved: update() non-critical change (applied directly)
PendingEditApproval --> Approved: update_approval() (no blocking changes)
PendingEditApproval --> Rejected: reject
- Create (new item): If approval is required, no live item row is created yet. Instead a row goes into
wa_inventory_item_approval_statuseswithstatus = "Pending New Approval"and the entire proposed item captured as JSON innew_data. A Task Center task (inventory_item_new) is registered and approvers (users withmaintain-items+item-approval) are notified. On approval (item_new_approval_approve, controller:2335) the realwa_inventory_itemsrow is created,stock_id_codeis generated (auto-numeric or manual), suppliers / conversion units / integration codes / branch settings are written, and aSyncApprovedItemToVscujob is dispatched (VSCU = KRA fiscal device sync). Rejection setsstatus = "Rejected"and cancels the task. - Edit (existing item):
update()runsgetOptimizedFieldChanges()to compute a delta. "Critical" fields (margin %, actual margin, selling price, standard cost, status, category, subcategory, tax, pack size, weights, HS code, block/allow-sell-at-loss, suppliers, integration codes) triggerapproval_status = "Pending Edit Approval", store the delta as JSON inchanges, and register aninventory_item_edittask. Non-critical edits apply immediately.
Screens/routes: maintain-items.index/create/store/edit/update/show/destroy (Route::resource('maintain-items', ...) at web.php:3456); item-new-approval (web.php:3421); item-approval/{status} dispatcher (web.php:3420); admin.show.approval (web.php:3427).
Outcome: A governed, audited item record; a live catalogue entry after approval.
3.2 UOM variants, categories & aliases¶
An item belongs to a category (wa_inventory_categories, via wa_inventory_category_id) and optionally a subcategory (wa_item_sub_categories, via item_sub_category_id). Its units of measure are rows in inventory_item_conversion_units: one base unit (is_base_unit, conversion_factor = 1), plus optional second/third units each with their own conversion_factor, selling_price, standard_cost, and additional_profit. Exactly one unit is is_default_unit. Per-branch UOM preference lives in wa_inventory_location_uom.
Aliases (alternate names/codes for an item and for a UOM) are managed inline via maintain-items.add-alias / update-alias / delete-alias and the add-uom-alias variants (web.php:2187–2192).
3.3 Selling price / cost / QOH / reorder levels & stock-status change requests¶
Core pricing columns on wa_inventory_items: standard_cost, selling_price, percentage_margin, actual_margin, margin_type (1 = percentage, 0 = value), last_grn_cost, weighted_average_cost, price_list_cost. QOH is not stored on the item — it is derived from wa_stock_moves. Per-branch stock policy (max stock, reorder level) lives in WaInventoryLocationStockStatus.
Changing a branch's max stock / reorder level goes through a request queue:
flowchart LR
A[User proposes new max_stock / re_order_level] --> B[(wa_stock_status_change: status=pending)]
B --> C{Approver acts}
C -->|bulkApprove| D[Update WaInventoryLocationStockStatus<br/>status=approved]
C -->|bulkReject| E[status=rejected + reason]
wa_stock_status_change stores previous vs. proposed max_stock and re_order_level, initiated_by, reviewed_by, reviewed_at, rejection_reason. Approval uses pessimistic locking and completes an inventory_stock_status task. Routes: stock-status-change-requests.index/bulk-approve/bulk-reject (web.php:3440–3442). Gated by maintain-items___item-approval (StockStatusChangeController:29).
3.4 Batch price change (the primary price-change flow)¶
Trigger: Inventory → Maintain Item → Price Change → Price Change (price-change.batch-requests, web.php:5455) → PriceChangeController@showBatchChangePage.
The user searches items (getItems), picks new cost/selling prices (per-item, percentage, or fixed value), and submits (processBatchPriceChange, PriceChangeController.php:283).
flowchart TD
A[Select items + new prices] --> B{Permission?}
B -->|manage-standard-cost / change-cost-price / change-selling-price| C[Validate parent↔child margin constraints]
B -->|none| X[Reject]
C --> D[Apply to wa_inventory_items directly]
D --> E[(Write wa_inventory_item_price_history: status=Approved)]
D --> F[(Write price_changes_timelines / PriceTimeline)]
D --> G{create_demand_on_price_change?}
G -->|yes, cost dropped| H[Create demand record]
D --> I{BLOCK_SALES_ON_COST_CHANGE?}
I -->|yes| J[Set block_this=1 on item]
Key behaviours:
- Permission-gated at controller level — one of
manage-standard-cost,change-cost-price,change-selling-priceis required or the request is rejected (PriceChangeController:287–291). - Parent/child margin guard — child (broken-down) items must keep a growing margin over the mother's converted cost. Governed by settings
MOTHER_TO_CHILD_PRICE_CHANGE_GROWING_MARGIN(default 5) andENFORCE_CHILD_SELLING_PRICE_ON_ATTACH(default true). - Settings that alter behaviour:
create_demand_on_price_change,SHOW_MARGIN_IN_PRICE_CHANGE,BLOCK_SALES_ON_COST_CHANGE,SEND_EMAIL_TO_CUSTOMER.
The batch flow updates the item directly (auto-approved). A separate, older request-queue path exists via wa_inventory_item_price_history rows with status = "Pending" that a user verifies through maintain-items.item_price_pending_verify (web.php:3415) — see §6 on the two coexisting mechanisms.
Outcome: Live prices updated; a history row and a price timeline row recorded; optional demand/block side effects.
3.5 Price change history¶
Price Change → Price Change History (maintain-items.item_price_history_list, web.php:3414). Lists non-pending history from wa_inventory_item_price_history filtered by item / supplier / branch (location_id) / date, exportable to Excel. Columns include old vs. new standard_cost, selling_price, price_list_cost, weighted_cost, last_grn_cost, initiated_by, approved_by, status, and source_action (e.g. "Price Change"). Item Centre has a parallel per-item view (item-centre.price-change-history, web.php:3452).
3.6 Transport sell price requests¶
Price Change → Transport sell requests (inventory-transport-price-change-requests.index, web.php:5456) → InventoryTransportPriceChangeRequestController. These are proposed selling-price changes originating from a GRN / receive-purchase-order context (a "transport sell" scenario). Stored in wa_inventory_transport_price_change_requests (columns include wa_inventory_item_id, grn_number, current_selling_price, proposed_selling_price, status, initiated_by, approved_by, reviewed_at).
stateDiagram-v2
[*] --> pending: request created from GRN context
pending --> approved: approve() (service applies price)
pending --> rejected: reject()
Setting TRANSPORT_SELL_CHANGE_AUTO_APPROVE_WITHIN (default 5) implies a scheduled job auto-approves changes within N days. Gated by maintain-items___manage-standard-cost.
3.7 Cost price lists (additional cost) — setting-gated¶
Price Change → Cost Price Lists (additional-cost-price-lists.index, web.php-region). Only visible when setting enable-additional-cost-price-list-module is on (default 0/off; sidebar:306). Lets you layer an additional cost (delivery/handling surcharge) on top of base prices per item, as either a value or percentage, with optional rounding, and attach the list to routes / branches / customers. Tables: additional_cost_price_lists, additional_cost_price_list_items (with cost_type, rounding_applied), _routes, _branches, _customers, _audit_log. Controller AdditionalCostPriceListController.
3.8 Blocked items¶
Price Change → Blocked Items (blocked-items, web.php:5558). An item is "blocked" (excluded from sale/repricing) via the boolean column wa_inventory_items.block_this. Blocks can be set manually or automatically when BLOCK_SALES_ON_COST_CHANGE is enabled and a cost changes. The page (PriceChangeController@showBlockedItemsPage) lists items where block_this = 1; unblockItem sets it back to 0. Gated by any of the three price permissions; the nav link additionally checks blocked-items___view-blocked-items.
Distinct from item-block approval (§3.13).
block_thison the item is the simple flag used here; the location-level, approval-driven blocking lives in a separateblocked_inventory_itemstable.
3.9 Price lists (route/branch/customer pricing)¶
Maintain Item → Price Lists (price-lists.index, web.php:5466) → PriceListController. The nav link only shows when the identify-sales-orders-from-normal-orders setting is on (sidebar:333–343). A price list is a named set of item prices that can be attached to sales routes, branches, and/or customers, so different channels see different prices.
Data model:
| Table | Role |
|---|---|
price_lists |
header: name, status, flags (is_distribution_price_list, is_category_a_list), formula fields (has_pricing_formula, formula_base_price, formula_minimum_margin, formula_additional_profit) |
price_list_items |
per (list, item, uom) price; unique on that triple |
price_list_routes / _branches / _customers |
attachment pivots; also denormalised onto routes.price_list_id, wa_location_and_stores.price_list_id, wa_route_customers.price_list_id |
price_list_audit_log |
every price change: old/new price, change_type, change_value, rounding, changed_by/at |
Formula-based pricing: a list (or item) can compute price from a base cost — formula_base_price picks one of standard_cost / price_list_cost / last_grn_cost / weighted_cost — plus a percentage_markup and UOM additional_profit, giving a computed minimum price. Setting a price below that minimum triggers the price-list approval path (§3.10). Bulk category formula application is gated by UPDATE_PRICE_FORMULA_PER_CATEGORY_UPDATES_PRICELIST.
flowchart LR
A[Create price list] --> B[Add items<br/>by filter / all / manual]
B --> C[Set prices or formula]
C --> D{Price ≥ formula minimum?}
D -->|yes| E[Apply to price_list_items<br/>+ audit log]
D -->|no, superadmin| F[Explicit confirm → apply]
D -->|no, non-admin| G[(price_change_requests: pending)]
E --> H[Attach to routes / branches / customers]
3.10 Price-list change approval¶
When a non-admin sets a price below the formula minimum, a row is created in price_change_requests (status = pending, with reason, minimum_allowed_price, loss_amount). It is reviewed via ApprovePriceListChangeController (maintain-items.approve-price-list-change / -confirm, web.php:2246–2247). Superadmins can confirm-through inline. Statuses: pending / approved / rejected / cancelled.
3.11 Reserved stocks¶
Maintain Item → Reserved Stocks (maintain-items.reserved-items.index, web.php:2206) → InventoryReservedItemController. Freezes a quantity of stock at a store for a purpose (e.g. a production order), so it is subtracted from "available" QOH.
Available-stock formula (store method): available = max(0, sum(wa_stock_moves.qauntity for item@store) − already-reserved-active-qty).
stateDiagram-v2
[*] --> pending: create()
pending --> active: store()
active --> partially_fulfilled: partial fulfill
active --> fulfilled: fulfill
partially_fulfilled --> fulfilled: fulfill remainder
active --> released: release()
partially_fulfilled --> released: release()
active --> cancelled: cancel()
released --> active: revert()
cancelled --> active: revert()
active --> expired: reserved_until passes
expired --> destroyed: destroy (purge)
cancelled --> destroyed: destroy
Tables: reserved_stocks (header — reference_number RES-YYYYMMDD-NNNN, store, from/until dates, status, actor + timestamp per state, reservable_type/reservable_id), reserved_stock_items (lines with item, uom, quantity, and captured cost snapshots: selling_price, standard_cost, weighted_avg_cost, last_grn_cost, price_list_cost), and reserved_stock_penalties / employee_penalty (penalties for expired reservations). Permissions: reserved-items___view/create/invoice/destroy/release/revert.
3.12 Subbin items — setting-gated¶
Maintain Item → Subbin Items (item-subbins.index, web.php:2320). Only appears when the activate-subbins setting's description = '1' (sidebar:250–275). A subbin is a sub-storage location inside a bin. SubbinItemsController lists/exports/deletes item→subbin mappings via cascading branch → bin → subbin filters. Tables: item_subbin_relations (inventory_id, subbin_id), sub_bins (name, bin_id), where a "bin" is a wa_unit_of_measures row tied to a wa_location_and_stores. Permissions: item-subbins___view/update.
3.13 Item block approval (location-level)¶
Item Approval → Item Block Approval (item-block-approval, web.php:2235) → ItemBlockController. A richer, per-location, approval-driven block distinct from the simple block_this flag (§3.8).
stateDiagram-v2
[*] --> pending_block: updateBlockStatus / blockAllLocations
pending_block --> blocked: approveBlockApproval (is_approved=true)
pending_block --> rejected: rejectBlockApproval
blocked --> pending_unblock: unblockAllLocations (pending_unblock=true)
pending_unblock --> unblocked: approve → soft-delete record
pending_unblock --> blocked: reject unblock (reset flag)
Table blocked_inventory_items (item_id, location_id, blocked_by/at, is_approved, approved_by/at, pending_unblock, unblock_requested_by/at, soft-deletes; unique per item+location). Uses Task Center workflows inventory_item_block / inventory_item_unblock. It syncs the item-level block_this flag via syncItemBlockThisFlag. Permissions: maintain-items___item-block-approval, maintain-items___view-item-block.
3.14 Branch retire / restore approval¶
Item Approval → Pending Retire Approval (item-pending-retire-approval, web.php:3438) → ItemBranchStatusController. A user with request-branch-retire requests retiring an item at specific stores (requires QOH = 0 there); rows go into branch_retire_item_requests (location_id, item_id, action retire/restore, initiated_by, status). An approver with approve-branch-retire acts:
- Approve retire: create
WaInventoryLocationRetiredItem; if retired at all stores, set itemstatus = 0(global retire); cascade viaItemRetirementCascadeService::cascadeBranchRetire(). - Approve restore: delete the retired-item row; if previously global-retired, set
status = 1; cascade restore; resolve any transfers held inPENDING_REACTIVATION. - Reject: status = rejected; cancel task.
Task workflow: inventory_branch_retire.
3.15 Item history / log & rejected requests¶
Item Approval → Item History (admin.show.item.log, web.php:3428) — a DataTable-backed log (showItemLog/showItemLogDatatable/showItemLogView). Backed by wa_inventory_item_approval_statuses (each approve/reject is a row; changes JSON holds field deltas, new_data holds full new-item JSON) and update_new_item_inventory_utility_logs (wa_inventory_item_id, initiated_by, approved_by). Rejected Request (item-approval with status rejected-approval, web.php:3420) lists rejected new/edit requests.
3.16 Promotions, hampers & sales basket¶
Promotions (Maintain Item → Promotions → Promotions, bulk-promotions.listings, PromotionsController). Five promotion types (buy-X-get-Y-free; price discount; group buy → free; group buy → discount; group buy → redeemable discount). Header promotions table (type, supplier, branch_ids, route/POS flags, start/end dates, demand_type/demand_value); qualifiers promotion_qualifying_groups + promotion_qualifying_items; rewards promotion_group_reward_items; a denormalised POS lookup promotion_group_item_index; and legacy single-item item_promotions. Supports CSV bulk upload. States: active / deactivated (deactivated_on) / expired (ends_at).
Hampers (promotions.hampers.index, HamperController) — a gift-bundle item assembled from child items. Lifecycle: Pending (hamper inventory item created) → Approved (categories/contents defined) → Processed (stock broken down / "fed"). Tables: hampers, hamper_categories, hamper_category_items, using wa_inventory_assigned_items + wa_stock_breaking(_item) for the split.
Sales basket (sales-basket.index, SalesBasketController) — a branch stock-verification/distribution workflow: HQ prompts a branch to build a basket by a deadline → items added → sent to storekeeper for QOH verification → verified quantities distributed to route mini-baskets. Tables: sales_baskets, sales_basket_items, basket_creation_prompts, basket_bin_stock_verification, mini_basket_items, basket_log. (This is a large sub-module in its own right; only its role in the Maintain-Item nav is documented here.)
3.17 Weighted averages¶
Maintain Item → Weighted Averages (weighted-average-history.index, WeightedAverageHistoryController). Shows a historical log (default last 7 days) comparing an item's opening_standard_cost vs grn_standard_cost to flag cost variances caused by goods receipt. Backed by the legacy weighted_average_history table (status, date, cost columns). The live weighted-average cost sits on wa_inventory_items.weighted_average_cost and is recalculated as GRNs arrive (see §5, Interactions). Gated by weighted-average-history___view.
---¶
4. Tables touched & key data¶
| Process | Primary tables | Key columns |
|---|---|---|
| Item master CRUD | wa_inventory_items |
stock_id_code, title, slug, wa_inventory_category_id, item_sub_category_id, pack_size_id, standard_cost, selling_price, percentage_margin, actual_margin, margin_type, block_this, allow_sell_at_loss, approval_status, status, last_grn_cost, weighted_average_cost, price_list_cost, item_type, min/max_order_quantity |
| New/edit approval | wa_inventory_item_approval_statuses |
wa_inventory_items_id, approval_by, status (Pending New/Edit, Approved, Rejected), changes (JSON), new_data (JSON), approval_date |
| Categories / UOM | wa_inventory_categories, wa_item_sub_categories, inventory_item_conversion_units, wa_inventory_location_uom |
conversion_factor, is_base_unit, is_default_unit, per-UOM selling_price/standard_cost/additional_profit |
| QOH / stock policy | wa_stock_moves (QOH derived), WaInventoryLocationStockStatus, wa_stock_status_change |
qauntity [sic]; max_stock, re_order_level; proposed vs previous, status, reviewed_by |
| Batch price change | wa_inventory_items, wa_inventory_item_price_history, price_changes_timelines (PriceTimeline) |
old/new cost & price, initiated_by, approved_by, status, source_action, location_id |
| Price change requests (formula/legacy) | price_change_requests, wa_inventory_item_price_history |
status, requested_price, minimum_allowed_price, loss_amount, change_type, reason |
| Transport sell requests | wa_inventory_transport_price_change_requests |
grn_number, current/proposed_selling_price, status, initiated_by, approved_by |
| Price lists | price_lists, price_list_items, price_list_routes/_branches/_customers, price_list_audit_log |
formula fields, per-(list,item,uom) price, attachment pivots |
| Additional cost price lists | additional_cost_price_lists(+_items/_routes/_branches/_customers/_audit_log) |
additional_cost, cost_type, rounding_applied |
| Blocked items (flag) | wa_inventory_items |
block_this |
| Item block approval (location) | blocked_inventory_items |
item_id, location_id, is_approved, pending_unblock, soft-deletes |
| Branch retire | branch_retire_item_requests, WaInventoryLocationRetiredItem |
action, status, location_id |
| Reserved stocks | reserved_stocks, reserved_stock_items, reserved_stock_penalties |
reference_number, status lifecycle, cost snapshots |
| Subbins | item_subbin_relations, sub_bins |
inventory_id, subbin_id, bin_id |
| Promotions | promotions, promotion_qualifying_groups/_items, promotion_group_reward_items, promotion_group_item_index, item_promotions |
type, dates, reward config, POS index |
| Hampers | hampers, hamper_categories, hamper_category_items |
pcs_in_hamper, conversion_factor, status |
| Sales basket | sales_baskets, sales_basket_items, basket_creation_prompts, mini_basket_items, basket_log |
verification & distribution fields |
| Item history/log | wa_inventory_item_approval_statuses, update_new_item_inventory_utility_logs |
changes, new_data, initiated_by, approved_by |
| Weighted averages | weighted_average_history; wa_inventory_items.weighted_average_cost |
opening_standard_cost, grn_standard_cost, date, status |
---¶
5. Interactions with other modules¶
- GRN / Receiving (Book 2): Goods receipt is the main driver of cost. Each GRN feeds
last_grn_costand recomputesweighted_average_coston the item; theweighted_average_historylog records opening vs GRN cost. GRN context is also the origin of transport sell price requests (wa_inventory_transport_price_change_requestscarriesgrn_numberand links to receive-purchase-order records). QOH-on-order in the item list joins purchase orders/GRNs (InventoryItemController.php:180–188). - Procurement / price approvals: Formula-below-minimum price-list edits become
price_change_requestsapproved viaApprovePriceListChangeController. Supplier data on items links towa_inventory_item_suppliers/WaSupplier. - Sales & stock (Book 3/POS): Selling prices and price lists attached to routes/branches/customers determine what POS charges;
promotion_group_item_indexis the denormalised table POS reads for promotions.block_thisand location blocks stop items from being sold. Reserved stocks reduce sellable QOH. - Stock breaking (Chapter in this book): Hampers and parent→child items rely on
wa_stock_breaking/wa_inventory_assigned_items; the parent↔child margin guard in batch price change enforces pricing coherence across split levels. - Task Center / notifications: Every approval loop (new item, edit, stock-status, branch-retire, item-block/unblock) registers/completes/cancels a Task Center task and notifies approvers.
- KRA fiscalisation (VSCU): Approving a new item dispatches
SyncApprovedItemToVscu. - Mobile apps (
retail_pay_utils): The Flutter apps cache inventory items locally via ObjectBox (retail_pay_utils/lib/objectbox.g.dart,object_box_relations_test.dart). Item master + pricing data from this module is what the mobile side consumes; details of the sync are out of scope for this chapter (see the mobile/sync chapter). [INFERENCE — confirmed only that an ObjectBox item cache exists, not the exact sync contract.]
---¶
6. Alternatives & variants¶
- Approval on/off:
REQUIRE_APPROVAL_FOR_NEW_OR_EDITED_INVENTORY_ITEMS(default true) toggles whether create/edit route through the pending queue or write live immediately. Tenants that disable it get direct edits. - UOM-based vs simple setup:
INVENTORY_SETUP = 'UOM BASED'turns on the third form step and multi-unit conversion; otherwise items are single-unit. - Two price-change mechanisms coexist: (a) the batch price change page applies changes directly to the item and records
Approvedhistory/timeline rows; (b) an older pending price change queue (wa_inventory_item_price_historyrows withstatus = "Pending", verified viaitem_price_pending_verify) still exists. Which one a tenant uses appears to depend on settings/permissions — see §7. - Two "blocking" concepts: the simple
block_thisflag (blocked-items page) vs the location-level, approval-drivenblocked_inventory_items(item-block approval). They are synced one-way viasyncItemBlockThisFlag. - Setting-gated nav items: Price Lists + Stock Move Report show only under
identify-sales-orders-from-normal-orders; Cost Price Lists only underenable-additional-cost-price-list-module(default off); Subbin Items only underactivate-subbins. - Transport auto-approve:
TRANSPORT_SELL_CHANGE_AUTO_APPROVE_WITHIN(default 5) implies scheduled auto-approval of transport sell price requests within N days. - Legacy/dead code:
PriceChangeControllercontains a large commented-out child-item cascade block (child cost/price auto-update) — parent→child cascading price changes are currently disabled there. Sidebar "Manual Cost Change" links (maintain-items.manual-cost-change,ManualPriceChangeController) are commented out in the nav (sidebar:324–329) though routes still exist. Theweighted_average_historytable has no recent migration and is queried directly (legacy). - QOH visibility:
maintain-items___restrict-qoh-visibilityhides QOH/QOO columns;maintain-items___view-per-branchwidens QOH from own-branch to all branches.
---¶
7. Open questions to confirm¶
CODE-PROVEN (verified by reading files):
- Item master model is App\Model\WaInventoryItem, table wa_inventory_items; QOH is derived from wa_stock_moves, not stored. (InventoryItemController.php:161–189)
- New/edit approval uses wa_inventory_item_approval_statuses with statuses Pending New Approval / Pending Edit Approval / Approved / Rejected, JSON changes and new_data. (controller:1820, 2335, 3108; ApprovalStatus enum)
- Approval gate driven by REQUIRE_APPROVAL_FOR_NEW_OR_EDITED_INVENTORY_ITEMS (default true). (controller:1820, 3108)
- Batch price change applies directly + writes wa_inventory_item_price_history and price_changes_timelines. (PriceChangeController)
- Price lists gated by identify-sales-orders-from-normal-orders; cost price lists by enable-additional-cost-price-list-module; subbins by activate-subbins. (sidebar:333, 306, 250)
- Reserved-stock state machine and tables as in §3.11. (InventoryReservedItemController)
- Item-block approval uses blocked_inventory_items + Task Center; simple flag is wa_inventory_items.block_this. (ItemBlockController, PriceChangeController)
INFERENCES / TO CONFIRM:
1. Which price-change path is authoritative per tenant — the batch (direct) path vs the legacy Pending history-verify queue. Both exist; the selection rule (setting? permission? flavor?) was not pinned down.
2. Weighted-average recompute location — the exact code that recalculates weighted_average_cost on GRN (likely in the GRN/receiving service) was not read in this chapter; confirm formula and trigger.
3. reserved-items route duplication — several route names are declared twice (e.g. reserved-items.release, .destroy at web.php:2215/2221 and 2218/2225) with different verbs/handlers; confirm which resolves and whether one is dead.
4. Transport sell auto-approve job — TRANSPORT_SELL_CHANGE_AUTO_APPROVE_WITHIN implies a scheduled command; the command/schedule entry was not located.
5. Sales basket full lifecycle and its interaction with routes/POS is only sketched here (it is arguably its own chapter).
6. Mobile sync contract — confirmed an ObjectBox item cache exists in retail_pay_utils; the sync endpoint/payload for item master + pricing was not traced.
7. Item Centre (item-centre.*) appears to be a newer per-item hub overlapping "maintain-items"; its relationship to the legacy Maintain-Item screens needs clarification.
8. item_type FK migration (2026_05_29) changed item_type from value to FK — confirm the item_types lookup table and whether both old/new forms coexist.
---¶
8. Source references¶
Navigation
- bizwiz/resources/views/admin/includes/sidebar_includes/inventory.blade.php:212–372 — Maintain Item menu tree, permission gates, setting checks (activate-subbins:250, price change submenu:278–332, promotions:345–370, item approval:453–507).
Routes (bizwiz/routes/web.php)
- maintain-items resource + CRUD helpers: :3456, :2185–2200, :3407–3454
- Reserved items group: :2205–2227
- Item block: :2231–2238
- Manual price change (legacy): :2242–2244
- Approve price-list change: :2246–2247
- Subbins: :2320–2324
- Price change pending/history/verify: :3413–3415
- Item approval (new/edit/rejected/history): :3420–3430
- Branch retire + stock-status change: :3436–3442
- Weighted average history resource: :3775
- Batch price change / transport / blocked items: :5455–5561
- Price lists: :5466–5514
- Sales basket: :510–543
- Promotions/hampers: bizwiz/routes/modules/inventory.php:29–54
Controllers (bizwiz/app/Http/Controllers/Admin/)
- InventoryItemController.php — index:140, create:1406, store:1776 (approval gate:1820), edit:2588, update:3049 (approval gate:3108), item_new_approval:2257/2316/2335/5419, item_price_pending_list:4874, item_price_history_list:4916, item_price_pending_verify:5023, showItemLog:3428-region
- PriceChangeController.php — showBatchChangePage:60, getItems:171, processBatchPriceChange:283, showBlockedItemsPage:145, getBlockedItems:213, unblockItem:256; commented child-cascade:~521–605
- PriceListController.php — formula calc:~1724–1746, savePriceChanges/below-minimum request:~1462–1564, attach:~1983–2296, getAuditLog:~2299
- InventoryTransportPriceChangeRequestController.php — index:20, approve:133, reject:149
- ApprovePriceListChangeController.php — index, confirmPriceChange
- AdditionalCostPriceListController.php — index/store:24–35
- InventoryReservedItemController.php — index:46, create:85, store:297 (available calc:407), release:815, revert:1442
- SubbinItemsController.php — index:32, datatable:75, delete:125
- WeightedAverageHistoryController.php — index:26
- ItemBlockController.php — updateBlockStatus:36, blockAllLocations:137, pendingApproval:268, approveBlockApproval:371, rejectBlockApproval:389
- ItemBranchStatusController.php — request:25, pendingRequests:133, approve:173
- StockStatusChangeController.php — index:24, bulkApprove:91, bulkReject:178
- PromotionsController.php — promotionsListings:92, processPromotions:273; HamperController.php — index:38, store:104, storeHamperCategories:261, feedStocks:575; SalesBasketController.php — showBasketsPage:35, createBasket:343, addItemToBasket:465
Models
- bizwiz/app/Model/WaInventoryItem.php:34 (relations from :309)
- bizwiz/app/Models/WaInventoryItemApprovalStatus.php, bizwiz/app/Enums/Status/ApprovalStatus.php
- bizwiz/app/Model/PriceChangeRequest.php, bizwiz/app/Models/PriceTimeline.php, bizwiz/app/Models/WaInventoryTransportPriceChangeRequest.php
- bizwiz/app/Model/PriceListItem.php, PriceListAuditLog.php, AdditionalCostPriceList.php, AdditionalCostPriceListItem.php
- bizwiz/app/Models/InventoryItemConversionUnit.php, bizwiz/app/Model/WaInventoryLocationUom.php, WaInventoryLocationStockStatus.php, WaStockStatusChange.php, BranchRetireItemRequest.php, BlockedInventoryItem.php
Migrations (bizwiz/database/migrations/)
- 2023_09_08_134414_create_wa_inventory_items_table.php
- 2024_04_18_100057_*_approval_statuses, 2024_04_30_110257_*_new_data
- 2026_01_18_120000_create_price_change_requests_table.php
- 2025_12_31_160602_create_price_lists_table.php (+ _items, _routes, _branches, _customers, _audit_log sibling migrations)
- 2026_01_06_150146_add_formula_fields_to_wa_inventory_items_table.php, 2026_07_16_112014_add_percentage_markup...
- 2026_02_18_100000_create_additional_cost_price_lists_table.php (+ siblings), 2026_02_19_100000_add_setting_to_enable_additional_cost_price_list_module.php
- 2026_04_22_120000_create_wa_inventory_transport_price_change_requests_table.php
- 2026_04_30_084719_create_reserved_stocks_table.php
- 2025_03_10_085708_create_item_subbin_relations_table.php
- 2026_05_22_103044_create_blocked_inventory_items_table.php
- 2026_06_02_120000_*_wa_stock_status_change
- 2025_05_21_124730_create_hampers_table.php
Permissions
- bizwiz/app/Permissions/Inventory.php — maintain-items___* (~:340–394), reserved-items___*, item-subbins___*, promotions___*, hampers___*, basket___*, weighted-average-history___view.
Mobile
- retail_pay_utils/lib/objectbox.g.dart, retail_pay_utils/lib/test/object_box_relations_test.dart — local ObjectBox item cache.