Skip to content

Book 2 · Chapter 10 — Inter-branch Transfers & Internal Requisitions

Module family: Inventory & Stock Sidebar groups: Inventory → Inter-branch Transfers and Inventory → Internal Requisition Primary controllers: NInventoryLocationTransferController, NInternalRequisitionController, NApproveInternalRequisitionController, NIssueFullfillRequisitionController, ProcessedRequisitionController Primary tables: n_wa_inventory_location_transfers, n_wa_inventory_location_transfer_items, n_wa_internal_requisitions, n_wa_internal_requisition_items


1. Purpose (plain language)

This chapter covers two ways that stock moves inside the distributor — never involving a supplier or a customer.

Inter-branch Transfers move physical stock from one branch/store location to another branch/store location. A user at the sending branch builds a transfer note listing items and quantities. In the newer flow, store keepers verify what's actually in their bins, the goods are dispatched, the receiving branch's store keeper marks the goods as physically arrived, and finally a GRN clerk confirms — at which point the system deducts stock from the source location and adds it to the destination location. There is also an older one-step "process transfer" path that does the deduction and addition in a single click.

Internal Requisitions are internal stock requests. A branch/department asks a source store for stock it needs. The request is raised ("New Stock Request"), then routed for authorisation ("Authorise Requisition"), then the source store issues/fulfils it ("Issue/Fulfill Requisition"), and finally it lands in "Processed Requisition" as a completed record. Fulfilling a requisition actually creates an inter-branch transfer record behind the scenes and moves the stock.

Think of it this way: a transfer is "I am pushing stock to you." A requisition is "I am asking you to send me stock" — and when granted, it turns into a transfer/issue that pushes the stock.

⚠️ Naming trap: There is a completely separate legacy table called wa_internal_requisitions (no n_ prefix) that is a SALES table — customer invoices/orders — handled by SalesOrderController. The inventory feature in this chapter uses n_wa_internal_requisitions (with the n_ prefix). They are different tables for different purposes. See §7.

---

2. Users & roles (+ permissions)

All permissions are defined in app/Permissions/Inventory.php under two menu groups: Inter-branch Transfers (lines 743–787) and Internal Requisition (lines 788–823). The config('app.allowed_role') super-admin role bypasses all of these checks (seen throughout the sidebar).

Inter-branch Transfers permissions

Menu item Permission model Abilities Source
Inter-branch Transfers (group) transfers view Inventory.php:744–746
Initiate Transfer transfers view, add, edit, edit-pending-transfer-destination, delete, return-list, resign-esd, send-to-store-keepers Inventory.php:749–760
Receive Transfers interbranch-transfers view, receive-goods, confirm-goods Inventory.php:763–769
Verify Transfer verify-transfers view, verify Inventory.php:771–776
Processed Transfers transfers view Inventory.php:779–783

The two-step receive split maps directly onto abilities: receive-goods = store keeper marks goods arrived; confirm-goods = GRN clerk confirms and moves stock. The sidebar shows the "Receive Transfer" link if the user has either ability (sidebar_includes/inventory.blade.php:805).

Internal Requisition permissions

Menu item Permission model Abilities Source
Internal Requisition (group) internal-requisitions view Inventory.php:789–791
New Stock Request internal-requisitions view, add Inventory.php:794–799
Authorise Requisition n-authorise-requisitions view Inventory.php:802–806
Issue/Fullfill Requisition issue-fullfill-requisition view Inventory.php:809–813
Processed Requisition processed-requisition view Inventory.php:816–820

Note the requisition group's authorise/issue/processed screens only carry a bare view ability in the permission tree — the actual approve/decline/issue actions are gated by the screen appearing at all plus multi-level approver logic in the controllers (see §3).

Typical actors

  • Branch/store user (initiator): raises transfers (transfers.add) or new stock requests (internal-requisitions.add).
  • Store keeper (source): verifies bin quantities (verify-transfers.verify) and issues/fulfils requisitions.
  • Store keeper (destination): marks goods received (interbranch-transfers.receive-goods).
  • GRN clerk (destination): confirms goods and triggers stock movement (interbranch-transfers.confirm-goods).
  • Authoriser(s): approve/decline requisitions, possibly across multiple approval levels.

