2026-08-23 18:45:41 +02:00
|
|
|
from flask import Blueprint, render_template, request, redirect, url_for, flash
|
|
|
|
|
from flask_login import login_required
|
|
|
|
|
|
|
|
|
|
from app_new.extensions import db
|
|
|
|
|
from app_new.core.models.college import RoomProfile, RoomProfileItem, RoomType, Room, RoomSurface
|
2026-08-23 19:25:32 +02:00
|
|
|
from app_new.core.models.equipment import EquipmentCategory, Equipment
|
2026-08-23 18:45:41 +02:00
|
|
|
from app_new.core.models.maintenance import Lot
|
|
|
|
|
from app_new.core.services.equipment_creation import create_equipment
|
|
|
|
|
|
|
|
|
|
room_profiles_bp = Blueprint("room_profiles", __name__, template_folder="templates")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _context(profile=None):
|
|
|
|
|
return {
|
|
|
|
|
"profile": profile,
|
|
|
|
|
"room_types": RoomType.query.order_by(RoomType.name).all(),
|
|
|
|
|
"categories": EquipmentCategory.query.order_by(EquipmentCategory.name).all(),
|
|
|
|
|
"lots": Lot.query.order_by(Lot.name).all(),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@room_profiles_bp.route("/")
|
|
|
|
|
@login_required
|
|
|
|
|
def index():
|
|
|
|
|
return render_template("room_profiles/index.html", profiles=RoomProfile.query.order_by(RoomProfile.name).all())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@room_profiles_bp.route("/new", methods=["GET", "POST"])
|
|
|
|
|
@login_required
|
|
|
|
|
def new():
|
|
|
|
|
if request.method == "POST":
|
|
|
|
|
name = (request.form.get("name") or "").strip()
|
|
|
|
|
if not name:
|
|
|
|
|
flash("Le nom du profil est obligatoire.", "danger")
|
|
|
|
|
return render_template("room_profiles/form.html", title="Nouveau profil", **_context()), 400
|
|
|
|
|
if RoomProfile.query.filter(db.func.lower(RoomProfile.name) == name.lower()).first():
|
|
|
|
|
flash("Un profil porte déjà ce nom.", "danger")
|
|
|
|
|
return render_template("room_profiles/form.html", title="Nouveau profil", **_context()), 400
|
|
|
|
|
profile = RoomProfile(
|
|
|
|
|
name=name,
|
|
|
|
|
description=(request.form.get("description") or "").strip() or None,
|
|
|
|
|
room_type_id=request.form.get("room_type_id", type=int) or None,
|
|
|
|
|
)
|
|
|
|
|
db.session.add(profile)
|
|
|
|
|
db.session.commit()
|
|
|
|
|
flash("Profil créé. Ajoutez ses propositions puis prévisualisez avant toute création.", "success")
|
|
|
|
|
return redirect(url_for("room_profiles.edit", id=profile.id))
|
|
|
|
|
return render_template("room_profiles/form.html", title="Nouveau profil", **_context())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@room_profiles_bp.route("/<int:id>/edit", methods=["GET", "POST"])
|
|
|
|
|
@login_required
|
|
|
|
|
def edit(id):
|
|
|
|
|
profile = RoomProfile.query.get_or_404(id)
|
|
|
|
|
if request.method == "POST":
|
|
|
|
|
profile.name = (request.form.get("name") or "").strip()
|
|
|
|
|
profile.description = (request.form.get("description") or "").strip() or None
|
|
|
|
|
profile.room_type_id = request.form.get("room_type_id", type=int) or None
|
|
|
|
|
if not profile.name:
|
|
|
|
|
flash("Le nom du profil est obligatoire.", "danger")
|
|
|
|
|
return render_template("room_profiles/form.html", title="Modifier le profil", **_context(profile)), 400
|
|
|
|
|
db.session.commit()
|
|
|
|
|
flash("Profil mis à jour.", "success")
|
|
|
|
|
return redirect(url_for("room_profiles.edit", id=profile.id))
|
|
|
|
|
return render_template("room_profiles/form.html", title="Modifier le profil", **_context(profile))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@room_profiles_bp.route("/<int:id>/items", methods=["POST"])
|
|
|
|
|
@login_required
|
|
|
|
|
def add_item(id):
|
|
|
|
|
profile = RoomProfile.query.get_or_404(id)
|
|
|
|
|
label = (request.form.get("label") or "").strip()
|
|
|
|
|
quantity = request.form.get("quantity", 1, type=int)
|
|
|
|
|
if not label or not quantity or quantity < 1:
|
|
|
|
|
flash("Libellé et quantité positive sont obligatoires.", "danger")
|
|
|
|
|
return redirect(url_for("room_profiles.edit", id=id))
|
|
|
|
|
item = RoomProfileItem(
|
|
|
|
|
profile_id=profile.id,
|
|
|
|
|
label=label,
|
|
|
|
|
category_id=request.form.get("category_id", type=int) or None,
|
|
|
|
|
lot_id=request.form.get("lot_id", type=int) or None,
|
|
|
|
|
quantity=quantity,
|
|
|
|
|
is_group=request.form.get("is_group") == "1",
|
|
|
|
|
enabled_by_default=request.form.get("enabled_by_default") == "1",
|
|
|
|
|
sort_order=request.form.get("sort_order", 0, type=int),
|
|
|
|
|
)
|
|
|
|
|
db.session.add(item)
|
|
|
|
|
db.session.commit()
|
|
|
|
|
flash("Proposition ajoutée au profil.", "success")
|
|
|
|
|
return redirect(url_for("room_profiles.edit", id=id))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@room_profiles_bp.route("/<int:profile_id>/items/<int:item_id>/delete", methods=["POST"])
|
|
|
|
|
@login_required
|
|
|
|
|
def delete_item(profile_id, item_id):
|
|
|
|
|
item = RoomProfileItem.query.filter_by(id=item_id, profile_id=profile_id).first_or_404()
|
|
|
|
|
db.session.delete(item)
|
|
|
|
|
db.session.commit()
|
|
|
|
|
flash("Proposition retirée du profil.", "success")
|
|
|
|
|
return redirect(url_for("room_profiles.edit", id=profile_id))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@room_profiles_bp.route("/<int:id>/preview", methods=["GET", "POST"])
|
|
|
|
|
@login_required
|
|
|
|
|
def preview(id):
|
|
|
|
|
profile = RoomProfile.query.get(id)
|
|
|
|
|
if profile is None:
|
|
|
|
|
from flask import abort
|
|
|
|
|
abort(404)
|
|
|
|
|
room_ids = request.form.getlist("room_ids", type=int) if request.method == "POST" else request.args.getlist("room_id", type=int)
|
|
|
|
|
rooms = Room.query.filter(Room.id.in_(room_ids)).order_by(Room.name).all() if room_ids else []
|
|
|
|
|
items = [item for item in profile.items if item.enabled_by_default]
|
2026-08-23 19:25:32 +02:00
|
|
|
existing = {(room.id, item.id): sum((equipment.quantity or 1) for equipment in Equipment.query.filter_by(
|
|
|
|
|
room_id=room.id, room_profile_item_id=item.id, is_deleted=False).all())
|
|
|
|
|
for room in rooms for item in items}
|
2026-08-23 18:45:41 +02:00
|
|
|
if request.method == "POST" and request.form.get("validate") == "1":
|
|
|
|
|
selected = {int(value) for value in request.form.getlist("item_ids") if value.isdigit()}
|
|
|
|
|
if not rooms or not selected:
|
|
|
|
|
flash("Sélectionnez au moins un local et une proposition.", "danger")
|
2026-08-23 19:25:32 +02:00
|
|
|
return render_template("room_profiles/preview.html", profile=profile, rooms=rooms, items=items, existing=existing)
|
2026-08-23 18:45:41 +02:00
|
|
|
created = 0
|
|
|
|
|
for room in rooms:
|
|
|
|
|
for item in items:
|
|
|
|
|
if item.id not in selected:
|
|
|
|
|
continue
|
|
|
|
|
quantity = request.form.get(f"quantity_{item.id}", item.quantity, type=int)
|
|
|
|
|
if quantity < 1:
|
|
|
|
|
continue
|
2026-08-23 19:25:32 +02:00
|
|
|
quantity = max(quantity - existing.get((room.id, item.id), 0), 0)
|
|
|
|
|
if not quantity:
|
|
|
|
|
continue
|
2026-08-23 18:45:41 +02:00
|
|
|
create_equipment(
|
|
|
|
|
name=item.label,
|
|
|
|
|
category_id=item.category_id,
|
|
|
|
|
lot_id=item.lot_id,
|
|
|
|
|
room_id=room.id,
|
|
|
|
|
quantity=quantity,
|
|
|
|
|
is_group=item.is_group,
|
2026-08-23 19:25:32 +02:00
|
|
|
room_profile_item_id=item.id,
|
2026-08-23 18:45:41 +02:00
|
|
|
)
|
|
|
|
|
created += 1
|
|
|
|
|
db.session.commit()
|
|
|
|
|
flash(f"{created} proposition(s) appliquée(s). Les équipements ont été créés via le service commun.", "success")
|
|
|
|
|
return redirect(url_for("rooms.index"))
|
2026-08-23 19:25:32 +02:00
|
|
|
return render_template("room_profiles/preview.html", profile=profile, rooms=rooms, items=items, existing=existing)
|
2026-08-23 18:45:41 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@room_profiles_bp.route("/<int:id>/surfaces", methods=["POST"])
|
|
|
|
|
@login_required
|
|
|
|
|
def add_surface(id):
|
|
|
|
|
profile = RoomProfile.query.get_or_404(id)
|
|
|
|
|
surface_type = request.form.get("surface_type")
|
|
|
|
|
material = (request.form.get("material") or "").strip()
|
|
|
|
|
if surface_type not in {"mur", "sol", "plafond"} or not material:
|
|
|
|
|
flash("Type et matériau de surface obligatoires.", "danger")
|
|
|
|
|
return redirect(url_for("room_profiles.edit", id=id))
|
|
|
|
|
db.session.add(RoomSurface(profile_id=profile.id, surface_type=surface_type, material=material,
|
|
|
|
|
finish=(request.form.get("finish") or "").strip() or None,
|
|
|
|
|
label=(request.form.get("label") or "").strip() or None))
|
|
|
|
|
db.session.commit()
|
|
|
|
|
flash("Composition proposée ajoutée.", "success")
|
|
|
|
|
return redirect(url_for("room_profiles.edit", id=id))
|