# 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).