Skip to main content

Purchasing Module

The purchasing domain manages the full procurement lifecycle across four backend modules:

ModulePathPurpose
Suppliersapps/backend/src/suppliers/Vendor/partner registry
Purchase Ordersapps/backend/src/purchase-orders/Procurement documents before goods receipt
Purchasesapps/backend/src/purchases/Goods received documents
Purchase Document Ingestionapps/backend/src/purchase-document-ingestion/FEL XML/PDF inbox → optional draft purchase

Architecture​

All four modules follow hexagonal architecture (ingestion uses discrete use-case classes instead of a single *.service.ts):

<module>/
├── <module>.module.ts # NestJS module definition
├── application/
│ ├── <module>.service.ts # Business logic (use cases)
│ ├── <module>.listener.ts # Event listeners (cross-module integration)
│ └── events/ # Domain events
├── domain/
│ └── <module>-repository.domain.ts # Repository interface (port)
├── infrastructure/
│ └── <module>.repository.ts # Kysely DB implementation (adapter)
└── interfaces/
├── <module>.controller.ts # HTTP routes (adapter)
├── dtos/ # Request validation DTOs
└── query/ # Query parameter classes

Dependency flow: interfaces → application → domain ← infrastructure

Domain Concepts​

Supplier​

A vendor or partner from which goods are purchased. Contains tax information (taxId, taxName, taxAddress), contact details, and geographic location. All operations are scoped by businessId.

Purchase Order (PO)​

A procurement document created before goods are received. Tracks:

  • Order items with product/variant details, quantities, and pricing
  • Receipt summary — ordered vs. received quantities per item (updated by GRN events)
  • Goods received note status — pending → partial → received
  • Document number — auto-generated unique identifier

Purchase​

A goods-received document recording physical receipt of items from a supplier. Tracks:

  • Purchase items with inventory detail (batch, serial, location allocation)
  • Cost detail — WAC-based cost snapshot at document creation time
  • Document number — auto-generated

Purchase document ingestion​

A captured supplier invoice (FEL XML and/or PDF) that can later post as a draft purchase. See Purchase Document Ingestion.

Main Flows​

1. Standard Procurement Flow​

Create Supplier → Create PO → Create GRN → PO receipt summary auto-updated

2. GRN → PO Integration (Event-Driven)​

When a Goods Received Note is created:

  1. OnCreateGoodsReceivedNoteEvent is emitted
  2. PurchaseOrdersListener handles the event, updates the PO's receiptSummary
  3. If all items are fully received, goodsReceivedNoteStatus transitions to received

3. Supplier invoice inbox → draft purchase​

Merchants can capture certified FEL XML (optional PDF) without retyping:

Upload or email XML/PDF → extract draft → map lines → POST :id/post → purchase (draft)

Stock still moves only when that purchase is submitted. Architecture, HTTP surface, and posting constraints: Purchase Document Ingestion. Operations: Purchase Document Ingestion Troubleshooting.

4. Cost Calculation​

On document creation/update:

  1. Items are extracted from purchaseDetail JSON
  2. Grouped by location and product
  3. Inventory records are ensured to exist
  4. Current WAC costs are fetched from inventory
  5. Cost detail is calculated and stored as costDetail JSON

API Endpoints​

Suppliers — /suppliers​

MethodPathDescriptionAuth
POST/suppliersCreate supplierCreate permission
GET/suppliersList suppliers (paginated)Read permission
GET/suppliers/:idGet supplier by IDRead permission
PATCH/suppliers/:idUpdate supplierUpdate permission
DELETE/suppliers/:idDelete supplierDelete permission

All endpoints require businessId for multi-tenancy scoping.

Purchase Orders — /purchase-orders​

All endpoints are protected by RolesGuard with PolicyResource.Purchase and require appropriate PolicyAction permission (Create/Read/Update/Delete).

MethodPathDescription
POST/purchase-ordersCreate PO
GET/purchase-ordersList POs (paginated, filtered)
GET/purchase-orders/searchSearch POs
GET/purchase-orders/:idGet PO by ID
GET/purchase-orders/:id/pdfDownload PO as PDF
GET/purchase-orders/:id/printHTML print preview
PATCH/purchase-orders/:idUpdate PO
DELETE/purchase-orders/:idDelete PO (requires businessId query)

Filters: status, goodsReceivedNoteStatus, createdAt range, orderDate range.

Purchases — /purchases​

MethodPathDescription
POST/purchasesCreate purchase
GET/purchasesList purchases (paginated, filtered)
GET/purchases/searchSearch purchases
GET/purchases/:idGet purchase by ID
GET/purchases/:id/pdfDownload purchase as PDF
GET/purchases/:id/pdf/contentGet PDF as base64 + metadata
GET/purchases/:id/printHTML print preview
PATCH/purchases/:idUpdate purchase
DELETE/purchases/:idDelete purchase

Filters: status, documentNumber, createdAt range, purchaseDate range.

Purchase document ingestions — /purchase-document-ingestions​

Full route table, auth, and posting rules: Purchase Document Ingestion. Public email webhook is POST /webhooks/sendgrid/inbound-purchase-documents (inbox token in To, not Firebase).

Database Tables​

TableKey columns
supplierid, businessId, name, taxId, taxName, taxAddress, contact (JSON), supplierCode
purchaseOrderid, businessId, supplierId, purchaseDetail (JSON), costDetail (JSON), receiptSummary (JSON), status, goodsReceivedNoteStatus, documentNumber
purchaseid, businessId, supplierId, purchaseDetail (JSON), costDetail (JSON), paymentDetail (JSON), status, documentNumber

Enums​

  • PurchaseStatus: draft, submitted, reviewed
  • PurchaseOrderStatus: draft, submitted, reviewed, approved, canceled, completed
  • ReceivedStatus: pending, partial, received
  • PaymentStatus: pending, partial, completed

Key Design Decisions​

  1. Variant validation — PO creation validates that referenced product variants are active and belong to the correct product (done in repository layer).
  2. Receipt tracking — POs maintain a receiptSummary JSON that is updated via events when GRNs are created, avoiding tight coupling.
  3. Cost snapshot — costDetail captures WAC costs at document time, providing an audit trail independent of inventory cost changes.
  4. PDF generation — Uses Puppeteer via a shared PdfModule. Supports configurable page size, margins, and template overrides.
  • purchase-document-ingestion/ — supplier invoice capture and draft-purchase posting
  • goods-received-notes/ — GRN documents that trigger PO receipt updates
  • inventories/ — Inventory records and WAC cost data
  • pdf/ — Shared PDF generation infrastructure