""" Medical views for the Tenhal Multidisciplinary Healthcare Platform. This module contains views for medical documentation including: - Medical consultations (MD-F-1) - Follow-up visits (MD-F-2) - Medication management - Lab/radiology integration """ from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Q from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django.views.generic import ListView, DetailView, CreateView, UpdateView, View from django.urls import reverse_lazy from django.contrib import messages from core.mixins import ( SignedDocumentEditPreventionMixin, TenantFilterMixin, RolePermissionMixin, AuditLogMixin, HTMXResponseMixin, SuccessMessageMixin, PaginationMixin, ConsentRequiredMixin, SignedDocumentEditPreventionMixin, ) from core.models import User, Patient from appointments.models import Appointment from .models import MedicalConsultation, MedicalFollowUp, MedicationPlan, ConsultationResponse, ConsultationFeedback from .forms import ( MedicalConsultationForm, MedicalFollowUpForm, MedicationPlanFormSet, ConsultationResponseForm, ConsultationFeedbackForm ) # Sign Views class MedicalConsultationSignView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View): """Sign a medical consultation.""" allowed_roles = [User.Role.ADMIN, User.Role.DOCTOR] def post(self, request, pk): consultation = get_object_or_404(MedicalConsultation, pk=pk, tenant=request.user.tenant) if consultation.signed_by: messages.warning(request, _("This consultation has already been signed.")) return HttpResponseRedirect(reverse_lazy('medical:consultation_detail', kwargs={'pk': pk})) if consultation.provider != request.user and request.user.role != User.Role.ADMIN: messages.error(request, _("Only the consultation provider or an administrator can sign this consultation.")) return HttpResponseRedirect(reverse_lazy('medical:consultation_detail', kwargs={'pk': pk})) consultation.signed_by = request.user consultation.signed_at = timezone.now() consultation.save() messages.success(request, _("Consultation signed successfully!")) return HttpResponseRedirect(reverse_lazy('medical:consultation_detail', kwargs={'pk': pk})) class MedicalFollowUpSignView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View): """Sign a medical follow-up.""" allowed_roles = [User.Role.ADMIN, User.Role.DOCTOR] def post(self, request, pk): followup = get_object_or_404(MedicalFollowUp, pk=pk, tenant=request.user.tenant) if followup.signed_by: messages.warning(request, _("This follow-up has already been signed.")) return HttpResponseRedirect(reverse_lazy('medical:followup_detail', kwargs={'pk': pk})) if followup.provider != request.user and request.user.role != User.Role.ADMIN: messages.error(request, _("Only the follow-up provider or an administrator can sign this follow-up.")) return HttpResponseRedirect(reverse_lazy('medical:followup_detail', kwargs={'pk': pk})) followup.signed_by = request.user followup.signed_at = timezone.now() followup.save() messages.success(request, _("Follow-up signed successfully!")) return HttpResponseRedirect(reverse_lazy('medical:followup_detail', kwargs={'pk': pk})) class MedicalConsultationListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin, HTMXResponseMixin, ListView): """ Medical consultation list view (MD-F-1). Features: - Filter by patient, provider, date range - Search by patient name/MRN - Role-based filtering (doctors see their consultations) """ model = MedicalConsultation template_name = 'medical/consultation_list.html' htmx_template_name = 'medical/partials/consultation_list_partial.html' context_object_name = 'consultations' paginate_by = 25 def get_queryset(self): """Get filtered queryset.""" queryset = super().get_queryset() user = self.request.user # Role-based filtering if user.role == User.Role.DOCTOR: queryset = queryset.filter(provider__user=user) # Apply search search_query = self.request.GET.get('search', '').strip() if search_query: queryset = queryset.filter( Q(patient__first_name_en__icontains=search_query) | Q(patient__last_name_en__icontains=search_query) | Q(patient__mrn__icontains=search_query) ) # Apply filters patient_id = self.request.GET.get('patient') if patient_id: queryset = queryset.filter(patient_id=patient_id) provider_id = self.request.GET.get('provider') if provider_id: queryset = queryset.filter(provider_id=provider_id) date_from = self.request.GET.get('date_from') if date_from: queryset = queryset.filter(consultation_date__gte=date_from) date_to = self.request.GET.get('date_to') if date_to: queryset = queryset.filter(consultation_date__lte=date_to) return queryset.select_related('patient', 'provider', 'appointment', 'signed_by').order_by('-consultation_date') def get_context_data(self, **kwargs): """Add filter options and unsigned counts.""" context = super().get_context_data(**kwargs) user = self.request.user # Add providers for filter context['providers'] = User.objects.filter( tenant=self.request.user.tenant, role=User.Role.DOCTOR ) # Get unsigned consultations unsigned_query = MedicalConsultation.objects.filter(tenant=user.tenant, signed_by__isnull=True) if user.role == User.Role.DOCTOR: unsigned_query = unsigned_query.filter(provider=user) context['unsigned_count'] = unsigned_query.count() context['unsigned_items'] = unsigned_query.select_related('patient', 'provider').order_by('-consultation_date')[:10] # Add current filters context['current_filters'] = { 'search': self.request.GET.get('search', ''), 'patient': self.request.GET.get('patient', ''), 'provider': self.request.GET.get('provider', ''), 'date_from': self.request.GET.get('date_from', ''), 'date_to': self.request.GET.get('date_to', ''), } return context class MedicalConsultationDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView): """ Medical consultation detail view (MD-F-1). Features: - Full consultation details with all sections - Medication plans - Lab/radiology orders - Follow-up consultations - Behavioral symptoms checklist """ model = MedicalConsultation template_name = 'medical/consultation_detail.html' context_object_name = 'consultation' def get_context_data(self, **kwargs): """Add related data.""" context = super().get_context_data(**kwargs) consultation = self.object # Get medication plans (medications is a JSONField, not a related manager) context['medications'] = consultation.medications if consultation.medications else [] # Get follow-ups context['followups'] = MedicalFollowUp.objects.filter( patient=consultation.patient, previous_consultation=consultation ).order_by('-followup_date') # Get responses from other disciplines context['responses'] = consultation.responses.select_related('responder').order_by('-response_date') # Get feedback context['feedback'] = consultation.feedback.select_related('submitted_by').order_by('-feedback_date') # Calculate average feedback rating feedback_list = list(context['feedback']) if feedback_list: avg_ratings = [f.average_rating for f in feedback_list if f.average_rating is not None] context['average_feedback_rating'] = sum(avg_ratings) / len(avg_ratings) if avg_ratings else None else: context['average_feedback_rating'] = None # Get lab/radiology orders (from integrations) try: from integrations.models import ExternalOrder context['lab_orders'] = ExternalOrder.objects.filter( patient=consultation.patient, order_type='LAB', created_at__gte=consultation.consultation_date ).order_by('-created_at')[:5] context['radiology_orders'] = ExternalOrder.objects.filter( patient=consultation.patient, order_type='RADIOLOGY', created_at__gte=consultation.consultation_date ).order_by('-created_at')[:5] except: context['lab_orders'] = [] context['radiology_orders'] = [] return context class MedicalConsultationCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin, AuditLogMixin, SuccessMessageMixin, CreateView): """ Medical consultation creation view (MD-F-1). Features: - Complete consultation form with all sections - Chief complaint, present history, past history - Vaccination, family history, social history - Pregnancy/neonatal/developmental history - Behavioral symptoms checklist - Physical examination - Summary and recommendations - Medication formset - Consent verification enforced before creation """ model = MedicalConsultation form_class = MedicalConsultationForm template_name = 'medical/consultation_form.html' success_message = _("Medical consultation recorded successfully!") allowed_roles = [User.Role.ADMIN, User.Role.DOCTOR] # Consent enforcement consent_service_type = 'MEDICAL' consent_error_message = _( "Patient must sign general treatment consent before medical consultation can be documented." ) def get_success_url(self): """Redirect to consultation detail.""" return reverse_lazy('medical:consultation_detail', kwargs={'pk': self.object.pk}) def get_patient(self): """Get patient for consent verification.""" patient_id = self.request.GET.get('patient') appointment_id = self.request.GET.get('appointment_id') if patient_id: return Patient.objects.get( pk=patient_id, tenant=self.request.user.tenant ) elif appointment_id: appointment = Appointment.objects.get( pk=appointment_id, tenant=self.request.user.tenant ) return appointment.patient return None def form_valid(self, form): """Set tenant and provider, handle medications JSON.""" # Set tenant form.instance.tenant = self.request.user.tenant # Set provider form.instance.provider = self.request.user # Set consultation date if not provided if not form.instance.consultation_date: form.instance.consultation_date = timezone.now().date() # Handle medications JSON field import json medications_json = self.request.POST.get('medications', '[]') try: form.instance.medications = json.loads(medications_json) except json.JSONDecodeError: form.instance.medications = [] # Save consultation self.object = form.save() # Update appointment if linked if self.object.appointment: # Appointment remains IN_PROGRESS until completed pass messages.success(self.request, self.success_message) return redirect(self.get_success_url()) def get_context_data(self, **kwargs): """Add form title, patient/appointment info, and medication formset.""" context = super().get_context_data(**kwargs) context['form_title'] = _('Medical Consultation (MD-F-1)') context['submit_text'] = _('Save Consultation') # Add medication formset if not already in context if 'medication_formset' not in context: context['medication_formset'] = MedicationPlanFormSet() # Get patient if provided patient_id = self.request.GET.get('patient') if patient_id: try: context['patient'] = Patient.objects.get( pk=patient_id, tenant=self.request.user.tenant ) except Patient.DoesNotExist: pass # Get appointment if provided appointment_id = self.request.GET.get('appointment_id') if appointment_id: try: context['appointment'] = Appointment.objects.get( pk=appointment_id, tenant=self.request.user.tenant ) context['patient'] = context['appointment'].patient except Appointment.DoesNotExist: pass return context class MedicalConsultationUpdateView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, AuditLogMixin, SuccessMessageMixin, UpdateView): """ Medical consultation update view (MD-F-1). Features: - Update consultation details - Medication formset - Version history - Audit trail """ model = MedicalConsultation form_class = MedicalConsultationForm template_name = 'medical/consultation_form.html' success_message = _("Medical consultation updated successfully!") allowed_roles = [User.Role.ADMIN, User.Role.DOCTOR] def get_success_url(self): """Redirect to consultation detail.""" return reverse_lazy('medical:consultation_detail', kwargs={'pk': self.object.pk}) def form_valid(self, form): """Save form and handle medications JSON.""" # Handle medications JSON field import json medications_json = self.request.POST.get('medications', '[]') try: form.instance.medications = json.loads(medications_json) except json.JSONDecodeError: form.instance.medications = [] # Save consultation self.object = form.save() messages.success(self.request, self.success_message) return redirect(self.get_success_url()) def get_context_data(self, **kwargs): """Add form title, medication formset, and history.""" context = super().get_context_data(**kwargs) context['form_title'] = _('Update Medical Consultation - %(mrn)s') % {'mrn': self.object.patient.mrn} context['submit_text'] = _('Update Consultation') context['patient'] = self.object.patient # Add medication formset if not already in context if 'medication_formset' not in context: context['medication_formset'] = MedicationPlanFormSet(instance=self.object) # Add version history if available if hasattr(self.object, 'history'): context['history'] = self.object.history.all()[:10] return context class MedicalFollowUpListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin, HTMXResponseMixin, ListView): """ Medical follow-up list view (MD-F-2). Features: - Filter by patient, provider, date range - Search by patient name/MRN - Link to previous consultations """ model = MedicalFollowUp template_name = 'medical/followup_list.html' htmx_template_name = 'medical/partials/followup_list_partial.html' context_object_name = 'followups' paginate_by = 25 def get_queryset(self): """Get filtered queryset.""" queryset = super().get_queryset() user = self.request.user # Role-based filtering if user.role == User.Role.DOCTOR: queryset = queryset.filter(provider__user=user) # Apply search search_query = self.request.GET.get('search', '').strip() if search_query: queryset = queryset.filter( Q(patient__first_name_en__icontains=search_query) | Q(patient__last_name_en__icontains=search_query) | Q(patient__mrn__icontains=search_query) ) # Apply filters patient_id = self.request.GET.get('patient') if patient_id: queryset = queryset.filter(patient_id=patient_id) date_from = self.request.GET.get('date_from') if date_from: queryset = queryset.filter(followup_date__gte=date_from) date_to = self.request.GET.get('date_to') if date_to: queryset = queryset.filter(followup_date__lte=date_to) return queryset.select_related( 'patient', 'provider', 'appointment', 'previous_consultation', 'signed_by' ).order_by('-followup_date') def get_context_data(self, **kwargs): """Add unsigned follow-ups count.""" context = super().get_context_data(**kwargs) user = self.request.user unsigned_query = MedicalFollowUp.objects.filter(tenant=user.tenant, signed_by__isnull=True) if user.role == User.Role.DOCTOR: unsigned_query = unsigned_query.filter(provider=user) context['unsigned_count'] = unsigned_query.count() context['unsigned_items'] = unsigned_query.select_related('patient', 'provider').order_by('-followup_date')[:10] return context class MedicalFollowUpDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView): """ Medical follow-up detail view (MD-F-2). Features: - Full follow-up details - Previous consultation reference - Previous complaints status - New complaints - Vital signs link - Assessment and recommendations - Family satisfaction score - Medication snapshot """ model = MedicalFollowUp template_name = 'medical/followup_detail.html' context_object_name = 'followup' def get_context_data(self, **kwargs): """Add related data.""" context = super().get_context_data(**kwargs) followup = self.object # Get previous consultation if followup.previous_consultation: context['previous_consultation'] = followup.previous_consultation # medications is a JSONField, not a related manager context['previous_medications'] = followup.previous_consultation.medications if followup.previous_consultation.medications else [] # Get vital signs from nursing from nursing.models import NursingEncounter if followup.appointment: context['vitals'] = NursingEncounter.objects.filter( appointment=followup.appointment ).first() return context class MedicalFollowUpCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin, AuditLogMixin, SuccessMessageMixin, CreateView): """ Medical follow-up creation view (MD-F-2). Features: - Link to previous consultation - Previous complaints with status (Resolved/Static/Worse) - New complaints - Vital signs reference - Assessment and recommendations - Family satisfaction (0/50/100) - Medication table snapshot - Consent verification enforced before creation """ model = MedicalFollowUp form_class = MedicalFollowUpForm template_name = 'medical/followup_form.html' success_message = _("Medical follow-up recorded successfully!") allowed_roles = [User.Role.ADMIN, User.Role.DOCTOR] # Consent enforcement consent_service_type = 'MEDICAL' consent_error_message = _( "Patient must sign general treatment consent before medical follow-up can be documented." ) def get_success_url(self): """Redirect to follow-up detail.""" return reverse_lazy('medical:followup_detail', kwargs={'pk': self.object.pk}) def get_patient(self): """Get patient for consent verification.""" patient_id = self.request.GET.get('patient') appointment_id = self.request.GET.get('appointment_id') if patient_id: return Patient.objects.get( pk=patient_id, tenant=self.request.user.tenant ) elif appointment_id: appointment = Appointment.objects.get( pk=appointment_id, tenant=self.request.user.tenant ) return appointment.patient return None def get_initial(self): """Pre-populate form fields from appointment.""" initial = super().get_initial() appointment_id = self.request.GET.get('appointment_id') if appointment_id: try: appointment = Appointment.objects.get(pk=appointment_id, tenant=self.request.user.tenant) initial['patient'] = appointment.patient.pk initial['appointment'] = appointment.pk initial['followup_date'] = appointment.appointment_date initial['provider'] = self.request.user.pk except Appointment.DoesNotExist: pass patient_id = self.request.GET.get('patient') if patient_id and not appointment_id: initial['patient'] = patient_id initial['provider'] = self.request.user.pk initial['followup_date'] = timezone.now().date() return initial def form_valid(self, form): """Set tenant and provider.""" # Set tenant form.instance.tenant = self.request.user.tenant # Set provider if not set if not form.instance.provider: form.instance.provider = self.request.user # Set follow-up date if not provided if not form.instance.followup_date: form.instance.followup_date = timezone.now().date() # Save follow-up response = super().form_valid(form) return response def get_context_data(self, **kwargs): """Add form title and previous consultation info.""" context = super().get_context_data(**kwargs) context['form_title'] = _('Medical Follow-up (MD-F-2)') context['submit_text'] = _('Save Follow-up') # Get patient if provided patient_id = self.request.GET.get('patient') if patient_id: try: patient = Patient.objects.get( pk=patient_id, tenant=self.request.user.tenant ) context['patient'] = patient # Get most recent consultation for this patient context['recent_consultations'] = MedicalConsultation.objects.filter( patient=patient ).order_by('-consultation_date')[:5] except Patient.DoesNotExist: pass # Get appointment if provided appointment_id = self.request.GET.get('appointment_id') if appointment_id: try: context['appointment'] = Appointment.objects.get( pk=appointment_id, tenant=self.request.user.tenant ) context['patient'] = context['appointment'].patient # Get most recent consultations for this patient context['recent_consultations'] = MedicalConsultation.objects.filter( patient=context['appointment'].patient ).order_by('-consultation_date')[:5] except Appointment.DoesNotExist: pass return context class MedicalFollowUpUpdateView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, AuditLogMixin, SuccessMessageMixin, UpdateView): """ Medical follow-up update view (MD-F-2). Features: - Update follow-up details - Version history - Audit trail """ model = MedicalFollowUp form_class = MedicalFollowUpForm template_name = 'medical/followup_form.html' success_message = _("Medical follow-up updated successfully!") allowed_roles = [User.Role.ADMIN, User.Role.DOCTOR] def get_success_url(self): """Redirect to follow-up detail.""" return reverse_lazy('medical:followup_detail', kwargs={'pk': self.object.pk}) def get_context_data(self, **kwargs): """Add form title and history.""" context = super().get_context_data(**kwargs) context['form_title'] = _('Update Medical Follow-up - %(mrn)s') % {'mrn': self.object.patient.mrn} context['submit_text'] = _('Update Follow-up') # Add version history if available if hasattr(self.object, 'history'): context['history'] = self.object.history.all()[:10] return context class ConsultationResponseCreateView(LoginRequiredMixin, RolePermissionMixin, AuditLogMixin, SuccessMessageMixin, CreateView): """ Create response to medical consultation from other disciplines. Features: - OT, SLP, ABA, Nursing can respond to consultations - Assessment and recommendations - Follow-up needed flag """ model = ConsultationResponse form_class = ConsultationResponseForm template_name = 'medical/response_form.html' success_message = _("Response submitted successfully!") allowed_roles = [User.Role.ADMIN, User.Role.DOCTOR, User.Role.NURSE, User.Role.OT, User.Role.SLP, User.Role.ABA] def get_success_url(self): """Redirect to consultation detail.""" return reverse_lazy('medical:consultation_detail', kwargs={'pk': self.object.consultation.pk}) def get_initial(self): """Pre-populate consultation and responder.""" initial = super().get_initial() consultation_id = self.kwargs.get('consultation_pk') if consultation_id: initial['consultation'] = consultation_id initial['responder'] = self.request.user.pk initial['response_date'] = timezone.now().date() return initial def form_valid(self, form): """Set tenant and responder.""" form.instance.tenant = self.request.user.tenant form.instance.responder = self.request.user if not form.instance.response_date: form.instance.response_date = timezone.now().date() return super().form_valid(form) def get_context_data(self, **kwargs): """Add consultation info.""" context = super().get_context_data(**kwargs) consultation_id = self.kwargs.get('consultation_pk') if consultation_id: try: consultation = MedicalConsultation.objects.get( pk=consultation_id, tenant=self.request.user.tenant ) context['consultation'] = consultation context['form_title'] = _('Respond to Consultation - %(mrn)s') % {'mrn': consultation.patient.mrn} except MedicalConsultation.DoesNotExist: pass return context class ConsultationFeedbackCreateView(LoginRequiredMixin, AuditLogMixin, SuccessMessageMixin, CreateView): """ Create feedback for medical consultation. Features: - Family/caregiver feedback - Team feedback - Ratings and comments """ model = ConsultationFeedback form_class = ConsultationFeedbackForm template_name = 'medical/feedback_form.html' success_message = _("Feedback submitted successfully!") def get_success_url(self): """Redirect to consultation detail.""" return reverse_lazy('medical:consultation_detail', kwargs={'pk': self.object.consultation.pk}) def get_initial(self): """Pre-populate consultation.""" initial = super().get_initial() consultation_id = self.kwargs.get('consultation_pk') if consultation_id: initial['consultation'] = consultation_id initial['feedback_date'] = timezone.now().date() # Set submitted_by if user is logged in if self.request.user.is_authenticated: initial['submitted_by'] = self.request.user.pk return initial def form_valid(self, form): """Set submitted_by if authenticated.""" if self.request.user.is_authenticated and not form.instance.submitted_by: form.instance.submitted_by = self.request.user if not form.instance.feedback_date: form.instance.feedback_date = timezone.now().date() return super().form_valid(form) def get_context_data(self, **kwargs): """Add consultation info.""" context = super().get_context_data(**kwargs) consultation_id = self.kwargs.get('consultation_pk') if consultation_id: try: consultation = MedicalConsultation.objects.get(pk=consultation_id) context['consultation'] = consultation context['form_title'] = _('Provide Feedback - %(mrn)s') % {'mrn': consultation.patient.mrn} except MedicalConsultation.DoesNotExist: pass return context # ============================================================================ # PDF Generation Views # ============================================================================ from core.pdf_service import BasePDFGenerator class MedicalConsultationPDFGenerator(BasePDFGenerator): """PDF generator for Medical Consultation (MD-F-1).""" def get_document_title(self): """Return document title in English and Arabic.""" consultation = self.document return ( f"Medical Consultation (MD-F-1) - {consultation.patient.mrn}", "استشارة طبية" ) def get_pdf_filename(self): """Return PDF filename.""" consultation = self.document date_str = consultation.consultation_date.strftime('%Y%m%d') return f"medical_consultation_{consultation.patient.mrn}_{date_str}.pdf" def get_document_sections(self): """Return document sections to render.""" consultation = self.document patient = consultation.patient sections = [] # Patient Information Section patient_name_ar = f"{patient.first_name_ar} {patient.last_name_ar}" if patient.first_name_ar else "" sections.append({ 'heading_en': 'Patient Information', 'heading_ar': 'معلومات المريض', 'type': 'table', 'content': [ ('Name', 'الاسم', f"{patient.first_name_en} {patient.last_name_en}", patient_name_ar), ('MRN', 'رقم السجل الطبي', patient.mrn, ""), ('Date of Birth', 'تاريخ الميلاد', patient.date_of_birth.strftime('%Y-%m-%d'), ""), ('Age', 'العمر', f"{patient.age} years", ""), ('Gender', 'الجنس', patient.get_sex_display(), ""), ] }) # Consultation Details Section sections.append({ 'heading_en': 'Consultation Details', 'heading_ar': 'تفاصيل الاستشارة', 'type': 'table', 'content': [ ('Date', 'التاريخ', consultation.consultation_date.strftime('%Y-%m-%d'), ""), ('Provider', 'مقدم الخدمة', consultation.provider.get_full_name() if consultation.provider else 'N/A', ""), ('Signed By', 'موقع من قبل', consultation.signed_by.get_full_name() if consultation.signed_by else 'Not signed', ""), ('Signed At', 'تاريخ التوقيع', consultation.signed_at.strftime('%Y-%m-%d %H:%M') if consultation.signed_at else 'N/A', ""), ] }) # Chief Complaint & History if consultation.chief_complaint: sections.append({ 'heading_en': 'Chief Complaint', 'heading_ar': 'الشكوى الرئيسية', 'type': 'text', 'content': [consultation.chief_complaint] }) if consultation.present_illness_history: sections.append({ 'heading_en': 'History of Present Illness', 'heading_ar': 'تاريخ المرض الحالي', 'type': 'text', 'content': [consultation.present_illness_history] }) if consultation.past_medical_history: sections.append({ 'heading_en': 'Past Medical History', 'heading_ar': 'التاريخ الطبي السابق', 'type': 'text', 'content': [consultation.past_medical_history] }) # Developmental Milestones if any([consultation.developmental_motor_milestones, consultation.developmental_language_milestones, consultation.developmental_social_milestones, consultation.developmental_cognitive_milestones]): dev_content = [] if consultation.developmental_motor_milestones: dev_content.append(f"Motor: {consultation.developmental_motor_milestones}") if consultation.developmental_language_milestones: dev_content.append(f"Language: {consultation.developmental_language_milestones}") if consultation.developmental_social_milestones: dev_content.append(f"Social: {consultation.developmental_social_milestones}") if consultation.developmental_cognitive_milestones: dev_content.append(f"Cognitive: {consultation.developmental_cognitive_milestones}") sections.append({ 'heading_en': 'Developmental Milestones', 'heading_ar': 'المعالم التنموية', 'type': 'text', 'content': dev_content }) # Clinical Summary & Recommendations if consultation.clinical_summary: sections.append({ 'heading_en': 'Clinical Summary', 'heading_ar': 'الملخص السريري', 'type': 'text', 'content': [consultation.clinical_summary] }) if consultation.recommendations: sections.append({ 'heading_en': 'Recommendations', 'heading_ar': 'التوصيات', 'type': 'text', 'content': [consultation.recommendations] }) # Medications if consultation.medications: med_content = [] for med in consultation.medications: med_text = f"• {med.get('drug_name', 'N/A')} - {med.get('dose', 'N/A')} - {med.get('frequency', 'N/A')}" if med.get('compliance'): med_text += f" (Compliance: {med.get('compliance')})" med_content.append(med_text) sections.append({ 'heading_en': 'Current Medications', 'heading_ar': 'الأدوية الحالية', 'type': 'text', 'content': med_content }) return sections class MedicalConsultationPDFView(LoginRequiredMixin, TenantFilterMixin, View): """Generate PDF for medical consultation.""" def get(self, request, pk): """Generate and return PDF.""" consultation = get_object_or_404( MedicalConsultation.objects.select_related( 'patient', 'provider', 'tenant', 'signed_by' ), pk=pk, tenant=request.user.tenant ) pdf_generator = MedicalConsultationPDFGenerator(consultation, request) view_mode = request.GET.get('view', 'download') return pdf_generator.generate_pdf(view_mode=view_mode) class MedicalConsultationEmailPDFView(LoginRequiredMixin, TenantFilterMixin, View): """Email medical consultation PDF to patient.""" def post(self, request, pk): """Send PDF via email.""" consultation = get_object_or_404( MedicalConsultation.objects.select_related( 'patient', 'provider', 'tenant' ), pk=pk, tenant=request.user.tenant ) email_address = request.POST.get('email_address', '').strip() custom_message = request.POST.get('email_message', '').strip() if not email_address: messages.error(request, _('Email address is required.')) return redirect('medical:consultation_detail', pk=pk) pdf_generator = MedicalConsultationPDFGenerator(consultation, request) subject = f"Medical Consultation - {consultation.patient.mrn}" body = f""" Dear {consultation.patient.first_name_en} {consultation.patient.last_name_en}, Please find attached your medical consultation details. Consultation Date: {consultation.consultation_date.strftime('%Y-%m-%d')} Provider: {consultation.provider.get_full_name() if consultation.provider else 'N/A'} Best regards, {consultation.tenant.name} """ success, message = pdf_generator.send_email( email_address=email_address, subject=subject, body=body, custom_message=custom_message ) if success: messages.success(request, _('PDF sent to %(email)s successfully!') % {'email': email_address}) else: messages.error(request, _('Failed to send email: %(error)s') % {'error': message}) return redirect('medical:consultation_detail', pk=pk) class MedicalFollowUpPDFGenerator(BasePDFGenerator): """PDF generator for Medical Follow-up (MD-F-2).""" def get_document_title(self): """Return document title in English and Arabic.""" followup = self.document return ( f"Medical Follow-up (MD-F-2) - {followup.patient.mrn}", "متابعة طبية" ) def get_pdf_filename(self): """Return PDF filename.""" followup = self.document date_str = followup.followup_date.strftime('%Y%m%d') return f"medical_followup_{followup.patient.mrn}_{date_str}.pdf" def get_document_sections(self): """Return document sections to render.""" followup = self.document patient = followup.patient sections = [] # Patient Information Section patient_name_ar = f"{patient.first_name_ar} {patient.last_name_ar}" if patient.first_name_ar else "" sections.append({ 'heading_en': 'Patient Information', 'heading_ar': 'معلومات المريض', 'type': 'table', 'content': [ ('Name', 'الاسم', f"{patient.first_name_en} {patient.last_name_en}", patient_name_ar), ('MRN', 'رقم السجل الطبي', patient.mrn, ""), ('Date of Birth', 'تاريخ الميلاد', patient.date_of_birth.strftime('%Y-%m-%d'), ""), ('Age', 'العمر', f"{patient.age} years", ""), ] }) # Follow-up Details Section sections.append({ 'heading_en': 'Follow-up Details', 'heading_ar': 'تفاصيل المتابعة', 'type': 'table', 'content': [ ('Date', 'التاريخ', followup.followup_date.strftime('%Y-%m-%d'), ""), ('Provider', 'مقدم الخدمة', followup.provider.get_full_name() if followup.provider else 'N/A', ""), ('Family Satisfaction', 'رضا العائلة', followup.get_family_satisfaction_display() if followup.family_satisfaction else 'N/A', ""), ('Signed By', 'موقع من قبل', followup.signed_by.get_full_name() if followup.signed_by else 'Not signed', ""), ] }) # New Complaints if followup.new_complaints: sections.append({ 'heading_en': 'New Complaints', 'heading_ar': 'الشكاوى الجديدة', 'type': 'text', 'content': [followup.new_complaints] }) # Assessment & Recommendations if followup.assessment: sections.append({ 'heading_en': 'Assessment', 'heading_ar': 'التقييم', 'type': 'text', 'content': [followup.assessment] }) if followup.recommendations: sections.append({ 'heading_en': 'Recommendations', 'heading_ar': 'التوصيات', 'type': 'text', 'content': [followup.recommendations] }) # Medication Snapshot if followup.medication_snapshot: med_content = [] for med in followup.medication_snapshot: med_text = f"• {med.get('drug_name', 'N/A')} - {med.get('dose', 'N/A')} - {med.get('frequency', 'N/A')}" if med.get('compliance'): med_text += f" (Compliance: {med.get('compliance')})" if med.get('improved'): med_text += " ✓ Improved" med_content.append(med_text) sections.append({ 'heading_en': 'Current Medications', 'heading_ar': 'الأدوية الحالية', 'type': 'text', 'content': med_content }) return sections class MedicalFollowUpPDFView(LoginRequiredMixin, TenantFilterMixin, View): """Generate PDF for medical follow-up.""" def get(self, request, pk): """Generate and return PDF.""" followup = get_object_or_404( MedicalFollowUp.objects.select_related( 'patient', 'provider', 'tenant', 'signed_by' ), pk=pk, tenant=request.user.tenant ) pdf_generator = MedicalFollowUpPDFGenerator(followup, request) view_mode = request.GET.get('view', 'download') return pdf_generator.generate_pdf(view_mode=view_mode) class MedicalFollowUpEmailPDFView(LoginRequiredMixin, TenantFilterMixin, View): """Email medical follow-up PDF to patient.""" def post(self, request, pk): """Send PDF via email.""" followup = get_object_or_404( MedicalFollowUp.objects.select_related( 'patient', 'provider', 'tenant' ), pk=pk, tenant=request.user.tenant ) email_address = request.POST.get('email_address', '').strip() custom_message = request.POST.get('email_message', '').strip() if not email_address: messages.error(request, _('Email address is required.')) return redirect('medical:followup_detail', pk=pk) pdf_generator = MedicalFollowUpPDFGenerator(followup, request) subject = f"Medical Follow-up - {followup.patient.mrn}" body = f""" Dear {followup.patient.first_name_en} {followup.patient.last_name_en}, Please find attached your medical follow-up details. Follow-up Date: {followup.followup_date.strftime('%Y-%m-%d')} Provider: {followup.provider.get_full_name() if followup.provider else 'N/A'} Best regards, {followup.tenant.name} """ success, message = pdf_generator.send_email( email_address=email_address, subject=subject, body=body, custom_message=custom_message ) if success: messages.success(request, _('PDF sent to %(email)s successfully!') % {'email': email_address}) else: messages.error(request, _('Failed to send email: %(error)s') % {'error': message}) return redirect('medical:followup_detail', pk=pk)