diff --git a/loko/interventions/locale/en/LC_MESSAGES/django.mo b/loko/interventions/locale/en/LC_MESSAGES/django.mo index b864e08..0ca6d19 100644 Binary files a/loko/interventions/locale/en/LC_MESSAGES/django.mo and b/loko/interventions/locale/en/LC_MESSAGES/django.mo differ diff --git a/loko/interventions/locale/en/LC_MESSAGES/django.po b/loko/interventions/locale/en/LC_MESSAGES/django.po index c21064d..6c11a24 100644 --- a/loko/interventions/locale/en/LC_MESSAGES/django.po +++ b/loko/interventions/locale/en/LC_MESSAGES/django.po @@ -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" diff --git a/loko/interventions/locale/fr/LC_MESSAGES/django.mo b/loko/interventions/locale/fr/LC_MESSAGES/django.mo index 2edbbd1..beb2ff2 100644 Binary files a/loko/interventions/locale/fr/LC_MESSAGES/django.mo and b/loko/interventions/locale/fr/LC_MESSAGES/django.mo differ diff --git a/loko/interventions/locale/fr/LC_MESSAGES/django.po b/loko/interventions/locale/fr/LC_MESSAGES/django.po index 6dc92dd..45b1a94 100644 --- a/loko/interventions/locale/fr/LC_MESSAGES/django.po +++ b/loko/interventions/locale/fr/LC_MESSAGES/django.po @@ -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" diff --git a/loko/interventions/locale/nl/LC_MESSAGES/django.mo b/loko/interventions/locale/nl/LC_MESSAGES/django.mo index 56dc04b..a7b7663 100644 Binary files a/loko/interventions/locale/nl/LC_MESSAGES/django.mo and b/loko/interventions/locale/nl/LC_MESSAGES/django.mo differ diff --git a/loko/interventions/locale/nl/LC_MESSAGES/django.po b/loko/interventions/locale/nl/LC_MESSAGES/django.po index 9888f8c..c341457 100644 --- a/loko/interventions/locale/nl/LC_MESSAGES/django.po +++ b/loko/interventions/locale/nl/LC_MESSAGES/django.po @@ -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" diff --git a/loko/interventions/migrations/0004_alter_intervention_type.py b/loko/interventions/migrations/0004_alter_intervention_type.py new file mode 100644 index 0000000..aaa6220 --- /dev/null +++ b/loko/interventions/migrations/0004_alter_intervention_type.py @@ -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), + ), + ] diff --git a/loko/interventions/models.py b/loko/interventions/models.py index 13d6113..7f1ffa1 100644 --- a/loko/interventions/models.py +++ b/loko/interventions/models.py @@ -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')), diff --git a/loko/interventions/tests/test_general.py b/loko/interventions/tests/test_general.py index f4acc52..ee32de2 100644 --- a/loko/interventions/tests/test_general.py +++ b/loko/interventions/tests/test_general.py @@ -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') + + + diff --git a/loko/interventions/views/detail.py b/loko/interventions/views/detail.py index 60cf679..83ee647 100644 --- a/loko/interventions/views/detail.py +++ b/loko/interventions/views/detail.py @@ -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'), diff --git a/loko/interventions/views/forms.py b/loko/interventions/views/forms.py index 0a08852..8692c39 100644 --- a/loko/interventions/views/forms.py +++ b/loko/interventions/views/forms.py @@ -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 = [ diff --git a/loko/interventions/views/helpers.py b/loko/interventions/views/helpers.py index 596ea7d..0ac8ed2 100644 --- a/loko/interventions/views/helpers.py +++ b/loko/interventions/views/helpers.py @@ -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')