Files
kevin-asprec bd8e83bf68 fix(02): revise plans based on checker feedback
- 02-05: Update outstanding report key_link and action to document
  Invoice.amountPaid as transactional convenience field (not standalone
  stored balance), consistent with creditBalance pattern in 02-03
- 02-04: Add applyCredit wiring to generateInvoiceForSubscriber, add
  credit-service.ts extraction to avoid circular imports, add credit
  auto-application tests
- 02-01/02-03: Add parallel Prisma migration serialization notes for
  Wave 1 concurrent execution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:35:59 +08:00

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
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
true
truths artifacts key_links
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
path provides contains
prisma/schema.prisma Account, AccountingPeriod models with tenant scoping model Account
path provides exports
src/lib/accounting/chart-of-accounts.ts ISP COA definition with account codes, types, normal balances
ISP_CHART_OF_ACCOUNTS
AccountType
NormalBalance
path provides exports
src/lib/accounting/seed-coa.ts Function to provision COA for a tenant
seedChartOfAccounts
path provides exports
src/lib/accounting/accounting-period.ts Accounting period open/close logic
closePeriod
getOpenPeriod
isDateInClosedPeriod
path provides
src/lib/tenant.ts Updated createTenant that calls seedChartOfAccounts
from to via pattern
src/lib/tenant.ts src/lib/accounting/seed-coa.ts createTenant calls seedChartOfAccounts inside transaction seedChartOfAccounts
from to via pattern
src/lib/accounting/seed-coa.ts prisma/schema.prisma creates Account records per ISP_CHART_OF_ACCOUNTS 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.

<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
  1. 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])
  2. 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])
  3. 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)
  4. 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
  5. 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.

  6. 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)
  1. 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
  2. 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.
  3. 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

<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>
After completion, create `.planning/phases/02-subscriber-and-billing-core/02-01-SUMMARY.md`