Skip to main content

Replacement Issue Notes Module

Overview

The replacement-issue-notes module manages replacement issue notes — documents created when a business issues replacement goods to a customer following an approved customer return. It tracks the lifecycle from creation through inventory reservation to completion, ensuring sufficient stock exists before committing replacements.

Domain Concepts

Status Lifecycle

PENDING → RESERVE_STOCK → COMPLETED
  • PENDING — note created, replacement items listed but not yet reserved
  • RESERVE_STOCK — inventory validated and reserved; cost snapshot captured
  • COMPLETED — replacement goods issued to customer

Inventory Validation

When transitioning from PENDING to RESERVE_STOCK, the service:

  1. Extracts items from the note's detail JSON
  2. Groups items by location and product
  3. Queries current inventory quantities and costs
  4. Rejects the transition if any item has insufficient inventory (HTTP 422)
  5. Captures an immutable cost snapshot in costDetail

Cost Snapshot

The costDetail field stores a point-in-time snapshot of:

  • documentCost — aggregated cost per grouped item (with WAC unit costs)
  • inventoryCosts — raw inventory rows at the time of reservation

This ensures accurate cost attribution regardless of future price changes.

Architecture

replacement-issue-notes/
├── replacement-issue-notes.module.ts
├── domain/
│ └── replacement-issue-notes-repository.domain.ts # Port interface + sortable keys + enum re-exports
├── application/
│ ├── replacement-issue-notes.service.ts # Use cases
│ └── events/
│ └── on-update-replacement-issue-note.event.ts # Update event payload
├── infrastructure/
│ └── replacement-issue-notes.repository.ts # Kysely adapter
└── interfaces/
├── replacement-issue-notes.controller.ts # HTTP endpoints
├── dtos/
│ ├── create-replacement-issue-note.dto.ts
│ └── update-replacement-issue-note.dto.ts
└── query/
└── paginate-replacement-issue-notes.query.ts

Layer Dependencies

  • Domain — framework-agnostic: repository interface, sortable keys, enum re-exports
  • Application — orchestrates: inventory validation, cost calculation, event emission
  • Infrastructure — Kysely queries implementing IReplacementIssueNotesRepository
  • Interfaces — thin controller mapping HTTP to service calls

Cross-Module Integration

ModuleRelationship
Customer ReturnsEach note links to a customerReturnId
InventoriesQueries inventory for availability and cost during RESERVE_STOCK transition
Event ListenersEmits replacementIssueNote.update for downstream side effects (e.g., inventory adjustments)

API Endpoints

MethodPathDescription
POST/replacement-issue-notesCreate a replacement issue note
GET/replacement-issue-notesList with pagination, search, sort
GET/replacement-issue-notes/:idGet by ID
PATCH/replacement-issue-notes/:idUpdate (status transitions)
DELETE/replacement-issue-notes/:idDelete a note

Query Parameters (GET list)

ParamTypeDescription
sizenumberPage size (0 = no limit)
pagenumberPage number (1-based)
orderBystringSort field: status, taxId, taxName, taxAddress, locationName
orderstringSort direction: asc or desc
searchstringSearch across status, taxId, taxName, taxAddress, locationName

Status Transition via PATCH

To reserve stock:

{
"status": "RESERVE_STOCK",
"updatedBy": "<employee-uuid>"
}

To mark completed:

{
"status": "COMPLETED",
"updatedBy": "<employee-uuid>"
}

Error Responses

StatusCondition
400Validation error (missing/invalid fields)
401Unauthorized (missing or invalid Firebase token)
404Replacement issue note not found
422Insufficient inventory for one or more items (RESERVE_STOCK transition)

Design Decisions

  1. Event-driven updates — Every update emits replacementIssueNote.update with both previous and new state, enabling downstream listeners (e.g., inventory adjustment, audit logging) without coupling.

  2. Cost snapshot immutability — Cost details are captured at RESERVE_STOCK time and stored as JSON. This prevents cost drift if inventory prices change after reservation.

  3. Detail as JSON — The detail column stores the full line-item structure as JSONB, matching the pattern used across other document modules (sales, customer returns, purchase orders).