diff --git a/loko/interventions/services/document_sequencing.py b/loko/interventions/services/document_sequencing.py new file mode 100644 index 0000000..b8a3137 --- /dev/null +++ b/loko/interventions/services/document_sequencing.py @@ -0,0 +1,198 @@ +from typing import List, Dict, Any, Optional +from datetime import datetime +from django.utils.translation import gettext as _ +from django.utils import timezone +from django.utils.dateformat import format as dj_format_date + + +def get_intervention_milestones(intervention) -> tuple[Optional[datetime], Optional[datetime]]: + """ + Détermine les jalons temporels d'une intervention : + - start_dt (T_début) : Début réel de l'exécution / réalisation + - end_dt (T_fin) : Fin réelle de l'exécution / réalisation + + Logique de résolution : + 1. start_dt : + - intervention.begin_time si défini + - Sinon premier événement timeline avec to_status='in_progress' + - Sinon premier événement InterventionRealizationTimeLine avec event_type='real_begin' + 2. end_dt : + - intervention.end_time si défini + - Sinon premier événement timeline avec to_status in ['finished', 'processed', 'validated', 'invoiced', 'closed'] + (intervenu après start_dt si start_dt existe) + - Sinon premier événement InterventionRealizationTimeLine avec event_type='real_end' + """ + start_dt: Optional[datetime] = intervention.begin_time or None + if not start_dt: + # Recherche dans InterventionTimeLine + first_in_progress = ( + intervention.events + .filter(to_status='in_progress') + .order_by('event_time') + .first() + ) + if first_in_progress: + start_dt = first_in_progress.event_time + else: + # Recherche dans InterventionRealizationTimeLine + first_real_begin = ( + intervention.realized_events + .filter(event_type='real_begin') + .order_by('event_time') + .first() + ) + if first_real_begin: + start_dt = first_real_begin.event_time + + end_dt: Optional[datetime] = intervention.end_time or None + if not end_dt: + # Recherche dans InterventionTimeLine + terminal_statuses = ['finished', 'processed', 'validated', 'invoiced', 'closed'] + events_qs = intervention.events.filter(to_status__in=terminal_statuses) + if start_dt: + events_qs = events_qs.filter(event_time__gte=start_dt) + first_terminal = events_qs.order_by('event_time').first() + if first_terminal: + end_dt = first_terminal.event_time + else: + # Recherche dans InterventionRealizationTimeLine + first_real_end = ( + intervention.realized_events + .filter(event_type='real_end') + .order_by('event_time') + .first() + ) + if first_real_end: + end_dt = first_real_end.event_time + + return start_dt, end_dt + + +def get_sequenced_intervention_documents(intervention) -> Dict[str, Any]: + """ + Organise les documents et photos de l'intervention en 3 blocs temporels : + 1. before_work : Avant réalisation (préparation, constat, signalement) + 2. during_work : Exécution du chantier (pendant les travaux) + 3. after_work : Après réalisation (contrôle, levée de réserves, clôture) + + Retourne un dictionnaire : + { + 'blocks': [ + { + 'key': 'before_work', + 'title': '...', + 'subtitle': '...', + 'icon': '...', + 'badge_class': '...', + 'period_display': '...', + 'modal_id': '...', + 'documents': [...], + 'count': int, + }, + ... + ], + 'total_count': int, + 'has_documents': bool, + 'start_dt': datetime|None, + 'end_dt': datetime|None, + } + """ + start_dt, end_dt = get_intervention_milestones(intervention) + + docs = list( + intervention.documents + .all() + .select_related('uploaded_by') + .order_by('upload_date', 'id') + ) + + before_docs: List[Any] = [] + during_docs: List[Any] = [] + after_docs: List[Any] = [] + + for doc in docs: + doc_dt = doc.upload_date or getattr(intervention, 'creation_time', None) + + if not start_dt: + # L'intervention n'a pas encore démarré : tout est classé en avant-réalisation + before_docs.append(doc) + elif not doc_dt: + before_docs.append(doc) + elif doc_dt < start_dt: + before_docs.append(doc) + else: + # doc_dt >= start_dt + if not end_dt: + during_docs.append(doc) + elif doc_dt <= end_dt: + during_docs.append(doc) + else: + after_docs.append(doc) + + # Libellés temporels formatés + def _fmt(dt: datetime) -> str: + loc_dt = timezone.localtime(dt) if timezone.is_aware(dt) else dt + return dj_format_date(loc_dt, 'd/m/Y H:i') + + if start_dt: + period_before = _("Jusqu'au %(date)s") % {'date': _fmt(start_dt)} + else: + period_before = _("Phase de préparation") + + if start_dt and end_dt: + period_during = _("Du %(start)s au %(end)s") % {'start': _fmt(start_dt), 'end': _fmt(end_dt)} + elif start_dt: + period_during = _("Depuis le %(date)s") % {'date': _fmt(start_dt)} + else: + period_during = "" + + if end_dt: + period_after = _("Après le %(date)s") % {'date': _fmt(end_dt)} + else: + period_after = "" + + blocks = [ + { + 'key': 'before_work', + 'title': _("Avant réalisation"), + 'subtitle': _("Préparation, constat initial et pièces administratives"), + 'icon': 'bi-clipboard-check', + 'badge_class': 'bg-secondary text-white', + 'period_display': period_before, + 'modal_id': 'interventionDocViewerModal_before', + 'documents': before_docs, + 'count': len(before_docs), + }, + { + 'key': 'during_work', + 'title': _("Exécution du chantier"), + 'subtitle': _("Travaux en cours et constat d'achèvement"), + 'icon': 'bi-tools', + 'badge_class': 'bg-primary text-white', + 'period_display': period_during, + 'modal_id': 'interventionDocViewerModal_during', + 'documents': during_docs, + 'count': len(during_docs), + }, + { + 'key': 'after_work', + 'title': _("Après réalisation"), + 'subtitle': _("Contrôle, levée de réserves et clôture"), + 'icon': 'bi-shield-check', + 'badge_class': 'bg-success text-white', + 'period_display': period_after, + 'modal_id': 'interventionDocViewerModal_after', + 'documents': after_docs, + 'count': len(after_docs), + }, + ] + + total_count = len(docs) + + return { + 'blocks': blocks, + 'total_count': total_count, + 'has_documents': total_count > 0, + 'start_dt': start_dt, + 'end_dt': end_dt, + } diff --git a/loko/interventions/templates/interventions/detail/card_documents.html b/loko/interventions/templates/interventions/detail/card_documents.html index dfaef8f..14bdc13 100644 --- a/loko/interventions/templates/interventions/detail/card_documents.html +++ b/loko/interventions/templates/interventions/detail/card_documents.html @@ -1,19 +1,47 @@ {% load document_viewer_tags i18n %}
{% translate "Aucun document ou photo pour cette intervention." %}
+