Goods Received Notes (GRN)
Overview
The GRN module manages the lifecycle of goods received against Purchase Orders. A GRN confirms physical delivery, verifies quantities (including damaged items), updates inventory, and supports cost tracking and PDF/print output.
Module path: apps/backend/src/goods-received-notes/
Architecture
The module follows hexagonal architecture:
goods-received-notes/
├── domain/
│ └── goods-received-notes-repository.domain.ts # Port interface + injection token
├── application/
│ ├── goods-received-notes.service.ts # Use cases / business logic
│ └── events/
│ ├── on-create-goods-received-note.event.ts
│ └── on-update-goods-received-note.event.ts
├── infrastructure/
│ └── goods-received-notes.repository.ts # Kysely adapter
└── interfaces/
├── goods-received-notes.controller.ts # REST endpoints
├── dtos/
│ ├── create-goods-received-note.dto.ts
│ └── update-goods-received-note.dto.ts
└── query/
└── paginate-goods-received-notes.query.ts
Dependency flow: Controller → Service → Repository (via domain interface)
The service injects IGoodsReceivedNotesRepository via the GOODS_RECEIVED_NOTES_REPOSITORY symbol token, keeping the application layer decoupled from infrastructure.
Domain Concepts
| Concept | Description |
|---|---|
| GRN | Document confirming receipt of goods against a Purchase Order |
| Status | draft → submitted → reviewed |
| goodsReceivedDetail | JSONB containing items array with quantities, prices, and inventory details |
| costDetail | JSONB with WAC cost calculations, computed on create/update |
| documentNumber | Auto-generated sequential number per business |
Status Lifecycle
DRAFT ──(submit)──> SUBMITTED ──(manual)──> REVIEWED
- DRAFT: Can be edited, deleted, or submitted
- SUBMITTED: Inventory details have been created; triggers
goodsReceivedNote.submittedevent - REVIEWED: Final state after human review
API Endpoints
| Method | Endpoint | Description |
|---|---|---|
POST | /goods-received-notes | Create a GRN (generates document number, calculates costs) |
GET | /goods-received-notes | List GRNs with pagination and filters |
GET | /goods-received-notes/search | Alias for list endpoint |
GET | /goods-received-notes/:id | Get a single GRN by UUID |
GET | /goods-received-notes/:id/pdf | Download PDF with optional page/margin settings |
GET | /goods-received-notes/:id/print | HTML print preview |
PUT | /goods-received-notes/:id/submit | Transition DRAFT → SUBMITTED, creates inventory details |
PATCH | /goods-received-notes/:id | Partial update (recalculates costs) |
DELETE | /goods-received-notes/:id?businessId= | Delete GRN (scoped by business) |
Query Parameters (GET list)
| Parameter | Type | Description |
|---|---|---|
businessId | UUID | Filter by business |
status | string | Filter by status (draft/submitted/reviewed) |
purchaseOrderId | UUID | Filter by linked PO |
createdAtFrom / createdAtTo | ISO date | Creation date range |
receivedDateFrom / receivedDateTo | ISO date | Received date range |
search | string | Text search on document number |
page, size | number | Pagination |
orderBy, order | string | Sorting |
Key Flows
Create GRN
- Extract items from
goodsReceivedDetail, validate inventory detail quantities - Group items by location + product
- Ensure inventory records exist (create if missing)
- Calculate WAC costs and populate
costDetail - Generate document number
- Insert record within a transaction
- Emit
goodsReceivedNote.createevent
Submit GRN
- Verify status is
DRAFT - For each received item: create inventory detail records (net quantity = received - damaged)
- Update status to
SUBMITTEDwithin a transaction - Emit
goodsReceivedNote.submittedevent
PDF Generation
Uses the shared PDF module: GenerateGoodsReceivedNotePdfUseCase → GoodsReceivedNoteDataTransformer → Handlebars template → Puppeteer PDF.
Integration Points
- Purchase Orders: Linked via
purchaseOrderIdFK - Inventories: Ensures inventory records exist; reads current costs for WAC
- Inventory Details: Creates detail records on submit (batch/serial tracking)
- Product Cost History: Cost changes tracked with
source_type = 'goods_received_note' - PDF Module: Dedicated use case, transformer, and template
- Document Counter: Auto-generates sequential numbers per business
Database
- Table:
goodsReceivedNote(camelCase via Kysely codegen) - Enum:
goods_received_note_status(draft, submitted, reviewed) - Migration:
2024-06-02t23:35:16.678z-onboarding-tables.mjs - Indexes:
idx_grn_business_id,idx_grn_purchase_order_id,idx_grn_status,idx_grn_received_date
Events
| Event | Payload | When |
|---|---|---|
goodsReceivedNote.create | { createdGoodsReceivedNoteRecord } | After creation |
goodsReceivedNote.update | { originalGoodsReceivedNoteRecord, updatedGoodsReceivedNoteRecord } | After update |
goodsReceivedNote.submitted | { grn } | After submission |
Bruno API Collection
Located at api-client/flowpos/collections/goods-received-notes/. Includes requests for all endpoints including create variants (complete, partial, multi-currency), PDF generation, print preview, update, delete, and submit.