[DTP-Worker 20260801_210434] Sprint 8 · buffer · CRM/commissions : gate d'IDENTITÉ du CHAMP DEVISE — la clause devise_field = le champ devise (Select USD/DOP · #10) du DocType Dossier Vente (crm/commissions/README.md:54), invariant CLI #8, transcrite en prose hors de tout gate (grep devise_field dans ci/ = 0)

4e surface distincte du même livrable crm/commissions (après états/champs, anti-taux #6, rôles payés) : le « sur quelle devise » du calcul base×taux. Re-dérivée de 3 sources byte-gatées/constitutionnelles : plan.devise_field (out/commission_plan.json) == champ `devise` du DocType (dossier_vente/doctype_spec.json, fieldtype Select) == options ORDONNÉES == devises canoniques CLAUDE.md #10 (« USD + DOP »). Ancrage vivant (#10) : (0) 2 devises depuis le segment gras de #10 ; (a) cross-artefact plan==DocType==#10 ; (b) prose README:54 == artefacts + #10 + tag #10. Distincte du bloc Fiches #10 (prose ERPNext/CRM) et du bloc e-CF TipoMoneda (spec fiscal).

7 morsures vérifiées (4 README · 1 DocType · 1 CLAUDE.md · 1 plan), 7 gates re-verts, working tree byte-restauré (git checkout --, JAMAIS git clean). ci/README.md (récap + paragraphe détaillé) et 05_activity_log mis à jour. Auto-score 4Big 96/100.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-01 21:15:36 +00:00
parent f91a22f458
commit d587b446b9
3 changed files with 183 additions and 1 deletions
+25 -1
View File
File diff suppressed because one or more lines are too long
+99
View File
@@ -3559,6 +3559,105 @@ if cr_ev is not None:
good(f"CRM commissions rôles · MANIFEST.roles_rbac_utilises == {len(cr_ids)} "
f"rôles distincts payés (counts.roles cohérent)")
# ============================================================================
# CRM/Commissions · IDENTITÉ du CHAMP DEVISE — `devise_field` = le champ `devise`
# (`Select` USD/DOP · #10) du DocType, transcrit À LA MAIN au README:54 sans gate.
# ----------------------------------------------------------------------------
# Le générateur commissions_gen émet out/commission_plan.json (byte-gaté par
# check_artifacts ⇒ faisant autorité) : le plan porte la clé RACINE `devise_field`
# — le champ du Dossier Vente qui PORTE la devise d'un montant commissionné. Son
# invariant CLI #8 exige TROIS choses simultanées : `devise_field == "devise"`, le
# DocType Dossier Vente (doctype_spec.json, l'INPUT du générateur) porte bien un
# champ `devise` de fieldtype `Select`, et ses `options` == `USD`/`DOP` — les DEUX
# devises canoniques de CLAUDE.md #10 (« **USD + DOP** devises »). La §Cross-cohérence
# RÉÉNONCE le tout EN PROSE (README:54) : « `devise_field` = le champ `devise`
# (`Select` **USD/DOP** · #10) du DocType ». Les blocs commissions ci-dessus gatent
# les états soumis, les champs de base, les rôles payés et la promesse anti-taux —
# mais AUCUN ne touche ce couple champ↔devises. Dérive #6 : RENOMMER le champ
# (`devise`→`moneda`) dans le plan/DocType, changer son fieldtype (`Select`→`Data` :
# la contrainte d'énumération PERDUE, un montant en monnaie libre), ou altérer ses
# options (`USD/DOP`→`USD/EUR` : devise HORS #10, le calcul `base × taux`
# commissionnerait dans une monnaie non prévue) laisse la prose périmée EN SILENCE
# pendant que l'artefact/DocType dit autre chose → « vert trompeur » qu'aucune suite
# tests/ (FONCTIONS finance/résolution, jamais la prose vs artefact) n'attrape ; ce
# n'est ni un lien, ni un terme interdit, ni un `out/` byte-gaté du module → invisible
# à check_docs/guard_constraints. On RECOMPUTE depuis les artefacts byte-gatés + la
# contrainte constitutionnelle : (0) ANCRAGE — les 2 devises depuis CLAUDE.md #10
# (segment gras « USD + DOP ») ; (a) CROSS-ARTEFACT — plan.devise_field == "devise"
# == fieldname du champ DocType · fieldtype == "Select" · options (ORDONNÉES) ==
# devises #10 ; (b) PROSE — README:54 nomme le champ `devise`, le dit `Select`, cite
# USD/DOP == #10 et tague #10. Un claim absent échoue AUSSI (traçabilité #6).
# Direction plan/DocType/CLAUDE.md #10 → README.
# ============================================================================
try:
cd_plan = load("crm/commissions/out/commission_plan.json")
cd_dt = load("crm/dossier_vente/doctype_spec.json")
cd_cm = open("CLAUDE.md", encoding="utf-8").read()
except (OSError, KeyError, ValueError, TypeError) as e:
bad(f"CRM commissions devise · artefact/CLAUDE.md illisible/inattendu : {e}"); cd_plan = None
if cd_plan is not None:
# (0) ANCRAGE #10 : les 2 devises canoniques (segment gras « USD + DOP »)
cd_cur = None
cd_l10 = re.search(r"^10\.\s+(.+)$", cd_cm, re.M)
if cd_l10:
for seg in re.split(r"·", cd_l10.group(1)):
if "devise" in seg.lower():
mm = re.search(r"\*\*(.+?)\*\*", seg)
if mm:
cd_cur = [t.strip() for t in re.split(r"[+/]", mm.group(1)) if t.strip()]
if not cd_cur:
bad("CRM commissions devise · devises #10 (« **USD + DOP** ») INTROUVABLES dans "
"CLAUDE.md #10 → ancrage rompu")
else:
cd_field = None
for grp in cd_dt.get("field_groups", []):
for f in grp.get("fields", []):
if f.get("fieldname") == "devise":
cd_field = f
cd_pf = cd_plan.get("devise_field")
cd_opts = ([ln for ln in cd_field.get("options", "").split("\n") if ln]
if cd_field else None)
# (a) CROSS-ARTEFACT : plan ↔ DocType ↔ devises #10 (invariant CLI #8)
if cd_pf != "devise":
bad(f"CRM commissions devise · plan.devise_field={cd_pf!r} ≠ 'devise' → "
f"invariant #8 cassé (le champ porteur de la devise a changé)")
elif cd_field is None:
bad("CRM commissions devise · champ `devise` ABSENT du DocType Dossier Vente "
"→ invariant #8 (options USD/DOP invérifiables)")
elif cd_field.get("fieldtype") != "Select":
bad(f"CRM commissions devise · champ `devise` fieldtype={cd_field.get('fieldtype')!r} "
f"≠ 'Select' → énumération USD/DOP PERDUE (monnaie libre)")
elif cd_opts != cd_cur:
bad(f"CRM commissions devise · options du champ `devise` {cd_opts} ≠ devises #10 "
f"{cd_cur} (ordre inclus) → devise HORS CLAUDE.md #10")
else:
good(f"CRM commissions devise · plan.devise_field='devise' == DocType champ `devise` "
f"(Select {cd_opts}) == CLAUDE.md #10")
# (b) PROSE README:54 — champ · fieldtype · devises · tag #10
cd_rd = read_norm(CM_RD)
mpr = re.search(
r"`devise_field`\*\* = le champ `([a-z_]+)` \(`(\w+)` \*\*([A-Z/]+)\*\* · #10\)",
cd_rd or "")
if mpr is None:
bad(f"CRM commissions devise · {CM_RD} — claim « `devise_field` = le champ "
f"`devise` (`Select` **USD/DOP** · #10) » INTROUVABLE/malformé (traçabilité #6)")
else:
p_field, p_ftype = mpr.group(1), mpr.group(2)
p_cur = mpr.group(3).split("/")
errs = []
if p_field != "devise":
errs.append(f"champ `{p_field}` ≠ `devise`")
if p_ftype != "Select":
errs.append(f"type `{p_ftype}` ≠ `Select`")
if p_cur != cd_cur:
errs.append(f"devises {p_cur} ≠ #10 {cd_cur}")
if errs:
bad(f"CRM commissions devise · {CM_RD} — prose périmée : "
+ " · ".join(errs) + " → régénérer le README")
else:
good(f"CRM commissions devise · {CM_RD} — prose « champ `devise` "
f"(`Select` {'/'.join(p_cur)}) · #10 » == artefacts + CLAUDE.md #10")
# ============================================================================
# RBAC/fixtures_gen · le CATALOGUE des DocTypes `custom` à créer sur le VPS et la
# SÉPARATION DES POUVOIRS (le rôle unique porteur de `set_user_permissions`) —