Saltar al contenido principal

Stock Count Module

Inventory cycle counting system with session-based workflows, scope-driven product selection, multi-strategy task generation, snapshot-based variance detection, and idempotent posting to inventory adjustments.

Architecture

stock-count/
├── stock-count.module.ts # NestJS module
├── domain/
│ └── stock-count-repository.domain.ts # Repository port (interface)
├── application/
│ └── stock-count.service.ts # Business logic / use cases
├── infrastructure/
│ └── stock-count.repository.ts # Kysely database adapter
└── interfaces/
├── stock-count.controller.ts # HTTP controller (18 endpoints)
├── dtos/ # Request DTOs (14 files)
└── query/ # Pagination query objects (3 files)

Dependencies: InventoryAdjustmentsModule, InventoryLedgersModule

Domain Concepts

Session Lifecycle

DRAFT → ACTIVE → CLOSED → APPROVED → POSTED
│ │ │
│ │ └──→ REJECTED
│ └──────────→ REJECTED
└───────────────────→ REJECTED
StatusDescription
DRAFTSession created, scopes and tasks can be added
ACTIVECounting in progress, entries can be submitted
CLOSEDCounting complete, no more entries accepted
APPROVEDReviewed and approved for posting
POSTEDAdjustments posted to inventory
REJECTEDSession rejected at any pre-posted stage

Session Types

  • CYCLE — Partial count targeting specific products/categories/zones
  • FULL — Complete inventory count for a location

Scopes

Scopes define what to count. A session can have multiple scopes:

  • category — All products in a category
  • brand — All products of a brand
  • product — A specific product
  • zone / bin — Products in a warehouse zone or bin
  • all — All products at the location

Task Generation Strategies

  • by_zone_bin — One task per unique zone/bin combination from scopes
  • by_category — One task per category scope, round-robin assigned
  • by_assignee — One task per assignee rule

Snapshots

Point-in-time capture of on-hand quantities before counting begins. Handles:

  • Tracked inventory (batch/serial) from inventoryDetail table
  • Residual inventory (difference between inventory total and detail sum)
  • Untracked inventory (no batch/serial records)

Variance Detection

The stockCountVarianceV database view compares snapshot quantities against counted entries. Supports filtering by task and minimum variance threshold.

Posting (Idempotent)

Posting creates inventory adjustments for variances:

  1. Atomically claims the session (prevents concurrent double-posting)
  2. Groups items by direction (INCREASE / DECREASE)
  3. Creates one InventoryAdjustment per direction
  4. Generates InventoryLedger entries via buildLedgerRowsFromDetail()
  5. Uses idempotencyKey for safe retries

API Endpoints

Sessions

MethodPathDescription
POST/stock-count/sessionsCreate session (DRAFT)
GET/stock-count/sessionsList sessions (paginated)
GET/stock-count/sessions/:idGet session by ID
PATCH/stock-count/sessions/:id/startStart session (DRAFT → ACTIVE)
PATCH/stock-count/sessions/:id/closeClose session (ACTIVE → CLOSED)
PATCH/stock-count/sessions/:id/approveApprove session (CLOSED → APPROVED)
PATCH/stock-count/sessions/:id/rejectReject session
POST/stock-count/sessions/:id/snapshotCreate inventory snapshot
GET/stock-count/sessions/:id/snapshotsGet snapshots
POST/stock-count/sessions/:id/postPost adjustments to inventory
GET/stock-count/sessions/:id/varianceGet variance report
GET/stock-count/sessions/:id/postingsGet post summary
GET/stock-count/sessions/:id/ledger-entriesGet ledger entries

Scopes

MethodPathDescription
POST/stock-count/sessions/:id/scopesAdd scope
GET/stock-count/sessions/:id/scopesList scopes
DELETE/stock-count/sessions/:sessionId/scopes/:scopeIdDelete scope

Tasks

MethodPathDescription
POST/stock-count/sessions/:id/tasks/generateGenerate tasks
GET/stock-count/tasksList tasks (paginated)
GET/stock-count/tasks/:idGet task by ID
PATCH/stock-count/tasks/:idUpdate task status
PATCH/stock-count/tasks/:id/approveApprove task (RECONCILED)
PATCH/stock-count/tasks/:id/recountRequest recount (PAUSED)

Entries

MethodPathDescription
POST/stock-count/entriesSubmit entries (bulk)
GET/stock-count/entriesList entries (paginated)
GET/stock-count/entries/:idGet entry by ID
PATCH/stock-count/entries/:idUpdate entry

Example Workflow

1. POST   /stock-count/sessions                        → Create DRAFT session
2. POST /stock-count/sessions/:id/scopes → Add scope (category: Beverages)
3. POST /stock-count/sessions/:id/tasks/generate → Generate tasks (by_category)
4. PATCH /stock-count/sessions/:id/start → Start session (ACTIVE)
5. POST /stock-count/sessions/:id/snapshot → Capture current on-hand
6. POST /stock-count/entries → Counter submits counts
7. GET /stock-count/sessions/:id/variance → Review variances
8. PATCH /stock-count/tasks/:taskId/approve → Approve task
9. PATCH /stock-count/sessions/:id/close → Close session
10. PATCH /stock-count/sessions/:id/approve → Approve session
11. POST /stock-count/sessions/:id/post → Post adjustments

Database Tables

  • stockCountSession — Main session container
  • stockCountScope — What products to count
  • stockCountTask — Assigned counting tasks
  • stockCountEntry — Individual count records
  • stockCountSnapshot — Point-in-time on-hand capture
  • stockCountVarianceV — View: snapshot vs. counted variance
  • stockCountPostSummaryV — View: what adjustments will be created

Multi-Tenancy

All queries are scoped by businessId and locationId. Sessions inherit business/location context, and all child entities (scopes, tasks, entries) operate within that scope.