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})