fix(interventions,mobile): allow multi-thematic visibility for contractor teams and prevent persistent search query
This commit is contained in:
parent
de73de7328
commit
8b13236a26
3 changed files with 146 additions and 7 deletions
|
|
@ -773,6 +773,16 @@ def filter_viewable_interventions_for_user(user, limit_to_default_thematic=False
|
||||||
qs = Intervention.objects.filter(q)
|
qs = Intervention.objects.filter(q)
|
||||||
|
|
||||||
if limit_to_default_thematic and user_config.default_thematic:
|
if limit_to_default_thematic and user_config.default_thematic:
|
||||||
|
if company_member:
|
||||||
|
assigned_q = (
|
||||||
|
Q(assigned_member=company_member) |
|
||||||
|
Q(assigned_team__in=list(company_member.teams.all())) |
|
||||||
|
Q(contract__allow_team_members_edit=True, assigned_member__teams__in=list(company_member.teams.all())) |
|
||||||
|
Q(assigned_control_team__in=list(company_member.teams.all())) |
|
||||||
|
Q(assigned_controller=company_member)
|
||||||
|
)
|
||||||
|
qs = qs.filter(Q(thematic=user_config.default_thematic) | assigned_q)
|
||||||
|
else:
|
||||||
qs = qs.filter(thematic=user_config.default_thematic)
|
qs = qs.filter(thematic=user_config.default_thematic)
|
||||||
|
|
||||||
if company_member:
|
if company_member:
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from django.contrib import messages
|
||||||
from django.contrib.messages import get_messages
|
from django.contrib.messages import get_messages
|
||||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
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 contracts.models import Company, CompanyTeam, CompanyMember, Contract, ContractPost, ContractOrder, ContractOrderQuote, ContractOrderQuoteItem
|
||||||
from assets.models import AssetCategory
|
from assets.models import AssetCategory
|
||||||
from interventions.models import (
|
from interventions.models import (
|
||||||
|
|
@ -804,6 +804,126 @@ class QuickProviderContractUpdateTests(TestCase):
|
||||||
self.assertTrue(can_edit_intervention(self.user, self.intervention))
|
self.assertTrue(can_edit_intervention(self.user, self.intervention))
|
||||||
|
|
||||||
|
|
||||||
|
class TeamMultiThematicVisibilityTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.User = get_user_model()
|
||||||
|
self.tech_role, _ = Role.objects.get_or_create(name='technician')
|
||||||
|
|
||||||
|
self.thematic_structures = Thematic.objects.create(code='structures', name_fr="Ouvrages d'art", name_nl='Kunstwerken')
|
||||||
|
self.thematic_its = Thematic.objects.create(code='its', name_fr='ITS', name_nl='ITS')
|
||||||
|
|
||||||
|
self.company = Company.objects.create(name='Eiffage')
|
||||||
|
self.team = CompanyTeam.objects.create(name='Equipe de jour Tunnels', company=self.company)
|
||||||
|
self.team.thematics.add(self.thematic_structures, self.thematic_its)
|
||||||
|
|
||||||
|
self.contract = Contract.objects.create(
|
||||||
|
contract_number='CNT-TUNNELS',
|
||||||
|
company=self.company,
|
||||||
|
start_date=timezone.now().date() - timedelta(days=10),
|
||||||
|
end_date=timezone.now().date() + timedelta(days=365),
|
||||||
|
is_active=True,
|
||||||
|
)
|
||||||
|
self.contract.thematics.add(self.thematic_structures, self.thematic_its)
|
||||||
|
|
||||||
|
self.user = self.User.objects.create_user(username='tech-zidan', password='pwd')
|
||||||
|
self.user_config = UserConfig.objects.create(
|
||||||
|
user=self.user,
|
||||||
|
is_intern=False,
|
||||||
|
default_thematic=self.thematic_structures,
|
||||||
|
limit_interventions_to_contracts=True,
|
||||||
|
)
|
||||||
|
self.user_config.roles.add(self.tech_role)
|
||||||
|
|
||||||
|
UserThematics.objects.create(
|
||||||
|
user_config=self.user_config,
|
||||||
|
thematic=self.thematic_structures,
|
||||||
|
can_view_interventions=True,
|
||||||
|
can_edit_interventions=True,
|
||||||
|
)
|
||||||
|
UserThematics.objects.create(
|
||||||
|
user_config=self.user_config,
|
||||||
|
thematic=self.thematic_its,
|
||||||
|
can_view_interventions=True,
|
||||||
|
can_edit_interventions=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
UserContractAccess.objects.create(
|
||||||
|
user_config=self.user_config,
|
||||||
|
contract=self.contract,
|
||||||
|
can_view_interventions=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.member = CompanyMember.objects.create(user=self.user, name='Zidane')
|
||||||
|
self.member.teams.add(self.team)
|
||||||
|
|
||||||
|
# Intervention 1: thematic ITS, assigned to the technician's team
|
||||||
|
self.itv_its = Intervention.objects.create(
|
||||||
|
title='Camera defect',
|
||||||
|
thematic=self.thematic_its,
|
||||||
|
status='to_be_processed',
|
||||||
|
assigned_provider=self.company,
|
||||||
|
assigned_team=self.team,
|
||||||
|
contract=self.contract,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Intervention 2: thematic structures, assigned to the technician's team
|
||||||
|
self.itv_struct = Intervention.objects.create(
|
||||||
|
title='Lighting defect',
|
||||||
|
thematic=self.thematic_structures,
|
||||||
|
status='to_be_processed',
|
||||||
|
assigned_provider=self.company,
|
||||||
|
assigned_team=self.team,
|
||||||
|
contract=self.contract,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_filter_viewable_keeps_team_assigned_intervention_even_with_limit_to_default_thematic(self):
|
||||||
|
from interventions.permissions import filter_viewable_interventions_for_user
|
||||||
|
|
||||||
|
# Even if limit_to_default_thematic is True, the ITS intervention assigned to their team must remain visible
|
||||||
|
qs_limited = filter_viewable_interventions_for_user(self.user, limit_to_default_thematic=True)
|
||||||
|
self.assertTrue(qs_limited.filter(id=self.itv_its.id).exists())
|
||||||
|
self.assertTrue(qs_limited.filter(id=self.itv_struct.id).exists())
|
||||||
|
|
||||||
|
def test_filter_viewable_without_limit_shows_all_contract_thematics(self):
|
||||||
|
from interventions.permissions import filter_viewable_interventions_for_user
|
||||||
|
|
||||||
|
qs = filter_viewable_interventions_for_user(self.user, limit_to_default_thematic=False)
|
||||||
|
self.assertTrue(qs.filter(id=self.itv_its.id).exists())
|
||||||
|
self.assertTrue(qs.filter(id=self.itv_struct.id).exists())
|
||||||
|
|
||||||
|
def test_mobile_index_does_not_persist_search_query_q(self):
|
||||||
|
self.client.login(username='tech-zidan', password='pwd')
|
||||||
|
url = reverse('mobile:interventions_index_mobile')
|
||||||
|
|
||||||
|
# Visit mobile index with a search query
|
||||||
|
response = self.client.get(f"{url}?q=Camera&status=to_be_processed")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
self.user_config.refresh_from_db()
|
||||||
|
saved = self.user_config.mobile_intervention_list_filters
|
||||||
|
self.assertNotIn('q', saved)
|
||||||
|
self.assertEqual(saved.get('status'), ['to_be_processed'])
|
||||||
|
|
||||||
|
def test_mobile_index_does_not_restore_stale_search_query_q(self):
|
||||||
|
# Simulate an old search query trapped in user_config
|
||||||
|
self.user_config.mobile_intervention_list_filters = {
|
||||||
|
'q': 'stale_search',
|
||||||
|
'status': ['to_be_processed'],
|
||||||
|
}
|
||||||
|
self.user_config.save()
|
||||||
|
|
||||||
|
self.client.login(username='tech-zidan', password='pwd')
|
||||||
|
url = reverse('mobile:interventions_index_mobile')
|
||||||
|
|
||||||
|
# Access mobile index without GET params -> redirects with saved filters
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
# Verify redirect target does NOT include q=stale_search
|
||||||
|
self.assertNotIn('q=', response.url)
|
||||||
|
self.assertIn('status=to_be_processed', response.url)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,8 @@ def interventions_index_mobile(request):
|
||||||
|
|
||||||
if saved_filters:
|
if saved_filters:
|
||||||
for key, value in saved_filters.items():
|
for key, value in saved_filters.items():
|
||||||
|
if key == 'q':
|
||||||
|
continue
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
query_params.setlist(key, value)
|
query_params.setlist(key, value)
|
||||||
elif value is not None:
|
elif value is not None:
|
||||||
|
|
@ -173,10 +175,7 @@ def interventions_index_mobile(request):
|
||||||
from contracts.services_teams import sync_member_current_teams
|
from contracts.services_teams import sync_member_current_teams
|
||||||
sync_member_current_teams(company_member_current)
|
sync_member_current_teams(company_member_current)
|
||||||
|
|
||||||
# permissions
|
# Get user config and company member for permissions and maintain type tabs logic
|
||||||
interventions = filter_viewable_interventions_for_user(request.user, limit_to_default_thematic=True)
|
|
||||||
|
|
||||||
# Get user config and company member for maintain type tabs logic
|
|
||||||
user_config = None
|
user_config = None
|
||||||
company_member = None
|
company_member = None
|
||||||
try:
|
try:
|
||||||
|
|
@ -187,10 +186,20 @@ def interventions_index_mobile(request):
|
||||||
if user_config and not user_config.is_intern:
|
if user_config and not user_config.is_intern:
|
||||||
company_member = CompanyMember.objects.prefetch_related('teams__company', 'visible_maintain_types').filter(user=request.user).first()
|
company_member = CompanyMember.objects.prefetch_related('teams__company', 'visible_maintain_types').filter(user=request.user).first()
|
||||||
|
|
||||||
# Save the current filters to user config
|
# permissions
|
||||||
|
# Les utilisateurs internes avec une thématique par défaut sont restreints à celle-ci.
|
||||||
|
# Pour les prestataires externes (membres d'équipes dont le contrat/métier couvre souvent
|
||||||
|
# plusieurs thématiques comme Ouvrages d'art + ITS + Centres de contrôle pour les Tunnels),
|
||||||
|
# ne pas restreindre à une seule thématique par défaut afin qu'ils voient toutes les interventions de leur contrat/équipes.
|
||||||
|
limit_thematic = bool(user_config and user_config.is_intern)
|
||||||
|
interventions = filter_viewable_interventions_for_user(request.user, limit_to_default_thematic=limit_thematic)
|
||||||
|
|
||||||
|
# Save the current filters to user config (excluding temporary search query 'q')
|
||||||
if user_config and request.GET:
|
if user_config and request.GET:
|
||||||
filters_to_save = {}
|
filters_to_save = {}
|
||||||
for key in request.GET:
|
for key in request.GET:
|
||||||
|
if key == 'q':
|
||||||
|
continue
|
||||||
values = request.GET.getlist(key)
|
values = request.GET.getlist(key)
|
||||||
if key == 'status':
|
if key == 'status':
|
||||||
filters_to_save[key] = values
|
filters_to_save[key] = values
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue