[DTP-Worker] Sprint 8 · buffer · Fiscal/e-CF DGII : gate d'IDENTITÉ des ÉVÈNEMENTS D'ÉMISSION (« le cœur du livrable ») — ENSEMBLE émetteurs {réservation,contrat} ⨯ DISJONCTION des brouillons (jamais de facture sur brouillon) ⨯ base_field ⨯ rôle unique compta-fiscaliste-ecf double-ancré RBAC portail compta ⨯ FormaPago défaut 3/Tarjeta/Cardnet #10, recomputés d'ecf_plan.json
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1508,3 +1508,66 @@ taux_a_confirmer` périmé 5→4 (c seul) · promesse anti-invention retirée du
|
|||||||
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; la
|
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; la
|
||||||
confirmation réelle des taux + le câblage du calcul restent côté Direction/VPS).
|
confirmation réelle des taux + le câblage du calcul restent côté Direction/VPS).
|
||||||
- **Auto-score 4Big** : 96/100.
|
- **Auto-score 4Big** : 96/100.
|
||||||
|
|
||||||
|
## Session `20260801_140334` · Buffer S8 · Domaine Fiscal/e-CF DGII : l'**IDENTITÉ des ÉVÈNEMENTS D'ÉMISSION** — la section §Cross-cohérence présentée comme **« le cœur du livrable »** (README:40-59) — était transcrite À LA MAIN SANS aucun gate d'identité. Le SEUL gate fiscal existant couvre le **contrat de format e-NCF** (`E+tipo(2)+seq(10)=13`) + le compte de tests ; le bloc racine, le seul agrégat « e-CF 39 tests ». RIEN ne confrontait à l'artefact byte-gaté QUEL évènement du pipeline émet un e-CF, sur quel `base_field`, par quel rôle Compta, ni la FormaPago défaut.
|
||||||
|
|
||||||
|
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
|
||||||
|
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Même classe « identité
|
||||||
|
d'un ENSEMBLE » que la colonne « Entité » de la SPEC §4, l'énumération des onglets
|
||||||
|
Mobile ou les transitions à séparation des pouvoirs du workflow — appliquée à la
|
||||||
|
**surface data-derived la plus critique du module fiscal** : les `emission_events`
|
||||||
|
d'`ecf_plan.json`, jamais gatés hors le format e-NCF et le compte de tests.
|
||||||
|
|
||||||
|
**Dérive silencieuse fermée** :
|
||||||
|
- `05_deliverables_mvp/fiscal/ecf_dgii/README.md:40-59` (§Cross-cohérence, « le cœur
|
||||||
|
du livrable ») — transcrit À LA MAIN : (L48) l'ENSEMBLE des évènements émetteurs
|
||||||
|
(« seulement réservation et contrat ») + l'ENSEMBLE exclu des brouillons (« on ne
|
||||||
|
facture pas un brouillon (lead/visite/devis) ») ; (L50) l'ENSEMBLE des `base_field`
|
||||||
|
(`montant_reservation`/`montant_contrat`) ; (L53-54) le rôle UNIQUE
|
||||||
|
(`compta-fiscaliste-ecf` · « OTO Compta Fiscaliste eCF » · portail compta) ;
|
||||||
|
(L59) la FormaPago défaut (« = 3 (Tarjeta) » · Cardnet #10).
|
||||||
|
- Source faisant autorité (byte-gatée par `check_artifacts`) :
|
||||||
|
`out/ecf_plan.json` — `emission_events[]` (`update_value`/`base_field`/`role_id`/
|
||||||
|
`erpnext_role_name`), `forma_pago_defaut` + catalogue `formas_pago`. Double
|
||||||
|
ancrage du rôle sur `rbac/rbac_50_roles.json` (id → `portail` compta → nom ERPNext).
|
||||||
|
- Piège #6/#5, la dérive la plus dommageable ici : **AJOUTER un `emission_event` sur
|
||||||
|
un état BROUILLON** (ex. `devis`) — l'anti-pattern EXACT que le module interdit
|
||||||
|
(« on ne facture pas un brouillon ») — pendant que L48 dit encore « seulement
|
||||||
|
réservation et contrat » → l'agent ERPNext Backend câblerait une **facture émise
|
||||||
|
avant soumission/encaissement**. Aucune suite `tests/` (FONCTIONS de résolution/
|
||||||
|
composition, jamais la prose vs l'artefact) ne l'attrape.
|
||||||
|
- **État courant** : émetteurs `{contrat, reservation}`, base_field
|
||||||
|
`{montant_contrat, montant_reservation}`, rôle unique `compta-fiscaliste-ecf`
|
||||||
|
(portail compta · RBAC concordant), FormaPago défaut `3`/Tarjeta/Cardnet —
|
||||||
|
tout recoupe l'artefact EXACTEMENT (anti-invention #6, rien à réécrire). Le
|
||||||
|
défaut est la **surface ungated**.
|
||||||
|
|
||||||
|
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Fiscal · e-CF DGII —
|
||||||
|
IDENTITÉ des ÉVÈNEMENTS D'ÉMISSION » avant `sys.exit`) : (0) **cohérence interne** —
|
||||||
|
rôle UNIQUE sur tous les évènements · défaut ∈ catalogue FormaPago ; (1) **ENSEMBLE
|
||||||
|
émetteurs** — set-diff README `seulement …` == `update_value` de l'artefact (ni
|
||||||
|
fantôme ni manquant · accents/casse normalisés) ; (2) **DISJONCTION** — l'ensemble
|
||||||
|
exclu `(lead/visite/devis)` ∩ émetteurs = ∅ (jamais de facture sur brouillon — mord
|
||||||
|
un `devis` émetteur ET la self-contradiction du README) ; (3) **ENSEMBLE `base_field`**
|
||||||
|
— set-diff README == artefact ; (4) **rôle unique** cité backtické + nom ERPNext,
|
||||||
|
**double-ancré** sur `rbac_50_roles` (id → portail compta → nom concordant) ; (5)
|
||||||
|
**FormaPago défaut** — code == artefact · label ∈ catalogue · motif cite Cardnet (#10).
|
||||||
|
Un claim absent échoue AUSSI (traçabilité #6).
|
||||||
|
|
||||||
|
**6 morsures vérifiées** : artefact facture un BROUILLON (`devis` émetteur → mismatch
|
||||||
|
d'ensemble **ET** clash de disjonction · 2 morsures en une) · artefact renomme un
|
||||||
|
`base_field` (`montant_reservation→montant_acompte`) · artefact change FormaPago défaut
|
||||||
|
`3→1` (Efectivo) · artefact casse le rôle unique (2ᵉ évènement → `compta-comptable`) ·
|
||||||
|
README périmé « seulement contrat » (réservation retiré) · motif du défaut ne cite
|
||||||
|
plus « Cardnet » (#10) ; restauré = green : émetteurs · disjonction · base_field ·
|
||||||
|
rôle == RBAC · FormaPago == artefact · exit 0. Working tree byte-restauré
|
||||||
|
(`git checkout --`, **JAMAIS** `git clean`, interdit absolu) · **7 gates re-verts**
|
||||||
|
(`check_readme_claims`/`check_docs`/`guard_constraints`/`check_artifacts`/
|
||||||
|
`check_ci_integrity`/`validate_json`/`check_regression`).
|
||||||
|
|
||||||
|
- `ci/README.md` (clause récap du pipeline `check-readme-claims` + paragraphe détaillé
|
||||||
|
« IDENTITÉ des évènements d'émission e-CF ancrée sur `ecf_plan.json` ») mis à jour.
|
||||||
|
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
|
||||||
|
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; la connexion
|
||||||
|
Compupar réelle + l'émission e-CF en production restent côté ERPNext Backend/VPS).
|
||||||
|
- **Auto-score 4Big** : 96/100.
|
||||||
|
|||||||
+27
-1
File diff suppressed because one or more lines are too long
@@ -5557,6 +5557,166 @@ if _rp_src is not None:
|
|||||||
bad(f"RunPod endpoint · AUCUNE fiche/master prompt ne mentionne RunPod — "
|
bad(f"RunPod endpoint · AUCUNE fiche/master prompt ne mentionne RunPod — "
|
||||||
f"la surface d'identité de l'endpoint GPU s'est évaporée (régression #6)")
|
f"la surface d'identité de l'endpoint GPU s'est évaporée (régression #6)")
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Fiscal · e-CF DGII — IDENTITÉ des ÉVÈNEMENTS D'ÉMISSION (le « cœur du livrable »)
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# La section §Cross-cohérence du README (05_deliverables_mvp/fiscal/ecf_dgii/
|
||||||
|
# README.md:40-59) — présentée comme « le cœur du livrable » — transcrit À LA MAIN
|
||||||
|
# l'IDENTITÉ des emission_events d'out/ecf_plan.json (byte-gaté par check_artifacts
|
||||||
|
# ⇒ artefact faisant autorité) :
|
||||||
|
# • L48 l'ENSEMBLE des évènements qui émettent un e-CF (« seulement réservation
|
||||||
|
# et contrat ») + l'ENSEMBLE exclu « on ne facture pas un brouillon
|
||||||
|
# (lead/visite/devis) » — la RAISON D'ÊTRE du module : jamais de facture
|
||||||
|
# sur un brouillon (états SOUMIS uniquement · doc_status=1)
|
||||||
|
# • L50 l'ENSEMBLE des base_field (« montant_reservation, montant_contrat »)
|
||||||
|
# • L53 le rôle UNIQUE (« compta-fiscaliste-ecf » · « OTO Compta Fiscaliste
|
||||||
|
# eCF » · portail compta)
|
||||||
|
# • L59 la FormaPago défaut (« = 3 (Tarjeta) » · Cardnet #10)
|
||||||
|
# Le SEUL gate fiscal existant couvre le CONTRAT DE FORMAT e-NCF + le compte de
|
||||||
|
# tests ; le bloc racine, le seul agrégat « e-CF 39 tests ». RIEN ne confronte
|
||||||
|
# cette identité à l'artefact. Dérive la plus grave, #6/#5 : AJOUTER un
|
||||||
|
# emission_event sur un état BROUILLON (ex. `devis`) — l'anti-pattern EXACT que le
|
||||||
|
# module interdit (« on ne facture pas un brouillon ») — pendant que L48 dit encore
|
||||||
|
# « seulement réservation et contrat » → l'agent ERPNext Backend câblerait une
|
||||||
|
# facturation sur brouillon (e-CF émis avant soumission/encaissement). Aucune suite
|
||||||
|
# tests/ (FONCTIONS de résolution/composition, jamais la prose vs l'artefact) ne
|
||||||
|
# l'attrape — classe « vert trompeur » notée en mémoire.
|
||||||
|
#
|
||||||
|
# On RECOMPUTE les ENSEMBLES depuis ecf_plan.json (jamais une liste à la main · #6)
|
||||||
|
# et EXIGE que le README les reflète EXACTEMENT (set-diff : ni fantôme ni manquant ;
|
||||||
|
# accents/casse normalisés). Le rôle est en outre DOUBLE-ANCRÉ sur rbac_50_roles
|
||||||
|
# (id → portail compta → nom ERPNext concordant). Cohérences croisées en bonus
|
||||||
|
# (mordent un plan INTERNEMENT incohérent) : rôle UNIQUE sur tous les évènements ·
|
||||||
|
# défaut ∈ catalogue FormaPago · ensembles NON VIDES. Un claim absent échoue AUSSI
|
||||||
|
# (traçabilité #6).
|
||||||
|
# ============================================================================
|
||||||
|
ECF_RD2 = f"{D}/fiscal/ecf_dgii/README.md"
|
||||||
|
try:
|
||||||
|
_ecfp = load("fiscal/ecf_dgii/out/ecf_plan.json")
|
||||||
|
_ecf_ev = _ecfp["emission_events"]
|
||||||
|
if not _ecf_ev:
|
||||||
|
raise ValueError("emission_events vide")
|
||||||
|
ecf_updvals = {e["update_value"] for e in _ecf_ev}
|
||||||
|
ecf_bases = {e["base_field"] for e in _ecf_ev}
|
||||||
|
ecf_roleids = {e["role_id"] for e in _ecf_ev}
|
||||||
|
ecf_rolenms = {e["erpnext_role_name"] for e in _ecf_ev}
|
||||||
|
_ecf_fpd = _ecfp["forma_pago_defaut"]
|
||||||
|
_ecf_fpcat = {str(f["code"]): f["label"] for f in _ecfp["formas_pago"]}
|
||||||
|
except (OSError, KeyError, TypeError, ValueError, json.JSONDecodeError) as e:
|
||||||
|
bad(f"Fiscal e-CF évènements · ecf_plan.json illisible/inattendu : {e}")
|
||||||
|
_ecf_ev = None
|
||||||
|
|
||||||
|
if _ecf_ev is not None:
|
||||||
|
def _ecf_ai(s): # accent-insensible · minuscule · sans espaces
|
||||||
|
return "".join(c for c in unicodedata.normalize("NFKD", str(s))
|
||||||
|
if unicodedata.category(c) != "Mn").lower().replace(" ", "")
|
||||||
|
try:
|
||||||
|
ecf_doc = re.sub(r"\s+", " ", open(ECF_RD2, encoding="utf-8").read())
|
||||||
|
except OSError as e:
|
||||||
|
bad(f"Fiscal e-CF évènements · {ECF_RD2} illisible : {e}"); ecf_doc = None
|
||||||
|
|
||||||
|
# (0) cohérence interne du plan : rôle UNIQUE + défaut ∈ catalogue FormaPago
|
||||||
|
if len(ecf_roleids) != 1 or len(ecf_rolenms) != 1:
|
||||||
|
bad(f"Fiscal e-CF évènements · plan INCOHÉRENT : rôle non unique sur les "
|
||||||
|
f"évènements (ids={sorted(ecf_roleids)} noms={sorted(ecf_rolenms)}) — "
|
||||||
|
f"le README promet un « rôle dédié » unique")
|
||||||
|
_fp_code = str(_ecf_fpd.get("code"))
|
||||||
|
if _fp_code not in _ecf_fpcat:
|
||||||
|
bad(f"Fiscal e-CF évènements · forma_pago_defaut.code={_fp_code!r} ABSENT "
|
||||||
|
f"du catalogue formas_pago {sorted(_ecf_fpcat)} (plan incohérent)")
|
||||||
|
|
||||||
|
if ecf_doc is not None:
|
||||||
|
# (1) ENSEMBLE des évènements émetteurs — L48 « seulement X et Y »
|
||||||
|
m_bill = re.search(r"seulement\s+([^.]+?)\.", ecf_doc)
|
||||||
|
want = {_ecf_ai(x) for x in ecf_updvals}
|
||||||
|
if m_bill is None:
|
||||||
|
bad(f"Fiscal e-CF évènements · {ECF_RD2} — énumération « seulement … » "
|
||||||
|
f"INTROUVABLE (attendu {sorted(ecf_updvals)})")
|
||||||
|
else:
|
||||||
|
bill = {_ecf_ai(x) for x in re.split(r"\s+et\s+|,\s*", m_bill.group(1)) if x.strip()}
|
||||||
|
if bill == want:
|
||||||
|
good(f"Fiscal e-CF évènements · émetteurs README == ecf_plan "
|
||||||
|
f"({sorted(ecf_updvals)})")
|
||||||
|
else:
|
||||||
|
bad(f"Fiscal e-CF évènements · émetteurs README {sorted(bill)} ≠ "
|
||||||
|
f"ecf_plan {sorted(want)} (fantôme={sorted(bill-want)} "
|
||||||
|
f"manquant={sorted(want-bill)}) → régénérer le README")
|
||||||
|
# (2) ENSEMBLE exclu (brouillons) DISJOINT des émetteurs — anti-facture-brouillon
|
||||||
|
m_excl = re.search(r"on ne facture pas un brouillon\s*\(([^)]+)\)", ecf_doc)
|
||||||
|
if m_excl is None:
|
||||||
|
bad(f"Fiscal e-CF évènements · {ECF_RD2} — clause « on ne facture pas un "
|
||||||
|
f"brouillon (…) » INTROUVABLE (raison d'être #6 · traçabilité)")
|
||||||
|
else:
|
||||||
|
excl = {_ecf_ai(x) for x in re.split(r"[/,]", m_excl.group(1)) if x.strip()}
|
||||||
|
clash = excl & want
|
||||||
|
if clash:
|
||||||
|
bad(f"Fiscal e-CF évènements · un état déclaré BROUILLON {sorted(clash)} "
|
||||||
|
f"est ÉMETTEUR dans ecf_plan → FACTURE SUR BROUILLON (l'anti-pattern "
|
||||||
|
f"que le module interdit · #6) OU README auto-contradictoire")
|
||||||
|
else:
|
||||||
|
good(f"Fiscal e-CF évènements · brouillons {sorted(excl)} ∩ émetteurs = ∅ "
|
||||||
|
f"(jamais de facture sur brouillon)")
|
||||||
|
# (3) ENSEMBLE des base_field — L50 « (montant_reservation, montant_contrat) »
|
||||||
|
m_base = re.search(r"champ \*\*Currency réel\*\*[^(]*\(([^)]+)\)", ecf_doc)
|
||||||
|
if m_base is None:
|
||||||
|
bad(f"Fiscal e-CF évènements · {ECF_RD2} — énumération des base_field "
|
||||||
|
f"INTROUVABLE (attendu {sorted(ecf_bases)})")
|
||||||
|
else:
|
||||||
|
docbases = set(re.findall(r"`([^`]+)`", m_base.group(1)))
|
||||||
|
if docbases == ecf_bases:
|
||||||
|
good(f"Fiscal e-CF évènements · base_field README == ecf_plan "
|
||||||
|
f"({sorted(ecf_bases)})")
|
||||||
|
else:
|
||||||
|
bad(f"Fiscal e-CF évènements · base_field README {sorted(docbases)} ≠ "
|
||||||
|
f"ecf_plan {sorted(ecf_bases)} (fantôme={sorted(docbases-ecf_bases)} "
|
||||||
|
f"manquant={sorted(ecf_bases-docbases)})")
|
||||||
|
# (4) rôle UNIQUE : backtické + nom ERPNext + DOUBLE-ANCRÉ portail compta (RBAC)
|
||||||
|
_rid = next(iter(ecf_roleids)); _rnm = next(iter(ecf_rolenms))
|
||||||
|
if f"`{_rid}`" not in ecf_doc:
|
||||||
|
bad(f"Fiscal e-CF évènements · rôle dédié `{_rid}` (== ecf_plan) NON cité "
|
||||||
|
f"backtické dans le README")
|
||||||
|
elif _rnm not in ecf_doc:
|
||||||
|
bad(f"Fiscal e-CF évènements · nom ERPNext « {_rnm} » (== ecf_plan) ABSENT "
|
||||||
|
f"du README")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
_rbac = load("rbac/rbac_50_roles.json")["roles"]
|
||||||
|
_rrec = next((r for r in _rbac if r.get("id") == _rid), None)
|
||||||
|
except (OSError, KeyError, TypeError, json.JSONDecodeError) as e:
|
||||||
|
bad(f"Fiscal e-CF évènements · rbac_50_roles illisible : {e}"); _rrec = "ERR"
|
||||||
|
if _rrec is None:
|
||||||
|
bad(f"Fiscal e-CF évènements · rôle `{_rid}` (ecf_plan) INTROUVABLE dans "
|
||||||
|
f"rbac_50_roles → rôle Compta fantôme")
|
||||||
|
elif _rrec == "ERR":
|
||||||
|
pass
|
||||||
|
elif _rrec.get("portail") != "compta":
|
||||||
|
bad(f"Fiscal e-CF évènements · rôle `{_rid}` portail={_rrec.get('portail')!r} "
|
||||||
|
f"≠ « compta » (le README exige le portail compta)")
|
||||||
|
elif _rrec.get("erpnext_role_name") != _rnm:
|
||||||
|
bad(f"Fiscal e-CF évènements · nom ERPNext discordant ecf_plan « {_rnm} » "
|
||||||
|
f"vs RBAC « {_rrec.get('erpnext_role_name')} »")
|
||||||
|
else:
|
||||||
|
good(f"Fiscal e-CF évènements · rôle unique `{_rid}` (« {_rnm} ») == "
|
||||||
|
f"ecf_plan == RBAC (portail compta)")
|
||||||
|
# (5) FormaPago défaut — L59 « = 3 (Tarjeta) » · Cardnet #10
|
||||||
|
m_fp = re.search(r"FormaPago défaut\s*=\s*(\d+)\s*\(([^)]+)\)", ecf_doc)
|
||||||
|
_lbl = _ecf_fpcat.get(_fp_code, "")
|
||||||
|
if m_fp is None:
|
||||||
|
bad(f"Fiscal e-CF évènements · {ECF_RD2} — « FormaPago défaut = N (…) » "
|
||||||
|
f"INTROUVABLE (attendu {_fp_code} / {_lbl})")
|
||||||
|
elif m_fp.group(1) != _fp_code:
|
||||||
|
bad(f"Fiscal e-CF évènements · FormaPago défaut README ={m_fp.group(1)} "
|
||||||
|
f"MAIS ecf_plan dit ={_fp_code} ({_lbl}) → défaut périmé")
|
||||||
|
elif _ecf_ai(m_fp.group(2)) not in _ecf_ai(_lbl):
|
||||||
|
bad(f"Fiscal e-CF évènements · FormaPago défaut label README "
|
||||||
|
f"« {m_fp.group(2)} » incohérent avec le catalogue « {_lbl} » (code {_fp_code})")
|
||||||
|
elif "cardnet" not in _ecf_ai(_ecf_fpd.get("motif", "")):
|
||||||
|
bad(f"Fiscal e-CF évènements · forma_pago_defaut.motif ne cite plus "
|
||||||
|
f"« Cardnet » (#10) → justification du défaut évaporée")
|
||||||
|
else:
|
||||||
|
good(f"Fiscal e-CF évènements · FormaPago défaut = {_fp_code} "
|
||||||
|
f"({_lbl}) · Cardnet #10 == ecf_plan")
|
||||||
|
|
||||||
sys.exit(1 if FAIL else 0)
|
sys.exit(1 if FAIL else 0)
|
||||||
PY
|
PY
|
||||||
rc=$?
|
rc=$?
|
||||||
|
|||||||
Reference in New Issue
Block a user