from django.conf import settings from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator from django.db.models import Q from django.shortcuts import render, redirect, get_object_or_404 from django.contrib import messages from django.views.decorators.http import require_POST from django.views.decorators.csrf import csrf_exempt from apps.core.decorators import block_source_user, hospital_admin_required from .models import Department, Hospital, Organization, Patient, Staff, StaffSection, StaffSubsection from .forms import StaffForm @block_source_user @login_required def hospital_list(request): """Hospitals list view""" queryset = Hospital.objects.all() # Apply RBAC filters user = request.user if not user.is_px_admin() and user.hospital: queryset = queryset.filter(id=user.hospital.id) # Apply filters status_filter = request.GET.get("status") if status_filter: queryset = queryset.filter(status=status_filter) # Search search_query = request.GET.get("search") if search_query: queryset = queryset.filter( Q(name__icontains=search_query) | Q(name_ar__icontains=search_query) | Q(code__icontains=search_query) ) # Ordering queryset = queryset.order_by("name") # Pagination page_size = int(request.GET.get("page_size", 25)) paginator = Paginator(queryset, page_size) page_number = request.GET.get("page", 1) page_obj = paginator.get_page(page_number) context = { "page_obj": page_obj, "hospitals": page_obj.object_list, "filters": request.GET, } return render(request, "organizations/hospital_list.html", context) @block_source_user @login_required def department_list(request): """Departments list view""" queryset = Department.objects.select_related("hospital", "manager") # Apply RBAC filters user = request.user if not user.is_px_admin() and user.hospital: queryset = queryset.filter(hospital=user.hospital) # Apply filters hospital_filter = request.GET.get("hospital") if hospital_filter: queryset = queryset.filter(hospital_id=hospital_filter) status_filter = request.GET.get("status") if status_filter: queryset = queryset.filter(status=status_filter) # Search search_query = request.GET.get("search") if search_query: queryset = queryset.filter( Q(name__icontains=search_query) | Q(name_ar__icontains=search_query) | Q(code__icontains=search_query) ) # Ordering queryset = queryset.order_by("hospital", "name") # Pagination page_size = int(request.GET.get("page_size", 25)) paginator = Paginator(queryset, page_size) page_number = request.GET.get("page", 1) page_obj = paginator.get_page(page_number) # Get hospitals for filter hospitals = Hospital.objects.filter(status="active") if not user.is_px_admin() and user.hospital: hospitals = hospitals.filter(id=user.hospital.id) context = { "page_obj": page_obj, "departments": page_obj.object_list, "hospitals": hospitals, "filters": request.GET, } return render(request, "organizations/department_list.html", context) @block_source_user @login_required def staff_list(request): """Staff list view - filtered by tenant hospital""" queryset = Staff.objects.select_related("hospital", "department", "user") # Always filter by tenant_hospital (set by TenantMiddleware) # This handles both PX admins (with selected hospital) and regular users if request.tenant_hospital: queryset = queryset.filter(hospital=request.tenant_hospital) # Apply filters department_filter = request.GET.get("department") if department_filter: queryset = queryset.filter(department_id=department_filter) status_filter = request.GET.get("status") if status_filter: queryset = queryset.filter(status=status_filter) staff_type_filter = request.GET.get("staff_type") if staff_type_filter: queryset = queryset.filter(staff_type=staff_type_filter) # is_head filter is_head_filter = request.GET.get("is_head") if is_head_filter: if is_head_filter.lower() == "true": queryset = queryset.filter(is_head=True) elif is_head_filter.lower() == "false": queryset = queryset.filter(is_head=False) # Filter by department ForeignKey department_filter = request.GET.get("department") if department_filter: queryset = queryset.filter(department_id=department_filter) section_filter = request.GET.get("section") if section_filter: queryset = queryset.filter(section__icontains=section_filter) subsection_filter = request.GET.get("subsection") if subsection_filter: queryset = queryset.filter(subsection__icontains=subsection_filter) # Search search_query = request.GET.get("search") if search_query: queryset = queryset.filter( Q(first_name__icontains=search_query) | Q(last_name__icontains=search_query) | Q(employee_id__icontains=search_query) | Q(license_number__icontains=search_query) | Q(specialization__icontains=search_query) | Q(job_title__icontains=search_query) ) # Ordering queryset = queryset.order_by("last_name", "first_name") # Pagination page_size = int(request.GET.get("page_size", 25)) paginator = Paginator(queryset, page_size) page_number = request.GET.get("page", 1) page_obj = paginator.get_page(page_number) # Get departments for filter dropdown (from current hospital context) if request.tenant_hospital: departments = Department.objects.filter(hospital=request.tenant_hospital, status="active").order_by("name") else: departments = Department.objects.filter(status="active").order_by("name") # Get unique values for section/subsection filters base_queryset = Staff.objects.select_related("hospital", "department", "user") if request.tenant_hospital: base_queryset = base_queryset.filter(hospital=request.tenant_hospital) sections = base_queryset.exclude(section="").values_list("section", flat=True).distinct().order_by("section") subsections = ( base_queryset.exclude(subsection="").values_list("subsection", flat=True).distinct().order_by("subsection") ) context = { "staff": page_obj, "filters": request.GET, "departments": departments, "sections": sections, "subsections": subsections, } return render(request, "organizations/staff_list.html", context) @block_source_user @login_required def organization_list(request): """Organizations list view""" queryset = Organization.objects.all() # Apply RBAC filters user = request.user if not user.is_px_admin() and user.hospital and user.hospital.organization: queryset = queryset.filter(id=user.hospital.organization.id) # Apply filters status_filter = request.GET.get("status") if status_filter: queryset = queryset.filter(status=status_filter) city_filter = request.GET.get("city") if city_filter: queryset = queryset.filter(city__icontains=city_filter) # Search search_query = request.GET.get("search") if search_query: queryset = queryset.filter( Q(name__icontains=search_query) | Q(name_ar__icontains=search_query) | Q(code__icontains=search_query) | Q(license_number__icontains=search_query) ) # Ordering queryset = queryset.order_by("name") # Pagination page_size = int(request.GET.get("page_size", 25)) paginator = Paginator(queryset, page_size) page_number = request.GET.get("page", 1) page_obj = paginator.get_page(page_number) context = { "page_obj": page_obj, "organizations": page_obj.object_list, "filters": request.GET, } return render(request, "organizations/organization_list.html", context) @block_source_user @login_required def organization_detail(request, pk): """Organization detail view""" organization = Organization.objects.get(pk=pk) # Apply RBAC filters user = request.user if not user.is_px_admin(): if user.hospital and user.hospital.organization: if organization.id != user.hospital.organization.id: # User doesn't have access to this organization from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to view this organization") else: from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to view this organization") hospitals = organization.hospitals.all() context = { "organization": organization, "hospitals": hospitals, } return render(request, "organizations/organization_detail.html", context) @block_source_user @login_required def organization_create(request): """Create organization view""" # Only PX Admins can create organizations user = request.user if not user.is_px_admin(): from django.http import HttpResponseForbidden return HttpResponseForbidden("Only PX Admins can create organizations") if request.method == "POST": name = request.POST.get("name") name_ar = request.POST.get("name_ar") code = request.POST.get("code") address = request.POST.get("address", "") city = request.POST.get("city", "") phone = request.POST.get("phone", "") email = request.POST.get("email", "") website = request.POST.get("website", "") license_number = request.POST.get("license_number", "") status = request.POST.get("status", "active") if name and code: organization = Organization.objects.create( name=name, name_ar=name_ar or name, code=code, address=address, city=city, phone=phone, email=email, website=website, license_number=license_number, status=status, ) # Redirect to organization detail from django.shortcuts import redirect return redirect("organizations:organization_detail", pk=organization.id) return render(request, "organizations/organization_form.html") @block_source_user @login_required def patient_list(request): """Patients list view""" queryset = Patient.objects.select_related("primary_hospital") # Filter by current hospital context user = request.user if request.tenant_hospital: queryset = queryset.filter(primary_hospital=request.tenant_hospital) status_filter = request.GET.get("status") if status_filter: queryset = queryset.filter(status=status_filter) gender_filter = request.GET.get("gender") if gender_filter: queryset = queryset.filter(gender=gender_filter) nationality_filter = request.GET.get("nationality") if nationality_filter: queryset = queryset.filter(nationality=nationality_filter) # Search search_query = request.GET.get("search") if search_query: queryset = queryset.filter( Q(mrn__icontains=search_query) | Q(first_name__icontains=search_query) | Q(last_name__icontains=search_query) | Q(national_id__icontains=search_query) | Q(phone__icontains=search_query) ) # Ordering order_by = request.GET.get("order_by", "last_name") if order_by not in ["last_name", "-last_name", "-created_at", "created_at"]: order_by = "last_name" queryset = queryset.order_by(order_by, "first_name") # Stats (computed from filtered queryset) stats = { "total": queryset.count(), "active": queryset.filter(status="active").count(), "hospitals": queryset.values("primary_hospital").distinct().count(), "visits": 0, } # Export CSV if request.GET.get("export") == "csv": import csv from django.http import HttpResponse response = HttpResponse(content_type="text/csv") response["Content-Disposition"] = 'attachment; filename="patients.csv"' writer = csv.writer(response) writer.writerow( [ "MRN", "First Name", "Last Name", "National ID", "Gender", "Nationality", "Phone", "Email", "Hospital", "Status", "Created At", ] ) for p in queryset[:10000]: writer.writerow( [ p.mrn, p.first_name, p.last_name, p.national_id, p.get_gender_display(), p.nationality, p.phone, p.email, p.primary_hospital.name if p.primary_hospital else "", p.get_status_display(), p.created_at.strftime("%Y-%m-%d %H:%M"), ] ) return response # Pagination page_size = int(request.GET.get("page_size", 25)) paginator = Paginator(queryset, page_size) page_number = request.GET.get("page", 1) page_obj = paginator.get_page(page_number) # Get nationalities for filter nationalities = ( Patient.objects.exclude(nationality="").values_list("nationality", flat=True).order_by("nationality").distinct() ) context = { "page_obj": page_obj, "patients": page_obj.object_list, "nationalities": nationalities, "filters": request.GET, "stats": stats, } return render(request, "organizations/patient_list.html", context) @block_source_user @login_required def staff_detail(request, pk): """Staff detail view""" staff = get_object_or_404(Staff.objects.select_related("user", "hospital", "department"), pk=pk) # Apply RBAC filters user = request.user if not user.is_px_admin() and staff.hospital != user.hospital: from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to view this staff member") context = { "staff": staff, } return render(request, "organizations/staff_detail.html", context) @block_source_user @login_required def staff_create(request): """Create staff view""" # Only PX Admins and Hospital Admins can create staff user = request.user if not user.is_px_admin() and not user.is_hospital_admin(): from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to create staff") if request.method == "POST": form = StaffForm(request.POST, request=request) if form.is_valid(): staff = form.save(commit=False) # Handle user account creation create_user = request.POST.get("create_user") == "on" if create_user and not staff.user and staff.email: from .services import StaffService try: role = StaffService.get_staff_type_role(staff.staff_type) user_account, was_created, password = StaffService.create_user_for_staff( staff, role=role, request=request ) if was_created and password: try: StaffService.send_credentials_email(staff, password, request) messages.success(request, "Staff member created and credentials email sent successfully.") except Exception as e: messages.warning(request, f"Staff member created but email sending failed: {str(e)}") elif not was_created: messages.success(request, "Existing user account linked successfully.") except Exception as e: messages.error(request, f"Staff member created but user account creation failed: {str(e)}") staff.save() # Send invitation email if requested if create_user and staff.user and request.POST.get("send_email") != "false": from .services import StaffService try: password = StaffService.generate_password() staff.user.set_password(password) staff.user.save() StaffService.send_credentials_email(staff, password, request) messages.success(request, "Credentials email sent successfully.") except Exception as e: messages.warning(request, f"Email sending failed: {str(e)}") messages.success(request, "Staff member created successfully.") return redirect("organizations:staff_detail", pk=staff.id) else: form = StaffForm(request=request) context = { "form": form, } return render(request, "organizations/staff_form.html", context) @block_source_user @login_required def staff_update(request, pk): """Update staff view""" staff = get_object_or_404(Staff.objects.select_related("user"), pk=pk) # Apply RBAC filters user = request.user if not user.is_px_admin() and not user.is_hospital_admin(): from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to update this staff member") if user.is_hospital_admin() and staff.hospital != user.hospital: from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to update this staff member") if request.method == "POST": form = StaffForm(request.POST, instance=staff) if form.is_valid(): staff = form.save(commit=False) # Handle user account creation create_user = request.POST.get("create_user") == "on" if create_user and not staff.user and staff.email: from .services import StaffService try: role = StaffService.get_staff_type_role(staff.staff_type) user_account, was_created, password = StaffService.create_user_for_staff( staff, role=role, request=request ) if was_created and password: try: StaffService.send_credentials_email(staff, password, request) messages.success(request, "User account created and credentials email sent.") except Exception as e: messages.warning(request, f"User account created but email sending failed: {str(e)}") elif not was_created: messages.success(request, "Existing user account linked successfully.") except Exception as e: messages.error(request, f"User account creation failed: {str(e)}") staff.save() messages.success(request, "Staff member updated successfully.") return redirect("organizations:staff_detail", pk=staff.id) else: form = StaffForm(instance=staff, request=request) context = { "form": form, "staff": staff, } return render(request, "organizations/staff_form.html", context) @block_source_user @login_required def staff_hierarchy(request): """ Staff hierarchy tree view Shows organizational structure based on report_to relationships """ queryset = Staff.objects.select_related("hospital", "department", "report_to") # Apply RBAC filters user = request.user if not user.is_px_admin() and user.hospital: queryset = queryset.filter(hospital=user.hospital) # Apply filters hospital_filter = request.GET.get("hospital") if hospital_filter: queryset = queryset.filter(hospital_id=hospital_filter) department_filter = request.GET.get("department") if department_filter: queryset = queryset.filter(department_id=department_filter) # Search functionality search_query = request.GET.get("search") search_result = None if search_query: try: search_result = Staff.objects.get( Q(employee_id__iexact=search_query) | Q(first_name__icontains=search_query) | Q(last_name__icontains=search_query) ) # If search result exists and user has access, start hierarchy from that staff if search_result and (user.is_px_admin() or search_result.hospital == user.hospital): queryset = Staff.objects.filter(Q(id=search_result.id) | Q(hospital=search_result.hospital)) except Staff.DoesNotExist: pass # Build hierarchy structure def build_hierarchy(staff_list, parent=None, level=0): """Recursively build hierarchy tree""" result = [] for staff in staff_list: if staff.report_to == parent: node = { "staff": staff, "level": level, "direct_reports": build_hierarchy(staff_list, staff, level + 1), "has_children": bool(staff.direct_reports.exists()), } result.append(node) return result # Get all staff for the current filter all_staff = list(queryset) # If searching, build hierarchy from search result up if search_result: # Get all managers up the chain manager_chain = [] current = search_result.report_to while current: if current in all_staff: manager_chain.insert(0, current) current = current.report_to # Add search result to chain if search_result not in manager_chain: manager_chain.append(search_result) # Build hierarchy for managers and their reports hierarchy = build_hierarchy(all_staff, parent=None) # Find and highlight search result def find_and_mark(node, target_id, path=None): if path is None: path = [] if node["staff"].id == target_id: node["is_search_result"] = True node["search_path"] = path + [node["staff"].id] return node for child in node["direct_reports"]: result = find_and_mark(child, target_id, path + [node["staff"].id]) if result: return result return None search_result_node = None for root in hierarchy: result = find_and_mark(root, search_result.id) if result: search_result_node = result break else: # Build hierarchy starting from top-level (no report_to) hierarchy = build_hierarchy(all_staff, parent=None) # Get hospitals for filter hospitals = Hospital.objects.filter(status="active") if not user.is_px_admin() and user.hospital: hospitals = hospitals.filter(id=user.hospital.id) # Get departments for filter departments = Department.objects.filter(status="active") if not user.is_px_admin() and user.hospital: departments = departments.filter(hospital=user.hospital) # Calculate statistics total_staff = queryset.count() top_managers = len(hierarchy) context = { "hierarchy": hierarchy, "hospitals": hospitals, "departments": departments, "filters": request.GET, "total_staff": total_staff, "top_managers": top_managers, "search_result": search_result, } return render(request, "organizations/staff_hierarchy.html", context) @block_source_user @login_required def staff_hierarchy_d3(request): """ Staff hierarchy D3 visualization view Shows interactive organizational chart using D3.js """ # Get hospitals for filter (used by client-side filters) hospitals = Hospital.objects.filter(status="active") user = request.user if not user.is_px_admin() and user.hospital: hospitals = hospitals.filter(id=user.hospital.id) context = { "hospitals": hospitals, } return render(request, "organizations/staff_hierarchy_d3.html", context) # ==================== Department CRUD ==================== @block_source_user @login_required def department_create(request): """Create department view""" user = request.user if not user.is_px_admin() and not user.is_hospital_admin(): from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to create departments") if request.method == "POST": name = request.POST.get("name") name_ar = request.POST.get("name_ar", "") code = request.POST.get("code") hospital_id = request.POST.get("hospital") status = request.POST.get("status", "active") phone = request.POST.get("phone", "") email = request.POST.get("email", "") location = request.POST.get("location", "") if name and code and hospital_id: # RBAC: Non-admins can only create in their hospital if not user.is_px_admin(): if str(user.hospital_id) != hospital_id: from django.http import HttpResponseForbidden return HttpResponseForbidden("You can only create departments in your hospital") department = Department.objects.create( name=name, name_ar=name_ar or name, code=code, hospital_id=hospital_id, status=status, phone=phone, email=email, location=location, ) messages.success(request, "Department created successfully.") return redirect("organizations:department_list") # Get hospitals for dropdown hospitals = Hospital.objects.filter(status="active") if not user.is_px_admin() and user.hospital: hospitals = hospitals.filter(id=user.hospital.id) context = { "hospitals": hospitals, } return render(request, "organizations/department_form.html", context) @block_source_user @login_required def department_update(request, pk): """Update department view""" department = get_object_or_404(Department, pk=pk) user = request.user if not user.is_px_admin() and not user.is_hospital_admin(): from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to update departments") if not user.is_px_admin() and department.hospital != user.hospital: from django.http import HttpResponseForbidden return HttpResponseForbidden("You can only update departments in your hospital") if request.method == "POST": department.name = request.POST.get("name", department.name) department.name_ar = request.POST.get("name_ar", "") department.code = request.POST.get("code", department.code) department.status = request.POST.get("status", department.status) department.phone = request.POST.get("phone", "") department.email = request.POST.get("email", "") department.location = request.POST.get("location", "") department.save() messages.success(request, "Department updated successfully.") return redirect("organizations:department_list") context = { "department": department, } return render(request, "organizations/department_form.html", context) @block_source_user @login_required def department_delete(request, pk): """Delete department view""" department = get_object_or_404(Department, pk=pk) user = request.user if not user.is_px_admin() and not user.is_hospital_admin(): from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to delete departments") if not user.is_px_admin() and department.hospital != user.hospital: from django.http import HttpResponseForbidden return HttpResponseForbidden("You can only delete departments in your hospital") if request.method == "POST": # Check for linked staff staff_count = department.staff.count() if staff_count > 0: messages.error(request, f"Cannot delete department. {staff_count} staff members are assigned to it.") return redirect("organizations:department_list") department.delete() messages.success(request, "Department deleted successfully.") return redirect("organizations:department_list") context = { "department": department, } return render(request, "organizations/department_confirm_delete.html", context) # ==================== Staff Section CRUD ==================== @block_source_user @login_required def section_list(request): """Sections list view""" queryset = StaffSection.objects.select_related("department", "department__hospital", "head") # Apply RBAC filters user = request.user if not user.is_px_admin() and user.hospital: queryset = queryset.filter(department__hospital=user.hospital) # Apply filters department_filter = request.GET.get("department") if department_filter: queryset = queryset.filter(department_id=department_filter) status_filter = request.GET.get("status") if status_filter: queryset = queryset.filter(status=status_filter) # Search search_query = request.GET.get("search") if search_query: queryset = queryset.filter( Q(name__icontains=search_query) | Q(name_ar__icontains=search_query) | Q(code__icontains=search_query) ) # Ordering queryset = queryset.order_by("department__name", "name") # Pagination page_size = int(request.GET.get("page_size", 25)) paginator = Paginator(queryset, page_size) page_number = request.GET.get("page", 1) page_obj = paginator.get_page(page_number) # Get departments for filter departments = Department.objects.filter(status="active") if not user.is_px_admin() and user.hospital: departments = departments.filter(hospital=user.hospital) context = { "page_obj": page_obj, "sections": page_obj.object_list, "departments": departments, "filters": request.GET, } return render(request, "organizations/section_list.html", context) @block_source_user @login_required def section_create(request): """Create section view""" user = request.user if not user.is_px_admin() and not user.is_hospital_admin(): from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to create sections") if request.method == "POST": name = request.POST.get("name") name_ar = request.POST.get("name_ar", "") code = request.POST.get("code", "") department_id = request.POST.get("department") status = request.POST.get("status", "active") head_id = request.POST.get("head") if name and department_id: department = get_object_or_404(Department, pk=department_id) # RBAC check if not user.is_px_admin() and department.hospital != user.hospital: from django.http import HttpResponseForbidden return HttpResponseForbidden("You can only create sections in your hospital") section = StaffSection.objects.create( name=name, name_ar=name_ar or name, code=code, department=department, status=status, head_id=head_id if head_id else None, ) messages.success(request, "Section created successfully.") return redirect("organizations:section_list") # Get departments for dropdown departments = Department.objects.filter(status="active") if not user.is_px_admin() and user.hospital: departments = departments.filter(hospital=user.hospital) context = { "departments": departments, } return render(request, "organizations/section_form.html", context) @block_source_user @login_required def section_update(request, pk): """Update section view""" section = get_object_or_404(StaffSection, pk=pk) user = request.user if not user.is_px_admin() and not user.is_hospital_admin(): from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to update sections") if not user.is_px_admin() and section.department.hospital != user.hospital: from django.http import HttpResponseForbidden return HttpResponseForbidden("You can only update sections in your hospital") if request.method == "POST": section.name = request.POST.get("name", section.name) section.name_ar = request.POST.get("name_ar", "") section.code = request.POST.get("code", "") section.status = request.POST.get("status", section.status) head_id = request.POST.get("head") section.head_id = head_id if head_id else None section.save() messages.success(request, "Section updated successfully.") return redirect("organizations:section_list") # Get departments for dropdown departments = Department.objects.filter(status="active") if not user.is_px_admin() and user.hospital: departments = departments.filter(hospital=user.hospital) context = { "section": section, "departments": departments, } return render(request, "organizations/section_form.html", context) @block_source_user @login_required def section_delete(request, pk): """Delete section view""" section = get_object_or_404(StaffSection, pk=pk) user = request.user if not user.is_px_admin() and not user.is_hospital_admin(): from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to delete sections") if not user.is_px_admin() and section.department.hospital != user.hospital: from django.http import HttpResponseForbidden return HttpResponseForbidden("You can only delete sections in your hospital") if request.method == "POST": subsection_count = section.subsections.count() if subsection_count > 0: messages.error(request, f"Cannot delete section. {subsection_count} subsections are linked to it.") return redirect("organizations:section_list") section.delete() messages.success(request, "Section deleted successfully.") return redirect("organizations:section_list") context = { "section": section, } return render(request, "organizations/section_confirm_delete.html", context) # ==================== Staff Subsection CRUD ==================== @block_source_user @login_required def subsection_list(request): """Subsections list view""" queryset = StaffSubsection.objects.select_related( "section", "section__department", "section__department__hospital", "head" ) # Apply RBAC filters user = request.user if not user.is_px_admin() and user.hospital: queryset = queryset.filter(section__department__hospital=user.hospital) # Apply filters section_filter = request.GET.get("section") if section_filter: queryset = queryset.filter(section_id=section_filter) department_filter = request.GET.get("department") if department_filter: queryset = queryset.filter(section__department_id=department_filter) status_filter = request.GET.get("status") if status_filter: queryset = queryset.filter(status=status_filter) # Search search_query = request.GET.get("search") if search_query: queryset = queryset.filter( Q(name__icontains=search_query) | Q(name_ar__icontains=search_query) | Q(code__icontains=search_query) ) # Ordering queryset = queryset.order_by("section__name", "name") # Pagination page_size = int(request.GET.get("page_size", 25)) paginator = Paginator(queryset, page_size) page_number = request.GET.get("page", 1) page_obj = paginator.get_page(page_number) # Get sections and departments for filter departments = Department.objects.filter(status="active") sections = StaffSection.objects.filter(status="active") if not user.is_px_admin() and user.hospital: departments = departments.filter(hospital=user.hospital) sections = sections.filter(department__hospital=user.hospital) context = { "page_obj": page_obj, "subsections": page_obj.object_list, "sections": sections, "departments": departments, "filters": request.GET, } return render(request, "organizations/subsection_list.html", context) @block_source_user @login_required def subsection_create(request): """Create subsection view""" user = request.user if not user.is_px_admin() and not user.is_hospital_admin(): from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to create subsections") if request.method == "POST": name = request.POST.get("name") name_ar = request.POST.get("name_ar", "") code = request.POST.get("code", "") section_id = request.POST.get("section") status = request.POST.get("status", "active") head_id = request.POST.get("head") if name and section_id: section = get_object_or_404(StaffSection, pk=section_id) # RBAC check if not user.is_px_admin() and section.department.hospital != user.hospital: from django.http import HttpResponseForbidden return HttpResponseForbidden("You can only create subsections in your hospital") subsection = StaffSubsection.objects.create( name=name, name_ar=name_ar or name, code=code, section=section, status=status, head_id=head_id if head_id else None, ) messages.success(request, "Subsection created successfully.") return redirect("organizations:subsection_list") # Get sections for dropdown sections = StaffSection.objects.filter(status="active").select_related("department") if not user.is_px_admin() and user.hospital: sections = sections.filter(department__hospital=user.hospital) context = { "sections": sections, } return render(request, "organizations/subsection_form.html", context) @block_source_user @login_required def subsection_update(request, pk): """Update subsection view""" subsection = get_object_or_404(StaffSubsection, pk=pk) user = request.user if not user.is_px_admin() and not user.is_hospital_admin(): from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to update subsections") if not user.is_px_admin() and subsection.section.department.hospital != user.hospital: from django.http import HttpResponseForbidden return HttpResponseForbidden("You can only update subsections in your hospital") if request.method == "POST": subsection.name = request.POST.get("name", subsection.name) subsection.name_ar = request.POST.get("name_ar", "") subsection.code = request.POST.get("code", "") subsection.status = request.POST.get("status", subsection.status) head_id = request.POST.get("head") subsection.head_id = head_id if head_id else None subsection.save() messages.success(request, "Subsection updated successfully.") return redirect("organizations:subsection_list") # Get sections for dropdown sections = StaffSection.objects.filter(status="active").select_related("department") if not user.is_px_admin() and user.hospital: sections = sections.filter(department__hospital=user.hospital) context = { "subsection": subsection, "sections": sections, } return render(request, "organizations/subsection_form.html", context) @block_source_user @login_required def subsection_delete(request, pk): """Delete subsection view""" subsection = get_object_or_404(StaffSubsection, pk=pk) user = request.user if not user.is_px_admin() and not user.is_hospital_admin(): from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to delete subsections") if not user.is_px_admin() and subsection.section.department.hospital != user.hospital: from django.http import HttpResponseForbidden return HttpResponseForbidden("You can only delete subsections in your hospital") if request.method == "POST": subsection.delete() messages.success(request, "Subsection deleted successfully.") return redirect("organizations:subsection_list") context = { "subsection": subsection, } return render(request, "organizations/subsection_confirm_delete.html", context) @block_source_user @login_required def patient_detail(request, pk): """Patient detail view""" patient = get_object_or_404(Patient.objects.select_related("primary_hospital"), pk=pk) # Apply RBAC filters user = request.user if not user.is_px_admin() and patient.primary_hospital != user.hospital: from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to view this patient") from apps.integrations.models import HISPatientVisit from apps.surveys.models import SurveyInstance tab = request.GET.get("tab", "visits") his_visits = ( HISPatientVisit.objects.filter(patient=patient) .select_related("hospital", "survey_instance", "primary_doctor_fk", "consultant_fk") .order_by("-admit_date")[:20] ) surveys = ( SurveyInstance.objects.filter(patient=patient) .select_related("survey_template") .prefetch_related("responses") .order_by("-created_at")[:10] ) complaints = patient.complaints.select_related("hospital", "department").order_by("-created_at")[:10] inquiries = patient.inquiries.select_related("hospital", "department").order_by("-created_at")[:10] stats = { "visits": HISPatientVisit.objects.filter(patient=patient).count(), "surveys": SurveyInstance.objects.filter(patient=patient).count(), "complaints": patient.complaints.count(), "inquiries": patient.inquiries.count(), } context = { "patient": patient, "tab": tab, "his_visits": his_visits, "surveys": surveys, "complaints": complaints, "inquiries": inquiries, "stats": stats, } return render(request, "organizations/patient_detail.html", context) @block_source_user @login_required def patient_visit_journey(request, patient_pk, visit_pk): """Patient visit journey timeline view""" from apps.integrations.models import HISPatientVisit patient = get_object_or_404(Patient.objects.select_related("primary_hospital"), pk=patient_pk) visit = get_object_or_404( HISPatientVisit.objects.prefetch_related("visit_events").select_related( "hospital", "survey_instance", "primary_doctor_fk", "consultant_fk" ), pk=visit_pk, patient=patient, ) user = request.user if not user.is_px_admin() and patient.primary_hospital != user.hospital: from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to view this visit") timeline = list(visit.visit_events.all()) context = { "patient": patient, "visit": visit, "timeline": timeline, } return render(request, "organizations/patient_visit_journey.html", context) @block_source_user @login_required def patient_create(request): """Create patient view""" user = request.user # Only PX Admins and Hospital Admins can create patients if not user.is_px_admin() and not user.is_hospital_admin(): from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to create patients") if request.method == "POST": form = PatientForm(user, request.POST) if form.is_valid(): patient = form.save() messages.success(request, f"Patient {patient.get_full_name()} created successfully.") return redirect("organizations:patient_detail", pk=patient.pk) else: form = PatientForm(user) context = { "form": form, "title": _("Create Patient"), } return render(request, "organizations/patient_form.html", context) @block_source_user @login_required def patient_update(request, pk): """Update patient view""" patient = get_object_or_404(Patient, pk=pk) user = request.user # Apply RBAC filters if not user.is_px_admin() and patient.primary_hospital != user.hospital: from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to edit this patient") # Only PX Admins and Hospital Admins can update patients if not user.is_px_admin() and not user.is_hospital_admin(): from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to edit patients") if request.method == "POST": form = PatientForm(user, request.POST, instance=patient) if form.is_valid(): patient = form.save() messages.success(request, f"Patient {patient.get_full_name()} updated successfully.") return redirect("organizations:patient_detail", pk=patient.pk) else: form = PatientForm(user, instance=patient) context = { "form": form, "patient": patient, "title": _("Edit Patient"), } return render(request, "organizations/patient_form.html", context) @block_source_user @login_required def patient_delete(request, pk): """Delete patient view""" patient = get_object_or_404(Patient, pk=pk) user = request.user # Apply RBAC filters if not user.is_px_admin() and patient.primary_hospital != user.hospital: from django.http import HttpResponseForbidden return HttpResponseForbidden("You don't have permission to delete this patient") # Only PX Admins can delete patients if not user.is_px_admin(): from django.http import HttpResponseForbidden return HttpResponseForbidden("Only PX Admins can delete patients") if request.method == "POST": patient_name = patient.get_full_name() patient.delete() messages.success(request, f"Patient {patient_name} deleted successfully.") return redirect("organizations:patient_list") context = { "patient": patient, } return render(request, "organizations/patient_confirm_delete.html", context) @csrf_exempt @login_required @require_POST def search_his_patient(request): """Search HIS by SSN or MobileNo, return patient demographics as JSON.""" import json from django.http import JsonResponse try: data = json.loads(request.body or b"{}") if not isinstance(data, dict): data = {} except (json.JSONDecodeError, TypeError): data = {} ssn = (data.get("ssn") or "").strip() mobile_no = (data.get("mobile_no") or "").strip() if not ssn and not mobile_no: return JsonResponse({"error": "Provide SSN or Mobile number"}, status=400) from apps.integrations.models import IntegrationConfig from apps.integrations.services.his_client import HISClient from django.conf import settings config = IntegrationConfig.objects.filter(source_system__in=["his", "other"]).first() if not config: return JsonResponse({"error": "HIS integration not configured"}, status=400) if settings.DEBUG: config.api_url = request.build_absolute_uri("/api/integrations/test-his-data/") client = HISClient(config) patients = client.fetch_patient_by_identifier(ssn=ssn or None, mobile_no=mobile_no or None) if patients is None: return JsonResponse({"error": "Failed to connect to HIS"}, status=500) if not patients: return JsonResponse({"patients": [], "total": 0}) result = [] for p in patients: result.append( { "patient_id": p.get("PatientID"), "admission_id": p.get("AdmissionID"), "name": p.get("PatientName", ""), "ssn": p.get("SSN", ""), "mobile_no": p.get("MobileNo", ""), "gender": p.get("Gender", ""), "nationality": p.get("PatientNationality", ""), "dob": p.get("DOB", ""), "patient_type": p.get("PatientType", ""), "hospital_name": p.get("HospitalName", ""), "hospital_id": p.get("HospitalID", ""), "admit_date": p.get("AdmitDate", ""), "discharge_date": p.get("DischargeDate", ""), "reg_code": p.get("RegCode", ""), "raw": p, } ) return JsonResponse({"patients": result, "total": len(result)}) @csrf_exempt @login_required @require_POST def save_his_patient(request): """Save a patient from HIS data locally.""" import json from django.http import JsonResponse try: data = json.loads(request.body or b"{}") if not isinstance(data, dict): data = {} except (json.JSONDecodeError, TypeError): data = {} patient_data = data.get("patient_data") if not patient_data: return JsonResponse({"error": "No patient data provided"}, status=400) from apps.integrations.models import IntegrationConfig from apps.integrations.services.his_adapter import HISAdapter config = IntegrationConfig.objects.filter(source_system__in=["his", "other"]).first() if not config: return JsonResponse({"error": "HIS integration not configured"}, status=400) try: hospital = HISAdapter.get_or_create_hospital(patient_data) patient = HISAdapter.get_or_create_patient(patient_data, hospital) visit = HISAdapter.save_patient_visit( patient=patient, hospital=hospital, patient_data=patient_data, visit_timeline=[], is_visit_complete=False, ) return JsonResponse( { "success": True, "patient_id": str(patient.id), "patient_name": patient.get_full_name(), "visit_id": str(visit.id), "admission_id": visit.admission_id, } ) except Exception as e: return JsonResponse({"error": str(e)}, status=500) @block_source_user @login_required @require_POST def send_complaint_link(request, pk): """Create a complaint session and send SMS link to the patient.""" from django.http import JsonResponse from apps.complaints.models import PatientComplaintSession from apps.notifications.services import NotificationService patient = get_object_or_404(Patient.objects.select_related("primary_hospital"), pk=pk) if not patient.phone: return JsonResponse({"error": "Patient has no phone number"}, status=400) session = PatientComplaintSession.objects.create( patient=patient, created_by=request.user, ) base_url = getattr(settings, "SURVEY_BASE_URL", "") link = f"{base_url}/complaints/patient/{session.token}/" message = ( f"Dear {patient.first_name}, we value your feedback. " f"If you have any concerns about your recent visit, please submit them here: {link}" ) NotificationService.send_sms(phone=patient.phone, message=message) return JsonResponse( { "success": True, "token": session.token, "link": link, "phone": patient.phone, } )