[DTP-Worker] Sprint 5 · Générateur DocType CONFOTUR Application (ONAPI/Legal · roadmap L55)

DocType custom porteur des dossiers d'incitation touristique CONFOTUR (Ley
158-01, RD), référencé par RBAC (3 rôles) et le workflow vente mais jamais
généré. Permissions = permissions_cibles RBAC mot pour mot ; is_submittable
déduit de l'action submit ; estado/dossier_vente dérivés du workflow ; aucun
taux/loi/montant fabriqué (#6). 14 invariants, 44 tests, hand-off dans out/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-07-30 08:09:41 +00:00
parent c0d2e21ef3
commit 35a20247f8
15 changed files with 1810 additions and 1 deletions
@@ -0,0 +1,57 @@
"""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