Transfer Goods Receipts
Overview
The Transfer Goods Receipts module handles the receiving end of inter-location inventory transfers. When goods arrive at a destination location, a Goods Receipt document is created to record what was received, calculate costs from in-transit inventory, and update the parent Inventory Transfer's receipt summary via events.
Each goods receipt tracks:
- Origin and destination locations
- Items received (products, quantities)
- Cost detail calculated from in-transit inventory costs
- Received date when goods arrived
- Transport detail optional carrier/vehicle information
- Document number auto-generated for reference
Architecture
transfers-goods-receipt/
├── transfers-goods-receipt.module.ts # NestJS module
├── domain/
│ └── transfers-goods-receipt-repository.domain.ts # Port interface + injection token + sortable keys
├── application/
│ ├── transfers-goods-receipt.service.ts # Use cases
│ └── events/
│ ├── on-create-transfer-goods-receipt.event.ts
│ └── on-update-transfer-goods-receipt.event.ts
├── infrastructure/
│ └── transfers-goods-receipt.repository.ts # Kysely adapter
└── interfaces/
├── transfers-goods-receipt.controller.ts # REST controller
├── dtos/
│ ├── create-transfer-goods-receipt.dto.ts
│ └── update-transfer-goods-receipt.dto.ts
└── query/
└── paginate-transfers-goods-receipt.query.ts
Domain Concepts
Relationship to Inventory Transfers
Transfer Request (approved)
│
▼
Inventory Transfer (created)
│
├── Dispatch Note (items shipped)
│
└── Goods Receipt (items received) ← this module
└── Updates receiptSummary on parent transfer
Cost Calculation
When a goods receipt is created or updated:
- Items are extracted from the
detailJSON - Items are grouped by location and product
- Inventory records are ensured to exist at the destination
- Current inventory costs are fetched
- Costs are calculated using in-transit incoming cost method
costDetailis populated on the document
Event Integration
Events Published
transferGoodsReceipt.create— after creating a goods receipttransferGoodsReceipt.update— after updating a goods receipt
Events Consumed (by InventoryTransferLifecycleHandler)
The parent inventory-transfers module listens to these events to update the transfer's receiptSummary and transferGoodsReceiptStatus.
API Endpoints
| Method | Path | Description |
|---|---|---|
POST | /transfers-goods-receipt | Create goods receipt |
GET | /transfers-goods-receipt | List with pagination |
GET | /transfers-goods-receipt/search | Search with advanced filters |
GET | /transfers-goods-receipt/:id | Get single goods receipt |
GET | /transfers-goods-receipt/:id/pdf | Download PDF document |
GET | /transfers-goods-receipt/:id/print | HTML print view |
PATCH | /transfers-goods-receipt/:id | Update goods receipt |
DELETE | /transfers-goods-receipt/:id | Delete goods receipt |
Search Filters
| Parameter | Type | Description |
|---|---|---|
businessId | UUID | Filter by business |
status | string | Filter by status (draft, pending, approved, rejected) |
originLocationId | UUID | Filter by origin location |
destinationLocationId | UUID | Filter by destination location |
createdAtFrom/To | ISO date | Filter by creation date range |
receivedDateFrom/To | ISO date | Filter by received date range |
query | string | Free-text search on location names |
Example: Create Goods Receipt
POST /transfers-goods-receipt
{
"businessId": "uuid",
"status": "draft",
"createdBy": "uuid",
"receivedDate": "2026-03-25",
"originLocationId": "uuid",
"originLocationName": "Main Warehouse",
"destinationLocationId": "uuid",
"destinationLocationName": "Retail Store #1",
"detail": {
"items": [
{
"id": "uuid",
"businessId": "uuid",
"locationId": "uuid",
"locationName": "Retail Store #1",
"productId": "uuid",
"productName": "Blue T-Shirt M",
"goodOrService": "B",
"quantity": 10
}
]
},
"inventoryTransferId": "uuid",
"transferDispatchNoteId": "uuid",
"note": "Received in good condition"
}
Example: Update Goods Receipt Status
PATCH /transfers-goods-receipt/:id
{
"status": "approved",
"updatedBy": "uuid"
}
Design Decisions
-
Event-driven lifecycle: Goods Receipt creation/update emits events consumed by the parent Inventory Transfer module to update receipt summaries, keeping modules loosely coupled.
-
Cost detail from in-transit: Unlike purchase GRNs that use WAC, transfer goods receipts use
updateCostsFromInTransitIncomingsince the cost was already established when the transfer was created. -
Hexagonal injection: Repository is injected via
TRANSFERS_GOODS_RECEIPT_REPOSITORYsymbol token, allowing easy test mocking and adapter swapping. -
Transaction safety: Create and update operations run inside database transactions to ensure cost calculation and document creation are atomic.