- 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>
131 lines
9.3 KiB
Markdown
131 lines
9.3 KiB
Markdown
# 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` (db `radiusdb`). 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 ERPNext `Service Subscription.radius_user/radius_pwd`. ERPNext `speed_down`/`speed_up` are **descriptive only** (not enforced).
|
||
- **The hub has no RADIUS code at all** — no `radiusdb` connection, 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_group`
|
||
- `last_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 `profileid` swap (n8n `dostuff` PATCH) 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_ENFORCE` env: unset/`report` = log intended actions, change nothing; `enforce` = act. (Same pattern as `PAYMENTS_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. `ensureStripeCustomer` already reuses F's authoritative `cus_…`, so no double customer.
|
||
- On payment success (webhook → `recordPayment`, idempotent): credit balance → extend `paid_through_date` → if state was Throttled/Suspended, `restore()` (group + CoA).
|
||
- Saved-card off-session top-up possible via `POST /payments/charge` for auto-renew prepaid.
|
||
|
||
---
|
||
|
||
## 6. Read-side (extend what exists)
|
||
|
||
- `GET /collab/service-status` → add a `prepaid` block: `{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 Account` doctype, 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.js` connects to `radiusdb`, logs what it *would* change. Identify the BNG/NAS for CoA. No live changes.
|
||
- **Phase 3 — throttle (gated)**: define `residentiel-throttle` group 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)
|
||
|
||
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.)
|
||
2. **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.
|
||
3. **Throttle target** — a new FreeRADIUS group (`residentiel-throttle`, e.g. 1 Mbps)? Its reply attributes must be defined on the RADIUS server.
|
||
4. **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.
|
||
5. **Windows** — grace = 3 d (given). Gap before disable? (e.g. throttle at d3, disable at d10.)
|
||
6. **Who is prepaid** — new signups only? Opt-in per service? A `billing_mode = prepaid|postpaid` flag 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()` (n8n `dostuff`, GET only).
|
||
- `services/targo-hub/lib/devices.js` — TR-069 `/tasks` passthrough (`: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.**
|