docs(04): create phase plan — Inventory, Expenses, and Financial Reports
Phase 04: 5 plans in 2 waves - Wave 1: 04-01 (inventory event-ledger), 04-03 (expense tracking), 04-05 (financial reports) — parallel - Wave 2: 04-02 (asset management), 04-04 (expense reports + audit trail) — sequential - Ready for execution Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
---
|
||||
phase: 04-inventory-expenses-and-financial-reports
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: ["04-01"]
|
||||
files_modified:
|
||||
- prisma/schema.prisma
|
||||
- src/lib/services/asset-service.ts
|
||||
- src/lib/services/inventory-service.ts
|
||||
- src/app/api/inventory/items/[id]/assign/route.ts
|
||||
- src/app/api/inventory/items/[id]/return/route.ts
|
||||
- src/app/api/inventory/items/[id]/dispose/route.ts
|
||||
- src/app/api/inventory/items/[id]/history/route.ts
|
||||
- src/lib/__tests__/asset-service.test.ts
|
||||
autonomous: true
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "An asset can be assigned to a subscriber with condition tracking"
|
||||
- "An asset can be assigned to a technician for field work"
|
||||
- "An assigned asset shows full location history (chronological timeline)"
|
||||
- "Returning an asset records condition at return time"
|
||||
- "Disposal requires admin role — non-admin users get 403"
|
||||
- "Disposal creates a write-off JE (DR 5030 Equipment Expense, CR 1200 Equipment Inventory)"
|
||||
artifacts:
|
||||
- path: "src/lib/services/asset-service.ts"
|
||||
provides: "assignToSubscriber, assignToTechnician, returnAsset, disposeAsset, getAssetHistory"
|
||||
exports: ["AssetService"]
|
||||
- path: "src/lib/__tests__/asset-service.test.ts"
|
||||
provides: "Tests for assignment, return, disposal, history, authorization"
|
||||
min_lines: 80
|
||||
key_links:
|
||||
- from: "src/lib/services/asset-service.ts"
|
||||
to: "src/lib/services/inventory-service.ts"
|
||||
via: "recordMovement for ISSUED/RETURNED/DISPOSED movements"
|
||||
pattern: "InventoryService\\.recordMovement"
|
||||
- from: "src/lib/services/asset-service.ts"
|
||||
to: "src/lib/accounting/journal-entry-service.ts"
|
||||
via: "JournalEntryService.createEntry for disposal write-off"
|
||||
pattern: "JournalEntryService\\.createEntry"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Build asset lifecycle management on top of the inventory event-ledger: assign serialized items to subscribers and technicians, track condition at assignment and return, enforce admin-only disposal with write-off JE, and provide chronological location history.
|
||||
|
||||
Purpose: Enables ISP staff to track where every piece of equipment is — from warehouse to technician to subscriber and back. The location history is the key differentiator for accountability.
|
||||
Output: AssetService with assign/return/dispose/history, API routes, and tests.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:\Users\KevinAsprec\.claude/get-shit-done/workflows/execute-plan.md
|
||||
@C:\Users\KevinAsprec\.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/04-inventory-expenses-and-financial-reports/04-CONTEXT.md
|
||||
@.planning/phases/04-inventory-expenses-and-financial-reports/04-01-SUMMARY.md
|
||||
@prisma/schema.prisma
|
||||
@src/lib/services/inventory-service.ts
|
||||
@src/lib/accounting/journal-entry-service.ts
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: AssetService — assignment, return, disposal, and history</name>
|
||||
<files>
|
||||
src/lib/services/asset-service.ts
|
||||
src/app/api/inventory/items/[id]/assign/route.ts
|
||||
src/app/api/inventory/items/[id]/return/route.ts
|
||||
src/app/api/inventory/items/[id]/dispose/route.ts
|
||||
src/app/api/inventory/items/[id]/history/route.ts
|
||||
</files>
|
||||
<action>
|
||||
**AssetService** (`src/lib/services/asset-service.ts`):
|
||||
Static class building on InventoryService.recordMovement:
|
||||
|
||||
- `assignToSubscriber(tenantPrisma, tenantId, { itemId, subscriberId, condition, performedById, notes? })`:
|
||||
- Validate item exists and is SERIALIZED (batch items cannot be individually assigned to subscribers)
|
||||
- Determine current location from latest movement (must be in WAREHOUSE or with a TECHNICIAN — cannot assign from subscriber to subscriber)
|
||||
- Call InventoryService.recordMovement with movementType=ISSUED, fromLocation=current location, toLocationType=SUBSCRIBER, toLocationId=subscriberId, condition
|
||||
- Return the created movement
|
||||
|
||||
- `assignToTechnician(tenantPrisma, tenantId, { itemId, technicianUserId, condition, performedById, notes? })`:
|
||||
- Validate item is SERIALIZED
|
||||
- Current location must be WAREHOUSE
|
||||
- Call InventoryService.recordMovement with movementType=ISSUED, fromLocationType=WAREHOUSE, toLocationType=TECHNICIAN, toLocationId=technicianUserId, condition
|
||||
|
||||
- `returnAsset(tenantPrisma, tenantId, { itemId, condition, performedById, notes? })`:
|
||||
- Validate item is SERIALIZED
|
||||
- Current location must be with SUBSCRIBER or TECHNICIAN (not already in warehouse)
|
||||
- Call InventoryService.recordMovement with movementType=RETURNED, from=current location, toLocationType=WAREHOUSE, condition (captures condition at return — NEW, USED, DAMAGED, REFURBISHED)
|
||||
|
||||
- `disposeAsset(tenantPrisma, tenantId, { itemId, performedById, notes?, userRoles })`:
|
||||
- Validate item is SERIALIZED
|
||||
- Validate userRoles includes ADMIN — disposal requires admin approval per CONTEXT.md
|
||||
- Current location must be WAREHOUSE (cannot dispose from field)
|
||||
- Create write-off JE: DR 5030 Equipment Expense, CR 1200 Equipment Inventory for the item's purchaseCost. Source=SYSTEM, referenceType="StockMovement", description="Disposal write-off: {item.name} SN:{item.serialNumber}"
|
||||
- Call InventoryService.recordMovement with movementType=DISPOSED, fromLocationType=WAREHOUSE, journalEntryId from JE
|
||||
|
||||
- `getAssetHistory(tenantPrisma, itemId)`:
|
||||
- Fetch item with all movements ordered by createdAt ASC
|
||||
- Return formatted timeline: each entry has { movementType, date, fromLocation (type+name), toLocation (type+name), condition, performedBy (user name), notes }
|
||||
- Resolve location names: WAREHOUSE="Warehouse", SUBSCRIBER=subscriber name, TECHNICIAN=user name
|
||||
|
||||
- Helper: `getCurrentLocation(tenantPrisma, itemId)` — returns { locationType, locationId } from latest movement's to-fields (or null if disposed)
|
||||
|
||||
**API Routes:**
|
||||
- `POST /api/inventory/items/[id]/assign` — body: { assigneeType: "SUBSCRIBER"|"TECHNICIAN", assigneeId, condition, notes? }. ADMIN, OFFICE_STAFF.
|
||||
- `POST /api/inventory/items/[id]/return` — body: { condition, notes? }. ADMIN, OFFICE_STAFF.
|
||||
- `POST /api/inventory/items/[id]/dispose` — body: { notes? }. ADMIN only.
|
||||
- `GET /api/inventory/items/[id]/history` — returns chronological timeline. ADMIN, OFFICE_STAFF, TECHNICIAN.
|
||||
|
||||
Use withPermission() HOF and dynamic route handler pattern from prior phases.
|
||||
</action>
|
||||
<verify>npx prisma validate passes; API route files exist and export correct HTTP methods</verify>
|
||||
<done>AssetService handles subscriber/technician assignment, return with condition, admin-only disposal with write-off JE, and chronological history</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Asset service tests</name>
|
||||
<files>src/lib/__tests__/asset-service.test.ts</files>
|
||||
<action>
|
||||
Write comprehensive tests for AssetService:
|
||||
|
||||
**Setup:** createTenant, create admin user + office_staff user + technician user, create subscriber, create serialized InventoryItem, record initial RECEIVED movement (so item is in warehouse).
|
||||
|
||||
**Test cases:**
|
||||
1. Assign item to subscriber — creates ISSUED movement with SUBSCRIBER location
|
||||
2. Assign item to technician — creates ISSUED movement with TECHNICIAN location
|
||||
3. Return item from subscriber — creates RETURNED movement back to WAREHOUSE with condition
|
||||
4. Return item from technician — creates RETURNED movement back to WAREHOUSE
|
||||
5. Reject assignment of item already with a subscriber (must return first)
|
||||
6. Reject assignment of batch item to subscriber (only SERIALIZED allowed)
|
||||
7. Dispose item — ADMIN role creates DISPOSED movement + write-off JE (verify DR 5030, CR 1200)
|
||||
8. Reject disposal by non-admin (OFFICE_STAFF user gets authorization error)
|
||||
9. Reject disposal of item not in warehouse
|
||||
10. Get asset history — returns chronological timeline with resolved location names
|
||||
11. Full lifecycle: RECEIVED -> ISSUED to tech -> RETURNED -> ISSUED to subscriber -> RETURNED -> DISPOSED — history shows all 6 entries
|
||||
|
||||
**Cleanup order:** stockMovements -> inventoryItems -> journalEntryLines -> null reversesEntryId -> journalEntries -> subscribers -> servicePlans -> accountingPeriods -> accounts -> users -> tenant
|
||||
</action>
|
||||
<verify>npx jest asset-service --verbose passes all tests</verify>
|
||||
<done>All 11 test cases pass covering assignment, return, disposal authorization, history, and full lifecycle</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `npx jest asset-service --verbose` — all tests pass
|
||||
- Subscriber assignment creates correct movement record
|
||||
- Technician assignment creates correct movement record
|
||||
- Disposal enforces admin-only and creates write-off JE
|
||||
- History returns chronological timeline with location names
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Assets can be assigned to subscribers and technicians
|
||||
- Condition is captured at assignment and return
|
||||
- Disposal requires admin role and posts write-off JE (DR 5030, CR 1200)
|
||||
- Full location history available as chronological timeline
|
||||
- All tests pass
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/04-inventory-expenses-and-financial-reports/04-02-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user