diff --git a/loko/interventions/permissions.py b/loko/interventions/permissions.py index 5942105..38d4712 100644 --- a/loko/interventions/permissions.py +++ b/loko/interventions/permissions.py @@ -194,6 +194,62 @@ def get_allowed_statuses_for_user(user): return list(statuses) +def get_today_intervention_visibility_q(today): + """ + Condition Q pour filtrer les interventions actives le jour 'today' : + 1. Planifiée pour aujourd'hui (date exacte ou comprise dans la période planifiée [planned_begin, planned_end]) + 2. Démarrée aujourd'hui (begin_time__date == today) + 3. Travail en cours ou en pause (status in ['in_progress', 'on_pause']) + 4. Créée aujourd'hui sans planification préalable (urgences du jour) + """ + return ( + Q(planned_begin_time__date=today) | + Q(planned_begin_time__date__lte=today, planned_end_time__date__gte=today) | + Q(begin_time__date=today) | + Q(status__in=['in_progress', 'on_pause']) | + Q(planned_begin_time__isnull=True, begin_time__isnull=True, expected_begin_time__date=today) | + Q(planned_begin_time__isnull=True, begin_time__isnull=True, expected_begin_time__isnull=True, creation_time__date=today) + ) + + +def is_intervention_active_for_today(intervention, today): + """ + Vérifie si une instance d'intervention concerne la journée 'today' : + 1. Travail en cours ou en pause + 2. Planifiée pour aujourd'hui (date exacte ou comprise dans la période planifiée) + 3. Démarrée aujourd'hui + 4. Créée aujourd'hui sans planification ni démarrage préalable (urgences du jour) + """ + if intervention.status in ('in_progress', 'on_pause'): + return True + + if intervention.planned_begin_time: + p_begin = timezone.localtime(intervention.planned_begin_time).date() if timezone.is_aware(intervention.planned_begin_time) else intervention.planned_begin_time.date() + if intervention.planned_end_time: + p_end = timezone.localtime(intervention.planned_end_time).date() if timezone.is_aware(intervention.planned_end_time) else intervention.planned_end_time.date() + if p_begin <= today <= p_end: + return True + elif p_begin == today: + return True + + if intervention.begin_time: + b_date = timezone.localtime(intervention.begin_time).date() if timezone.is_aware(intervention.begin_time) else intervention.begin_time.date() + if b_date == today: + return True + + if not intervention.planned_begin_time and not intervention.begin_time: + if intervention.expected_begin_time: + e_date = timezone.localtime(intervention.expected_begin_time).date() if timezone.is_aware(intervention.expected_begin_time) else intervention.expected_begin_time.date() + if e_date == today: + return True + elif intervention.creation_time: + c_date = timezone.localtime(intervention.creation_time).date() if timezone.is_aware(intervention.creation_time) else intervention.creation_time.date() + if c_date == today: + return True + + return False + + def can_view_intervention(user, intervention): """ @@ -232,21 +288,8 @@ def can_view_intervention(user, intervention): local_now = timezone.localtime(timezone.now()) today = local_now.date() - # Vérifier la restriction de date (doit être aujourd'hui) - itv_date = ( - intervention.begin_time or - intervention.planned_begin_time or - intervention.expected_begin_time or - intervention.creation_time - ) - if itv_date: - if timezone.is_aware(itv_date): - itv_date = timezone.localtime(itv_date) - itv_date_only = itv_date.date() - else: - itv_date_only = None - - if itv_date_only != today: + # Vérifier la restriction de date (doit concerner aujourd'hui) + if not is_intervention_active_for_today(intervention, today): return False # Vérifier la restriction d'heure @@ -550,17 +593,7 @@ def get_accessible_interventions_for_technician(user): return base_qs.none() # Filtrer sur les interventions du jour uniquement - from django.db.models.functions import Coalesce, Cast - from django.db.models import DateTimeField, DateField - effective_begin_expr = Coalesce( - 'begin_time', - 'planned_begin_time', - 'expected_begin_time', - 'creation_time', - output_field=DateTimeField() - ) - qs = qs.annotate(effective_begin_date=Cast(effective_begin_expr, output_field=DateField())) - qs = qs.filter(effective_begin_date=today) + qs = qs.filter(get_today_intervention_visibility_q(today)) return qs else: qs = base_qs.filter(assigned_member__user=user) @@ -584,17 +617,7 @@ def get_accessible_interventions_for_technician(user): return base_qs.none() # Filtrer sur les interventions du jour uniquement - from django.db.models.functions import Coalesce, Cast - from django.db.models import DateTimeField, DateField - effective_begin_expr = Coalesce( - 'begin_time', - 'planned_begin_time', - 'expected_begin_time', - 'creation_time', - output_field=DateTimeField() - ) - qs = qs.annotate(effective_begin_date=Cast(effective_begin_expr, output_field=DateField())) - qs = qs.filter(effective_begin_date=today) + qs = qs.filter(get_today_intervention_visibility_q(today)) return qs @@ -758,17 +781,7 @@ def filter_viewable_interventions_for_user(user, limit_to_default_thematic=False return Intervention.objects.none() # Filtrer sur les interventions du jour uniquement - from django.db.models.functions import Coalesce, Cast - from django.db.models import DateTimeField, DateField - effective_begin_expr = Coalesce( - 'begin_time', - 'planned_begin_time', - 'expected_begin_time', - 'creation_time', - output_field=DateTimeField() - ) - qs = qs.annotate(effective_begin_date=Cast(effective_begin_expr, output_field=DateField())) - qs = qs.filter(effective_begin_date=today) + qs = qs.filter(get_today_intervention_visibility_q(today)) # Exclusion des interventions bloquées pour les utilisateurs externes (prestataires) if not getattr(user_config, 'is_intern', False): qs = qs.exclude(is_blocked_due_to_contract=True) diff --git a/loko/interventions/tests/test_visibility_permissions.py b/loko/interventions/tests/test_visibility_permissions.py index 5b066b7..8619ed5 100644 --- a/loko/interventions/tests/test_visibility_permissions.py +++ b/loko/interventions/tests/test_visibility_permissions.py @@ -208,6 +208,66 @@ class TeamDailyVisibilityTests(TestCase): visible_qs = filter_viewable_interventions_for_user(self.tech_user) self.assertIn(itv_yesterday, visible_qs) + @mock.patch('django.utils.timezone.now') + def test_visibility_replanned_past_intervention_visible_today(self, mock_now): + """ + Une intervention débutée dans le passé mais replanifiée pour aujourd'hui + doit être visible par le technicien restreint à aujourd'hui. + De même pour une intervention en cours (in_progress). + Une intervention débutée dans le passé et replanifiée pour demain ne doit pas être visible aujourd'hui. + """ + from datetime import datetime + import pytz + from interventions.models import Intervention + from interventions.permissions import can_view_intervention, filter_viewable_interventions_for_user + + # Fix today to 2026-06-17 at 8:00 AM UTC (after start hour) + mock_now.return_value = datetime(2026, 6, 17, 8, 0, 0, tzinfo=pytz.UTC) + + # 1. Began 2 weeks ago, replanned for today -> MUST be visible today + itv_replanned_today = Intervention.objects.create( + title='Replanned Today Task', + status='to_be_processed', + contract=self.contract, + thematic=self.thematic, + assigned_provider=self.company, + assigned_team=self.team, + begin_time=datetime(2026, 6, 1, 9, 0, 0, tzinfo=pytz.UTC), + planned_begin_time=datetime(2026, 6, 17, 10, 0, 0, tzinfo=pytz.UTC), + ) + + # 2. Began 2 weeks ago, replanned for tomorrow -> MUST NOT be visible today + itv_replanned_tomorrow = Intervention.objects.create( + title='Replanned Tomorrow Task', + status='to_be_processed', + contract=self.contract, + thematic=self.thematic, + assigned_provider=self.company, + assigned_team=self.team, + begin_time=datetime(2026, 6, 1, 9, 0, 0, tzinfo=pytz.UTC), + planned_begin_time=datetime(2026, 6, 18, 10, 0, 0, tzinfo=pytz.UTC), + ) + + # 3. Began yesterday, currently in_progress without today's planned_begin_time -> MUST be visible + itv_in_progress = Intervention.objects.create( + title='In Progress Task', + status='in_progress', + contract=self.contract, + thematic=self.thematic, + assigned_provider=self.company, + assigned_team=self.team, + begin_time=datetime(2026, 6, 16, 15, 0, 0, tzinfo=pytz.UTC), + ) + + self.assertTrue(can_view_intervention(self.tech_user, itv_replanned_today)) + self.assertFalse(can_view_intervention(self.tech_user, itv_replanned_tomorrow)) + self.assertTrue(can_view_intervention(self.tech_user, itv_in_progress)) + + visible_qs = filter_viewable_interventions_for_user(self.tech_user) + self.assertIn(itv_replanned_today, visible_qs) + self.assertNotIn(itv_replanned_tomorrow, visible_qs) + self.assertIn(itv_in_progress, visible_qs) +