- Add ExpenseStatus and ExpensePaymentMethod enums
- Add Vendor model (@@unique([tenantId, name]))
- Add ExpenseCategory model with accountCode linking to COA
- Add Expense model with approval workflow and JE reference
- Add User relations: createdExpenses, approvedExpenses
- Add COA accounts 5080 Fuel/Transportation, 5085 Rent Expense (31 total)
- Seed 9 default expense categories in createTenant transaction
- Add Vendor subject to CASL types, Expense/Vendor permissions for OFFICE_STAFF
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- InventoryService: registerItem, recordMovement, getStockLevels, getItemMovements, listItems
- RECEIVED movements auto-post JE (DR 1200 Equipment Inventory, CR 2010 AP)
- Stock levels derived from movement aggregation (no mutable quantity column)
- All 5 movement types validated with type-specific rules
- API routes: POST/GET items, GET item detail, POST/GET movements, GET stock-levels
- CASL: OFFICE_STAFF gets manage Inventory permission
- Migration applied: add_inventory_models (4 enums, 2 tables)
- 13 tests: registration, all movement types, stock derivation, JE posting, history
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ItemTrackingType (SERIALIZED/BATCH), ItemCondition, MovementType, LocationType enums
- Add InventoryItem model with dual tracking (serialized by serial number, batch by quantity)
- Add StockMovement model as immutable movement ledger (no updatedAt)
- Add recordedMovements relation on User model
- Indexes on tenantId, itemType, trackingType, inventoryItemId, movementType
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 04: Inventory, Expenses, and Financial Reports
- Implementation decisions documented
- Phase boundary established
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Added checkTicketAutoResolve/checkTicketRevertToOpen to ticket-service.ts
- Created job-order-service.ts: createJobOrder, updateJobOrderStatus, updateJobOrder,
getJobOrder, listJobOrders, getMyJobOrders with VALID_JO_TRANSITIONS guard map
- PENDING->IN_PROGRESS->COMPLETED(outcomeNotes required)/CANCELLED lifecycle enforced
- OPEN ticket auto-transitions to ASSIGNED on first job order creation
- Auto-resolves ticket when all non-cancelled jobs COMPLETED
- Reverts ticket to OPEN when all jobs CANCELLED
- Created POST /api/tickets/[id]/job-orders, GET/PUT /api/job-orders/[id],
POST /api/job-orders/[id]/status, GET /api/job-orders (with TECHNICIAN filter)
- 17 integration tests: lifecycle, auto-resolve, revert-to-open, partial completion,
technician self-service, cross-tenant isolation — all green
- Added JobOrderStatus enum (PENDING, IN_PROGRESS, COMPLETED, CANCELLED)
- Added JobOrder model with ticket FK, assignedTo/createdBy user FKs
- Added orderNumber (JO-NNNN), scheduledDate, timestamps, outcomeNotes
- Added jobOrders reverse relation on Ticket model
- Added assignedJobOrders/createdJobOrders reverse relations on User model
- Applied migration: 20260304235859_add_job_orders
- Added jobOrder to TENANT_SCOPED_MODELS with full 12-operation extension block
- Added 1030 Cash in Transit to ISP_CHART_OF_ACCOUNTS (between 1020 and 1100)
- Added CollectionStatus (COMPLETED/VOIDED) and RemittanceStatus (PENDING/VERIFIED) enums
- Added Collection model with FIFO allocations, zone-scoped collector, JE link, void fields
- Added CollectionAllocation model linking collections to invoices
- Added Remittance model with two-party verification, variance field, JE link
- Added reverse relations on User (collections, remittances) and Subscriber (collections)
- Applied migration: add-collections-remittances
- Extended TENANT_SCOPED_MODELS with collection, collectionAllocation, remittance
- Added full 12-operation extension blocks for all three new models
Tasks completed: 2/2
- Task 1: Zone schema, migration, and tenant scoping
- Task 2: Zone service, API routes, and integration tests
SUMMARY: .planning/phases/03-operational-modules/03-01-SUMMARY.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Zone model (id, tenantId, name, description, isActive)
- Add ZoneAssignment model (collector-to-zone join table)
- Replace Subscriber.zone String? with Subscriber.zoneId FK to Zone
- Add Zone + ZoneAssignment to TENANT_SCOPED_MODELS with full operation blocks
- Add "Zone" to AppSubjects in types.ts
- Grant OFFICE_STAFF manage Zone, COLLECTOR read Zone in permissions.ts
- Migration 20260305000000_add_zones applied to DB
Phase 03: Operational Modules
- Standard stack identified (no new dependencies)
- Architecture patterns documented (10 patterns from codebase)
- Pitfalls catalogued (8 pitfalls from direct code inspection)
Invoices were created with default DRAFT status, but PaymentService queries
for SENT/PARTIAL/OVERDUE. This broke the generate → pay flow. Now invoices
are immediately SENT with issuedAt timestamp on generation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix shouldBillToday() month-wrapping logic for PREPAID lead days
- POST /api/billing/generate — triggers monthly invoice generation cycle
- GET /api/invoices — paginated list with status/subscriber/date filters
- GET /api/invoices/[id] — invoice detail with lines and subscriber
- POST /api/invoices/[id]/void — void with JE reversal
Test coverage (38 tests):
- computeBillingPeriod pure function
- shouldBillToday: postpaid, prepaid, and month-wrapping edge case
- Invoice number sequencing per tenant/year
- Invoice generation: amounts, InvoiceLine, period dates
- Journal entries: DR AR (1100), CR Revenue (4010), balanced
- Idempotency: duplicate prevention via unique(subscriberId, periodStart)
- Credit auto-application: full, partial, zero credit, JE (DR 1150, CR 1100)
- Billing cycle: active-only, suspended/cancelled excluded, prepaid lead days
- Overdue detection: bulk update of DRAFT/SENT/PARTIAL past due date
- Void: JE reversal, already-voided guard, PAID guard
- getInvoice, listInvoices, status filtering
- Tenant isolation: Tenant B cannot see Tenant A invoices
- GET/POST /api/accounting/journal-entries (list + create manual entry)
- GET /api/accounting/journal-entries/[id] (single entry with lines)
- POST /api/accounting/journal-entries/[id]/approve (maker-checker approval)
- POST /api/accounting/journal-entries/[id]/reverse (create reversing entry)
- GET /api/accounting/accounts/[id]/balance (derived balance, never stored)
- All routes use closure pattern over withPermission HOF (same as periods/close)
- Add startDate parameter to getAccountBalance for date range queries
- 36 integration tests: balance enforcement, closed period, immutability,
reversals, maker-checker, entry numbering, account balance derivation,
trial balance self-verification
- Total test suite: 198 tests all passing
- Add JournalEntryStatus and JournalEntrySource enums to Prisma schema
- Add JournalEntry model with maker-checker fields, self-referential reversal relation, and audit timestamps
- Add JournalEntryLine model with debit/credit Decimal(15,2) fields
- Update Account model with journalEntryLines back-relation
- Update User model with createdJournalEntries and approvedJournalEntries back-relations
- Run migration: 20260304150817_add_journal_entry_models
- Add journalEntry and journalEntryLine to TENANT_SCOPED_MODELS in prisma-tenant.ts
- Create JournalEntryService with createEntry, approveEntry, reverseEntry, getAccountBalance, getTrialBalance
- Enforce debit=credit balance using integer cents comparison (avoids float issues)
- SYSTEM source entries auto-posted; MANUAL entries start as DRAFT for maker-checker
- Add AccountType, NormalBalance, PeriodStatus enums to schema
- Add Account model with tenant scoping, code/name/type/normalBalance/parentId
- Add AccountingPeriod model with year/month/status/closedAt/closedById
- Create ISP_CHART_OF_ACCOUNTS with 28 accounts across all 5 types (1000-5000 ranges)
- Create accounting-period.ts with getOpenPeriod, closePeriod, isDateInClosedPeriod
- Extend TENANT_SCOPED_MODELS with account and accountingPeriod
- Add full query extension blocks for account and accountingPeriod in withTenantContext
- Run migration: 20260304144656_add_accounting_models
Phase 02: Subscriber and Billing Core
- Implementation decisions documented
- Phase boundary established
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- withSuperAdmin() HOF: checks isSuperAdmin from session, returns 401/403
- GET /api/admin/tenants: lists all tenants with userCount, subscriberCount
- GET /api/admin/tenants/[id]: single tenant detail with users list
- POST /api/admin/tenants/[id]/suspend: suspend/activate with 7-day grace
- prisma/seed.ts: add Test ISP 2 tenant and admin2@demo.com for isolation tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>