""" Routes du planificateur - GMAO College Permet de lancer le planificateur manuellement et de voir les resultats. FUSION: ScheduledTask est maintenant integre dans Intervention (statut 'planifiee'). """ from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify from flask_login import login_required from ..extensions import db from ..scheduler.engine import run_scheduler, get_due_tasks, group_by_zone from ..core.models.maintenance import Intervention from datetime import date, datetime, timezone, timedelta scheduler_bp = Blueprint('scheduler', __name__, url_prefix='/scheduler', template_folder='templates') @scheduler_bp.route('/') @login_required def index(): """Page du planificateur.""" due_tasks = get_due_tasks(weeks_ahead=4) groups = group_by_zone(due_tasks) # Interventions planifiees (anciennes ScheduledTask 'planned') planned = Intervention.query.filter( Intervention.status == 'planifiee', Intervention.scheduled_date >= date.today(), Intervention.is_deleted == False ).order_by(Intervention.scheduled_date).all() # Interventions en retard overdue = Intervention.query.filter( Intervention.status == 'planifiee', Intervention.scheduled_date < date.today(), Intervention.is_deleted == False ).order_by(Intervention.scheduled_date).all() return render_template('scheduler/index.html', due_tasks=due_tasks, groups=groups, planned_tasks=planned, overdue_tasks=overdue, today=date.today()) @scheduler_bp.route('/run', methods=['POST']) @login_required def run(): """Lance le planificateur manuellement.""" weeks = int(request.form.get('weeks', 4)) result = run_scheduler(weeks_ahead=weeks) if result.get('success'): flash(result.get('message', 'Planificateur execute'), 'success') else: flash(f"Erreur: {result.get('error', 'inconnue')}", 'danger') return redirect(url_for('scheduler.index')) @scheduler_bp.route('/api/run', methods=['POST']) @login_required def api_run(): """Lance le planificateur via API.""" weeks = int(request.json.get('weeks', 4)) if request.is_json else 4 result = run_scheduler(weeks_ahead=weeks) return jsonify(result) @scheduler_bp.route('/api/preview') @login_required def api_preview(): """Previsualise les taches dues sans les planifier.""" due = get_due_tasks(weeks_ahead=4) groups = group_by_zone(due) result = { 'total': len(due), 'groups': {} } for zone, tasks in groups.items(): result['groups'][zone] = len(tasks) return jsonify(result) @scheduler_bp.route('/task//ignore', methods=['POST']) @login_required def ignore_task(task_id): """Ignore une intervention planifiee.""" t = Intervention.query.get_or_404(task_id) reason = request.form.get('reason', '') t.ignore(reason) flash('Tache ignoree.', 'info') return redirect(url_for('scheduler.index')) @scheduler_bp.route('/task//postpone', methods=['POST']) @login_required def postpone_task(task_id): """Reporte une intervention planifiee.""" t = Intervention.query.get_or_404(task_id) new_date_str = request.form.get('new_date') if new_date_str: t.scheduled_date = datetime.strptime(new_date_str, '%Y-%m-%d').date() t.postpone_reason = request.form.get('reason', '') db.session.commit() flash('Tache reportee.', 'success') return redirect(url_for('scheduler.index')) @scheduler_bp.route('/task//start', methods=['POST']) @login_required def start_task(task_id): """Demarre une intervention planifiee (planifiee -> en_cours).""" t = Intervention.query.get_or_404(task_id) t.status = 'en_cours' t.started_at = datetime.now(timezone.utc) db.session.commit() flash('Intervention demarree.', 'success') return redirect(url_for('scheduler.index')) @scheduler_bp.route('/task//complete', methods=['POST']) @login_required def complete_task(task_id): """Marque une intervention comme terminee.""" t = Intervention.query.get_or_404(task_id) t.status = 'terminee' t.completed_at = datetime.now(timezone.utc) t.completed_date = date.today() t.actual_duration = float(request.form.get('actual_duration', t.estimated_duration or 60)) db.session.commit() flash('Intervention terminee.', 'success') return redirect(url_for('scheduler.index')) @scheduler_bp.route('/api/overdue-count') @login_required def api_overdue_count(): """Compte les interventions en retard (pour alerte dashboard).""" overdue = Intervention.query.filter( Intervention.is_deleted == False, Intervention.status == 'planifiee', Intervention.scheduled_date < date.today() ).count() return jsonify({'overdue_tasks': overdue, 'overdue_interventions': overdue}) @scheduler_bp.route('/calendar') @login_required def calendar(): """Compatibilité : le calendrier est désormais celui de /planning/.""" return redirect(url_for('planning.index', year=request.args.get('year', date.today().year, type=int))) @scheduler_bp.route('/api/daily-summary') @login_required def api_daily_summary(): """Synthese quotidienne (P13) : resume de la journee.""" from ..scheduler.engine import get_working_hours, is_user_available from ..outlook.models import OutlookMailInterpretation from app_new.ent.interpretation_models import EntMessageInterpretation today = date.today() # Interventions planifiees aujourd'hui today_tasks = Intervention.query.filter( Intervention.scheduled_date == today, Intervention.status == 'planifiee', Intervention.is_deleted == False ).all() # Interventions en cours active_interventions = Intervention.query.filter( Intervention.is_deleted == False, ~Intervention.status.in_(['terminee', 'cloturee', 'annulee', 'brouillon', 'ignoree']) ).count() # Interpretations en attente outlook_pending = OutlookMailInterpretation.query.filter_by(status='pending').count() ent_pending = EntMessageInterpretation.query.filter_by(status='pending').count() # Retards overdue = Intervention.query.filter( Intervention.is_deleted == False, Intervention.status == 'planifiee', Intervention.scheduled_date < today ).count() # Horaires wh = get_working_hours(today) avail, _ = is_user_available(today) return jsonify({ 'date': today.strftime('%d/%m/%Y'), 'tasks_today': len(today_tasks), 'active_interventions': active_interventions, 'outlook_pending': outlook_pending, 'ent_pending': ent_pending, 'overdue': overdue, 'working': wh is not None and avail, 'tasks': [{'time': t.scheduled_start.strftime('%H:%M') if t.scheduled_start else '', 'equipment': t.equipment.name if t.equipment else '', 'room': t.room.name if t.room else '', 'duration': t.estimated_duration or 60} for t in today_tasks] })