Planificateur « Suggérer » : 4 stratégies (smart / meilleurs d'abord / équilibré / juste ce qu'il faut), compétences+niveaux par tech (édition inline), niveau requis par compétence + par job, carte des tournées (1 couleur/tech, domicile→arrêts, sélecteur de jour), fenêtre de dispatch auj.+demain (dates sélectionnées), règle week-end + placeholder « en attente du quart », clustering + lasso + filtre-date sur la carte, accès rapide « À assigner » (badge). Boîte : liste /conversations allégée (45 Mo → ~1 Mo, 17×) + messages chargés à l'ouverture. Rapports : cache SWR sur revenue-explorer (22×). Session : keep-alive + timeout fetch global + authFetch durci → fin des rechargements manuels. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
117 lines
7.0 KiB
Markdown
117 lines
7.0 KiB
Markdown
# Automatisation : Installation → Approbation → Facturation
|
|
|
|
> Statut : **PLAN** (2026-06-18). Touche F (live, autoritatif) → tout changement F = test-first.
|
|
> Décisions validées : (1) approbation **explicite dans Ops** ; (2) encaissement **immédiat à l'approbation**.
|
|
|
|
## Objectif
|
|
|
|
Chaîne **100 % automatisée après un seul point de contrôle humain** (l'approbation) :
|
|
installation complétée (tech) → **approuvée** (dispatcher/admin dans Ops) → facturation prorata
|
|
**consolidée, émise et encaissée immédiatement**, sans double-débit, F restant la source de vérité
|
|
de facturation.
|
|
|
|
## Principe de sécurité central — UN seul chargeur, DEUX déclencheurs
|
|
|
|
F ne charge pas « une facture » : `stripe_ppa.php` charge `sum(total_amt - billed_amt)` — **le reste
|
|
impayé, facture par facture**. Donc :
|
|
|
|
- **Déclencheur A — à l'approbation (immédiat)** : on charge le prorata off-session puis on pose
|
|
`billed_amt = total` sur cette facture.
|
|
- **Déclencheur B — balayage du 15 (`stripe_ppa.php`)** : charge le reste impayé ; le prorata déjà
|
|
réglé (`billed_amt = total`) contribue **0** → **jamais rechargé**.
|
|
|
|
Les deux écrivent le **même registre `billed_amt`** → le reste impayé d'une facture est chargé
|
|
exactement une fois. **Pas de double-débit par construction** — surtout ne PAS bâtir un 2ᵉ chargeur
|
|
OPS (cause de l'incident de juin 2026, cf `feedback_ppa_double_charge_incident`).
|
|
|
|
## Pipeline cible
|
|
|
|
```
|
|
[Tech] complète le job TERMINAL de la chaîne
|
|
│ (existant : setJobStatusWithChain → _isChainTerminal → activateSubscriptionForJob)
|
|
▼
|
|
État « À approuver » (abonnements restent 'En attente' ; aperçu prorata calculé, AUCUNE écriture)
|
|
│
|
|
▼
|
|
[Dispatcher/Admin] révise l'install + l'aperçu prorata → clique « Approuver la facturation »
|
|
│
|
|
▼ séquence atomique-idempotente (clé = job/approbation)
|
|
a. Construire le prorata consolidé (moteur : récurrent proraté, ponctuel/install plein,
|
|
rabais selon le type, arrondi plus-grand-reste)
|
|
b. Créer la facture dans F (table `invoice`, date_orig = date d'approbation) ← push-to-F
|
|
c. Charger immédiatement off-session via Stripe (clé d'idempotence = id facture)
|
|
d. Sur succès : poser billed_amt = total (règlement) → exclu du balayage du 15
|
|
e. Activer les abonnements (Actif, start_date = lendemain — jour d'activation gratuit)
|
|
f. Re-synchro miroir ERPNext
|
|
│
|
|
▼
|
|
Récurrent : cycle normal de F (émission le 1er via task_charge_recurrent, balayage le 15)
|
|
```
|
|
|
|
## État actuel (acquis — P0)
|
|
|
|
- **Moteur** `services/targo-hub/lib/proration.js` : consolidation *type-aware* (récurrent proraté /
|
|
ponctuel-install plein / rabais selon le type), arrondi **plus-grand-reste** (somme exacte, 0 résidu),
|
|
écriture **brouillon double-gatée** (`commit:true` + env `PRORATION_WRITE=on`, OFF par défaut).
|
|
Endpoints `POST /billing/prorate-preview` et `POST /billing/activation-preview` (lecture seule).
|
|
- **dispatch.js** `activateSubscriptionForJob` : déjà branché sur le moteur → **UNE** facture
|
|
consolidée (au lieu d'une par abonnement) ; filtre strict `En attente` (ne re-facture jamais les
|
|
actifs) ; gaté `PRORATION_WRITE` (n'écrit rien tant que non armé).
|
|
- **F** : `stripe_ppa.php` (balayage du 15, `sum(total_amt-billed_amt)`), `task_charge_recurrent.php`
|
|
(émission le 1er, due_date 16), `payment_apply.php` / `credit_creance.php` (règlent `billed_amt`),
|
|
`pay_*`/`payment_add.php` (paiement d'une facture précise).
|
|
|
|
## Composants à bâtir
|
|
|
|
### A. Ops — la porte d'approbation
|
|
- Nouvel état sur `Dispatch Job` (ou doctype léger « Approbation Facturation ») : `À approuver` → `Approuvé`.
|
|
- Vue Ops : installs complétées en attente, avec **aperçu de la facture prorata** (via
|
|
`/billing/activation-preview`, lecture seule) — l'approbateur voit exactement ce qui sera facturé.
|
|
- Action « Approuver la facturation » (permission dispatcher/admin) → `POST` hub. Idempotente.
|
|
|
|
### B. Hub — l'orchestrateur
|
|
- `POST /billing/approve-activation { job | customer + service_location }` (staff-gated).
|
|
- Séquence b→f ci-dessus, **idempotente** (clé = job/approbation/mois), avec **reprise sur échec partiel**
|
|
(ex. facture créée mais charge échouée → reprend à la charge, ne recrée pas la facture).
|
|
|
|
### C. F — charge immédiate *scoped* (test-first)
|
|
- Réutiliser la logique **off-session** de `stripe_ppa.php` + le **règlement** de `payment_apply.php`,
|
|
mais **scoped à UNE facture** (le prorata) au lieu du solde complet.
|
|
- Clé d'idempotence Stripe par facture ; `billed_amt = total` + `billing_status = 1` sur succès.
|
|
|
|
### D. Push facture (hub/ERPNext → F)
|
|
- Le prorata DOIT exister dans la table `invoice` de F (sinon ni chargé, ni balayé). Relié au chantier
|
|
`project_sales_sync_f` (push-to-F). Reco : créer en ERPNext (déjà bâti) **puis pousser vers F**.
|
|
|
|
## Garde-fous (non négociables)
|
|
|
|
- **Idempotence** : clé Stripe par facture + reprise. (Leçon juin : charge OK mais règlement échoue
|
|
sous charge → recharge. Ici : clé idempotence + règlement fiable.)
|
|
- **`PRORATION_WRITE` off** jusqu'à validation ; brouillon → revue → armement.
|
|
- **Test-first** : Stripe **test mode** + client **C-LPB4**, puis **1 client réel**, puis rollout.
|
|
- **Pas de carte au dossier** → courriel (comme `stripe_ppa.php`), NE PAS marquer payé.
|
|
- **Échec de charge** → facture reste due (`billed_amt` inchangé) → rejoint le **balayage du 15** (filet)
|
|
+ alerte ; NE JAMAIS activer « payé » sans confirmation Stripe.
|
|
- **Dernier jour du mois** → prorata 0 (le récurrent du 1er couvre) — pas de facture prorata.
|
|
- **Ré-approbation / double-clic** → idempotent : ne pas créer 2 factures prorata pour la même
|
|
activation (clé = service_location + job + mois).
|
|
- **CRTC 2026-43** : frais d'installation facturables (exception) — la ligne installation au plein
|
|
est conforme (cf `project_crtc_2026_43`).
|
|
|
|
## Phasage (incrémental, réversible)
|
|
|
|
| Phase | Contenu | Risque facturation |
|
|
|---|---|---|
|
|
| **P0** ✅ | Moteur + facture consolidée brouillon (gaté, ERPNext) | nul |
|
|
| **P1** | Porte d'approbation Ops : état + **aperçu lecture seule** + action (sans facturer) | nul |
|
|
| **P2** | Push prorata → F (créer la facture dans F, **sans charger**) ; valider qu'elle serait balayée le 15 | faible (filet 15) |
|
|
| **P3** | Charge immédiate *scoped* dans F (test mode → C-LPB4 → 1 client) + règlement `billed_amt` + idempotence | contrôlé |
|
|
| **P4** | Câbler approbation → orchestrateur complet (auto) + monitoring/alertes | contrôlé |
|
|
| **P5** | Armer en prod (`PRORATION_WRITE=on`) après revue d'un cycle complet | go-live |
|
|
|
|
## Décisions ouvertes restantes
|
|
|
|
- **Locus de création de la facture** : F-direct vs ERPNext+push. Reco : ERPNext d'abord (déjà bâti) puis push-to-F.
|
|
- **Ce que l'approbateur révise** (checklist d'install, photos ?) — hors scope facturation pour l'instant.
|
|
- **Mapping client F↔ERPNext** pour le push (legacy_account_id / stripe_id) — à confirmer via `project_sales_sync_f`.
|