diff --git a/loko/interventions/tests/test_calendar.py b/loko/interventions/tests/test_calendar.py index 7d0ee83..7d8402d 100644 --- a/loko/interventions/tests/test_calendar.py +++ b/loko/interventions/tests/test_calendar.py @@ -8,7 +8,7 @@ from django.contrib import messages from django.contrib.messages import get_messages from django.core.files.uploadedfile import SimpleUploadedFile -from common.models import UserConfig, Role, Thematic, UserThematics +from common.models import UserConfig, Role, Thematic, UserThematics, UserContractAccess from contracts.models import Company, CompanyTeam, CompanyMember, Contract, ContractPost, ContractOrder, ContractOrderQuote, ContractOrderQuoteItem from assets.models import AssetCategory from interventions.models import ( @@ -384,3 +384,155 @@ class CalendarSearchTests(TestCase): self.assertNotIn(self.intv_other.id, feature_ids) +class CalendarContractFilterTests(TestCase): + def setUp(self): + User = get_user_model() + self.thematic = Thematic.objects.create(code='lighting', name_fr='Éclairage public', name_nl='Openbare verlichting') + self.company = Company.objects.create(name='Provider Corp') + + # Deux contrats sur la même thématique + today = timezone.now().date() + self.contract_allowed = Contract.objects.create( + contract_number='CTR-ALLOWED', + company=self.company, + start_date=today - timedelta(days=10), + end_date=today + timedelta(days=365), + is_active=True, + ) + self.contract_allowed.thematics.add(self.thematic) + + self.contract_forbidden = Contract.objects.create( + contract_number='CTR-FORBIDDEN', + company=self.company, + start_date=today - timedelta(days=10), + end_date=today + timedelta(days=365), + is_active=True, + ) + self.contract_forbidden.thematics.add(self.thematic) + + # Utilisateur externe + self.external_user = User.objects.create_user(username='external-user', password='pwd') + self.role_external_manager = Role.objects.create(name='external_manager') + self.external_config = UserConfig.objects.create( + user=self.external_user, + is_intern=False, + default_thematic=self.thematic, + ) + self.external_config.roles.add(self.role_external_manager) + UserThematics.objects.create( + user_config=self.external_config, + thematic=self.thematic, + can_view_interventions=True, + can_edit_interventions=True, + ) + # Accès uniquement à contract_allowed + UserContractAccess.objects.create( + user_config=self.external_config, + contract=self.contract_allowed, + can_view_interventions=True, + ) + + # Utilisateur admin + self.admin_user = User.objects.create_user(username='admin-user', password='pwd') + self.role_admin = Role.objects.get_or_create(name='admin')[0] + self.admin_config = UserConfig.objects.create( + user=self.admin_user, + is_intern=True, + default_thematic=self.thematic, + ) + self.admin_config.roles.add(self.role_admin) + UserThematics.objects.create( + user_config=self.admin_config, + thematic=self.thematic, + can_view_interventions=True, + can_edit_interventions=True, + ) + + def test_external_user_only_sees_accessible_contracts_in_calendar(self): + self.client.login(username='external-user', password='pwd') + url = reverse('interventions:interventions_calendar') + response = self.client.get(url, follow=True) + self.assertEqual(response.status_code, 200) + + contract_choices = response.context['contract_choices'] + contract_ids = [c[0] for c in contract_choices] + + # L'externe ne doit voir que CTR-ALLOWED, pas CTR-FORBIDDEN + self.assertIn(self.contract_allowed.id, contract_ids) + self.assertNotIn(self.contract_forbidden.id, contract_ids) + + def test_admin_user_sees_all_contracts_for_thematic_in_calendar(self): + self.client.login(username='admin-user', password='pwd') + url = reverse('interventions:interventions_calendar') + response = self.client.get(url, follow=True) + self.assertEqual(response.status_code, 200) + + contract_choices = response.context['contract_choices'] + contract_ids = [c[0] for c in contract_choices] + + # L'admin voit tous les contrats de la thématique + self.assertIn(self.contract_allowed.id, contract_ids) + self.assertIn(self.contract_forbidden.id, contract_ids) + + def test_internal_user_with_limit_interventions_to_contracts_only_sees_accessible(self): + User = get_user_model() + intern_user = User.objects.create_user(username='intern-limited', password='pwd') + intern_config = UserConfig.objects.create( + user=intern_user, + is_intern=True, + limit_interventions_to_contracts=True, + default_thematic=self.thematic, + ) + UserThematics.objects.create( + user_config=intern_config, + thematic=self.thematic, + can_view_interventions=True, + can_edit_interventions=True, + ) + UserContractAccess.objects.create( + user_config=intern_config, + contract=self.contract_allowed, + can_view_interventions=True, + ) + + self.client.login(username='intern-limited', password='pwd') + url = reverse('interventions:interventions_calendar') + response = self.client.get(url, follow=True) + self.assertEqual(response.status_code, 200) + + contract_choices = response.context['contract_choices'] + contract_ids = [c[0] for c in contract_choices] + + self.assertIn(self.contract_allowed.id, contract_ids) + self.assertNotIn(self.contract_forbidden.id, contract_ids) + + def test_internal_user_without_limit_interventions_to_contracts_sees_all(self): + User = get_user_model() + intern_user = User.objects.create_user(username='intern-unlimited', password='pwd') + intern_config = UserConfig.objects.create( + user=intern_user, + is_intern=True, + limit_interventions_to_contracts=False, + default_thematic=self.thematic, + ) + UserThematics.objects.create( + user_config=intern_config, + thematic=self.thematic, + can_view_interventions=True, + can_edit_interventions=True, + ) + + self.client.login(username='intern-unlimited', password='pwd') + url = reverse('interventions:interventions_calendar') + response = self.client.get(url, follow=True) + self.assertEqual(response.status_code, 200) + + contract_choices = response.context['contract_choices'] + contract_ids = [c[0] for c in contract_choices] + + self.assertIn(self.contract_allowed.id, contract_ids) + self.assertIn(self.contract_forbidden.id, contract_ids) + + + + diff --git a/loko/interventions/views/calendar.py b/loko/interventions/views/calendar.py index 9eb7d8a..a843d02 100644 --- a/loko/interventions/views/calendar.py +++ b/loko/interventions/views/calendar.py @@ -209,7 +209,8 @@ def interventions_calendar(request): .order_by(f'{"name_nl" if lang == "nl" else "name_fr"}') ) - # Contrats : filtrés par les thématiques autorisées + # Contrats : filtrés par les thématiques autorisées et les droits de l'utilisateur + is_admin = bool(role_names & {'admin', 'top_manager'}) or request.user.is_superuser contracts_qs = ( Contract.objects .filter(thematics__id__in=allowed_thematic_ids) @@ -217,6 +218,13 @@ def interventions_calendar(request): .distinct() .order_by('contract_number') ) + if not is_admin: + if not user_config.is_intern or user_config.limit_interventions_to_contracts: + accessible_contract_ids = UserContractAccess.objects.filter( + user_config=user_config, + can_view_interventions=True, + ).values_list('contract_id', flat=True) + contracts_qs = contracts_qs.filter(id__in=accessible_contract_ids) # Choix enrichis avec les codes de thématique (pour filtrage dynamique JS) asset_category_choices = [