2026-08-14 18:02:25 +02:00
|
|
|
"""
|
|
|
|
|
Lots Routes - GMAO Collège
|
|
|
|
|
Gestion des lots de maintenance
|
|
|
|
|
"""
|
|
|
|
|
from flask import Blueprint, render_template, request, flash, redirect, url_for
|
|
|
|
|
from flask_login import login_required
|
|
|
|
|
from app_new.extensions import db
|
|
|
|
|
from ..core.models.maintenance import Lot, LotTask
|
2026-08-20 00:49:15 +02:00
|
|
|
from ..core.models.planning import ScheduledTask
|
|
|
|
|
from ..core.services.maintenance_engine import generate_due_tasks
|
2026-08-14 18:02:25 +02:00
|
|
|
from ..core.models.company import Company
|
2026-08-21 00:28:47 +02:00
|
|
|
from ..contracts.models import Contract
|
2026-08-14 19:21:03 +02:00
|
|
|
from ..core.models.equipment import Equipment
|
2026-08-14 18:02:25 +02:00
|
|
|
|
|
|
|
|
lots_bp = Blueprint('lots', __name__)
|
|
|
|
|
|
|
|
|
|
@lots_bp.route('/')
|
|
|
|
|
@login_required
|
|
|
|
|
def index():
|
|
|
|
|
"""Liste des lots."""
|
2026-08-14 19:21:03 +02:00
|
|
|
page = request.args.get('page', 1, type=int)
|
|
|
|
|
search = (request.args.get('search') or '').strip()
|
|
|
|
|
company_id = request.args.get('company_id', type=int)
|
|
|
|
|
presence = request.args.get('presence', '')
|
|
|
|
|
|
|
|
|
|
query = Lot.query
|
|
|
|
|
if search:
|
|
|
|
|
query = query.filter(Lot.name.ilike(f'%{search}%'))
|
|
|
|
|
if company_id:
|
|
|
|
|
query = query.filter(Lot.company_id == company_id)
|
|
|
|
|
if presence == 'present':
|
|
|
|
|
query = query.filter(Lot.is_present.is_(True))
|
|
|
|
|
elif presence == 'absent':
|
|
|
|
|
query = query.filter(Lot.is_present.is_(False))
|
|
|
|
|
|
|
|
|
|
lots = query.order_by(Lot.name).paginate(page=page, per_page=50, error_out=False)
|
|
|
|
|
companies = Company.query.order_by(Company.name).all()
|
|
|
|
|
return render_template('lots/index.html', lots=lots, companies=companies)
|
2026-08-14 18:02:25 +02:00
|
|
|
|
|
|
|
|
@lots_bp.route('/<int:id>')
|
|
|
|
|
@login_required
|
|
|
|
|
def detail(id):
|
|
|
|
|
"""Détail d'un lot."""
|
|
|
|
|
lot = Lot.query.get_or_404(id)
|
|
|
|
|
# Récupérer les équipements de ce lot
|
|
|
|
|
equipments = Equipment.query.filter_by(lot_id=id).order_by(Equipment.name).all()
|
2026-08-20 00:49:15 +02:00
|
|
|
task_ids = [task.id for task in lot.tasks]
|
|
|
|
|
task_counts = {'planned': 0, 'suspended': 0, 'overdue': 0}
|
|
|
|
|
if task_ids:
|
|
|
|
|
occurrences = ScheduledTask.query.filter(ScheduledTask.lot_task_id.in_(task_ids)).all()
|
|
|
|
|
task_counts['planned'] = sum(item.status == 'planned' for item in occurrences)
|
|
|
|
|
task_counts['suspended'] = sum(item.status == 'suspended' for item in occurrences)
|
|
|
|
|
task_counts['overdue'] = sum(item.is_overdue for item in occurrences)
|
|
|
|
|
return render_template('lots/detail.html', lot=lot, equipments=equipments, task_counts=task_counts)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _apply_task_form(task):
|
|
|
|
|
task.num_tache = (request.form.get('num_tache') or '').strip() or None
|
|
|
|
|
task.tache = (request.form.get('tache') or '').strip() or None
|
|
|
|
|
task.periodicite = (request.form.get('periodicite') or '').strip() or None
|
|
|
|
|
task.jours_entre_interventions = request.form.get('jours_entre_interventions', type=int)
|
|
|
|
|
task.duree_minutes = request.form.get('duree_minutes', type=int)
|
|
|
|
|
task.type_maintenance = (request.form.get('type_maintenance') or 'preventive').strip()
|
|
|
|
|
task.trigger_type = request.form.get('trigger_type') or 'calendar'
|
|
|
|
|
task.trigger_event = (request.form.get('trigger_event') or '').strip() or None
|
|
|
|
|
task.meter_threshold = request.form.get('meter_threshold', type=float)
|
|
|
|
|
task.season_start_month = request.form.get('season_start_month', type=int)
|
|
|
|
|
task.season_end_month = request.form.get('season_end_month', type=int)
|
|
|
|
|
task.advance_days = max(request.form.get('advance_days', type=int) or 0, 0)
|
|
|
|
|
task.application_scope = request.form.get('application_scope') or 'auto'
|
|
|
|
|
task.unavailable_policy = request.form.get('unavailable_policy') or 'suspend'
|
|
|
|
|
task.after_repair_policy = request.form.get('after_repair_policy') or 'preserve'
|
|
|
|
|
task.is_safety_critical = request.form.get('is_safety_critical') == 'on'
|
|
|
|
|
task.is_active = request.form.get('is_active') == 'on'
|
2026-08-21 00:28:47 +02:00
|
|
|
task.company_id = request.form.get('company_id', type=int) or None
|
|
|
|
|
task.contract_id = request.form.get('contract_id', type=int) or None
|
2026-08-20 00:49:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@lots_bp.route('/<int:lot_id>/tasks/new', methods=['GET', 'POST'])
|
|
|
|
|
@login_required
|
|
|
|
|
def task_create(lot_id):
|
|
|
|
|
lot = Lot.query.get_or_404(lot_id)
|
|
|
|
|
task = LotTask(lot_id=lot.id, is_active=True)
|
|
|
|
|
if request.method == 'POST':
|
|
|
|
|
_apply_task_form(task)
|
|
|
|
|
if not task.tache:
|
|
|
|
|
flash('Le nom de la tâche est obligatoire.', 'danger')
|
|
|
|
|
else:
|
|
|
|
|
db.session.add(task)
|
|
|
|
|
db.session.commit()
|
|
|
|
|
flash('Tâche préventive ajoutée au lot.', 'success')
|
|
|
|
|
return redirect(url_for('lots.detail', id=lot.id))
|
2026-08-21 00:28:47 +02:00
|
|
|
return render_template('lots/task_form.html', lot=lot, task=task, creating=True, companies=Company.query.order_by(Company.name).all(), contracts=Contract.query.order_by(Contract.name).all())
|
2026-08-20 00:49:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@lots_bp.route('/<int:lot_id>/tasks/<int:task_id>/edit', methods=['GET', 'POST'])
|
|
|
|
|
@login_required
|
|
|
|
|
def task_edit(lot_id, task_id):
|
|
|
|
|
lot = Lot.query.get_or_404(lot_id)
|
|
|
|
|
task = LotTask.query.filter_by(id=task_id, lot_id=lot.id).first_or_404()
|
|
|
|
|
if request.method == 'POST':
|
|
|
|
|
_apply_task_form(task)
|
|
|
|
|
if not task.tache:
|
|
|
|
|
flash('Le nom de la tâche est obligatoire.', 'danger')
|
|
|
|
|
else:
|
|
|
|
|
db.session.commit()
|
|
|
|
|
flash('Règle préventive mise à jour.', 'success')
|
|
|
|
|
return redirect(url_for('lots.detail', id=lot.id))
|
2026-08-21 00:28:47 +02:00
|
|
|
return render_template('lots/task_form.html', lot=lot, task=task, creating=False, companies=Company.query.order_by(Company.name).all(), contracts=Contract.query.order_by(Contract.name).all())
|
2026-08-20 00:49:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@lots_bp.route('/<int:lot_id>/generate', methods=['POST'])
|
|
|
|
|
@login_required
|
|
|
|
|
def generate_tasks(lot_id):
|
|
|
|
|
lot = Lot.query.get_or_404(lot_id)
|
|
|
|
|
if not lot.is_present:
|
|
|
|
|
flash('Aucune échéance générée : ce lot est marqué non présent.', 'warning')
|
|
|
|
|
return redirect(url_for('lots.detail', id=lot.id))
|
|
|
|
|
created = []
|
|
|
|
|
for task in lot.tasks:
|
|
|
|
|
created.extend(generate_due_tasks(task, commit=False))
|
|
|
|
|
db.session.commit()
|
|
|
|
|
flash(f'{len(created)} nouvelle(s) échéance(s) générée(s), sans doublon.', 'success')
|
|
|
|
|
return redirect(url_for('lots.detail', id=lot.id))
|
2026-08-14 18:02:25 +02:00
|
|
|
|
2026-08-14 19:21:03 +02:00
|
|
|
@lots_bp.route('/<int:id>/edit', methods=['GET', 'POST'])
|
2026-08-14 18:02:25 +02:00
|
|
|
@login_required
|
|
|
|
|
def edit(id):
|
|
|
|
|
"""Modifier un lot (formulaire)."""
|
|
|
|
|
lot = Lot.query.get_or_404(id)
|
2026-08-14 19:21:03 +02:00
|
|
|
if request.method == 'POST':
|
|
|
|
|
lot.company_id = request.form.get('company_id', type=int)
|
|
|
|
|
lot.absence_reason = (request.form.get('absence_reason') or '').strip() or None
|
|
|
|
|
db.session.commit()
|
|
|
|
|
flash('Lot modifié.', 'success')
|
|
|
|
|
return redirect(url_for('lots.detail', id=id))
|
|
|
|
|
|
|
|
|
|
companies = Company.query.order_by(Company.name).all()
|
|
|
|
|
return render_template('lots/edit.html', lot=lot, companies=companies)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@lots_bp.route('/<int:id>/presence', methods=['POST'])
|
|
|
|
|
@login_required
|
|
|
|
|
def set_presence(id):
|
|
|
|
|
"""Marquer un lot comme présent ou non présent dans l'établissement."""
|
|
|
|
|
lot = Lot.query.get_or_404(id)
|
|
|
|
|
action = request.form.get('action')
|
|
|
|
|
|
|
|
|
|
if action == 'absent':
|
|
|
|
|
if lot.active_equipment_count:
|
|
|
|
|
flash(
|
|
|
|
|
f"Impossible de marquer ce lot non présent : "
|
|
|
|
|
f"{lot.active_equipment_count} équipement(s) y sont encore rattachés.",
|
|
|
|
|
'danger',
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
lot.is_present = False
|
|
|
|
|
lot.absence_reason = (request.form.get('absence_reason') or '').strip() or None
|
|
|
|
|
db.session.commit()
|
|
|
|
|
flash(f"Le lot « {lot.name} » est marqué non présent.", 'success')
|
|
|
|
|
elif action == 'present':
|
|
|
|
|
lot.is_present = True
|
|
|
|
|
lot.absence_reason = None
|
|
|
|
|
db.session.commit()
|
|
|
|
|
flash(f"Le lot « {lot.name} » est marqué présent.", 'success')
|
|
|
|
|
else:
|
|
|
|
|
flash('Action de présence invalide.', 'danger')
|
|
|
|
|
|
|
|
|
|
return redirect(request.referrer or url_for('lots.detail', id=id))
|
2026-08-14 18:02:25 +02:00
|
|
|
|
|
|
|
|
@lots_bp.route('/<int:id>/delete', methods=['POST'])
|
|
|
|
|
@login_required
|
|
|
|
|
def delete(id):
|
|
|
|
|
"""Supprimer un lot."""
|
|
|
|
|
lot = Lot.query.get_or_404(id)
|
|
|
|
|
# TODO: suppression avec déplacement vers corbeille
|
|
|
|
|
flash("Fonctionnalité en cours de développement", "info")
|
2026-08-14 19:21:03 +02:00
|
|
|
return redirect(url_for('lots.index'))
|