diff --git a/app_new/wizard/routes.py b/app_new/wizard/routes.py
index 8708ed7..2bf9c5c 100644
--- a/app_new/wizard/routes.py
+++ b/app_new/wizard/routes.py
@@ -27,12 +27,89 @@ def equipment_wizard():
# 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]
return render_template('wizard/equipment_wizard.html',
lots=lots,
categories=categories,
buildings=buildings,
room_types=room_types,
- lots_by_category=lots_by_category)
+ 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} n°{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
@wizard_bp.route('/api/rooms/')
@@ -278,4 +355,4 @@ def preview_equipments():
return jsonify({
'preview': preview,
'total': len(preview)
- })
\ No newline at end of file
+ })
diff --git a/app_new/wizard/templates/wizard/equipment_wizard.html b/app_new/wizard/templates/wizard/equipment_wizard.html
index 515e2d8..b9f7982 100644
--- a/app_new/wizard/templates/wizard/equipment_wizard.html
+++ b/app_new/wizard/templates/wizard/equipment_wizard.html
@@ -1,363 +1,41 @@
{% extends "base.html" %}
-{% block title %}Wizard Création Équipements — GMAO Collège{% endblock %}
-
+{% block title %}Inventaire guidé — GMAO{% endblock %}
{% block content %}
-
-
-
-
Création d'équipements par lot
-
-
-
-
-
-
-
-
Étape 1 : Catégorie et Lot
-
-
-
-
-
-
- Optionnel. Si vide, le nom de la catégorie sera utilisé.
-
-
-
-
-
-
- Type d'équipement (Fenêtre, Porte, etc.)
-
-
-
-
-
- Sélectionnez d'abord une catégorie
-
-
-
-
Maintenances préventives du lot
-
-
-
-
-
-
-
-
-
-
Étape 2 : Groupe parent (optionnel)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Étape 3 : Salles et quantités
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Salles sélectionnées
-
-
- Sélectionnez des salles dans le panneau de droite
-