Phase 02: Subscriber and Billing Core - 5 plans in 4 waves - Wave 1: 02-01 (COA), 02-03 (Subscribers) parallel - Wave 2: 02-02 (Journal Entry Service) - Wave 3: 02-04 (Billing Engine) - Wave 4: 02-05 (Payment Tracker) - Ready for execution Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
10 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-subscriber-and-billing-core | 01 | execute | 1 |
|
true |
|
Purpose: The COA is the foundation of the double-entry accounting system. Without accounts, journal entries have nowhere to post. This must exist before any financial transaction can be recorded. Output: Account and AccountingPeriod Prisma models, ISP COA seed data, auto-provisioning on tenant signup, period close API.
<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>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/02-subscriber-and-billing-core/02-CONTEXT.md @prisma/schema.prisma @src/lib/tenant.ts @src/lib/prisma-tenant.ts Task 1: Account and AccountingPeriod Prisma models + COA definition prisma/schema.prisma src/lib/accounting/chart-of-accounts.ts src/lib/accounting/accounting-period.ts src/lib/prisma-tenant.ts 1. Add enums to prisma/schema.prisma: - `AccountType`: ASSET, LIABILITY, EQUITY, REVENUE, EXPENSE - `NormalBalance`: DEBIT, CREDIT - `PeriodStatus`: OPEN, CLOSED-
Add
Accountmodel to prisma/schema.prisma:- id (uuid), tenantId (String, required), code (String, e.g., "1000"), name (String), accountType (AccountType), normalBalance (NormalBalance), parentId (String?, self-relation for sub-accounts), isSystemAccount (Boolean, default true — COA accounts cannot be deleted), createdAt, updatedAt
- @@unique([tenantId, code]) — account codes unique per tenant
- @@index([tenantId])
-
Add
AccountingPeriodmodel:- id (uuid), tenantId (String), year (Int), month (Int), status (PeriodStatus, default OPEN), closedAt (DateTime?), closedById (String?, relation to User), createdAt, updatedAt
- @@unique([tenantId, year, month]) — one period per tenant per month
- @@index([tenantId])
-
Create src/lib/accounting/chart-of-accounts.ts:
- Export
ISP_CHART_OF_ACCOUNTSas a typed array of account definitions with code, name, accountType, normalBalance, parentCode (optional). Standard ISP accounts:- Assets (1000s): Cash on Hand (1010), Cash in Bank (1020), Accounts Receivable (1100), Subscriber Credits (1150, contra-receivable for overpayments), Equipment Inventory (1200), Prepaid Expenses (1300)
- Liabilities (2000s): Accounts Payable (2010), Unearned Revenue (2100, for prepaid subscriber payments), Taxes Payable (2200)
- Equity (3000s): Owner's Equity (3010), Retained Earnings (3020)
- Revenue (4000s): Subscription Revenue (4010), Installation Fees (4020), Reconnection Fees (4030), Other Revenue (4090)
- Expenses (5000s): Salary Expense (5010), Technician Compensation (5020), Equipment Expense (5030), Internet Bandwidth (5040), Office Supplies (5050), Utilities (5060), Depreciation (5070), Other Expense (5090)
- Export TypeScript types: AccountType, NormalBalance (mirrors the Prisma enums for use outside Prisma context)
- Export
-
Create src/lib/accounting/accounting-period.ts:
getOpenPeriod(tenantPrisma, year, month)— finds or creates an OPEN period for the given monthclosePeriod(tenantPrisma, periodId, closedById)— sets status to CLOSED, records closedAt and closedById. Throws if already closed.isDateInClosedPeriod(tenantPrisma, date)— returns boolean, checks if the month/year of the date has a CLOSED period
-
Update TENANT_SCOPED_MODELS in src/lib/prisma-tenant.ts to include "account" and "accountingPeriod". Add the same query extension blocks (findMany, findFirst, create, update, delete, etc.) following the existing
userpattern exactly. -
Run
npx prisma migrate dev --name add_accounting_modelsto generate and apply the migration.npx prisma migrate statusshows no pending migrationsnpx prisma generatesucceeds- TypeScript compiles:
npx tsc --noEmit - ISP_CHART_OF_ACCOUNTS has entries covering all 5 account types Account and AccountingPeriod models exist in database, COA definition exported, tenant scoping extensions updated, accounting period functions exported
-
Update src/lib/tenant.ts createTenant():
- Import seedChartOfAccounts
- Inside the existing $transaction block, after creating tenant and user, call
await seedChartOfAccounts(tx, tenant.id) - This means every new tenant signup gets a full COA automatically
-
Create API routes (all require ADMIN role via withPermission):
- GET /api/accounting/accounts — list all accounts for the tenant, ordered by code. Use withPermission("read", "Account"). Returns accounts with their type, code, name, normalBalance.
- GET /api/accounting/periods — list accounting periods for the tenant, ordered by year desc, month desc. Use withPermission("read", "Account").
- POST /api/accounting/periods/[id]/close — close an accounting period. Use withPermission("manage", "Account"). Calls closePeriod(). Returns updated period.
-
Write tests in src/lib/tests/accounting-coa.test.ts:
- Test seedChartOfAccounts creates the correct number of accounts for a tenant
- Test seedChartOfAccounts sets parentId correctly for sub-accounts
- Test all 5 account types are represented
- Test normal balances are correct (assets/expenses = DEBIT, liabilities/equity/revenue = CREDIT)
- Test closePeriod sets status to CLOSED and records timestamp
- Test closePeriod throws on already-closed period
- Test isDateInClosedPeriod returns true for closed month, false for open month
- Test createTenant now provisions COA (integration test — create tenant, verify accounts exist)
Run: npx vitest run src/lib/__tests__/accounting-coa.test.ts
- npx vitest run src/lib/__tests__/accounting-coa.test.ts — all tests pass
- npx tsc --noEmit — no type errors
- Creating a tenant via the signup API produces Account records in the database
New tenant signup auto-provisions ISP Chart of Accounts. Admin can list accounts and close accounting periods via API. All tests pass.
<success_criteria>
- Account model exists with code, name, type, normalBalance, tenantId
- AccountingPeriod model exists with year, month, status, tenantId
- ISP COA has 20+ accounts across all 5 types
- createTenant() auto-provisions COA in same transaction
- Period close prevents entries in closed periods (enforcement in 02-02)
- Zero mutable balance fields anywhere
- All tests pass </success_criteria>