| 02-subscriber-and-billing-core |
03 |
database |
| prisma |
| postgresql |
| subscriber |
| billing |
| service-plans |
| tenant-isolation |
|
| phase |
provides |
| 02-subscriber-and-billing-core/02-01 |
Prisma setup, withTenantContext HOF, COA models — established patterns reused here |
|
| phase |
provides |
| 01-foundation/01-03 |
prisma-tenant.ts TENANT_SCOPED_MODELS pattern extended with new models |
|
| phase |
provides |
| 01-foundation/01-04 |
withPermission HOF wrapping API routes |
|
|
| 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) |
|
| 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) |
|
| 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 |
|
|
| created |
modified |
| 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 |
|
| prisma/schema.prisma |
| src/lib/prisma-tenant.ts |
|
|
| 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 |
|
| 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<typeof withTenantContext>) — never takes tenantId directly, always scoped client |
| Status transition matrix: Record<SubscriberStatus, SubscriberStatus[]> — explicit, exhaustive, easily auditable |
| generateAccountNumber queries max accountNumber and increments — sequential per tenant, format SUB-NNNN |
|
7min |
2026-03-04 |