Skip to main content

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:

  1. Items are extracted from the detail JSON
  2. Items are grouped by location and product
  3. Inventory records are ensured to exist at the destination
  4. Current inventory costs are fetched
  5. Costs are calculated using in-transit incoming cost method
  6. costDetail is populated on the document

Event Integration

Events Published

  • transferGoodsReceipt.create — after creating a goods receipt
  • transferGoodsReceipt.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

MethodPathDescription
POST/transfers-goods-receiptCreate goods receipt
GET/transfers-goods-receiptList with pagination
GET/transfers-goods-receipt/searchSearch with advanced filters
GET/transfers-goods-receipt/:idGet single goods receipt
GET/transfers-goods-receipt/:id/pdfDownload PDF document
GET/transfers-goods-receipt/:id/printHTML print view
PATCH/transfers-goods-receipt/:idUpdate goods receipt
DELETE/transfers-goods-receipt/:idDelete goods receipt

Search Filters

ParameterTypeDescription
businessIdUUIDFilter by business
statusstringFilter by status (draft, pending, approved, rejected)
originLocationIdUUIDFilter by origin location
destinationLocationIdUUIDFilter by destination location
createdAtFrom/ToISO dateFilter by creation date range
receivedDateFrom/ToISO dateFilter by received date range
querystringFree-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

  1. Event-driven lifecycle: Goods Receipt creation/update emits events consumed by the parent Inventory Transfer module to update receipt summaries, keeping modules loosely coupled.

  2. Cost detail from in-transit: Unlike purchase GRNs that use WAC, transfer goods receipts use updateCostsFromInTransitIncoming since the cost was already established when the transfer was created.

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

  4. Transaction safety: Create and update operations run inside database transactions to ensure cost calculation and document creation are atomic.