From 764cf005813d7ec0beb8d78e8567a908b9809c98 Mon Sep 17 00:00:00 2001 From: Domi31tls Date: Tue, 16 Jun 2026 12:00:10 +0200 Subject: [PATCH] =?UTF-8?q?refactor(gui):=20int=C3=A9grer=20les=20R=C3=A8g?= =?UTF-8?q?les=20dans=20Administration=20>=20Profils?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Retour Dom : « les règles du profil doivent être dans le menu profil, pas à part ! ». Même logique que le Masquage — les règles qui influencent l'anonymisation appartiennent au profil ; un sous-onglet séparé crée la même confusion. - Retrait du sous-onglet « Administration > Règles » (_SUBTABS, builder, méthode _build_regles supprimée). Sous-onglets restants : Réglages / Profils / Partage. - Section « Profils > Règles du profil » enrichie : wording clair (règles d'anonymisation portées par le profil), aperçu illustratif de la table des règles (réutilise _rule_row + _HELP_REGLES), édition fine annoncée « à venir ». - Abandon du « Testeur de règle » (écran outil global) pour ne pas réintroduire un second réglage métier. Cible UX : Réglages / Profils (Général・Masquage・Mots・Moteurs・Règles du profil) / Partage. Test obsolète test_rules_subtab_has_no_unexplained_2 remplacé par test_no_separate_rules_subtab. 262 tests unit OK (0 régression), self-test OK, nav 3 sous-onglets + section Règles dans Profils + thème OK. Préserve d8bc0cd + GO Qwen. Aucun build/push sans GO Dom. Co-Authored-By: Claude Opus 4.8 (1M context) --- gui_v6/tabs/tab_config.py | 59 ++++++++++------------------- tests/unit/test_gui_v6_app_shell.py | 10 +++-- tests/unit/test_gui_v6_profiles.py | 22 +++++++++++ 3 files changed, 47 insertions(+), 44 deletions(-) diff --git a/gui_v6/tabs/tab_config.py b/gui_v6/tabs/tab_config.py index b89a725..4c2088f 100644 --- a/gui_v6/tabs/tab_config.py +++ b/gui_v6/tabs/tab_config.py @@ -24,7 +24,6 @@ _SUBTABS = [ ("reg", "⚙️ Réglages"), ("pro", "👤 Profils"), ("shr", "🔄 Partage"), - ("rul", "🛡️ Règles"), ] _DETECTION_OPTIONS = [ @@ -210,7 +209,6 @@ class ConfigTab(ctk.CTkFrame): "reg": self._build_reglages, "pro": self._build_profils, "shr": self._build_partage, - "rul": self._build_regles, } for key, builder in builders.items(): panel = ctk.CTkFrame(self._body, fg_color="transparent") @@ -528,7 +526,25 @@ class ConfigTab(ctk.CTkFrame): rules = ui_kit.Card(parent, p, title="🛡️ Règles du profil") rules.pack(fill="x", pady=(8, 0)) - self._note(rules, "Les règles embarquées par profil seront éditables prochainement.") + rules_intro = ctk.CTkFrame(rules, fg_color="transparent") + rules_intro.pack(fill="x", padx=12, pady=(0, 2)) + ctk.CTkLabel( + rules_intro, + text="Règles d'anonymisation portées par ce profil (adaptées à votre établissement).", + text_color=p["text_dim"], font=ui_kit.font(12), anchor="w", justify="left", wraplength=520, + ).pack(side="left", padx=(0, 6)) + ui_kit.help_button(rules_intro, p, _HELP_REGLES, title="Les Règles du profil").pack(side="right") + headers = ctk.CTkFrame(rules, fg_color="transparent") + headers.pack(fill="x", padx=12, pady=(2, 4)) + for text, width in [("Label", 190), ("Type", 80), ("Cible → Résultat", 210), ("Statut", 70), ("", 70)]: + ctk.CTkLabel(headers, text=text.upper(), width=width, anchor="w", text_color=p["text_muted"], font=ui_kit.font(10, "bold")).pack(side="left") + for row in [ + ("Masquer le sigle CHUXX", "exact", "CHUXX → [MASK]", "Actif"), + ("Préserver “classification internationale”", "preserve", "conservé tel quel", "Actif"), + ("Identifier N° 1234567", "norm-id", "N° 1234567 → [NDA]", "Candidat"), + ]: + self._rule_row(rules, row) + self._note(rules, "Aperçu illustratif. L'édition fine des règles du profil arrivera dans une prochaine version.") self._mockup_button(rules, "+ Ajouter une règle").pack(anchor="w", padx=12, pady=(0, 12)) self._pro_refresh_and_load() @@ -716,43 +732,6 @@ class ConfigTab(ctk.CTkFrame): self._note(import_card, "Fusionne la configuration reçue avec vos réglages locaux.") self._mockup_button(import_card, "⬆ Importer (.json)").pack(anchor="w", padx=12, pady=(0, 12)) - def _build_regles(self, parent) -> None: - p = self._p - self._section_intro( - parent, - "Règles d'adaptation du moteur à votre établissement.", - _HELP_REGLES, - "Les Règles", - ) - card = ui_kit.Card(parent, p, title="🛡️ Règles actives") - card.pack(fill="x", pady=(0, 8)) - self._note(card, "Ces règles adaptent le moteur à votre établissement. Chaque règle est validée avant activation.") - headers = ctk.CTkFrame(card, fg_color="transparent") - headers.pack(fill="x", padx=12, pady=(0, 4)) - for text, width in [("Label", 190), ("Type", 80), ("Cible → Résultat", 210), ("Statut", 70), ("", 70)]: - ctk.CTkLabel(headers, text=text.upper(), width=width, anchor="w", text_color=p["text_muted"], font=ui_kit.font(10, "bold")).pack(side="left") - for row in [ - ("Masquer le sigle CHUXX", "exact", "CHUXX → [MASK]", "Actif"), - ("Préserver “classification internationale”", "preserve", "conservé tel quel", "Actif"), - ("Identifier N° 1234567", "norm-id", "N° 1234567 → [NDA]", "Candidat"), - ]: - self._rule_row(card, row) - actions = ctk.CTkFrame(card, fg_color="transparent") - actions.pack(fill="x", padx=12, pady=(8, 12)) - self._mockup_button(actions, "+ Nouvelle règle", primary=True).pack(side="left", padx=(0, 8)) - self._mockup_button(actions, "🔄 Recharger").pack(side="left") - - sim = ui_kit.Card(parent, p, title="🧪 Testeur de règle") - sim.pack(fill="x") - ctk.CTkLabel(sim, text="Texte de test", text_color=p["text_muted"], font=ui_kit.font(12)).pack(anchor="w", padx=12) - txt = ctk.CTkTextbox(sim, height=74, fg_color=p["divider"], text_color=p["text"], border_color=p["card_border"], border_width=1) - txt.pack(fill="x", padx=12, pady=(5, 8)) - txt.insert("1.0", "Compte rendu CHUXX, patient N° 1234567.") - btns = ctk.CTkFrame(sim, fg_color="transparent") - btns.pack(fill="x", padx=12, pady=(0, 12)) - self._mockup_button(btns, "▶ Tester", primary=True).pack(side="left", padx=(0, 8)) - self._mockup_button(btns, "✖ Fermer").pack(side="left") - # -- helpers aide / maquette ----------------------------------------- def _section_intro(self, parent, sentence: str, help_text: str, help_title: str) -> None: diff --git a/tests/unit/test_gui_v6_app_shell.py b/tests/unit/test_gui_v6_app_shell.py index 5fb922d..4e0d59d 100644 --- a/tests/unit/test_gui_v6_app_shell.py +++ b/tests/unit/test_gui_v6_app_shell.py @@ -53,13 +53,15 @@ def test_main_tab_renamed_to_administration(): assert not any("Configuration" in lbl for lbl in labels) -def test_rules_subtab_has_no_unexplained_2(): - """Retour Dom #3 : « Règles 2 » incompréhensible → simple « Règles ».""" +def test_no_separate_rules_subtab(): + """Retour Dom : les règles appartiennent au profil → plus de sous-onglet + « Règles » séparé (et donc plus de « Règles 2 » incompréhensible).""" from gui_v6.tabs.tab_config import _SUBTABS + keys = [key for key, _ in _SUBTABS] labels = [label for _, label in _SUBTABS] - assert any(lbl.strip() == "🛡️ Règles" for lbl in labels) - assert not any("Règles 2" in lbl or "Règles 2" in lbl for lbl in labels) + assert "rul" not in keys + assert not any("Règles" in lbl for lbl in labels) def test_help_button_opens_help_window(app): diff --git a/tests/unit/test_gui_v6_profiles.py b/tests/unit/test_gui_v6_profiles.py index 03a49b8..1da9c54 100644 --- a/tests/unit/test_gui_v6_profiles.py +++ b/tests/unit/test_gui_v6_profiles.py @@ -273,3 +273,25 @@ def test_masquage_moved_into_profils(ctk_root, tmp_path, monkeypatch): tab._on_mask_template_saved(saved) assert tab._pro_template_var.get().endswith("depuis_editeur.json") tab.destroy() + + +def test_regles_moved_into_profils(ctk_root, tmp_path, monkeypatch): + """Retour Dom : le sous-onglet Règles séparé est retiré ; les règles du + profil sont une section de Profils.""" + from gui_v6.tabs import tab_config + + keys = [k for k, _ in tab_config._SUBTABS] + assert "rul" not in keys # plus de sous-onglet Règles séparé + + monkeypatch.setattr(tab_config, "_app_base_dir", lambda: tmp_path) + tab = tab_config.ConfigTab(ctk_root) + tab._show_sub("pro") + tab.update_idletasks() + + # la section des règles du profil est dans le panneau Profils + texts = " | ".join(_all_texts(tab._panels["pro"])) + assert "Règles d'anonymisation portées par ce profil" in texts + assert "Masquer le sigle CHUXX" in texts # table de règles relocalisée dans Profils + # le builder du sous-onglet séparé n'existe plus + assert "rul" not in tab._panels + tab.destroy()