--- phase: 02-subscriber-and-billing-core plan: "01" type: execute wave: 1 depends_on: [] files_modified: - prisma/schema.prisma - src/lib/accounting/chart-of-accounts.ts - src/lib/accounting/seed-coa.ts - src/lib/accounting/accounting-period.ts - src/lib/tenant.ts - src/app/api/accounting/accounts/route.ts - src/app/api/accounting/periods/route.ts - src/app/api/accounting/periods/[id]/close/route.ts - prisma/migrations/*_add_accounting_models/migration.sql - src/lib/__tests__/accounting-coa.test.ts autonomous: true must_haves: truths: - "A new tenant signup auto-provisions a complete ISP Chart of Accounts" - "Account balances are never stored — only derived from journal entry sums" - "Admin can close an accounting period, preventing future entries in that period" - "COA accounts have correct normal balance types (debit/credit) for ISP operations" artifacts: - path: "prisma/schema.prisma" provides: "Account, AccountingPeriod models with tenant scoping" contains: "model Account" - path: "src/lib/accounting/chart-of-accounts.ts" provides: "ISP COA definition with account codes, types, normal balances" exports: ["ISP_CHART_OF_ACCOUNTS", "AccountType", "NormalBalance"] - path: "src/lib/accounting/seed-coa.ts" provides: "Function to provision COA for a tenant" exports: ["seedChartOfAccounts"] - path: "src/lib/accounting/accounting-period.ts" provides: "Accounting period open/close logic" exports: ["closePeriod", "getOpenPeriod", "isDateInClosedPeriod"] - path: "src/lib/tenant.ts" provides: "Updated createTenant that calls seedChartOfAccounts" key_links: - from: "src/lib/tenant.ts" to: "src/lib/accounting/seed-coa.ts" via: "createTenant calls seedChartOfAccounts inside transaction" pattern: "seedChartOfAccounts" - from: "src/lib/accounting/seed-coa.ts" to: "prisma/schema.prisma" via: "creates Account records per ISP_CHART_OF_ACCOUNTS" pattern: "account\\.create" --- Create the Chart of Accounts (COA) data model and auto-provisioning system, plus accounting period management. Every new tenant gets a pre-configured ISP-specific COA on signup. No mutable balance fields exist anywhere — all balances will be derived from journal entries (built in 02-02). 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. @C:\Users\KevinAsprec\.claude/get-shit-done/workflows/execute-plan.md @C:\Users\KevinAsprec\.claude/get-shit-done/templates/summary.md @.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 2. Add `Account` model 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]) 3. Add `AccountingPeriod` model: - 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]) 4. Create src/lib/accounting/chart-of-accounts.ts: - Export `ISP_CHART_OF_ACCOUNTS` as 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) 5. Create src/lib/accounting/accounting-period.ts: - `getOpenPeriod(tenantPrisma, year, month)` — finds or creates an OPEN period for the given month - `closePeriod(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 6. 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 `user` pattern exactly. 7. Run `npx prisma migrate dev --name add_accounting_models` to generate and apply the migration. NOTE — Parallel migration conflict: This plan (02-01) and plan 02-03 are both Wave 1 and both run `prisma migrate dev`. When executing these plans in parallel, Prisma migrations MUST be serialized: one plan must complete its migration before the other begins its migration step. The executor should run Task 1 of whichever plan starts first through the migration step, then allow the other plan to proceed with its migration. Non-migration tasks (code files, tests) can still run in parallel. - `npx prisma migrate status` shows no pending migrations - `npx prisma generate` succeeds - 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 Task 2: COA auto-provisioning on tenant signup + API routes + tests src/lib/accounting/seed-coa.ts src/lib/tenant.ts src/app/api/accounting/accounts/route.ts src/app/api/accounting/periods/route.ts src/app/api/accounting/periods/[id]/close/route.ts src/lib/__tests__/accounting-coa.test.ts 1. Create src/lib/accounting/seed-coa.ts: - `seedChartOfAccounts(tx, tenantId)` — takes a Prisma transaction client and tenantId. Iterates ISP_CHART_OF_ACCOUNTS, creates Account records. Resolves parentCode to parentId by looking up already-created parent accounts. Returns count of accounts created. - Must work inside an existing transaction (receives `tx` not a full PrismaClient) 2. 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 3. 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. 4. 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. - `npx vitest run` — all existing + new tests pass - `npx tsc --noEmit` — clean compilation - Create a test tenant via POST /api/tenants/signup, verify Account records exist for that tenant - GET /api/accounting/accounts returns the full COA for the authenticated tenant - POST /api/accounting/periods/[id]/close successfully closes a period - 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 After completion, create `.planning/phases/02-subscriber-and-billing-core/02-01-SUMMARY.md`