Files
NetForge/.planning/phases/02-subscriber-and-billing-core/02-01-SUMMARY.md
kevin-asprec e49db94f38 docs(02-01): complete Chart of Accounts plan
Tasks completed: 2/2
- Task 1: Account and AccountingPeriod Prisma models + COA definition
- Task 2: COA auto-provisioning on tenant signup + API routes + tests

SUMMARY: .planning/phases/02-subscriber-and-billing-core/02-01-SUMMARY.md
2026-03-04 22:53:38 +08:00

7.4 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established duration completed
02-subscriber-and-billing-core 01 database
prisma
postgresql
accounting
double-entry
chart-of-accounts
multi-tenancy
phase provides
01-foundation Tenant model, User model, createTenant(), withTenantContext(), withPermission() HOF
Account Prisma model with tenant scoping, code/name/accountType/normalBalance/parentId
AccountingPeriod Prisma model with year/month/status/closedAt/closedById
ISP_CHART_OF_ACCOUNTS
28-account definition covering all 5 accounting types
seedChartOfAccounts(tx, tenantId)
auto-provisions COA inside Prisma transaction
createTenant() now provisions full COA atomically with tenant+user creation
closePeriod, getOpenPeriod, isDateInClosedPeriod functions
GET /api/accounting/accounts — list COA for authenticated tenant
GET /api/accounting/periods — list accounting periods
POST /api/accounting/periods/[id]/close — close a period
02-02-journal-entry-service (posts to Account records created here; enforces closed periods)
02-03-invoice-billing (uses Subscription Revenue 4010, Accounts Receivable 1100)
02-04-payment-collection (uses Cash accounts 1010/1020, credits AR)
All future phases that touch financial data
added patterns
COA auto-provisioning in Prisma $transaction — atomic tenant+user+COA creation
Zero mutable balance fields — all balances derived from journal entry sums
On-demand period creation via getOpenPeriod() — periods created at first use
TxClient pattern — seed functions accept transaction client, not full PrismaClient
withTenantContext() extended with account and accountingPeriod query blocks
created modified
prisma/migrations/20260304144656_add_accounting_models/migration.sql
src/lib/accounting/chart-of-accounts.ts
src/lib/accounting/accounting-period.ts
src/lib/accounting/seed-coa.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
prisma/schema.prisma
src/lib/prisma-tenant.ts
src/lib/tenant.ts
28 accounts in ISP COA — 5 parent headers (1000/2000/3000/4000/5000) + 23 leaf accounts
Subscriber Credits (1150) is contra-asset with CREDIT normal balance — reduces AR
seedChartOfAccounts receives Prisma tx client (not full PrismaClient) — works inside $transaction
Periods created on-demand via getOpenPeriod() — not pre-seeded at tenant creation
closedPeriods back-reference added on User model for Prisma relation integrity
close route uses closure pattern (withPermission wrapping inside POST fn) for param access
Accounting isolation: zero mutable balance fields — enforced at model level (no balanceColumn exists)
COA seeding: ISP_CHART_OF_ACCOUNTS array ordered parents-before-children for correct parentId resolution
7min 2026-03-04

Phase 2 Plan 01: Chart of Accounts and Accounting Period Management Summary

ISP double-entry COA (28 accounts, all 5 types) auto-provisioned atomically on tenant signup, with period open/close management — zero mutable balance fields anywhere

Performance

  • Duration: 7 min
  • Started: 2026-03-04T14:44:47Z
  • Completed: 2026-03-04T14:51:56Z
  • Tasks: 2
  • Files modified: 11

Accomplishments

  • Account and AccountingPeriod Prisma models added with full tenant scoping (RLS-ready indexes, @@unique per tenant)
  • 28-account ISP Chart of Accounts (1000-5000 ranges) with correct normal balances across all 5 types
  • Every new tenant signup atomically provisions a complete COA inside the existing $transaction
  • Accounting period close logic (closePeriod, getOpenPeriod, isDateInClosedPeriod) ready for journal entry enforcement in 02-02
  • 28 new tests (121 total) covering COA definition purity, seeding correctness, period management, and createTenant integration

Task Commits

Each task was committed atomically:

  1. Task 1: Account and AccountingPeriod Prisma models + COA definition - 7c0caf5 (feat)
  2. Task 2: COA auto-provisioning on tenant signup + API routes + tests - a53ee9c (feat)

Files Created/Modified

  • prisma/schema.prisma - Added AccountType/NormalBalance/PeriodStatus enums, Account model, AccountingPeriod model with User.closedPeriods back-reference
  • prisma/migrations/20260304144656_add_accounting_models/migration.sql - Migration creating account and accounting_period tables
  • src/lib/accounting/chart-of-accounts.ts - ISP_CHART_OF_ACCOUNTS (28 accounts), AccountType and NormalBalance TypeScript types
  • src/lib/accounting/accounting-period.ts - getOpenPeriod, closePeriod, isDateInClosedPeriod functions
  • src/lib/accounting/seed-coa.ts - seedChartOfAccounts(tx, tenantId) — resolves parentCode to parentId during seeding
  • src/lib/prisma-tenant.ts - Added "account" and "accountingPeriod" to TENANT_SCOPED_MODELS; full query extension blocks for both models
  • src/lib/tenant.ts - createTenant() now calls seedChartOfAccounts(tx, tenant.id) inside the existing $transaction
  • src/app/api/accounting/accounts/route.ts - GET /api/accounting/accounts (read:Account permission)
  • src/app/api/accounting/periods/route.ts - GET /api/accounting/periods (read:Account permission)
  • src/app/api/accounting/periods/[id]/close/route.ts - POST /api/accounting/periods/[id]/close (manage:Account permission)
  • src/lib/__tests__/accounting-coa.test.ts - 28 integration and unit tests

Decisions Made

  • 28 accounts with parent headers: 5 category headers (1000, 2000, 3000, 4000, 5000) plus 23 leaf accounts — provides hierarchical COA structure for reporting
  • Contra-asset 1150: Subscriber Credits has CREDIT normal balance despite being ASSET type — correctly models overpayments that reduce the AR balance
  • TxClient via any cast in tests: Prisma transaction client types don't align perfectly with interface extraction; using any in test is safe (the actual seedChartOfAccounts function is typed correctly via PrismaClient.$transaction parameter inference)
  • Close route uses closure pattern: withPermission HOF doesn't support route params directly — POST function closes over params from Next.js route context before invoking the HOF
  • On-demand period creation: Periods are created on first use (getOpenPeriod), not pre-seeded — avoids creating 12 periods per tenant on signup for months that may never have entries

Deviations from Plan

None — plan executed exactly as written.

Issues Encountered

None.

User Setup Required

None — no external service configuration required.

Next Phase Readiness

  • Account and AccountingPeriod models in database — ready for journal entry posting (02-02)
  • All 28 standard ISP accounts provisioned for every new tenant — journal entries can reference accounts by code
  • closedPeriods enforcement hook ready — 02-02 JournalEntryService calls isDateInClosedPeriod before posting
  • CASL "Account" subject already defined in casl/types.ts — permissions are operational
  • withPermission("manage", "Account") guards period close endpoint — aligns with ADMIN-only restriction

Phase: 02-subscriber-and-billing-core Completed: 2026-03-04