From 5c6969343b26526452e513937ae3748ed74460e0 Mon Sep 17 00:00:00 2001 From: kevin-asprec Date: Wed, 4 Mar 2026 23:04:15 +0800 Subject: [PATCH] docs(02-03): complete Subscriber and ServicePlan management plan Tasks completed: 2/2 - Task 1: Subscriber and ServicePlan Prisma models + service layer - Task 2: Subscriber and ServicePlan API routes + tests SUMMARY: .planning/phases/02-subscriber-and-billing-core/02-03-SUMMARY.md --- .planning/STATE.md | 20 ++- .../02-03-SUMMARY.md | 144 ++++++++++++++++++ 2 files changed, 156 insertions(+), 8 deletions(-) create mode 100644 .planning/phases/02-subscriber-and-billing-core/02-03-SUMMARY.md diff --git a/.planning/STATE.md b/.planning/STATE.md index e68c86e..5428a41 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -10,11 +10,11 @@ See: .planning/PROJECT.md (updated 2026-03-04) ## Current Position Phase: 2 of 5 (Subscriber and Billing Core) — In progress -Plan: 1 of 5 in phase 2 complete (6/20 total) -Status: In progress. 02-01 (COA + Accounting Periods) complete. -Last activity: 2026-03-04 — Completed 02-01-PLAN.md (ISP Chart of Accounts, accounting period management, 121 tests) +Plan: 3 of 5 in phase 2 complete (8/20 total) +Status: In progress. 02-01 (COA + Accounting Periods) and 02-03 (Subscriber + ServicePlan) complete. +Last activity: 2026-03-04 — Completed 02-03-PLAN.md (Subscriber management, service plans, 41 new tests, 162 total) -Progress: [██████░░░░] 30% (6/20 plans across all phases) +Progress: [████████░░] 40% (8/20 plans across all phases) ## Performance Metrics @@ -28,10 +28,10 @@ Progress: [██████░░░░] 30% (6/20 plans across all phases) | Phase | Plans | Total | Avg/Plan | |-------|-------|-------|----------| | 01-foundation | 5/5 complete | 41 min | 8.2 min | -| 02-subscriber-and-billing-core | 1/5 complete | 7 min | 7 min | +| 02-subscriber-and-billing-core | 3/5 complete | 21 min | 7 min | **Recent Trend:** -- Last 6 plans: 01-01 (11 min), 01-02 (8 min), 01-03 (9 min), 01-04 (7 min), 01-05 (6 min), 02-01 (7 min) +- Last 7 plans: 01-01 (11 min), 01-02 (8 min), 01-03 (9 min), 01-04 (7 min), 01-05 (6 min), 02-01 (7 min), 02-03 (7 min) - Trend: stable around 7-8 min *Updated after each plan completion* @@ -74,6 +74,10 @@ Recent decisions affecting current work: - [02-01]: seedChartOfAccounts receives Prisma tx client — works inside createTenant $transaction for atomic provisioning - [02-01]: Accounting periods created on-demand via getOpenPeriod() — not pre-seeded on signup (no wasted periods for unused months) - [02-01]: close route uses closure pattern over withPermission HOF — withPermission doesn't support dynamic params directly; POST fn closes over Next.js params +- [02-03]: creditBalance on Subscriber is operational convenience (FIFO credit allocation for 02-05), NOT a ledger balance — always updated atomically with journal entries +- [02-03]: billingDay capped at 28 — subscribers signing up on days 29-31 get billingDay=28 to avoid month-length invoice generation issues +- [02-03]: CANCELLED -> ACTIVE transition is reversible by design — ISPs frequently reinstate cancelled accounts per CONTEXT.md +- [02-03]: as any cast in service create() calls — Prisma static type requires tenantId but withTenantContext() extension injects at runtime; cast is intentional ### Pending Todos @@ -88,6 +92,6 @@ None. ## Session Continuity -Last session: 2026-03-04T14:51:56Z -Stopped at: Completed 02-01-PLAN.md (COA + accounting period management + 28 new tests, 121 total) +Last session: 2026-03-04T15:02:45Z +Stopped at: Completed 02-03-PLAN.md (Subscriber + ServicePlan management + 41 new tests, 162 total) Resume file: None diff --git a/.planning/phases/02-subscriber-and-billing-core/02-03-SUMMARY.md b/.planning/phases/02-subscriber-and-billing-core/02-03-SUMMARY.md new file mode 100644 index 0000000..7d3f95c --- /dev/null +++ b/.planning/phases/02-subscriber-and-billing-core/02-03-SUMMARY.md @@ -0,0 +1,144 @@ +--- +phase: 02-subscriber-and-billing-core +plan: "03" +subsystem: database +tags: [prisma, postgresql, subscriber, billing, service-plans, tenant-isolation] + +# Dependency graph +requires: + - phase: 02-subscriber-and-billing-core/02-01 + provides: Prisma setup, withTenantContext HOF, COA models — established patterns reused here + - phase: 01-foundation/01-03 + provides: prisma-tenant.ts TENANT_SCOPED_MODELS pattern extended with new models + - phase: 01-foundation/01-04 + provides: withPermission HOF wrapping API routes +provides: + - Subscriber Prisma model with accountNumber, status lifecycle, billingDay, creditBalance + - ServicePlan Prisma model with name, speed, monthlyPrice, billingType, soft-delete + - TenantSettings Prisma model with autoSuspendDays and prepaidLeadDays + - subscriber-service.ts with createSubscriber, updateSubscriber, changeSubscriberStatus, searchSubscribers, getSubscriber + - service-plan-service.ts with createServicePlan, updateServicePlan, listServicePlans, deactivateServicePlan + - Full REST API for subscribers and service plans + - 41 integration tests (162 total passing) +affects: + - 02-04-billing-engine (Subscriber and ServicePlan are the billing targets) + - 02-05-payment-allocation (creditBalance field on Subscriber for FIFO allocation) + - 03-collector-app (zone field on Subscriber for collector routing) + - 04-inventory (subscriber association) + +# Tech tracking +tech-stack: + added: [] + patterns: + - Closure pattern for dynamic route params with withPermission HOF + - as any cast for Prisma create() data when tenantId injected by extension + - Soft-delete pattern via isActive boolean on ServicePlan + - Reversible status lifecycle with explicit transition validation matrix + +key-files: + created: + - prisma/migrations/20260304145633_add_subscriber_models/migration.sql + - src/lib/services/service-plan-service.ts + - src/lib/services/subscriber-service.ts + - src/app/api/service-plans/route.ts + - src/app/api/service-plans/[id]/route.ts + - src/app/api/subscribers/route.ts + - src/app/api/subscribers/[id]/route.ts + - src/app/api/subscribers/[id]/status/route.ts + - src/lib/__tests__/subscriber.test.ts + modified: + - prisma/schema.prisma + - src/lib/prisma-tenant.ts + +key-decisions: + - "creditBalance on Subscriber is NOT a ledger balance — it is an operational convenience field for FIFO overpayment credit allocation (02-05), always updated atomically with journal entries" + - "billingDay capped at 28 — avoids month-length issues (no SUB-0001 billed on Feb 29 that doesn't exist)" + - "CANCELLED -> ACTIVE reversible by design (per CONTEXT.md) — ISPs frequently reinstate cancelled accounts" + - "Closure pattern for dynamic params — withPermission HOF signature doesn't pass params; outer fn receives them from Next.js then inner handler uses via closure (same as accounting/periods/[id]/close pattern)" + - "as any cast in create() calls — Prisma static type requires tenantId but withTenantContext() extension injects it at runtime; cast is intentional and safe" + +patterns-established: + - "Closure pattern for dynamic params: export function PUT(req, { params }) { return withPermission(...)(async (req, { user }) => { const { id } = await params; ... })(req); }" + - "Service layer takes TenantPrisma (ReturnType) — never takes tenantId directly, always scoped client" + - "Status transition matrix: Record — explicit, exhaustive, easily auditable" + - "generateAccountNumber queries max accountNumber and increments — sequential per tenant, format SUB-NNNN" + +# Metrics +duration: 7min +completed: 2026-03-04 +--- + +# Phase 2 Plan 03: Subscriber and ServicePlan Management Summary + +**Subscriber and ServicePlan Prisma models with full CRUD, status lifecycle (ACTIVE/SUSPENDED/CANCELLED all reversible), sequential account numbers (SUB-0001+), billingDay from signup date, and paginated search/filter — the core billing targets for 02-04** + +## Performance + +- **Duration:** 7 min +- **Started:** 2026-03-04T14:55:45Z +- **Completed:** 2026-03-04T15:02:45Z +- **Tasks:** 2/2 +- **Files modified:** 11 + +## Accomplishments + +- Subscriber and ServicePlan Prisma models with full migration applied to database +- TenantSettings model for per-tenant autoSuspendDays and prepaidLeadDays configuration +- Complete service layer: subscriber CRUD, status lifecycle transitions, plan CRUD with soft-delete +- Full REST API (7 endpoints) with closure pattern for dynamic routes +- 41 new integration tests covering all lifecycle transitions, search/filter, pagination, and tenant isolation + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Subscriber and ServicePlan Prisma models + service layer** - `9cc6af1` (feat) +2. **Task 2: Subscriber and ServicePlan API routes + tests** - `03a4a29` (feat) + +**Plan metadata:** (docs commit follows) + +## Files Created/Modified + +- `prisma/schema.prisma` - Added SubscriberStatus, BillingType enums; ServicePlan, Subscriber, TenantSettings models +- `prisma/migrations/20260304145633_add_subscriber_models/migration.sql` - Database migration +- `src/lib/prisma-tenant.ts` - Extended TENANT_SCOPED_MODELS and query blocks for subscriber, servicePlan, tenantSettings +- `src/lib/services/service-plan-service.ts` - createServicePlan, updateServicePlan, listServicePlans, deactivateServicePlan +- `src/lib/services/subscriber-service.ts` - createSubscriber, updateSubscriber, changeSubscriberStatus, searchSubscribers, getSubscriber, generateAccountNumber +- `src/app/api/service-plans/route.ts` - GET (list with activeOnly filter), POST (create) +- `src/app/api/service-plans/[id]/route.ts` - PUT (partial update) +- `src/app/api/subscribers/route.ts` - GET (paginated search with filters), POST (register) +- `src/app/api/subscribers/[id]/route.ts` - GET (with servicePlan relation), PUT (profile update) +- `src/app/api/subscribers/[id]/status/route.ts` - PATCH (status lifecycle transitions) +- `src/lib/__tests__/subscriber.test.ts` - 41 integration tests + +## Decisions Made + +- **creditBalance as operational convenience field** — Distinguishing from "no mutable balance fields" rule: creditBalance tracks overpayment credits for FIFO allocation (02-05), always updated atomically with journal entries. Architectural mutable balance fields (account balances) remain journal-derived. +- **billingDay capped at 28** — Subscribers who sign up on day 29, 30, or 31 get billingDay=28 to avoid month-length boundary issues with invoice generation. +- **Reversible CANCELLED -> ACTIVE transition** — Per CONTEXT.md, ISPs frequently reinstate cancelled accounts. No one-way door on cancellation. +- **Closure pattern for dynamic params** — `withPermission` HOF doesn't pass Next.js route params to handlers. Using closure pattern (same as accounting/periods/[id]/close) instead of modifying the HOF. +- **as any cast in create() calls** — The Prisma extended client injects tenantId at runtime but static types still require it. Safe to cast since the extension guarantees injection. + +## Deviations from Plan + +None — plan executed exactly as written. + +## Issues Encountered + +None. + +## User Setup Required + +None — no external service configuration required. + +## Next Phase Readiness + +- Subscriber and ServicePlan models ready for 02-04 billing engine (invoice generation targets) +- creditBalance field on Subscriber ready for 02-05 FIFO payment allocation +- zone field on Subscriber ready for Phase 3 collector routing +- TenantSettings provides autoSuspendDays for auto-suspension logic in billing engine +- All tenant isolation verified: Subscriber/ServicePlan data not visible across tenants + +--- +*Phase: 02-subscriber-and-billing-core* +*Completed: 2026-03-04*