feat: prevent automatic intervention re-creation in Observation model using ref check

This commit is contained in:
kdeterme 2026-08-11 11:09:28 +02:00
parent 107575aa60
commit 8c4556a34c
2 changed files with 93 additions and 1 deletions

View file

@ -438,6 +438,28 @@ class Observation(models.Model):
intervention = models.ForeignKey('interventions.Intervention', on_delete=models.SET_NULL, null=True, blank=True, related_name='observations') intervention = models.ForeignKey('interventions.Intervention', on_delete=models.SET_NULL, null=True, blank=True, related_name='observations')
project = models.ForeignKey('projects.Project', on_delete=models.SET_NULL, null=True, blank=True, related_name='observations', verbose_name=_("Projet lié")) project = models.ForeignKey('projects.Project', on_delete=models.SET_NULL, null=True, blank=True, related_name='observations', verbose_name=_("Projet lié"))
def has_existing_intervention(self):
"""
Vérifie si une intervention est déjà liée à cette observation,
ou si une intervention a déjà été créée à partir de cette observation (ex. via source_ref).
Permet d'éviter de ré-instancier automatiquement une intervention en boucle
lorsqu'une intervention existante est annulée et que l'observation repasse au statut 'À traiter'.
"""
if self.intervention_id is not None:
return True
if not self.pk:
return False
from interventions.models import Intervention
possible_refs = [str(self.pk)]
if self.code:
possible_refs.append(self.code)
possible_refs.append(f"OBS{self.pk:05d}")
return Intervention.objects.filter(
models.Q(observations=self) | models.Q(source_ref__in=possible_refs)
).exists()
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
is_new = self.pk is None is_new = self.pk is None
old_status = None old_status = None
@ -459,7 +481,7 @@ class Observation(models.Model):
if (self.symptom_id and if (self.symptom_id and
self.symptom.auto_create_intervention and self.symptom.auto_create_intervention and
self.status == 'to_process' and self.status == 'to_process' and
not self.intervention_id): not self.has_existing_intervention()):
status_changed_to_to_process = (old_status != 'to_process' and self.status == 'to_process') status_changed_to_to_process = (old_status != 'to_process' and self.status == 'to_process')
symptom_changed_to_auto = (old_symptom_id != self.symptom_id and self.symptom.auto_create_intervention) symptom_changed_to_auto = (old_symptom_id != self.symptom_id and self.symptom.auto_create_intervention)

View file

@ -502,3 +502,73 @@ class ObservationPermissionTests(TestCase):
self.assertContains(response, "Une intervention liée à cette observation a été annulée") self.assertContains(response, "Une intervention liée à cette observation a été annulée")
self.assertContains(response, "INT-TEST-CANCEL") self.assertContains(response, "INT-TEST-CANCEL")
def test_no_auto_intervention_loop_on_cancellation(self):
from contracts.models import Contract, Company
from interventions.models import Symptom, Intervention
from interventions.views.workflow import attempt_status_update
from django.utils import timezone
company = Company.objects.create(name='Provider Co Loop')
contract = Contract.objects.create(
contract_number='C-LOOP',
company=company,
start_date=timezone.now().date(),
end_date=timezone.now().date() + timezone.timedelta(days=365)
)
symptom = Symptom.objects.create(
name_fr='Symptome Auto Loop Test',
name_nl='Symptoom Auto Loop Test',
thematic=self.thematic_a,
auto_create_intervention=True,
contract=contract,
provider=company,
)
# 1. Create observation in 'to_process' -> auto-creates intervention
obs = Observation.objects.create(
description='Loop test observation',
latitude=50.5,
longitude=4.5,
created_by=self.internal_user,
thematic=self.thematic_a,
symptom=symptom,
status='to_process',
)
obs.refresh_from_db()
self.assertIsNotNone(obs.intervention)
self.assertEqual(obs.status, 'in_progress')
first_intervention = obs.intervention
# 2. Cancel the intervention
success, msg, _ = attempt_status_update(
request=None,
intervention=first_intervention,
user=self.admin_user,
new_status='canceled',
cancellation_type='other',
cancellation_reason='Test cancellation reason',
)
self.assertTrue(success, msg)
# 3. Observation status is reset to 'to_process'
obs.refresh_from_db()
self.assertEqual(obs.status, 'to_process')
# 4. Save observation again (or unlink and save) - MUST NOT create a new intervention
obs.save()
obs.refresh_from_db()
self.assertEqual(obs.status, 'to_process')
# Unlink intervention and save observation again
obs.intervention = None
obs.save()
obs.refresh_from_db()
# Total interventions with source_ref equal to obs.code or obs.pk must still be 1
possible_refs = [str(obs.pk), obs.code, f"OBS{obs.pk:05d}"]
total_interventions = Intervention.objects.filter(source_ref__in=possible_refs).count()
self.assertEqual(total_interventions, 1)
self.assertEqual(obs.status, 'to_process')