From e7641e7be0dad83a2dd7314fd65a9b607d37d0c7 Mon Sep 17 00:00:00 2001 From: mariomitte Date: Sat, 1 Aug 2026 09:48:44 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20pro=C5=A1iri=20bilje=C5=A1ke=20na=20?= =?UTF-8?q?=C4=8Dlanove=20tima=20i=20osvje=C5=BEi=20kalendar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Widget Bilješke sada omogućuje odabir više članova tima (ne samo servisera), a backend validacija i testovi su usklađeni. Dodatno, nakon realtime service_note notifikacije sada se osvježava service-notes store, te create flow više ne briše lokalne bilješke kada odgovor vrati praznu listu. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- backend/modules/fleet/serializers.py | 14 ++++---- .../fleet/tests/test_service_context_notes.py | 35 +++++++++++++++++++ .../components/layout/ServiceNotesWidget.jsx | 10 +++--- .../components/layout/TaskCalendarPortal.jsx | 14 ++++---- frontend/src/stores/notificationStore.js | 4 +++ frontend/src/stores/serviceNotesStore.js | 2 +- 6 files changed, 59 insertions(+), 20 deletions(-) diff --git a/backend/modules/fleet/serializers.py b/backend/modules/fleet/serializers.py index 1a8f2c4..76ab3c0 100644 --- a/backend/modules/fleet/serializers.py +++ b/backend/modules/fleet/serializers.py @@ -155,12 +155,12 @@ class ServiceContextNoteCreateSerializer(serializers.Serializer): note_date = serializers.DateField(required=False, allow_null=True) audience = serializers.ChoiceField(choices=AUDIENCE_CHOICES, default='self') target_user = serializers.PrimaryKeyRelatedField( - queryset=User.objects.filter(is_active=True), + queryset=User.objects.filter(is_active=True, is_team_member=True), required=False, allow_null=True, ) target_users = serializers.ListField( - child=serializers.PrimaryKeyRelatedField(queryset=User.objects.filter(is_active=True)), + child=serializers.PrimaryKeyRelatedField(queryset=User.objects.filter(is_active=True, is_team_member=True)), required=False, allow_empty=False, ) @@ -203,12 +203,12 @@ class ServiceContextNoteCreateSerializer(serializers.Serializer): if audience in {'self', 'team'} and target_user: raise serializers.ValidationError({'target_user': 'Odabrani tip publike ne koristi ciljano polje člana tima.'}) if audience in {'self', 'team'} and len(target_users) > 0: - raise serializers.ValidationError({'target_users': 'Odabrani tip publike ne koristi listu servisera.'}) - if target_user and not getattr(target_user, 'is_serviser', False): - raise serializers.ValidationError({'target_user': 'Bilješku je moguće poslati samo serviseru.'}) + raise serializers.ValidationError({'target_users': 'Odabrani tip publike ne koristi listu članova tima.'}) + if target_user and not getattr(target_user, 'is_team_member', False): + raise serializers.ValidationError({'target_user': 'Bilješku je moguće poslati samo članu tima.'}) for selected_user in target_users: - if not getattr(selected_user, 'is_serviser', False): - raise serializers.ValidationError({'target_users': 'Bilješku je moguće poslati samo serviserima.'}) + if not getattr(selected_user, 'is_team_member', False): + raise serializers.ValidationError({'target_users': 'Bilješku je moguće poslati samo članovima tima.'}) if work_order: if not work_order.is_active or str(work_order.status or '').lower() != 'open': diff --git a/backend/modules/fleet/tests/test_service_context_notes.py b/backend/modules/fleet/tests/test_service_context_notes.py index b8c19f4..ec1fcc1 100644 --- a/backend/modules/fleet/tests/test_service_context_notes.py +++ b/backend/modules/fleet/tests/test_service_context_notes.py @@ -41,6 +41,13 @@ class ServiceContextNotesApiTests(TestCase): is_team_member=True, is_serviser=True, ) + self.team_member_non_servicer = user_model.objects.create_user( + username=f'team-{suffix}', + email=f'team-{suffix}@example.test', + password='test1234', + is_team_member=True, + is_serviser=False, + ) self.vehicle = Vehicle.objects.create( asset_type='crane', @@ -132,3 +139,31 @@ class ServiceContextNotesApiTests(TestCase): ) self.assertEqual(response.status_code, 201, response.content) self.assertEqual(int(response.data.get('created_count', 0)), 2) + + def test_supervisor_can_create_member_note_for_non_servicer_team_member(self): + self.client.force_authenticate(user=self.supervisor) + response = self.client.post( + reverse('service-context-note-list'), + data={ + 'note': 'Bilješka za team člana koji nije serviser.', + 'audience': 'member', + 'target_users': [str(self.team_member_non_servicer.id)], + }, + format='json', + ) + self.assertEqual(response.status_code, 201, response.content) + self.assertEqual(int(response.data.get('created_count', 0)), 1) + + def test_member_note_rejects_non_team_member_target(self): + self.client.force_authenticate(user=self.supervisor) + response = self.client.post( + reverse('service-context-note-list'), + data={ + 'note': 'Bilješka ne-smije ići van tima.', + 'audience': 'member', + 'target_users': [str(self.other.id)], + }, + format='json', + ) + self.assertEqual(response.status_code, 400, response.content) + self.assertIn('target_users', response.data) diff --git a/frontend/src/components/layout/ServiceNotesWidget.jsx b/frontend/src/components/layout/ServiceNotesWidget.jsx index 83b4db9..ba9809c 100644 --- a/frontend/src/components/layout/ServiceNotesWidget.jsx +++ b/frontend/src/components/layout/ServiceNotesWidget.jsx @@ -24,7 +24,7 @@ export default function ServiceNotesWidget({ canManageRecipients = false, activeTasks = [], activeWorkOrders = [], - servicers = [], + teamMembers = [], onCreate, onClose, }) { @@ -156,7 +156,7 @@ export default function ServiceNotesWidget({ className="w-full rounded-md border border-border-hairline bg-canvas-base px-2 py-1.5 text-xs text-text-main" > - {canManageRecipients && } + {canManageRecipients && } {canManageRecipients && } {canManageRecipients && audience === 'member' && ( @@ -167,10 +167,10 @@ export default function ServiceNotesWidget({ const selected = Array.from(event.currentTarget.selectedOptions).map((option) => option.value); setTargetUsers(selected); }} - size={Math.min(6, Math.max(3, servicers.length || 3))} + size={Math.min(6, Math.max(3, teamMembers.length || 3))} className="w-full rounded-md border border-border-hairline bg-canvas-base px-2 py-1.5 text-xs text-text-main" > - {servicers.map((member) => ( + {teamMembers.map((member) => ( @@ -179,7 +179,7 @@ export default function ServiceNotesWidget({ )} {canManageRecipients && audience === 'member' && (

- Držite Ctrl (ili Cmd) za odabir više servisera. + Držite Ctrl (ili Cmd) za odabir više članova tima.

)}