---

3. Processes (state machines)

3.1 Inter-branch transfer lifecycle

The transfer header lives in n_wa_inventory_location_transfers; its status column is an enum that has grown over time to: DRAFT, PENDING, VERIFICATION, COMPLETED, CANCELLED, PENDING_REACTIVATION (migrations ...create_n_wa_inventory_location_transfers_table.php, 2026_02_03_140000 added CANCELLED, 2026_09_01_081258 added PENDING_REACTIVATION).

There are effectively two coexisting flows: a newer verification + two-step receive flow, and a legacy one-shot processTransfer flow. Both are live in the controller.

stateDiagram-v2
    [*] --> DRAFT: store() action=save
    [*] --> PENDING: store() action=process (legacy direct)
    DRAFT --> PENDING_REACTIVATION: store() detects retired items at dest
    PENDING_REACTIVATION --> PENDING: reactivation approved
    DRAFT --> VERIFICATION: sendToStoreKeepers()
    VERIFICATION --> DRAFT: submitVerification() (all items verified, qty adjusted)
    VERIFICATION --> PENDING: processAfterVerification() (sets processed_by, builds loading sheets)
    PENDING --> COMPLETED: processTransfer() [LEGACY: moves stock now]
    PENDING --> PENDING: storeKeeperReceiveGoods() (goods_received_at set, NO stock move)
    PENDING --> COMPLETED: grnClerkConfirmGoods() [NEW: moves stock now]
    PENDING --> CANCELLED: cancelTransfer() (reverses source stock moves)
    VERIFICATION --> CANCELLED: cancelTransfer()
    COMPLETED --> [*]
    CANCELLED --> [*]

Key methods (all in NInventoryLocationTransferController.php):

Step Method Line Status effect Stock (QOH) effect
List/initiate screen index 305 none none
Create transfer store 1202 → DRAFT (:1336) or PENDING; → PENDING_REACTIVATION if retired items (:1163) none (draft only)
Bulk create processBulkUpload ~574 → DRAFT (:726) none
Send for bin verification sendToStoreKeepers 3616 DRAFT → VERIFICATION (:3630); creates InterbranchTransferVerification rows; SMS to bin managers none
Verify list indexVerify 3917 reads VERIFICATION (:3932) none
Submit verification submitVerification 4016 when all items verified → back to DRAFT (:4124); adjusts item quantities to verified amounts (:4128) none (adjusts requested qty)
Process after verification processAfterVerification 3718 VERIFICATION → PENDING, sets processed_by (:3733–3734); creates loading sheets none directly
Receive list indexReceive 385 reads PENDING + grn_confirmed_at IS NULL (:401–402) none
Step 1: goods received storeKeeperReceiveGoods 1653 no status change; sets goods_received_at, goods_received_by (:1745) none — records physical arrival only
Step 2: GRN confirm grnClerkConfirmGoods 1773 → COMPLETED, sets grn_confirmed_at/by (:1956–1959) YES — posts stock moves via InterbranchTransferStockMovePoster::postStockMovesForTransfer() (:1818)
Legacy one-shot process processTransfer 2611 PENDING → COMPLETED (:2625) YES — writes negative WaStockMove at source (:2642) + positive at destination (:2656)
Cancel cancelTransfer 3761 PENDING/VERIFICATION → CANCELLED (:3828) Reversal — positive WaStockMove to restore source (:3810); blocked if goods already received (:3785)
Processed list indexProcessed 430 reads COMPLETED (:440) none

Flow (a) — Legacy direct: store(action=process) → PENDING → processTransfer → COMPLETED. Both source-deduct and destination-add stock moves happen in processTransfer (:2632–2662).

Flow (b) — New verification + two-step receive: store → DRAFT → sendToStoreKeepers → VERIFICATION → submitVerification (→ DRAFT, quantities trued-up) → processAfterVerification → PENDING (loading sheets built) → storeKeeperReceiveGoods (arrival stamped, no stock move) → grnClerkConfirmGoods → COMPLETED (stock moves posted).

