"""Lecture des roles RBAC qui referencent un DocType donne (source unique · #6). Le contrat `rbac_50_roles.json` est l'UNIQUE source de verite des roles ERPNext (valide par `rbac.schema.json` dans le job CI `rbac-tests`). Ici on ne fait que le LIRE : pour le DocType `CONFOTUR Application`, quels roles y ont acces et avec quelles actions. Ces `permissions_cibles` deviennent, mot pour mot, les DocPerm du DocType genere -> les permissions du fixture SONT le contrat RBAC, pas une reinvention. Reutilisation (workflow #5, zero duplication) : la resolution `id -> nom Frappe` et le `portail` restent delegues au `RoleResolver` du module CRM voisin — on ne redefinit ni le format des roles ni leur nommage. """ from __future__ import annotations import json import os from typing import Any _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") def load_contract(path: str = RBAC_CONTRACT_PATH) -> dict[str, Any]: with open(path, encoding="utf-8") as fh: return json.load(fh) def roles_targeting(doctype: str, contract: dict[str, Any]) -> list[dict[str, Any]]: """Roles dont un `permissions_cibles` vise `doctype`. Retour trie par `id` de role (determinisme). Chaque element : {role_id, actions: [..], custom: bool} `actions` reprend, sans ajout ni retrait, la liste declaree dans le contrat. Leve si un meme role declare deux fois le meme doctype (contrat ambigu). """ out: list[dict[str, Any]] = [] for role in contract.get("roles", []): matches = [pc for pc in role.get("permissions_cibles", []) if pc.get("doctype") == doctype] if not matches: continue if len(matches) > 1: raise ValueError( f"Role {role.get('id')!r} declare {doctype!r} plusieurs fois " f"dans permissions_cibles (contrat ambigu)." ) pc = matches[0] out.append({ "role_id": role["id"], "actions": list(pc.get("actions", [])), "custom": bool(pc.get("custom", False)), }) out.sort(key=lambda r: r["role_id"]) return out