- Add Collection and Remittance to AppSubjects union type
- Grant OFFICE_STAFF manage:Collection and manage:Remittance
- Grant COLLECTOR create/read:Collection and create/read:Remittance
- Update all 8 collection/remittance route handlers from Subscriber to
their dedicated CASL subjects (Collection or Remittance)
- Update JSDoc comments in route files to reflect new subject names
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add expense, expenseCategory, inventoryItem, stockMovement, vendor,
ticketComment to TENANT_SCOPED_MODELS (22 -> 28 models)
- Add complete $extends query blocks for each model with all 14 operations
(findMany, findFirst, findUnique, create, update, delete, etc.)
- Closes P0 tenant isolation gap where these models bypassed app-layer scoping
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Workflow 4: inventory receiving with balanced JE (DR 1200 / CR 2010)
- Workflow 4: expense recording with auto-post JE (DR 5040 / CR 1010)
- Workflow 4: trial balance verification after inventory/expense txns
- Workflow 5: portal ticket creation via createPortalTicket service
- Workflow 5: staff queue visibility and shadow user verification
- 21 total E2E tests (up from 16), all passing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks completed: 1/1
- API RBAC integration test suite (43 tests)
SUMMARY: .planning/phases/05-visibility-and-client-portal/05-04-SUMMARY.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Subscriber registration -> invoice generation -> payment recording
- Full payment marks invoice PAID with correct JE (DR Cash, CR AR)
- Partial payment marks invoice PARTIAL with balanced JE
- Trial balance verified balanced after all transactions
- Dashboard reflects billing activity (revenue + subscriber metrics)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks completed: 2/2
- Task 1: TicketComment model and portal ticket service
- Task 2: Portal ticket API routes, payment scaffold, and tests
SUMMARY: .planning/phases/05-visibility-and-client-portal/05-03-SUMMARY.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- GET/POST /api/portal/tickets for listing and creating tickets
- GET /api/portal/tickets/[id] for ticket detail with conversation
- GET/POST /api/portal/tickets/[id]/comments for conversation threads
- GET /api/portal/payments/coming-soon returns outstanding balance and payment instructions
- 6 integration tests: SUBSCRIBER source, staff visibility, isolation, comments, closed rejection, thread ordering
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add TicketComment model for conversation threads (append-only)
- Add paymentInstructions field to TenantSettings
- Add ticketComments relation to User model
- Create portal-ticket-service with ensurePortalUser shadow User pattern
- Implements createPortalTicket, listPortalTickets, getPortalTicket, addTicketComment
- Portal ticket creation delegates to existing createTicket with source=SUBSCRIBER
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks completed: 2/2
- Portal authentication via subscriber account number
- PortalService, API routes, and integration tests (5 tests)
SUMMARY: .planning/phases/05-visibility-and-client-portal/05-02-SUMMARY.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create PortalService with getPortalAccount, getPortalInvoices, getPortalPayments
- All queries scoped to single subscriberId (no cross-subscriber access)
- Create withPortalAuth middleware (validates subscriberId in session)
- Add GET /api/portal/account (subscriber profile + plan details)
- Add GET /api/portal/invoices (paginated, includes line items)
- Add GET /api/portal/payments (paginated payment history)
- Add 5 integration tests: account details, pagination, isolation, line items
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks completed: 2/2
- DashboardService with 6 metric aggregation methods
- GET /api/dashboard endpoint and 6 integration tests
SUMMARY: .planning/phases/05-visibility-and-client-portal/05-01-SUMMARY.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- GET /api/dashboard with read Report permission (ADMIN, OFFICE_STAFF)
- Optional startDate/endDate query params for cash flow date range
- 6 integration tests verifying all metric methods against seeded data
- Tests cover revenue, overdue, subscriber status, cash flow, collectors
- Comprehensive cleanup order for dashboard test data
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add passwordHash field to Subscriber model (nullable for existing subscribers)
- Add portal-credentials NextAuth provider (accountNumber + password + tenantId)
- Persist subscriberId in JWT token and session for portal user identification
- Extend next-auth types with optional subscriberId on Session, User, and JWT
- Exclude /portal/login and /api/portal/auth from middleware auth requirement
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- getRevenueMetrics: today + month revenue from COMPLETED payments
- getOverdueMetrics: overdue subscriber count + outstanding total
- getSubscriberMetrics: active/suspended/cancelled status breakdown
- getCashFlowSummary: money in vs out from POSTED JE lines
- getCollectorSummary: today's collections + unverified remittances
- getDashboardSummary: aggregator calling all five methods in parallel
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 05: Visibility and Client Portal
- Implementation decisions documented
- Phase boundary established
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks completed: 2/2
- ExpenseReportService and AuditTrailService with API routes
- 9 passing integration tests
SUMMARY: .planning/phases/04-inventory-expenses-and-financial-reports/04-04-SUMMARY.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Expense by category: correct totals, excludes DRAFT/VOIDED
- Expense by vendor: correct totals, No Vendor bucket
- Expense summary: combined view with grand total
- Date range filtering works
- JE audit trail: createdBy, source, referenceType for expense JE
- Audit trail for entity: original + void reversal entries
- Cross-source audit: Payment JE referenceType correctly returned
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ExpenseReportService: getExpensesByCategory, getExpensesByVendor, getExpenseSummary
- AuditTrailService: getJournalEntryAudit, getAuditTrailForEntity (ACCT-08)
- GET /api/reports/expenses -- expense summary by category
- GET /api/reports/expenses/by-vendor -- expense summary by vendor
- GET /api/accounting/journal-entries/[id]/audit -- JE audit trail
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks completed: 2/2
- AssetService with assign, return, dispose, history
- 11 integration tests all passing
SUMMARY: .planning/phases/04-inventory-expenses-and-financial-reports/04-02-SUMMARY.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Assign to subscriber/technician creates correct ISSUED movements
- Return from subscriber/technician creates RETURNED with condition
- Rejects reassignment without return, rejects batch items
- Admin disposal creates write-off JE (DR 5030, CR 1200)
- Non-admin disposal rejected, field disposal rejected
- History returns chronological timeline with resolved names
- Full lifecycle: RECEIVED->ISSUED->RETURNED->ISSUED->RETURNED->DISPOSED
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- AssetService: assignToSubscriber, assignToTechnician, returnAsset, disposeAsset, getAssetHistory
- getCurrentLocation helper derives location from latest movement
- Disposal validates ADMIN role and creates write-off JE (DR 5030, CR 1200)
- API routes: POST assign, POST return, POST dispose, GET history
- Location history resolves subscriber/technician/warehouse names
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Trial Balance: debits = credits (isBalanced), per-account balances, asOfDate filter
- Income Statement: revenue/expense sections, net income = 600, header exclusion, date range
- Balance Sheet: A = L + E (isBalanced), cash = 600, net income in equity, date filter
- Drill-down: running balance, entry metadata (entryNumber, referenceType)
- Edge cases: empty tenant balanced, leaf-only accounts across all reports
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- GET /api/reports/trial-balance with optional asOfDate filter
- GET /api/reports/income-statement with required startDate/endDate
- GET /api/reports/balance-sheet with required asOfDate
- GET /api/reports/accounts/[id]/entries drill-down with optional date range
- All routes use withPermission("read", "Report") for ADMIN access
- FinancialReportService already existed with all 4 methods
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- VendorService: CRUD with unique name validation per tenant
- ExpenseService: create, approve, post, void with full JE integration
- Auto-generated EXP-NNNN expense numbers per tenant
- JE posting: DR category expense account, CR cash (1010) or bank (1020)
- Optional approval workflow: requireApproval flag controls DRAFT-only vs immediate post
- Void reverses JE via JournalEntryService.reverseEntry
- Custom category creation with COA account validation
- System categories protected from deletion
- API routes: expenses CRUD, approve, categories CRUD, vendors CRUD
- Migration: add_expense_vendor_models applied via db push + resolve
- 15 integration tests covering all expense lifecycle scenarios
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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