Backend Modules
apps.customers — Subscriber Lifecycle & CRM
Customer data model, PPPoE credentials, connection status, and public verification queries.
apps.customers — Subscriber Lifecycle & CRM
IMPLEMENTED
- Location:
backend/apps/customers/ - Responsibilities: Customer database, PPPoE credentials, physical connection locations, status tracking, and subscriber query endpoints.
1. Database Model: Customer
The Customer model represents the core subscriber entity:
class Customer(TenantScopedModel):
# Identity
name = models.CharField(max_length=150)
phone = models.CharField(max_length=20, db_index=True)
email = models.EmailField(blank=True, null=True)
nid = models.CharField(max_length=30, blank=True)
address = models.TextField()
# Network / PPPoE Credentials
pppoe_username = models.CharField(max_length=100, db_index=True)
pppoe_password = models.CharField(max_length=100)
ip_address = models.GenericIPAddressField(null=True, blank=True)
mac_address = models.CharField(max_length=30, blank=True)
router = models.ForeignKey('network.Router', on_delete=models.PROTECT, related_name='customers')
# Subscription & Billing
package = models.ForeignKey('billing.Package', on_delete=models.PROTECT, related_name='customers')
reseller = models.ForeignKey('authentication.Reseller', null=True, blank=True, on_delete=models.SET_NULL)
status = models.CharField(max_length=20, choices=CustomerStatus.choices, default=CustomerStatus.ACTIVE)
monthly_bill = models.DecimalField(max_digits=10, decimal_places=2)
discount = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
due_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
expiry_date = models.DateField(null=True, blank=True, db_index=True)
billing_type = models.CharField(max_length=20, default='PREPAID')2. API Endpoints
Core Subscriber CRUD
GET /api/v1/customers/: Paginated subscriber search with filters bystatus,package,router,search.POST /api/v1/customers/: Provisions a new subscriber, initializesBillingAccount, and triggers MikroTik sync.GET /api/v1/customers/{id}/: Detail view including recent invoices, payments, and live session stats.POST /api/v1/customers/{id}/toggle_status/: Quick toggle to suspend or resume Internet access.POST /api/v1/customer/query/: Public/portal query endpoint allowing subscribers to check bill and due amount by phone or PPPoE username.
Financial Management & Grace Periods
GET /api/v1/customers/{id}/financial-summary/: Consolidated breakdown of subscriber financial state.- Returns
due_amount,advance_amount,current_balance,open_invoices_count,unallocated_payments_total, and recent ledger transactions.
- Returns
POST /api/v1/customers/{id}/recalculate-balance/: Authoritatively re-computesdue_amountandadvance_amountdirectly from liveInvoiceandLedgerEntryrecords, fixing any cached column drift.POST /api/v1/customers/{id}/grant-grace-period/: Temporarily extends Internet service without requiring upfront payment.- Payload parameters:
{"days": 3}or{"promise_date": "2026-09-15"}. - Sets
customer.promise_dateandcustomer.grace_period_until. - Automatically restores
customer.status = CustomerStatus.ACTIVE. - Records an immutable
AuditLogentry. - Dispatches a post-commit
NetworkSyncJob(ENABLE_USER) to activate the subscriber's PPPoE credentials on the edge router.
- Payload parameters:
3. Grace Period & Expiry Invariant
In the daily midnight cron task (expire_customers), subscribers are evaluated against their expiration and grace period:
- If
customer.promise_date >= today, the customer is considered IN_GRACE_PERIOD and is exempt from automatic suspension. - Active PPPoE sessions are preserved, avoiding disruption while the subscriber has an active payment promise.
- Once
today > promise_date(andtoday > expiry_date), auto-lock proceeds, terminating sessions and dispatchingNetworkSyncJob(DISABLE_USER).