Tasks completed: 2/2 - Task 1: Schema — InventoryItem, StockMovement models and enums - Task 2: InventoryService, API routes, migration, and tests (13 passing) SUMMARY: .planning/phases/04-inventory-expenses-and-financial-reports/04-01-SUMMARY.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5.4 KiB
5.4 KiB
phase, plan, subsystem, tags, dependency-graph, tech-stack, key-files, decisions, metrics
| phase | plan | subsystem | tags | dependency-graph | tech-stack | key-files | decisions | metrics | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04-inventory-expenses-and-financial-reports | 01 | inventory |
|
|
|
|
|
|
Phase 4 Plan 1: Inventory Event-Ledger Foundation Summary
Immutable stock movement ledger with dual tracking (serialized by serial number, batch by quantity), derived stock level computation, and automatic JE posting for receiving movements.
What Was Built
Schema (Task 1)
- 4 new enums:
ItemTrackingType(SERIALIZED/BATCH),ItemCondition(NEW/REFURBISHED/USED/DAMAGED),MovementType(RECEIVED/ISSUED/RETURNED/DISPOSED/TRANSFERRED),LocationType(WAREHOUSE/TECHNICIAN/SUBSCRIBER) InventoryItemmodel: dual tracking, serial number uniqueness per tenant (nullable unique), purchaseCost/purchaseDate/warrantyExpiryStockMovementmodel: immutable (no updatedAt), journalEntryId for RECEIVED, performedBy relation to User- Indexes on tenantId, itemType, trackingType, inventoryItemId, movementType
InventoryService (Task 2)
registerItem: creates items, validates SERIALIZED requires serial number, BATCH rejects serial numberrecordMovement: validates per-movement-type rules, enforces qty=1 for SERIALIZED, auto-posts JE for RECEIVED (DR 1200 Equipment Inventory, CR 2010 Accounts Payable)getStockLevels: derives stock from movement aggregation (RECEIVED/RETURNED add, ISSUED/TRANSFERRED remove+add, DISPOSED remove), filters zero-quantity entriesgetItemMovements: chronological history with performedBy detailslistItems: filterable by itemType, trackingType, isActivegetItem: single item with recent movements
API Routes
POST /api/inventory/items— register new item (ADMIN, OFFICE_STAFF)GET /api/inventory/items— list items with filtersGET /api/inventory/items/[id]— item detailPOST /api/inventory/items/[id]/movements— record movementGET /api/inventory/items/[id]/movements— movement historyGET /api/inventory/stock-levels— derived stock levels
CASL Permissions
- OFFICE_STAFF:
can("manage", "Inventory")added - TECHNICIAN: existing
can("read", "Inventory")preserved
Tests (13 passing)
- Registration: serialized with serial, batch without, reject serialized without serial, reject batch with serial
- Movements: RECEIVED with JE (DR 1200, CR 2010), ISSUED, RETURNED, DISPOSED, reject SERIALIZED qty>1
- Stock derivation: batch receive 10 issue 3 = 7, serialized latest location
- History: chronological order
- List: itemType filter
Decisions Made
| Decision | Rationale |
|---|---|
| Only RECEIVED movements create JEs | Other movement types (ISSUED, RETURNED, etc.) are internal transfers that don't affect AP. Asset reclassification JEs for ISSUED/DISPOSED will be added in 04-02 if needed |
| CASL subject "Inventory" not "InventoryItem" | Reuses existing subject type from types.ts. OFFICE_STAFF gets full manage access |
| Stock levels computed in JS not SQL | Follows existing pattern (collector balances, outstanding reports). Acceptable for ISP scale |
Deviations from Plan
Auto-fixed Issues
1. [Rule 2 - Missing Critical] OFFICE_STAFF inventory CASL permission
- Found during: Task 2 (API route creation)
- Issue: OFFICE_STAFF had no Inventory permissions in CASL, would fail withPermission() checks
- Fix: Added
can("manage", "Inventory")to OFFICE_STAFF role - Files modified: src/lib/casl/permissions.ts
- Commit:
a742f70
2. [Rule 3 - Blocking] CASL subject mismatch
- Found during: Task 2 (API route creation)
- Issue: Plan specified "InventoryItem" as withPermission subject but types.ts only has "Inventory"
- Fix: Used "Inventory" subject in all API routes
- Files modified: All API route files
- Commit:
a742f70
Next Phase Readiness
04-02 (Asset Lifecycle) can build on:
- InventoryItem and StockMovement models are stable
- InventoryService.recordMovement handles all 5 movement types
- Stock level derivation ready for assignment tracking (ISSUED to subscriber)
- JE posting pattern established (can extend for disposal write-offs)
Cleanup Order for Tests
stockMovements -> inventoryItems -> journalEntryLines -> null reversesEntryId -> journalEntries -> accountingPeriods -> accounts -> users -> tenant