gmao/app_new/wizard/routes.py

359 lines
14 KiB
Python
Raw Normal View History

2026-08-14 18:02:25 +02:00
"""
Wizard de création d'équipements par lot - GMAO Collège
"""
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, current_app
from flask_login import login_required
from app_new.extensions import db
from app_new.core.models.equipment import Equipment, EquipmentCategory
from app_new.core.models.maintenance import Lot
from app_new.core.models.college import Room, Building, Zone, RoomType
wizard_bp = Blueprint('wizard', __name__, url_prefix='/wizard', template_folder='templates')
@wizard_bp.route('/equipments')
@login_required
def equipment_wizard():
"""Page principale du wizard de création d'équipements."""
categories = EquipmentCategory.query.order_by(EquipmentCategory.name).all()
buildings = Building.query.order_by(Building.name).all()
room_types = RoomType.query.order_by(RoomType.name).all()
# Récupérer les lots groupés par catégorie (relation directe)
lots_by_category = {}
for cat in categories:
lots_by_category[cat.id] = [{'id': lot.id, 'name': lot.name} for lot in cat.lots]
# Tous les lots pour fallback
lots = Lot.query.order_by(Lot.name).all()
housing_id = request.args.get('housing_id', type=int)
selected_room_ids = []
if housing_id:
from app_new.core.models.college import HousingUnit
housing = HousingUnit.query.get_or_404(housing_id)
selected_room_ids = [room.id for room in housing.rooms]
2026-08-14 18:02:25 +02:00
return render_template('wizard/equipment_wizard.html',
lots=lots,
categories=categories,
buildings=buildings,
room_types=room_types,
lots_by_category=lots_by_category,
selected_room_ids=selected_room_ids)
@wizard_bp.route('/equipments/bulk-create', methods=['POST'])
@login_required
def bulk_create_equipments():
"""Crée plusieurs familles d'équipements en une transaction."""
data = request.get_json(silent=True) or {}
room_ids = list(dict.fromkeys(data.get('room_ids') or []))
items = data.get('items') or []
rooms = Room.query.filter(Room.id.in_(room_ids)).all() if room_ids else []
if len(rooms) != len(room_ids):
return jsonify({'error': 'Une ou plusieurs pièces sont invalides.'}), 400
if not rooms or not items:
return jsonify({'error': 'Sélectionnez au moins une pièce et un équipement.'}), 400
created = []
try:
for raw in items:
name = (raw.get('name') or '').strip()
category = db.session.get(EquipmentCategory, int(raw.get('category_id') or 0))
lot = db.session.get(Lot, int(raw.get('lot_id') or 0))
quantity = int(raw.get('quantity') or 1)
mode = raw.get('mode', 'quantitatif')
mobility = raw.get('mobility', 'non_precise')
if not name or not category or not lot or quantity < 1 or quantity > 1000:
raise ValueError('Une ligne contient des données invalides.')
if lot.category_id and lot.category_id != category.id:
raise ValueError(f'Le lot « {lot.name} » ne correspond pas à la catégorie « {category.name} ».')
root = Equipment(
name=name, category_id=category.id, lot_id=lot.id,
is_group=True, quantity=quantity * len(rooms), mobility=mobility,
tracked_individually=(mode == 'individuel'), status='en_service',
)
db.session.add(root)
db.session.flush()
created.append(root)
for room in rooms:
group = Equipment(
name=name, parent_id=root.id, room_id=room.id, is_group=True,
quantity=quantity, mobility=mobility, status='en_service',
tracked_individually=(mode == 'individuel'),
)
db.session.add(group)
db.session.flush()
created.append(group)
if mode == 'individuel':
for number in range(1, quantity + 1):
unit = Equipment(
name=f'{name} #{number}', parent_id=group.id, room_id=room.id,
is_group=False, quantity=1, mobility=mobility,
tracked_individually=True, individual_number=number,
status='en_service', position=f'{name}{number}',
)
db.session.add(unit)
created.append(unit)
# Les tâches sont générées sur les unités individualisées, sinon sur
# les groupes de pièce, jamais sur le parent de synthèse.
task_targets = [eq for eq in created if (not eq.is_group or (eq.parent_id and not eq.tracked_individually))]
for equipment in task_targets:
equipment.create_scheduled_tasks_from_lot(commit=False)
db.session.commit()
return jsonify({'success': True, 'created_count': len(created), 'families': len(items), 'rooms': len(rooms)})
except (TypeError, ValueError) as exc:
db.session.rollback()
return jsonify({'error': str(exc)}), 400
except Exception:
db.session.rollback()
current_app.logger.exception('Échec de la création massive du patrimoine')
return jsonify({'error': 'La création a été annulée intégralement.'}), 500
2026-08-14 18:02:25 +02:00
@wizard_bp.route('/api/rooms/<int:building_id>')
@login_required
def get_rooms(building_id):
"""Récupère les salles d'un bâtiment."""
rooms = Room.query.filter_by(building_id=building_id).order_by(Room.name).all()
return jsonify([{
'id': r.id,
'name': r.name,
'zone': r.zone.name if r.zone else None
} for r in rooms])
@wizard_bp.route('/api/zones/<int:building_id>')
@login_required
def get_zones(building_id):
"""Récupère les zones d'un bâtiment."""
zones = Zone.query.filter_by(building_id=building_id).order_by(Zone.name).all()
return jsonify([{
'id': z.id,
'name': z.name
} for z in zones])
@wizard_bp.route('/api/lot_tasks/<int:lot_id>')
@login_required
def get_lot_tasks(lot_id):
"""Récupère les tâches préventives d'un lot."""
from app_new.core.models.maintenance import LotTask
tasks = LotTask.query.filter_by(lot_id=lot_id).order_by(LotTask.num_tache).all()
return jsonify([{
'id': t.id,
'num_tache': t.num_tache,
'tache': t.tache,
'periodicite': t.periodicite,
'contrat': t.contrat,
'gestionnaire': t.gestionnaire,
'type_maintenance': t.type_maintenance
} for t in tasks])
@wizard_bp.route('/equipments/create', methods=['POST'])
@login_required
def create_equipments():
"""Crée les équipements selon les paramètres du wizard."""
try:
data = request.get_json()
lot_id = data.get('lot_id')
category_id = data.get('category_id')
equipment_name = data.get('name', '').strip() # Nom personnalisé
parent_name = data.get('parent_name')
create_parent = data.get('create_parent', False)
building_id = data.get('building_id')
zone_id = data.get('zone_id')
rooms_data = data.get('rooms', []) # [{room_id, count}, ...]
tracked_individually = data.get('tracked_individually', False)
# Debug
current_app.logger.info(f"Création équipements: tracked_individually={tracked_individually}, rooms={rooms_data}")
if not lot_id or not category_id:
return jsonify({'error': 'Lot et catégorie requis'}), 400
if not rooms_data:
return jsonify({'error': 'Au moins une salle requise'}), 400
lot = Lot.query.get(lot_id)
category = EquipmentCategory.query.get(category_id)
# Nom de base: personnalisé ou catégorie
base_name = equipment_name if equipment_name else category.name
created_equipments = []
# Étape 1: Créer le parent global (si demandé)
parent = None
if create_parent and parent_name:
parent = Equipment(
name=parent_name,
lot_id=lot_id,
category_id=category_id,
is_group=True,
parent_id=None
)
db.session.add(parent)
db.session.flush() # Pour avoir l'ID
# Étape 2: Pour chaque salle, créer le groupe et les individuels
for room_data in rooms_data:
room_id = room_data.get('room_id')
count = room_data.get('count', 1)
room = Room.query.get(room_id)
if not room:
continue
if tracked_individually:
# Avec suivi individuel: créer un groupe + des individus
room_group = Equipment(
name=f"{base_name} {room.name}",
lot_id=lot_id if not parent else None,
category_id=category_id if not parent else None,
is_group=True,
parent_id=parent.id if parent else None,
room_id=room_id,
quantity=1
)
db.session.add(room_group)
db.session.flush()
created_equipments.append({
'type': 'group',
'name': room_group.name,
'room': room.name,
'quantity': 1
})
# Créer les équipements individuels
if count > 1:
for i in range(1, count + 1):
individual = Equipment(
name=f"{base_name} {i}",
lot_id=lot_id, # Hériter du lot
category_id=category_id, # Hériter de la catégorie
is_group=False,
tracked_individually=True,
parent_id=room_group.id,
room_id=room_id,
position=f"{base_name}{i}"
)
db.session.add(individual)
created_equipments.append({
'type': 'individual',
'name': individual.name,
'room': room.name
})
else:
# Sans suivi individuel: créer un seul équipement avec quantity
equipment = Equipment(
name=f"{base_name} {room.name}",
lot_id=lot_id if not parent else None,
category_id=category_id if not parent else None,
is_group=False,
parent_id=parent.id if parent else None,
room_id=room_id,
quantity=count
)
db.session.add(equipment)
db.session.flush()
created_equipments.append({
'type': 'equipment',
'name': equipment.name,
'room': room.name,
'quantity': count
})
db.session.commit()
# Créer les tâches planifiées pour tous les équipements créés
for eq in created_equipments:
# Récupérer l'équipement depuis la DB pour avoir son ID
equipment = Equipment.query.filter_by(name=eq['name']).first()
if equipment and equipment.effective_lot_id:
equipment.create_scheduled_tasks_from_lot()
return jsonify({
'success': True,
'count': len(created_equipments),
'equipments': created_equipments
})
except Exception as e:
db.session.rollback()
return jsonify({'error': str(e)}), 500
@wizard_bp.route('/equipments/preview', methods=['POST'])
@login_required
def preview_equipments():
"""Prévisualise les équipements qui seront créés."""
data = request.get_json()
lot_id = data.get('lot_id')
category_id = data.get('category_id')
parent_name = data.get('parent_name')
create_parent = data.get('create_parent', False)
rooms_data = data.get('rooms', [])
tracked_individually = data.get('tracked_individually', False)
lot = Lot.query.get(lot_id) if lot_id else None
category = EquipmentCategory.query.get(category_id) if category_id else None
preview = []
# Parent global
if create_parent and parent_name:
preview.append({
'level': 0,
'name': parent_name,
'type': 'Groupe parent',
'lot': lot.name if lot else '-',
'category': category.name if category else '-',
'room': '-'
})
# Par salle
for room_data in rooms_data:
room_id = room_data.get('room_id')
count = room_data.get('count', 1)
room = Room.query.get(room_id)
if not room:
continue
# Groupe salle
level = 1 if create_parent else 0
preview.append({
'level': level,
'name': f"{category.name} {room.name}",
'type': 'Groupe salle',
'lot': lot.name if lot else '-',
'category': category.name if category else '-',
'room': room.name
})
# Individuels
if tracked_individually:
for i in range(1, count + 1):
preview.append({
'level': level + 1,
'name': f"{category.name} {i}",
'type': 'Individuel',
'lot': '(hérité)',
'category': '(hérité)',
'room': room.name
})
return jsonify({
'preview': preview,
'total': len(preview)
})