Retired-item branch: if store detects items retired at the destination, the transfer is parked as PENDING_REACTIVATION (:1163, badge rendering at :356–360) until reactivation is approved.

3.2 Internal requisition lifecycle

The requisition header lives in n_wa_internal_requisitions; its status enum is UNAPPROVED, PENDING, PROCESSING, APPROVED, DECLINED, COMPLETED (base migration), later extended with PAID, DELIVERED (2023_09_22_111306, 2026_07_28_100001 — those two extra states appear sales-adjacent and are not exercised by the inventory issue flow below).

stateDiagram-v2
    [*] --> UNAPPROVED: store() action=save (draft)
    [*] --> PENDING: store() action=send
    UNAPPROVED --> PENDING: sendRequisitionRequest() [deducts source stock]
    PENDING --> PROCESSING: approve, more approval levels remain
    PROCESSING --> APPROVED: final approval
    PENDING --> APPROVED: approve (single level)
    PENDING --> DECLINED: decline
    PROCESSING --> DECLINED: decline
    APPROVED --> COMPLETED: issue/fulfil [creates transfer + deducts destination stock]
    COMPLETED --> [*]
    DECLINED --> [*]

Stage 1 — New Stock Request (NInternalRequisitionController.php):

Action Method Line Status effect Stock effect
List (completed, 7 days) index 32 reads COMPLETED (:37) none
Create store 97 save → UNAPPROVED (:181); send → PENDING (:224) none at save; QOH validated against wa_stock_moves_C/wa_stock_moves_supreme (:139–142)
Update draft update 358 resets to UNAPPROVED (:415), send → PENDING (:479) none
Send draft to approval sendRequisitionRequest 254 UNAPPROVED → PENDING (:294) YES — negative WaStockMove at source (:286) before status flip
QOH lookup (AJAX) getItemQohAjax 648 none none

Stage 2 — Authorise Requisition (NApproveInternalRequisitionController.php):

Action Method Line Status effect Stock effect
List pending approvals index 33 reads permission rows status='NEW' (:44) none
Show for decision edit 75 none none
Approve / Decline update 106 more levels → PROCESSING (:126); final → APPROVED (:134); reject → DECLINED (:146) none (authorisation only)

⚠️ This controller instantiates NWaInternalRequisitionDemo / NWaInternalRequisitionItemDemo / NWaInternalReqPermissionDemo (the *Demo models) rather than the plain NWaInternalRequisition used elsewhere. See §7 — this is a likely partial refactor and should be confirmed against the live DB.

Stage 3 — Issue/Fulfill Requisition (NIssueFullfillRequisitionController.php):

Action Method Line Status effect Stock effect
List approved index 33 reads APPROVED (:44) none
Show show 62 reads APPROVED (:64) none
Fulfil update 115 → COMPLETED (:251) YES — creates transfer record in n_wa_inventory_location_transfers (:154), plus negative WaStockMove at destination (:234), into wa_stock_moves_C/wa_stock_moves_supreme. Supports partial fulfilment via selected related_item_ids.
Remove item destroy 98 none none

Stage 4 — Processed Requisition (ProcessedRequisitionController.php): read-only.

Action Method Line Notes
List completed index 31 reads COMPLETED (:37)
Show show 60 uses NWaInternalRequisitionDemo (:62) — model mixing, see §7
PDF / print exportToPdf / printPage 74 / 87 output only

---

4. Tables touched & key data

Transfers

n_wa_inventory_location_transfers — migration 2023_09_08_134414_create_n_wa_inventory_location_transfers_table.php; model App\Model\NWaInventoryLocationTransfer. - Identity: transfer_no (unique), slug, transfer_date. - Actors/context: user_id (initiator), restaurant_id (branch), wa_department_id. - Locations: from_store_location_id (source), to_store_location_id (destination) — both FK to WaLocationAndStore. - Lifecycle: status enum (see §3.1). - Receive tracking (migration 2025_08_10_180000_add_goods_received_fields_to_transfers.php): goods_received_at, goods_received_by, grn_confirmed_at, grn_confirmed_by. - processed_by (migration 2026_02_03_120000_...). - Reactivation (migration 2026_09_01_081258): reactivation_status, reactivation_cancelled_reason. - Pack receiving: received_as_whole_packs (bool cast), pack_receiving_option (migration 2026_09_03_071300_...).

