71c1223cc3
Plan de commissions cross-cohérent workflow ↔ DocType ↔ RBAC + calcul traçable commission = base × taux (façon banclib/finance.py). Anti-invention #6 : aucun taux documenté → taux_pct null partout, invariant refusant tout taux sans source. Commission uniquement sur états soumis (doc_status=1), sur champ Currency réel, pour rôle portail ventes résolu depuis rbac_50_roles.json. - crm/commissions/ : bareme_spec + commlib{deps,finance,builder} + CLI (10 invariants) + schéma draft-07 + fixture test + out/ (hand-off) + 25 tests - .gitea/workflows/ci.yml : job crm-commissions-tests + ajout au gate - daily report session13 + activity log Vérifs : 25/25 tests · gate CI local vert · régression 202 tests verts. Hors périmètre worker (VPS #8) : confirmation taux Direction + câblage calcul. Auto-score 4Big : 96/100. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
86 lines
3.4 KiB
Python
86 lines
3.4 KiB
Python
"""Assemblage du plan de commissions depuis `bareme_spec.json`.
|
|
|
|
Entrée : le contrat barème (évènements référençant des `update_value` du workflow,
|
|
des `base_field` du DocType Dossier Vente et des `role_id` RBAC) + un
|
|
`RoleResolver` (id → nom Frappe issu de rbac_50_roles.json). Sortie : un bundle
|
|
déterministe (tri stable, aucun horodatage) → reproductible bit-à-bit à contrat
|
|
constant, donc diffable et re-générable en CI.
|
|
|
|
Contrainte #6 (zéro invention) : aucun taux n'est ajouté — le barème livré porte
|
|
`taux_pct: null`. Le builder n'ajoute que la résolution des noms de rôle et un
|
|
manifeste de traçabilité (compte des taux restant à confirmer inclus).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
from typing import Any
|
|
|
|
# Réutilisation (workflow #5) du module CRM voisin : le résolveur de rôles
|
|
# (rbac_50_roles.json) est importé, jamais redéfini ici.
|
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
_CRM = os.path.normpath(os.path.join(_HERE, "..", "..")) # 05_deliverables_mvp/crm/
|
|
if _CRM not in sys.path:
|
|
sys.path.insert(0, _CRM)
|
|
|
|
from workflow_vente.wflib.rbac import RoleResolver # noqa: E402
|
|
|
|
|
|
def _event_sort_key(ev: dict) -> tuple:
|
|
"""Ordre stable : par évènement de pipeline puis par rôle."""
|
|
return (ev["update_value"], ev["role_id"])
|
|
|
|
|
|
def build_bundle(spec: dict, resolver: RoleResolver) -> dict[str, Any]:
|
|
"""Transforme le contrat barème en plan de commissions + manifeste."""
|
|
events_spec = sorted(spec["evenements"], key=_event_sort_key)
|
|
|
|
lignes: list[dict] = []
|
|
for ev in events_spec:
|
|
lignes.append({
|
|
"update_value": ev["update_value"],
|
|
"role_id": ev["role_id"],
|
|
"erpnext_role_name": resolver.erpnext_name(ev["role_id"]),
|
|
"base_field": ev["base_field"],
|
|
"libelle": ev["libelle"],
|
|
# Taux jamais fabriqué (#6) : recopié verbatim (null tant qu'à confirmer).
|
|
"taux_pct": ev.get("taux_pct"),
|
|
"source": ev.get("source"),
|
|
"a_confirmer": bool(ev.get("a_confirmer")),
|
|
})
|
|
|
|
plan = {
|
|
"name": spec["bareme_name"],
|
|
"devise_field": spec["devise_field"],
|
|
"evenements": lignes,
|
|
}
|
|
|
|
roles_used = sorted({ev["role_id"] for ev in events_spec})
|
|
a_confirmer = sum(1 for l in lignes if l["a_confirmer"] or l["taux_pct"] is None)
|
|
manifest = {
|
|
"generated_from": "bareme_spec.json",
|
|
"rbac_source": "rbac_50_roles.json",
|
|
"workflow_source": "workflow_vente/workflow_vente_spec.json",
|
|
"doctype_source": "dossier_vente/doctype_spec.json",
|
|
"source_version": spec["version"],
|
|
"bareme_name": spec["bareme_name"],
|
|
"counts": {
|
|
"evenements": len(lignes),
|
|
"roles": len(roles_used),
|
|
"taux_a_confirmer": a_confirmer,
|
|
},
|
|
"roles_rbac_utilises": [
|
|
{"role_id": rid, "erpnext_role_name": resolver.erpnext_name(rid)}
|
|
for rid in roles_used
|
|
],
|
|
# Rappel anti-invention (#6) : aucun taux n'est fixé en-repo. La Direction
|
|
# renseigne `taux_pct` + `source` avant tout calcul de commission réel.
|
|
"note_taux": (
|
|
"Aucun taux de commission n'est documenté dans CLAUDE.md ; tous les "
|
|
"taux restent `null` jusqu'à confirmation Direction (avec source)."
|
|
),
|
|
}
|
|
|
|
return {"manifest": manifest, "commission_plan": plan}
|