diff --git a/app_new/core/services/planning_service.py b/app_new/core/services/planning_service.py index 390fbfd..abb70fa 100644 --- a/app_new/core/services/planning_service.py +++ b/app_new/core/services/planning_service.py @@ -251,9 +251,10 @@ class PlanningService: return schedule # Récupérer les interventions planifiées pour ce jour - scheduled_tasks = ScheduledTask.query.filter_by( - scheduled_date=d, - status='planned' + scheduled_tasks = ScheduledTask.query.filter( + ScheduledTask.scheduled_date == d, + ScheduledTask.status.in_(('planned', 'in_progress', 'suspended', 'postponed')), + ScheduledTask.intervention_id.is_(None), ).all() for task in scheduled_tasks: @@ -277,23 +278,26 @@ class PlanningService: } schedule.items.append(item) - # Récupérer les interventions curatives en cours - interventions = Intervention.query.filter_by( - scheduled_date=d, - type='curatif' + # Les interventions préventives et curatives appartiennent au même + # planning. Les échéances déjà converties sont représentées ici par + # leur intervention, sans doublon avec ScheduledTask. + interventions = Intervention.query.filter( + Intervention.scheduled_date == d, + Intervention.is_deleted.is_(False), + ~Intervention.status.in_(('terminee', 'cloturee', 'annulee', 'ignoree')), ).all() for interv in interventions: item = { 'id': interv.id, - 'type': 'curative', + 'type': 'curative' if interv.type == 'curatif' else 'preventive', 'title': interv.title, 'start_time': None, 'end_time': None, 'duration': None, 'room_id': None, 'room_name': interv.room.name if interv.room else None, - 'status': interv.status + 'status': interv.status, } schedule.items.append(item) diff --git a/app_new/interventions/planning.py b/app_new/interventions/planning.py index 12cd0cb..51be13d 100644 --- a/app_new/interventions/planning.py +++ b/app_new/interventions/planning.py @@ -12,18 +12,11 @@ planning_bp = Blueprint("interventions_planning", __name__, url_prefix="/interve @planning_bp.route('/planning') @login_required def planning(): - """Planning des interventions préventives.""" - from datetime import datetime, timezone, timedelta - - # Recuperer les interventions preventives planifiees - preventive_interventions = Intervention.query.filter( - Intervention.type == 'preventif', - Intervention.scheduled_date != None - ).order_by(Intervention.scheduled_date).all() - - return render_template('interventions/planning.html', - interventions=preventive_interventions, - stats={'total': len(preventive_interventions), 'en_attente': 0, 'en_cours': 0, 'terminee': 0}) + """Compatibilité : redirige vers le planning unifié.""" + params = {} + if request.args.get('year', type=int): + params['year'] = request.args.get('year', type=int) + return redirect(url_for('planning.index', **params)) @planning_bp.route('//delete', methods=['POST']) diff --git a/app_new/planning/schedules.py b/app_new/planning/schedules.py index 10a4628..b57e532 100644 --- a/app_new/planning/schedules.py +++ b/app_new/planning/schedules.py @@ -28,7 +28,9 @@ def index(): # Récupérer l'année demandée ou année courante year = request.args.get('year', date.today().year, type=int) - # Préparer les données du calendrier + # Préparer les données du calendrier. Le calendrier principal est la + # source unique : il rassemble les interventions et les échéances + # préventives qui n'ont pas encore été converties en intervention. months_data = [] months_names = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'] @@ -52,6 +54,59 @@ def index(): TechnicianAvailability.date <= date(year, 12, 31) ).all() leave_dates = {l.date: l for l in leaves} + + year_start = date(year, 1, 1) + year_end = date(year, 12, 31) + terminal_intervention_statuses = ('terminee', 'cloturee', 'annulee', 'ignoree') + interventions = Intervention.query.filter( + Intervention.scheduled_date >= year_start, + Intervention.scheduled_date <= year_end, + Intervention.is_deleted.is_(False), + ~Intervention.status.in_(terminal_intervention_statuses), + ).order_by(Intervention.scheduled_date, Intervention.scheduled_start).all() + scheduled_tasks = ScheduledTask.query.filter( + ScheduledTask.scheduled_date >= year_start, + ScheduledTask.scheduled_date <= year_end, + ScheduledTask.status.in_(('planned', 'in_progress', 'suspended', 'postponed')), + # Une échéance convertie est déjà représentée par son intervention. + ScheduledTask.intervention_id.is_(None), + ).order_by(ScheduledTask.scheduled_date, ScheduledTask.scheduled_start).all() + + events_by_date = {} + for intervention in interventions: + events_by_date.setdefault(intervention.scheduled_date, []).append({ + 'title': intervention.title or 'Intervention', + 'type': intervention.type or 'curatif', + 'status': intervention.status, + 'start_time': intervention.scheduled_start, + 'end_time': intervention.scheduled_end, + 'duration': intervention.estimated_duration, + 'room_name': intervention.room.name if intervention.room else None, + 'url': url_for('interventions.detail', id=intervention.id), + 'source': 'Intervention', + 'color': 'danger' if intervention.type == 'curatif' else 'primary', + }) + for task in scheduled_tasks: + title = 'Tâche préventive' + if task.lot_task and task.lot_task.tache: + title = task.lot_task.tache + elif task.preventive_task and task.preventive_task.name: + title = task.preventive_task.name + elif task.equipment: + title = f'Intervention sur {task.equipment.name}' + room = task.room or (task.equipment.effective_room if task.equipment else None) + events_by_date.setdefault(task.scheduled_date, []).append({ + 'title': title, + 'type': 'preventive', + 'status': task.status, + 'start_time': task.scheduled_start, + 'end_time': task.scheduled_end, + 'duration': task.estimated_duration, + 'room_name': room.name if room else None, + 'url': url_for('planning.scheduled_detail', id=task.id), + 'source': 'Échéance préventive', + 'color': 'info', + }) for month in range(1, 13): # Nombre de jours dans le mois @@ -76,7 +131,8 @@ def index(): 'is_vacation': vacation is not None, 'vacation_name': vacation, 'is_leave': leave is not None, - 'leave_type': leave.availability_type if leave else None + 'leave_type': leave.availability_type if leave else None, + 'events': events_by_date.get(d, []), }) # Si on atteint dimanche (fin de semaine), nouvelle semaine @@ -94,8 +150,8 @@ def index(): # Statistiques stats = { - 'preventive_tasks': ScheduledTask.query.filter_by(status='planned').count(), - 'curative_interventions': Intervention.query.filter_by(type='curatif', status='en_cours').count(), + 'preventive_tasks': len(scheduled_tasks), + 'curative_interventions': sum(1 for i in interventions if i.type == 'curatif'), 'admin_tasks': AdminTask.query.filter_by(is_active=True).count(), 'pending_items': PlanningItem.query.filter_by(status='planned').count(), } @@ -124,9 +180,10 @@ def day(date_str): schedule = PlanningService.get_day_schedule(d) # Récupérer les tâches planifiées pour ce jour - scheduled_tasks = ScheduledTask.query.filter_by( - scheduled_date=d, - status='planned' + scheduled_tasks = ScheduledTask.query.filter( + ScheduledTask.scheduled_date == d, + ScheduledTask.status.in_(('planned', 'in_progress', 'suspended', 'postponed')), + ScheduledTask.intervention_id.is_(None), ).all() return render_template('planning/day.html', diff --git a/app_new/scheduler/routes.py b/app_new/scheduler/routes.py index 09b553b..1ad1a51 100644 --- a/app_new/scheduler/routes.py +++ b/app_new/scheduler/routes.py @@ -145,67 +145,8 @@ def api_overdue_count(): @scheduler_bp.route('/calendar') @login_required def calendar(): - """Vue calendrier mensuel.""" - from datetime import timedelta - import calendar as cal_module - - month = request.args.get('month', date.today().month, type=int) - year = request.args.get('year', date.today().year, type=int) - - cal_obj = cal_module.Calendar(firstweekday=0) - month_days = cal_obj.monthdays2calendar(year, month) - - first_day = date(year, month, 1) - if month == 12: - last_day = date(year, month, 31) - else: - last_day = date(year, month + 1, 1) - timedelta(days=1) - - # Toutes les interventions non terminees du mois - interventions = Intervention.query.filter( - Intervention.scheduled_date >= first_day, - Intervention.scheduled_date <= last_day, - Intervention.is_deleted == False, - ~Intervention.status.in_(['terminee', 'cloturee', 'annulee']) - ).all() - - # Organiser par jour - items_by_day = {} - for i in interventions: - if i.scheduled_date: - items_by_day.setdefault(i.scheduled_date.day, []).append({ - 'title': i.title[:25] if i.title else 'Intervention', - 'start_time': i.scheduled_start.strftime('%H:%M') if i.scheduled_start else '', - 'color': 'danger' if i.type == 'curatif' else 'primary', - }) - - # Construire les semaines - weeks = [] - today = date.today() - for week in month_days: - week_days = [] - for day_num, day_week in week: - if day_num == 0: - week_days.append(None) - else: - week_days.append({ - 'day': day_num, - 'is_today': date(year, month, day_num) == today, - 'items': items_by_day.get(day_num, []) - }) - weeks.append(week_days) - - prev_month = date(year, month, 1) - timedelta(days=1) - next_month = date(year, month, 1) + timedelta(days=31) - month_names = ['', 'Janvier', 'Fevrier', 'Mars', 'Avril', 'Mai', 'Juin', - 'Juillet', 'Aout', 'Septembre', 'Octobre', 'Novembre', 'Decembre'] - - return render_template('scheduler/calendar.html', - weeks=weeks, - month=month, year=year, - month_name=month_names[month], - prev_month=prev_month, - next_month=next_month) + """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') @@ -258,4 +199,4 @@ def api_daily_summary(): '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] - }) \ No newline at end of file + }) diff --git a/app_new/templates/base.html b/app_new/templates/base.html index 86dc241..86f5070 100644 --- a/app_new/templates/base.html +++ b/app_new/templates/base.html @@ -181,9 +181,7 @@