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"
msgstr "Collection"
#: interventions/models.py:105
msgid "Reprogrammation"
msgstr "Reprogramming"
#: interventions/models.py:108
msgid "Avarie"
msgstr "Damage"

View file

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

View file

@ -149,6 +149,10 @@ msgstr "Bouw"
msgid "Ramassage"
msgstr "Ophaling"
#: interventions/models.py:105
msgid "Reprogrammation"
msgstr "Herprogrammering"
#: interventions/models.py:108
msgid "Avarie"
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')),
('build', _('Construction')),
('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 = [
('damage', _('Avarie')),
('defect', _('Défaut/Panne')),

View file

@ -28,6 +28,7 @@ from interventions.models import (
OperationStatus,
STATUS_ORDERS,
InterventionDocument,
get_type_choices,
)
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType
@ -1665,6 +1666,114 @@ class InterventionGeojsonExternalFilterTests(TestCase):
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,
GuidedOperationTemplate, GuidedOperationData, GuidedOperationMedia,
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,
InterventionOccupationForm, PreventiveInterventionForm, AssetsLinkForm,
LinkExistingInterventionsForm)
@ -637,7 +637,7 @@ def intervention_detail(request, intervention_id):
'providers': providers,
'contracts': contracts,
'maintain_choices': MAINTAIN_CHOICES,
'type_choices': TYPE_CHOICES,
'type_choices': get_type_choices(intervention.thematic),
'origin_type_choices': ORIGIN_TYPE_CHOICES,
'priority_choices': Intervention._meta.get_field('priority').choices,
'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 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 common.models import UserConfig, UserThematics
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
]
# 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')
maintain_type_value = self.initial.get('maintain_type') or self.data.get('maintain_type')
if type_value == 'inspection':
@ -515,6 +520,9 @@ class PreventiveInterventionForm(forms.Form):
def __init__(self, *args, user=None, thematic=None, **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'
if type_value == 'inspection':
self.fields['status'].choices = [

View file

@ -62,7 +62,7 @@ from interventions.models import (InterventionSubscription, Intervention, Interv
GuidedOperationTemplate, GuidedOperationData, GuidedOperationMedia,
InterventionPostPeriodQuantity, InterventionEmailLog, InterventionAssignment,
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,
InterventionOccupationForm, PreventiveInterventionForm, AssetsLinkForm,
LinkExistingInterventionsForm)
@ -2505,7 +2505,8 @@ def _update_intervention_field(intervention, field, new_value, user_config=None)
return False, _('Maintain Type not found')
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
return True, None
return False, _('Type not found')