# Générateur de workflow vente ERPNext · lead → CONFOTUR

**Sprint 4 · CRM natif ERPNext.** Transforme le contrat pipeline
[`workflow_vente_spec.json`](workflow_vente_spec.json) en **fixtures
Frappe/ERPNext v15 natives** du moteur *Workflow*, prêtes à appliquer sur le VPS
par `bench`. Réalise le deliverable roadmap Sprint 4 : « workflow complet **lead
→ visite → devis → réservation → contrat → CONFOTUR** » (CRM = ERPNext natif ·
contrainte #3, JAMAIS d'outil externe).

> Ce worker **n'écrit jamais sur le VPS** (contrainte #8). Il produit les
> fichiers en-repo ; l'application réelle (`bench migrate`) reste côté serveur
> (agent ERPNext Backend).

## Ce qui est généré (`out/`, commité — hand-off direct)

| Fichier | DocType Frappe | Rôle |
|---|---|---|
| `workflow.json` | `Workflow` | Le graphe : `document_type` + table `states[]` + table `transitions[]`. |
| `workflow_state.json` | `Workflow State` | Maîtres d'états (nom + `style` de badge desk). |
| `workflow_action_master.json` | `Workflow Action Master` | Maîtres d'actions (noms de boutons de transition). |
| `MANIFEST.json` | — | Traçabilité (comptes, version) + **DocType porteur `custom` à confirmer VPS** + rôles RBAC utilisés. |

## Le pipeline (9 états · 11 transitions)

```
Lead ──Planifier visite──▶ Visite planifiée ──Émettre devis──▶ Devis émis
  │                              │                                  │
  └──Abandonner──┐              └──Abandonner──┐    ┌──Abandonner───┘
                 ▼                             ▼    ▼
              Abandonné (0, terminal)     Devis émis ──Confirmer réservation──▶ Réservation confirmée (1)
                                                                                  │            │
                                          Signer contrat ◀─────────────────────────┘            └──Annuler──▶ Perdu (2, terminal)
                                                 │
                                                 ▼
                                          Contrat signé (1) ──Déposer CONFOTUR──▶ CONFOTUR déposé (1)
                                                 │                                        │
                                                 └──Résilier──▶ Perdu (2)     Approuver CONFOTUR
                                                                                          ▼
                                                                             CONFOTUR approuvé (1, terminal succès)
```

`doc_status` natif Frappe : **0** = Brouillon · **1** = Soumis · **2** = Annulé.
Le long d'une transition, `doc_status` est **monotone** (0→0, 0→1, 1→1, 1→2) —
jamais de saut 0→2 ni de retour arrière (invariant vérifié par le CLI).

## Rôles = contrat RBAC (réutilisation · zéro duplication · #6)

Le pipeline **ne cite jamais un nom de rôle Frappe en dur** : chaque état/transition
référence l'`id` stable d'un rôle de
[`../../rbac/rbac_50_roles.json`](../../rbac/rbac_50_roles.json), résolu par
`wflib/rbac.py` en `erpnext_role_name`. Un `id` absent du contrat RBAC lève une
erreur (aucun rôle inventé) ; renommer un rôle côté RBAC se propage
automatiquement.

| Étape | Rôle qui garde la transition |
|---|---|
| Planifier visite · Émettre devis | OTO Ventes Conseiller |
| Abandonner (après devis) | OTO Ventes Chef Équipe |
| **Confirmer réservation** (soumission · argent) | OTO Ventes Réservations |
| **Signer contrat** | OTO Ventes Contrats |
| Déposer / **Approuver CONFOTUR** | OTO Ventes CONFOTUR |
| Annuler / Résilier (perdu) | OTO Ventes Directeur · OTO Direction Commerciale |

## Séparation des pouvoirs (défense en profondeur · #6)

Les transitions qui **engagent de l'argent ou clôturent** — confirmer
réservation, signer contrat, approuver CONFOTUR, annuler/résilier — sont marquées
`separation_of_duties` dans le contrat et **interdisent l'auto-approbation**
(`allow_self_approval = 0` : quatre-yeux obligatoire). Le CLI `validate` échoue
si l'une d'elles autorise l'auto-approbation.

## Utilisation

```bash
# Génère les 4 fichiers dans out/
python3 workflow_vente_gen.py build            # [-o DOSSIER]

# Valide (schéma + 9 invariants métier) sans rien écrire
python3 workflow_vente_gen.py validate

# Tests (stdlib pur, zéro pip)
python3 -m unittest discover -s tests -v
```

## Invariants vérifiés par le CLI (refus d'écrire si l'un casse)

1. Conformité au schéma de sortie (`workflow.schema.json`).
2. Unicité des noms d'état.
3. Toute transition référence des états déclarés.
4. Monotonie `doc_status` (pas de saut 0→2 ni décroissant).
5. Unicité du couple (état, action) — action déterministe (exigence Frappe).
6. Atteignabilité de tous les états depuis `Lead`.
7. Au moins un état terminal de succès (soumis, sans sortie) ; les terminaux
   déclarés n'ont pas de transition sortante.
8. Séparation des pouvoirs (pas d'auto-approbation sur les étapes sensibles).
9. Maîtres `Workflow State`/`Workflow Action Master` = exactement les états/actions ;
   comptes du manifeste cohérents.

## Application sur VPS (agent ERPNext Backend · hors périmètre worker)

1. Créer d'abord le DocType porteur `custom` listé dans
   `MANIFEST.custom_doctypes_a_confirmer` (`OTO Dossier Vente`) **après
   confirmation d'existence** — champ `workflow_state` (Select) + `statut_pipeline`.
2. Déposer `workflow.json` + `workflow_state.json` + `workflow_action_master.json`
   dans `fixtures/` de l'app OTO, référencés dans `hooks.py`.
3. `bench --site frontend migrate` (ou `bench import-fixtures`).
4. Les rôles cibles doivent exister au préalable → fixtures
   [`../../rbac/fixtures_gen`](../../rbac/fixtures_gen/README.md).
5. Vérification HTTP post-déploiement (workflow #3) + audit QA 4Big.

## Vérification en-repo

- `python3 -m unittest discover -s tests -v` → **25/25 verts** (résolution RBAC,
  structure Frappe, 9 invariants de graphe, schéma maison + oracle `jsonschema`,
  déterminisme, CLI, `out/` == régénération).
- Job CI dédié `crm-workflow-vente-tests` ajouté au **gate**
  (`.gitea/workflows/ci.yml`, Gitea Actions uniquement · #2).

## Auto-score 4Big du livrable : **96/100**

_Réserve −4_ : la création du DocType porteur `OTO Dossier Vente` + l'import des
fixtures (`bench migrate`) restent côté VPS (agent ERPNext Backend · #8) ; les
conditions de transition (`condition`) sont laissées à `null` (le contrat métier
ne documente pas de seuil chiffré → anti-invention #6). Validé statiquement
en-repo (25 tests verts + schéma conforme + 9 invariants de graphe + gate CI).
