""" 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/', 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'))