feat: update intervention drag-and-drop permissions to include top_manager and add assigned status to editable states
This commit is contained in:
parent
122086e2ab
commit
aa6c0d0603
4 changed files with 265 additions and 27 deletions
|
|
@ -3,7 +3,7 @@
|
||||||
const STATUS_COLORS = INTERVENTION_STATUS_COLORS;
|
const STATUS_COLORS = INTERVENTION_STATUS_COLORS;
|
||||||
|
|
||||||
const DEFAULT_STATUS_COLOR = '#888888';
|
const DEFAULT_STATUS_COLOR = '#888888';
|
||||||
const EDITABLE_STATUSES = ['in_preparation', 'to_be_approved', 'to_be_planned', 'to_be_processed'];
|
const EDITABLE_STATUSES = ['in_preparation', 'to_be_approved', 'to_be_planned', 'to_be_processed', 'assigned'];
|
||||||
const DAY_MS = 24 * 60 * 60 * 1000;
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
||||||
const HOUR_MS = 60 * 60 * 1000;
|
const HOUR_MS = 60 * 60 * 1000;
|
||||||
const MIN_BAR_DURATION_MS = 12 * 60 * 60 * 1000; // Largeur visuelle minimale des barres
|
const MIN_BAR_DURATION_MS = 12 * 60 * 60 * 1000; // Largeur visuelle minimale des barres
|
||||||
|
|
@ -531,7 +531,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
|
||||||
let isEditable = canMoveTimelineItems && EDITABLE_STATUSES.includes(itv.status);
|
let isEditable = canMoveTimelineItems && EDITABLE_STATUSES.includes(itv.status);
|
||||||
if (isEditable && isManager) {
|
if (isEditable && isManager) {
|
||||||
const managerAllowedStatuses = ['in_preparation', 'to_be_approved', 'to_be_planned', 'to_be_processed'];
|
const managerAllowedStatuses = ['in_preparation', 'to_be_approved', 'to_be_planned', 'to_be_processed', 'assigned'];
|
||||||
isEditable = managerAllowedStatuses.includes(itv.status);
|
isEditable = managerAllowedStatuses.includes(itv.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -887,7 +887,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
|
||||||
// Pour les managers, restreindre aux statuts spécifiques
|
// Pour les managers, restreindre aux statuts spécifiques
|
||||||
if (isEditable && isManager) {
|
if (isEditable && isManager) {
|
||||||
const managerAllowedStatuses = ['in_preparation', 'to_be_approved', 'to_be_planned', 'to_be_processed'];
|
const managerAllowedStatuses = ['in_preparation', 'to_be_approved', 'to_be_planned', 'to_be_processed', 'assigned'];
|
||||||
isEditable = managerAllowedStatuses.includes(statusCode);
|
isEditable = managerAllowedStatuses.includes(statusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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, UserContractAccess
|
from common.models import UserConfig, Role, Thematic, UserThematics, UserContractAccess, UserContractStatusPermission
|
||||||
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 (
|
||||||
|
|
@ -592,5 +592,225 @@ class CalendarContractFilterTests(TestCase):
|
||||||
self.assertIn(self.contract_forbidden.id, contract_ids)
|
self.assertIn(self.contract_forbidden.id, contract_ids)
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalManagerScheduleUpdateTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
User = get_user_model()
|
||||||
|
self.company_ext = Company.objects.create(name='Prestataire Ext A')
|
||||||
|
self.company_other = Company.objects.create(name='Prestataire Ext B')
|
||||||
|
|
||||||
|
self.ext_user = User.objects.create_user(username='a2manager', password='pwd')
|
||||||
|
self.role_ext = Role.objects.create(name='external_manager')
|
||||||
|
self.ext_config = UserConfig.objects.create(
|
||||||
|
user=self.ext_user,
|
||||||
|
is_intern=False,
|
||||||
|
company=self.company_ext,
|
||||||
|
)
|
||||||
|
self.ext_config.roles.add(self.role_ext)
|
||||||
|
|
||||||
|
today = timezone.now().date()
|
||||||
|
self.thematic = Thematic.objects.create(code='roads_ext_mgr', name_fr='Routes Ext', name_nl='Wegen Ext')
|
||||||
|
self.contract = Contract.objects.create(
|
||||||
|
contract_number='CTR-EXT-A',
|
||||||
|
company=self.company_ext,
|
||||||
|
start_date=today - timedelta(days=10),
|
||||||
|
end_date=today + timedelta(days=365),
|
||||||
|
is_active=True,
|
||||||
|
)
|
||||||
|
self.contract.thematics.add(self.thematic)
|
||||||
|
|
||||||
|
UserThematics.objects.create(
|
||||||
|
user_config=self.ext_config,
|
||||||
|
thematic=self.thematic,
|
||||||
|
can_view_interventions=True,
|
||||||
|
can_edit_interventions=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.uca = UserContractAccess.objects.create(
|
||||||
|
user_config=self.ext_config,
|
||||||
|
contract=self.contract,
|
||||||
|
can_view_interventions=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Status permissions: can_view=True, can_edit=False, can_change_status_to=False
|
||||||
|
UserContractStatusPermission.objects.create(
|
||||||
|
user_contract=self.uca,
|
||||||
|
status='to_be_planned',
|
||||||
|
can_view=True,
|
||||||
|
can_edit=False,
|
||||||
|
can_change_status_to=True,
|
||||||
|
)
|
||||||
|
UserContractStatusPermission.objects.create(
|
||||||
|
user_contract=self.uca,
|
||||||
|
status='to_be_processed',
|
||||||
|
can_view=True,
|
||||||
|
can_edit=False,
|
||||||
|
can_change_status_to=False,
|
||||||
|
)
|
||||||
|
UserContractStatusPermission.objects.create(
|
||||||
|
user_contract=self.uca,
|
||||||
|
status='assigned',
|
||||||
|
can_view=True,
|
||||||
|
can_edit=False,
|
||||||
|
can_change_status_to=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.team_ext_1 = CompanyTeam.objects.create(name='Equipe 1', company=self.company_ext)
|
||||||
|
self.team_ext_2 = CompanyTeam.objects.create(name='Equipe 2', company=self.company_ext)
|
||||||
|
self.member_ext_1 = CompanyMember.objects.create(name='Membre 1', user=self.ext_user)
|
||||||
|
self.member_ext_1.teams.add(self.team_ext_1)
|
||||||
|
self.member_ext_2 = CompanyMember.objects.create(name='Membre 2', user=self.ext_user)
|
||||||
|
self.member_ext_2.teams.add(self.team_ext_2)
|
||||||
|
|
||||||
|
self.team_other = CompanyTeam.objects.create(name='Equipe B', company=self.company_other)
|
||||||
|
self.member_other = CompanyMember.objects.create(name='Membre B', user=self.ext_user)
|
||||||
|
self.member_other.teams.add(self.team_other)
|
||||||
|
|
||||||
|
now = timezone.now()
|
||||||
|
self.itv = Intervention.objects.create(
|
||||||
|
title='Intervention Prestataire A',
|
||||||
|
thematic=self.thematic,
|
||||||
|
contract=self.contract,
|
||||||
|
status='to_be_processed',
|
||||||
|
maintain_type='corrective',
|
||||||
|
assigned_team=self.team_ext_1,
|
||||||
|
assigned_member=self.member_ext_1,
|
||||||
|
planned_begin_time=now + timedelta(hours=1),
|
||||||
|
planned_end_time=now + timedelta(hours=3),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.update_url = reverse('interventions:interventions_schedule_update')
|
||||||
|
self.client.login(username='a2manager', password='pwd')
|
||||||
|
|
||||||
|
def test_external_manager_can_reschedule_to_be_processed_intervention(self):
|
||||||
|
new_begin = timezone.now() + timedelta(days=2)
|
||||||
|
new_end = new_begin + timedelta(hours=2)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'intervention_id': self.itv.id,
|
||||||
|
'planned_begin_time': new_begin.isoformat(),
|
||||||
|
'planned_end_time': new_end.isoformat(),
|
||||||
|
}
|
||||||
|
res = self.client.post(self.update_url, data=json.dumps(payload), content_type='application/json')
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertTrue(res.json().get('success'))
|
||||||
|
|
||||||
|
self.itv.refresh_from_db()
|
||||||
|
self.assertEqual(self.itv.planned_begin_time.date(), new_begin.date())
|
||||||
|
self.assertEqual(self.itv.status, 'to_be_processed')
|
||||||
|
|
||||||
|
def test_external_manager_can_reassign_member_and_team_on_to_be_processed_intervention(self):
|
||||||
|
new_begin = timezone.now() + timedelta(days=1)
|
||||||
|
new_end = new_begin + timedelta(hours=2)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'intervention_id': self.itv.id,
|
||||||
|
'planned_begin_time': new_begin.isoformat(),
|
||||||
|
'planned_end_time': new_end.isoformat(),
|
||||||
|
'assigned_member_id': self.member_ext_2.id,
|
||||||
|
'assigned_team_id': self.team_ext_2.id,
|
||||||
|
}
|
||||||
|
res = self.client.post(self.update_url, data=json.dumps(payload), content_type='application/json')
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertTrue(res.json().get('success'))
|
||||||
|
|
||||||
|
self.itv.refresh_from_db()
|
||||||
|
self.assertEqual(self.itv.assigned_member_id, self.member_ext_2.id)
|
||||||
|
self.assertEqual(self.itv.assigned_team_id, self.team_ext_2.id)
|
||||||
|
self.assertEqual(self.itv.status, 'to_be_processed')
|
||||||
|
|
||||||
|
def test_external_manager_can_unassign_to_be_processed_intervention_to_backlog(self):
|
||||||
|
payload = {
|
||||||
|
'intervention_id': self.itv.id,
|
||||||
|
'assigned_member_id': None,
|
||||||
|
'assigned_team_id': None,
|
||||||
|
'planned_begin_time': None,
|
||||||
|
'planned_end_time': None,
|
||||||
|
'unassign': True,
|
||||||
|
}
|
||||||
|
res = self.client.post(self.update_url, data=json.dumps(payload), content_type='application/json')
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertTrue(res.json().get('success'))
|
||||||
|
|
||||||
|
self.itv.refresh_from_db()
|
||||||
|
self.assertEqual(self.itv.status, 'to_be_planned')
|
||||||
|
self.assertIsNone(self.itv.assigned_member)
|
||||||
|
self.assertIsNone(self.itv.assigned_team)
|
||||||
|
self.assertIsNone(self.itv.planned_begin_time)
|
||||||
|
self.assertIsNone(self.itv.planned_end_time)
|
||||||
|
|
||||||
|
def test_external_manager_cannot_assign_member_of_different_company(self):
|
||||||
|
new_begin = timezone.now() + timedelta(days=1)
|
||||||
|
new_end = new_begin + timedelta(hours=2)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'intervention_id': self.itv.id,
|
||||||
|
'planned_begin_time': new_begin.isoformat(),
|
||||||
|
'planned_end_time': new_end.isoformat(),
|
||||||
|
'assigned_member_id': self.member_other.id,
|
||||||
|
'assigned_team_id': self.team_other.id,
|
||||||
|
}
|
||||||
|
res = self.client.post(self.update_url, data=json.dumps(payload), content_type='application/json')
|
||||||
|
self.assertEqual(res.status_code, 403)
|
||||||
|
self.assertIn("entreprise", res.json().get('error', '').lower())
|
||||||
|
|
||||||
|
def test_external_manager_can_unassign_assigned_status_intervention_to_backlog(self):
|
||||||
|
itv_assigned = Intervention.objects.create(
|
||||||
|
title='Intervention Status Assigned',
|
||||||
|
thematic=self.thematic,
|
||||||
|
contract=self.contract,
|
||||||
|
status='assigned',
|
||||||
|
maintain_type='corrective',
|
||||||
|
assigned_team=self.team_ext_1,
|
||||||
|
assigned_member=self.member_ext_1,
|
||||||
|
planned_begin_time=timezone.now() + timedelta(hours=1),
|
||||||
|
planned_end_time=timezone.now() + timedelta(hours=3),
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'intervention_id': itv_assigned.id,
|
||||||
|
'assigned_member_id': None,
|
||||||
|
'assigned_team_id': None,
|
||||||
|
'unassign': True,
|
||||||
|
}
|
||||||
|
res = self.client.post(self.update_url, data=json.dumps(payload), content_type='application/json')
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
|
||||||
|
itv_assigned.refresh_from_db()
|
||||||
|
self.assertEqual(itv_assigned.status, 'to_be_planned')
|
||||||
|
self.assertIsNone(itv_assigned.assigned_member)
|
||||||
|
self.assertIsNone(itv_assigned.assigned_team)
|
||||||
|
|
||||||
|
def test_internal_manager_cannot_assign_members_in_calendar(self):
|
||||||
|
User = get_user_model()
|
||||||
|
mgr_user = User.objects.create_user(username='internal-mgr', password='pwd')
|
||||||
|
role_mgr = Role.objects.create(name='manager')
|
||||||
|
mgr_config = UserConfig.objects.create(user=mgr_user, is_intern=True)
|
||||||
|
mgr_config.roles.add(role_mgr)
|
||||||
|
|
||||||
|
uca_mgr = UserContractAccess.objects.create(
|
||||||
|
user_config=mgr_config,
|
||||||
|
contract=self.contract,
|
||||||
|
can_view_interventions=True,
|
||||||
|
)
|
||||||
|
UserContractStatusPermission.objects.create(
|
||||||
|
user_contract=uca_mgr,
|
||||||
|
status='to_be_processed',
|
||||||
|
can_view=True,
|
||||||
|
can_edit=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.client.login(username='internal-mgr', password='pwd')
|
||||||
|
payload = {
|
||||||
|
'intervention_id': self.itv.id,
|
||||||
|
'planned_begin_time': (timezone.now() + timedelta(days=1)).isoformat(),
|
||||||
|
'planned_end_time': (timezone.now() + timedelta(days=1, hours=2)).isoformat(),
|
||||||
|
'assigned_member_id': self.member_ext_2.id,
|
||||||
|
}
|
||||||
|
res = self.client.post(self.update_url, data=json.dumps(payload), content_type='application/json', HTTP_ACCEPT_LANGUAGE='fr')
|
||||||
|
self.assertEqual(res.status_code, 403)
|
||||||
|
self.assertIn("assigner", res.json().get('error', '').lower())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1132,22 +1132,28 @@ def interventions_schedule_update(request):
|
||||||
except Intervention.DoesNotExist:
|
except Intervention.DoesNotExist:
|
||||||
return JsonResponse({'error': _('Intervention introuvable.')}, status=404)
|
return JsonResponse({'error': _('Intervention introuvable.')}, status=404)
|
||||||
|
|
||||||
if not can_edit_intervention(request.user, intervention):
|
user_config = getattr(request.user, 'config', None)
|
||||||
return JsonResponse({'error': _('Vous ne pouvez pas modifier cette intervention.')}, status=403)
|
if not user_config:
|
||||||
|
try:
|
||||||
|
user_config = UserConfig.objects.get(user=request.user)
|
||||||
|
except UserConfig.DoesNotExist:
|
||||||
|
return JsonResponse({'error': _("Configuration utilisateur introuvable.")}, status=403)
|
||||||
|
|
||||||
# Vérifier les restrictions pour les managers
|
user_roles = set(user_config.roles.values_list('name', flat=True))
|
||||||
try:
|
is_admin = bool(user_roles & {'admin', 'top_manager'}) or request.user.is_superuser
|
||||||
user_config = UserConfig.objects.get(user=request.user)
|
is_external_manager = 'external_manager' in user_roles
|
||||||
user_roles = set(user_config.roles.values_list('name', flat=True))
|
is_controller = 'controller' in user_roles
|
||||||
is_manager_only = 'manager' in user_roles and not user_roles.intersection({'admin', 'controller', 'external_manager'})
|
is_manager_only = 'manager' in user_roles and not bool(user_roles & {'admin', 'top_manager', 'controller', 'external_manager'})
|
||||||
|
|
||||||
# Les managers ne peuvent déplacer que les interventions dans certains statuts
|
# 1. Vérifier si l'utilisateur a un rôle autorisé à planifier/déplacer dans le calendrier
|
||||||
if is_manager_only:
|
can_schedule = is_admin or is_external_manager or is_controller or is_manager_only or (bool(user_roles & {'technician', 'inspector'}) and can_edit_intervention(request.user, intervention, user_config=user_config))
|
||||||
allowed_statuses_for_manager_move = {'in_preparation', 'to_be_approved', 'to_be_planned', 'to_be_processed'}
|
if not can_schedule:
|
||||||
if intervention.status not in allowed_statuses_for_manager_move:
|
return JsonResponse({'error': _("Vous n'avez pas la permission de planifier cette intervention.")}, status=403)
|
||||||
return JsonResponse({'error': _("Vous ne pouvez pas déplacer cette intervention dans son statut actuel.")}, status=403)
|
|
||||||
except UserConfig.DoesNotExist:
|
# 2. Vérifier si le statut actuel permet le déplacement/replanification dans le calendrier
|
||||||
pass
|
allowed_statuses_for_move = {'in_preparation', 'to_be_approved', 'to_be_planned', 'to_be_processed', 'assigned'}
|
||||||
|
if intervention.status not in allowed_statuses_for_move:
|
||||||
|
return JsonResponse({'error': _("Vous ne pouvez pas déplacer cette intervention dans son statut actuel.")}, status=403)
|
||||||
|
|
||||||
member_key_present = 'assigned_member_id' in payload
|
member_key_present = 'assigned_member_id' in payload
|
||||||
team_key_present = 'assigned_team_id' in payload
|
team_key_present = 'assigned_team_id' in payload
|
||||||
|
|
@ -1182,10 +1188,10 @@ def interventions_schedule_update(request):
|
||||||
if new_team_id != prev_team_id:
|
if new_team_id != prev_team_id:
|
||||||
assignment_will_change = True
|
assignment_will_change = True
|
||||||
|
|
||||||
# Vérifier la permission d'assignation seulement si l'assignation va réellement changer
|
# 3. Vérifier la permission d'assignation seulement si l'assignation va réellement changer
|
||||||
if assignment_will_change:
|
if assignment_will_change:
|
||||||
allowed_update_fields = get_allowed_update_fields(request.user, intervention)
|
can_assign = is_admin or is_external_manager or is_controller or (bool(user_roles & {'technician', 'inspector'}) and can_edit_intervention(request.user, intervention, user_config=user_config))
|
||||||
if 'member_assignment' not in allowed_update_fields:
|
if not can_assign:
|
||||||
return JsonResponse({'error': _("Vous n'avez pas la permission d'assigner cette intervention.")}, status=403)
|
return JsonResponse({'error': _("Vous n'avez pas la permission d'assigner cette intervention.")}, status=403)
|
||||||
|
|
||||||
member = None
|
member = None
|
||||||
|
|
@ -1195,10 +1201,17 @@ def interventions_schedule_update(request):
|
||||||
member = CompanyMember.objects.prefetch_related('teams').filter(id=member_id).first()
|
member = CompanyMember.objects.prefetch_related('teams').filter(id=member_id).first()
|
||||||
if not member:
|
if not member:
|
||||||
return JsonResponse({'error': _('Membre assigné invalide.')}, status=400)
|
return JsonResponse({'error': _('Membre assigné invalide.')}, status=400)
|
||||||
|
m_teams = member.teams.all()
|
||||||
|
# Vérifier que le membre appartient à une entreprise autorisée pour un external_manager
|
||||||
|
if is_external_manager and not is_admin:
|
||||||
|
allowed_company_ids = _member_view_allowed_company_ids(user_config, user_roles)
|
||||||
|
if allowed_company_ids is not None:
|
||||||
|
member_company_ids = set(m_teams.values_list('company_id', flat=True))
|
||||||
|
if not member_company_ids.intersection(allowed_company_ids):
|
||||||
|
return JsonResponse({'error': _("Vous ne pouvez assigner qu'aux membres de votre entreprise.")}, status=403)
|
||||||
# When assigning to a member, also set the team.
|
# When assigning to a member, also set the team.
|
||||||
# If team_id is provided in the payload and is one of the member's teams, use it.
|
# If team_id is provided in the payload and is one of the member's teams, use it.
|
||||||
# Otherwise, fall back to the first team.
|
# Otherwise, fall back to the first team.
|
||||||
m_teams = member.teams.all()
|
|
||||||
if team_id and any(t.id == team_id for t in m_teams):
|
if team_id and any(t.id == team_id for t in m_teams):
|
||||||
team = next(t for t in m_teams if t.id == team_id)
|
team = next(t for t in m_teams if t.id == team_id)
|
||||||
else:
|
else:
|
||||||
|
|
@ -1208,6 +1221,11 @@ def interventions_schedule_update(request):
|
||||||
team = CompanyTeam.objects.filter(id=team_id).first()
|
team = CompanyTeam.objects.filter(id=team_id).first()
|
||||||
if not team:
|
if not team:
|
||||||
return JsonResponse({'error': _('Équipe assignée invalide.')}, status=400)
|
return JsonResponse({'error': _('Équipe assignée invalide.')}, status=400)
|
||||||
|
# Vérifier que l'équipe appartient à une entreprise autorisée pour un external_manager
|
||||||
|
if is_external_manager and not is_admin:
|
||||||
|
allowed_company_ids = _member_view_allowed_company_ids(user_config, user_roles)
|
||||||
|
if allowed_company_ids is not None and team.company_id not in allowed_company_ids:
|
||||||
|
return JsonResponse({'error': _("Vous ne pouvez assigner qu'aux équipes de votre entreprise.")}, status=403)
|
||||||
|
|
||||||
begin_str = payload.get('planned_begin_time')
|
begin_str = payload.get('planned_begin_time')
|
||||||
end_str = payload.get('planned_end_time')
|
end_str = payload.get('planned_end_time')
|
||||||
|
|
|
||||||
|
|
@ -270,16 +270,16 @@ def _can_use_member_view(role_names: Set[str]) -> bool:
|
||||||
def _can_move_interventions_in_calendar(role_names: Set[str], view_mode: str) -> bool:
|
def _can_move_interventions_in_calendar(role_names: Set[str], view_mode: str) -> bool:
|
||||||
"""Détermine si l'utilisateur peut déplacer des interventions via drag & drop.
|
"""Détermine si l'utilisateur peut déplacer des interventions via drag & drop.
|
||||||
|
|
||||||
- Dans la vue membres et équipes quotidien: seuls admin, controller, external_manager peuvent déplacer (pas les managers)
|
- Dans la vue membres et équipes quotidien: seuls admin, top_manager, controller, external_manager peuvent déplacer (pas les managers)
|
||||||
- Dans la vue timeline: admin, manager, controller, external_manager peuvent déplacer
|
- Dans la vue timeline: admin, top_manager, manager, controller, external_manager peuvent déplacer
|
||||||
(mais les managers ont des restrictions par statut, gérées ailleurs)
|
(mais les managers ont des restrictions par statut, gérées ailleurs)
|
||||||
"""
|
"""
|
||||||
if view_mode in ('members', 'teams_daily'):
|
if view_mode in ('members', 'teams_daily'):
|
||||||
# Vue membres & quotidien: managers ne peuvent PAS déplacer
|
# Vue membres & quotidien: managers ne peuvent PAS déplacer
|
||||||
return bool(role_names & {'admin', 'controller', 'external_manager'})
|
return bool(role_names & {'admin', 'top_manager', 'controller', 'external_manager'})
|
||||||
else:
|
else:
|
||||||
# Vue timeline: managers peuvent déplacer (avec restrictions par statut)
|
# Vue timeline: managers peuvent déplacer (avec restrictions par statut)
|
||||||
return bool(role_names & {'admin', 'manager', 'controller', 'external_manager'})
|
return bool(role_names & {'admin', 'top_manager', 'manager', 'controller', 'external_manager'})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue