9b502e67fe
CI / Contraintes NON-NÉGOCIABLES (CLAUDE.md) (push) Has been cancelled
CI / Validation JSON (schémas Faisabilité) (push) Has been cancelled
CI / Qualité documentaire (liens + 4Big) (push) Has been cancelled
CI / Reproductibilité des artefacts out/ (build == commité) (push) Has been cancelled
CI / Fraîcheur matrice de régression (run == commité) (push) Has been cancelled
CI / Intégrité du câblage CI (gate agrège tout · gates statiques verrouillés) (push) Has been cancelled
CI / Intégrité des chiffres du README (valeur == artefact cité · (push) Has been cancelled
CI / Publiciste · parser + schéma + generator (unittest) (push) Has been cancelled
CI / RBAC · 50 rôles + schéma (unittest) (push) Has been cancelled
CI / Faisabilité · générateur 4 volets + round-trip (unittest) (push) Has been cancelled
CI / RBAC · fixtures ERPNext (Role + Custom DocPerm) (push) Has been cancelled
CI / RBAC · plan User Permission (row-level) (push) Has been cancelled
CI / RBAC · Role Profile (bundles par portail) (push) Has been cancelled
CI / RBAC · run-book d'application unifié (agrégat 3 volets) (push) Has been cancelled
CI / Faisabilité · dossier bancable trilingue FR/EN/ES (push) Has been cancelled
CI / CRM · workflow vente ERPNext (lead → CONFOTUR) (push) Has been cancelled
CI / CRM · DocType porteur OTO Dossier Vente (push) Has been cancelled
CI / CRM · barème commissions vendeurs (push) Has been cancelled
CI / CRM · Financement Bancaire (gate hypothécaire RD) (push) Has been cancelled
CI / Fiscal · e-CF DGII (Compupar) (push) Has been cancelled
CI / Frontend · Workspaces 5 portails rôle (push) Has been cancelled
CI / Legal · DocType CONFOTUR Application (push) Has been cancelled
CI / QA · Audit 5D conformité (push) Has been cancelled
CI / SEO · mots-clés trilingues + schema.org + hreflang (push) Has been cancelled
CI / Chat OTOIA · montage par portail (Custom Block) (push) Has been cancelled
CI / QA · Audit 4Big (95+/100 sur 100% deliverables) (push) Has been cancelled
CI / Démo · Scénarios (run-sheet P07 banquier / P05 client) (push) Has been cancelled
CI / QA · Matrice de régression exhaustive (Sprint 8) (push) Has been cancelled
CI / DevOps · Run-book de déploiement VPS unifié (Sprint 8) (push) Has been cancelled
CI / QA · Matrice d'acceptation / traçabilité MVP (Sprint 8) (push) Has been cancelled
CI / Mobile · config app Expo/EAS (navigation par rôle) (push) Has been cancelled
CI / E2E baseline Playwright (manuel) (push) Has been cancelled
CI / Gate qualité (agrégat) (push) Has been cancelled
57 lines
2.2 KiB
Python
57 lines
2.2 KiB
Python
"""Résolution des rôles du module Financement Bancaire depuis le contrat RBAC.
|
|
|
|
Réutilisation (workflow #5 : zéro duplication de VALEUR) de l'unique source de
|
|
vérité des rôles ERPNext : `05_deliverables_mvp/rbac/rbac_50_roles.json` (validé
|
|
par `rbac.schema.json` dans le job CI `rbac-tests`). Le contrat du module
|
|
(`financement_spec.json`) ne cite JAMAIS un nom de rôle Frappe en dur : il
|
|
référence l'`id` stable d'un rôle RBAC → ici on résout `id → erpnext_role_name`.
|
|
|
|
Conséquence anti-invention (#6) : un `role_id` absent du contrat RBAC lève une
|
|
erreur (aucun rôle fabriqué), et renommer un rôle côté RBAC se propage
|
|
automatiquement au module sans édition manuelle.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
|
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
_DELIVERABLES = os.path.normpath(os.path.join(_HERE, "..", "..", ".."))
|
|
RBAC_CONTRACT_PATH = os.path.join(_DELIVERABLES, "rbac", "rbac_50_roles.json")
|
|
|
|
|
|
class RoleResolver:
|
|
"""Table `role_id → erpnext_role_name` construite depuis le contrat RBAC."""
|
|
|
|
def __init__(self, contract: dict) -> None:
|
|
self._by_id: dict[str, dict] = {}
|
|
for role in contract.get("roles", []):
|
|
rid = role.get("id")
|
|
if rid is None:
|
|
raise ValueError("Rôle RBAC sans `id` — contrat corrompu.")
|
|
if rid in self._by_id:
|
|
raise ValueError(f"`id` de rôle RBAC dupliqué : {rid!r}")
|
|
self._by_id[rid] = role
|
|
|
|
@classmethod
|
|
def from_path(cls, path: str = RBAC_CONTRACT_PATH) -> "RoleResolver":
|
|
with open(path, encoding="utf-8") as fh:
|
|
return cls(json.load(fh))
|
|
|
|
def erpnext_name(self, role_id: str) -> str:
|
|
"""`id` RBAC → nom de rôle Frappe. Lève si l'`id` n'existe pas."""
|
|
role = self._by_id.get(role_id)
|
|
if role is None:
|
|
raise KeyError(
|
|
f"role_id {role_id!r} introuvable dans rbac_50_roles.json "
|
|
f"(aucun rôle inventé · #6)."
|
|
)
|
|
name = role.get("erpnext_role_name")
|
|
if not name:
|
|
raise ValueError(f"Rôle {role_id!r} sans `erpnext_role_name`.")
|
|
return name
|
|
|
|
def known_ids(self) -> frozenset[str]:
|
|
return frozenset(self._by_id)
|