n_wa_inventory_location_transfer_items — model App\Model\NWaInventoryLocationTransferItem. - FKs: wa_inventory_location_transfer_id, wa_inventory_item_id. - Quantities/costs: quantity, issued_quantity, standard_cost, total_cost, vat_rate, vat_amount, total_cost_with_vat, note. - wa_internal_requisition_item_id (nullable) — links a transfer line back to the requisition line that spawned it (the requisition→transfer bridge). - UOM (migration 2025_12_30_122757_...): uom_id, uom_conversion_factor, uom_quantity, uom_name.

Requisitions

n_wa_internal_requisitions — migration 2023_09_08_134414_create_n_wa_internal_requisitions_table.php; model App\Model\NWaInternalRequisition. - Identity: requisition_no, slug, requisition_date. - Actors/context: user_id, restaurant_id, wa_department_id. - Locations: wa_location_and_store_id (source/from), to_store_id (destination). - Lifecycle: status enum (see §3.2). - uom_id (migration 2025_03_27_180128_...).

n_wa_internal_requisition_items — model App\Model\NWaInternalRequisitionItem. - FKs: wa_internal_requisition_id, wa_inventory_item_id. - Quantities: quantity, issued_quanity (sic — misspelled column in the migration). - Costs: standard_cost, total_cost, vat_rate, vat_amount, total_cost_with_vat, selling_price, order_price, etc. - Dispatch tracking: is_dispatched, dispatched_by, dispatched_time, dispatch_no.

Cross-cutting

  • WaStockMove (and store-partitioned wa_stock_moves_C / wa_stock_moves_supreme) — the actual ledger of quantity changes. This is where QOH really moves; transfers and requisition fulfilment both write here.
  • InterbranchTransferVerification (model App\Models\InterbranchTransferVerification) — per-item, per-bin verification rows created by sendToStoreKeepers, filled in by submitVerification (serviceable_quantity, remarks, verified_at).
  • Multi-level approval permission rows: NWaInternalReqPermission / NWaInternalReqPermissionDemo.

---

5. Interactions with other modules

  • Source & destination branch QOH: Neither transfers nor requisitions touch quantity-on-hand at draft/request time. QOH only changes when WaStockMove rows are written — for transfers that's grnClerkConfirmGoods (new flow) or processTransfer (legacy); for requisitions it's sendRequisitionRequest (source deduction) and issue update (destination-side move). QOH is read for validation via wa_stock_moves_C / wa_stock_moves_supreme and the getItemQohAjax endpoint.
  • Requisition → Transfer bridge: Fulfilling a requisition (NIssueFullfillRequisitionController::update) creates a real n_wa_inventory_location_transfers record, and transfer items carry wa_internal_requisition_item_id back to the originating requisition line. So the two features are chained, not parallel.
  • Store C / Overflow (Ch11): The QOH validation reads store-partitioned move tables wa_stock_moves_C (Store C) and wa_stock_moves_supreme, so the special stores participate as source/destination locations. The Store-C/Overflow mini-requisition screens (store-c-requisitions, overflow-requisitions, supreme-store-requisitions in the sidebar) are a separate set of controllers and are covered in Ch11 — do not conflate them with the n-internal-requisitions flow here.
  • Logistics / Loading sheets: processAfterVerification builds interbranch loading sheets, and the two-step receive routes (storeKeeperReceiveGoods, grnClerkConfirmGoods) live in routes/modules/logistics.php:187–188. Vehicle assignment and driver-GRN budgeted expense are wired into grnClerkConfirmGoods (:1863–1953).
  • GRN / supplier (Ch9): Distinct. Supplier GRN uses the purchase-order/receive-purchase-order controllers. Inter-branch "GRN confirm" reuses the word GRN but operates on transfers, not supplier receipts.
  • Sales (legacy wa_internal_requisitions): The internal-requisitions/{id}/print route points to SalesOrderController::printInvoice using the WaInternalRequisition model / wa_internal_requisitions table — a sales artifact, unrelated to this chapter's n_-prefixed inventory tables. See §7.

