diff --git a/VERSION b/VERSION index f876286..7ba2705 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.0-dev.9 +0.1.0-dev.10 diff --git a/app_new/core/preventive_catalog.py b/app_new/core/preventive_catalog.py new file mode 100644 index 0000000..39d9f9f --- /dev/null +++ b/app_new/core/preventive_catalog.py @@ -0,0 +1,160 @@ +"""Catalogue préventif historique embarqué. + +Le blob est une extraction figée du dump de référence (330 lignes de +``lot_tasks``). Le dump n'est jamais lu à l'exécution : cette représentation +compacte permet de garder une seule source embarquée et réutilisable par le +setup wizard et les tests de comparaison. +""" +import base64 +import json +import zlib + +_BLOB = ( +"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" +) +_FIELDS = ("lot", "category", "num_tache", "tache", "periodicite", + "contrat", "gestionnaire", "jours_entre_interventions", + "type_maintenance", "duree_minutes", "trigger_type", "trigger_event", + "meter_threshold", "season_start_month", "season_end_month", + "advance_days", "is_active", "application_scope", + "unavailable_policy", "after_repair_policy", "is_safety_critical") + + +def load_preventive_catalog(): + raw = json.loads(zlib.decompress(base64.b64decode(_BLOB))) + return [dict(zip(_FIELDS, row)) for row in raw] + + +LOT_TASKS = tuple(load_preventive_catalog()) + diff --git a/app_new/core/routes/setup_wizard.py b/app_new/core/routes/setup_wizard.py index 8ebed29..73e5130 100644 --- a/app_new/core/routes/setup_wizard.py +++ b/app_new/core/routes/setup_wizard.py @@ -13,12 +13,12 @@ from ...extensions import db, csrf from ..models.user import User from ..models.college import College, Building, Zone, Room from ..models.equipment import EquipmentCategory -from ..models.maintenance import Lot +from ..models.maintenance import Lot, LotTask from ..models.rbac import Role from ..services.rbac import ensure_rbac_catalog, replace_user_roles from ..authorization import has_permission from ..models.planning import WorkSchedule, CollegeClosure, ClosureWorkDay -from ..setup_catalog import EQUIPMENT_CATEGORIES, LOTS, grouped_catalog +from ..setup_catalog import EQUIPMENT_CATEGORIES, LOTS, LOT_TASKS, grouped_catalog, task_count_by_lot setup_wizard_bp = Blueprint('setup_wizard', __name__, url_prefix='/setup-wizard') @@ -208,7 +208,8 @@ def index(): return render_template('setup_wizard/index.html', progress=progress.to_dict(), - lots_data=lots_data) + lots_data=lots_data, + task_counts=task_count_by_lot()) @setup_wizard_bp.route('/api/progress') @@ -439,7 +440,7 @@ def api_save_step(step): return jsonify({'success': True, 'step': 5, 'rooms': saved}) elif step == 6: - # Étape 6 : catalogue fixe des catégories et lots du dump SQL. + # Étape 6 : catalogue fixe des catégories, lots et tâches du dump SQL. categories_by_name = {} for name in EQUIPMENT_CATEGORIES: category = EquipmentCategory.query.filter( @@ -451,20 +452,58 @@ def api_save_step(step): db.session.flush() categories_by_name[name] = category + selected_names = data.get('selected_lots') + selected_names = {str(name).strip().casefold() for name in selected_names} if isinstance(selected_names, list) else None + imported_lots = [] + lots_by_name = {} for lot_name, category_name in LOTS: + if selected_names is not None and lot_name.casefold() not in selected_names: + continue lot = Lot.query.filter(db.func.lower(Lot.name) == lot_name.lower()).first() if lot is None: lot = Lot(name=lot_name) db.session.add(lot) + db.session.flush() lot.category_id = categories_by_name[category_name].id + lots_by_name[lot_name.casefold()] = lot + imported_lots.append(lot_name) + + # Les tâches du catalogue sont ajoutées par clé fonctionnelle. Une + # ligne déjà présente n'est jamais écrasée : elle peut avoir été + # adaptée par l'établissement depuis le dernier import. + task_count = 0 + for item in LOT_TASKS: + lot = lots_by_name.get(item['lot'].casefold()) + if lot is None: + continue + existing = LotTask.query.filter( + LotTask.lot_id == lot.id, + db.func.lower(LotTask.tache) == item['tache'].casefold(), + ).first() if item.get('tache') else None + if existing is not None: + continue + task = LotTask(lot_id=lot.id) + for field in ( + 'num_tache', 'tache', 'periodicite', 'contrat', 'gestionnaire', + 'jours_entre_interventions', 'type_maintenance', 'duree_minutes', + 'trigger_type', 'trigger_event', 'meter_threshold', + 'season_start_month', 'season_end_month', 'advance_days', + 'is_active', 'application_scope', 'unavailable_policy', + 'after_repair_policy', 'is_safety_critical', + ): + if field in item: + setattr(task, field, item[field]) + db.session.add(task) + task_count += 1 progress.categories_json = json.dumps(list(EQUIPMENT_CATEGORIES), ensure_ascii=False) - progress.set_lots([name for name, _category in LOTS]) + progress.set_lots(imported_lots) progress.step = max(progress.step or 0, 6) db.session.commit() return jsonify({ 'success': True, 'step': 6, - 'categories_count': len(EQUIPMENT_CATEGORIES), 'lots_count': len(LOTS), + 'categories_count': len(EQUIPMENT_CATEGORIES), 'lots_count': len(imported_lots), + 'lot_tasks_count': task_count, }) elif step == 7: diff --git a/app_new/core/setup_catalog.py b/app_new/core/setup_catalog.py index 0587abb..74dd476 100644 --- a/app_new/core/setup_catalog.py +++ b/app_new/core/setup_catalog.py @@ -4,6 +4,7 @@ Ces valeurs reprennent les catégories et les lots du dump SQL de référence. Les noms sont les clés fonctionnelles utilisées pour une synchronisation idempotente : le wizard ne supprime jamais une ligne déjà présente dans la base. """ +from .preventive_catalog import LOT_TASKS EQUIPMENT_CATEGORIES = ( "Chauffage", "Courants faibles", "Gros œuvre", "Revêtement de façades", @@ -85,3 +86,11 @@ def grouped_catalog(): for lot_name, category_name in LOTS: grouped[category_name].append(lot_name) return grouped + + +def task_count_by_lot(): + """Nombre de règles préventives historiques pour chaque lot.""" + counts = {} + for task in LOT_TASKS: + counts[task["lot"]] = counts.get(task["lot"], 0) + 1 + return counts diff --git a/app_new/lots/routes.py b/app_new/lots/routes.py index 845608c..aec6f6b 100644 --- a/app_new/lots/routes.py +++ b/app_new/lots/routes.py @@ -10,7 +10,7 @@ from ..core.models.planning import ScheduledTask from ..core.services.maintenance_engine import generate_due_tasks from ..core.models.company import Company from ..contracts.models import Contract -from ..core.models.equipment import Equipment +from ..core.models.equipment import Equipment, EquipmentCategory lots_bp = Blueprint('lots', __name__) @@ -37,6 +37,27 @@ def index(): companies = Company.query.order_by(Company.name).all() return render_template('lots/index.html', lots=lots, companies=companies) + +@lots_bp.route('/new', methods=['GET', 'POST']) +@login_required +def create(): + """Créer un lot indépendamment de la création d'un équipement.""" + categories = EquipmentCategory.query.order_by(EquipmentCategory.name).all() + if request.method == 'POST': + name = (request.form.get('name') or '').strip() + category_id = request.form.get('category_id', type=int) or None + if not name: + flash('Le nom du lot est obligatoire.', 'danger') + elif category_id and db.session.get(EquipmentCategory, category_id) is None: + flash('La catégorie sélectionnée est invalide.', 'danger') + else: + lot = Lot(name=name, category_id=category_id, is_present=request.form.get('is_present') == 'on') + db.session.add(lot) + db.session.commit() + flash('Lot créé. Ajoutez maintenant ses tâches préventives.', 'success') + return redirect(url_for('lots.detail', id=lot.id)) + return render_template('lots/create.html', categories=categories) + @lots_bp.route('/') @login_required def detail(id): diff --git a/app_new/templates/lots/create.html b/app_new/templates/lots/create.html new file mode 100644 index 0000000..82248c8 --- /dev/null +++ b/app_new/templates/lots/create.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} +{% block title %}Ajouter un lot — GMAO{% endblock %} +{% block content %} +
+
+

