feat: improve intervention visibility rules to correctly handle active and replanned interventions
This commit is contained in:
parent
9522eed8f8
commit
660c40f0f7
2 changed files with 121 additions and 48 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue