AlphaAlpha Docs

Stock Transfers

Moving inventory between stock sites — full flow and every action

Stock Transfers

A stock transfer has two linked entities:

  • StockTransfer (outgoing) — created and managed by the sender site.
  • IncomingStockTransfer (incoming) — auto-created at the destination site on approval.

Both are driven independently by their own status machines and endpoints.

Statuses

Outgoing (StockTransfer)

Incoming (IncomingStockTransfer)

End-to-end flow

1. Sender — Create draft

POST /stock-transfers/emptyStockTransferService.createDraft()

Creates a new transfer in DRAFT with a unique transfer number. No products yet.

2. Sender — Edit transfer

PUT /stock-transfers/{id}StockTransferService.update()

Allowed from DRAFT or REJECTED only. Sets subsidiary, source site, destination, products, batch allocations, delivery date/method/priority. Validates available stock at source and batch allocations.

3. Sender — Validate (optional preview)

GET /stock-transfers/{id}/validateStockTransferService.validate()

Read-only pre-flight. Checks products, source/destination stock, batch allocations. Does not change state.

4. Sender — Cancel draft / reinstate (optional)

  • PATCH /stock-transfers/{id}/cancel-draftcancelDraft()DRAFT/REJECTEDDRAFT_CANCELLED.
  • PATCH /stock-transfers/{id}/reinstatereinstate()DRAFT_CANCELLEDDRAFT.

5. Sender — Request approval

PATCH /stock-transfers/{id}/request-approvalrequestApproval()

From DRAFT/REJECTED. Runs validation, transitions to PENDING_APPROVAL, notifies all TENANT_ADMIN users.

6. Approver — Approve or reject

  • PATCH /stock-transfers/{id}/approveapprove()PENDING_APPROVALOPEN. Auto-creates the IncomingStockTransfer at destination with status EXPECTED. Assigns transfer number if missing.
  • PATCH /stock-transfers/{id}/rejectreject()PENDING_APPROVALREJECTED. Sender can edit and resubmit.

7. Warehouse — Reserve products

POST /stock-transfers/{id}/reservationsreserveProducts()

Only from OPEN. Reserves source stock via StockInventoryService.reserve(). Partial reservation is allowed — requested quantity may be less than pending. Updates transfer.reservedProducts.

8. Warehouse — Cancel reservations (optional)

PATCH /stock-transfers/{id}/reservations/cancelcancelReservations()

Only from OPEN. Fails if the reservation is already assigned to a picklist. Removes reservations from inventory.

9. Warehouse — Generate picklist

POST /stock-transfers/{id}/picklistsgeneratePicklist()

Only from OPEN. Requires reserved, unassigned quantities. Creates a Picklist (type StockReservationType.TRANSFER) and links it to the transfer via picklistIds.

10. Warehouse — Cancel picklist (optional)

PATCH /stock-transfers/{id}/picklists/{picklistId}/cancelcancelPicklist()

Marks the linked picklist CANCELLED. Does not auto-release the underlying reservations — cancel those separately if needed.

11. Warehouse — Pick, ship, deliver (driven by the picklist lifecycle)

The picklist service calls back into the transfer:

  • markAsPicked() — picked products removed from reservedProducts.
  • markAsShipped() — shipment event recorded; stock moves to in-transit.
  • markAsDelivered() — auto-transitions the outgoing transfer to COMPLETED (all delivered) or PARTIALLY_COMPLETED (nothing still pending).

These are not directly called from the UI — they fire when the picklist is advanced.

12. Receiver — Receive goods

PATCH /incoming-stock-transfers/{id}/receiveIncomingStockTransferService.receive()

Allowed from EXPECTED or PARTIALLY_RECEIVED. Accepts an array of received products with, per line:

  • Accepted / rejected quantities (rejections carry a reason).
  • Batch and serial numbers.
  • Destination location within the site.
  • UoM split (if receiving in different units than shipped).

Side effects:

  • Accepted stock → StockInventoryService.add() at the destination site.
  • In-transit inventory decremented.
  • Status auto-derived per call: RECEIVED (all accepted), REJECTED (all rejected), otherwise PARTIALLY_RECEIVED.
  • Events recorded per rejection reason and per acceptance.

Call as many times as needed until everything is resolved.

13. Receiver — Close incoming transfer

PATCH /incoming-stock-transfers/{id}/closeclose()

Requires zero pending items on the enriched transfer. Sets status to CLOSED. Incoming transfers cannot be bulk-deleted — the DELETE endpoint throws InvalidOperationException.

14. Sender — Close outgoing transfer

PATCH /stock-transfers/{id}/closeStockTransferService.close()

From COMPLETED or PARTIALLY_COMPLETED. Finalises the outgoing record.

Full endpoint reference

Outgoing — /stock-transfers

MethodPathService methodPurpose
POST/emptycreateDraftCreate blank draft
GET/{id}getEnrichedRead one, enriched
PUT/{id}updateEdit while DRAFT/REJECTED
GET/{id}/validatevalidatePre-flight check
PATCH/{id}/request-approvalrequestApprovalPENDING_APPROVAL
PATCH/{id}/approveapproveOPEN, creates incoming
PATCH/{id}/rejectrejectREJECTED
PATCH/{id}/cancel-draftcancelDraftDRAFT_CANCELLED
PATCH/{id}/reinstatereinstateDRAFT
POST/{id}/reservationsreserveProductsReserve source stock
PATCH/{id}/reservations/cancelcancelReservationsRelease reservations
POST/{id}/picklistsgeneratePicklistCreate linked picklist
PATCH/{id}/picklists/{picklistId}/cancelcancelPicklistCancel a linked picklist
PATCH/{id}/closecloseCLOSED
GET/pagefindPagePaginated list
GET/page-enrichedfindPageEnrichedPaginated list, enriched
POST/dynamic-search-enrichedsearchEnrichedDynamic search
GET/statsgetStatsTop products + status counts
POST/{id}/commentaddCommentAndReturnEnrichedAppend comment

Service-only (called from PicklistService): markAsPicked, markAsShipped, markAsDelivered.

Incoming — /incoming-stock-transfers

MethodPathService methodPurpose
GET/{id}getEnrichedRead one, enriched
PATCH/{id}/receivereceiveAccept/reject lines
PATCH/{id}/closecloseCLOSED
POST/{id}/commentaddCommentAndReturnEnrichedAppend comment
GET/pagefindPagePaginated list
GET/page-enrichedfindPageEnrichedPaginated list, enriched
GET/statsgetIncomingStockTransferStatsTop products + status counts
DELETE/deleteManyBlocked — throws InvalidOperationException

Service-only: create (called from StockTransferService.approve()).

Batch, serial, UoM, location handling

  • Batch allocations are validated on update() via validateBatchAllocations() against actual batches at the source site.
  • UoM splitsreceive() accepts ReceivedGoodsDto.uomSplit so goods can be received in a different unit than shipped.
  • Serials / batch numbers — carried on both allocation and acceptance. Stored on incoming acceptedProducts.
  • Locations — destination bin/location recorded per accepted line (ProductPriceQuantityLocation).

Inventory side effects summary

ActionSource siteDestination site
reserveProductsreserve()
cancelReservationsunreserve
generatePicklist— (links picklist)
markAsPicked (via picklist)deduct from reserved
markAsShipped (via picklist)→ in-transit
receive (accepted)in-transit decrementadd() to on-hand
receive (rejected)in-transit decrement

Frontend routes

RoutePurpose
/stock/transfers/List + create
/stock/transfers/$stockTransferIdSender-side editor
/stock/transfers/received/$stockTransferReceivedIdReceiver-side

Key files

LayerPath
Outgoing modelsrc/backend/.../model/StockTransfer.kt
Incoming modelsrc/backend/.../model/IncomingStockTransfer.kt
Outgoing controllersrc/backend/.../controller/StockTransferController.kt
Outgoing servicesrc/backend/.../service/StockTransferService.kt
Incoming controllersrc/backend/.../controller/IncomingStockTransferController.kt
Incoming servicesrc/backend/.../service/IncomingStockTransferService.kt
Frontend routessrc/frontend/src/routes/_app/(erp)/stock/transfers/

On this page