---

6. Alternatives & variants

  • Legacy direct-process vs. new verify+two-step receive (transfers). Both are live code. processTransfer (:2611) is a one-click PENDING → COMPLETED that immediately moves stock. The newer path adds bin VERIFICATION, loading-sheet dispatch, and a split receive-goods / confirm-goods step. Which path a tenant uses depends on whether sendToStoreKeepers/verify-transfers permissions are granted and whether operators use the "Receive Transfer" two-step screens. (INFERENCE — no single boolean setting was confirmed to toggle this; flagged in §7.)
  • Bulk transfer upload. bulkTransfer / bulkUpload / processBulkUpload create draft transfers from an uploaded template (n-transfers.bulk-* routes, web.php:601–605). Gated by a bulk-transfer feature check inside the controller.
  • Pack receiving options. received_as_whole_packs / pack_receiving_option allow receiving as whole parent packs vs. broken small packs (migration 2026_09_03_071300, logic in the receive methods).
  • Retired-item reactivation. If destination has retired the item, the transfer is parked PENDING_REACTIVATION pending approval rather than failing outright.
  • Multi-level requisition authorisation. Approval can cascade through several approver levels (PROCESSING between PENDING and APPROVED), driven by NWaInternalReqPermission(Demo) rows ordered by approve_level.
  • Partial fulfilment. Requisition issue can fulfil a subset of lines via related_item_ids, leaving the rest.

---

7. Open questions to confirm

CODE-PROVEN

  1. wa_internal_requisitions (sales) ≠ n_wa_internal_requisitions (inventory). Two distinct tables. The legacy wa_internal_requisitions carries customer fields (customer, customer_phone_number, customer_pin, route_id) and PAID/DELIVERED states, and is used by SalesOrderController::printInvoice via the WaInternalRequisition model. The inventory feature in this chapter uses n_wa_internal_requisitions via NWaInternalRequisition. The internal-requisitions.print route (web.php:1450) belongs to the sales table, not this chapter. (Migrations: create_wa_internal_requisitions_table.php vs create_n_wa_internal_requisitions_table.php.)
  2. Requisition fulfilment creates a transfer. NIssueFullfillRequisitionController::update writes an n_wa_inventory_location_transfers row and the transfer item carries wa_internal_requisition_item_id.
  3. Two live transfer completion paths (processTransfer legacy vs grnClerkConfirmGoods new) both post to WaStockMove.
  4. storeKeeperReceiveGoods does NOT move stock; only grnClerkConfirmGoods does (:1818).
  5. Column typo: issued_quanity in n_wa_internal_requisition_items.

INFERENCES / TO CONFIRM

  1. Model mixing in the requisition approval + processed screens. NApproveInternalRequisitionController and ProcessedRequisitionController::show use *Demo models (NWaInternalRequisitionDemo, NWaInternalRequisitionItemDemo, NWaInternalReqPermissionDemo) while create/issue use the non-Demo models. Confirm whether the *Demo models point at the same tables (n_wa_internal_requisitions etc.) via a $table override, or at separate *_demos tables. If the latter, the approve/issue stages may operate on different rows than create — a correctness risk worth verifying against the live schema. (Could not confirm the $table on the Demo models from exploration.)
  2. What toggles legacy vs new transfer flow per tenant/flavor? No single setting slug was confirmed; it appears driven by which permissions (send-to-store-keepers, verify-transfers) are assigned. Confirm whether a Setting slug also gates it.
  3. processAfterVerification QOH timing. It builds loading sheets and flips to PENDING but the exact point of source-stock deduction in the new flow (loading-sheet dispatch vs grnClerkConfirmGoods) should be traced end-to-end; grnClerkConfirmGoods uses InterbranchTransferStockMovePoster whose posting logic (source-and-destination vs destination-only) was not fully read.
  4. indexReceiveInitial / indexReceiveConfirm routes exist (web.php:618–619) but the corresponding controller methods were not found during exploration — likely dead/renamed routes. Flag as possible dead code.
  5. PAID / DELIVERED statuses on n_wa_internal_requisitions are added by migration but not exercised by the inventory issue flow observed. Confirm whether any code path sets them (possible leftover from sales-table parity).

