- Comms: sélecteur d'expéditeur « De: » (défaut groupe Support TARGO) via resolveSendFrom + alias vérifiés
- Notifs: prefs de feeds PAR utilisateur (/conversations/notif-prefs) + cloche à bascules ; boot tooltip-ux (clic prioritaire + anti-empilement)
- Courriel: invitation à évaluer = modèle Unlayer éditable (transactional-rating-invite-*) ; test-send via Gmail + expansion {{rating}} ; logo TARGO auto-hébergé sur le magasin d'actifs du hub
- Planif: bloc « sans déplacement » (damier, début de quart, alerte si pas de quart), quart éditable dans l'éditeur de jour, icônes de compétence en vue jour (TV pour télé), clic cellule → éditeur, clic gauche lane → liste / clic droit → menu quart, lien ↗ ticket par job
- Tickets: défaut « Non fermés » + correction du filtre « Mes tickets » (owner)
- Inbox: poll Gmail 1 min + rafraîchir à la demande (poll-now)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9.3 KiB
Prepaid Service Model — Design (design-first, not yet built)
Status: DRAFT for review · 2026-06-16 · grounded in an infra audit of the hub/ERPNext/F/RADIUS/OLT stack.
Goal: offer a prepaid internet service. When the paid-through date lapses, give a 3-day grace (full speed), then reduce speed, then disable internet — all reversible instantly on top-up.
0. The critical reality (read this first)
Today there is ZERO automated network enforcement. Marking a service Suspendu/disabled in F or ERPNext only changes records and what staff see — no code cuts or restores a customer's internet. Verified across the whole hub + ops app + legacy bridge.
The actual speed/access lever is RADIUS:
- A FreeRADIUS + MySQL server at
10.5.2.25(dbradiusdb). Subscriber auth =radcheck(Cleartext-Password) +radusergroup(policy class). The group name (e.g.residentiel) is the speed bucket — FreeRADIUS reply attributes for that group define the rate limit. - Per-service RADIUS creds live on the service record: legacy
service.radius_user/radius_pwd, mirrored to ERPNextService Subscription.radius_user/radius_pwd. ERPNextspeed_down/speed_upare descriptive only (not enforced). - The hub has no RADIUS code at all — no
radiusdbconnection, no CoA/Disconnect.
Implication: the prepaid ledger + grace state machine is straightforward to build in the hub/ERPNext. The hard part — and the real prerequisite — is a RADIUS control plane: write radusergroup + push a live change to the subscriber session (CoA/Disconnect) on the BNG/NAS. That actuator must be built, and the BNG/NAS that terminates PPPoE/IPoE sessions (for CoA) is not identified anywhere in the repo — only the FreeRADIUS DB host is known.
Enforcement levers, ranked
| Lever | State today | Use |
|---|---|---|
| RADIUS group reassign + CoA/Disconnect | Not built; radiusdb@10.5.2.25 known, BNG unknown |
Primary — matches how speed already works |
OLT ONU service-profile (profileid) swap via n8n dostuff PATCH |
Contract exists, not wired (hub only calls GET) | Secondary GPON-layer throttle |
| OLT ONU enable/disable | CLI strings generated (provision.js), not executed; SNMP read-only |
Disable fallback via n8n SSH |
TR-069 setParameterValues (rate-limit/disable WAN) |
Raw /tasks proxy exists (devices.js:523); no caller; CPE param support unverified |
Tertiary, if CPE model supports it |
| Modem GUI (modem-bridge) | Read-only Playwright scraper | Not a control lever as built |
1. Customer flow & state machine
top-up (any state) ─────────────────────────────┐
▼
ACTIVE ──paid_through passes──▶ GRACE (3 d, full speed, notify) ──▶ THROTTLED ──▶ SUSPENDED
▲ (full speed) │ remind day 1/2/3 (slow group) (walled-garden / reject)
└──────────────────────── top-up restores group + CoA ◀───────────────────────────┘
- ACTIVE: paid_through_date in the future. Normal RADIUS group.
- GRACE (
0 < days_past ≤ 3): full speed, escalating notifications (SMS + email + portal banner). - THROTTLED (
3 < days_past ≤ disable_after): RADIUS group →residentiel-throttle(e.g. 1 Mbps) + CoA to apply live. - SUSPENDED (
days_past > disable_after): RADIUS → walled-garden/reject + Disconnect-Message. - Top-up from any state: extend paid_through_date, restore full group, CoA → back to ACTIVE.
All transitions idempotent (act only on change) and audited (every customer-impacting action logged) — this is the hard lesson from the PPA double-charge incident.
2. Data model (the prepaid ledger — does not exist today)
New ERPNext doctype Prepaid Account (hub/OPS-owned; see §8 decision on F vs ERPNext):
customer(link),service(link to Service Subscription),radius_user(denormalized for the actuator)daily_rate(derived from the plan price ÷ cycle, or a flat prepaid daily price)paid_through_date(the anchor — everything derives from this)state(Active / Grace / Throttled / Suspended)grace_days(default 3),disable_after_days(e.g. 10),throttle_group,active_grouplast_enforced_state+last_enforced_at(idempotency)
Top-ups recorded as ERPNext Payment Entry (existing, idempotent by reference_no) and extend paid_through_date by amount / daily_rate.
3. Enforcement actuator — new hub module lib/service-control.js
setGroup(radius_user, group) // UPDATE radusergroup.groupname (radiusdb @ 10.5.2.25)
coa(radius_user, action) // CoA (apply new policy) or Disconnect (drop session) → BNG/NAS
throttle(svc) / suspend(svc) / restore(svc) // setGroup + coa, idempotent + audited
- New env
RADIUS_DB_URL(mysql @ 10.5.2.25/radiusdb) +BNG_HOST/BNG_COA_SECRET(OPEN — see §8). - If live CoA is infeasible at first: change the group anyway; it applies on next re-auth/lease renewal (slower, but functional). CoA is the upgrade for instant effect.
- Fallbacks wired behind the same interface: OLT
profileidswap (n8ndostuffPATCH) and TR-069 set-param.
4. Daily cron (gated, dry-run first)
Hub cron (once daily, low-traffic hour): for each Prepaid Account, compute days_past = today − paid_through_date, derive target state, and only act if it differs from last_enforced_state. Emit notifications on entering GRACE (day 1/2/3), THROTTLED, SUSPENDED.
Safety (mandatory, per PPA-incident lessons):
PREPAID_ENFORCEenv: unset/report= log intended actions, change nothing;enforce= act. (Same pattern asPAYMENTS_AUTH.)- Idempotency on
last_enforced_state; never re-disable/re-charge. - Per-run cap + kill switch; structured audit log of every state change.
5. Top-up (reuse existing Stripe flow — already usable)
- New
POST /prepaid/topup(or reuse/payments/checkout-product): ad-hoc Stripe Checkout for $X.ensureStripeCustomeralready reuses F's authoritativecus_…, so no double customer. - On payment success (webhook →
recordPayment, idempotent): credit balance → extendpaid_through_date→ if state was Throttled/Suspended,restore()(group + CoA). - Saved-card off-session top-up possible via
POST /payments/chargefor auto-renew prepaid.
6. Read-side (extend what exists)
GET /collab/service-status→ add aprepaidblock:{state, balance_days, paid_through, daily_rate}.- Client
/diag/<jwt>page → show "X days remaining / topped through DATE", state banner, Top-up now CTA.
7. Phasing (value early, risk late)
- Phase 1 — ledger + top-up + reminders, NO enforcement (SAFE, no network actions):
Prepaid Accountdoctype, Stripe top-up, balance/days on service-status +/diag, grace/expiry notifications. Delivers prepaid billing + dunning immediately with zero risk. - Phase 2 — actuator in report mode:
lib/service-control.jsconnects toradiusdb, logs what it would change. Identify the BNG/NAS for CoA. No live changes. - Phase 3 — throttle (gated): define
residentiel-throttlegroup attrs in FreeRADIUS; enable group reassign + CoA for the THROTTLED transition. - Phase 4 — disable (gated): walled-garden/reject group + Disconnect for SUSPENDED.
8. Open decisions (need your input before Phase 1)
- Balance home — ERPNext/hub-owned ledger (recommended) vs F. F is authoritative for postpaid billing but has no prepaid concept; bolting prepaid onto legacy F is heavy. Recommend the hub/ERPNext owns the prepaid ledger, with prepaid services flagged distinctly. (This is a deliberate divergence from "F authoritative" — for a model F doesn't have.)
- The BNG/NAS that terminates subscriber PPPoE/IPoE sessions — IP + CoA shared secret? Without it, enforcement relies on re-auth/lease expiry (minutes-to-hours lag) instead of instant CoA.
- Throttle target — a new FreeRADIUS group (
residentiel-throttle, e.g. 1 Mbps)? Its reply attributes must be defined on the RADIUS server. - Disable UX — hard reject (no internet) vs walled-garden (captive redirect to the top-up page). Walled-garden is far better UX but needs a captive portal.
- Windows — grace = 3 d (given). Gap before disable? (e.g. throttle at d3, disable at d10.)
- Who is prepaid — new signups only? Opt-in per service? A
billing_mode = prepaid|postpaidflag on the service.
Key files (for whoever builds this)
services/targo-hub/lib/payments.js— Stripe (ensureStripeCustomer,checkout-product,charge,recordPayment); reuse for top-ups.services/targo-hub/lib/ticket-collab.js—serviceStatus()(read-side),dostuffStatus()(n8ndostuff, GET only).services/targo-hub/lib/devices.js— TR-069/taskspassthrough (:523).services/targo-hub/lib/olt-snmp.js(read-only),lib/provision.js(OLT CLI gen, not executed),lib/client-diag.js(/diag).docs/reference/legacy-wizard/account_wizard.php:142-169— how RADIUS users/groups are provisioned today (the template for the actuator).- RADIUS: FreeRADIUS + MySQL
radiusdb@10.5.2.25(creds in the legacy wizard). BNG/NAS for CoA: unknown — must be located.