From 2080f01aa1c9f1347a676ea742b9b1f419f3bcbe Mon Sep 17 00:00:00 2001 From: root Date: Sun, 23 Aug 2026 00:02:46 +0000 Subject: [PATCH] =?UTF-8?q?feat(ui):=20add=20m=C3=A9tier=20navigation=20an?= =?UTF-8?q?d=20UX=20guidance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/planning/room_schedules.html | 2 +- app_new/templates/_navigation_phase16.html | 175 ++++++++++++++++++ app_new/templates/admin/permissions.html | 5 +- app_new/templates/base.html | 11 +- app_new/templates/dashboard/index.html | 1 + app_new/templates/documents/fds.html | 2 +- app_new/templates/documents/index.html | 2 +- tests/integration/test_phase16_navigation.py | 65 +++++++ 8 files changed, 254 insertions(+), 9 deletions(-) create mode 100644 app_new/templates/_navigation_phase16.html create mode 100644 tests/integration/test_phase16_navigation.py diff --git a/app_new/planning/templates/planning/room_schedules.html b/app_new/planning/templates/planning/room_schedules.html index 9f425db..74bfcb3 100644 --- a/app_new/planning/templates/planning/room_schedules.html +++ b/app_new/planning/templates/planning/room_schedules.html @@ -1,4 +1,4 @@ {% extends "base.html" %}{% block title %}Planning des salles{% endblock %}{% block content %}

Planning interne des salles

Les créneaux restent utilisables sans Pronote. Leur provenance est indiquée pour vous aider à les comprendre.

Ajouter un créneau
-{% if selected_room_id %}
{% for item in schedules %}{% endfor %}{% if not schedules %}{% endif %}
JourHorairesLibelléOrigineProtectionAction
{{ item.day_of_week }}{{ item.start_time }}–{{ item.end_time }}{{ item.course_name or item.subject or item.event_type }}{% if item.conflict_note %}
Conflit à résoudre
{{ item.conflict_note }}{% endif %}
{% if item.source == 'pronote' %}Mis à jour par Pronote{% elif item.source == 'import' %}Importé{% else %}Saisi manuellement{% endif %}{% if item.protected_from_sync %}Conservé lors des synchronisations{% else %}Peut être repris par Pronote{% endif %}
{% if item.source == 'pronote' %}
{% endif %}
Aucun créneau actif pour cette salle.
{% endif %}
{% endblock %} +{% if selected_room_id %}
{% for item in schedules %}{% endfor %}{% if not schedules %}{% endif %}
Créneaux d'occupation de la salle sélectionnée
JourHorairesLibelléOrigineProtectionAction
{{ item.day_of_week }}{{ item.start_time }}–{{ item.end_time }}{{ item.course_name or item.subject or item.event_type }}{% if item.conflict_note %}
Conflit à résoudre
{{ item.conflict_note }}{% endif %}
{% if item.source == 'pronote' %}Mis à jour par Pronote{% elif item.source == 'import' %}Importé{% else %}Saisi manuellement{% endif %}{% if item.protected_from_sync %}Conservé lors des synchronisations{% else %}Peut être repris par Pronote{% endif %}
{% if item.source == 'pronote' %}
{% endif %}
Aucun créneau actif pour cette salle. Ajoutez un créneau ou sélectionnez une autre salle.
{% endif %}{% endblock %} diff --git a/app_new/templates/_navigation_phase16.html b/app_new/templates/_navigation_phase16.html new file mode 100644 index 0000000..7f43443 --- /dev/null +++ b/app_new/templates/_navigation_phase16.html @@ -0,0 +1,175 @@ + diff --git a/app_new/templates/admin/permissions.html b/app_new/templates/admin/permissions.html index 8f6ff00..0a111b7 100644 --- a/app_new/templates/admin/permissions.html +++ b/app_new/templates/admin/permissions.html @@ -13,7 +13,7 @@
- Les rôles peuvent cumuler plusieurs permissions. Le code technique reste affiché à titre secondaire pour le diagnostic. + Les rôles peuvent cumuler plusieurs permissions. Les administrateurs disposent automatiquement des droits d'administration de leur rôle ; cette page explique les droits attribués sans modifier leur portée.
@@ -33,7 +33,8 @@ {% else %}
{% for link in role.permission_links if link.effect == 'allow' and link.permission.is_active %} - {{ link.permission.name }} ({{ link.permission.code }}) + {% set permission_label, permission_description = permission_metadata(link.permission.code) %} + {{ permission_label }} ({{ link.permission.code }}) {% endfor %}
{% endif %}{% endif %} diff --git a/app_new/templates/base.html b/app_new/templates/base.html index ea19208..0c83be3 100644 --- a/app_new/templates/base.html +++ b/app_new/templates/base.html @@ -160,9 +160,11 @@
diff --git a/app_new/templates/dashboard/index.html b/app_new/templates/dashboard/index.html index 5d22da3..afb0a7a 100644 --- a/app_new/templates/dashboard/index.html +++ b/app_new/templates/dashboard/index.html @@ -10,6 +10,7 @@ {% block content %}

