From 6e02156034e76c009327283ef00418e223430fda Mon Sep 17 00:00:00 2001 From: kdeterme Date: Mon, 17 Aug 2026 17:06:02 +0200 Subject: [PATCH] fix: prevent forced intervention status transition to to_be_planned during unassignment if unauthorized --- loko/interventions/tests/test_calendar.py | 29 ++++++++++++++++++++++- loko/interventions/views/calendar.py | 12 ++++++---- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/loko/interventions/tests/test_calendar.py b/loko/interventions/tests/test_calendar.py index d81adbe..b6515bd 100644 --- a/loko/interventions/tests/test_calendar.py +++ b/loko/interventions/tests/test_calendar.py @@ -738,6 +738,31 @@ class ExternalManagerScheduleUpdateTests(TestCase): self.assertIsNone(self.itv.planned_begin_time) self.assertIsNone(self.itv.planned_end_time) + def test_external_manager_unassign_without_to_be_planned_permission_keeps_current_status(self): + # Disable can_change_status_to on to_be_planned + UserContractStatusPermission.objects.filter( + user_contract=self.uca, + status='to_be_planned', + ).update(can_change_status_to=False) + + payload = { + 'intervention_id': self.itv.id, + 'assigned_member_id': None, + 'assigned_team_id': None, + 'unassign': True, + } + res = self.client.post(self.update_url, data=json.dumps(payload), content_type='application/json') + self.assertEqual(res.status_code, 200) + self.assertTrue(res.json().get('success')) + + self.itv.refresh_from_db() + # Status remains to_be_processed since user cannot transition to to_be_planned + self.assertEqual(self.itv.status, 'to_be_processed') + self.assertIsNone(self.itv.assigned_member) + self.assertIsNone(self.itv.assigned_team) + self.assertIsNone(self.itv.planned_begin_time) + self.assertIsNone(self.itv.planned_end_time) + def test_external_manager_cannot_assign_member_of_different_company(self): new_begin = timezone.now() + timedelta(days=1) new_end = new_begin + timedelta(hours=2) @@ -776,7 +801,9 @@ class ExternalManagerScheduleUpdateTests(TestCase): self.assertEqual(res.status_code, 200) itv_assigned.refresh_from_db() - self.assertEqual(itv_assigned.status, 'to_be_planned') + # Since 'assigned' -> 'to_be_planned' is not an allowed transition in ALLOWED_TRANSITIONS, + # the status remains 'assigned' while unassigning member/team/dates + self.assertEqual(itv_assigned.status, 'assigned') self.assertIsNone(itv_assigned.assigned_member) self.assertIsNone(itv_assigned.assigned_team) diff --git a/loko/interventions/views/calendar.py b/loko/interventions/views/calendar.py index 3df070a..38a765f 100644 --- a/loko/interventions/views/calendar.py +++ b/loko/interventions/views/calendar.py @@ -560,7 +560,7 @@ def _interventions_events_by_members( planned_begin = getattr(itv, 'planned_begin_time', None) return bool(planned_begin and planned_begin > timezone.now()) - backlog_allowed_statuses = {'in_preparation', 'to_be_approved', 'to_be_planned', 'to_be_processed'} + backlog_allowed_statuses = {'in_preparation', 'to_be_approved', 'to_be_planned', 'to_be_processed', 'assigned'} PRE_PROCESSING_STATUSES = {'in_preparation', 'to_be_approved', 'to_be_planned'} backlog_allowed_types = {'corrective', 'preventive', 'ameliorative'} maintain_type_labels = dict(MAINTAIN_CHOICES) @@ -1010,7 +1010,7 @@ def _interventions_events_by_teams_daily( }) # Compute backlog groups for the assignment sidebar - backlog_allowed_statuses = {'in_preparation', 'to_be_approved', 'to_be_planned', 'to_be_processed'} + backlog_allowed_statuses = {'in_preparation', 'to_be_approved', 'to_be_planned', 'to_be_processed', 'assigned'} backlog_allowed_types = {'corrective', 'preventive', 'ameliorative'} maintain_type_labels = dict(MAINTAIN_CHOICES) contract_filter = None if allowed_contract_ids is None else set(allowed_contract_ids) @@ -1293,9 +1293,13 @@ def interventions_schedule_update(request): # If intervention was "to_be_planned" and is now assigned -> change to "to_be_processed" if prev_status == 'to_be_planned' and is_now_assigned: new_status = 'to_be_processed' - # If intervention is being unassigned (back to backlog) -> change to "to_be_planned" + # If intervention is being unassigned (back to backlog) -> change to "to_be_planned" only if user has transition rights elif is_unassigning and prev_status in ('to_be_processed', 'assigned'): - new_status = 'to_be_planned' + allowed_transitions = user_allowed_transitions(request.user, prev_status, intervention=intervention) + if 'to_be_planned' in allowed_transitions or is_admin: + new_status = 'to_be_planned' + else: + new_status = prev_status if new_status != prev_status: intervention.status = new_status