751 lines
31 KiB
Python
751 lines
31 KiB
Python
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_visibility_no_teams_assigned(self):
|
|
from interventions.models import Intervention
|
|
from interventions.permissions import can_view_intervention, filter_viewable_interventions_for_user
|
|
|
|
# Member belongs to NO team (self.member.teams is empty)
|
|
self.member.teams.clear()
|
|
|
|
# 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: only directly assigned task is visible
|
|
self.assertFalse(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: only directly assigned task is visible
|
|
visible_qs = filter_viewable_interventions_for_user(self.tech_user)
|
|
self.assertEqual(visible_qs.count(), 1)
|
|
self.assertIn(itv_member, visible_qs)
|
|
self.assertNotIn(itv_team, visible_qs)
|
|
self.assertNotIn(itv_unassigned, visible_qs)
|
|
self.assertNotIn(itv_other, 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))
|
|
|
|
|
|
class DefaultContractTests(TestCase):
|
|
def setUp(self):
|
|
from datetime import date
|
|
from django.contrib.auth import get_user_model
|
|
from common.models import UserConfig, UserContractAccess
|
|
from contracts.models import Contract, Company
|
|
|
|
self.User = get_user_model()
|
|
self.user = self.User.objects.create_user(username='default-contract-user', password='pwd')
|
|
self.company = Company.objects.create(name='Test Company')
|
|
self.contract1 = Contract.objects.create(contract_number='CNT-001', company=self.company, start_date=date(2025, 1, 1), end_date=date(2030, 1, 1))
|
|
self.contract2 = Contract.objects.create(contract_number='CNT-002', company=self.company, start_date=date(2025, 1, 1), end_date=date(2030, 1, 1))
|
|
|
|
self.config = UserConfig.objects.create(
|
|
user=self.user,
|
|
is_intern=False,
|
|
limit_interventions_to_contracts=True,
|
|
)
|
|
|
|
def test_get_default_contract_explicit(self):
|
|
self.config.default_contract = self.contract1
|
|
self.config.save()
|
|
self.assertEqual(self.config.get_default_contract(), self.contract1)
|
|
|
|
def test_get_default_contract_single_contract_fallback(self):
|
|
from common.models import UserContractAccess
|
|
UserContractAccess.objects.create(user_config=self.config, contract=self.contract2)
|
|
# With only 1 contract in UserContractAccess, get_default_contract returns contract2
|
|
self.assertEqual(self.config.get_default_contract(), self.contract2)
|
|
|
|
# With 2 contracts, get_default_contract returns None if default_contract not set
|
|
UserContractAccess.objects.create(user_config=self.config, contract=self.contract1)
|
|
self.assertIsNone(self.config.get_default_contract())
|
|
|
|
def test_observation_conversion_assigns_default_contract(self):
|
|
from observations.models import Observation
|
|
from interventions.models import Symptom
|
|
from common.models import Thematic, UserContractAccess
|
|
|
|
self.config.default_contract = self.contract1
|
|
self.config.save()
|
|
UserContractAccess.objects.create(user_config=self.config, contract=self.contract1)
|
|
|
|
thematic = Thematic.objects.create(code='roads_def', name_fr='Voirie Def')
|
|
symptom = Symptom.objects.create(name_fr='Symptom test', thematic=thematic)
|
|
|
|
obs = Observation.objects.create(
|
|
symptom=symptom,
|
|
thematic=thematic,
|
|
created_by=self.user,
|
|
latitude=50.85,
|
|
longitude=4.35,
|
|
address='123 Test Street'
|
|
)
|
|
obs.create_auto_intervention()
|
|
itv = obs.intervention
|
|
self.assertIsNotNone(itv)
|
|
self.assertEqual(itv.contract, self.contract1)
|
|
self.assertEqual(itv.assigned_provider, self.company)
|
|
|
|
|
|
class GetContractsByProviderTests(TestCase):
|
|
def setUp(self):
|
|
from datetime import date
|
|
from django.contrib.auth import get_user_model
|
|
from common.models import UserConfig, UserContractAccess, Role
|
|
from contracts.models import Contract, Company
|
|
|
|
self.User = get_user_model()
|
|
self.manager_role, _ = Role.objects.get_or_create(name='manager')
|
|
|
|
self.company = Company.objects.create(name="Provider A")
|
|
self.contract1 = Contract.objects.create(
|
|
contract_number="C-001",
|
|
description="Short desc",
|
|
company=self.company,
|
|
start_date=date(2025, 1, 1),
|
|
end_date=date(2030, 1, 1)
|
|
)
|
|
self.contract2 = Contract.objects.create(
|
|
contract_number="C-002",
|
|
description="Another desc",
|
|
company=self.company,
|
|
start_date=date(2025, 1, 1),
|
|
end_date=date(2030, 1, 1)
|
|
)
|
|
|
|
# User limited to contracts
|
|
self.limited_user = self.User.objects.create_user(username='limited-user', password='pwd')
|
|
self.limited_config = UserConfig.objects.create(
|
|
user=self.limited_user,
|
|
is_intern=True,
|
|
limit_interventions_to_contracts=True,
|
|
)
|
|
self.limited_config.roles.add(self.manager_role)
|
|
# Give access only to contract1
|
|
UserContractAccess.objects.create(user_config=self.limited_config, contract=self.contract1, can_view_interventions=True)
|
|
|
|
# Unrestricted internal user
|
|
self.unlimited_user = self.User.objects.create_user(username='unlimited-user', password='pwd')
|
|
self.unlimited_config = UserConfig.objects.create(
|
|
user=self.unlimited_user,
|
|
is_intern=True,
|
|
limit_interventions_to_contracts=False,
|
|
)
|
|
self.unlimited_config.roles.add(self.manager_role)
|
|
|
|
def test_limited_user_only_gets_single_accessible_contract(self):
|
|
self.client.login(username='limited-user', password='pwd')
|
|
url = reverse('interventions:get_contracts_by_provider')
|
|
response = self.client.get(f"{url}?provider_id={self.company.id}")
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
self.assertEqual(len(data), 1)
|
|
self.assertEqual(data[0]['id'], self.contract1.id)
|
|
self.assertEqual(data[0]['contract_number'], "C-001")
|
|
self.assertEqual(data[0]['description'], "Short desc")
|
|
self.assertEqual(data[0]['label'], "C-001 (Short desc)")
|
|
|
|
def test_unlimited_user_gets_all_contracts(self):
|
|
self.client.login(username='unlimited-user', password='pwd')
|
|
url = reverse('interventions:get_contracts_by_provider')
|
|
response = self.client.get(f"{url}?provider_id={self.company.id}")
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
self.assertEqual(len(data), 2)
|
|
contract_numbers = [c['contract_number'] for c in data]
|
|
self.assertIn("C-001", contract_numbers)
|
|
self.assertIn("C-002", contract_numbers)
|
|
|
|
|
|
class QuickProviderContractUpdateTests(TestCase):
|
|
def setUp(self):
|
|
from common.models import UserContractAccess, UserContractStatusPermission
|
|
self.User = get_user_model()
|
|
self.manager_role, _ = Role.objects.get_or_create(name='manager')
|
|
self.thematic = Thematic.objects.create(name_fr='Voirie Test', allow_planning_without_contract=True)
|
|
|
|
self.company1 = Company.objects.create(name='Company 1')
|
|
self.company2 = Company.objects.create(name='Company 2')
|
|
|
|
self.contract1 = Contract.objects.create(
|
|
contract_number="C-001",
|
|
company=self.company1,
|
|
start_date=timezone.now().date(),
|
|
end_date=timezone.now().date() + timedelta(days=365)
|
|
)
|
|
self.contract1.thematics.add(self.thematic)
|
|
|
|
self.contract2 = Contract.objects.create(
|
|
contract_number="C-002",
|
|
company=self.company2,
|
|
start_date=timezone.now().date(),
|
|
end_date=timezone.now().date() + timedelta(days=365)
|
|
)
|
|
self.contract2.thematics.add(self.thematic)
|
|
|
|
# User with limited contract access
|
|
self.user = self.User.objects.create_user(username='contract-manager', password='pwd')
|
|
self.user_config = UserConfig.objects.create(
|
|
user=self.user,
|
|
is_intern=True,
|
|
limit_interventions_to_contracts=True,
|
|
)
|
|
self.user_config.roles.add(self.manager_role)
|
|
|
|
# User thematic without explicit UserThematicStatusPermission
|
|
UserThematics.objects.create(
|
|
user_config=self.user_config,
|
|
thematic=self.thematic,
|
|
can_view_interventions=True,
|
|
can_edit_interventions=True,
|
|
)
|
|
|
|
# Access and edit permissions on both contracts for to_be_planned status
|
|
uca1 = UserContractAccess.objects.create(user_config=self.user_config, contract=self.contract1, can_view_interventions=True)
|
|
UserContractStatusPermission.objects.create(user_contract=uca1, status='to_be_planned', can_edit=True, can_view=True)
|
|
|
|
uca2 = UserContractAccess.objects.create(user_config=self.user_config, contract=self.contract2, can_view_interventions=True)
|
|
UserContractStatusPermission.objects.create(user_contract=uca2, status='to_be_planned', can_edit=True, can_view=True)
|
|
|
|
self.intervention = Intervention.objects.create(
|
|
title='Test Quick Edit Intervention',
|
|
thematic=self.thematic,
|
|
status='to_be_planned',
|
|
assigned_provider=self.company1,
|
|
contract=self.contract1,
|
|
)
|
|
|
|
def test_batch_update_provider_and_contract(self):
|
|
from interventions.permissions import can_edit_intervention
|
|
self.assertTrue(can_edit_intervention(self.user, self.intervention))
|
|
|
|
self.client.login(username='contract-manager', password='pwd')
|
|
url = reverse('interventions:update_intervention', args=[self.intervention.id])
|
|
payload = {
|
|
'assigned_provider': str(self.company2.id),
|
|
'contract': str(self.contract2.id),
|
|
}
|
|
response = self.client.post(url, data=json.dumps(payload), content_type='application/json')
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
self.assertTrue(data['success'])
|
|
|
|
self.intervention.refresh_from_db()
|
|
self.assertEqual(self.intervention.assigned_provider, self.company2)
|
|
self.assertEqual(self.intervention.contract, self.contract2)
|
|
|
|
def test_can_edit_intervention_when_contract_is_none(self):
|
|
from interventions.permissions import can_edit_intervention
|
|
self.intervention.contract = None
|
|
self.intervention.assigned_provider = self.company2
|
|
self.intervention.save()
|
|
|
|
# User should still be allowed to edit because they have edit rights on contracts of this thematic/provider
|
|
self.assertTrue(can_edit_intervention(self.user, self.intervention))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|