---

8. Source references

Navigation - bizwiz/resources/views/admin/includes/sidebar_includes/inventory.blade.php:784–823 — Inter-branch Transfers group - bizwiz/resources/views/admin/includes/sidebar_includes/inventory.blade.php:825–864 — Internal Requisition group

Routes - bizwiz/routes/web.php:589–624 — all n-transfers.* routes (resource at :612; verify at :609–611; receive/processed at :614–615) - bizwiz/routes/web.php:1450 — legacy internal-requisitions.print → SalesOrderController::printInvoice (SALES) - bizwiz/routes/web.php:1453–1471 — n-internal-requisitions.* (resource at :1471) - bizwiz/routes/web.php:1474–1477 — n-authorise-requisitions.* - bizwiz/routes/web.php:1480–1482 — n-issue-fullfill-requisition.* - bizwiz/routes/web.php:3552–3554 — processed-requisition.* - bizwiz/routes/modules/logistics.php:187–188 — two-step receive (storeKeeperReceiveGoods, grnClerkConfirmGoods)

Permissions - bizwiz/app/Permissions/Inventory.php:743–787 — transfers; :788–823 — requisitions

Controllers - bizwiz/app/Http/Controllers/Admin/NInventoryLocationTransferController.php — index:305, indexReceive:385, indexProcessed:430, store:1202, storeKeeperReceiveGoods:1653, grnClerkConfirmGoods:1773, receiveInterBranchTransfer:2095, receiveInterBranchTransfer2:2134, receiveInterBranchTransferProcessed:2544, processTransfer:2611, sendToStoreKeepers:3616, processAfterVerification:3718, cancelTransfer:3761, indexVerify:3917, verifyTransfer:3968, submitVerification:4016 - bizwiz/app/Http/Controllers/Admin/NInternalRequisitionController.php — index:32, store:97, sendRequisitionRequest:254, update:358, getItemQohAjax:648 - bizwiz/app/Http/Controllers/Admin/NApproveInternalRequisitionController.php — index:33, edit:75, update:106 - bizwiz/app/Http/Controllers/Admin/NIssueFullfillRequisitionController.php — index:33, show:62, update:115, destroy:98 - bizwiz/app/Http/Controllers/Admin/ProcessedRequisitionController.php — index:31, show:60

Models - bizwiz/app/Model/NWaInventoryLocationTransfer.php — $fillable:24–35, relations :50–160 - bizwiz/app/Model/NWaInventoryLocationTransferItem.php — relations :11–24 - bizwiz/app/Model/NWaInternalRequisition.php — relations :17–53 - bizwiz/app/Model/NWaInternalRequisitionItem.php — relations :11–17 - bizwiz/app/Models/InterbranchTransferVerification.php

Migrations - bizwiz/database/migrations/2023_09_08_134414_create_n_wa_inventory_location_transfers_table.php - bizwiz/database/migrations/2023_09_08_134414_create_n_wa_inventory_location_transfer_items_table.php - bizwiz/database/migrations/2025_08_10_180000_add_goods_received_fields_to_transfers.php - bizwiz/database/migrations/2026_02_03_120000_add_processed_by_to_n_wa_inventory_location_transfers.php - bizwiz/database/migrations/2026_09_01_081258_* (reactivation), 2026_09_03_071300_add_pack_receiving_option_..., 2025_12_30_122757_add_uom_columns_... - bizwiz/database/migrations/2023_09_08_134414_create_n_wa_internal_requisitions_table.php - bizwiz/database/migrations/2023_09_08_134414_create_n_wa_internal_requisition_items_table.php - bizwiz/database/migrations/2023_09_22_111306_add_additional_status_options_to_wa_internal_requisitions.php, 2025_03_27_180128_add_uom_id_... - bizwiz/database/migrations/2023_09_08_134414_create_wa_internal_requisitions_table.php — legacy SALES table (contrast)