Saltar al contenido principal

Inventory Transfers

Overview

The Inventory Transfers module manages the movement of inventory between business locations. It sits at the center of the transfer lifecycle and coordinates with Transfer Requests, Dispatch Notes, and Goods Receipts to provide end-to-end stock movement tracking.

Each transfer tracks:

  • Origin and destination locations
  • Items being transferred (products, quantities)
  • Cost detail calculated from current inventory costs
  • Dispatch summary tracking what has been shipped
  • Receipt summary tracking what has been received
  • Document number auto-generated for reference

Architecture

inventory-transfers/
├── inventory-transfers.module.ts # NestJS module
├── domain/
│ └── inventory-transfers-repository.domain.ts # Port interface + injection token
├── application/
│ ├── inventory-transfers.service.ts # Use cases
│ ├── handlers/
│ │ └── inventory-transfer-lifecycle.handler.ts # Event handlers (SRP)
│ └── events/
│ ├── on-create-inventory-transfer.event.ts
│ └── on-update-inventory-transfer.event.ts
├── infrastructure/
│ └── inventory-transfers.repository.ts # Kysely adapter
└── interfaces/
├── inventory-transfers.controller.ts # REST controller
├── dtos/
│ ├── create-inventory-transfer.dto.ts
│ └── update-inventory-transfer.dto.ts
└── query/
└── paginate-inventory-transfers.query.ts

Domain Concepts

Transfer Statuses

StatusDescription
draftTransfer created but not yet submitted
pendingTransfer submitted for approval
approvedTransfer approved — triggers inventory updates
rejectedTransfer rejected

Dispatch & Receipt Tracking

Each transfer maintains two summary objects:

  • Dispatch Summary: tracks orderedQuantity vs dispatchedQuantity per item
  • Receipt Summary: tracks orderedQuantity vs receivedQuantity per item

Status progression: pendingpartialdispatched/received

Transfer Lifecycle

Transfer Request (approved)


Inventory Transfer (auto-created)

├── Dispatch Note (created/approved)
│ └── Updates dispatchSummary + transferDispatchNoteStatus

└── Goods Receipt (created/approved)
└── Updates receiptSummary + transferGoodsReceiptStatus
  1. A Transfer Request is approved → automatically creates an Inventory Transfer
  2. The transfer status changes to APPROVED → triggers inventory quantity updates
  3. Dispatch Notes are created as items ship → dispatch summary is updated
  4. Goods Receipts are created as items arrive → receipt summary is updated
  5. When all items are dispatched/received, status becomes DISPATCHED/RECEIVED

Event Integration

Events Published

  • inventoryTransfer.create — after creating a transfer
  • inventoryTransfer.update — after updating a transfer

Events Consumed (via InventoryTransferLifecycleHandler)

  • transferRequest.create — auto-creates transfer if request is approved
  • transferRequest.update — auto-creates transfer when request status changes to approved
  • transferDispatchNote.create/update — updates dispatch summary
  • transferGoodsReceipt.create/update — updates receipt summary

API Endpoints

MethodPathDescription
POST/inventory-transfersCreate inventory transfer
GET/inventory-transfersList with pagination
GET/inventory-transfers/searchSearch with advanced filters
GET/inventory-transfers/:idGet single transfer
GET/inventory-transfers/:id/pdfDownload PDF document
GET/inventory-transfers/:id/printHTML print view
PATCH/inventory-transfers/:idUpdate transfer
DELETE/inventory-transfers/:idDelete transfer

Search Filters

ParameterTypeDescription
businessIdUUIDFilter by business
statusstringFilter by transfer status
originLocationIdUUIDFilter by origin location
destinationLocationIdUUIDFilter by destination location
createdAtFrom/ToISO dateFilter by creation date range
transferDateFrom/ToISO dateFilter by transfer date range
querystringFree-text search on document number and location names

Example: Create Transfer

POST /inventory-transfers
{
"businessId": "uuid",
"status": "draft",
"createdBy": "uuid",
"transferDate": "2026-03-25",
"originLocationId": "uuid",
"originLocationName": "Main Warehouse",
"destinationLocationId": "uuid",
"destinationLocationName": "Retail Store #1",
"detail": {
"items": [
{
"id": "uuid",
"businessId": "uuid",
"locationId": "uuid",
"locationName": "Main Warehouse",
"productId": "uuid",
"productName": "Blue T-Shirt M",
"goodOrService": "B",
"quantity": 10
}
]
},
"note": "Seasonal stock rebalance"
}

Example: Update Transfer Status

PATCH /inventory-transfers/:id
{
"status": "approved",
"updatedBy": "uuid"
}

Design Decisions

  1. Event-driven lifecycle: Transfer Request → Inventory Transfer → Dispatch Note / Goods Receipt chain is coordinated via domain events, keeping modules loosely coupled.

  2. Cost detail calculation: Cost is computed from current inventory WAC (weighted average cost) at creation time and recalculated if items change on update.

  3. Hexagonal injection: Repository is injected via INVENTORY_TRANSFERS_REPOSITORY symbol token, allowing easy test mocking and adapter swapping.

  4. Handler separation: Event handlers live in handlers/inventory-transfer-lifecycle.handler.ts to keep the service focused on use cases (SRP).