Skip to main content

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​

ConceptDescription
GRNDocument confirming receipt of goods against a Purchase Order
Statusdraft → submitted → reviewed
goodsReceivedDetailJSONB containing items array with quantities, prices, and inventory details
costDetailJSONB with WAC cost calculations, computed on create/update
documentNumberAuto-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.submitted event
  • REVIEWED: Final state after human review

API Endpoints​

MethodEndpointDescription
POST/goods-received-notesCreate a GRN (generates document number, calculates costs)
GET/goods-received-notesList GRNs with pagination and filters
GET/goods-received-notes/searchAlias for list endpoint
GET/goods-received-notes/:idGet a single GRN by UUID
GET/goods-received-notes/:id/pdfDownload PDF with optional page/margin settings
GET/goods-received-notes/:id/printHTML print preview
PUT/goods-received-notes/:id/submitTransition DRAFT → SUBMITTED, creates inventory details
PATCH/goods-received-notes/:idPartial update (recalculates costs)
DELETE/goods-received-notes/:id?businessId=Delete GRN (scoped by business)

Query Parameters (GET list)​

ParameterTypeDescription
businessIdUUIDFilter by business
statusstringFilter by status (draft/submitted/reviewed)
purchaseOrderIdUUIDFilter by linked PO
createdAtFrom / createdAtToISO dateCreation date range
receivedDateFrom / receivedDateToISO dateReceived date range
searchstringText search on document number
page, sizenumberPagination
orderBy, orderstringSorting

Key Flows​

Create GRN​

  1. Extract items from goodsReceivedDetail, validate inventory detail quantities
  2. Group items by location + product
  3. Ensure inventory records exist (create if missing)
  4. Calculate WAC costs and populate costDetail
  5. Generate document number
  6. Insert record within a transaction
  7. Emit goodsReceivedNote.create event

Submit GRN​

  1. Verify status is DRAFT
  2. For each received item: create inventory detail records (net quantity = received - damaged)
  3. Update status to SUBMITTED within a transaction
  4. Emit goodsReceivedNote.submitted event

PDF Generation​

Uses the shared PDF module: GenerateGoodsReceivedNotePdfUseCase → GoodsReceivedNoteDataTransformer → Handlebars template → Puppeteer PDF.

Integration Points​

  • Purchase Orders: Linked via purchaseOrderId FK
  • 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​

EventPayloadWhen
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.