Inventory Adjustments
Overview
The inventory adjustments module handles stock quantity corrections — both increases and decreases — outside of normal purchasing or sales flows. Common scenarios include damage write-offs, expiration, promotional giveaways, internal use, and manual count corrections (via the stock-count module).
Each adjustment creates a document with a cost snapshot at the time of creation, then emits an event that triggers downstream inventory processing in the inventories module.
Architecture
inventory-adjustments/
├── inventory-adjustments.module.ts
├── application/
│ ├── inventory-adjustments.service.ts # Use cases
│ └── events/
│ ├── on-create-inventory-adjustment.event.ts
│ └── on-update-inventory-adjustment.event.ts
├── domain/
│ └── inventory-adjustments-repository.domain.ts # Port interface
├── infrastructure/
│ └── inventory-adjustments.repository.ts # Kysely adapter
└── interfaces/
├── inventory-adjustments.controller.ts # HTTP endpoints
├── dtos/
│ ├── create-inventory-adjustment.dto.ts
│ └── update-inventory-adjustment.dto.ts
└── query/
└── paginate-inventory-adjustments.query.ts
Layer responsibilities:
| Layer | Responsibility |
|---|---|
| Domain | Repository interface (port) — no framework dependencies |
| Application | Orchestrates creation with cost snapshots, emits events |
| Infrastructure | Kysely queries against inventoryAdjustment table |
| Interfaces | HTTP controllers, request validation (DTOs/queries) |
Domain Concepts
Adjustment Direction
- INCREASE — Adds stock (e.g., found items, corrections)
- DECREASE — Removes stock (e.g., damage, expiration, internal use)
Adjustment Type
MANUAL— Operator-initiated correctionDAMAGE— Stock damaged and written offEXPIRATION— Stock expiredPROMOTIONAL— Used for promotional purposesINTERNAL_USE— Consumed internally
Status Lifecycle
DRAFT → READY → POSTED
→ CANCELED
- DRAFT — Created but not finalized
- READY — Finalized, triggers inventory processing
- POSTED — Fully applied to inventory
- CANCELED — Voided
Cost Snapshot
On creation, the service snapshots current inventory costs into costDetail (JSON). This ensures historical accuracy — cost changes after the adjustment don't affect the document.
Event Flow
Create/Update adjustment
→ Emit OnCreateInventoryAdjustmentEvent / OnUpdateInventoryAdjustmentEvent
→ InventoryAdjustmentHandler (inventories module) listens
→ If status is READY or POSTED:
→ INCREASE: processInventoryIncrease() → emit OnStockIncreasedByInventoryAdjustmentEvent
→ DECREASE: processInventoryDecrease() (FIFO batch) → emit OnStockDecreasedByInventoryAdjustmentEvent
API Endpoints
| Method | Path | Description |
|---|---|---|
POST | /inventory-adjustments | Create an adjustment |
GET | /inventory-adjustments | List adjustments (paginated, filtered by businessId/locationId) |
GET | /inventory-adjustments/search | Advanced search (status, date range, direction, type) |
GET | /inventory-adjustments/:id | Get single adjustment by ID |
GET | /inventory-adjustments/:id/pdf | Download PDF document |
GET | /inventory-adjustments/:id/print | HTML print preview |
PATCH | /inventory-adjustments/:id | Partial update |
DELETE | /inventory-adjustments/:id | Delete adjustment |
Query Parameters
List endpoint (GET /):
| Param | Required | Description |
|---|---|---|
businessId | Yes | Business UUID |
locationId | No | Filter by location |
size | No | Page size (default: all) |
page | No | Page number |
search | No | Search by location name |
orderBy | No | Sort field (locationName) |
order | No | Sort direction (asc/desc) |
Search endpoint (GET /search):
All list params plus:
| Param | Required | Description |
|---|---|---|
status | No | draft, ready, posted, canceled |
adjustmentDirection | No | INCREASE, DECREASE |
adjustmentType | No | MANUAL, DAMAGE, EXPIRATION, PROMOTIONAL, INTERNAL_USE |
createdAtFrom | No | ISO 8601 date |
createdAtTo | No | ISO 8601 date |
Integration Points
- Stock Count module — Creates adjustments automatically from count session variances
- Inventories module — Listens for create/update events to process stock changes
- PDF module — Generates PDF documents and HTML print previews
Design Decisions
-
Cost snapshot at creation —
costDetailis a JSON snapshot of inventory costs at the time the adjustment is created. This prevents retroactive cost changes from affecting already-posted documents. -
Event-driven inventory processing — Adjustments don't directly modify inventory. Instead, events are emitted and the inventories module handles stock updates. This decouples the adjustment document lifecycle from inventory processing.
-
Unscoped internal lookup (
findByIdInternal) — PDF/print generation uses an unscoped lookup (no businessId filter) because these operations are triggered after the adjustment is already authorized. The scopedfindByIdis used for all user-facing operations. -
Separate list and search endpoints — The list endpoint uses simple equality filters while the search endpoint supports range filters (date) and advanced filtering. This keeps the common case simple.