""" Organizations views and viewsets """ from django.db import models from rest_framework import status, viewsets from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from apps.accounts.permissions import ( CanAccessDepartmentData, CanAccessHospitalData, IsPXAdminOrHospitalAdmin, IsPXAdmin ) from .models import Department, Hospital, Organization, Patient, Staff from .models import Staff as StaffModel from .serializers import ( DepartmentSerializer, HospitalSerializer, OrganizationSerializer, PatientListSerializer, PatientSerializer, StaffSerializer, ) class OrganizationViewSet(viewsets.ModelViewSet): """ ViewSet for Organization model. Permissions: - PX Admins can manage organizations - Others can view organizations """ queryset = Organization.objects.all() serializer_class = OrganizationSerializer permission_classes = [IsAuthenticated] filterset_fields = ['status', 'city'] search_fields = ['name', 'name_ar', 'code', 'license_number'] ordering_fields = ['name', 'created_at'] ordering = ['name'] def get_queryset(self): """Filter organizations based on user role""" queryset = super().get_queryset().prefetch_related('hospitals') user = self.request.user # PX Admins see all organizations if user.is_px_admin(): return queryset # Hospital Admins and others see their organization if user.is_hospital_admin() and user.hospital and user.hospital.organization: return queryset.filter(id=user.hospital.organization.id) # Others with hospital see their organization if user.hospital and user.hospital.organization: return queryset.filter(id=user.hospital.organization.id) return queryset.none() class HospitalViewSet(viewsets.ModelViewSet): """ ViewSet for Hospital model. Permissions: - PX Admins and Hospital Admins can manage hospitals - Others can view hospitals they belong to """ queryset = Hospital.objects.all() serializer_class = HospitalSerializer permission_classes = [IsAuthenticated, CanAccessHospitalData] filterset_fields = ['status', 'city', 'organization'] search_fields = ['name', 'name_ar', 'code', 'city'] ordering_fields = ['name', 'created_at'] ordering = ['name'] def get_queryset(self): """Filter hospitals based on user role""" queryset = super().get_queryset() user = self.request.user # PX Admins see all hospitals if user.is_px_admin(): return queryset # Hospital Admins see their hospital if user.is_hospital_admin() and user.hospital: return queryset.filter(id=user.hospital.id) # Department Managers see their hospital if user.is_department_manager() and user.hospital: return queryset.filter(id=user.hospital.id) # Others see hospitals they're associated with if user.hospital: return queryset.filter(id=user.hospital.id) return queryset.none() class DepartmentViewSet(viewsets.ModelViewSet): """ ViewSet for Department model. Permissions: - PX Admins and Hospital Admins can manage departments - Department Managers can view their department """ queryset = Department.objects.all() serializer_class = DepartmentSerializer permission_classes = [IsAuthenticated, CanAccessDepartmentData] filterset_fields = ['status', 'hospital', 'parent', 'hospital__organization'] search_fields = ['name', 'name_ar', 'code'] ordering_fields = ['name', 'created_at'] ordering = ['hospital', 'name'] def get_queryset(self): """Filter departments based on user role""" queryset = super().get_queryset().select_related('hospital', 'parent', 'manager') user = self.request.user # PX Admins see all departments if user.is_px_admin(): return queryset # Hospital Admins see departments in their hospital if user.is_hospital_admin() and user.hospital: return queryset.filter(hospital=user.hospital) # Department Managers see their department and sub-departments if user.is_department_manager() and user.department: return queryset.filter( hospital=user.hospital ).filter( models.Q(id=user.department.id) | models.Q(parent=user.department) ) # Others see departments in their hospital if user.hospital: return queryset.filter(hospital=user.hospital) return queryset.none() class StaffViewSet(viewsets.ModelViewSet): """ ViewSet for Staff model. Permissions: - PX Admins and Hospital Admins can manage staff - Others can view staff """ queryset = StaffModel.objects.all() serializer_class = StaffSerializer permission_classes = [IsAuthenticated] filterset_fields = ['status', 'hospital', 'department', 'staff_type', 'specialization', 'job_title', 'hospital__organization'] search_fields = ['first_name', 'last_name', 'first_name_ar', 'last_name_ar', 'employee_id', 'license_number', 'job_title'] ordering_fields = ['last_name', 'created_at'] ordering = ['last_name', 'first_name'] def get_permissions(self): """Set permissions based on action""" if self.action in ['create_user_account', 'link_user', 'unlink_user', 'send_invitation']: return [IsAuthenticated()] return super().get_permissions() def get_queryset(self): """Filter staff based on user role""" queryset = super().get_queryset().select_related('hospital', 'department', 'user') user = self.request.user # PX Admins see all staff if user.is_px_admin(): return queryset # Hospital Admins see staff in their hospital if user.is_hospital_admin() and user.hospital: return queryset.filter(hospital=user.hospital) # Department Managers see staff in their department if user.is_department_manager() and user.department: return queryset.filter(department=user.department) # Others see staff in their hospital if user.hospital: return queryset.filter(hospital=user.hospital) return queryset.none() @action(detail=True, methods=['post']) def create_user_account(self, request, pk=None): """ Create a user account for a staff member. Auto-generates username, password, and sends email. """ staff = self.get_object() if staff.user: return Response( {'error': 'Staff member already has a user account'}, status=status.HTTP_400_BAD_REQUEST ) # Check permissions user = request.user if not user.is_px_admin() and not user.is_hospital_admin(): return Response( {'error': 'You do not have permission to create user accounts'}, status=status.HTTP_403_FORBIDDEN ) # Hospital Admins can only create accounts for staff in their hospital if user.is_hospital_admin() and staff.hospital != user.hospital: return Response( {'error': 'You can only create accounts for staff in your hospital'}, status=status.HTTP_403_FORBIDDEN ) # Get role from request or use default based on staff_type from .services import StaffService role = request.data.get('role', StaffService.get_staff_type_role(staff.staff_type)) try: user_account = StaffService.create_user_for_staff( staff, role=role, request=request ) # Generate password for email password = StaffService.generate_password() user_account.set_password(password) user_account.save() # Send email try: StaffService.send_credentials_email(staff, password, request) message = 'User account created and credentials emailed successfully' except Exception as e: message = f'User account created. Email sending failed: {str(e)}' serializer = self.get_serializer(staff) return Response({ 'message': message, 'staff': serializer.data, 'email': user_account.email }, status=status.HTTP_201_CREATED) except ValueError as e: return Response( {'error': str(e)}, status=status.HTTP_400_BAD_REQUEST ) @action(detail=True, methods=['post']) def link_user(self, request, pk=None): """ Link an existing user account to a staff member. """ staff = self.get_object() if staff.user: return Response( {'error': 'Staff member already has a user account'}, status=status.HTTP_400_BAD_REQUEST ) # Check permissions user = request.user if not user.is_px_admin() and not user.is_hospital_admin(): return Response( {'error': 'You do not have permission to link user accounts'}, status=status.HTTP_403_FORBIDDEN ) # Hospital Admins can only link accounts for staff in their hospital if user.is_hospital_admin() and staff.hospital != user.hospital: return Response( {'error': 'You can only link accounts for staff in your hospital'}, status=status.HTTP_403_FORBIDDEN ) user_id = request.data.get('user_id') if not user_id: return Response( {'error': 'user_id is required'}, status=status.HTTP_400_BAD_REQUEST ) from .services import StaffService try: StaffService.link_user_to_staff(staff, user_id, request=request) serializer = self.get_serializer(staff) return Response({ 'message': 'User account linked successfully', 'staff': serializer.data }) except ValueError as e: return Response( {'error': str(e)}, status=status.HTTP_400_BAD_REQUEST ) @action(detail=True, methods=['post']) def unlink_user(self, request, pk=None): """ Remove user account association from a staff member. """ staff = self.get_object() if not staff.user: return Response( {'error': 'Staff member has no user account'}, status=status.HTTP_400_BAD_REQUEST ) # Check permissions user = request.user if not user.is_px_admin() and not user.is_hospital_admin(): return Response( {'error': 'You do not have permission to unlink user accounts'}, status=status.HTTP_403_FORBIDDEN ) # Hospital Admins can only unlink accounts for staff in their hospital if user.is_hospital_admin() and staff.hospital != user.hospital: return Response( {'error': 'You can only unlink accounts for staff in your hospital'}, status=status.HTTP_403_FORBIDDEN ) from .services import StaffService try: StaffService.unlink_user_from_staff(staff, request=request) serializer = self.get_serializer(staff) return Response({ 'message': 'User account unlinked successfully', 'staff': serializer.data }) except ValueError as e: return Response( {'error': str(e)}, status=status.HTTP_400_BAD_REQUEST ) @action(detail=True, methods=['post']) def send_invitation(self, request, pk=None): """ Send credentials email to staff member. Generates new password and emails it. """ staff = self.get_object() if not staff.user: return Response( {'error': 'Staff member has no user account'}, status=status.HTTP_400_BAD_REQUEST ) # Check permissions user = request.user if not user.is_px_admin() and not user.is_hospital_admin(): return Response( {'error': 'You do not have permission to send invitations'}, status=status.HTTP_403_FORBIDDEN ) # Hospital Admins can only send invitations to staff in their hospital if user.is_hospital_admin() and staff.hospital != user.hospital: return Response( {'error': 'You can only send invitations to staff in your hospital'}, status=status.HTTP_403_FORBIDDEN ) from .services import StaffService try: # Generate new password password = StaffService.generate_password() # Update user password staff.user.set_password(password) staff.user.save() # Send email StaffService.send_credentials_email(staff, password, request) serializer = self.get_serializer(staff) return Response({ 'message': 'Invitation email sent successfully', 'staff': serializer.data }) except ValueError as e: return Response( {'error': str(e)}, status=status.HTTP_400_BAD_REQUEST ) @action(detail=False, methods=['get']) def hierarchy(self, request): """ Get staff hierarchy as D3-compatible JSON. Used for interactive tree visualization. Note: This action uses a more permissive queryset to allow all authenticated users to view the organization hierarchy for visualization purposes. """ from django.db.models import Q # Get filter parameters hospital_id = request.query_params.get('hospital') department_id = request.query_params.get('department') search = request.query_params.get('search', '').strip() # Build base queryset - use all staff for hierarchy visualization # This allows any authenticated user to see the full organizational structure queryset = StaffModel.objects.all().select_related('report_to', 'hospital', 'department') # Apply filters if hospital_id: queryset = queryset.filter(hospital_id=hospital_id) if department_id: queryset = queryset.filter(department_id=department_id) if search: queryset = queryset.filter( Q(first_name__icontains=search) | Q(last_name__icontains=search) | Q(employee_id__icontains=search) ) # Get all staff with their managers staff_list = queryset.select_related('report_to', 'hospital', 'department') # Build staff lookup dictionary staff_dict = {staff.id: staff for staff in staff_list} # Build hierarchy tree def build_node(staff): """Recursively build hierarchy node for D3""" node = { 'id': staff.id, 'name': staff.get_full_name(), 'employee_id': staff.employee_id, 'job_title': staff.job_title or '', 'hospital': staff.hospital.name if staff.hospital else '', 'department': staff.department.name if staff.department else '', 'status': staff.status, 'staff_type': staff.staff_type, 'team_size': 0, # Will be calculated 'children': [] } # Find direct reports direct_reports = [ s for s in staff_list if s.report_to_id == staff.id ] # Recursively build children for report in direct_reports: child_node = build_node(report) node['children'].append(child_node) node['team_size'] += 1 + child_node['team_size'] return node # Group root nodes by organization from collections import defaultdict org_groups = defaultdict(list) # Find root nodes (staff with no manager in the filtered set) root_staff = [ staff for staff in staff_list if staff.report_to_id is None or staff.report_to_id not in staff_dict ] # Group root staff by organization for staff in root_staff: if staff.hospital and staff.hospital.organization: org_name = staff.hospital.organization.name else: org_name = 'Organization' org_groups[org_name].append(staff) # Build hierarchy for each organization hierarchy = [] top_managers = 0 for org_name, org_root_staff in org_groups.items(): # Build hierarchy nodes for this organization's root staff org_root_nodes = [build_node(staff) for staff in org_root_staff] # Calculate total team size for this organization org_team_size = sum(node['team_size'] + 1 for node in org_root_nodes) # Create organization node as parent org_node = { 'id': None, 'name': org_name, 'employee_id': '', 'job_title': 'Organization', 'hospital': '', 'department': '', 'status': 'active', 'staff_type': 'organization', 'team_size': org_team_size, 'children': org_root_nodes, 'is_organization_root': True } hierarchy.append(org_node) top_managers += len(org_root_nodes) # If there are multiple organizations, wrap them in a single root if len(hierarchy) > 1: total_team_size = sum(node['team_size'] for node in hierarchy) hierarchy = [{ 'id': None, 'name': 'All Organizations', 'employee_id': '', 'job_title': '', 'hospital': '', 'department': '', 'status': 'active', 'staff_type': 'root', 'team_size': total_team_size, 'children': hierarchy, 'is_virtual_root': True }] # Calculate statistics total_staff = len(staff_list) return Response({ 'hierarchy': hierarchy, 'statistics': { 'total_staff': total_staff, 'top_managers': top_managers } }) class PatientViewSet(viewsets.ModelViewSet): """ ViewSet for Patient model. Permissions: - All authenticated users can view patients - PX Admins and Hospital Admins can manage patients """ queryset = Patient.objects.all() permission_classes = [IsAuthenticated] filterset_fields = ['status', 'gender', 'primary_hospital', 'city', 'primary_hospital__organization'] search_fields = ['mrn', 'national_id', 'first_name', 'last_name', 'phone', 'email'] ordering_fields = ['last_name', 'created_at'] ordering = ['last_name', 'first_name'] def get_serializer_class(self): """Use simplified serializer for list view""" if self.action == 'list': return PatientListSerializer return PatientSerializer def get_queryset(self): """Filter patients based on user role""" queryset = super().get_queryset().select_related('primary_hospital') user = self.request.user # PX Admins see all patients if user.is_px_admin(): return queryset # Hospital Admins see patients in their hospital if user.is_hospital_admin() and user.hospital: return queryset.filter(primary_hospital=user.hospital) # Others see patients in their hospital if user.hospital: return queryset.filter(primary_hospital=user.hospital) return queryset