feat(02-03): Subscriber and ServicePlan Prisma models + service layer
- Add SubscriberStatus (ACTIVE/SUSPENDED/CANCELLED) and BillingType (PREPAID/POSTPAID) enums - Add ServicePlan model with name, speed, monthlyPrice, billingType, soft-delete - Add Subscriber model with accountNumber, billingDay, status lifecycle, creditBalance - Add TenantSettings model with autoSuspendDays and prepaidLeadDays - Migrate: 20260304145633_add_subscriber_models - Extend prisma-tenant.ts with subscriber, servicePlan, tenantSettings query scoping - Create service-plan-service.ts: createServicePlan, updateServicePlan, listServicePlans, deactivateServicePlan - Create subscriber-service.ts: createSubscriber, updateSubscriber, changeSubscriberStatus, searchSubscribers, getSubscriber, generateAccountNumber
This commit is contained in:
@@ -57,6 +57,17 @@ enum PeriodStatus {
|
||||
CLOSED
|
||||
}
|
||||
|
||||
enum SubscriberStatus {
|
||||
ACTIVE
|
||||
SUSPENDED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
enum BillingType {
|
||||
PREPAID
|
||||
POSTPAID
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MODELS
|
||||
// =============================================================================
|
||||
@@ -137,6 +148,87 @@ model AccountingPeriod {
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
/// TenantSettings holds per-tenant configuration for billing and auto-suspension.
|
||||
/// One record per tenant, created on first access or at tenant provisioning.
|
||||
model TenantSettings {
|
||||
id String @id @default(uuid())
|
||||
tenantId String @unique
|
||||
/// Days overdue before automatic subscriber suspension (default 30)
|
||||
autoSuspendDays Int @default(30)
|
||||
/// Days before billing date to generate prepaid invoices (default 7)
|
||||
prepaidLeadDays Int @default(7)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
/// RLS-ready index — always present on tenant-scoped models
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
/// A ServicePlan defines the internet service offering (speed, price, billing type).
|
||||
/// Plans are soft-deleted (isActive=false) to preserve subscriber history.
|
||||
model ServicePlan {
|
||||
id String @id @default(uuid())
|
||||
tenantId String
|
||||
name String
|
||||
/// Human-readable speed description (e.g., "50 Mbps", "100/20 Mbps")
|
||||
speed String
|
||||
/// Monthly charge for this plan (2 decimal places)
|
||||
monthlyPrice Decimal @db.Decimal(10, 2)
|
||||
billingType BillingType
|
||||
description String?
|
||||
/// Soft-delete: inactive plans cannot be assigned to new subscribers
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
subscribers Subscriber[]
|
||||
|
||||
/// Plan names must be unique within a tenant
|
||||
@@unique([tenantId, name])
|
||||
/// RLS-ready index — always present on tenant-scoped models
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
/// A Subscriber is a customer of the ISP — the core billing entity.
|
||||
/// All invoices, payments, and collections target subscribers.
|
||||
model Subscriber {
|
||||
id String @id @default(uuid())
|
||||
tenantId String
|
||||
/// Auto-generated sequential identifier per tenant (e.g., "SUB-0001")
|
||||
accountNumber String
|
||||
firstName String
|
||||
lastName String
|
||||
email String?
|
||||
phone String?
|
||||
address String
|
||||
/// Zone for collector routing (Phase 3)
|
||||
zone String?
|
||||
servicePlanId String
|
||||
servicePlan ServicePlan @relation(fields: [servicePlanId], references: [id])
|
||||
status SubscriberStatus @default(ACTIVE)
|
||||
/// Day of month for invoice generation — derived from signup date, capped at 28
|
||||
billingDay Int
|
||||
activatedAt DateTime @default(now())
|
||||
suspendedAt DateTime?
|
||||
cancelledAt DateTime?
|
||||
/// Per-subscriber auto-suspend override (null = use TenantSettings.autoSuspendDays)
|
||||
autoSuspendDays Int?
|
||||
notes String?
|
||||
/// Overpayment credit balance — always updated atomically with journal entries.
|
||||
/// This is NOT a stored ledger balance; it tracks credits for the FIFO allocation
|
||||
/// system (02-05) and is always updated within the same transaction as the journal entry.
|
||||
creditBalance Decimal @default(0) @db.Decimal(10, 2)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
/// Account numbers must be unique within a tenant
|
||||
@@unique([tenantId, accountNumber])
|
||||
/// RLS-ready index — always present on tenant-scoped models
|
||||
@@index([tenantId])
|
||||
@@index([tenantId, status])
|
||||
@@index([tenantId, servicePlanId])
|
||||
}
|
||||
|
||||
/// A User belongs to a Tenant (or is a super-admin with no tenant).
|
||||
/// Email uniqueness is enforced per-tenant, not globally.
|
||||
/// Super-admins have isSuperAdmin=true and tenantId=null.
|
||||
|
||||
Reference in New Issue
Block a user