feat: prevent automatic intervention re-creation in Observation model using ref check
This commit is contained in:
parent
107575aa60
commit
8c4556a34c
2 changed files with 93 additions and 1 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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')
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue