feat: add reprogrammation intervention type restricted to trafficlights thematic and update forms accordingly

This commit is contained in:
kdeterme 2026-08-19 10:35:55 +02:00
parent f810041e98
commit 46f1d0d384
12 changed files with 171 additions and 5 deletions

View file

@ -149,6 +149,10 @@ msgstr "Construction"
msgid "Ramassage" msgid "Ramassage"
msgstr "Collection" msgstr "Collection"
#: interventions/models.py:105
msgid "Reprogrammation"
msgstr "Reprogramming"
#: interventions/models.py:108 #: interventions/models.py:108
msgid "Avarie" msgid "Avarie"
msgstr "Damage" msgstr "Damage"

View file

@ -149,6 +149,10 @@ msgstr "Construction"
msgid "Ramassage" msgid "Ramassage"
msgstr "Ramassage" msgstr "Ramassage"
#: interventions/models.py:105
msgid "Reprogrammation"
msgstr "Reprogrammation"
#: interventions/models.py:108 #: interventions/models.py:108
msgid "Avarie" msgid "Avarie"
msgstr "Avarie" msgstr "Avarie"

View file

@ -149,6 +149,10 @@ msgstr "Bouw"
msgid "Ramassage" msgid "Ramassage"
msgstr "Ophaling" msgstr "Ophaling"
#: interventions/models.py:105
msgid "Reprogrammation"
msgstr "Herprogrammering"
#: interventions/models.py:108 #: interventions/models.py:108
msgid "Avarie" msgid "Avarie"
msgstr "Schade" msgstr "Schade"

View file

@ -0,0 +1,18 @@
# Generated by Django 6.0.7 on 2026-08-19 08:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('interventions', '0003_operationtemplate_team'),
]
operations = [
migrations.AlterField(
model_name='intervention',
name='type',
field=models.CharField(blank=True, choices=[('fixing', 'Dépannage'), ('repair', 'Réparation'), ('repair_post_preventive', 'Réparation post préventif'), ('replace', 'Remplacement'), ('care', 'Entretien'), ('control', 'Contrôle'), ('inspection', 'Inspection'), ('build', 'Construction'), ('collection', 'Ramassage'), ('reprogramming', 'Reprogrammation')], null=True),
),
]

View file

@ -102,8 +102,26 @@ TYPE_CHOICES = [
('inspection', _('Inspection')), ('inspection', _('Inspection')),
('build', _('Construction')), ('build', _('Construction')),
('collection', _('Ramassage')), ('collection', _('Ramassage')),
('reprogramming', _('Reprogrammation')),
] ]
def get_type_choices(thematic=None):
"""
Renvoie la liste des choix de type d'intervention selon la thématique.
Le type 'reprogramming' (Reprogrammation) n'est disponible que pour la thématique 'trafficlights'.
"""
thematic_code = None
if thematic:
if isinstance(thematic, str):
thematic_code = thematic
elif hasattr(thematic, 'code'):
thematic_code = thematic.code
if thematic_code == 'trafficlights':
return list(TYPE_CHOICES)
return [c for c in TYPE_CHOICES if c[0] != 'reprogramming']
ORIGIN_TYPE_CHOICES = [ ORIGIN_TYPE_CHOICES = [
('damage', _('Avarie')), ('damage', _('Avarie')),
('defect', _('Défaut/Panne')), ('defect', _('Défaut/Panne')),

View file

@ -28,6 +28,7 @@ from interventions.models import (
OperationStatus, OperationStatus,
STATUS_ORDERS, STATUS_ORDERS,
InterventionDocument, InterventionDocument,
get_type_choices,
) )
from django.db.models import Q from django.db.models import Q
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@ -1665,6 +1666,114 @@ class InterventionGeojsonExternalFilterTests(TestCase):
self.assertIn(self.itv_external.id, ids_ext) self.assertIn(self.itv_external.id, ids_ext)
class TrafficlightsReprogrammingTypeTests(TestCase):
def setUp(self):
User = get_user_model()
self.user = User.objects.create_user(username='admin_tl', password='pwd')
self.role_admin = Role.objects.create(name='admin')
self.user_config = UserConfig.objects.create(user=self.user, is_intern=True)
self.user_config.roles.add(self.role_admin)
self.tl_thematic = Thematic.objects.create(code='trafficlights', name_fr='Feux tricolores', name_nl='Verkeerslichten')
self.struct_thematic = Thematic.objects.create(code='structures', name_fr='Ouvrages d\'art', name_nl='Kunstwerken')
UserThematics.objects.create(user_config=self.user_config, thematic=self.tl_thematic, can_edit_interventions=True)
UserThematics.objects.create(user_config=self.user_config, thematic=self.struct_thematic, can_edit_interventions=True)
self.itv_tl = Intervention.objects.create(
code='ITV-TL-001',
title='Intervention Feux',
status='in_preparation',
thematic=self.tl_thematic,
type='fixing',
created_by=self.user
)
self.itv_struct = Intervention.objects.create(
code='ITV-ST-001',
title='Intervention Ouvrage',
status='in_preparation',
thematic=self.struct_thematic,
type='fixing',
created_by=self.user
)
def test_get_type_choices(self):
tl_choices = [c[0] for c in get_type_choices(self.tl_thematic)]
self.assertIn('reprogramming', tl_choices)
self.assertIn('fixing', tl_choices)
tl_str_choices = [c[0] for c in get_type_choices('trafficlights')]
self.assertIn('reprogramming', tl_str_choices)
struct_choices = [c[0] for c in get_type_choices(self.struct_thematic)]
self.assertNotIn('reprogramming', struct_choices)
self.assertIn('fixing', struct_choices)
struct_str_choices = [c[0] for c in get_type_choices('structures')]
self.assertNotIn('reprogramming', struct_str_choices)
none_choices = [c[0] for c in get_type_choices(None)]
self.assertNotIn('reprogramming', none_choices)
def test_intervention_form_choices(self):
from interventions.views.forms import InterventionForm
form_tl = InterventionForm(thematic=self.tl_thematic, user=self.user)
tl_choices = [c[0] for c in form_tl.fields['type'].choices]
self.assertIn('reprogramming', tl_choices)
form_struct = InterventionForm(thematic=self.struct_thematic, user=self.user)
struct_choices = [c[0] for c in form_struct.fields['type'].choices]
self.assertNotIn('reprogramming', struct_choices)
def test_preventive_intervention_form_choices(self):
from interventions.views.forms import PreventiveInterventionForm
form_tl = PreventiveInterventionForm(thematic=self.tl_thematic, user=self.user)
tl_choices = [c[0] for c in form_tl.fields['type'].choices]
self.assertIn('reprogramming', tl_choices)
form_struct = PreventiveInterventionForm(thematic=self.struct_thematic, user=self.user)
struct_choices = [c[0] for c in form_struct.fields['type'].choices]
self.assertNotIn('reprogramming', struct_choices)
def test_intervention_detail_context(self):
self.client.force_login(self.user)
resp_tl = self.client.get(reverse('interventions:intervention_detail', kwargs={'intervention_id': self.itv_tl.id}))
self.assertEqual(resp_tl.status_code, 200)
tl_type_choices = [c[0] for c in resp_tl.context['type_choices']]
self.assertIn('reprogramming', tl_type_choices)
resp_struct = self.client.get(reverse('interventions:intervention_detail', kwargs={'intervention_id': self.itv_struct.id}))
self.assertEqual(resp_struct.status_code, 200)
struct_type_choices = [c[0] for c in resp_struct.context['type_choices']]
self.assertNotIn('reprogramming', struct_type_choices)
def test_update_intervention_type_reprogramming(self):
self.client.force_login(self.user)
# Updating trafficlights intervention to reprogramming -> SUCCESS
update_url_tl = reverse('interventions:update_intervention', kwargs={'intervention_id': self.itv_tl.id})
resp = self.client.post(
update_url_tl,
data=json.dumps({'type': 'reprogramming'}),
content_type='application/json'
)
self.assertEqual(resp.status_code, 200)
self.itv_tl.refresh_from_db()
self.assertEqual(self.itv_tl.type, 'reprogramming')
# Updating structures intervention to reprogramming -> REJECTED
update_url_struct = reverse('interventions:update_intervention', kwargs={'intervention_id': self.itv_struct.id})
resp = self.client.post(
update_url_struct,
data=json.dumps({'type': 'reprogramming'}),
content_type='application/json'
)
self.itv_struct.refresh_from_db()
self.assertEqual(self.itv_struct.type, 'fixing')

View file

@ -61,7 +61,7 @@ from interventions.models import (InterventionSubscription, Intervention, Interv
Operation, OperationTemplate, OperationTemplateItem, OperationStatus, OperationStatusSet, MeasurementDefinition, Operation, OperationTemplate, OperationTemplateItem, OperationStatus, OperationStatusSet, MeasurementDefinition,
GuidedOperationTemplate, GuidedOperationData, GuidedOperationMedia, GuidedOperationTemplate, GuidedOperationData, GuidedOperationMedia,
InterventionPostPeriodQuantity, InterventionEmailLog, InterventionAssignment) InterventionPostPeriodQuantity, InterventionEmailLog, InterventionAssignment)
from interventions.models import STATUS_CHOICES, STATUS_ORDERS, MAINTAIN_CHOICES, TYPE_CHOICES, ORIGIN_TYPE_CHOICES, PAUSE_REASON_CHOICES from interventions.models import STATUS_CHOICES, STATUS_ORDERS, MAINTAIN_CHOICES, TYPE_CHOICES, ORIGIN_TYPE_CHOICES, PAUSE_REASON_CHOICES, get_type_choices
from interventions.views.forms import (InterventionForm, InterventionNoteForm, InterventionDocumentForm, from interventions.views.forms import (InterventionForm, InterventionNoteForm, InterventionDocumentForm,
InterventionOccupationForm, PreventiveInterventionForm, AssetsLinkForm, InterventionOccupationForm, PreventiveInterventionForm, AssetsLinkForm,
LinkExistingInterventionsForm) LinkExistingInterventionsForm)
@ -637,7 +637,7 @@ def intervention_detail(request, intervention_id):
'providers': providers, 'providers': providers,
'contracts': contracts, 'contracts': contracts,
'maintain_choices': MAINTAIN_CHOICES, 'maintain_choices': MAINTAIN_CHOICES,
'type_choices': TYPE_CHOICES, 'type_choices': get_type_choices(intervention.thematic),
'origin_type_choices': ORIGIN_TYPE_CHOICES, 'origin_type_choices': ORIGIN_TYPE_CHOICES,
'priority_choices': Intervention._meta.get_field('priority').choices, 'priority_choices': Intervention._meta.get_field('priority').choices,
'symptoms': Symptom.objects.filter(is_active=True, thematic=intervention.thematic).order_by('name_fr'), 'symptoms': Symptom.objects.filter(is_active=True, thematic=intervention.thematic).order_by('name_fr'),

View file

@ -8,7 +8,7 @@ import bleach
import json import json
import re import re
from interventions.models import Intervention, InterventionNote, InterventionDocument, Symptom, OperationTemplate, InterventionOccupation, SourceCategory, OperationStatusSet, MeasurementDefinition, TYPE_CHOICES from interventions.models import Intervention, InterventionNote, InterventionDocument, Symptom, OperationTemplate, InterventionOccupation, SourceCategory, OperationStatusSet, MeasurementDefinition, TYPE_CHOICES, get_type_choices
from assets.models import AssetCategory from assets.models import AssetCategory
from common.models import UserConfig, UserThematics from common.models import UserConfig, UserThematics
from contracts.models import Contract, ContractOrder, CompanyMember from contracts.models import Contract, ContractOrder, CompanyMember
@ -93,6 +93,11 @@ class InterventionForm(forms.ModelForm):
(val, lbl) for val, lbl in self.fields['status'].choices if val in allowed_statuses (val, lbl) for val, lbl in self.fields['status'].choices if val in allowed_statuses
] ]
# Filtrer les types selon la thématique (ex: reprogrammation uniquement pour trafficlights)
thematic_obj = thematic or (self.instance.thematic if self.instance and getattr(self.instance, 'pk', None) else None)
if 'type' in self.fields:
self.fields['type'].choices = get_type_choices(thematic_obj)
type_value = self.initial.get('type') or self.data.get('type') type_value = self.initial.get('type') or self.data.get('type')
maintain_type_value = self.initial.get('maintain_type') or self.data.get('maintain_type') maintain_type_value = self.initial.get('maintain_type') or self.data.get('maintain_type')
if type_value == 'inspection': if type_value == 'inspection':
@ -515,6 +520,9 @@ class PreventiveInterventionForm(forms.Form):
def __init__(self, *args, user=None, thematic=None, **kwargs): def __init__(self, *args, user=None, thematic=None, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if 'type' in self.fields:
self.fields['type'].choices = get_type_choices(thematic)
type_value = self.initial.get('type') or (self.data.get('type') if self.data else None) or 'care' type_value = self.initial.get('type') or (self.data.get('type') if self.data else None) or 'care'
if type_value == 'inspection': if type_value == 'inspection':
self.fields['status'].choices = [ self.fields['status'].choices = [

View file

@ -62,7 +62,7 @@ from interventions.models import (InterventionSubscription, Intervention, Interv
GuidedOperationTemplate, GuidedOperationData, GuidedOperationMedia, GuidedOperationTemplate, GuidedOperationData, GuidedOperationMedia,
InterventionPostPeriodQuantity, InterventionEmailLog, InterventionAssignment, InterventionPostPeriodQuantity, InterventionEmailLog, InterventionAssignment,
DirectContractRouting) DirectContractRouting)
from interventions.models import STATUS_CHOICES, STATUS_ORDERS, MAINTAIN_CHOICES, TYPE_CHOICES, ORIGIN_TYPE_CHOICES, PAUSE_REASON_CHOICES from interventions.models import STATUS_CHOICES, STATUS_ORDERS, MAINTAIN_CHOICES, TYPE_CHOICES, ORIGIN_TYPE_CHOICES, PAUSE_REASON_CHOICES, get_type_choices
from interventions.views.forms import (InterventionForm, InterventionNoteForm, InterventionDocumentForm, from interventions.views.forms import (InterventionForm, InterventionNoteForm, InterventionDocumentForm,
InterventionOccupationForm, PreventiveInterventionForm, AssetsLinkForm, InterventionOccupationForm, PreventiveInterventionForm, AssetsLinkForm,
LinkExistingInterventionsForm) LinkExistingInterventionsForm)
@ -2505,7 +2505,8 @@ def _update_intervention_field(intervention, field, new_value, user_config=None)
return False, _('Maintain Type not found') return False, _('Maintain Type not found')
elif field == "type": elif field == "type":
if new_value in dict(TYPE_CHOICES).keys(): allowed_types = dict(get_type_choices(intervention.thematic)).keys()
if new_value in allowed_types:
intervention.type = new_value intervention.type = new_value
return True, None return True, None
return False, _('Type not found') return False, _('Type not found')