feat: implement preventive intervention templates with grouping and associated forms
This commit is contained in:
parent
7b519f16bd
commit
0521e2624e
7 changed files with 181 additions and 8 deletions
|
|
@ -189,6 +189,9 @@ def contracts_orders_detail(request, order_id):
|
|||
# Déterminer les droits de création d'interventions par type
|
||||
user_roles = set(user_config.roles.values_list('name', flat=True)) if user_config else set()
|
||||
can_create_preventive = bool(user_roles & {'admin', 'manager', 'controller'})
|
||||
if not can_create_preventive and 'external_manager' in user_roles:
|
||||
from interventions.permissions import is_external_manager_with_contract_creation_rights
|
||||
can_create_preventive = is_external_manager_with_contract_creation_rights(request.user)
|
||||
can_create_ameliorative = bool(user_roles & {'admin', 'manager', 'controller', 'external_manager'})
|
||||
|
||||
context = {
|
||||
|
|
|
|||
|
|
@ -173,6 +173,9 @@ def index(request):
|
|||
# Détermine les droits de création d'interventions par type
|
||||
user_roles = set(user_config.roles.values_list('name', flat=True))
|
||||
can_create_preventive = bool(user_roles & {'admin', 'manager', 'controller', 'top_manager'})
|
||||
if not can_create_preventive and 'external_manager' in user_roles:
|
||||
from interventions.permissions import is_external_manager_with_contract_creation_rights
|
||||
can_create_preventive = is_external_manager_with_contract_creation_rights(request.user)
|
||||
can_create_ameliorative = bool(user_roles & {'admin', 'manager', 'controller', 'external_manager', 'operator', 'top_manager'})
|
||||
is_operator = 'operator' in user_roles
|
||||
is_admin = 'admin' in user_roles
|
||||
|
|
|
|||
|
|
@ -865,8 +865,9 @@ def intervention_add_permission_required(view_func):
|
|||
def intervention_add_preventive_permission_required(view_func):
|
||||
"""
|
||||
Décorateur pour les vues de création d'intervention préventive.
|
||||
Seuls les admin, manager et controller peuvent créer des interventions préventives.
|
||||
Les external_managers et operators ne peuvent pas en créer.
|
||||
Seuls les admin, manager, controller, top_manager et les external_managers
|
||||
avec droit de création d'interventions sur au moins un contrat peuvent créer des
|
||||
interventions préventives.
|
||||
"""
|
||||
@wraps(view_func)
|
||||
def _wrapped_view(request, thematic_code=None, *args, **kwargs):
|
||||
|
|
@ -878,11 +879,14 @@ def intervention_add_preventive_permission_required(view_func):
|
|||
if not can_add_intervention(request.user, thematic):
|
||||
raise PermissionDenied(_("Vous n'avez pas la permission d'ajouter une intervention pour cette thématique."))
|
||||
|
||||
# Seuls admin, manager, controller peuvent créer des préventives
|
||||
# Seuls admin, manager, controller, top_manager, et external_manager avec droits
|
||||
try:
|
||||
user_config = UserConfig.objects.get(user=request.user)
|
||||
user_roles = set(user_config.roles.values_list('name', flat=True))
|
||||
if not (user_roles & {'admin', 'manager', 'controller', 'top_manager'}):
|
||||
is_allowed = bool(user_roles & {'admin', 'manager', 'controller', 'top_manager'})
|
||||
if not is_allowed and 'external_manager' in user_roles:
|
||||
is_allowed = is_external_manager_with_contract_creation_rights(request.user)
|
||||
if not is_allowed:
|
||||
raise PermissionDenied(_("Vous n'avez pas la permission de créer des interventions préventives."))
|
||||
except UserConfig.DoesNotExist:
|
||||
raise PermissionDenied(_("Vous n'avez pas la permission de créer des interventions préventives."))
|
||||
|
|
@ -1478,8 +1482,8 @@ def should_show_created_by_provider_badge(user, intervention):
|
|||
except UserConfig.DoesNotExist:
|
||||
return False
|
||||
|
||||
# Visible pour admin, manager et controller
|
||||
return user_config.roles.filter(name__in=['admin', 'manager', 'controller']).exists()
|
||||
# Visible pour admin, manager, controller et top_manager
|
||||
return user_config.roles.filter(name__in=['admin', 'manager', 'controller', 'top_manager']).exists()
|
||||
|
||||
|
||||
def is_external_manager_with_contract_creation_rights(user, contract=None):
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
@ -449,5 +449,160 @@ class AssetOperationCleanupTests(TestCase):
|
|||
self.assertEqual(remaining_ops.count(), 2)
|
||||
|
||||
|
||||
class ExternalManagerPreventivePermissionsTests(TestCase):
|
||||
def setUp(self):
|
||||
User = get_user_model()
|
||||
self.role_ext_manager, _ = Role.objects.get_or_create(name='external_manager')
|
||||
self.role_manager, _ = Role.objects.get_or_create(name='manager')
|
||||
|
||||
self.thematic = Thematic.objects.create(code='test_thema', name_fr='Test', name_nl='Test')
|
||||
|
||||
self.provider = Company.objects.create(name="Test Provider")
|
||||
from datetime import date, timedelta
|
||||
today = date.today()
|
||||
self.contract_with_rights = Contract.objects.create(
|
||||
contract_number="C-001",
|
||||
company=self.provider,
|
||||
is_active=True,
|
||||
start_date=today - timedelta(days=30),
|
||||
end_date=today + timedelta(days=365)
|
||||
)
|
||||
self.contract_with_rights.thematics.add(self.thematic)
|
||||
|
||||
self.contract_without_rights = Contract.objects.create(
|
||||
contract_number="C-002",
|
||||
company=self.provider,
|
||||
is_active=True,
|
||||
start_date=today - timedelta(days=30),
|
||||
end_date=today + timedelta(days=365)
|
||||
)
|
||||
self.contract_without_rights.thematics.add(self.thematic)
|
||||
|
||||
# 1. User with creation rights
|
||||
self.user_with_rights = User.objects.create_user(username='ext_with_rights', password='pwd')
|
||||
self.uc_with_rights = UserConfig.objects.create(user=self.user_with_rights, is_intern=False)
|
||||
self.uc_with_rights.roles.add(self.role_ext_manager)
|
||||
UserThematics.objects.create(
|
||||
user_config=self.uc_with_rights,
|
||||
thematic=self.thematic,
|
||||
can_view_assets=True,
|
||||
can_edit_interventions=True,
|
||||
)
|
||||
UserContractAccess.objects.create(
|
||||
user_config=self.uc_with_rights,
|
||||
contract=self.contract_with_rights,
|
||||
can_create_interventions=True
|
||||
)
|
||||
|
||||
# 2. User without creation rights
|
||||
self.user_without_rights = User.objects.create_user(username='ext_without_rights', password='pwd')
|
||||
self.uc_without_rights = UserConfig.objects.create(user=self.user_without_rights, is_intern=False)
|
||||
self.uc_without_rights.roles.add(self.role_ext_manager)
|
||||
UserThematics.objects.create(
|
||||
user_config=self.uc_without_rights,
|
||||
thematic=self.thematic,
|
||||
can_view_assets=True,
|
||||
can_edit_interventions=True,
|
||||
)
|
||||
UserContractAccess.objects.create(
|
||||
user_config=self.uc_without_rights,
|
||||
contract=self.contract_without_rights,
|
||||
can_create_interventions=False
|
||||
)
|
||||
|
||||
# 3. An internal manager to view the detail page and verify the badge
|
||||
self.internal_manager = User.objects.create_user(username='int_manager', password='pwd')
|
||||
self.uc_int = UserConfig.objects.create(user=self.internal_manager, is_intern=True)
|
||||
self.uc_int.roles.add(self.role_manager)
|
||||
|
||||
def test_access_to_add_preventive_view(self):
|
||||
# User with rights should access
|
||||
self.client.login(username='ext_with_rights', password='pwd')
|
||||
url = reverse('interventions:intervention_add_preventive', kwargs={'thematic_code': 'test_thema'})
|
||||
|
||||
# We need to set up a mock draft in the session
|
||||
session = self.client.session
|
||||
session['intervention_drafts'] = {
|
||||
'draft-1': {
|
||||
'locations': [],
|
||||
'assets': [],
|
||||
'form_data': {}
|
||||
}
|
||||
}
|
||||
session.save()
|
||||
|
||||
response = self.client.get(f"{url}?draft_id=draft-1")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# User without rights should be Forbidden (403)
|
||||
self.client.login(username='ext_without_rights', password='pwd')
|
||||
response = self.client.get(f"{url}?draft_id=draft-1")
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_contracts_filtered_in_form(self):
|
||||
self.client.login(username='ext_with_rights', password='pwd')
|
||||
url = reverse('interventions:intervention_add_preventive', kwargs={'thematic_code': 'test_thema'})
|
||||
|
||||
session = self.client.session
|
||||
session['intervention_drafts'] = {
|
||||
'draft-1': {
|
||||
'locations': [],
|
||||
'assets': [],
|
||||
'form_data': {}
|
||||
}
|
||||
}
|
||||
session.save()
|
||||
|
||||
response = self.client.get(f"{url}?draft_id=draft-1")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Verify that contract_with_rights is in form's queryset, but contract_without_rights is not
|
||||
contracts_queryset = response.context['form'].fields['contract'].queryset
|
||||
self.assertIn(self.contract_with_rights, contracts_queryset)
|
||||
self.assertNotIn(self.contract_without_rights, contracts_queryset)
|
||||
|
||||
def test_preventive_intervention_created_by_provider(self):
|
||||
self.client.login(username='ext_with_rights', password='pwd')
|
||||
url = reverse('interventions:intervention_add_preventive', kwargs={'thematic_code': 'test_thema'})
|
||||
|
||||
session = self.client.session
|
||||
session['intervention_drafts'] = {
|
||||
'draft-1': {
|
||||
'locations': [],
|
||||
'assets': [],
|
||||
'form_data': {}
|
||||
}
|
||||
}
|
||||
session.save()
|
||||
|
||||
# Submit creation form
|
||||
post_data = {
|
||||
'title': 'Test Preventive Creation',
|
||||
'status': 'in_preparation',
|
||||
'type': 'care',
|
||||
'contract': self.contract_with_rights.id,
|
||||
'expected_begin_time': '2026-08-13T12:00',
|
||||
'expected_end_time': '2026-08-13T13:00',
|
||||
'recurrence_count': 0,
|
||||
'type_of_selected_elements': 'locations',
|
||||
'intervention_creation_mode': 'single_for_all',
|
||||
'draft_id': 'draft-1',
|
||||
}
|
||||
response = self.client.post(url, post_data)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
# Check created intervention
|
||||
intervention = Intervention.objects.filter(title='Test Preventive Creation').first()
|
||||
self.assertIsNotNone(intervention)
|
||||
self.assertEqual(intervention.created_by, self.user_with_rights)
|
||||
self.assertTrue(intervention.created_by_provider)
|
||||
self.assertEqual(intervention.maintain_type, 'preventive')
|
||||
|
||||
# Check that the badge is shown for internal manager
|
||||
from interventions.permissions import should_show_created_by_provider_badge
|
||||
self.assertTrue(should_show_created_by_provider_badge(self.internal_manager, intervention))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -540,6 +540,9 @@ class PreventiveInterventionForm(forms.Form):
|
|||
# 2. Contrats associés à au moins une thématique que l'utilisateur peut éditer
|
||||
# OU contrats sans thématiques (pour la rétrocompatibilité)
|
||||
base_contracts = user_config.contracts.filter(is_active=True)
|
||||
user_roles = set(user_config.roles.values_list('name', flat=True))
|
||||
if 'external_manager' in user_roles and not (user_roles & {'admin', 'manager', 'controller', 'top_manager'}):
|
||||
base_contracts = base_contracts.filter(usercontractaccess__can_create_interventions=True)
|
||||
|
||||
# Si une thématique spécifique est fournie, filtrer aussi par cette thématique
|
||||
if thematic:
|
||||
|
|
|
|||
|
|
@ -220,8 +220,9 @@ def index(request):
|
|||
# - Améliorative : admin, manager, controller, external_manager, operator
|
||||
user_roles = set(user_config.roles.values_list('name', flat=True))
|
||||
|
||||
# Préventive : uniquement admin, manager, controller
|
||||
can_create_preventive = bool(user_roles & {'admin', 'manager', 'controller'})
|
||||
if not can_create_preventive and 'external_manager' in user_roles:
|
||||
can_create_preventive = is_external_manager_with_contract_creation_rights(request.user)
|
||||
|
||||
# Améliorative : admin, manager, controller, external_manager, operator
|
||||
can_create_ameliorative = bool(user_roles & {'admin', 'manager', 'controller', 'external_manager', 'operator'})
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
from interventions.views.helpers import *
|
||||
|
||||
@intervention_add_preventive_permission_required
|
||||
def preventive_intervention_draft(request, thematic_code):
|
||||
if request.method == 'POST':
|
||||
draft = InterventionDraftManager(request.session)
|
||||
|
|
@ -147,6 +148,7 @@ def preventive_intervention_draft(request, thematic_code):
|
|||
)
|
||||
|
||||
|
||||
@intervention_add_preventive_permission_required
|
||||
def preventive_link_assets(request, thematic_code):
|
||||
draft_id = request.POST.get("preventive_draft_id")
|
||||
drafts = InterventionDraftManager(request.session)
|
||||
|
|
@ -174,6 +176,7 @@ def preventive_link_assets(request, thematic_code):
|
|||
return redirect(f"{reverse('interventions:intervention_add_preventive', kwargs={'thematic_code': thematic_code})}?draft_id={draft_id}")
|
||||
|
||||
|
||||
@intervention_add_preventive_permission_required
|
||||
def preventive_remove_element(request, thematic_code):
|
||||
"""Supprime un élément (location ou asset) de la sélection du draft"""
|
||||
draft_id = request.POST.get("draft_id")
|
||||
|
|
@ -206,6 +209,7 @@ def preventive_remove_element(request, thematic_code):
|
|||
}, status=400)
|
||||
|
||||
|
||||
@intervention_add_preventive_permission_required
|
||||
def intervention_add_preventive(request, thematic_code):
|
||||
thematic = Thematic.objects.filter(code=thematic_code).first()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue