Inventory Module Backend Documentation
Inventory runtime architecture, stock mutation rules, queue processing behavior, and operational constraints.
Inventory Module - Backend Documentation
1. Backend Scope and Boundaries
Inventory backend owns:
- physical stock state in
product_inventory - stock mutation math and invariants (
reserve,finalize,release,adjust,sync) - admin inventory read/mutation HTTP surface
- BullMQ processors for stock lifecycle jobs
Inventory does not own:
- checkout/cart business eligibility
- payment gateway execution
- order lifecycle state machine
- outbox orchestration policy (owned by order processing runtime)
2. Module Composition (Aggregate + Leaf)
InventoryModule composes:
InventorySharedModuleInventoryAdminModuleInventoryProcessorModule
Ownership model:
InventorySharedModuleexportsInventoryServiceInventoryAdminModuleowns admin controller/service + DTO mappingInventoryProcessorModuleowns BullMQ processors and job handlers
Application registration:
InventoryModuleis imported inAppModuleand available in full runtime profile
3. Data Model (Drizzle / PostgreSQL)
Primary table:
product_inventory
Column-level summary:
| Column | Type | Constraint | Notes |
|---|---|---|---|
id | serial | PK | inventory row id |
product_id | int | unique FK -> product.id | one row per product |
total_quantity | int | default 0 | total tracked quantity |
reserved_quantity | int | default 0 | reserved pending units |
available_quantity | int | default 0 | sellable units |
low_stock_threshold | int | default 5 | low-stock indicator threshold |
track_inventory | boolean | default true | whether inventory checks apply |
allow_oversell | boolean | default false | oversell policy |
created_at | timestamptz | not null | row creation time |
updated_at | timestamptz | not null | mutation time |
Indexes/constraints:
product_inventory_product_id_idx- check:
total_quantity >= 0 - check:
reserved_quantity >= 0 - check:
available_quantity >= 0
Schema references:
packages/db/src/schema/inventory/product-inventory.tspackages/db/src/schema/inventory/index.ts- exported via
packages/db/src/schema/index.ts
4. Service Contracts and Mutation Semantics
Core service: InventoryService
Public methods:
reserveStock(payload)finalizeStock(payload)releaseStock(payload)adjustStock(productId, dto)syncStock(productId?)getByProductId(productId)getLowStock(threshold?)
4.1 Reserve (reserveStock)
Behavior:
- opens transaction
- row-locks inventory row (
FOR UPDATE) forproductId - if row missing: auto-creates zeroed row and returns
success: falsewithINVENTORY_CREATED_AUTOMATICALLY - when
trackInventory=true && allowOversell=false, rejects ifavailable < quantity - updates:
available = available - quantityreserved = reserved + quantity
Error behavior:
INVENTORY_INSUFFICIENT_STOCKINVENTORY_INVALID_OPERATION
4.2 Finalize (finalizeStock)
Behavior:
- opens transaction
- row-locks inventory row
- requires row to exist
- ensures
reserved >= quantityandtotal >= quantity - updates:
reserved = reserved - quantitytotal = total - quantity
Error behavior:
INVENTORY_NOT_FOUNDINVENTORY_INVALID_OPERATION
4.3 Release (releaseStock)
Behavior:
- opens transaction
- row-locks inventory row
- missing row => safe no-op success (idempotent skip)
- release quantity =
min(requested, reserved) - if release quantity is
<= 0, treat as safe no-op success - updates:
reserved = reserved - releaseQuantityavailable = available + releaseQuantity
Error behavior:
- non-business failures return
success: false - business no-op branches are logged as
[skip]
4.4 Adjust (adjustStock)
Behavior:
- opens transaction
- row-locks inventory row
- if row missing: creates row with normalized total/available values
- if row exists: applies delta to total+available
- rejects when any resulting quantity becomes negative
Error behavior:
INVENTORY_INVALID_ADJUSTMENT
4.5 Sync (syncStock)
Behavior:
- if
productIdprovided: reconcile only one row - otherwise: reconcile all rows where
available != total - reserved - updates only inconsistent rows
4.6 Low stock (getLowStock)
Predicate:
track_inventory = trueavailable_quantity <= threshold
Default threshold:
5when not provided
5. Processor Runtime (BullMQ)
Processors in apps/api/src/modules/inventory/processors/inventory.processor.ts:
| Processor class | Queue | Job names |
|---|---|---|
InventoryReserveProcessor | orders_reservations | stock.reserve |
InventoryFinalizeProcessor | orders_stock_finalize | stock.finalize |
InventoryReleaseProcessor | orders_stock_release | stock.release |
InventoryAdjustProcessor | inventory | stock.adjust, stock.sync |
Processor behavior:
- strict job-name routing via
switch (job.name) - structured logs:
[start],[success],[failure] - delegates business logic to
InventoryService - unknown jobs are skipped with warning log
6. API Layer and Guards
Admin HTTP surface:
/api/admin/inventory
Guards and authorization:
JwtAuthGuardRoleGuard- permission checks with
@Permissions(...)
Permissions used:
Inventory_READInventory_UPDATE
DTO layer:
InventoryListQueryDtoStockAdjustDtoBulkSyncDtoInventoryResponseDto
7. Order Integration Contracts
Inventory lifecycle is driven by order outbox events:
| Order event point | Inventory event | Queue |
|---|---|---|
| order create (checkout/buy-now) | stock.reserve | orders_reservations |
| payment success | stock.finalize | orders_stock_finalize |
| cancel/payment_failed/expired | stock.release | orders_stock_release |
Dispatcher ownership in current runtime includes:
ordersorders_maintenancecartorders_reservationsorders_stockorders_stock_finalizeorders_stock_releaseinventory
Known mismatch:
orders_stockis owned/registered but has no active processor consumer in current code.
8. Idempotency, Concurrency, and Integrity
Concurrency safeguards:
- all stock writes are transaction-scoped
- all lifecycle writes lock target row using
FOR UPDATE
Integrity safeguards:
- service-level guards prevent negative calculations
- DB check constraints prevent negative persisted quantities
Idempotency status:
- dedupe keys are generated at outbox emission points
- queue/job id dedupe is handled in dispatcher (
jobId) - service-level idempotency ledger table is not implemented (replay safety depends on order/outbox + current state checks)
9. Logging, Monitoring, and Audit
Current runtime logging:
- inventory service emits structured logs with order/product/quantity context
- processors emit lifecycle logs by job type
Current audit gap:
- Mongo
AuditLogwrites for inventory mutations are not yet implemented in inventory service
Related operational checks:
- outbox dispatcher + maintenance scheduler monitor queue health and failed thresholds
10. Cache and Config Status
Current cache behavior:
- inventory service/admin service currently perform direct DB reads/writes
- inventory cache TTL env knobs exist but are not wired in runtime logic
Configured but currently unused inventory env keys:
INVENTORY_STOCK_CACHE_TTL_SECONDSINVENTORY_LIST_CACHE_TTL_SECONDSINVENTORY_CONFIG_CACHE_TTL_SECONDSINVENTORY_HISTORY_CACHE_TTL_SECONDSINVENTORY_RESERVATIONS_CACHE_TTL_SECONDS
11. Error and Resilience Contracts
Inventory-specific error codes:
INVENTORY_NOT_FOUNDINVENTORY_INSUFFICIENT_STOCKINVENTORY_ALREADY_FINALIZEDINVENTORY_ALREADY_RELEASEDINVENTORY_INVALID_ADJUSTMENTINVENTORY_INVALID_OPERATION
Current usage note:
INVENTORY_ALREADY_FINALIZEDandINVENTORY_ALREADY_RELEASEDexist but are not emitted by service logic at this time.
Retry model:
- job retries are BullMQ-configurable via global/queue defaults
- processors are designed to be retry-safe where possible through state-aware mutation logic
12. Backend Diagrams
12.1 Stock lifecycle ownership
12.2 Admin mutation path
13. Release/QA Checklist
- Row-locking is present on reserve/finalize/release/adjust paths.
- Reserve rejects insufficient stock when oversell is disabled.
- Finalize decreases both
reservedandtotalsafely. - Release no-op behavior is stable for missing/no-reserved cases.
- Admin endpoints return
ResponseDtoshape and correct permission gating. - Inventory processors consume all expected inventory jobs.
-
orders_stockqueue ownership is either implemented with a consumer or removed. - Mongo inventory audit logging gap is closed before production hardening.
- Inventory env knobs are either wired into runtime or removed from env contract.
14. File Map
apps/api/src/modules/inventory/inventory.module.tsapps/api/src/modules/inventory/shared/inventory-shared.module.tsapps/api/src/modules/inventory/shared/inventory.service.tsapps/api/src/modules/inventory/admin/inventory-admin.module.tsapps/api/src/modules/inventory/admin/inventory-admin.controller.tsapps/api/src/modules/inventory/admin/inventory-admin.service.tsapps/api/src/modules/inventory/admin/dto/*apps/api/src/modules/inventory/processors/inventory-processor.module.tsapps/api/src/modules/inventory/processors/inventory.processor.tspackages/db/src/schema/inventory/product-inventory.tspackages/jobs/src/index.ts
15. Environment Variables
| Variable | Default | Current status |
|---|---|---|
INVENTORY_STOCK_CACHE_TTL_SECONDS | unset | defined in validation, not wired in service |
INVENTORY_LIST_CACHE_TTL_SECONDS | unset | defined in validation, not wired in service |
INVENTORY_CONFIG_CACHE_TTL_SECONDS | unset | defined in validation, not wired in service |
INVENTORY_HISTORY_CACHE_TTL_SECONDS | unset | defined in validation, not wired in service |
INVENTORY_RESERVATIONS_CACHE_TTL_SECONDS | unset | defined in validation, not wired in service |