gmao/app_new/core/routes/dashboard.py
2026-08-14 16:02:25 +00:00

304 lines
No EOL
12 KiB
Python

"""
Core Routes - Dashboard
GMAO Collège
"""
from flask import Blueprint, render_template, request, url_for, flash, redirect, jsonify
from flask_login import login_required, current_user
from datetime import datetime
from ...extensions import db
from ..models.maintenance import Intervention
from ..models.equipment import Equipment
from ..models.company import Company
from ..models.college import Building, Room
from app_new.constants import INTERVENTION_STATUSES
dashboard_bp = Blueprint('dashboard', __name__)
@dashboard_bp.route('/')
@login_required
def index():
"""Tableau de bord principal."""
from datetime import date
from ..services.planning_service import PlanningService
from ..models.planning import ScheduledTask, AdminTask
# Statistiques
stats = {
'interventions_total': Intervention.query.count(),
'interventions_en_cours': Intervention.query.filter_by(status='en_cours').count(),
'interventions_en_attente': Intervention.query.filter_by(status='en_attente').count(),
'interventions_urgentes': Intervention.query.filter_by(status='urgence').count(),
'equipments_total': Equipment.query.count(),
'equipments_actifs': Equipment.query.filter_by(status='actif').count(),
'equipments_panne': Equipment.query.filter_by(status='panne').count(),
'buildings_total': Building.query.count(),
'rooms_total': Room.query.count(),
'companies_total': Company.query.count(),
'parts_low_stock': 0, # TODO: implémenter quand le modèle Part sera disponible
}
# Dernières interventions
recent_interventions = Intervention.query.order_by(Intervention.created_at.desc()).limit(10).all()
# Interventions urgentes
urgent_interventions = Intervention.query.filter_by(status='urgence').limit(5).all()
# Interventions du jour (planning)
today = date.today()
day_schedule = PlanningService.get_day_schedule(today)
# Interprétations en attente (non traitées)
from ...outlook.models import OutlookMailInterpretation
from ...ent.interpretation_models import EntMessageInterpretation
# Récupérer les interprétations Outlook
outlook_interpretations = OutlookMailInterpretation.query.filter_by(
status='pending',
user_id=current_user.id
).order_by(OutlookMailInterpretation.created_at.desc()).limit(10).all()
# Récupérer les interprétations ENT
ent_interpretations = EntMessageInterpretation.query.filter_by(
status='pending'
).order_by(EntMessageInterpretation.created_at.desc()).limit(10).all()
# Combiner les deux listes avec une source
pending_interpretations = []
for interp in outlook_interpretations:
pending_interpretations.append({
'id': interp.id,
'source': 'Outlook',
'date': interp.mail.received_at if interp.mail else None,
'sender': interp.mail.from_name if interp.mail else 'Inconnu',
'subject': interp.mail.subject if interp.mail else 'N/A',
'type': interp.analysis_type,
'action': interp.analysis_action,
'description': interp.suggested_description,
'equipment': interp.suggested_equipment,
'confidence': getattr(interp, 'analysis_confidence', 0.5),
'detail_url': url_for('outlook_pages.mail_view', account_id=interp.mail.account_id, mail_id=interp.mail_id) if interp.mail else url_for('outlook_dashboard.all_interpretations'),
'create_url': url_for('outlook_dashboard.create_intervention_from_interpretation', interp_id=interp.id)
})
for interp in ent_interpretations:
pending_interpretations.append({
'id': interp.id,
'source': 'ENT',
'date': interp.message.date if interp.message else None,
'sender': interp.message.sender_name if interp.message else 'Inconnu',
'subject': interp.message.subject if interp.message else 'N/A',
'type': interp.analysis_type,
'action': interp.analysis_action,
'description': interp.suggested_description,
'equipment': interp.suggested_equipment,
'confidence': getattr(interp, 'analysis_confidence', 0.5),
'detail_url': url_for('ent.message_detail', msg_id=interp.message_id) if interp.message else url_for('ent.interpretations'),
'create_url': url_for('interventions.create', from_interpretation=interp.id, source='ent')
})
# Trier par date décroissante
pending_interpretations.sort(key=lambda x: x['date'] if x['date'] else datetime.min, reverse=True)
pending_interpretations = pending_interpretations[:15] # Limiter à 15
return render_template('dashboard/index.html',
stats=stats,
recent_interventions=recent_interventions,
urgent_interventions=urgent_interventions,
pending_interpretations=pending_interpretations,
day_schedule=day_schedule,
today=today,
statuses=INTERVENTION_STATUSES)
@dashboard_bp.route('/api/stats')
@login_required
def api_stats():
"""API JSON pour les graphiques du dashboard."""
from datetime import date, timedelta
from sqlalchemy import func
# Interventions par mois (6 derniers mois)
six_months_ago = date.today() - timedelta(days=180)
monthly = db.session.query(
func.date_format(Intervention.created_at, '%Y-%m').label('month'),
func.count().label('count')
).filter(Intervention.created_at >= six_months_ago).group_by('month').order_by('month').all()
monthly_data = [{'month': r.month, 'count': r.count} for r in monthly]
# Interventions par statut
status_data = {}
for status_val, info in INTERVENTION_STATUSES.items():
count = Intervention.query.filter_by(status=status_val).count()
if count > 0:
status_data[info['label']] = count
# Equipements par statut
equip_status = {}
for s in ['actif', 'panne', 'maintenance', 'hors_service']:
count = Equipment.query.filter_by(status=s).count()
if count > 0:
equip_status[s] = count
# Top 5 equipements les plus intervenus
top_equip = db.session.query(
Equipment.name,
func.count(Intervention.id).label('count')
).join(Intervention, Intervention.equipment_id == Equipment.id)\
.group_by(Equipment.id)\
.order_by(func.count(Intervention.id).desc())\
.limit(5).all()
top_data = [{'name': r.name, 'count': r.count} for r in top_equip]
return jsonify({
'monthly': monthly_data,
'by_status': status_data,
'equip_status': equip_status,
'top_equipments': top_data
})
@dashboard_bp.route('/search')
@login_required
def search():
"""Recherche globale."""
from flask import request
query = request.args.get('q', '')
results = {
'interventions': [],
'equipments': [],
'rooms': [],
}
if query:
# Recherche interventions
results['interventions'] = Intervention.query.filter(
db.or_(
Intervention.title.ilike(f'%{query}%'),
Intervention.description.ilike(f'%{query}%')
)
).limit(10).all()
# Recherche équipements
results['equipments'] = Equipment.query.filter(
db.or_(
Equipment.name.ilike(f'%{query}%'),
Equipment.serial_number.ilike(f'%{query}%')
)
).limit(10).all()
# Recherche salles
results['rooms'] = Room.query.filter(
Room.name.ilike(f'%{query}%')
).limit(10).all()
return render_template('dashboard/search.html', query=query, results=results)
@dashboard_bp.route('/alerts')
@login_required
def alerts():
"""Liste des alertes."""
from app_new.core.models.maintenance import Intervention
from app_new.core.models.company import Alert
alerts_list = Alert.query.filter_by(is_read=False).order_by(Alert.created_at.desc()).all()
# Interventions urgentes (statut haute priorité)
urgent_interventions = Intervention.query.filter(
Intervention.priority == 'haute',
Intervention.status.in_(['planifiee', 'en_cours', 'en_attente'])
).order_by(Intervention.scheduled_date.desc()).all()
# Équipements en panne
from app_new.core.models.equipment import Equipment
equipments_panne = Equipment.query.filter_by(status='panne').all()
# Équipements à jeter
equipments_to_trash = Equipment.query.filter_by(status='a_jeter').all()
# Interprétations en attente
from ...outlook.models import OutlookMailInterpretation
from ...ent.interpretation_models import EntMessageInterpretation
pending_outlook = OutlookMailInterpretation.query.filter_by(
status='pending', user_id=current_user.id
).order_by(OutlookMailInterpretation.created_at.desc()).all()
pending_ent = EntMessageInterpretation.query.filter_by(
status='pending'
).order_by(EntMessageInterpretation.created_at.desc()).all()
return render_template('dashboard/alerts.html',
alerts=alerts_list,
urgent_interventions=urgent_interventions,
equipments_panne=equipments_panne,
equipments_to_trash=equipments_to_trash,
pending_outlook=pending_outlook,
pending_ent=pending_ent)
@dashboard_bp.route('/alerts/mark-read/<int:alert_id>', methods=['POST'])
@login_required
def mark_alert_read(alert_id):
"""Marque une alerte comme lue."""
from app_new.core.models.company import Alert
alert = Alert.query.get(alert_id)
if alert:
alert.is_read = True
db.session.commit()
flash('Alerte marquée comme lue.', 'success')
return redirect(url_for('dashboard.alerts'))
@dashboard_bp.route('/create-month-interventions', methods=['POST'])
@login_required
def create_month_interventions():
"""Créer les interventions pour les 30 prochains jours à partir des tâches planifiées."""
from datetime import date, timedelta
from ..models.planning import ScheduledTask
from ..models.maintenance import Intervention
from ..models.company import Company
from flask_login import current_user
today = date.today()
one_month_later = today + timedelta(days=30)
# Récupérer les tâches planifiées dans le mois (uniquement avec équipement)
tasks = db.session.query(ScheduledTask).filter(
ScheduledTask.equipment_id.isnot(None),
ScheduledTask.scheduled_date >= today,
ScheduledTask.scheduled_date <= one_month_later,
ScheduledTask.status.in_(['planned', 'scheduled'])
).all()
created = 0
for task in tasks:
# Vérifier si l'intervention existe déjà
existing = db.session.query(Intervention).filter(
Intervention.equipment_id == task.equipment_id,
Intervention.scheduled_date == task.scheduled_date,
Intervention.scheduled_start == task.scheduled_start
).first()
if not existing:
intervention = Intervention(
title=f"Intervention: {task.preventive_task.name if task.preventive_task else (task.lot_task.tache if task.lot_task else 'Tâche planifiée')}",
description=task.notes or '',
equipment_id=task.equipment_id,
room_id=task.room_id,
scheduled_date=task.scheduled_date,
scheduled_start=task.scheduled_start,
scheduled_end=task.scheduled_end,
estimated_duration=task.estimated_duration or 30,
status='en_attente',
type='preventif',
assigned_to_id=task.assigned_to_id,
company_id=task.company_id,
author_id=current_user.id,
requester_name='Système automatique'
)
db.session.add(intervention)
created += 1
db.session.commit()
flash(f'{created} interventions créées pour les 30 prochains jours', 'success')
return redirect(url_for('dashboard.index'))