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)
|
||||
|
||||
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)
|
||||
|
||||
if company_member:
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
@ -804,6 +804,126 @@ class QuickProviderContractUpdateTests(TestCase):
|
|||
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:
|
||||
for key, value in saved_filters.items():
|
||||
if key == 'q':
|
||||
continue
|
||||
if isinstance(value, list):
|
||||
query_params.setlist(key, value)
|
||||
elif value is not None:
|
||||
|
|
@ -173,10 +175,7 @@ def interventions_index_mobile(request):
|
|||
from contracts.services_teams import sync_member_current_teams
|
||||
sync_member_current_teams(company_member_current)
|
||||
|
||||
# permissions
|
||||
interventions = filter_viewable_interventions_for_user(request.user, limit_to_default_thematic=True)
|
||||
|
||||
# Get user config and company member for maintain type tabs logic
|
||||
# Get user config and company member for permissions and maintain type tabs logic
|
||||
user_config = None
|
||||
company_member = None
|
||||
try:
|
||||
|
|
@ -187,10 +186,20 @@ def interventions_index_mobile(request):
|
|||
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()
|
||||
|
||||
# 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:
|
||||
filters_to_save = {}
|
||||
for key in request.GET:
|
||||
if key == 'q':
|
||||
continue
|
||||
values = request.GET.getlist(key)
|
||||
if key == 'status':
|
||||
filters_to_save[key] = values
|
||||
|
|
|
|||
Loading…
Reference in a new issue