Ajouter un lot

+

Un lot regroupe des équipements et leurs actions de maintenance préventive.

+ Annuler +
+
+
+ +
+
+
+
+
+ +
+
+
+{% endblock %} diff --git a/app_new/templates/lots/index.html b/app_new/templates/lots/index.html index a9bf598..5539aa2 100644 --- a/app_new/templates/lots/index.html +++ b/app_new/templates/lots/index.html @@ -5,8 +5,11 @@

Gestion des Lots

diff --git a/docker-compose.override.yml b/docker-compose.override.yml index e1c6581..2a9f866 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -1,7 +1,7 @@ services: gmao: environment: - GMAO_VERSION: ${GMAO_VERSION:-0.1.0-dev.9} + GMAO_VERSION: ${GMAO_VERSION:-0.1.0-dev.10} GMAO_GUNICORN_FLAGS: --reload --reload-engine poll FLASK_ENV: development # Ce profil est servi en HTTP sur le réseau de test ; un cookie Secure diff --git a/docker-compose.yml b/docker-compose.yml index ef447f6..cdce705 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,12 +25,12 @@ services: memory: 128M gmao: - image: gmao-college:${GMAO_VERSION:-0.1.0-dev.9} + image: gmao-college:${GMAO_VERSION:-0.1.0-dev.10} build: context: . dockerfile: Dockerfile args: - GMAO_VERSION: ${GMAO_VERSION:-0.1.0-dev.9} + GMAO_VERSION: ${GMAO_VERSION:-0.1.0-dev.10} GIT_VERSION: ${GIT_VERSION:-unknown} container_name: gmao-flask restart: unless-stopped @@ -51,7 +51,7 @@ services: - TZ=Europe/Paris - DB_HOST=mariadb - DB_PORT=3306 - - GMAO_VERSION=${GMAO_VERSION:-0.1.0-dev.9} + - GMAO_VERSION=${GMAO_VERSION:-0.1.0-dev.10} - GMAO_GUNICORN_FLAGS=--preload - DOCKER_GMAO_WORKDIR=/app working_dir: /app diff --git a/docs/ui-audit/PHASE21_REQUALIFICATION.md b/docs/ui-audit/PHASE21_REQUALIFICATION.md index d973f1e..de1cfad 100644 --- a/docs/ui-audit/PHASE21_REQUALIFICATION.md +++ b/docs/ui-audit/PHASE21_REQUALIFICATION.md @@ -1,7 +1,7 @@ # Requalification Phase 2.1 — prérequis métier et interface Date : 2026-08-23 -Version auditée : 0.1.0-dev.8 +Version auditée initialement : 0.1.0-dev.8 ; correction catalogue : 0.1.0-dev.10 ## Conclusion @@ -123,4 +123,5 @@ aucune donnée n'a été nettoyée. La phase est **PARTIELLEMENT VALIDÉE** : l'isolation des horaires et l'absence de fallback silencieux sont corrigées et testées ; le parcours UI préventif réel, la création de deux techniciens depuis l'UI et la génération sans -fixture restent à démontrer. +fixture restent à démontrer. Le catalogue embarqué contient désormais les 330 +tâches historiques de `lot_tasks` et son import est idempotent. diff --git a/docs/ui-audit/PREVENTIVE_CATALOG_AUDIT.md b/docs/ui-audit/PREVENTIVE_CATALOG_AUDIT.md new file mode 100644 index 0000000..398205d --- /dev/null +++ b/docs/ui-audit/PREVENTIVE_CATALOG_AUDIT.md @@ -0,0 +1,65 @@ +# Audit du catalogue préventif historique + +Date : 2026-08-23 +Source de référence : `/opt/gmao/dump_sql_base_remplie.sql` (lecture seule) + +## Tables analysées + +| Table | Lignes dans le dump | Utilité | +|---|---:|---| +| `equipment_categories` | 36 | catégories patrimoniales | +| `lots` | 92 | lots, catégorie par `category_id`, présence | +| `lot_tasks` | 330 | référentiel historique des actions par `lot_id` | +| `preventive_tasks` | 0 | ancienne table, structure présente mais aucune donnée | +| `scheduled_tasks` | 516 | échéances historiques, pas le catalogue initial | + +Le modèle actuel utilise `LotTask`, et non `preventive_tasks`, pour les règles +préventives. Le dump contient 36 catégories et 92 lots, déjà représentés dans +`setup_catalog.py`; il manquait les 330 lignes de `lot_tasks`. + +## Durée et périodicité + +- 330 tâches ont été extraites sans modification des valeurs historiques ; +- 5 tâches ont une `duree_minutes` renseignée ; 325 ont une durée inconnue ; +- 327 ont une périodicité textuelle ; 3 ont une périodicité vide ; +- les valeurs `jours_entre_interventions`, type, contrat, gestionnaire, + déclenchement et politiques ont été conservées lorsqu'elles existent. + +La valeur de 60 minutes de `LotTask.effective_duration()` n'est pas présente +dans le dump : c'est un fallback du code actuel, pas une donnée historique. +Elle reste temporairement compatible avec les anciennes données, mais une tâche +sans durée doit être signalée avant une validation métier définitive. + +## Catalogue embarqué + +`app_new/core/preventive_catalog.py` contient une extraction compressée et +figée des 330 tâches. Le dump n'est jamais ouvert par l'application. Le module +expose `LOT_TASKS`, utilisé par le setup wizard et les tests. + +Le wizard crée les catégories, les lots sélectionnés et leurs tâches. La clé +fonctionnelle de tâche est `(lot, tache)` ; une ligne déjà existante n'est pas +écrasée, afin de préserver une personnalisation locale. Les éléments manquants +sont ajoutés. La seconde exécution est donc idempotente. + +## Relation équipement + +`Equipment.lot_id -> Lot.id`, avec héritage par `parent.effective_lot_id`. +Les tâches sont donc visibles via le lot effectif et peuvent générer des +`ScheduledTask` pour les équipements actifs. Un équipement rebuté ou supprimé +ne doit pas produire de nouvelle échéance. + +## Première échéance + +Le mécanisme actuel `generate_due_tasks()` calcule la première échéance à +partir de la date de génération et de `jours_entre_interventions`/périodicité. +Le dump ne fournit pas une date de première échéance catalogue. Une règle +métier explicite (date d'association, prochaine échéance saisie ou campagne) +reste à valider avant une automatisation plus ambitieuse. + +## Écarts et limites + +- le catalogue est maintenant complet pour les tables historiques validées ; +- les tâches sans durée restent à renseigner pour une planification fiable ; +- la validation bout en bout par interface (installation, association d'un + équipement, génération, Ma journée) reste à exécuter avec un compte UI ; +- `preventive_tasks` est documentée mais vide et n'est pas importée. diff --git a/tests/integration/test_auth_setup.py b/tests/integration/test_auth_setup.py index 4f7c0a5..07a69f9 100644 --- a/tests/integration/test_auth_setup.py +++ b/tests/integration/test_auth_setup.py @@ -141,8 +141,8 @@ def test_setup_creates_zones_and_rooms_with_valid_links(client, app, monkeypatch def test_setup_imports_embedded_lot_catalog_idempotently(client, app, monkeypatch, tmp_path): from app_new.core.models.equipment import EquipmentCategory - from app_new.core.models.maintenance import Lot - from app_new.core.setup_catalog import EQUIPMENT_CATEGORIES, LOTS + from app_new.core.models.maintenance import Lot, LotTask + from app_new.core.setup_catalog import EQUIPMENT_CATEGORIES, LOTS, LOT_TASKS _authorize_setup(client, monkeypatch, tmp_path) first = client.post('/setup-wizard/api/step/6', json={}) @@ -150,6 +150,7 @@ def test_setup_imports_embedded_lot_catalog_idempotently(client, app, monkeypatc assert first.status_code == second.status_code == 200 assert first.get_json()['categories_count'] == len(EQUIPMENT_CATEGORIES) assert first.get_json()['lots_count'] == len(LOTS) + assert first.get_json()['lot_tasks_count'] == len(LOT_TASKS) with app.app_context(): for name in EQUIPMENT_CATEGORIES: @@ -157,6 +158,7 @@ def test_setup_imports_embedded_lot_catalog_idempotently(client, app, monkeypatc for name, category_name in LOTS: lot = Lot.query.filter_by(name=name).one() assert lot.category.name == category_name + assert LotTask.query.count() == len(LOT_TASKS) def test_setup_saves_exact_work_days_inside_school_holidays(client, app, monkeypatch, tmp_path): diff --git a/tests/integration/test_preventive_catalog.py b/tests/integration/test_preventive_catalog.py new file mode 100644 index 0000000..8693cc8 --- /dev/null +++ b/tests/integration/test_preventive_catalog.py @@ -0,0 +1,16 @@ +from app_new.core.setup_catalog import LOT_TASKS + + +def test_embedded_preventive_catalog_has_historical_shape(): + """Garde-fou du catalogue extrait du dump de référence.""" + assert len(LOT_TASKS) == 330 + assert all(item['lot'] and item['tache'] for item in LOT_TASKS) + assert all(item['type_maintenance'] in ('préventive', 'preventive', 'curative', None) for item in LOT_TASKS) + assert sum(item['duree_minutes'] is not None for item in LOT_TASKS) == 5 + assert sum(bool(item['periodicite']) for item in LOT_TASKS) == 327 + assert len({(item['lot'], item['num_tache']) for item in LOT_TASKS}) == len(LOT_TASKS) + + +def test_catalog_tasks_have_stable_functional_keys(): + keys = [(item['lot'].casefold(), item['tache'].casefold()) for item in LOT_TASKS] + assert len(keys) == len(set(keys)) diff --git a/tests/unit/test_version.py b/tests/unit/test_version.py index 202298f..d02665b 100644 --- a/tests/unit/test_version.py +++ b/tests/unit/test_version.py @@ -3,7 +3,7 @@ from app_new.version import get_version def test_project_version_is_available(monkeypatch): monkeypatch.delenv('GMAO_VERSION', raising=False) - assert get_version() == '0.1.0-dev.9' + assert get_version() == '0.1.0-dev.10' def test_environment_version_takes_precedence(monkeypatch):