371 lines
13 KiB
Python
371 lines
13 KiB
Python
import json
|
|
|
|
from django.contrib.auth import get_user_model
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.core.exceptions import PermissionDenied
|
|
from django.http import JsonResponse
|
|
from django.shortcuts import get_object_or_404, render
|
|
from django.db.models import Count, Q
|
|
from django.views.decorators.http import require_GET, require_POST
|
|
|
|
from contracts.models import CompanyMember, CompanyTeam
|
|
from contracts.permissions import (
|
|
get_viewable_teams_for_user,
|
|
user_can_edit_specific_team,
|
|
user_can_edit_teams,
|
|
user_can_view_teams,
|
|
)
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
def _get_user_company(user):
|
|
"""Retourne la Company associée à l'utilisateur, ou None."""
|
|
try:
|
|
return user.config.company
|
|
except AttributeError:
|
|
return None
|
|
|
|
|
|
def _is_admin(user):
|
|
try:
|
|
return user.config.roles.filter(name="admin").exists()
|
|
except AttributeError:
|
|
return False
|
|
|
|
|
|
@login_required
|
|
def teams_index(request):
|
|
"""Vue principale de gestion des équipes."""
|
|
if not user_can_view_teams(request.user):
|
|
raise PermissionDenied("Vous n'avez pas accès à la gestion des équipes.")
|
|
|
|
teams = (
|
|
get_viewable_teams_for_user(request.user)
|
|
.select_related("company")
|
|
.prefetch_related("members__user")
|
|
.annotate(
|
|
active_interventions_count=Count(
|
|
'assigned_interventions',
|
|
filter=Q(assigned_interventions__status_order__lte=51)
|
|
)
|
|
)
|
|
.order_by("company__name", "name")
|
|
)
|
|
can_edit = user_can_edit_teams(request.user)
|
|
user_company = _get_user_company(request.user)
|
|
is_admin_user = _is_admin(request.user)
|
|
|
|
# Les non-admins ne voient que les utilisateurs de leur propre société et des sociétés des équipes qu'ils ont le droit d'éditer
|
|
if is_admin_user or user_company is None:
|
|
available_users = User.objects.all().select_related("config__company").order_by("first_name", "last_name", "username")
|
|
else:
|
|
from contracts.models import UserCompanyTeamAccess
|
|
editable_team_companies = CompanyTeam.objects.filter(
|
|
user_accesses__user_config=request.user.config,
|
|
user_accesses__can_edit=True
|
|
).values_list('company_id', flat=True)
|
|
|
|
allowed_company_ids = set(editable_team_companies)
|
|
allowed_company_ids.add(user_company.id)
|
|
|
|
available_users = User.objects.filter(
|
|
config__company_id__in=allowed_company_ids
|
|
).select_related("config__company").order_by("first_name", "last_name", "username")
|
|
|
|
# Membres non assignés à une équipe
|
|
unassigned_qs = CompanyMember.objects.filter(teams__isnull=True).select_related("user")
|
|
if not is_admin_user and user_company is not None:
|
|
unassigned_qs = unassigned_qs.filter(user__config__company=user_company)
|
|
unassigned_members = unassigned_qs.order_by("name")
|
|
|
|
return render(
|
|
request,
|
|
"contracts/teams_index.html",
|
|
{
|
|
"teams": teams,
|
|
"can_edit": can_edit,
|
|
"available_users": available_users,
|
|
"unassigned_members": unassigned_members,
|
|
"user_company": user_company,
|
|
"is_admin": is_admin_user,
|
|
},
|
|
)
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def teams_move_member(request, member_id):
|
|
"""Déplacer un membre vers une autre équipe (AJAX)."""
|
|
try:
|
|
data = json.loads(request.body)
|
|
except (json.JSONDecodeError, ValueError):
|
|
return JsonResponse({"error": "JSON invalide"}, status=400)
|
|
|
|
# team_id peut être None / absent pour désassigner
|
|
raw_team_id = data.get("team_id")
|
|
new_team_id = int(raw_team_id) if raw_team_id is not None else None
|
|
|
|
# old_team_id represents the team column they are dragged out of
|
|
old_team_id = data.get("old_team_id")
|
|
old_team_id = int(old_team_id) if old_team_id is not None else None
|
|
|
|
member = get_object_or_404(CompanyMember, id=member_id)
|
|
viewable = get_viewable_teams_for_user(request.user)
|
|
|
|
# Vérifier l'accès à l'équipe source (si le membre était dans une équipe)
|
|
if old_team_id is not None:
|
|
if not viewable.filter(id=old_team_id).exists():
|
|
raise PermissionDenied("Accès refusé.")
|
|
if not user_can_edit_specific_team(request.user, old_team_id):
|
|
raise PermissionDenied("Droits d'édition insuffisants.")
|
|
else:
|
|
if not user_can_edit_teams(request.user):
|
|
raise PermissionDenied("Droits d'édition insuffisants.")
|
|
|
|
if new_team_id is not None:
|
|
new_team = get_object_or_404(CompanyTeam, id=new_team_id)
|
|
if not viewable.filter(id=new_team_id).exists():
|
|
raise PermissionDenied("Accès refusé.")
|
|
if not user_can_edit_specific_team(request.user, new_team_id):
|
|
raise PermissionDenied("Droits d'édition insuffisants.")
|
|
else:
|
|
new_team = None
|
|
|
|
old_team = None
|
|
if old_team_id is not None:
|
|
old_team = get_object_or_404(CompanyTeam, id=old_team_id)
|
|
member.teams.remove(old_team)
|
|
|
|
if new_team_id is not None:
|
|
member.teams.add(new_team)
|
|
|
|
member.save()
|
|
|
|
return JsonResponse(
|
|
{
|
|
"success": True,
|
|
"member_id": member.id,
|
|
"member_name": member.name,
|
|
"old_team_name": old_team.name if old_team else None,
|
|
"new_team_name": new_team.name if new_team else None,
|
|
}
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def teams_create_member(request):
|
|
"""Créer un nouveau membre, optionnellement dans une équipe (AJAX)."""
|
|
try:
|
|
data = json.loads(request.body)
|
|
except (json.JSONDecodeError, ValueError):
|
|
return JsonResponse({"error": "JSON invalide"}, status=400)
|
|
|
|
name = data.get("name", "").strip()
|
|
user_id = data.get("user_id")
|
|
raw_team_id = data.get("team_id")
|
|
team_id = int(raw_team_id) if raw_team_id is not None else None
|
|
|
|
if not name:
|
|
return JsonResponse({"error": "Le nom du membre est requis."}, status=400)
|
|
if not user_id:
|
|
return JsonResponse({"error": "L'utilisateur est requis."}, status=400)
|
|
|
|
if team_id is not None:
|
|
if not user_can_edit_specific_team(request.user, team_id):
|
|
raise PermissionDenied("Droits d'édition insuffisants.")
|
|
viewable = get_viewable_teams_for_user(request.user)
|
|
if not viewable.filter(id=team_id).exists():
|
|
raise PermissionDenied("Accès refusé.")
|
|
team = get_object_or_404(CompanyTeam, id=team_id)
|
|
else:
|
|
if not user_can_edit_teams(request.user):
|
|
raise PermissionDenied("Droits d'édition insuffisants.")
|
|
team = None
|
|
|
|
user_obj = get_object_or_404(User, id=user_id)
|
|
|
|
# Les non-admins ne peuvent ajouter que des utilisateurs de leur société, ou de la société de l'équipe concernée
|
|
user_company = _get_user_company(request.user)
|
|
if not _is_admin(request.user) and user_company is not None:
|
|
target_company = _get_user_company(user_obj)
|
|
allowed_companies = {user_company}
|
|
if team is not None:
|
|
allowed_companies.add(team.company)
|
|
|
|
if target_company not in allowed_companies:
|
|
return JsonResponse(
|
|
{"error": "Vous ne pouvez ajouter que des utilisateurs de votre société ou de la société de l'équipe."},
|
|
status=403,
|
|
)
|
|
|
|
# Check if there is already a CompanyMember for this user
|
|
member = CompanyMember.objects.filter(user=user_obj).first()
|
|
if member:
|
|
if team:
|
|
if member.teams.filter(id=team.id).exists():
|
|
return JsonResponse(
|
|
{"error": "Cet utilisateur fait déjà partie de cette équipe."},
|
|
status=400,
|
|
)
|
|
else:
|
|
if not member.teams.exists():
|
|
return JsonResponse(
|
|
{"error": "Cet utilisateur est déjà dans les membres non assignés."},
|
|
status=400,
|
|
)
|
|
if name:
|
|
member.name = name
|
|
member.save()
|
|
else:
|
|
member = CompanyMember.objects.create(name=name, user=user_obj)
|
|
|
|
if team:
|
|
member.teams.add(team)
|
|
|
|
return JsonResponse(
|
|
{
|
|
"success": True,
|
|
"member": {
|
|
"id": member.id,
|
|
"name": member.name,
|
|
"team_id": team.id if team else None,
|
|
"team_name": team.name if team else None,
|
|
"user_display": user_obj.get_full_name() or user_obj.username,
|
|
},
|
|
}
|
|
)
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def teams_delete_member(request, member_id):
|
|
"""Supprimer un membre (AJAX)."""
|
|
member = get_object_or_404(CompanyMember, id=member_id)
|
|
|
|
member_team_ids = list(member.teams.values_list('id', flat=True))
|
|
if member_team_ids:
|
|
viewable = get_viewable_teams_for_user(request.user)
|
|
for t_id in member_team_ids:
|
|
if not viewable.filter(id=t_id).exists():
|
|
raise PermissionDenied("Accès refusé.")
|
|
if not user_can_edit_specific_team(request.user, t_id):
|
|
raise PermissionDenied("Droits d'édition insuffisants.")
|
|
else:
|
|
# Membre non assigné : droits d'édition généraux suffisent
|
|
if not user_can_edit_teams(request.user):
|
|
raise PermissionDenied("Droits d'édition insuffisants.")
|
|
|
|
member_name = member.name
|
|
member.delete()
|
|
|
|
return JsonResponse({"success": True, "member_name": member_name})
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def teams_update_member(request, member_id):
|
|
"""Modifier le nom et/ou l'équipe d'un membre (AJAX)."""
|
|
member = get_object_or_404(CompanyMember, id=member_id)
|
|
viewable = get_viewable_teams_for_user(request.user)
|
|
|
|
try:
|
|
data = json.loads(request.body)
|
|
except (json.JSONDecodeError, ValueError):
|
|
return JsonResponse({"error": "JSON invalide"}, status=400)
|
|
|
|
# old_team_id represents the team column they clicked edit on (or None if unassigned)
|
|
old_team_id = data.get("old_team_id")
|
|
old_team_id = int(old_team_id) if old_team_id is not None else None
|
|
|
|
# Vérifier l'accès à l'équipe source
|
|
if old_team_id is not None:
|
|
if not viewable.filter(id=old_team_id).exists():
|
|
raise PermissionDenied("Accès refusé.")
|
|
if not user_can_edit_specific_team(request.user, old_team_id):
|
|
raise PermissionDenied("Droits d'édition insuffisants.")
|
|
else:
|
|
if not user_can_edit_teams(request.user):
|
|
raise PermissionDenied("Droits d'édition insuffisants.")
|
|
|
|
new_name = data.get("name", "").strip()
|
|
if not new_name:
|
|
return JsonResponse({"error": "Le nom du membre est requis."}, status=400)
|
|
|
|
raw_team_id = data.get("team_id") # peut être absent (non modifié) ou None (désassigner)
|
|
team_changed = "team_id" in data
|
|
|
|
new_team_id = None
|
|
new_team = None
|
|
|
|
if team_changed:
|
|
new_team_id = int(raw_team_id) if raw_team_id is not None else None
|
|
if new_team_id is not None:
|
|
new_team = get_object_or_404(CompanyTeam, id=new_team_id)
|
|
if not viewable.filter(id=new_team_id).exists():
|
|
raise PermissionDenied("Accès refusé.")
|
|
if not user_can_edit_specific_team(request.user, new_team_id):
|
|
raise PermissionDenied("Droits d'édition insuffisants.")
|
|
else:
|
|
new_team = None
|
|
|
|
# Effectuer le transfert
|
|
if old_team_id is not None:
|
|
old_team = get_object_or_404(CompanyTeam, id=old_team_id)
|
|
member.teams.remove(old_team)
|
|
if new_team_id is not None:
|
|
member.teams.add(new_team)
|
|
|
|
member.name = new_name
|
|
member.save()
|
|
|
|
return JsonResponse({
|
|
"success": True,
|
|
"member_id": member.id,
|
|
"member_name": member.name,
|
|
"team_id": new_team_id if team_changed else old_team_id,
|
|
"team_name": new_team.name if team_changed and new_team else None,
|
|
})
|
|
|
|
|
|
@login_required
|
|
@require_GET
|
|
def team_interventions_json(request, team_id):
|
|
"""Retourne les interventions assignées à une équipe (JSON, pour la modale)."""
|
|
from django.urls import reverse
|
|
from interventions.models import Intervention
|
|
|
|
team = get_object_or_404(CompanyTeam, id=team_id)
|
|
viewable = get_viewable_teams_for_user(request.user)
|
|
if not viewable.filter(id=team_id).exists():
|
|
raise PermissionDenied("Accès refusé.")
|
|
|
|
interventions = (
|
|
Intervention.objects
|
|
.filter(assigned_team=team, status_order__lte=51)
|
|
.select_related('assigned_member')
|
|
.order_by('-creation_time')
|
|
[:500]
|
|
)
|
|
|
|
data = []
|
|
for i in interventions:
|
|
data.append({
|
|
'id': i.id,
|
|
'code': i.code,
|
|
'title': i.title,
|
|
'status': i.status,
|
|
'status_display': i.get_status_display(),
|
|
'status_color': i.get_status_color(),
|
|
'assigned_member_name': i.assigned_member.name if i.assigned_member else None,
|
|
'summary_url': reverse('interventions:intervention_summary_modal', args=[i.id]),
|
|
'detail_url': reverse('interventions:intervention_detail', args=[i.id]),
|
|
})
|
|
|
|
return JsonResponse({'interventions': data, 'team_name': team.name})
|