Phase 03: Operational Modules - 5 plans in 3 waves - Wave 1: 03-01 (zones), 03-03 (tickets) — parallel - Wave 2: 03-02 (collector collections), 03-04 (job orders) — parallel - Wave 3: 03-05 (technician compensation) - Ready for execution Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
285 lines
16 KiB
Markdown
285 lines
16 KiB
Markdown
---
|
|
phase: 03-operational-modules
|
|
plan: "02"
|
|
type: execute
|
|
wave: 2
|
|
depends_on: ["03-01"]
|
|
files_modified:
|
|
- prisma/schema.prisma
|
|
- src/lib/prisma-tenant.ts
|
|
- src/lib/accounting/chart-of-accounts.ts
|
|
- src/lib/services/collector-service.ts
|
|
- src/lib/services/remittance-service.ts
|
|
- src/lib/services/collection-report-service.ts
|
|
- src/app/api/collections/route.ts
|
|
- src/app/api/collections/[id]/route.ts
|
|
- src/app/api/collections/[id]/void/route.ts
|
|
- src/app/api/remittances/route.ts
|
|
- src/app/api/remittances/[id]/verify/route.ts
|
|
- src/app/api/reports/collections/route.ts
|
|
- src/lib/__tests__/collector-service.test.ts
|
|
- src/lib/__tests__/remittance-service.test.ts
|
|
autonomous: true
|
|
|
|
must_haves:
|
|
truths:
|
|
- "Collector can log a cash collection against a subscriber (lump sum, FIFO allocation)"
|
|
- "Collection creates JE: DR 1030 Cash in Transit, CR 1100 AR"
|
|
- "Office staff can verify a remittance by entering their counted total"
|
|
- "Remittance verification creates JE: DR 1010 Cash on Hand, CR 1030 Cash in Transit"
|
|
- "Variance between collector total and staff count is recorded but does NOT block remittance"
|
|
- "Collector can only collect from subscribers in their assigned zones"
|
|
- "Daily collection summary shows per-collector totals (collected, remitted, variance)"
|
|
- "Collector balances are derived from transactions, never stored"
|
|
artifacts:
|
|
- path: "prisma/schema.prisma"
|
|
provides: "Collection and Remittance models"
|
|
contains: "model Collection"
|
|
- path: "src/lib/accounting/chart-of-accounts.ts"
|
|
provides: "Account 1030 Cash in Transit"
|
|
contains: "1030"
|
|
- path: "src/lib/services/collector-service.ts"
|
|
provides: "Collection recording with FIFO allocation and zone enforcement"
|
|
exports: ["recordCollection", "voidCollection", "getCollectionHistory"]
|
|
- path: "src/lib/services/remittance-service.ts"
|
|
provides: "Remittance creation and verification with JE"
|
|
exports: ["createRemittance", "verifyRemittance"]
|
|
- path: "src/lib/services/collection-report-service.ts"
|
|
provides: "Daily collection summary report"
|
|
exports: ["getDailyCollectionSummary"]
|
|
- path: "src/lib/__tests__/collector-service.test.ts"
|
|
provides: "Collection tests including FIFO, zone enforcement, JE verification"
|
|
min_lines: 150
|
|
- path: "src/lib/__tests__/remittance-service.test.ts"
|
|
provides: "Remittance tests including variance, JE verification"
|
|
min_lines: 100
|
|
key_links:
|
|
- from: "src/lib/services/collector-service.ts"
|
|
to: "src/lib/accounting/journal-entry-service.ts"
|
|
via: "JournalEntryService.createEntry for collection JE (DR 1030, CR 1100)"
|
|
pattern: "JournalEntryService\\.createEntry"
|
|
- from: "src/lib/services/remittance-service.ts"
|
|
to: "src/lib/accounting/journal-entry-service.ts"
|
|
via: "JournalEntryService.createEntry for remittance verification JE (DR 1010, CR 1030)"
|
|
pattern: "JournalEntryService\\.createEntry"
|
|
- from: "src/lib/services/collector-service.ts"
|
|
to: "src/lib/services/zone-service.ts"
|
|
via: "zone scoping — validates subscriber is in collector's zones before collection"
|
|
pattern: "zone"
|
|
---
|
|
|
|
<objective>
|
|
Create the collector field collection and remittance system: Collection model (lump sum with FIFO allocation), Remittance model (two-party verification), accounting journal entries for both events, zone-scoped collection enforcement, daily collection summary report.
|
|
|
|
Purpose: This is the core cash flow tracking for field operations — collectors log what they receive, management verifies what they remit, and every peso is traceable through the double-entry ledger.
|
|
Output: Collection/Remittance models, 3 service files, 6 API routes, integration 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/03-operational-modules/03-CONTEXT.md
|
|
@.planning/phases/03-operational-modules/03-RESEARCH.md
|
|
@.planning/phases/03-operational-modules/03-01-SUMMARY.md
|
|
@prisma/schema.prisma
|
|
@src/lib/prisma-tenant.ts
|
|
@src/lib/accounting/chart-of-accounts.ts
|
|
@src/lib/accounting/journal-entry-service.ts
|
|
@src/lib/services/payment-service.ts (FIFO pattern reference — adapt for 1030 account)
|
|
@src/lib/services/zone-service.ts (zone scoping reference)
|
|
@src/lib/__tests__/payment.test.ts (test pattern reference)
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Collection/Remittance schema, 1030 account, migration, tenant scoping</name>
|
|
<files>
|
|
prisma/schema.prisma
|
|
src/lib/prisma-tenant.ts
|
|
src/lib/accounting/chart-of-accounts.ts
|
|
</files>
|
|
<action>
|
|
1. Add 1030 Cash in Transit to ISP_CHART_OF_ACCOUNTS in chart-of-accounts.ts:
|
|
```
|
|
{ code: "1030", name: "Cash in Transit", accountType: "ASSET", normalBalance: "DEBIT", parentCode: "1000" }
|
|
```
|
|
Insert AFTER 1020 Cash in Bank and BEFORE 1100 Accounts Receivable to maintain code order.
|
|
|
|
2. Add enums to schema.prisma:
|
|
- `enum CollectionStatus { COMPLETED VOIDED }`
|
|
- `enum RemittanceStatus { PENDING VERIFIED }`
|
|
|
|
3. Add Collection model:
|
|
- id (uuid), tenantId
|
|
- collectorId (String, FK to User — the collector who made the collection)
|
|
- subscriberId (String, FK to Subscriber)
|
|
- amount (Decimal @db.Decimal(10,2)) — lump sum received from subscriber
|
|
- collectionDate (DateTime) — when collected in the field
|
|
- status (CollectionStatus, default COMPLETED)
|
|
- notes (String?)
|
|
- journalEntryId (String?) — JE created on collection (DR 1030, CR 1100)
|
|
- voidedAt (DateTime?), voidedById (String?), voidJournalEntryId (String?)
|
|
- createdAt, updatedAt
|
|
- Relations: collector -> User, subscriber -> Subscriber
|
|
- Add PaymentAllocation relation: collectionAllocations PaymentAllocation[] (reuse PaymentAllocation or create CollectionAllocation — prefer creating CollectionAllocation to avoid polluting PaymentAllocation with nullable fields)
|
|
- Actually, create CollectionAllocation as a separate model (same structure as PaymentAllocation but for collections):
|
|
id, tenantId, collectionId, invoiceId, amount, createdAt, @@index([collectionId]), @@index([invoiceId]), @@index([tenantId])
|
|
- @@unique([tenantId, collectorId, subscriberId, collectionDate]) — prevent double-recording same subscriber same day same collector
|
|
- @@index([tenantId]), @@index([tenantId, collectorId]), @@index([tenantId, collectionDate])
|
|
|
|
4. Add Remittance model:
|
|
- id (uuid), tenantId
|
|
- collectorId (String, FK to User)
|
|
- remittanceDate (DateTime) — the date of remittance
|
|
- collectedTotal (Decimal @db.Decimal(10,2)) — sum of collector's collections for the period (derived at creation time, stored for audit trail)
|
|
- verifiedTotal (Decimal? @db.Decimal(10,2)) — amount counted by office staff (null until verified)
|
|
- variance (Decimal? @db.Decimal(10,2)) — collectedTotal - verifiedTotal (null until verified)
|
|
- status (RemittanceStatus, default PENDING)
|
|
- verifiedById (String?, FK to User)
|
|
- verifiedAt (DateTime?)
|
|
- journalEntryId (String?) — JE created on verification (DR 1010, CR 1030)
|
|
- notes (String?)
|
|
- createdAt, updatedAt
|
|
- Relations: collector -> User, verifiedBy -> User
|
|
- @@index([tenantId]), @@index([tenantId, collectorId]), @@index([tenantId, remittanceDate])
|
|
|
|
5. Add reverse relations on User: `collections Collection[]`, `verifiedRemittances Remittance[]`
|
|
Add reverse relation on Subscriber: `collections Collection[]`
|
|
|
|
6. Run `npx prisma migrate dev --name add-collections-remittances`
|
|
|
|
7. Add Collection, CollectionAllocation, and Remittance to TENANT_SCOPED_MODELS in prisma-tenant.ts with FULL 12-operation extension blocks.
|
|
</action>
|
|
<verify>
|
|
- `npx prisma migrate dev` succeeds
|
|
- `npx tsc --noEmit` passes
|
|
- Grep chart-of-accounts.ts confirms "1030" exists
|
|
- Grep prisma-tenant.ts confirms "collection", "collectionAllocation", "remittance" in TENANT_SCOPED_MODELS
|
|
</verify>
|
|
<done>Collection, CollectionAllocation, and Remittance models exist, 1030 Cash in Transit added to COA, migration applied, tenant scoping configured.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Collector service, remittance service, report service, APIs, and tests</name>
|
|
<files>
|
|
src/lib/services/collector-service.ts
|
|
src/lib/services/remittance-service.ts
|
|
src/lib/services/collection-report-service.ts
|
|
src/app/api/collections/route.ts
|
|
src/app/api/collections/[id]/route.ts
|
|
src/app/api/collections/[id]/void/route.ts
|
|
src/app/api/remittances/route.ts
|
|
src/app/api/remittances/[id]/verify/route.ts
|
|
src/app/api/reports/collections/route.ts
|
|
src/lib/__tests__/collector-service.test.ts
|
|
src/lib/__tests__/remittance-service.test.ts
|
|
</files>
|
|
<action>
|
|
1. Create src/lib/services/collector-service.ts:
|
|
- `recordCollection(tenantPrisma, tenantId, { collectorId, subscriberId, amount, collectionDate, notes? })`:
|
|
a. ZONE ENFORCEMENT: Query collector's zone assignments. Query subscriber's zoneId. If subscriber's zone is NOT in collector's assigned zones, throw error "Subscriber not in your assigned zones" (security boundary).
|
|
b. FIFO allocation: Query subscriber's unpaid invoices ordered by dueDate ASC (same pattern as PaymentService). Allocate lump sum amount across invoices. Create CollectionAllocation records for each invoice touched. Update invoice.amountPaid and invoice.status atomically.
|
|
c. Create JE via JournalEntryService.createEntry: DR 1030 Cash in Transit, CR 1100 Accounts Receivable. Look up accounts by code "1030" and "1100". Use referenceType "Collection", referenceId = collection.id.
|
|
d. Handle overpayment: any excess after all invoices paid goes to subscriber.creditBalance (same pattern as PaymentService).
|
|
e. All inside a $transaction. Pass tenantId explicitly in all create/update data.
|
|
f. Return collection record with allocations.
|
|
- `voidCollection(tenantPrisma, tenantId, collectionId, voidedById)`:
|
|
- Reverse the JE via JournalEntryService (same pattern as voidPayment)
|
|
- Reverse invoice.amountPaid and status for each allocation
|
|
- Reverse subscriber.creditBalance if overpayment was applied
|
|
- Mark collection as VOIDED
|
|
- `getCollectionHistory(tenantPrisma, { collectorId?, subscriberId?, dateFrom?, dateTo?, page?, limit? })` — filtered list
|
|
|
|
2. Create src/lib/services/remittance-service.ts:
|
|
- `createRemittance(tenantPrisma, tenantId, { collectorId, remittanceDate })`:
|
|
- Calculate collectedTotal: sum of all COMPLETED collections by this collector for this date (or unremitted collections if date-based grouping is complex — use all COMPLETED collections by collector where no remittance has been created yet). Simpler approach: sum COMPLETED collections by collectorId where collectionDate = remittanceDate.
|
|
- Create Remittance with status PENDING, collectedTotal set.
|
|
- `verifyRemittance(tenantPrisma, tenantId, { remittanceId, verifiedTotal, verifiedById, notes? })`:
|
|
- Load remittance, validate status is PENDING
|
|
- Calculate variance: collectedTotal - verifiedTotal (positive = collector short, negative = collector over)
|
|
- Create JE via JournalEntryService.createEntry: DR 1010 Cash on Hand (verifiedTotal), CR 1030 Cash in Transit (verifiedTotal). Note: JE is for verified amount, NOT collected total. Variance does NOT block.
|
|
- Update remittance: verifiedTotal, variance, verifiedById, verifiedAt, journalEntryId, status = VERIFIED
|
|
- `listRemittances(tenantPrisma, { collectorId?, status?, dateFrom?, dateTo? })` — filtered list
|
|
|
|
3. Create src/lib/services/collection-report-service.ts:
|
|
- `getDailyCollectionSummary(tenantPrisma, { date, collectorId? })`:
|
|
- For each collector active on the given date:
|
|
- collectedTotal: sum of COMPLETED collections on that date
|
|
- remittedTotal: sum of VERIFIED remittance verifiedTotal on that date
|
|
- variance: collectedTotal - remittedTotal
|
|
- collectionCount: count of collections
|
|
- Return array of { collectorId, collectorName, collectedTotal, remittedTotal, variance, collectionCount }
|
|
- `getCollectorCollectionDetail(tenantPrisma, { collectorId, date })`:
|
|
- Per-subscriber breakdown for drill-down: subscriber name, amount, collection time
|
|
|
|
4. Create API routes:
|
|
- POST /api/collections: withPermission("create", "Payment") -> recordCollection (collectors have "create" Payment permission)
|
|
- GET /api/collections: withPermission("read", "Payment") -> getCollectionHistory with query param filters
|
|
- GET /api/collections/[id]: withPermission("read", "Payment") -> single collection detail
|
|
- POST /api/collections/[id]/void: withPermission("manage", "Payment") -> voidCollection (admin/staff only)
|
|
- POST /api/remittances: withPermission("manage", "Payment") -> createRemittance (staff initiates)
|
|
- POST /api/remittances/[id]/verify: withPermission("manage", "Payment") -> verifyRemittance (staff verifies)
|
|
- GET /api/reports/collections: withPermission("read", "Report") -> getDailyCollectionSummary with date query param
|
|
|
|
5. Create src/lib/__tests__/collector-service.test.ts:
|
|
- Setup: create tenant (seeds COA including 1030 now), admin user, collector user, create 2 zones, assign collector to zone 1, create service plan, create 2 subscribers in zone 1, create 1 subscriber in zone 2, generate invoices for subscribers
|
|
- Test: recordCollection succeeds for subscriber in collector's zone
|
|
- Test: recordCollection FIFO allocates to oldest invoice first
|
|
- Test: recordCollection throws for subscriber NOT in collector's zones
|
|
- Test: collection creates JE with DR 1030, CR 1100 (verify journal entry lines)
|
|
- Test: overpayment adds to subscriber.creditBalance
|
|
- Test: voidCollection reverses JE and invoice allocations
|
|
- Test: cross-tenant isolation
|
|
- Cleanup: collectionAllocations -> collections -> invoiceLines -> invoices -> journalEntryLines -> null reversesEntryId -> journalEntries -> zoneAssignments -> subscribers -> servicePlans -> zones -> tenantSettings -> accountingPeriods -> accounts -> ticketCategories -> users -> tenant
|
|
|
|
6. Create src/lib/__tests__/remittance-service.test.ts:
|
|
- Setup: reuse similar setup, record some collections first
|
|
- Test: createRemittance calculates correct collectedTotal
|
|
- Test: verifyRemittance with matching amount (zero variance)
|
|
- Test: verifyRemittance with different amount (non-zero variance, still completes)
|
|
- Test: verification creates JE with DR 1010, CR 1030 for verifiedTotal
|
|
- Test: cannot verify already-verified remittance
|
|
- Cleanup: remittances -> collectionAllocations -> collections -> (same chain as above)
|
|
</action>
|
|
<verify>
|
|
- `npx vitest run src/lib/__tests__/collector-service.test.ts` — all tests pass
|
|
- `npx vitest run src/lib/__tests__/remittance-service.test.ts` — all tests pass
|
|
- `npx tsc --noEmit` passes
|
|
</verify>
|
|
<done>Collectors can record zone-scoped collections with FIFO allocation, collections create correct JEs (DR 1030 CR 1100), remittance verification creates correct JEs (DR 1010 CR 1030), variance recorded but non-blocking, daily summary report works, all tests pass.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `npx prisma migrate dev` succeeds
|
|
- `npx tsc --noEmit` passes
|
|
- `npx vitest run src/lib/__tests__/collector-service.test.ts` — all green
|
|
- `npx vitest run src/lib/__tests__/remittance-service.test.ts` — all green
|
|
- Collection JEs use account 1030 (NOT 1010) — verified by test assertions on JE lines
|
|
- Remittance verification JEs use DR 1010, CR 1030
|
|
- Variance does not block remittance completion
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- Collection model with FIFO allocation (same pattern as PaymentService but with 1030)
|
|
- Zone enforcement on collections (collector can only collect from their zones)
|
|
- Remittance two-party verification (collector collects, staff counts and verifies)
|
|
- Correct accounting chain: Collection DR 1030/CR 1100, Remittance DR 1010/CR 1030
|
|
- Variance recorded but non-blocking
|
|
- Daily collection summary report with per-collector totals
|
|
- Collector balances derived (no stored balance field)
|
|
- All integration tests pass
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/03-operational-modules/03-02-SUMMARY.md`
|
|
</output>
|