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 %}
Jour Horaires Libellé Origine Protection Action {% for item in schedules %}{{ 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 %} {% endfor %}{% if not schedules %}Aucun créneau actif pour cette salle. {% endif %}
{% endif %}
{% endblock %}
+{% if selected_room_id %}Créneaux d'occupation de la salle sélectionnée Jour Horaires Libellé Origine Protection Action {% for item in schedules %}{{ 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 %} {% endfor %}{% if not schedules %}Aucun créneau actif pour cette salle. Ajoutez un créneau ou sélectionnez une autre salle.
{% endif %}
{% 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 @@
+
+ Accueil
+ {% if has_permission('dashboard.view') %}
+ Tableau de bord
+ {% endif %}
+
+ {% set work_access = has_permission('intervention.view') or has_permission('planning.view') or has_permission('prevention.view') %}
+ {% if work_access %}
+
+ Mon travail
+
+
+ {% endif %}
+
+ {% set heritage_access = has_permission('patrimoine.view') %}
+ {% if heritage_access %}
+
+ Patrimoine
+
+
+ {% endif %}
+
+ {% if has_permission('stock.view') %}
+
+ Produits & stocks
+
+
+ {% endif %}
+
+ {% if has_permission('contract.view') %}
+
+ Entreprises
+
+
+ {% endif %}
+
+ {% if has_permission('documents.view') %}
+
+ Documents
+
+
+ {% endif %}
+
+ {% if has_permission('system.admin') %}
+
+ Administration
+
+
+ {% endif %}
+
+ Alertes{% if unread %} {{ unread }} {% endif %}
+
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 @@
-
-
- Principal
+ {% include "_navigation_phase16.html" %}
+ {% if false %}
+
+
+ {% endif %}
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 %}
diff --git a/app_new/templates/documents/fds.html b/app_new/templates/documents/fds.html
index 89fba8a..d17cc98 100644
--- a/app_new/templates/documents/fds.html
+++ b/app_new/templates/documents/fds.html
@@ -3,5 +3,5 @@
{% block content %}
Fiches de données de sécurité Les FDS sont consultées depuis les documents des références commerciales accessibles.
Centre documentaire
-{% if results %}
Produit Fabricant/fournisseur Fichier Version/date Emplacements Action {% for item in results %}{% if item.context_url %}{{ item.context_label }} {% else %}{{ item.context_label }}{% endif %} {{ item.issuer or '—' }} {{ item.filename }} {{ item.version or '—' }}{% if item.document_date %}{{ item.document_date|datetime_fmt }} {% endif %} {{ item.location_summary }} Télécharger {% endfor %}
{% else %}
Aucune FDS accessible n'a été trouvée.
{% endif %}
+{% if results %}
Produit Fabricant/fournisseur Fichier Version/date Emplacements Action {% for item in results %}{% if item.context_url %}{{ item.context_label }} {% else %}{{ item.context_label }}{% endif %} {{ item.issuer or '—' }} {{ item.filename }} {{ item.version or '—' }}{% if item.document_date %}{{ item.document_date|datetime_fmt }} {% endif %} {{ item.location_summary }} Télécharger {% if item.context_url %}Voir le produit {% endif %} {% endfor %}
{% else %}
Aucune FDS accessible n'a été trouvée. Les FDS sont ajoutées depuis les références commerciales des produits.
{% endif %}
{% 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 @@
Document Type Lié à Localisation Date Actions
{% for item in results %}{{ 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 {% endfor %}
- {% 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