Transfer Requests
Overview
The Transfer Requests module manages the request/approval workflow that precedes inter-location inventory transfers. A transfer request captures the intent to move inventory between locations and goes through an approval flow before an actual inventory transfer is created.
Each transfer request tracks:
- Origin and destination locations
- Items requested for transfer (products, quantities)
- Cost detail calculated from current inventory WAC (Weighted Average Cost)
- Approval workflow (approver, approval date)
- Linked inventory transfer (populated after the transfer is created)
- Document number auto-generated for reference
Architecture
transfers-request/
├── transfers-request.module.ts # NestJS module
├── domain/
│ └── transfers-request-repository.domain.ts # Port interface + sortable keys
├── application/
│ ├── transfers-request.service.ts # Use cases + event handlers
│ └── events/
│ ├── on-create-transfer-request.event.ts # Emitted on create
│ └── on-update-transfer-request.event.ts # Emitted on update
├── infrastructure/
│ └── transfers-request.repository.ts # Kysely adapter
└── interfaces/
├── transfers-request.controller.ts # HTTP routes
├── dtos/
│ ├── create-transfer-request.dto.ts
│ └── update-transfer-request.dto.ts
└── query/
└── paginate-transfers-request.query.ts
Layer Responsibilities
| Layer | Responsibility |
|---|---|
| Domain | Repository interface (port), sortable key definitions — no framework dependencies |
| Application | Business logic: cost calculation, document number generation, event handling for inventory transfer linkage |
| Infrastructure | Kysely database queries implementing the domain port |
| Interfaces | HTTP controllers, DTOs, query objects, Swagger documentation |
Domain Concepts
Transfer Request Lifecycle
- Draft — Created with item details; costs auto-calculated from inventory WAC
- Pending — Submitted for approval
- Approved — Approved by a manager; can trigger creation of an inventory transfer
- Rejected — Denied; no further action
Cost Detail
On create/update, the service:
- Extracts items from the detail JSON
- Groups items by location and product
- Fetches current WAC costs from inventory
- Calculates cost detail using shared utility functions
Cost detail is recalculated on update only when the items detail has changed (compared via stable JSON stringify).
Inventory Transfer Linkage
When an inventory transfer is created from an approved transfer request, the OnCreateInventoryTransferEvent is consumed to link the two records:
inventoryTransferId— UUID of the created transferinventoryTransferData— metadata snapshot (e.g., document number)
API Endpoints
| Method | Path | Description |
|---|---|---|
POST | /transfers-request | Create a new transfer request |
GET | /transfers-request | List transfer requests (paginated) |
GET | /transfers-request/search | Search with filters (date ranges, status, locations) |
GET | /transfers-request/:id | Get transfer request by ID |
GET | /transfers-request/:id/pdf | Download PDF document |
GET | /transfers-request/:id/print | Get HTML print view |
PATCH | /transfers-request/:id | Update transfer request |
DELETE | /transfers-request/:id | Delete transfer request |
Search Filters
The GET /search endpoint supports:
businessId— filter by business UUIDstatus— filter by status (draft, pending, approved, rejected)originLocationId/destinationLocationId— filter by locationcreatedAtFrom/createdAtTo— date range on creation datetransferDateFrom/transferDateTo— date range on requested transfer datesearch— free-text search on document number and location names- Standard pagination:
page,size,orderBy,order
Event Integration
Events Emitted
| Event | When |
|---|---|
transferRequest.create | After a transfer request is created |
transferRequest.update | After a transfer request is updated |
Events Consumed
| Event | Action |
|---|---|
inventoryTransfer.create | Links the created inventory transfer to the originating transfer request |
Design Decisions
-
Repository injected via domain token — The service depends on
ITransfersRequestRepository(domain port) viaTRANSFERS_REQUEST_REPOSITORYSymbol, enabling testability and respecting hexagonal boundaries. -
Cost calculated at request time — WAC is captured when the request is created/updated, giving approvers visibility into the financial impact before approving.
-
Shared date-range utility — Repository uses
buildDateRangeConditionfrom@/utils/date-range-filter.utilsfor consistent date filtering across all inventory modules. -
Sortable keys defined in domain — The
sortableTransferRequestKeysconstant lives in the domain layer since it represents a domain concept (which fields support sorting), avoiding a dependency inversion from domain to interfaces.