[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:
@@ -0,0 +1,151 @@
|
||||
"""Modele Frappe/ERPNext v15 : structure native du fixture `DocType`.
|
||||
|
||||
Separe la CONNAISSANCE FRAPPE (types de champ legitimes, drapeaux de docfield,
|
||||
modele DocPerm, enveloppe du document DocType) de l'assemblage metier
|
||||
(`builder.py`). Contrainte #1 « ERPNext natif = priorite absolue » : on produit
|
||||
le DocType standard du moteur Frappe, porteur des dossiers d'incitation CONFOTUR.
|
||||
|
||||
Aucun acces VPS : chaque fonction renvoie un dict serialisable que l'agent
|
||||
ONAPI/Legal importera via `bench` (hand-off · #8).
|
||||
|
||||
Nota vs `crm/dossier_vente/dvlib/frappe.py` : ce module ajoute la permission
|
||||
native `report` (le role `legal-directeur` la porte cote RBAC). Les deux modules
|
||||
restent volontairement independants — chaque livrable possede sa connaissance
|
||||
Frappe, on n'ouvre que ce que le contrat emploie reellement.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
# Types de champ Frappe employes par ce DocType. Hors de cet ensemble = invention
|
||||
# -> refusee par `docfield()`.
|
||||
VALID_FIELDTYPES: frozenset[str] = frozenset(
|
||||
{
|
||||
"Section Break",
|
||||
"Column Break",
|
||||
"Data",
|
||||
"Select",
|
||||
"Link",
|
||||
"Date",
|
||||
"Datetime",
|
||||
"Small Text",
|
||||
"Text",
|
||||
"Check",
|
||||
}
|
||||
)
|
||||
|
||||
# Types « de mise en page » : ni requis, ni porteurs de donnee.
|
||||
LAYOUT_FIELDTYPES: frozenset[str] = frozenset({"Section Break", "Column Break"})
|
||||
|
||||
# Permissions natives du child table `DocPerm` d'un DocType Frappe (v15).
|
||||
# `report` est inclus (porte par le role legal-directeur cote RBAC).
|
||||
VALID_PERMS: frozenset[str] = frozenset(
|
||||
{"read", "write", "create", "submit", "cancel", "amend", "delete",
|
||||
"print", "email", "report", "import", "export", "share"}
|
||||
)
|
||||
|
||||
# Ordre de serialisation stable des drapeaux DocPerm (diff propre).
|
||||
_PERM_ORDER = ("read", "write", "create", "submit", "cancel", "amend", "delete",
|
||||
"print", "email", "report", "import", "export", "share")
|
||||
|
||||
# Drapeaux de docfield connus (defaut 0) — on ne serialise que ceux a 1.
|
||||
_DOCFIELD_FLAGS = ("reqd", "read_only", "in_list_view", "in_standard_filter", "hidden", "bold")
|
||||
|
||||
|
||||
def docfield(
|
||||
fieldname: str,
|
||||
fieldtype: str,
|
||||
*,
|
||||
label: str | None = None,
|
||||
options: str | None = None,
|
||||
default: str | None = None,
|
||||
flags: dict[str, int] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Une ligne de la table enfant `fields` d'un DocType."""
|
||||
if fieldtype not in VALID_FIELDTYPES:
|
||||
raise ValueError(f"Fieldtype non natif Frappe pour {fieldname!r} : {fieldtype!r}")
|
||||
row: dict[str, Any] = {"fieldname": fieldname, "fieldtype": fieldtype}
|
||||
if label is not None:
|
||||
row["label"] = label
|
||||
if options is not None:
|
||||
row["options"] = options
|
||||
if default is not None:
|
||||
row["default"] = default
|
||||
flags = flags or {}
|
||||
for f in _DOCFIELD_FLAGS:
|
||||
v = int(flags.get(f, 0))
|
||||
if v not in (0, 1):
|
||||
raise ValueError(f"Drapeau {f} de {fieldname!r} non booleen : {v!r}")
|
||||
if v:
|
||||
row[f] = 1
|
||||
return row
|
||||
|
||||
|
||||
def section_break(fieldname: str, label: str) -> dict[str, Any]:
|
||||
return {"fieldname": fieldname, "fieldtype": "Section Break", "label": label}
|
||||
|
||||
|
||||
def permission_row(role: str, perms: set[str], *, permlevel: int = 0) -> dict[str, Any]:
|
||||
"""Ligne `permissions` (DocPerm integre) d'un DocType.
|
||||
|
||||
Emet uniquement les drapeaux a 1 (defaut sur = refus), dans l'ordre stable
|
||||
`_PERM_ORDER` — diff reproductible.
|
||||
"""
|
||||
unknown = perms - VALID_PERMS
|
||||
if unknown:
|
||||
raise ValueError(f"Permission(s) non native(s) pour {role!r} : {sorted(unknown)}")
|
||||
row: dict[str, Any] = {"role": role, "permlevel": permlevel}
|
||||
for p in _PERM_ORDER:
|
||||
if p in perms:
|
||||
row[p] = 1
|
||||
return row
|
||||
|
||||
|
||||
def naming_series_field(series: str) -> dict[str, Any]:
|
||||
"""Le champ Select `naming_series` requis quand `autoname = "naming_series:"`.
|
||||
|
||||
Frappe nomme le document depuis ce champ ; ses `options` portent le motif de
|
||||
serie. Rendu `read_only` (serie imposee, pas de saisie libre).
|
||||
"""
|
||||
return docfield(
|
||||
"naming_series",
|
||||
"Select",
|
||||
label="Serie de nommage",
|
||||
options=series,
|
||||
default=series,
|
||||
flags={"read_only": 1},
|
||||
)
|
||||
|
||||
|
||||
def doctype_doc(
|
||||
*,
|
||||
name: str,
|
||||
module: str,
|
||||
is_submittable: int,
|
||||
title_field: str,
|
||||
search_fields: str,
|
||||
track_changes: int,
|
||||
track_seen: int,
|
||||
fields: list[dict],
|
||||
permissions: list[dict],
|
||||
) -> dict[str, Any]:
|
||||
"""Le document `DocType` complet (fixture custom pret pour `bench`)."""
|
||||
return {
|
||||
"doctype": "DocType",
|
||||
"name": name,
|
||||
"module": module,
|
||||
"custom": 1,
|
||||
"is_submittable": 1 if is_submittable else 0,
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"autoname": "naming_series:",
|
||||
"title_field": title_field,
|
||||
"search_fields": search_fields,
|
||||
"track_changes": 1 if track_changes else 0,
|
||||
"track_seen": 1 if track_seen else 0,
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [f["fieldname"] for f in fields],
|
||||
"fields": fields,
|
||||
"permissions": permissions,
|
||||
}
|
||||
Reference in New Issue
Block a user