Fusionner les plannings dans une vue unifiée
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run
This commit is contained in:
parent
4f8237aba9
commit
f0c3dafd25
6 changed files with 102 additions and 96 deletions
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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('/<int:id>/delete', methods=['POST'])
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -181,9 +181,7 @@
|
|||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-dark" style="background:#34495e;">
|
||||
<li><a class="dropdown-item" href="{{ url_for('interventions.index') }}"><i class="bi bi-list-ul"></i> Toutes les interventions</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('interventions_planning.planning') }}"><i class="bi bi-calendar3"></i> Planning interventions</a></li>
|
||||
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('planning.index') }}"><i class="bi bi-calendar-check"></i> Tableau planning</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('planning.index') }}"><i class="bi bi-calendar-check"></i> Planning unifié</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('planning.tasks') }}"><i class="bi bi-list-check"></i> Tâches préventives</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('planning.scheduled') }}"><i class="bi bi-calendar-week"></i> Tâches planifiées</a></li>
|
||||
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
||||
|
|
@ -200,8 +198,7 @@
|
|||
<li><a class="dropdown-item" href="{{ url_for('exports.export_interventions') }}"><i class="bi bi-file-earmark-pdf"></i> Export interventions</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('exports.export_costs_csv') }}"><i class="bi bi-cash-stack"></i> Export coûts et stock</a></li>
|
||||
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('scheduler.index') }}"><i class="bi bi-calendar-week"></i> Planificateur auto</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('scheduler.calendar') }}"><i class="bi bi-calendar3"></i> Calendrier</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('scheduler.index') }}"><i class="bi bi-calendar-week"></i> Génération automatique</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@
|
|||
<span><span class="badge bg-secondary">Week-end</span></span>
|
||||
<span><span class="badge bg-dark">Vacances</span></span>
|
||||
<span><span class="badge bg-warning text-dark">Congé</span></span>
|
||||
<span><span class="badge bg-primary">Intervention</span></span>
|
||||
<span><span class="badge bg-info text-dark">Échéance préventive</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -107,6 +109,18 @@
|
|||
title="{{ day_info.vacation_name if day_info.vacation_name else '' }}">
|
||||
{{ day_info.day }}
|
||||
</a>
|
||||
{% if day_info.events %}
|
||||
<div class="mt-1 d-flex flex-column gap-1">
|
||||
{% for event in day_info.events[:3] %}
|
||||
<a href="{{ event.url }}" class="badge bg-{{ event.color }} text-truncate text-decoration-none" title="{{ event.title }}{% if event.room_name %} — {{ event.room_name }}{% endif %}">
|
||||
{% if event.start_time %}{{ event.start_time.strftime('%H:%M') }} {% endif %}{{ event.title[:18] }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% if day_info.events|length > 3 %}
|
||||
<a href="{{ url_for('planning.day', date_str=date_str) }}" class="small text-decoration-none">+{{ day_info.events|length - 3 }} autre(s)</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
|
@ -120,4 +134,4 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
Loading…
Reference in a new issue