Skip to content

Stock Transfer

Location: dg_smart_pos_api/modules/StockTransfer (no space in the directory name, unlike an earlier draft of this page). Owns two related but distinct workflows: a store directly pushing stock to another store, and a store asking another store for stock which is then approved or rejected. Both ultimately move stock via App\Models\Store-scoped quantities in store_stocks; the module depends on Inventory for the stock items themselves (Modules\Inventory\Models\NewStock) and is in turn a listed dependency of Inventory in config/modules.php.

  • StockRequest (stock_requests table) - a pull-model ask: requesting_store wants requested_quantity of a stock item from fulfilling_store. No status column; status is always computed from quantities via computeStatus() (pending, partially_fulfilled, fulfilled, rejected, cancelled).
  • TransferHistory (transfer_histories table) - an actual movement record: origin_store to destination_store, transferred_quantity vs received_quantity vs returned_to_origin_quantity. Status is likewise computed, not stored, via quantityInTransit() (in_transit, partially_received, fully_received, settled).

Approving a StockRequest creates/updates a TransferHistory under the hood (StockRequestController::approve doc comment: “moves stock via StockTransferService”) - a request is the ask, a transfer is the actual stock movement.

StockRequestController against StockRequest:

  • index - lists requests for a store, filterable by type (outgoing/incoming), status, from_date/to_date. “Incoming” includes requests aimed at any warehouse the store owns (Store::warehouseIdsOwnedBy), since a warehouse has no logins of its own to act on its own requests.
  • store (StockRequestCreateRequest) - fulfilling_store (required), lines (array of stock_id/requested_quantity/optional suggested_serials), notes. Rejects requesting from your own store.
  • approve (StockRequestApproveRequest) - fully or partially approves, moving stock via StockTransferService. Requires inventory:transfer specifically (stricter than read/create).
  • reject (StockRequestRejectRequest) - closes the remaining open quantity with a reason. Same inventory:transfer-only gate as approve.
  • cancel - requester-side cancellation of the remaining open quantity. Allowed under the broader read/create permission set.
  • getPendingApprovals / getPendingApprovalCount - incoming requests still awaiting a decision at this store.
  • getAllPendingApprovals - every pending request tenant-wide, gated by a separate inventory:resolve-any-request permission; visibility only, approving/rejecting still goes through the store-scoped routes above.

StockTransferController against TransferHistory:

  • store (StockTransferCreateRequest) - stock_to_transfer (array of id/transfer_quantity/optional serials), to_store. Note the field names differ from the request flow above (id/transfer_quantity here vs stock_id/requested_quantity there). Gated by inventory:transfer|inventory:add plus a stock.write.admin middleware that additionally blocks warehouse-origin transfers for inventory:add-only users unless the store’s cashier_can_transfer_from_warehouse setting allows it.
  • index / show - filterable by type (transfer/receive), status, date range.
  • receive (StockTransferReceiveRequest) - receives a quantity of a specific transfer’s stock at the destination.
  • reverse (StockTransferReverseRequest) - reverses a quantity of a transfer, optionally scoped to specific serials.
  • getPendingReceived, getTransferred, getReceived - convenience list endpoints.
  • deliveryNote - metadata for client-side thermal/PDF delivery notes, addressed either by ids=1,2,3 or a shared batch= transfer batch number.
  • exportPdf - A4 PDF export of the filtered transfer list (same filters as index).

AdminStockTransferController (not store-scoped): dashboard (summary cards plus per-shop-pair breakdown), transfers (paginated, filtered list across every store), exportPdf (same, as a PDF). All three require inventory:transfer.

Registered as a tenant route module (StockTransfer is in ModulesServiceProvider::TENANT_ROUTE_MODULES), required once each from routes/tenant.php (no duplicate registration, unlike Sales):

Stock requests - auth:sanctum, prefix stock-requests/stores/{store} (from modules/StockTransfer/Routes/stockRequest.php):

Method Path Controller method
GET requests/pending-approvals getPendingApprovals
GET requests/pending-approval-count getPendingApprovalCount
GET requests index
GET requests/{stockRequest} show
POST requests store
POST requests/{stockRequest}/cancel cancel
POST requests/{stockRequest}/approve approve
POST requests/{stockRequest}/reject reject
GET stock-requests/pending-approvals/all (no {store}) getAllPendingApprovals

Stock transfers - auth:sanctum, prefix stock-transfer/stores/{store} (from modules/StockTransfer/Routes/transfer.php):

Method Path Controller method
GET transfers/delivery-note deliveryNote
GET transfers/export-pdf exportPdf
GET transfers index
GET transfers/{transferHistory} show
POST transfers store
POST transfers/{transferHistory}/receive receive
POST transfers/{transferHistory}/reverse reverse
GET transfer/pending/received getPendingReceived
GET transfer/sent getTransferred
GET transfer/received getReceived

Admin - auth:sanctum, prefix stock-transfer/admin:

Method Path Controller method
GET dashboard AdminStockTransferController::dashboard
GET transfers AdminStockTransferController::transfers
GET transfers/export-pdf AdminStockTransferController::exportPdf

A stock transfer list item (StockTransferListResource):

{
"id": 1,
"transfer_batch_no": "TB-000001",
"stock_id": 5,
"stock_name": "Widget 500ml",
"origin_store_name": "Main Branch",
"destination_store_name": "Warehouse A",
"transfer_date": "2026-01-01",
"transferred_quantity": 10,
"received_quantity": 4,
"returned_to_origin_quantity": 0,
"in_transit_quantity": 6,
"status": "partially_received",
"transferred_by_name": "Jane Doe",
"cost_snapshot": 250.5,
"transfer_value": 2505
}

A stock request list item (StockRequestListResource) additionally carries remaining_quantity, available_at_fulfilling_store (only populated when the viewing store is the request’s own fulfilling_store, null otherwise so a requester can’t infer the other store’s stock), and a computed status.

  • Inventory - stock items, quantities, and store_stocks that transfers and requests move
  • Sales