import json from datetime import timedelta from unittest import mock from django.test import TestCase from django.contrib.auth import get_user_model 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 contracts.models import Company, CompanyTeam, CompanyMember, Contract, ContractPost, ContractOrder, ContractOrderQuote, ContractOrderQuoteItem from assets.models import AssetCategory from interventions.models import ( Intervention, InterventionContractPost, InterventionPlanificationTimeLine, InterventionRealizationTimeLine, Operation, InterventionAsset, OperationTemplate, OperationTemplateItem, OperationStatusSet, OperationStatus, STATUS_ORDERS, InterventionDocument, ) from django.contrib.contenttypes.models import ContentType from interventions.views import _group_operations_by_asset_and_template, _create_equipment_operations_for_assets from interventions.permissions import can_manage_occupations, can_delete_document from django.urls import reverse from django.utils import timezone from decimal import Decimal class OccupationPermissionsTests(TestCase): def setUp(self): self.User = get_user_model() self.technician_role, _ = Role.objects.get_or_create(name='technician') self.external_role, _ = Role.objects.get_or_create(name='external_manager') self.company = Company.objects.create(name='Test Company') self.team = CompanyTeam.objects.create(name='Team A', company=self.company) def _create_user_with_role(self, username, role): user = self.User.objects.create_user(username=username, password='pwd') user_config = UserConfig.objects.create(user=user) user_config.roles.add(role) return user, user_config def test_can_manage_occupations_for_assigned_technician(self): user, _ = self._create_user_with_role('tech', self.technician_role) member = CompanyMember.objects.create(user=user, name='Tech One') member.teams.add(self.team) intervention = Intervention.objects.create(title='Test intervention', assigned_member=member) self.assertTrue(can_manage_occupations(user, intervention)) def test_cannot_manage_occupations_for_unrelated_technician(self): user, _ = self._create_user_with_role('other-tech', self.technician_role) member = CompanyMember.objects.create(user=user, name='Tech Two') member.teams.add(self.team) other_company = Company.objects.create(name='Other Company') other_team = CompanyTeam.objects.create(name='Team B', company=other_company) intervention = Intervention.objects.create(title='Another intervention', assigned_team=other_team) self.assertFalse(can_manage_occupations(user, intervention)) def test_external_manager_can_manage(self): user, _ = self._create_user_with_role('external', self.external_role) intervention = Intervention.objects.create(title='External intervention') self.assertTrue(can_manage_occupations(user, intervention)) class TeamDailyVisibilityTests(TestCase): def setUp(self): from contracts.models import Company, CompanyTeam, CompanyMember from common.models import Role, UserConfig, Thematic, UserThematics, UserContractAccess self.User = get_user_model() self.tech_user = self.User.objects.create_user(username='tech-user', password='pwd') self.role_tech, _ = Role.objects.get_or_create(name='technician') self.tech_config = UserConfig.objects.create(user=self.tech_user, is_intern=False) self.tech_config.roles.add(self.role_tech) self.company = Company.objects.create(name='Test Company') # Team with restriction: only today's interventions from 6 AM from datetime import time self.team = CompanyTeam.objects.create( name='Restricted Team', company=self.company, restrict_today_interventions_visibility=True, visibility_start_hour=time(6, 0) ) self.member = CompanyMember.objects.create(user=self.tech_user, name='Technician Doe') self.member.teams.add(self.team) self.thematic = Thematic.objects.create(code='roads', name_fr='Voirie', name_nl='Wegen') UserThematics.objects.create( user_config=self.tech_config, thematic=self.thematic, can_view_interventions=True, can_edit_interventions=True, ) # Grant access via contract as well self.contract = Contract.objects.create( company=self.company, contract_number='C-TEST', start_date=timezone.now().date() - timedelta(days=10), end_date=timezone.now().date() + timedelta(days=10), is_active=True ) UserContractAccess.objects.create( user_config=self.tech_config, contract=self.contract, can_view_interventions=True, ) @mock.patch('django.utils.timezone.now') def test_visibility_before_and_after_start_hour(self, mock_now): from datetime import datetime import pytz from interventions.models import Intervention from interventions.permissions import can_view_intervention, filter_viewable_interventions_for_user # Fix today to 2026-06-17 # 1. Test before 6 AM (3:30 AM UTC = 5:30 AM Brussels local time CEST) mock_now.return_value = datetime(2026, 6, 17, 3, 30, 0, tzinfo=pytz.UTC) # Create today's, yesterday's, and tomorrow's interventions itv_today = Intervention.objects.create( title='Today Task', status='to_be_processed', contract=self.contract, thematic=self.thematic, assigned_provider=self.company, planned_begin_time=datetime(2026, 6, 17, 10, 0, 0, tzinfo=pytz.UTC), ) itv_yesterday = Intervention.objects.create( title='Yesterday Task', status='to_be_processed', contract=self.contract, thematic=self.thematic, assigned_provider=self.company, planned_begin_time=datetime(2026, 6, 16, 10, 0, 0, tzinfo=pytz.UTC), ) itv_tomorrow = Intervention.objects.create( title='Tomorrow Task', status='to_be_processed', contract=self.contract, thematic=self.thematic, assigned_provider=self.company, planned_begin_time=datetime(2026, 6, 18, 10, 0, 0, tzinfo=pytz.UTC), ) # Before 6 AM, technician should not see anything (empty queryset and False for can_view) self.assertFalse(can_view_intervention(self.tech_user, itv_today)) self.assertFalse(can_view_intervention(self.tech_user, itv_yesterday)) visible_qs = filter_viewable_interventions_for_user(self.tech_user) self.assertEqual(visible_qs.count(), 0) # 2. Test after 6 AM (7:30 AM UTC = 9:30 AM Brussels local time) mock_now.return_value = datetime(2026, 6, 17, 7, 30, 0, tzinfo=pytz.UTC) # After 6 AM, they should only be able to view today's intervention self.assertTrue(can_view_intervention(self.tech_user, itv_today)) self.assertFalse(can_view_intervention(self.tech_user, itv_yesterday)) self.assertFalse(can_view_intervention(self.tech_user, itv_tomorrow)) visible_qs = filter_viewable_interventions_for_user(self.tech_user) self.assertEqual(visible_qs.count(), 1) self.assertIn(itv_today, visible_qs) self.assertNotIn(itv_yesterday, visible_qs) self.assertNotIn(itv_tomorrow, visible_qs) @mock.patch('django.utils.timezone.now') def test_visibility_privileged_users_not_restricted(self, mock_now): from datetime import datetime import pytz from interventions.models import Intervention from interventions.permissions import can_view_intervention, filter_viewable_interventions_for_user from common.models import Role # Fix today to 2026-06-17 at 3:30 AM UTC (before start hour) mock_now.return_value = datetime(2026, 6, 17, 3, 30, 0, tzinfo=pytz.UTC) itv_yesterday = Intervention.objects.create( title='Yesterday Task', status='to_be_processed', contract=self.contract, thematic=self.thematic, assigned_provider=self.company, planned_begin_time=datetime(2026, 6, 16, 10, 0, 0, tzinfo=pytz.UTC), ) # If user gets role external_manager, they should see it despite being in the restricted team role_ext_mgr, _ = Role.objects.get_or_create(name='external_manager') self.tech_config.roles.add(role_ext_mgr) self.assertTrue(can_view_intervention(self.tech_user, itv_yesterday)) visible_qs = filter_viewable_interventions_for_user(self.tech_user) self.assertIn(itv_yesterday, visible_qs) class TeamAssignedOnlyVisibilityTests(TestCase): def setUp(self): from contracts.models import Company, CompanyTeam, CompanyMember from common.models import Role, UserConfig, Thematic, UserThematics, UserContractAccess self.User = get_user_model() self.tech_user = self.User.objects.create_user(username='tech-user-2', password='pwd') self.role_tech, _ = Role.objects.get_or_create(name='technician') self.tech_config = UserConfig.objects.create(user=self.tech_user, is_intern=False) self.tech_config.roles.add(self.role_tech) self.company = Company.objects.create(name='Test Company 2') # Restricted and Unrestricted Teams self.team_restricted = CompanyTeam.objects.create( name='Restricted Team 2', company=self.company, restrict_to_assigned_interventions=True ) self.team_unrestricted = CompanyTeam.objects.create( name='Unrestricted Team 2', company=self.company, restrict_to_assigned_interventions=False ) self.other_team = CompanyTeam.objects.create( name='Other Team 2', company=self.company, restrict_to_assigned_interventions=False ) self.member = CompanyMember.objects.create(user=self.tech_user, name='Technician Doe 2') self.thematic = Thematic.objects.create(code='roads2', name_fr='Voirie 2', name_nl='Wegen 2') UserThematics.objects.create( user_config=self.tech_config, thematic=self.thematic, can_view_interventions=True, can_edit_interventions=True, ) self.contract = Contract.objects.create( company=self.company, contract_number='C-TEST-2', start_date=timezone.now().date() - timedelta(days=10), end_date=timezone.now().date() + timedelta(days=10), is_active=True ) UserContractAccess.objects.create( user_config=self.tech_config, contract=self.contract, can_view_interventions=True, ) def test_visibility_restricted_team_only(self): from interventions.models import Intervention from interventions.permissions import can_view_intervention, filter_viewable_interventions_for_user # Member only belongs to restricted team self.member.teams.add(self.team_restricted) # 1. Assigned to team itv_team = Intervention.objects.create( title='Team Task', status='to_be_processed', contract=self.contract, thematic=self.thematic, assigned_provider=self.company, assigned_team=self.team_restricted, ) # 2. Assigned to member itv_member = Intervention.objects.create( title='Member Task', status='to_be_processed', contract=self.contract, thematic=self.thematic, assigned_provider=self.company, assigned_member=self.member, ) # 3. Unassigned company task itv_unassigned = Intervention.objects.create( title='Unassigned Task', status='to_be_processed', contract=self.contract, thematic=self.thematic, assigned_provider=self.company, ) # 4. Assigned to other team itv_other = Intervention.objects.create( title='Other Team Task', status='to_be_processed', contract=self.contract, thematic=self.thematic, assigned_provider=self.company, assigned_team=self.other_team, ) # Check individual visibility self.assertTrue(can_view_intervention(self.tech_user, itv_team)) self.assertTrue(can_view_intervention(self.tech_user, itv_member)) self.assertFalse(can_view_intervention(self.tech_user, itv_unassigned)) self.assertFalse(can_view_intervention(self.tech_user, itv_other)) # Check list visibility visible_qs = filter_viewable_interventions_for_user(self.tech_user) self.assertEqual(visible_qs.count(), 2) self.assertIn(itv_team, visible_qs) self.assertIn(itv_member, visible_qs) self.assertNotIn(itv_unassigned, visible_qs) self.assertNotIn(itv_other, visible_qs) def test_visibility_unrestricted_team(self): from interventions.models import Intervention from interventions.permissions import can_view_intervention, filter_viewable_interventions_for_user # Member only belongs to unrestricted team self.member.teams.add(self.team_unrestricted) # Unassigned company task itv_unassigned = Intervention.objects.create( title='Unassigned Task', status='to_be_processed', contract=self.contract, thematic=self.thematic, assigned_provider=self.company, ) self.assertTrue(can_view_intervention(self.tech_user, itv_unassigned)) visible_qs = filter_viewable_interventions_for_user(self.tech_user) self.assertIn(itv_unassigned, visible_qs) def test_visibility_mixed_teams(self): from interventions.models import Intervention from interventions.permissions import can_view_intervention, filter_viewable_interventions_for_user # Member belongs to both restricted and unrestricted teams of the same company self.member.teams.add(self.team_restricted, self.team_unrestricted) # Unassigned company task itv_unassigned = Intervention.objects.create( title='Unassigned Task', status='to_be_processed', contract=self.contract, thematic=self.thematic, assigned_provider=self.company, ) self.assertTrue(can_view_intervention(self.tech_user, itv_unassigned)) visible_qs = filter_viewable_interventions_for_user(self.tech_user) self.assertIn(itv_unassigned, visible_qs) def test_visibility_privileged_user_not_restricted(self): from interventions.models import Intervention from interventions.permissions import can_view_intervention, filter_viewable_interventions_for_user from common.models import Role # Member belongs only to restricted team self.member.teams.add(self.team_restricted) # Give the user a privileged role: operator role_operator, _ = Role.objects.get_or_create(name='operator') self.tech_config.roles.add(role_operator) # Unassigned company task itv_unassigned = Intervention.objects.create( title='Unassigned Task', status='to_be_processed', contract=self.contract, thematic=self.thematic, assigned_provider=self.company, ) self.assertTrue(can_view_intervention(self.tech_user, itv_unassigned)) visible_qs = filter_viewable_interventions_for_user(self.tech_user) self.assertIn(itv_unassigned, visible_qs) def test_is_restricted_team_context(self): from interventions.models import Intervention from django.urls import reverse # 1. User belongs only to restricted team self.member.teams.add(self.team_restricted) itv = Intervention.objects.create( title='Team Task', status='to_be_processed', contract=self.contract, thematic=self.thematic, assigned_provider=self.company, assigned_team=self.team_restricted, ) self.client.login(username='tech-user-2', password='pwd') url = reverse('mobile:intervention_detail_mobile', args=[itv.id]) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) self.assertTrue(resp.context['is_restricted_team']) # 2. User has an unrestricted team too self.member.teams.add(self.team_unrestricted) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) self.assertFalse(resp.context['is_restricted_team']) class CreatorVisibilityTests(TestCase): def setUp(self): from django.contrib.auth import get_user_model from common.models import Role, UserConfig, Thematic, UserThematics self.User = get_user_model() self.user1 = self.User.objects.create_user(username='creator-user-1', password='pwd') self.user2 = self.User.objects.create_user(username='other-user-2', password='pwd') self.role_tech, _ = Role.objects.get_or_create(name='technician') self.config1 = UserConfig.objects.create(user=self.user1, is_intern=False, limit_interventions_to_contracts=True) self.config1.roles.add(self.role_tech) self.config2 = UserConfig.objects.create(user=self.user2, is_intern=False, limit_interventions_to_contracts=True) self.config2.roles.add(self.role_tech) self.thematic = Thematic.objects.create(code='roads_creator', name_fr='Voirie Creator', name_nl='Wegen Creator') UserThematics.objects.create( user_config=self.config1, thematic=self.thematic, can_view_interventions=True, can_edit_interventions=True, ) UserThematics.objects.create( user_config=self.config2, thematic=self.thematic, can_view_interventions=True, can_edit_interventions=True, ) def test_creator_can_view_own_created_intervention(self): from interventions.models import Intervention from interventions.permissions import can_view_intervention, filter_viewable_interventions_for_user # Create intervention with no contract, created by user1 itv = Intervention.objects.create( title='Created by User 1', status='to_be_processed', contract=None, thematic=self.thematic, created_by=self.user1, ) # User 1 should see it (creator) self.assertTrue(can_view_intervention(self.user1, itv)) self.assertIn(itv, filter_viewable_interventions_for_user(self.user1)) # User 2 should NOT see it (not the creator, and no contract assigned) self.assertFalse(can_view_intervention(self.user2, itv)) self.assertNotIn(itv, filter_viewable_interventions_for_user(self.user2))