Tableau de bord

+

Retrouvez ici les interventions, équipements et stocks qui demandent votre attention. Utilisez le menu pour ouvrir une action ou un référentiel.

{% if dashboard_permissions.ai %} {% endblock %} diff --git a/app_new/templates/documents/index.html b/app_new/templates/documents/index.html index 4279959..e4c527b 100644 --- a/app_new/templates/documents/index.html +++ b/app_new/templates/documents/index.html @@ -19,7 +19,7 @@
{% for item in results %}{% endfor %}
DocumentTypeLié àLocalisationDateActions
{{ item.title }}
{{ item.filename }}
{{ item.document_type_label }}{% if item.context_url %}{{ item.context_label }}{% else %}{{ item.context_label }}{% endif %}{{ item.location_summary }}{% if item.location_labels|length > 1 %}
Détail
    {% for location in item.location_labels %}
  • {{ location }}
  • {% endfor %}
{% endif %}
{{ item.document_date|datetime_fmt if item.document_date else '—' }} Télécharger
- {% else %}
Aucun document accessible n'a été trouvé. Les documents sont ajoutés depuis les fiches des équipements, interventions et produits.
{% endif %} + {% else %}
Aucun document accessible n'a été trouvé.
Les documents sont ajoutés depuis les fiches des équipements, interventions et produits.
{% endif %} {% if total > per_page %}{% endif %} {% endblock %} diff --git a/tests/integration/test_phase16_navigation.py b/tests/integration/test_phase16_navigation.py new file mode 100644 index 0000000..765419e --- /dev/null +++ b/tests/integration/test_phase16_navigation.py @@ -0,0 +1,65 @@ +"""Couverture de la navigation métier introduite en phase 1.6.""" + +from app_new import db +from app_new.core.models.rbac import Role, UserRole +from app_new.core.models.user import User + + +def test_admin_sees_new_menu_and_legacy_reference(authenticated_client): + response = authenticated_client.get("/auth/profile") + assert response.status_code == 200 + body = response.get_data(as_text=True) + for label in ("Mon travail", "Patrimoine", "Produits & stocks", "Documents", "Administration"): + assert label in body + assert "Ancien menu" in body + assert "Référentiel temporaire" in body or "temporaire" in body + + +def test_non_admin_does_not_see_admin_navigation(client, app): + with app.app_context(): + role = Role.query.filter_by(slug="lecture", is_active=True).first() + user = User(username="phase16_lecture", email="phase16_lecture@gmao.local", full_name="Lecture phase 1.6", is_active=True) + user.set_password("phase16-lecture-password") + db.session.add(user) + db.session.flush() + db.session.add(UserRole(user_id=user.id, role_id=role.id)) + db.session.commit() + user_id = user.id + try: + login = client.post("/auth/login", data={"username": "phase16_lecture", "password": "phase16-lecture-password"}) + assert login.status_code == 302 + response = client.get("/auth/profile") + assert response.status_code == 200 + body = response.get_data(as_text=True) + assert "Administration" not in body + assert "Ancien menu" not in body + finally: + with app.app_context(): + user = db.session.get(User, user_id) + if user: + db.session.delete(user) + db.session.commit() + + +def test_new_menu_destinations_do_not_return_accidental_404_or_500(authenticated_client): + paths = ( + "/interventions/", + "/planning/rooms", + "/planning/", + "/equipments/", + "/equipments/buildings/", + "/equipments/zones/", + "/equipments/rooms/", + "/cleaning/products", + "/cleaning/references", + "/cleaning/stock", + "/companies/", + "/documents/", + "/documents/fds", + "/admin/users", + "/admin/roles", + "/admin/permissions", + ) + for path in paths: + response = authenticated_client.get(path) + assert response.status_code not in (404, 500), path