gmao/app_new/scheduler/routes.py

203 lines
7.1 KiB
Python
Raw Normal View History

2026-08-14 18:02:25 +02:00
"""
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/<int:task_id>/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/<int:task_id>/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/<int:task_id>/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/<int:task_id>/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)))
2026-08-14 18:02:25 +02:00
@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]
})