1399 lines
56 KiB
Python
1399 lines
56 KiB
Python
"""
|
|
SLP views for the Tenhal Multidisciplinary Healthcare Platform.
|
|
|
|
This module contains views for Speech-Language Pathology documentation including:
|
|
- SLP consultations (SLP-F-1) - variants for ASD/LanguageDelay/Fluency
|
|
- SLP assessments (SLP-F-2)
|
|
- SLP interventions (SLP-F-3)
|
|
- SLP progress reports (SLP-F-4)
|
|
"""
|
|
|
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
from django.contrib import messages
|
|
from django.db.models import Q, Avg
|
|
from django.http import HttpResponseRedirect
|
|
from django.shortcuts import get_object_or_404
|
|
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 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 SLPConsult, SLPAssessment, SLPIntervention, SLPProgressReport, SLPTarget
|
|
from .forms import SLPConsultForm, SLPAssessmentForm, SLPInterventionForm, SLPProgressReportForm
|
|
|
|
|
|
# Sign Views
|
|
class SLPConsultSignView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View):
|
|
"""Sign an SLP consultation."""
|
|
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
|
|
|
|
def post(self, request, pk):
|
|
consult = get_object_or_404(SLPConsult, pk=pk, tenant=request.user.tenant)
|
|
if consult.signed_by:
|
|
messages.warning(request, _("This consultation has already been signed."))
|
|
return HttpResponseRedirect(reverse_lazy('slp:consult_detail', kwargs={'pk': pk}))
|
|
if consult.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('slp:consult_detail', kwargs={'pk': pk}))
|
|
consult.signed_by = request.user
|
|
consult.signed_at = timezone.now()
|
|
consult.save()
|
|
messages.success(request, _("Consultation signed successfully!"))
|
|
return HttpResponseRedirect(reverse_lazy('slp:consult_detail', kwargs={'pk': pk}))
|
|
|
|
|
|
class SLPAssessmentSignView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View):
|
|
"""Sign an SLP assessment."""
|
|
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
|
|
|
|
def post(self, request, pk):
|
|
assessment = get_object_or_404(SLPAssessment, pk=pk, tenant=request.user.tenant)
|
|
if assessment.signed_by:
|
|
messages.warning(request, _("This assessment has already been signed."))
|
|
return HttpResponseRedirect(reverse_lazy('slp:assessment_detail', kwargs={'pk': pk}))
|
|
if assessment.provider != request.user and request.user.role != User.Role.ADMIN:
|
|
messages.error(request, _("Only the assessment provider or an administrator can sign this assessment."))
|
|
return HttpResponseRedirect(reverse_lazy('slp:assessment_detail', kwargs={'pk': pk}))
|
|
assessment.signed_by = request.user
|
|
assessment.signed_at = timezone.now()
|
|
assessment.save()
|
|
messages.success(request, _("Assessment signed successfully!"))
|
|
return HttpResponseRedirect(reverse_lazy('slp:assessment_detail', kwargs={'pk': pk}))
|
|
|
|
|
|
class SLPInterventionSignView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View):
|
|
"""Sign an SLP intervention."""
|
|
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
|
|
|
|
def post(self, request, pk):
|
|
intervention = get_object_or_404(SLPIntervention, pk=pk, tenant=request.user.tenant)
|
|
if intervention.signed_by:
|
|
messages.warning(request, _("This intervention has already been signed."))
|
|
return HttpResponseRedirect(reverse_lazy('slp:intervention_detail', kwargs={'pk': pk}))
|
|
if intervention.provider != request.user and request.user.role != User.Role.ADMIN:
|
|
messages.error(request, _("Only the intervention provider or an administrator can sign this intervention."))
|
|
return HttpResponseRedirect(reverse_lazy('slp:intervention_detail', kwargs={'pk': pk}))
|
|
intervention.signed_by = request.user
|
|
intervention.signed_at = timezone.now()
|
|
intervention.save()
|
|
messages.success(request, _("Intervention signed successfully!"))
|
|
return HttpResponseRedirect(reverse_lazy('slp:intervention_detail', kwargs={'pk': pk}))
|
|
|
|
|
|
class SLPProgressReportSignView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View):
|
|
"""Sign an SLP progress report."""
|
|
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
|
|
|
|
def post(self, request, pk):
|
|
report = get_object_or_404(SLPProgressReport, pk=pk, tenant=request.user.tenant)
|
|
if report.signed_by:
|
|
messages.warning(request, _("This progress report has already been signed."))
|
|
return HttpResponseRedirect(reverse_lazy('slp:progress_report_detail', kwargs={'pk': pk}))
|
|
if report.provider != request.user and request.user.role != User.Role.ADMIN:
|
|
messages.error(request, _("Only the report provider or an administrator can sign this progress report."))
|
|
return HttpResponseRedirect(reverse_lazy('slp:progress_report_detail', kwargs={'pk': pk}))
|
|
report.signed_by = request.user
|
|
report.signed_at = timezone.now()
|
|
report.save()
|
|
messages.success(request, _("Progress report signed successfully!"))
|
|
return HttpResponseRedirect(reverse_lazy('slp:progress_report_detail', kwargs={'pk': pk}))
|
|
|
|
|
|
class SLPConsultListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin,
|
|
HTMXResponseMixin, ListView):
|
|
"""
|
|
SLP consultation list view (SLP-F-1).
|
|
|
|
Features:
|
|
- Filter by patient, provider, date range, consult type
|
|
- Search by patient name/MRN
|
|
- Role-based filtering
|
|
"""
|
|
model = SLPConsult
|
|
template_name = 'slp/consultation_list.html'
|
|
htmx_template_name = 'slp/partials/consult_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.SLP:
|
|
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)
|
|
|
|
consult_type = self.request.GET.get('consult_type')
|
|
if consult_type:
|
|
if consult_type == 'ASD':
|
|
queryset = queryset.filter(is_asd_variant=True)
|
|
elif consult_type == 'LANGUAGE':
|
|
queryset = queryset.filter(is_language_delay_variant=True)
|
|
elif consult_type == 'FLUENCY':
|
|
queryset = queryset.filter(is_fluency_variant=True)
|
|
|
|
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 unsigned consultations count for current user."""
|
|
context = super().get_context_data(**kwargs)
|
|
user = self.request.user
|
|
|
|
unsigned_query = SLPConsult.objects.filter(tenant=user.tenant, signed_by__isnull=True)
|
|
if user.role == User.Role.SLP:
|
|
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]
|
|
return context
|
|
|
|
|
|
# ============================================================================
|
|
# PDF Generation Views
|
|
# ============================================================================
|
|
|
|
from core.pdf_service import BasePDFGenerator
|
|
from django.shortcuts import redirect
|
|
|
|
|
|
class SLPConsultPDFGenerator(BasePDFGenerator):
|
|
"""PDF generator for SLP Consultation (SLP-F-1)."""
|
|
|
|
def get_document_title(self):
|
|
"""Return document title in English and Arabic."""
|
|
consult = self.document
|
|
return (
|
|
f"SLP Consultation (SLP-F-1) - {consult.patient.mrn}",
|
|
"استشارة علاج النطق واللغة"
|
|
)
|
|
|
|
def get_pdf_filename(self):
|
|
"""Return PDF filename."""
|
|
consult = self.document
|
|
date_str = consult.consultation_date.strftime('%Y%m%d')
|
|
return f"slp_consultation_{consult.patient.mrn}_{date_str}.pdf"
|
|
|
|
def get_document_sections(self):
|
|
"""Return document sections to render."""
|
|
consult = self.document
|
|
patient = consult.patient
|
|
|
|
sections = []
|
|
|
|
# Patient Information
|
|
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, ""),
|
|
('Age', 'العمر', f"{patient.age} years", ""),
|
|
]
|
|
})
|
|
|
|
# Consultation Details
|
|
sections.append({
|
|
'heading_en': 'Consultation Details',
|
|
'heading_ar': 'تفاصيل الاستشارة',
|
|
'type': 'table',
|
|
'content': [
|
|
('Date', 'التاريخ', consult.consultation_date.strftime('%Y-%m-%d'), ""),
|
|
('Provider', 'مقدم الخدمة', consult.provider.get_full_name() if consult.provider else 'N/A', ""),
|
|
('Variant', 'النوع', consult.get_consult_variant_display(), ""),
|
|
('Service Type', 'نوع الخدمة', consult.get_type_of_service_display(), ""),
|
|
('Screen Time', 'وقت الشاشة', f"{consult.screen_time_hours} hours/day" if consult.screen_time_hours else 'N/A', ""),
|
|
('Signed By', 'موقع من قبل', consult.signed_by.get_full_name() if consult.signed_by else 'Not signed', ""),
|
|
]
|
|
})
|
|
|
|
# Primary Concern
|
|
if consult.primary_concern:
|
|
sections.append({
|
|
'heading_en': 'Primary Concern',
|
|
'heading_ar': 'القلق الأساسي',
|
|
'type': 'text',
|
|
'content': [consult.primary_concern]
|
|
})
|
|
|
|
# Suspected Areas
|
|
if consult.suspected_areas:
|
|
sections.append({
|
|
'heading_en': 'Suspected Areas',
|
|
'heading_ar': 'المجالات المشتبه بها',
|
|
'type': 'text',
|
|
'content': [consult.suspected_areas]
|
|
})
|
|
|
|
# Communication Modes
|
|
if consult.communication_modes:
|
|
sections.append({
|
|
'heading_en': 'Communication Modes',
|
|
'heading_ar': 'أنماط التواصل',
|
|
'type': 'text',
|
|
'content': [consult.communication_modes]
|
|
})
|
|
|
|
# Skills to Observe
|
|
if consult.skills_to_observe:
|
|
sections.append({
|
|
'heading_en': 'Skills to Observe',
|
|
'heading_ar': 'المهارات المراقبة',
|
|
'type': 'text',
|
|
'content': [consult.skills_to_observe]
|
|
})
|
|
|
|
# Oral Motor Screening
|
|
if consult.oral_motor_screening:
|
|
sections.append({
|
|
'heading_en': 'Oral Motor Screening',
|
|
'heading_ar': 'فحص الحركة الفموية',
|
|
'type': 'text',
|
|
'content': [consult.oral_motor_screening]
|
|
})
|
|
|
|
# Recommendations
|
|
if consult.recommendations:
|
|
sections.append({
|
|
'heading_en': 'Recommendations',
|
|
'heading_ar': 'التوصيات',
|
|
'type': 'text',
|
|
'content': [consult.recommendations]
|
|
})
|
|
|
|
return sections
|
|
|
|
|
|
class SLPConsultPDFView(LoginRequiredMixin, TenantFilterMixin, View):
|
|
"""Generate PDF for SLP consultation."""
|
|
|
|
def get(self, request, pk):
|
|
consult = get_object_or_404(
|
|
SLPConsult.objects.select_related('patient', 'provider', 'tenant', 'signed_by'),
|
|
pk=pk, tenant=request.user.tenant
|
|
)
|
|
pdf_generator = SLPConsultPDFGenerator(consult, request)
|
|
return pdf_generator.generate_pdf(request.GET.get('view', 'download'))
|
|
|
|
|
|
class SLPConsultEmailPDFView(LoginRequiredMixin, TenantFilterMixin, View):
|
|
"""Email SLP consultation PDF."""
|
|
|
|
def post(self, request, pk):
|
|
consult = get_object_or_404(SLPConsult, pk=pk, tenant=request.user.tenant)
|
|
email_address = request.POST.get('email_address', '').strip()
|
|
|
|
if not email_address:
|
|
messages.error(request, _('Email address is required.'))
|
|
return redirect('slp:consult_detail', pk=pk)
|
|
|
|
pdf_generator = SLPConsultPDFGenerator(consult, request)
|
|
subject = f"SLP Consultation - {consult.patient.mrn}"
|
|
body = f"""Dear {consult.patient.first_name_en} {consult.patient.last_name_en},
|
|
|
|
Please find attached your Speech-Language Pathology consultation details.
|
|
|
|
Best regards,
|
|
{consult.tenant.name}"""
|
|
|
|
success, msg = pdf_generator.send_email(email_address, subject, body, request.POST.get('email_message', ''))
|
|
|
|
if success:
|
|
messages.success(request, _('PDF sent successfully!'))
|
|
else:
|
|
messages.error(request, _('Failed to send email: %(error)s') % {'error': msg})
|
|
|
|
return redirect('slp:consult_detail', pk=pk)
|
|
|
|
|
|
class SLPAssessmentPDFGenerator(BasePDFGenerator):
|
|
"""PDF generator for SLP Assessment (SLP-F-2)."""
|
|
|
|
def get_document_title(self):
|
|
return (f"SLP Assessment (SLP-F-2) - {self.document.patient.mrn}", "تقييم علاج النطق واللغة")
|
|
|
|
def get_pdf_filename(self):
|
|
date_str = self.document.assessment_date.strftime('%Y%m%d')
|
|
return f"slp_assessment_{self.document.patient.mrn}_{date_str}.pdf"
|
|
|
|
def get_document_sections(self):
|
|
assessment = self.document
|
|
patient = assessment.patient
|
|
sections = []
|
|
|
|
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, ""),
|
|
('Age', 'العمر', f"{patient.age} years", ""),
|
|
]
|
|
})
|
|
|
|
sections.append({
|
|
'heading_en': 'Assessment Details', 'heading_ar': 'تفاصيل التقييم', 'type': 'table',
|
|
'content': [
|
|
('Date', 'التاريخ', assessment.assessment_date.strftime('%Y-%m-%d'), ""),
|
|
('Provider', 'مقدم الخدمة', assessment.provider.get_full_name() if assessment.provider else 'N/A', ""),
|
|
('Frequency', 'التكرار', f"{assessment.frequency_per_week}/week" if assessment.frequency_per_week else 'N/A', ""),
|
|
('Duration', 'المدة', f"{assessment.session_duration_minutes} min" if assessment.session_duration_minutes else 'N/A', ""),
|
|
]
|
|
})
|
|
|
|
if assessment.diagnosis_statement:
|
|
sections.append({'heading_en': 'Diagnosis', 'heading_ar': 'التشخيص', 'type': 'text', 'content': [assessment.diagnosis_statement]})
|
|
|
|
if assessment.clinical_summary:
|
|
sections.append({'heading_en': 'Clinical Summary', 'heading_ar': 'الملخص السريري', 'type': 'text', 'content': [assessment.clinical_summary]})
|
|
|
|
if assessment.recommendations:
|
|
sections.append({'heading_en': 'Recommendations', 'heading_ar': 'التوصيات', 'type': 'text', 'content': [assessment.recommendations]})
|
|
|
|
return sections
|
|
|
|
|
|
class SLPAssessmentPDFView(LoginRequiredMixin, TenantFilterMixin, View):
|
|
"""Generate PDF for SLP assessment."""
|
|
|
|
def get(self, request, pk):
|
|
assessment = get_object_or_404(SLPAssessment, pk=pk, tenant=request.user.tenant)
|
|
pdf_generator = SLPAssessmentPDFGenerator(assessment, request)
|
|
return pdf_generator.generate_pdf(request.GET.get('view', 'download'))
|
|
|
|
|
|
class SLPAssessmentEmailPDFView(LoginRequiredMixin, TenantFilterMixin, View):
|
|
"""Email SLP assessment PDF."""
|
|
|
|
def post(self, request, pk):
|
|
assessment = get_object_or_404(SLPAssessment, pk=pk, tenant=request.user.tenant)
|
|
email_address = request.POST.get('email_address', '').strip()
|
|
|
|
if not email_address:
|
|
messages.error(request, _('Email address is required.'))
|
|
return redirect('slp:assessment_detail', pk=pk)
|
|
|
|
pdf_generator = SLPAssessmentPDFGenerator(assessment, request)
|
|
subject = f"SLP Assessment - {assessment.patient.mrn}"
|
|
body = f"""Dear {assessment.patient.first_name_en},
|
|
|
|
Please find attached your SLP assessment details.
|
|
|
|
Best regards,
|
|
{assessment.tenant.name}"""
|
|
|
|
success, msg = pdf_generator.send_email(email_address, subject, body, request.POST.get('email_message', ''))
|
|
messages.success(request, _('PDF sent successfully!')) if success else messages.error(request, f'Error: {msg}')
|
|
return redirect('slp:assessment_detail', pk=pk)
|
|
|
|
|
|
class SLPInterventionPDFGenerator(BasePDFGenerator):
|
|
"""PDF generator for SLP Intervention (SLP-F-3)."""
|
|
|
|
def get_document_title(self):
|
|
return (f"SLP Intervention Session #{self.document.session_number} - {self.document.patient.mrn}", "جلسة تدخل علاج النطق")
|
|
|
|
def get_pdf_filename(self):
|
|
date_str = self.document.session_date.strftime('%Y%m%d')
|
|
return f"slp_intervention_{self.document.patient.mrn}_session{self.document.session_number}_{date_str}.pdf"
|
|
|
|
def get_document_sections(self):
|
|
intervention = self.document
|
|
patient = intervention.patient
|
|
sections = []
|
|
|
|
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, ""),
|
|
]
|
|
})
|
|
|
|
sections.append({
|
|
'heading_en': 'Session Details', 'heading_ar': 'تفاصيل الجلسة', 'type': 'table',
|
|
'content': [
|
|
('Session Number', 'رقم الجلسة', str(intervention.session_number), ""),
|
|
('Date', 'التاريخ', intervention.session_date.strftime('%Y-%m-%d'), ""),
|
|
('Time', 'الوقت', intervention.session_time.strftime('%H:%M'), ""),
|
|
('Provider', 'مقدم الخدمة', intervention.provider.get_full_name() if intervention.provider else 'N/A', ""),
|
|
]
|
|
})
|
|
|
|
# Targets
|
|
targets = intervention.targets.all()
|
|
if targets:
|
|
for target in targets:
|
|
target_content = []
|
|
if target.subjective:
|
|
target_content.append(f"<b>Subjective:</b> {target.subjective}")
|
|
if target.objective:
|
|
target_content.append(f"<b>Objective:</b> {target.objective}")
|
|
if target.assessment:
|
|
target_content.append(f"<b>Assessment:</b> {target.assessment}")
|
|
if target.plan:
|
|
target_content.append(f"<b>Plan:</b> {target.plan}")
|
|
if target.prompt_strategies:
|
|
target_content.append(f"<b>Prompt Strategies:</b> {target.prompt_strategies}")
|
|
|
|
sections.append({
|
|
'heading_en': f'Target #{target.target_number}',
|
|
'heading_ar': f'الهدف #{target.target_number}',
|
|
'type': 'text',
|
|
'content': target_content
|
|
})
|
|
|
|
return sections
|
|
|
|
|
|
class SLPInterventionPDFView(LoginRequiredMixin, TenantFilterMixin, View):
|
|
"""Generate PDF for SLP intervention."""
|
|
|
|
def get(self, request, pk):
|
|
intervention = get_object_or_404(
|
|
SLPIntervention.objects.select_related('patient', 'provider', 'tenant').prefetch_related('targets'),
|
|
pk=pk, tenant=request.user.tenant
|
|
)
|
|
pdf_generator = SLPInterventionPDFGenerator(intervention, request)
|
|
return pdf_generator.generate_pdf(request.GET.get('view', 'download'))
|
|
|
|
|
|
class SLPInterventionEmailPDFView(LoginRequiredMixin, TenantFilterMixin, View):
|
|
"""Email SLP intervention PDF."""
|
|
|
|
def post(self, request, pk):
|
|
intervention = get_object_or_404(SLPIntervention, pk=pk, tenant=request.user.tenant)
|
|
email_address = request.POST.get('email_address', '').strip()
|
|
|
|
if not email_address:
|
|
messages.error(request, _('Email address is required.'))
|
|
return redirect('slp:intervention_detail', pk=pk)
|
|
|
|
pdf_generator = SLPInterventionPDFGenerator(intervention, request)
|
|
subject = f"SLP Intervention Session #{intervention.session_number}"
|
|
body = f"""Dear {intervention.patient.first_name_en},
|
|
|
|
Please find attached your SLP intervention session details.
|
|
|
|
Best regards,
|
|
{intervention.tenant.name}"""
|
|
|
|
success, msg = pdf_generator.send_email(email_address, subject, body, request.POST.get('email_message', ''))
|
|
messages.success(request, _('PDF sent successfully!')) if success else messages.error(request, f'Error: {msg}')
|
|
return redirect('slp:intervention_detail', pk=pk)
|
|
|
|
|
|
class SLPProgressReportPDFGenerator(BasePDFGenerator):
|
|
"""PDF generator for SLP Progress Report (SLP-F-4)."""
|
|
|
|
def get_document_title(self):
|
|
return (f"SLP Progress Report (SLP-F-4) - {self.document.patient.mrn}", "تقرير تقدم علاج النطق")
|
|
|
|
def get_pdf_filename(self):
|
|
date_str = self.document.report_date.strftime('%Y%m%d')
|
|
return f"slp_progress_report_{self.document.patient.mrn}_{date_str}.pdf"
|
|
|
|
def get_document_sections(self):
|
|
report = self.document
|
|
patient = report.patient
|
|
sections = []
|
|
|
|
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, ""),
|
|
]
|
|
})
|
|
|
|
sections.append({
|
|
'heading_en': 'Report Details', 'heading_ar': 'تفاصيل التقرير', 'type': 'table',
|
|
'content': [
|
|
('Report Date', 'تاريخ التقرير', report.report_date.strftime('%Y-%m-%d'), ""),
|
|
('Provider', 'مقدم الخدمة', report.provider.get_full_name() if report.provider else 'N/A', ""),
|
|
('Sessions Scheduled', 'الجلسات المجدولة', str(report.sessions_scheduled), ""),
|
|
('Sessions Attended', 'الجلسات المحضورة', str(report.sessions_attended), ""),
|
|
('Attendance Rate', 'معدل الحضور', f"{report.attendance_rate}%" if report.attendance_rate else 'N/A', ""),
|
|
]
|
|
})
|
|
|
|
if report.final_diagnosis:
|
|
sections.append({'heading_en': 'Final Diagnosis', 'heading_ar': 'التشخيص النهائي', 'type': 'text', 'content': [report.final_diagnosis]})
|
|
|
|
if report.objectives_progress:
|
|
sections.append({'heading_en': 'Objectives Progress', 'heading_ar': 'تقدم الأهداف', 'type': 'text', 'content': [report.objectives_progress]})
|
|
|
|
if report.overall_progress:
|
|
sections.append({'heading_en': 'Overall Progress', 'heading_ar': 'التقدم العام', 'type': 'text', 'content': [report.overall_progress]})
|
|
|
|
if report.recommendations:
|
|
sections.append({'heading_en': 'Recommendations', 'heading_ar': 'التوصيات', 'type': 'text', 'content': [report.recommendations]})
|
|
|
|
return sections
|
|
|
|
|
|
class SLPProgressReportPDFView(LoginRequiredMixin, TenantFilterMixin, View):
|
|
"""Generate PDF for SLP progress report."""
|
|
|
|
def get(self, request, pk):
|
|
report = get_object_or_404(SLPProgressReport, pk=pk, tenant=request.user.tenant)
|
|
pdf_generator = SLPProgressReportPDFGenerator(report, request)
|
|
return pdf_generator.generate_pdf(request.GET.get('view', 'download'))
|
|
|
|
|
|
class SLPProgressReportEmailPDFView(LoginRequiredMixin, TenantFilterMixin, View):
|
|
"""Email SLP progress report PDF."""
|
|
|
|
def post(self, request, pk):
|
|
report = get_object_or_404(SLPProgressReport, pk=pk, tenant=request.user.tenant)
|
|
email_address = request.POST.get('email_address', '').strip()
|
|
|
|
if not email_address:
|
|
messages.error(request, _('Email address is required.'))
|
|
return redirect('slp:progress_report_detail', pk=pk)
|
|
|
|
pdf_generator = SLPProgressReportPDFGenerator(report, request)
|
|
subject = f"SLP Progress Report - {report.patient.mrn}"
|
|
body = f"""Dear {report.patient.first_name_en},
|
|
|
|
Please find attached your SLP progress report.
|
|
|
|
Best regards,
|
|
{report.tenant.name}"""
|
|
|
|
success, msg = pdf_generator.send_email(email_address, subject, body, request.POST.get('email_message', ''))
|
|
messages.success(request, _('PDF sent successfully!')) if success else messages.error(request, f'Error: {msg}')
|
|
return redirect('slp:progress_report_detail', pk=pk)
|
|
|
|
|
|
class SLPConsultDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView):
|
|
"""
|
|
SLP consultation detail view (SLP-F-1).
|
|
|
|
Features:
|
|
- Full consultation details
|
|
- Primary concern, suspected areas
|
|
- Type of service (Consult/Eval/Intervention/ParentTraining)
|
|
- Communication modes, screen time
|
|
- Variant-specific questionnaires (ASD/LanguageDelay/Fluency)
|
|
- Skills-to-observe matrices
|
|
- Oral-motor screening
|
|
"""
|
|
model = SLPConsult
|
|
template_name = 'slp/consultation_detail.html'
|
|
context_object_name = 'consultation'
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""Add related assessments and interventions."""
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
# Get related assessments
|
|
context['assessments'] = SLPAssessment.objects.filter(
|
|
patient=self.object.patient
|
|
).order_by('-assessment_date')[:5]
|
|
|
|
# Get related interventions
|
|
context['interventions'] = SLPIntervention.objects.filter(
|
|
patient=self.object.patient
|
|
).order_by('-session_date')[:10]
|
|
|
|
return context
|
|
|
|
|
|
class SLPConsultCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin,
|
|
AuditLogMixin, SuccessMessageMixin, CreateView):
|
|
"""
|
|
SLP consultation creation view (SLP-F-1).
|
|
|
|
Features:
|
|
- Base consultation + subtype flags (ASD/LanguageDelay/Fluency)
|
|
- Primary concern, suspected areas
|
|
- Type of service
|
|
- Communication modes, screen time
|
|
- Variant-specific questionnaires
|
|
- Skills-to-observe matrices
|
|
- Oral-motor screening
|
|
- Consent verification enforced before creation
|
|
"""
|
|
model = SLPConsult
|
|
form_class = SLPConsultForm
|
|
template_name = 'slp/consult_form.html'
|
|
success_message = _("SLP consultation recorded successfully!")
|
|
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
|
|
|
|
# Consent enforcement
|
|
consent_service_type = 'SLP'
|
|
consent_error_message = _(
|
|
"Patient must sign SLP therapy consent before consultation can be documented."
|
|
)
|
|
|
|
def get_success_url(self):
|
|
"""Redirect to consult detail."""
|
|
return reverse_lazy('slp:consult_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."""
|
|
# 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()
|
|
|
|
# Save consultation
|
|
response = super().form_valid(form)
|
|
|
|
return response
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""Add form title and patient/appointment info."""
|
|
context = super().get_context_data(**kwargs)
|
|
context['form_title'] = _('SLP Consultation (SLP-F-1)')
|
|
context['submit_text'] = _('Save Consultation')
|
|
|
|
# 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 SLPConsultUpdateView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin,
|
|
AuditLogMixin, SuccessMessageMixin, UpdateView):
|
|
"""SLP consultation update view (SLP-F-1)."""
|
|
model = SLPConsult
|
|
form_class = SLPConsultForm
|
|
template_name = 'slp/consult_form.html'
|
|
success_message = _("SLP consultation updated successfully!")
|
|
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy('slp:consult_detail', kwargs={'pk': self.object.pk})
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['form_title'] = _('Update SLP Consultation - %(mrn)s') % {'mrn': self.object.patient.mrn}
|
|
context['submit_text'] = _('Update Consultation')
|
|
if hasattr(self.object, 'history'):
|
|
context['history'] = self.object.history.all()[:10]
|
|
return context
|
|
|
|
|
|
class SLPAssessmentListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin,
|
|
HTMXResponseMixin, ListView):
|
|
"""
|
|
SLP assessment list view (SLP-F-2).
|
|
|
|
Features:
|
|
- Filter by patient, provider, date range
|
|
- Search by patient name/MRN
|
|
- Role-based filtering
|
|
"""
|
|
model = SLPAssessment
|
|
template_name = 'slp/assessment_list.html'
|
|
htmx_template_name = 'slp/partials/assessment_list_partial.html'
|
|
context_object_name = 'assessments'
|
|
paginate_by = 25
|
|
|
|
def get_queryset(self):
|
|
"""Get filtered queryset."""
|
|
queryset = super().get_queryset()
|
|
user = self.request.user
|
|
|
|
if user.role == User.Role.SLP:
|
|
queryset = queryset.filter(provider__user=user)
|
|
|
|
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)
|
|
)
|
|
|
|
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(assessment_date__gte=date_from)
|
|
|
|
date_to = self.request.GET.get('date_to')
|
|
if date_to:
|
|
queryset = queryset.filter(assessment_date__lte=date_to)
|
|
|
|
return queryset.select_related('patient', 'provider', 'appointment', 'signed_by').order_by('-assessment_date')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""Add unsigned assessments count for current user."""
|
|
context = super().get_context_data(**kwargs)
|
|
user = self.request.user
|
|
|
|
unsigned_query = SLPAssessment.objects.filter(tenant=user.tenant, signed_by__isnull=True)
|
|
if user.role == User.Role.SLP:
|
|
unsigned_query = unsigned_query.filter(provider=user)
|
|
|
|
context['unsigned_count'] = unsigned_query.count()
|
|
context['unsigned_items'] = unsigned_query.select_related('patient', 'provider').order_by('-assessment_date')[:10]
|
|
return context
|
|
|
|
|
|
class SLPAssessmentDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView):
|
|
"""
|
|
SLP assessment detail view (SLP-F-2).
|
|
|
|
Features:
|
|
- Diagnosis statement, case history
|
|
- Prenatal/perinatal/postnatal history
|
|
- Developmental history
|
|
- Medical status
|
|
- Speech/language detail (GFTA-3/JAT, SSI scores)
|
|
- Oral mechanism
|
|
- Language skills via Rossetti scale (domain/age levels)
|
|
- Joint-attention skills (present/absent) summaries
|
|
- Clinical summary
|
|
- Recommendations (frequency 1-2x/wk, 45-60 mins)
|
|
- Plan & referral rules
|
|
"""
|
|
model = SLPAssessment
|
|
template_name = 'slp/assessment_detail.html'
|
|
context_object_name = 'assessment'
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""Add related interventions."""
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
# Get related interventions
|
|
context['interventions'] = SLPIntervention.objects.filter(
|
|
patient=self.object.patient,
|
|
session_date__gte=self.object.assessment_date
|
|
).order_by('-session_date')[:10]
|
|
|
|
return context
|
|
|
|
|
|
class SLPAssessmentCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin,
|
|
AuditLogMixin, SuccessMessageMixin, CreateView):
|
|
"""
|
|
SLP assessment creation view (SLP-F-2).
|
|
|
|
Features:
|
|
- Complete assessment/reassessment form
|
|
- All sections from diagnosis to recommendations
|
|
- Consent verification enforced before creation
|
|
"""
|
|
model = SLPAssessment
|
|
form_class = SLPAssessmentForm
|
|
template_name = 'slp/assessment_form.html'
|
|
success_message = _("SLP assessment recorded successfully!")
|
|
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
|
|
|
|
# Consent enforcement
|
|
consent_service_type = 'SLP'
|
|
consent_error_message = _(
|
|
"Patient must sign SLP therapy consent before assessment can be documented."
|
|
)
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy('slp:assessment_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['assessment_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['assessment_date'] = timezone.now().date()
|
|
|
|
return initial
|
|
|
|
def form_valid(self, form):
|
|
form.instance.tenant = self.request.user.tenant
|
|
if not form.instance.provider:
|
|
form.instance.provider = self.request.user
|
|
if not form.instance.assessment_date:
|
|
form.instance.assessment_date = timezone.now().date()
|
|
return super().form_valid(form)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['form_title'] = _('SLP Assessment (SLP-F-2)')
|
|
context['submit_text'] = _('Save Assessment')
|
|
|
|
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
|
|
|
|
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 SLPAssessmentUpdateView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin,
|
|
AuditLogMixin, SuccessMessageMixin, UpdateView):
|
|
"""SLP assessment update view (SLP-F-2)."""
|
|
model = SLPAssessment
|
|
form_class = SLPAssessmentForm
|
|
template_name = 'slp/assessment_form.html'
|
|
success_message = _("SLP assessment updated successfully!")
|
|
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy('slp:assessment_detail', kwargs={'pk': self.object.pk})
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['form_title'] = _('Update SLP Assessment - %(mrn)s') % {'mrn': self.object.patient.mrn}
|
|
context['submit_text'] = _('Update Assessment')
|
|
if hasattr(self.object, 'history'):
|
|
context['history'] = self.object.history.all()[:10]
|
|
return context
|
|
|
|
|
|
class SLPInterventionListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin,
|
|
HTMXResponseMixin, ListView):
|
|
"""
|
|
SLP intervention list view (SLP-F-3).
|
|
|
|
Features:
|
|
- Filter by patient, provider, date range
|
|
- Search by patient name/MRN
|
|
- Role-based filtering
|
|
"""
|
|
model = SLPIntervention
|
|
template_name = 'slp/intervention_list.html'
|
|
htmx_template_name = 'slp/partials/intervention_list_partial.html'
|
|
context_object_name = 'interventions'
|
|
paginate_by = 25
|
|
|
|
def get_queryset(self):
|
|
queryset = super().get_queryset()
|
|
user = self.request.user
|
|
|
|
if user.role == User.Role.SLP:
|
|
queryset = queryset.filter(provider__user=user)
|
|
|
|
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)
|
|
)
|
|
|
|
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(session_date__gte=date_from)
|
|
|
|
date_to = self.request.GET.get('date_to')
|
|
if date_to:
|
|
queryset = queryset.filter(session_date__lte=date_to)
|
|
|
|
return queryset.select_related('patient', 'provider', 'appointment', 'previous_session', 'signed_by').order_by('-session_date')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""Add unsigned interventions count for current user."""
|
|
context = super().get_context_data(**kwargs)
|
|
user = self.request.user
|
|
|
|
unsigned_query = SLPIntervention.objects.filter(tenant=user.tenant, signed_by__isnull=True)
|
|
if user.role == User.Role.SLP:
|
|
unsigned_query = unsigned_query.filter(provider=user)
|
|
|
|
context['unsigned_count'] = unsigned_query.count()
|
|
context['unsigned_items'] = unsigned_query.select_related('patient', 'provider').order_by('-session_date')[:10]
|
|
return context
|
|
|
|
|
|
class SLPInterventionDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView):
|
|
"""
|
|
SLP intervention detail view (SLP-F-3).
|
|
|
|
Features:
|
|
- Session number/time
|
|
- Target#1/2 entries with SOAP (Subjective/Objective/Assessment/Plan)
|
|
- Prompt strategies
|
|
- Preview previous SLP-F-3 relation
|
|
"""
|
|
model = SLPIntervention
|
|
template_name = 'slp/intervention_detail.html'
|
|
context_object_name = 'intervention'
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
# Get targets
|
|
context['targets'] = self.object.targets.all()
|
|
|
|
# Get previous session if linked
|
|
if self.object.previous_session:
|
|
context['previous_session'] = self.object.previous_session
|
|
context['previous_targets'] = self.object.previous_session.targets.all()
|
|
|
|
return context
|
|
|
|
|
|
class SLPInterventionCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin,
|
|
AuditLogMixin, SuccessMessageMixin, CreateView):
|
|
"""
|
|
SLP intervention creation view (SLP-F-3).
|
|
|
|
Features:
|
|
- Session number/time
|
|
- Target entries with SOAP + prompt strategies
|
|
- Link to previous session
|
|
- Consent verification enforced before creation
|
|
"""
|
|
model = SLPIntervention
|
|
form_class = SLPInterventionForm
|
|
template_name = 'slp/intervention_form.html'
|
|
success_message = _("SLP intervention recorded successfully!")
|
|
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
|
|
|
|
# Consent enforcement
|
|
consent_service_type = 'SLP'
|
|
consent_error_message = _(
|
|
"Patient must sign SLP therapy consent before intervention can be documented."
|
|
)
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy('slp:intervention_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['session_date'] = appointment.appointment_date
|
|
initial['session_time'] = appointment.start_time
|
|
initial['provider'] = self.request.user.pk
|
|
|
|
# Auto-calculate session number based on previous sessions
|
|
previous_count = SLPIntervention.objects.filter(
|
|
patient=appointment.patient,
|
|
tenant=self.request.user.tenant
|
|
).count()
|
|
initial['session_number'] = previous_count + 1
|
|
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['session_date'] = timezone.now().date()
|
|
initial['session_time'] = timezone.now().time()
|
|
|
|
# Auto-calculate session number
|
|
try:
|
|
patient = Patient.objects.get(pk=patient_id, tenant=self.request.user.tenant)
|
|
previous_count = SLPIntervention.objects.filter(
|
|
patient=patient,
|
|
tenant=self.request.user.tenant
|
|
).count()
|
|
initial['session_number'] = previous_count + 1
|
|
except Patient.DoesNotExist:
|
|
pass
|
|
|
|
return initial
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['form_title'] = _('SLP Intervention (SLP-F-3)')
|
|
context['submit_text'] = _('Save Intervention')
|
|
|
|
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 previous sessions for linking
|
|
context['previous_sessions'] = SLPIntervention.objects.filter(
|
|
patient=patient
|
|
).order_by('-session_date')[:10]
|
|
except Patient.DoesNotExist:
|
|
pass
|
|
|
|
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 previous sessions for this patient
|
|
context['previous_sessions'] = SLPIntervention.objects.filter(
|
|
patient=context['appointment'].patient
|
|
).order_by('-session_date')[:10]
|
|
except Appointment.DoesNotExist:
|
|
pass
|
|
|
|
return context
|
|
|
|
def form_valid(self, form):
|
|
form.instance.tenant = self.request.user.tenant
|
|
if not form.instance.provider:
|
|
form.instance.provider = self.request.user
|
|
if not form.instance.session_date:
|
|
form.instance.session_date = timezone.now().date()
|
|
|
|
return super().form_valid(form)
|
|
|
|
|
|
class SLPInterventionUpdateView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin,
|
|
AuditLogMixin, SuccessMessageMixin, UpdateView):
|
|
"""SLP intervention update view (SLP-F-3)."""
|
|
model = SLPIntervention
|
|
form_class = SLPInterventionForm
|
|
template_name = 'slp/intervention_form.html'
|
|
success_message = _("SLP intervention updated successfully!")
|
|
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy('slp:intervention_detail', kwargs={'pk': self.object.pk})
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['form_title'] = _('Update SLP Intervention - %(mrn)s') % {'mrn': self.object.patient.mrn}
|
|
context['submit_text'] = _('Update Intervention')
|
|
|
|
if hasattr(self.object, 'history'):
|
|
context['history'] = self.object.history.all()[:10]
|
|
return context
|
|
|
|
|
|
class SLPProgressReportListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin, ListView):
|
|
"""
|
|
SLP progress report list view (SLP-F-4).
|
|
|
|
Features:
|
|
- Filter by patient, provider, date range
|
|
- Search by patient name/MRN
|
|
"""
|
|
model = SLPProgressReport
|
|
template_name = 'slp/progress_list.html'
|
|
context_object_name = 'progress_reports'
|
|
paginate_by = 25
|
|
|
|
def get_queryset(self):
|
|
queryset = super().get_queryset()
|
|
user = self.request.user
|
|
|
|
if user.role == User.Role.SLP:
|
|
queryset = queryset.filter(provider__user=user)
|
|
|
|
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)
|
|
)
|
|
|
|
patient_id = self.request.GET.get('patient')
|
|
if patient_id:
|
|
queryset = queryset.filter(patient_id=patient_id)
|
|
|
|
return queryset.select_related('patient', 'provider', 'signed_by').order_by('-report_date')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""Add unsigned progress reports count for current user."""
|
|
context = super().get_context_data(**kwargs)
|
|
user = self.request.user
|
|
|
|
unsigned_query = SLPProgressReport.objects.filter(tenant=user.tenant, signed_by__isnull=True)
|
|
if user.role == User.Role.SLP:
|
|
unsigned_query = unsigned_query.filter(provider=user)
|
|
|
|
context['unsigned_count'] = unsigned_query.count()
|
|
context['unsigned_items'] = unsigned_query.select_related('patient', 'provider').order_by('-report_date')[:10]
|
|
return context
|
|
|
|
|
|
class SLPProgressReportDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView):
|
|
"""
|
|
SLP progress report detail view (SLP-F-4).
|
|
|
|
Features:
|
|
- Demographic snapshot
|
|
- Sessions scheduled/attended
|
|
- Final diagnosis
|
|
- Objectives & progress (% accuracy)
|
|
- Plan (continue/add/fade prompts/generalization)
|
|
- Overall progress, participation, attendance, carryover
|
|
- Prognosis, recommendations with package & sessions count
|
|
- Reassessment tick
|
|
"""
|
|
model = SLPProgressReport
|
|
template_name = 'slp/progress_detail.html'
|
|
context_object_name = 'report'
|
|
|
|
|
|
class SLPProgressReportCreateView(LoginRequiredMixin, RolePermissionMixin, AuditLogMixin,
|
|
SuccessMessageMixin, CreateView):
|
|
"""
|
|
SLP progress report creation view (SLP-F-4).
|
|
|
|
Features:
|
|
- Complete progress report
|
|
- Aggregates sessions and produces goals with % accuracy
|
|
"""
|
|
model = SLPProgressReport
|
|
form_class = SLPProgressReportForm
|
|
template_name = 'slp/progress_form.html'
|
|
success_message = _("SLP progress report created successfully!")
|
|
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy('slp:progress_report_detail', kwargs={'pk': self.object.pk})
|
|
|
|
def form_valid(self, form):
|
|
form.instance.tenant = self.request.user.tenant
|
|
form.instance.provider = self.request.user
|
|
if not form.instance.report_date:
|
|
form.instance.report_date = timezone.now().date()
|
|
return super().form_valid(form)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['form_title'] = _('SLP Progress Report (SLP-F-4)')
|
|
context['submit_text'] = _('Save Report')
|
|
|
|
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 session statistics
|
|
interventions = SLPIntervention.objects.filter(patient=patient)
|
|
context['session_stats'] = {
|
|
'total_sessions': interventions.count(),
|
|
'date_range': {
|
|
'start': interventions.order_by('session_date').first().session_date if interventions.exists() else None,
|
|
'end': interventions.order_by('-session_date').first().session_date if interventions.exists() else None,
|
|
}
|
|
}
|
|
except Patient.DoesNotExist:
|
|
pass
|
|
|
|
return context
|
|
|
|
|
|
class SLPProgressReportUpdateView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin,
|
|
AuditLogMixin, SuccessMessageMixin, UpdateView):
|
|
"""SLP progress report update view (SLP-F-4)."""
|
|
model = SLPProgressReport
|
|
form_class = SLPProgressReportForm
|
|
template_name = 'slp/progress_form.html'
|
|
success_message = _("SLP progress report updated successfully!")
|
|
allowed_roles = [User.Role.ADMIN, User.Role.SLP]
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy('slp:progress_report_detail', kwargs={'pk': self.object.pk})
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['form_title'] = _('Update SLP Progress Report - %(mrn)s') % {'mrn': self.object.patient.mrn}
|
|
context['submit_text'] = _('Update Report')
|
|
if hasattr(self.object, 'history'):
|
|
context['history'] = self.object.history.all()[:10]
|
|
return context
|
|
|
|
|
|
class PatientProgressView(LoginRequiredMixin, TenantFilterMixin, DetailView):
|
|
"""
|
|
Patient progress overview view.
|
|
|
|
Shows all SLP documentation for a specific patient:
|
|
- Progress reports
|
|
- Assessments
|
|
- Interventions
|
|
- Consultations
|
|
"""
|
|
model = Patient
|
|
template_name = 'slp/patient_progress.html'
|
|
context_object_name = 'patient'
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
patient = self.object
|
|
|
|
# Get all SLP documentation for this patient
|
|
context['progress_reports'] = SLPProgressReport.objects.filter(
|
|
patient=patient
|
|
).select_related('provider').order_by('-report_date')
|
|
|
|
context['assessments'] = SLPAssessment.objects.filter(
|
|
patient=patient
|
|
).select_related('provider').order_by('-assessment_date')
|
|
|
|
context['interventions'] = SLPIntervention.objects.filter(
|
|
patient=patient
|
|
).select_related('provider').order_by('-session_date')[:20]
|
|
|
|
context['consults'] = SLPConsult.objects.filter(
|
|
patient=patient
|
|
).select_related('provider').order_by('-consultation_date')
|
|
|
|
# Calculate statistics
|
|
interventions_all = SLPIntervention.objects.filter(patient=patient)
|
|
context['stats'] = {
|
|
'total_sessions': interventions_all.count(),
|
|
'total_reports': context['progress_reports'].count(),
|
|
'total_assessments': context['assessments'].count(),
|
|
'total_consults': context['consults'].count(),
|
|
}
|
|
|
|
return context
|