1882 lines
66 KiB
Python
1882 lines
66 KiB
Python
"""
|
||
Views for EMR app.
|
||
"""
|
||
|
||
from django.shortcuts import render, get_object_or_404, redirect
|
||
from django.contrib.auth.decorators import login_required
|
||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||
from django.urls import reverse_lazy, reverse
|
||
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView
|
||
from django.http import JsonResponse, HttpResponse, Http404
|
||
from django.utils.translation import gettext_lazy as _
|
||
from django.utils import timezone
|
||
from django.contrib import messages
|
||
from django.core.paginator import Paginator
|
||
from django.template.loader import render_to_string
|
||
from datetime import datetime, timedelta, date
|
||
import json
|
||
from django.contrib.messages.views import SuccessMessageMixin
|
||
from core.models import AuditLogEntry
|
||
from core.utils import AuditLogger
|
||
from patients.models import PatientProfile
|
||
from .models import *
|
||
from .forms import *
|
||
from core.mixins import TenantMixin, FormKwargsMixin
|
||
from django.utils.dateformat import format as dj_format
|
||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||
from django.db.models import *
|
||
|
||
|
||
|
||
|
||
class EMRDashboardView(LoginRequiredMixin, ListView):
|
||
"""
|
||
Main dashboard for EMR management.
|
||
"""
|
||
template_name = 'emr/dashboard.html'
|
||
context_object_name = 'encounters'
|
||
|
||
def get_queryset(self):
|
||
"""Get recent encounters for current tenant."""
|
||
return Encounter.objects.filter(
|
||
tenant=self.request.user.tenant
|
||
).select_related('patient', 'provider').order_by('-start_datetime')[:10]
|
||
|
||
def get_context_data(self, **kwargs):
|
||
context = super().get_context_data(**kwargs)
|
||
tenant = self.request.user.tenant
|
||
today = timezone.now().date()
|
||
|
||
# Dashboard statistics
|
||
context.update({
|
||
'total_encounters': Encounter.objects.filter(tenant=tenant).count(),
|
||
'active_encounters': Encounter.objects.filter(
|
||
tenant=tenant,
|
||
status__in=['ARRIVED', 'TRIAGED', 'IN_PROGRESS', 'ON_HOLD']
|
||
).count(),
|
||
'todays_encounters': Encounter.objects.filter(
|
||
tenant=tenant,
|
||
start_datetime__date=today
|
||
).count(),
|
||
'pending_documentation': Encounter.objects.filter(
|
||
tenant=tenant,
|
||
documentation_complete=False,
|
||
status='FINISHED'
|
||
).count(),
|
||
'unsigned_notes': ClinicalNote.objects.filter(
|
||
patient__tenant=tenant,
|
||
status='COMPLETED',
|
||
electronically_signed=False
|
||
).count(),
|
||
'active_problems': ProblemList.objects.filter(
|
||
tenant=tenant,
|
||
status='ACTIVE'
|
||
).count(),
|
||
'active_care_plans': CarePlan.objects.filter(
|
||
tenant=tenant,
|
||
status='ACTIVE'
|
||
).count(),
|
||
'critical_vitals': VitalSigns.objects.filter(
|
||
patient__tenant=tenant,
|
||
measured_datetime__date=today
|
||
).exclude(critical_values=[]).count(),
|
||
})
|
||
|
||
return context
|
||
|
||
|
||
class EncounterListView(LoginRequiredMixin, ListView):
|
||
"""
|
||
List view for encounters.
|
||
"""
|
||
model = Encounter
|
||
template_name = 'emr/encounters/encounter_list.html'
|
||
context_object_name = 'encounters'
|
||
paginate_by = 25
|
||
|
||
def get_queryset(self):
|
||
queryset = Encounter.objects.filter(tenant=self.request.user.tenant)
|
||
|
||
# Search functionality
|
||
search = self.request.GET.get('search')
|
||
if search:
|
||
queryset = queryset.filter(
|
||
Q(patient__first_name__icontains=search) |
|
||
Q(patient__last_name__icontains=search) |
|
||
Q(patient__mrn__icontains=search) |
|
||
Q(provider__first_name__icontains=search) |
|
||
Q(provider__last_name__icontains=search) |
|
||
Q(chief_complaint__icontains=search)
|
||
)
|
||
|
||
# Filter by encounter type
|
||
encounter_type = self.request.GET.get('encounter_type')
|
||
if encounter_type:
|
||
queryset = queryset.filter(encounter_type=encounter_type)
|
||
|
||
# Filter by status
|
||
status = self.request.GET.get('status')
|
||
if status:
|
||
queryset = queryset.filter(status=status)
|
||
|
||
# Filter by provider
|
||
provider_id = self.request.GET.get('provider')
|
||
if provider_id:
|
||
queryset = queryset.filter(provider_id=provider_id)
|
||
|
||
# Filter by date range
|
||
date_from = self.request.GET.get('date_from')
|
||
date_to = self.request.GET.get('date_to')
|
||
if date_from:
|
||
queryset = queryset.filter(start_datetime__date__gte=date_from)
|
||
if date_to:
|
||
queryset = queryset.filter(start_datetime__date__lte=date_to)
|
||
|
||
return queryset.select_related('patient', 'provider').order_by('-start_datetime')
|
||
|
||
def get_context_data(self, **kwargs):
|
||
context = super().get_context_data(**kwargs)
|
||
context.update({
|
||
'encounter_types': Encounter._meta.get_field('encounter_type').choices,
|
||
'encounter_statuses': Encounter._meta.get_field('status').choices,
|
||
})
|
||
return context
|
||
|
||
|
||
class EncounterCreateView(LoginRequiredMixin, CreateView):
|
||
model = Encounter
|
||
form_class = EncounterForm
|
||
template_name = 'emr/encounters/encounter_create.html'
|
||
success_message = _('Encounter for %(patient)s created successfully.')
|
||
|
||
def form_valid(self, form):
|
||
form.instance.tenant = self.request.user.tenant
|
||
response = super().form_valid(form)
|
||
AuditLogEntry.objects.create(
|
||
tenant=self.request.user.tenant,
|
||
user=self.request.user,
|
||
action='CREATE',
|
||
model_name='Encounter',
|
||
object_id=str(self.object.pk),
|
||
changes={'status': 'Encounter created'}
|
||
)
|
||
return response
|
||
|
||
def get_success_url(self):
|
||
return reverse_lazy('emr:encounter_detail', kwargs={'pk': self.object.pk})
|
||
|
||
|
||
class EncounterDetailView(LoginRequiredMixin, DetailView):
|
||
"""
|
||
Detail view for encounter.
|
||
"""
|
||
model = Encounter
|
||
template_name = 'emr/encounters/encounter_detail.html'
|
||
context_object_name = 'encounter'
|
||
|
||
def get_queryset(self):
|
||
return Encounter.objects.filter(tenant=self.request.user.tenant)
|
||
|
||
def get_context_data(self, **kwargs):
|
||
context = super().get_context_data(**kwargs)
|
||
encounter = self.object
|
||
|
||
# Get related data
|
||
context.update({
|
||
'vital_signs': encounter.vital_signs.all().order_by('-measured_datetime'),
|
||
'clinical_notes': encounter.clinical_notes.all().order_by('-note_datetime'),
|
||
'problems': encounter.problems_identified.all().order_by('-created_at'),
|
||
})
|
||
|
||
return context
|
||
|
||
|
||
class VitalSignsListView(LoginRequiredMixin, ListView):
|
||
"""
|
||
List view for vital signs.
|
||
"""
|
||
model = VitalSigns
|
||
template_name = 'emr/vital_signs_list.html'
|
||
context_object_name = 'vital_signs'
|
||
paginate_by = 25
|
||
|
||
def get_queryset(self):
|
||
queryset = VitalSigns.objects.filter(patient__tenant=self.request.user.tenant)
|
||
|
||
# Filter by patient
|
||
patient_id = self.request.GET.get('patient')
|
||
if patient_id:
|
||
queryset = queryset.filter(patient_id=patient_id)
|
||
|
||
# Filter by encounter
|
||
encounter_id = self.request.GET.get('encounter')
|
||
if encounter_id:
|
||
queryset = queryset.filter(encounter_id=encounter_id)
|
||
|
||
# Filter by date range
|
||
date_from = self.request.GET.get('date_from')
|
||
date_to = self.request.GET.get('date_to')
|
||
if date_from:
|
||
queryset = queryset.filter(measured_datetime__date__gte=date_from)
|
||
if date_to:
|
||
queryset = queryset.filter(measured_datetime__date__lte=date_to)
|
||
|
||
# Search functionality
|
||
search = self.request.GET.get('search')
|
||
if search:
|
||
queryset = queryset.filter(
|
||
Q(patient__first_name__icontains=search) |
|
||
Q(patient__last_name__icontains=search) |
|
||
Q(patient__mrn__icontains=search)
|
||
)
|
||
|
||
return queryset.select_related('patient', 'encounter', 'measured_by').order_by('-measured_datetime')
|
||
|
||
|
||
class ProblemListView(LoginRequiredMixin, ListView):
|
||
"""
|
||
List view for problem list.
|
||
"""
|
||
model = ProblemList
|
||
template_name = 'emr/problems/problem_list.html'
|
||
context_object_name = 'problems'
|
||
paginate_by = 25
|
||
|
||
def get_queryset(self):
|
||
queryset = ProblemList.objects.filter(tenant=self.request.user.tenant)
|
||
|
||
# Filter by patient
|
||
patient_id = self.request.GET.get('patient')
|
||
if patient_id:
|
||
queryset = queryset.filter(patient_id=patient_id)
|
||
|
||
# Filter by status
|
||
status = self.request.GET.get('status')
|
||
if status:
|
||
queryset = queryset.filter(status=status)
|
||
|
||
# Filter by problem type
|
||
problem_type = self.request.GET.get('problem_type')
|
||
if problem_type:
|
||
queryset = queryset.filter(problem_type=problem_type)
|
||
|
||
# Filter by priority
|
||
priority = self.request.GET.get('priority')
|
||
if priority:
|
||
queryset = queryset.filter(priority=priority)
|
||
|
||
# Search functionality
|
||
search = self.request.GET.get('search')
|
||
if search:
|
||
queryset = queryset.filter(
|
||
Q(patient__first_name__icontains=search) |
|
||
Q(patient__last_name__icontains=search) |
|
||
Q(patient__mrn__icontains=search) |
|
||
Q(problem_name__icontains=search) |
|
||
Q(problem_code__icontains=search)
|
||
)
|
||
|
||
return queryset.select_related(
|
||
'patient', 'diagnosing_provider', 'managing_provider'
|
||
).order_by('-created_at')
|
||
|
||
def get_context_data(self, **kwargs):
|
||
context = super().get_context_data(**kwargs)
|
||
context.update({
|
||
'problem_types': ProblemList._meta.get_field('problem_type').choices,
|
||
'problem_statuses': ProblemList._meta.get_field('status').choices,
|
||
'priorities': ProblemList._meta.get_field('priority').choices,
|
||
})
|
||
return context
|
||
|
||
|
||
class CarePlanListView(LoginRequiredMixin, ListView):
|
||
"""
|
||
List view for care plans.
|
||
"""
|
||
model = CarePlan
|
||
template_name = 'emr/care_plans/care_plan_list.html'
|
||
context_object_name = 'care_plans'
|
||
paginate_by = 25
|
||
|
||
def get_queryset(self):
|
||
queryset = CarePlan.objects.filter(tenant=self.request.user.tenant)
|
||
|
||
# Filter by patient
|
||
patient_id = self.request.GET.get('patient')
|
||
if patient_id:
|
||
queryset = queryset.filter(patient_id=patient_id)
|
||
|
||
# Filter by status
|
||
status = self.request.GET.get('status')
|
||
if status:
|
||
queryset = queryset.filter(status=status)
|
||
|
||
# Filter by plan type
|
||
plan_type = self.request.GET.get('plan_type')
|
||
if plan_type:
|
||
queryset = queryset.filter(plan_type=plan_type)
|
||
|
||
# Filter by provider
|
||
provider_id = self.request.GET.get('provider')
|
||
if provider_id:
|
||
queryset = queryset.filter(primary_provider_id=provider_id)
|
||
|
||
# Search functionality
|
||
search = self.request.GET.get('search')
|
||
if search:
|
||
queryset = queryset.filter(
|
||
Q(patient__first_name__icontains=search) |
|
||
Q(patient__last_name__icontains=search) |
|
||
Q(patient__mrn__icontains=search) |
|
||
Q(title__icontains=search) |
|
||
Q(description__icontains=search)
|
||
)
|
||
|
||
return queryset.select_related('patient', 'primary_provider').order_by('-created_at')
|
||
|
||
def get_context_data(self, **kwargs):
|
||
context = super().get_context_data(**kwargs)
|
||
context.update({
|
||
'plan_types': CarePlan._meta.get_field('plan_type').choices,
|
||
'plan_statuses': CarePlan._meta.get_field('status').choices,
|
||
})
|
||
return context
|
||
|
||
|
||
class ClinicalNoteListView(LoginRequiredMixin, ListView):
|
||
"""
|
||
List view for clinical notes.
|
||
"""
|
||
model = ClinicalNote
|
||
template_name = 'emr/clinical_notes/clinical_note_list.html'
|
||
context_object_name = 'notes'
|
||
paginate_by = 25
|
||
|
||
def get_queryset(self):
|
||
queryset = ClinicalNote.objects.filter(patient__tenant=self.request.user.tenant)
|
||
|
||
# Filter by patient
|
||
patient_id = self.request.GET.get('patient')
|
||
if patient_id:
|
||
queryset = queryset.filter(patient_id=patient_id)
|
||
|
||
# Filter by note type
|
||
note_type = self.request.GET.get('note_type')
|
||
if note_type:
|
||
queryset = queryset.filter(note_type=note_type)
|
||
|
||
# Filter by status
|
||
status = self.request.GET.get('status')
|
||
if status:
|
||
queryset = queryset.filter(status=status)
|
||
|
||
# Filter by author
|
||
author_id = self.request.GET.get('author')
|
||
if author_id:
|
||
queryset = queryset.filter(author_id=author_id)
|
||
|
||
# Search functionality
|
||
search = self.request.GET.get('search')
|
||
if search:
|
||
queryset = queryset.filter(
|
||
Q(patient__first_name__icontains=search) |
|
||
Q(patient__last_name__icontains=search) |
|
||
Q(patient__mrn__icontains=search) |
|
||
Q(title__icontains=search) |
|
||
Q(content__icontains=search)
|
||
)
|
||
|
||
return queryset.select_related('patient', 'encounter', 'author').order_by('-note_datetime')
|
||
|
||
def get_context_data(self, **kwargs):
|
||
context = super().get_context_data(**kwargs)
|
||
context.update({
|
||
'note_types': ClinicalNote._meta.get_field('note_type').choices,
|
||
'note_statuses': ClinicalNote._meta.get_field('status').choices,
|
||
})
|
||
return context
|
||
|
||
|
||
class ClinicalNoteDetailView(LoginRequiredMixin, TenantMixin, DetailView):
|
||
model = ClinicalNote
|
||
template_name = 'emr/clinical_notes/clinical_note_detail.html'
|
||
context_object_name = 'clinical_note'
|
||
|
||
|
||
class ClinicalNoteCreateView(LoginRequiredMixin, CreateView):
|
||
model = ClinicalNote
|
||
form_class = ClinicalNoteForm
|
||
template_name = 'emr/clinical_notes/clinical_note_form.html'
|
||
success_message = _('Clinical note created successfully.')
|
||
|
||
def form_valid(self, form):
|
||
form.instance.author = self.request.user
|
||
response = super().form_valid(form)
|
||
AuditLogEntry.objects.create(
|
||
tenant=self.request.user.tenant,
|
||
user=self.request.user,
|
||
action='CREATE',
|
||
model_name='ClinicalNote',
|
||
object_id=str(self.object.pk),
|
||
changes={'status': 'Clinical note created'}
|
||
)
|
||
return response
|
||
|
||
def get_success_url(self):
|
||
return reverse_lazy('emr:clinical_note_detail', kwargs={'pk': self.object.pk})
|
||
|
||
|
||
class ClinicalNoteUpdateView(LoginRequiredMixin,UpdateView):
|
||
model = ClinicalNote
|
||
form_class = ClinicalNoteForm
|
||
template_name = 'emr/clinical_notes/clinical_note_form.html'
|
||
|
||
success_message = _('Clinical note updated successfully.')
|
||
|
||
def dispatch(self, request, *args, **kwargs):
|
||
# Ensure tenant exists
|
||
self.tenant = request.user.tenant
|
||
if not self.tenant:
|
||
return JsonResponse({"error": "No tenant found"}, status=400)
|
||
|
||
return super().dispatch(request, *args, **kwargs)
|
||
|
||
def form_valid(self, form):
|
||
response = super().form_valid(form)
|
||
|
||
AuditLogEntry.objects.create(
|
||
tenant=self.request.user.tenant,
|
||
user=self.request.user,
|
||
action='UPDATE',
|
||
model_name='ClinicalNote',
|
||
object_id=str(self.object.pk),
|
||
changes={'status': 'Clinical note updated'}
|
||
)
|
||
return response
|
||
|
||
def get_success_url(self):
|
||
return reverse_lazy('emr:clinical_note_detail', kwargs={'pk': self.object.pk})
|
||
|
||
def get_context_data(self, **kwargs):
|
||
ctx = super().get_context_data(**kwargs)
|
||
encounter = get_object_or_404(Encounter, pk=self.kwargs['pk'], tenant=self.tenant)
|
||
|
||
ctx['patient'] = encounter.patient
|
||
ctx['encounter'] = encounter
|
||
ctx['author'] = self.request.user # or Employee profile if you have one
|
||
return ctx
|
||
|
||
|
||
class ClinicalNoteDeleteView(LoginRequiredMixin,SuccessMessageMixin, DeleteView):
|
||
model = ClinicalNote
|
||
template_name = 'emr/clinical_notes/clinical_note_confirm_delete.html'
|
||
success_url = reverse_lazy('emr:clinical_note_list')
|
||
success_message = _('Clinical note deleted successfully.')
|
||
|
||
def delete(self, request, *args, **kwargs):
|
||
cn = self.get_object()
|
||
AuditLogEntry.objects.create(
|
||
tenant=request.user.tenant,
|
||
user=request.user,
|
||
action='DELETE',
|
||
model_name='ClinicalNote',
|
||
object_id=str(cn.pk),
|
||
changes={'status': 'Clinical note deleted'}
|
||
)
|
||
messages.success(request, self.success_message)
|
||
return super().delete(request, *args, **kwargs)
|
||
|
||
#
|
||
# # encounters/views.py
|
||
#
|
||
# from django.shortcuts import get_object_or_404, redirect, render
|
||
# from django.urls import reverse_lazy
|
||
# from django.contrib.auth.decorators import login_required
|
||
# from django.contrib.auth.mixins import LoginRequiredMixin
|
||
# from django.contrib import messages
|
||
# from django.views.generic import (
|
||
# TemplateView, ListView, DetailView, CreateView, UpdateView, DeleteView
|
||
# )
|
||
# from django.views.generic.edit import FormMixin
|
||
# from django.contrib.messages.views import SuccessMessageMixin
|
||
# from django.db.models import Q, Avg
|
||
# from django.utils import timezone
|
||
# from django.http import JsonResponse
|
||
#
|
||
# from .models import *
|
||
# from .forms import *
|
||
# from core.models import AuditLogEntry
|
||
# from patients.models import PatientProfile
|
||
# from django.utils.translation import gettext_lazy as _
|
||
#
|
||
#
|
||
# # Mixins -------------------------------------------------------------------------
|
||
#
|
||
# class TenantMixin:
|
||
# def get_queryset(self):
|
||
# qs = super().get_queryset()
|
||
# tenant = getattr(self.request.user, 'tenant', None)
|
||
# if tenant and not self.request.user.is_superuser:
|
||
# # Models with patient FK:
|
||
# if hasattr(qs.model, 'patient'):
|
||
# return qs.filter(patient__tenant=tenant)
|
||
# # NoteTemplate uses tenant directly:
|
||
# return qs.filter(tenant=tenant)
|
||
# return qs
|
||
#
|
||
# def get_object(self, queryset=None):
|
||
# qs = queryset or self.get_queryset()
|
||
# return super().get_object(qs)
|
||
#
|
||
#
|
||
# class FormKwargsMixin:
|
||
# def get_form_kwargs(self):
|
||
# kw = super().get_form_kwargs()
|
||
# kw['user'] = self.request.user
|
||
# kw['tenant'] = getattr(self.request.user, 'tenant', None)
|
||
# return kw
|
||
#
|
||
#
|
||
# # Dashboard ----------------------------------------------------------------------
|
||
#
|
||
# class DashboardView(LoginRequiredMixin, TemplateView):
|
||
# template_name = 'emr/dashboard.html'
|
||
#
|
||
# def get_context_data(self, **kwargs):
|
||
# ctx = super().get_context_data(**kwargs)
|
||
# tenant = getattr(self.request.user, 'tenant', None)
|
||
# today = timezone.now().date()
|
||
# week_ago = today - timezone.timedelta(days=7)
|
||
#
|
||
# enc = Encounter.objects.filter(patient__tenant=tenant)
|
||
# vs = VitalSigns.objects.filter(encounter__patient__tenant=tenant)
|
||
# pb = ProblemList.objects.filter(patient__tenant=tenant)
|
||
# cp = CarePlan.objects.filter(patient__tenant=tenant)
|
||
# cn = ClinicalNote.objects.filter(encounter__patient__tenant=tenant)
|
||
#
|
||
# ctx.update({
|
||
# 'total_encounters': enc.count(),
|
||
# 'encounters_today': enc.filter(scheduled_datetime__date=today).count(),
|
||
# 'encounters_this_week': enc.filter(scheduled_datetime__date__gte=week_ago).count(),
|
||
# 'active_encounters': enc.filter(status='IN_PROGRESS').count(),
|
||
#
|
||
# 'total_vital_signs': vs.count(),
|
||
# 'vital_signs_today': vs.filter(recorded_at__date=today).count(),
|
||
# 'avg_temp_week': vs.filter(recorded_at__date__gte=week_ago).aggregate(Avg('temperature'))['temperature__avg'],
|
||
# 'avg_hr_week': vs.filter(recorded_at__date__gte=week_ago).aggregate(Avg('heart_rate'))['heart_rate__avg'],
|
||
#
|
||
# 'total_problems': pb.count(),
|
||
# 'active_problems': pb.filter(status='ACTIVE').count(),
|
||
# 'resolved_problems': pb.filter(status='RESOLVED').count(),
|
||
#
|
||
# 'total_care_plans': cp.count(),
|
||
# 'active_care_plans': cp.filter(status='ACTIVE').count(),
|
||
# 'completed_care_plans': cp.filter(status='COMPLETED').count(),
|
||
#
|
||
# 'total_notes': cn.count(),
|
||
# 'notes_today': cn.filter(created_at__date=today).count(),
|
||
#
|
||
# 'recent_encounters': enc.select_related('patient','provider').order_by('-scheduled_datetime')[:5],
|
||
# 'recent_vitals': vs.select_related('encounter','recorded_by').order_by('-recorded_at')[:5],
|
||
# 'recent_notes': cn.select_related('encounter','author').order_by('-created_at')[:5],
|
||
# })
|
||
# return ctx
|
||
#
|
||
#
|
||
# # Encounter ----------------------------------------------------------------------
|
||
#
|
||
# class EncounterListView(LoginRequiredMixin, TenantMixin, FormMixin, ListView):
|
||
# model = Encounter
|
||
# template_name = 'emr/encounter_list.html'
|
||
# context_object_name = 'encounters'
|
||
# paginate_by = 20
|
||
# form_class = EMRSearchForm
|
||
#
|
||
# def get_queryset(self):
|
||
# qs = super().get_queryset().select_related('patient','provider').order_by('-scheduled_datetime')
|
||
# if self.request.GET:
|
||
# form = self.get_form()
|
||
# if form.is_valid():
|
||
# cd = form.cleaned_data
|
||
# if cd.get('search'):
|
||
# qs = qs.filter(
|
||
# Q(patient__first_name__icontains=cd['search']) |
|
||
# Q(patient__last_name__icontains=cd['search']) |
|
||
# Q(chief_complaint__icontains=cd['search'])
|
||
# )
|
||
# for fld in ('patient','provider','encounter_type','status'):
|
||
# if cd.get(fld):
|
||
# qs = qs.filter(**{fld: cd[fld]})
|
||
# if cd.get('date_from'):
|
||
# qs = qs.filter(scheduled_datetime__date__gte=cd['date_from'])
|
||
# if cd.get('date_to'):
|
||
# qs = qs.filter(scheduled_datetime__date__lte=cd['date_to'])
|
||
# return qs
|
||
#
|
||
# def get_context_data(self, **kwargs):
|
||
# ctx = super().get_context_data(**kwargs)
|
||
# ctx['search_form'] = self.get_form()
|
||
# ctx['total_count'] = self.get_queryset().count()
|
||
# return ctx
|
||
#
|
||
#
|
||
# class EncounterDetailView(LoginRequiredMixin, TenantMixin, DetailView):
|
||
# model = Encounter
|
||
# template_name = 'emr/encounter_detail.html'
|
||
# context_object_name = 'encounter'
|
||
#
|
||
# def get_context_data(self, **kwargs):
|
||
# ctx = super().get_context_data(**kwargs)
|
||
# enc = self.object
|
||
# ctx.update({
|
||
# 'vital_signs': VitalSigns.objects.filter(encounter=enc).order_by('-recorded_at'),
|
||
# 'clinical_notes': ClinicalNote.objects.filter(encounter=enc).order_by('-created_at'),
|
||
# 'problems': ProblemList.objects.filter(patient=enc.patient, status='ACTIVE'),
|
||
# 'care_plans': CarePlan.objects.filter(patient=enc.patient, status='ACTIVE'),
|
||
# })
|
||
# return ctx
|
||
#
|
||
#
|
||
# class EncounterCreateView(
|
||
# LoginRequiredMixin, FormKwargsMixin,
|
||
# SuccessMessageMixin, CreateView
|
||
# ):
|
||
# model = Encounter
|
||
# form_class = EncounterForm
|
||
# template_name = 'emr/encounter_form.html'
|
||
# success_message = _('Encounter for %(patient)s created successfully.')
|
||
#
|
||
# def form_valid(self, form):
|
||
# form.instance.tenant = self.request.user.tenant
|
||
# response = super().form_valid(form)
|
||
# AuditLogEntry.objects.create(
|
||
# tenant=self.request.user.tenant,
|
||
# user=self.request.user,
|
||
# action='CREATE',
|
||
# model_name='Encounter',
|
||
# object_id=str(self.object.pk),
|
||
# changes={'status': 'Encounter created'}
|
||
# )
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# return reverse_lazy('emr:encounter_detail', kwargs={'pk': self.object.pk})
|
||
#
|
||
#
|
||
# class EncounterUpdateView(
|
||
# LoginRequiredMixin, FormKwargsMixin,
|
||
# SuccessMessageMixin, TenantMixin, UpdateView
|
||
# ):
|
||
# model = Encounter
|
||
# form_class = EncounterForm
|
||
# template_name = 'emr/encounter_form.html'
|
||
# success_message = _('Encounter for %(patient)s updated successfully.')
|
||
#
|
||
# def dispatch(self, request, *args, **kwargs):
|
||
# enc = self.get_object()
|
||
# if enc.status == 'COMPLETED' and not request.user.is_superuser:
|
||
# messages.error(request, _('Cannot modify a completed encounter.'))
|
||
# return redirect('emr:encounter_detail', pk=enc.pk)
|
||
# return super().dispatch(request, *args, **kwargs)
|
||
#
|
||
# def form_valid(self, form):
|
||
# response = super().form_valid(form)
|
||
# AuditLogEntry.objects.create(
|
||
# tenant=self.request.user.tenant,
|
||
# user=self.request.user,
|
||
# action='UPDATE',
|
||
# model_name='Encounter',
|
||
# object_id=str(self.object.pk),
|
||
# changes={'status': 'Encounter updated'}
|
||
# )
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# return reverse_lazy('emr:encounter_detail', kwargs={'pk': self.object.pk})
|
||
#
|
||
#
|
||
# class EncounterDeleteView(
|
||
# LoginRequiredMixin, TenantMixin,
|
||
# SuccessMessageMixin, DeleteView
|
||
# ):
|
||
# model = Encounter
|
||
# template_name = 'emr/encounter_confirm_delete.html'
|
||
# success_url = reverse_lazy('emr:encounter_list')
|
||
# success_message = _('Encounter deleted successfully.')
|
||
#
|
||
# def delete(self, request, *args, **kwargs):
|
||
# enc = self.get_object()
|
||
# AuditLogEntry.objects.create(
|
||
# tenant=request.user.tenant,
|
||
# user=request.user,
|
||
# action='DELETE',
|
||
# model_name='Encounter',
|
||
# object_id=str(enc.pk),
|
||
# changes={'status': 'Encounter deleted'}
|
||
# )
|
||
# messages.success(request, self.success_message)
|
||
# return super().delete(request, *args, **kwargs)
|
||
#
|
||
#
|
||
# # VitalSigns ----------------------------------------------------------------------
|
||
#
|
||
# class VitalSignsListView(LoginRequiredMixin, TenantMixin, ListView):
|
||
# model = VitalSigns
|
||
# template_name = 'emr/vital_signs_list.html'
|
||
# context_object_name = 'vital_signs'
|
||
# paginate_by = 20
|
||
#
|
||
# def get_queryset(self):
|
||
# qs = super().get_queryset().select_related('encounter','recorded_by').order_by('-recorded_at')
|
||
# # (Search/filter logic would use a VitalSignsSearchForm — omitted for brevity)
|
||
# return qs
|
||
#
|
||
|
||
class VitalSignsDetailView(LoginRequiredMixin, DetailView):
|
||
model = VitalSigns
|
||
template_name = 'emr/vital_signs/vital_signs_detail.html'
|
||
context_object_name = 'vital_signs'
|
||
|
||
|
||
class VitalSignsCreateView(LoginRequiredMixin, CreateView):
|
||
model = VitalSigns
|
||
form_class = VitalSignsForm
|
||
template_name = 'emr/vital_signs/vital_signs_form.html'
|
||
success_message = _('Vital signs recorded successfully.')
|
||
|
||
def dispatch(self, request, *args, **kwargs):
|
||
# Ensure tenant exists
|
||
self.tenant = getattr(request, "tenant", None)
|
||
if not self.tenant:
|
||
return JsonResponse({"error": "No tenant found"}, status=400)
|
||
|
||
return super().dispatch(request, *args, **kwargs)
|
||
|
||
def get_form_kwargs(self):
|
||
kwargs = super().get_form_kwargs()
|
||
# pass what the form actually expects
|
||
kwargs['tenant'] = self.tenant
|
||
kwargs['user'] = self.request.user
|
||
# (Optional) initial values for fields that ARE in the form:
|
||
# kwargs.setdefault('initial', {})
|
||
# kwargs['initial'].setdefault('measured_datetime', timezone.now())
|
||
return kwargs
|
||
|
||
def form_valid(self, form):
|
||
# set server-controlled fields not present on the form
|
||
form.instance.measured_by = self.request.user
|
||
|
||
encounter = get_object_or_404(
|
||
Encounter,
|
||
pk=self.kwargs['pk'],
|
||
tenant=self.tenant
|
||
)
|
||
form.instance.encounter = encounter
|
||
form.instance.patient = encounter.patient
|
||
|
||
response = super().form_valid(form)
|
||
|
||
AuditLogEntry.objects.create(
|
||
tenant=self.tenant, # use the resolved tenant
|
||
user=self.request.user,
|
||
action='CREATE',
|
||
model_name='VitalSigns',
|
||
object_id=str(self.object.pk),
|
||
changes={'status': 'Vital signs recorded'}
|
||
)
|
||
return response
|
||
|
||
def get_success_url(self):
|
||
return reverse_lazy('emr:vital_signs_detail', kwargs={'pk': self.object.pk})
|
||
|
||
def get_context_data(self, **kwargs):
|
||
ctx = super().get_context_data(**kwargs)
|
||
# encounter is coming from URL; fetch it with tenant check
|
||
encounter = get_object_or_404(
|
||
Encounter, pk=self.kwargs['pk'], tenant=self.tenant
|
||
)
|
||
ctx['patient'] = encounter.patient
|
||
ctx['encounter'] = encounter
|
||
ctx['measured_by'] = self.request.user # or Employee profile if you have one
|
||
return ctx
|
||
|
||
|
||
class ProblemListListView(LoginRequiredMixin, ListView):
|
||
model = ProblemList
|
||
template_name = 'emr/problems/problem_list.html'
|
||
context_object_name = 'problems'
|
||
paginate_by = 20
|
||
|
||
def get_queryset(self):
|
||
return super().get_queryset().select_related('patient','identified_by').order_by('-created_at')
|
||
|
||
|
||
class ProblemListDetailView(LoginRequiredMixin, TenantMixin, DetailView):
|
||
model = ProblemList
|
||
template_name = 'emr/problems/problem_detail.html'
|
||
context_object_name = 'problem'
|
||
|
||
|
||
class ProblemListCreateView(LoginRequiredMixin, CreateView):
|
||
model = ProblemList
|
||
form_class = ProblemListForm
|
||
template_name = 'emr/problems/problem_form.html'
|
||
success_message = _('Problem added successfully.')
|
||
|
||
def form_valid(self, form):
|
||
form.instance.identified_by = self.request.user
|
||
response = super().form_valid(form)
|
||
AuditLogEntry.objects.create(
|
||
tenant=self.request.user.tenant,
|
||
user=self.request.user,
|
||
action='CREATE',
|
||
model_name='ProblemList',
|
||
object_id=str(self.object.pk),
|
||
changes={'status': 'Problem added'}
|
||
)
|
||
return response
|
||
|
||
def get_success_url(self):
|
||
return reverse_lazy('emr:problem_detail', kwargs={'pk': self.object.pk})
|
||
|
||
|
||
class ProblemListUpdateView(LoginRequiredMixin, UpdateView):
|
||
model = ProblemList
|
||
form_class = ProblemListForm
|
||
template_name = 'emr/problems/problem_form.html'
|
||
success_message = _('Problem updated successfully.')
|
||
|
||
def form_valid(self, form):
|
||
response = super().form_valid(form)
|
||
AuditLogEntry.objects.create(
|
||
tenant=self.request.user.tenant,
|
||
user=self.request.user,
|
||
action='UPDATE',
|
||
model_name='ProblemList',
|
||
object_id=str(self.object.pk),
|
||
changes={'status': 'Problem updated'}
|
||
)
|
||
return response
|
||
|
||
def get_success_url(self):
|
||
return reverse_lazy('emr:problem_detail', kwargs={'pk': self.object.pk})
|
||
|
||
|
||
class ProblemListDeleteView(LoginRequiredMixin, DeleteView):
|
||
model = ProblemList
|
||
template_name = 'emr/problems/problem_confirm_delete.html'
|
||
success_url = reverse_lazy('emr:problem_list')
|
||
success_message = _('Problem deleted successfully.')
|
||
|
||
def delete(self, request, *args, **kwargs):
|
||
prob = self.get_object()
|
||
AuditLogEntry.objects.create(
|
||
tenant=request.user.tenant,
|
||
user=request.user,
|
||
action='DELETE',
|
||
model_name='ProblemList',
|
||
object_id=str(prob.pk),
|
||
changes={'status': 'Problem deleted'}
|
||
)
|
||
messages.success(request, self.success_message)
|
||
return super().delete(request, *args, **kwargs)
|
||
#
|
||
|
||
class CarePlanDetailView(LoginRequiredMixin, DetailView):
|
||
model = CarePlan
|
||
template_name = 'emr/care_plans/care_plan_detail.html'
|
||
context_object_name = 'care_plan'
|
||
|
||
|
||
class CarePlanCreateView(LoginRequiredMixin, CreateView):
|
||
model = CarePlan
|
||
form_class = CarePlanForm
|
||
template_name = 'emr/care_plans/care_plan_form.html'
|
||
success_message = _('Care plan created successfully.')
|
||
|
||
def form_valid(self, form):
|
||
response = super().form_valid(form)
|
||
AuditLogEntry.objects.create(
|
||
tenant=self.request.user.tenant,
|
||
user=self.request.user,
|
||
action='CREATE',
|
||
model_name='CarePlan',
|
||
object_id=str(self.object.pk),
|
||
changes={'status': 'Care plan created'}
|
||
)
|
||
return response
|
||
|
||
def get_success_url(self):
|
||
return reverse_lazy('emr:care_plan_detail', kwargs={'pk': self.object.pk})
|
||
|
||
|
||
class CarePlanUpdateView(LoginRequiredMixin, UpdateView):
|
||
model = CarePlan
|
||
form_class = CarePlanForm
|
||
template_name = 'emr/care_plans/care_plan_form.html'
|
||
success_message = _('Care plan updated successfully.')
|
||
|
||
def form_valid(self, form):
|
||
response = super().form_valid(form)
|
||
AuditLogEntry.objects.create(
|
||
tenant=self.request.user.tenant,
|
||
user=self.request.user,
|
||
action='UPDATE',
|
||
model_name='CarePlan',
|
||
object_id=str(self.object.pk),
|
||
changes={'status': 'Care plan updated'}
|
||
)
|
||
return response
|
||
|
||
def get_success_url(self):
|
||
return reverse_lazy('emr:care_plan_detail', kwargs={'pk': self.object.pk})
|
||
|
||
|
||
class CarePlanProgressUpdateView(LoginRequiredMixin, UpdateView):
|
||
"""
|
||
Update only progress-related fields of a CarePlan.
|
||
"""
|
||
model = CarePlan
|
||
form_class = CarePlanProgressForm
|
||
template_name = 'emr/care_plans/care_plan_progress_form.html'
|
||
# permission_required = 'emr.change_careplan'
|
||
|
||
def get_form_kwargs(self):
|
||
kwargs = super().get_form_kwargs()
|
||
kwargs['tenant'] = getattr(self.request, 'tenant', None)
|
||
return kwargs
|
||
|
||
def get_queryset(self):
|
||
tenant = getattr(self.request, 'tenant', None)
|
||
if not tenant:
|
||
return CarePlan.objects.none()
|
||
# Limit by tenant, and optionally eager-load relations used in your template
|
||
return CarePlan.objects.filter(tenant=tenant)
|
||
|
||
def form_valid(self, form):
|
||
# Auto-set last_reviewed to today if user left it blank
|
||
if not form.cleaned_data.get('last_reviewed'):
|
||
form.instance.last_reviewed = timezone.now().date()
|
||
|
||
# If user marks plan completed and end_date is empty, set end_date to today
|
||
if form.cleaned_data.get('status') == 'COMPLETED' and not form.instance.end_date:
|
||
form.instance.end_date = timezone.now().date()
|
||
|
||
response = super().form_valid(form)
|
||
|
||
try:
|
||
AuditLogger.log_event(
|
||
tenant=form.instance.tenant,
|
||
event_type='UPDATE',
|
||
event_category='CARE_PLAN',
|
||
action='Update Care Plan Progress',
|
||
description=f'Progress updated for care plan #{self.object.pk} ({self.object.title})',
|
||
user=self.request.user,
|
||
content_object=self.object,
|
||
request=self.request,
|
||
)
|
||
except Exception:
|
||
# Don’t block user flow if logging fails
|
||
pass
|
||
|
||
messages.success(self.request, 'Care plan progress updated successfully.')
|
||
return response
|
||
|
||
def get_success_url(self):
|
||
return reverse('emr:care_plan_detail', args=[self.object.pk])
|
||
|
||
|
||
class CarePlanDeleteView(LoginRequiredMixin, DeleteView):
|
||
model = CarePlan
|
||
template_name = 'emr/care_plans/care_plan_confirm_delete.html'
|
||
success_url = reverse_lazy('emr:care_plan_list')
|
||
success_message = _('Care plan deleted successfully.')
|
||
|
||
def delete(self, request, *args, **kwargs):
|
||
cp = self.get_object()
|
||
AuditLogEntry.objects.create(
|
||
tenant=request.user.tenant,
|
||
user=request.user,
|
||
action='DELETE',
|
||
model_name='CarePlan',
|
||
object_id=str(cp.pk),
|
||
changes={'status': 'Care plan deleted'}
|
||
)
|
||
messages.success(request, self.success_message)
|
||
return super().delete(request, *args, **kwargs)
|
||
#
|
||
#
|
||
# # ClinicalNote --------------------------------------------------------------------
|
||
#
|
||
# class ClinicalNoteListView(LoginRequiredMixin, TenantMixin, ListView):
|
||
# model = ClinicalNote
|
||
# template_name = 'emr/clinical_note_list.html'
|
||
# context_object_name = 'notes'
|
||
# paginate_by = 20
|
||
#
|
||
#
|
||
# class ClinicalNoteDetailView(LoginRequiredMixin, TenantMixin, DetailView):
|
||
# model = ClinicalNote
|
||
# template_name = 'emr/clinical_note_detail.html'
|
||
# context_object_name = 'note'
|
||
#
|
||
#
|
||
# class ClinicalNoteCreateView(
|
||
# LoginRequiredMixin, FormKwargsMixin,
|
||
# SuccessMessageMixin, CreateView
|
||
# ):
|
||
# model = ClinicalNote
|
||
# form_class = ClinicalNoteForm
|
||
# template_name = 'emr/clinical_note_form.html'
|
||
# success_message = _('Clinical note created successfully.')
|
||
#
|
||
# def form_valid(self, form):
|
||
# form.instance.author = self.request.user
|
||
# response = super().form_valid(form)
|
||
# AuditLogEntry.objects.create(
|
||
# tenant=self.request.user.tenant,
|
||
# user=self.request.user,
|
||
# action='CREATE',
|
||
# model_name='ClinicalNote',
|
||
# object_id=str(self.object.pk),
|
||
# changes={'status': 'Clinical note created'}
|
||
# )
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# return reverse_lazy('emr:clinical_note_detail', kwargs={'pk': self.object.pk})
|
||
#
|
||
#
|
||
# class ClinicalNoteUpdateView(
|
||
# LoginRequiredMixin, FormKwargsMixin,
|
||
# SuccessMessageMixin, TenantMixin, UpdateView
|
||
# ):
|
||
# model = ClinicalNote
|
||
# form_class = ClinicalNoteForm
|
||
# template_name = 'emr/clinical_note_form.html'
|
||
# success_message = _('Clinical note updated successfully.')
|
||
#
|
||
# def form_valid(self, form):
|
||
# response = super().form_valid(form)
|
||
# AuditLogEntry.objects.create(
|
||
# tenant=self.request.user.tenant,
|
||
# user=self.request.user,
|
||
# action='UPDATE',
|
||
# model_name='ClinicalNote',
|
||
# object_id=str(self.object.pk),
|
||
# changes={'status': 'Clinical note updated'}
|
||
# )
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# return reverse_lazy('emr:clinical_note_detail', kwargs={'pk': self.object.pk})
|
||
#
|
||
#
|
||
# class ClinicalNoteDeleteView(
|
||
# LoginRequiredMixin, TenantMixin,
|
||
# SuccessMessageMixin, DeleteView
|
||
# ):
|
||
# model = ClinicalNote
|
||
# template_name = 'emr/clinical_note_confirm_delete.html'
|
||
# success_url = reverse_lazy('emr:clinical_note_list')
|
||
# success_message = _('Clinical note deleted successfully.')
|
||
#
|
||
# def delete(self, request, *args, **kwargs):
|
||
# cn = self.get_object()
|
||
# AuditLogEntry.objects.create(
|
||
# tenant=request.user.tenant,
|
||
# user=request.user,
|
||
# action='DELETE',
|
||
# model_name='ClinicalNote',
|
||
# object_id=str(cn.pk),
|
||
# changes={'status': 'Clinical note deleted'}
|
||
# )
|
||
# messages.success(request, self.success_message)
|
||
# return super().delete(request, *args, **kwargs)
|
||
#
|
||
#
|
||
# # NoteTemplate -------------------------------------------------------------------
|
||
#
|
||
# class NoteTemplateListView(LoginRequiredMixin, TenantMixin, ListView):
|
||
# model = NoteTemplate
|
||
# template_name = 'emr/note_template_list.html'
|
||
# context_object_name = 'templates'
|
||
# paginate_by = 20
|
||
#
|
||
#
|
||
# class NoteTemplateDetailView(LoginRequiredMixin, TenantMixin, DetailView):
|
||
# model = NoteTemplate
|
||
# template_name = 'emr/note_template_detail.html'
|
||
# context_object_name = 'template'
|
||
#
|
||
#
|
||
# class NoteTemplateCreateView(
|
||
# LoginRequiredMixin, FormKwargsMixin,
|
||
# SuccessMessageMixin, CreateView
|
||
# ):
|
||
# model = NoteTemplate
|
||
# form_class = NoteTemplateForm
|
||
# template_name = 'emr/note_template_form.html'
|
||
# success_message = _('Note template created successfully.')
|
||
#
|
||
# def form_valid(self, form):
|
||
# form.instance.tenant = self.request.user.tenant
|
||
# form.instance.created_by = self.request.user
|
||
# response = super().form_valid(form)
|
||
# AuditLogEntry.objects.create(
|
||
# tenant=self.request.user.tenant,
|
||
# user=self.request.user,
|
||
# action='CREATE',
|
||
# model_name='NoteTemplate',
|
||
# object_id=str(self.object.pk),
|
||
# changes={'status': 'Template created'}
|
||
# )
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# return reverse_lazy('emr:note_template_detail', kwargs={'pk': self.object.pk})
|
||
#
|
||
#
|
||
# class NoteTemplateUpdateView(
|
||
# LoginRequiredMixin, FormKwargsMixin,
|
||
# SuccessMessageMixin, TenantMixin, UpdateView
|
||
# ):
|
||
# model = NoteTemplate
|
||
# form_class = NoteTemplateForm
|
||
# template_name = 'emr/note_template_form.html'
|
||
# success_message = _('Note template updated successfully.')
|
||
#
|
||
# def form_valid(self, form):
|
||
# response = super().form_valid(form)
|
||
# AuditLogEntry.objects.create(
|
||
# tenant=self.request.user.tenant,
|
||
# user=self.request.user,
|
||
# action='UPDATE',
|
||
# model_name='NoteTemplate',
|
||
# object_id=str(self.object.pk),
|
||
# changes={'status': 'Template updated'}
|
||
# )
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# return reverse_lazy('emr:note_template_detail', kwargs={'pk': self.object.pk})
|
||
#
|
||
#
|
||
# class NoteTemplateDeleteView(
|
||
# LoginRequiredMixin, TenantMixin,
|
||
# SuccessMessageMixin, DeleteView
|
||
# ):
|
||
# model = NoteTemplate
|
||
# template_name = 'emr/note_template_confirm_delete.html'
|
||
# success_url = reverse_lazy('emr:note_template_list')
|
||
# success_message = _('Note template deleted successfully.')
|
||
#
|
||
# def delete(self, request, *args, **kwargs):
|
||
# nt = self.get_object()
|
||
# if ClinicalNote.objects.filter(template=nt).exists():
|
||
# messages.error(request, _('Cannot delete a template in use.'))
|
||
# return redirect('emr:note_template_detail', pk=nt.pk)
|
||
#
|
||
# AuditLogEntry.objects.create(
|
||
# tenant=request.user.tenant,
|
||
# user=request.user,
|
||
# action='DELETE',
|
||
# model_name='NoteTemplate',
|
||
# object_id=str(nt.pk),
|
||
# changes={'status': 'Template deleted'}
|
||
# )
|
||
# messages.success(request, self.success_message)
|
||
# return super().delete(request, *args, **kwargs)
|
||
#
|
||
#
|
||
# # HTMX & Actions ------------------------------------------------------------------
|
||
#
|
||
# @login_required
|
||
# def htmx_emr_stats(request):
|
||
# tenant = request.user.tenant
|
||
# today = timezone.now().date()
|
||
# enc = Encounter.objects.filter(patient__tenant=tenant)
|
||
# vs = VitalSigns.objects.filter(encounter__patient__tenant=tenant)
|
||
# pb = ProblemList.objects.filter(patient__tenant=tenant)
|
||
# cp = CarePlan.objects.filter(patient__tenant=tenant)
|
||
# cn = ClinicalNote.objects.filter(encounter__patient__tenant=tenant)
|
||
#
|
||
# stats = {
|
||
# 'total_encounters': enc.count(),
|
||
# 'encounters_today': enc.filter(scheduled_datetime__date=today).count(),
|
||
# 'active_encounters': enc.filter(status='IN_PROGRESS').count(),
|
||
# 'total_vital_signs': vs.count(),
|
||
# 'vital_signs_today': vs.filter(recorded_at__date=today).count(),
|
||
# 'active_problems': pb.filter(status='ACTIVE').count(),
|
||
# 'active_care_plans': cp.filter(status='ACTIVE').count(),
|
||
# 'total_notes': cn.count(),
|
||
# 'notes_today': cn.filter(created_at__date=today).count(),
|
||
# }
|
||
# return JsonResponse(stats)
|
||
#
|
||
|
||
@login_required
|
||
def start_encounter(request, pk):
|
||
enc = get_object_or_404(Encounter, pk=pk, patient__tenant=request.user.tenant)
|
||
if enc.status == 'SCHEDULED':
|
||
enc.status = 'IN_PROGRESS'
|
||
enc.save()
|
||
AuditLogEntry.objects.create(
|
||
tenant=request.user.tenant, user=request.user,
|
||
action='UPDATE', model_name='Encounter',
|
||
object_id=str(enc.pk), changes={'status': 'Encounter started'}
|
||
)
|
||
messages.success(request, _('Encounter started.'))
|
||
else:
|
||
messages.error(request, _('Only scheduled encounters can be started.'))
|
||
return redirect('emr:encounter_detail', pk=pk)
|
||
|
||
|
||
@login_required
|
||
def complete_encounter(request, pk):
|
||
enc = get_object_or_404(Encounter, pk=pk, patient__tenant=request.user.tenant)
|
||
if enc.status == 'IN_PROGRESS':
|
||
enc.status = 'COMPLETED'; enc.save()
|
||
AuditLogEntry.objects.create(
|
||
tenant=request.user.tenant, user=request.user,
|
||
action='UPDATE', model_name='Encounter',
|
||
object_id=str(enc.pk), changes={'status': 'Encounter completed'}
|
||
)
|
||
messages.success(request, _('Encounter completed.'))
|
||
else:
|
||
messages.error(request, _('Only in-progress encounters can be completed.'))
|
||
return redirect('emr:encounter_detail', pk=pk)
|
||
|
||
|
||
@login_required
|
||
def resolve_problem(request, pk):
|
||
prob = get_object_or_404(ProblemList, pk=pk, patient__tenant=request.user.tenant)
|
||
if prob.status == 'ACTIVE':
|
||
prob.status = 'RESOLVED'; prob.save()
|
||
AuditLogEntry.objects.create(
|
||
tenant=request.user.tenant, user=request.user,
|
||
action='UPDATE', model_name='ProblemList',
|
||
object_id=str(prob.pk), changes={'status': 'Problem resolved'}
|
||
)
|
||
messages.success(request, _('Problem resolved.'))
|
||
else:
|
||
messages.error(request, _('Only active problems can be resolved.'))
|
||
return redirect('emr:problem_detail', pk=pk)
|
||
|
||
|
||
@login_required
|
||
def complete_care_plan(request, pk):
|
||
cp = get_object_or_404(CarePlan, pk=pk, patient__tenant=request.user.tenant)
|
||
if cp.status == 'ACTIVE':
|
||
cp.status = 'COMPLETED'
|
||
cp.save()
|
||
AuditLogger.log_event(
|
||
tenant=request.user.tenant,
|
||
event_type='UPDATE',
|
||
event_category='CARE_PLAN',
|
||
action='Care plan completed',
|
||
description=f'Progress updated for care plan #{cp.pk} ({cp.title})',
|
||
user=request.user,
|
||
content_object=cp,
|
||
request=request,
|
||
)
|
||
|
||
messages.success(request, _('Care plan completed.'))
|
||
else:
|
||
messages.error(request, _('Only active care plans can be completed.'))
|
||
return redirect('emr:care_plan_detail', pk=pk)
|
||
|
||
|
||
@login_required
|
||
def approve_care_plan(request, pk):
|
||
cp = get_object_or_404(CarePlan, pk=pk, patient__tenant=request.user.tenant)
|
||
if cp.status == 'DRAFT':
|
||
cp.status = 'ACTIVE'
|
||
cp.save()
|
||
AuditLogger.log_event(
|
||
tenant=request.user.tenant,
|
||
event_type='UPDATE',
|
||
event_category='CARE_PLAN',
|
||
action='Care plan approved',
|
||
description=f'Approval of care plan #{cp.pk} ({cp.title})',
|
||
user=request.user,
|
||
content_object=cp,
|
||
request=request,
|
||
)
|
||
messages.success(request, _('Care plan approved.'))
|
||
else:
|
||
messages.error(request, _('Only pending care plans can be approved.'))
|
||
return redirect('emr:care_plan_detail', pk=pk)
|
||
|
||
|
||
# @login_required
|
||
# def sign_note(request, pk):
|
||
# note = get_object_or_404(ClinicalNote, pk=pk, encounter__patient__tenant=request.user.tenant)
|
||
# if note.status == 'DRAFT' and note.author == request.user:
|
||
# note.status = 'SIGNED'; note.signed_at = timezone.now(); note.save()
|
||
# AuditLogEntry.objects.create(
|
||
# tenant=request.user.tenant, user=request.user,
|
||
# action='UPDATE', model_name='ClinicalNote',
|
||
# object_id=str(note.pk), changes={'status': 'Clinical note signed'}
|
||
# )
|
||
# messages.success(request, _('Note signed.'))
|
||
# else:
|
||
# messages.error(request, _('Only your draft notes can be signed.'))
|
||
# return redirect('emr:clinical_note_detail', pk=pk)
|
||
#
|
||
#
|
||
# class PatientEMRView(LoginRequiredMixin, DetailView):
|
||
# model = PatientProfile
|
||
# template_name = 'emr/patient_emr.html'
|
||
# pk_url_kwarg = 'patient_id'
|
||
#
|
||
# def get_queryset(self):
|
||
# return PatientProfile.objects.filter(tenant=self.request.user.tenant)
|
||
#
|
||
#
|
||
|
||
|
||
|
||
@login_required
|
||
def vital_signs_search(request):
|
||
tenant = request.user.tenant
|
||
search = request.GET.get('search', '')
|
||
vs = VitalSigns.objects.filter(encounter__patient__tenant=tenant)
|
||
if search:
|
||
vs = vs.filter(
|
||
Q(encounter__patient__first_name__icontains=search) |
|
||
Q(encounter__patient__last_name__icontains=search)
|
||
)
|
||
vs = vs.order_by('-measured_datetime')[:10]
|
||
return render(request, 'emr/partials/vital_signs_search.html', {'vital_signs': vs})
|
||
|
||
|
||
# class VitalSignsUpdateView(LoginRequiredMixin, FormKwargsMixin, SuccessMessageMixin, TenantMixin, UpdateView):
|
||
# model = VitalSigns
|
||
# form_class = VitalSignsForm
|
||
# template_name = 'emr/vital_signs_form.html'
|
||
# success_message = _('Vital signs updated successfully.')
|
||
# # implement form_valid, get_success_url, etc.
|
||
#
|
||
# class VitalSignsDeleteView(LoginRequiredMixin, TenantMixin, SuccessMessageMixin, DeleteView):
|
||
# model = VitalSigns
|
||
# template_name = 'emr/vital_signs_confirm_delete.html'
|
||
# success_url = reverse_lazy('emr:vitalsigns_list')
|
||
# success_message = _('Vital signs entry deleted.')
|
||
|
||
@login_required
|
||
def get_template_content(request, pk):
|
||
"""
|
||
Return the raw content of a NoteTemplate (e.g. for AJAX/HTMX).
|
||
"""
|
||
template = get_object_or_404(
|
||
NoteTemplate,
|
||
pk=pk,
|
||
tenant=request.user.tenant
|
||
)
|
||
return JsonResponse({
|
||
'template_id': str(template.template_id),
|
||
'name': template.name,
|
||
'content': template.template_content,
|
||
})
|
||
|
||
|
||
@login_required
|
||
def emr_stats(request):
|
||
"""
|
||
HTMX endpoint for EMR statistics.
|
||
"""
|
||
tenant = request.user.tenant
|
||
today = timezone.now().date()
|
||
|
||
stats = {
|
||
'active_encounters': Encounter.objects.filter(
|
||
tenant=tenant,
|
||
status__in=['ARRIVED', 'TRIAGED', 'IN_PROGRESS', 'ON_HOLD']
|
||
).count(),
|
||
'pending_documentation': Encounter.objects.filter(
|
||
tenant=tenant,
|
||
documentation_complete=False,
|
||
status='FINISHED'
|
||
).count(),
|
||
'unsigned_notes': ClinicalNote.objects.filter(
|
||
patient__tenant=tenant,
|
||
status='COMPLETED',
|
||
electronically_signed=False
|
||
).count(),
|
||
'critical_vitals': VitalSigns.objects.filter(
|
||
patient__tenant=tenant,
|
||
measured_datetime__date=today
|
||
).exclude(critical_values=[]).count(),
|
||
}
|
||
|
||
return render(request, 'emr/partials/emr_stats.html', {'stats': stats})
|
||
|
||
|
||
@login_required
|
||
def encounter_search(request):
|
||
"""
|
||
HTMX endpoint for encounter search.
|
||
"""
|
||
search = request.GET.get('search', '')
|
||
status = request.GET.get('status', '')
|
||
encounter_type = request.GET.get('encounter_type', '')
|
||
|
||
queryset = Encounter.objects.filter(tenant=request.user.tenant)
|
||
|
||
if search:
|
||
queryset = queryset.filter(
|
||
Q(patient__first_name__icontains=search) |
|
||
Q(patient__last_name__icontains=search) |
|
||
Q(patient__mrn__icontains=search) |
|
||
Q(chief_complaint__icontains=search)
|
||
)
|
||
|
||
if status:
|
||
queryset = queryset.filter(status=status)
|
||
|
||
if encounter_type:
|
||
queryset = queryset.filter(encounter_type=encounter_type)
|
||
|
||
encounters = queryset.select_related('patient', 'provider').order_by('-start_datetime')[:20]
|
||
|
||
return render(request, 'emr/partials/encounter_list.html', {'encounters': encounters})
|
||
|
||
|
||
@login_required
|
||
def vital_signs_chart(request, patient_id):
|
||
"""
|
||
HTMX endpoint for vital signs chart.
|
||
"""
|
||
tenant = request.user.tenant
|
||
patient = get_object_or_404(
|
||
PatientProfile,
|
||
id=patient_id,
|
||
tenant=tenant
|
||
)
|
||
|
||
# Get recent vital signs
|
||
vital_signs = VitalSigns.objects.filter(
|
||
patient=patient
|
||
).order_by('-measured_datetime')[:15]
|
||
|
||
context = {
|
||
'patient': patient,
|
||
'vital_signs': vital_signs
|
||
}
|
||
return render(request, 'emr/partials/vital_signs_chart.html', context)
|
||
|
||
|
||
@login_required
|
||
def problem_list_patient(request, patient_id):
|
||
"""
|
||
HTMX endpoint for patient problem list.
|
||
"""
|
||
patient = get_object_or_404(
|
||
PatientProfile,
|
||
id=patient_id,
|
||
tenant=request.user.tenant
|
||
)
|
||
|
||
problems = ProblemList.objects.filter(
|
||
patient=patient
|
||
).select_related('diagnosing_provider').order_by('-created_at')
|
||
|
||
return render(request, 'emr/partials/problem_list.html', {
|
||
'patient': patient,
|
||
'problems': problems
|
||
})
|
||
|
||
|
||
@login_required
|
||
def add_vital_signs(request, pk):
|
||
"""
|
||
HTMX endpoint for adding vital signs.
|
||
"""
|
||
tenant = request.user.tenant
|
||
if request.method == 'POST':
|
||
encounter = get_object_or_404(
|
||
Encounter,
|
||
pk=pk,
|
||
tenant=tenant
|
||
)
|
||
vital_signs_data = {
|
||
'encounter': encounter,
|
||
'patient': encounter.patient,
|
||
'measured_by': request.user,
|
||
'measured_datetime': timezone.now(),
|
||
}
|
||
|
||
# Extract vital signs data
|
||
# Add measurements if provided
|
||
if request.POST.get('temperature'):
|
||
vital_signs_data['temperature'] = float(request.POST.get('temperature'))
|
||
vital_signs_data['temperature_method'] = request.POST.get('temperature_method', 'ORAL')
|
||
|
||
if request.POST.get('systolic_bp') and request.POST.get('diastolic_bp'):
|
||
vital_signs_data['systolic_bp'] = int(request.POST.get('systolic_bp'))
|
||
vital_signs_data['diastolic_bp'] = int(request.POST.get('diastolic_bp'))
|
||
vital_signs_data['bp_position'] = request.POST.get('bp_position', 'SITTING')
|
||
|
||
if request.POST.get('heart_rate'):
|
||
vital_signs_data['heart_rate'] = int(request.POST.get('heart_rate'))
|
||
vital_signs_data['heart_rhythm'] = request.POST.get('heart_rhythm', 'REGULAR')
|
||
|
||
if request.POST.get('respiratory_rate'):
|
||
vital_signs_data['respiratory_rate'] = int(request.POST.get('respiratory_rate'))
|
||
|
||
if request.POST.get('oxygen_saturation'):
|
||
vital_signs_data['oxygen_saturation'] = int(request.POST.get('oxygen_saturation'))
|
||
vital_signs_data['oxygen_delivery'] = request.POST.get('oxygen_delivery', 'ROOM_AIR')
|
||
|
||
if request.POST.get('pain_scale'):
|
||
vital_signs_data['pain_scale'] = int(request.POST.get('pain_scale'))
|
||
vital_signs_data['pain_location'] = request.POST.get('pain_location', '')
|
||
|
||
if request.POST.get('weight'):
|
||
vital_signs_data['weight'] = float(request.POST.get('weight'))
|
||
|
||
if request.POST.get('height'):
|
||
vital_signs_data['height'] = float(request.POST.get('height'))
|
||
|
||
# Create vital signs record
|
||
vital_signs = VitalSigns.objects.create(**vital_signs_data)
|
||
|
||
# Log the action
|
||
AuditLogger.log_event(
|
||
user=request.user,
|
||
action='VITAL_SIGNS_RECORDED',
|
||
model='VitalSigns',
|
||
object_id=vital_signs.id,
|
||
details=f"Vital signs recorded for {encounter.patient.get_full_name()}"
|
||
)
|
||
|
||
messages.success(request, 'Vital signs recorded successfully')
|
||
else:
|
||
messages.error(request, 'An error occurred while recording the vital signs. Please try again later.')
|
||
# return JsonResponse({'success': True, 'vital_signs_id': vital_signs.id})
|
||
return redirect('emr:encounter_detail', pk=pk)
|
||
# return JsonResponse({'error': 'Invalid request'}, status=400)
|
||
|
||
|
||
@login_required
|
||
def add_problem(request, patient_id):
|
||
"""
|
||
HTMX endpoint for adding a problem.
|
||
"""
|
||
if request.method == 'POST':
|
||
patient = get_object_or_404(
|
||
PatientProfile,
|
||
id=patient_id,
|
||
tenant=request.user.tenant
|
||
)
|
||
|
||
problem_data = {
|
||
'tenant': request.user.tenant,
|
||
'patient': patient,
|
||
'problem_name': request.POST.get('problem_name'),
|
||
'problem_type': request.POST.get('problem_type', 'DIAGNOSIS'),
|
||
'severity': request.POST.get('severity'),
|
||
'priority': request.POST.get('priority', 'MEDIUM'),
|
||
'diagnosing_provider': request.user,
|
||
'clinical_notes': request.POST.get('clinical_notes', ''),
|
||
}
|
||
|
||
# Add optional fields
|
||
if request.POST.get('problem_code'):
|
||
problem_data['problem_code'] = request.POST.get('problem_code')
|
||
problem_data['coding_system'] = request.POST.get('coding_system', 'ICD10')
|
||
|
||
if request.POST.get('onset_date'):
|
||
problem_data['onset_date'] = request.POST.get('onset_date')
|
||
|
||
# Create problem
|
||
problem = ProblemList.objects.create(**problem_data)
|
||
|
||
# Log the action
|
||
AuditLogger.log_event(
|
||
user=request.user,
|
||
action='PROBLEM_ADDED',
|
||
model='ProblemList',
|
||
object_id=problem.id,
|
||
details=f"Problem added for {patient.get_full_name()}: {problem.problem_name}"
|
||
)
|
||
|
||
messages.success(request, f'Problem "{problem.problem_name}" added successfully')
|
||
return JsonResponse({'success': True, 'problem_id': problem.id})
|
||
|
||
return JsonResponse({'error': 'Invalid request'}, status=400)
|
||
|
||
|
||
@login_required
|
||
def update_encounter_status(request, encounter_id):
|
||
"""
|
||
HTMX endpoint for updating encounter status.
|
||
"""
|
||
if request.method == 'POST':
|
||
encounter = get_object_or_404(
|
||
Encounter,
|
||
id=encounter_id,
|
||
tenant=request.user.tenant
|
||
)
|
||
|
||
new_status = request.POST.get('status')
|
||
if new_status in dict(Encounter._meta.get_field('status').choices):
|
||
old_status = encounter.status
|
||
encounter.status = new_status
|
||
|
||
# Handle status-specific logic
|
||
if new_status == 'FINISHED':
|
||
encounter.end_datetime = timezone.now()
|
||
elif new_status == 'IN_PROGRESS' and not encounter.end_datetime:
|
||
encounter.end_datetime = None
|
||
|
||
encounter.save()
|
||
|
||
# Log the action
|
||
AuditLogger.log_event(
|
||
user=request.user,
|
||
action='ENCOUNTER_STATUS_UPDATED',
|
||
model='Encounter',
|
||
object_id=encounter.id,
|
||
details=f"Encounter status changed from {old_status} to {new_status}"
|
||
)
|
||
|
||
return JsonResponse({
|
||
'success': True,
|
||
'new_status': encounter.get_status_display(),
|
||
'status_class': get_status_class(new_status)
|
||
})
|
||
|
||
return JsonResponse({'error': 'Invalid request'}, status=400)
|
||
|
||
|
||
@login_required
|
||
def sign_note(request, note_id):
|
||
"""
|
||
HTMX endpoint for signing a clinical note.
|
||
"""
|
||
tenant = request.user.tenant
|
||
if request.method == 'POST':
|
||
note = get_object_or_404(
|
||
ClinicalNote,
|
||
id=note_id,
|
||
patient__tenant=tenant,
|
||
author=request.user,
|
||
status='DRAFT'
|
||
)
|
||
|
||
note.status = 'SIGNED'
|
||
note.electronically_signed = True
|
||
note.signed_datetime = timezone.now()
|
||
note.signature_method = 'ELECTRONIC'
|
||
note.save()
|
||
|
||
# Log the action
|
||
AuditLogger.log_event(
|
||
tenant=tenant,
|
||
event_type='NOTE_SIGNED',
|
||
event_category='ClinicalNote',
|
||
action='Sign clinical note',
|
||
description=f"Clinical note signed: {note.title}",
|
||
user=request.user,
|
||
content_object=note,
|
||
request=request
|
||
)
|
||
|
||
messages.success(request, f'Note "{note.title}" signed successfully')
|
||
else:
|
||
messages.error(request, 'An error occurred while signing the note. Please try again later.')
|
||
|
||
return redirect('emr:clinical_note_detail', id=note_id)
|
||
|
||
|
||
def get_status_class(status):
|
||
"""
|
||
Get CSS class for status display.
|
||
"""
|
||
status_classes = {
|
||
'PLANNED': 'secondary',
|
||
'ARRIVED': 'info',
|
||
'TRIAGED': 'warning',
|
||
'IN_PROGRESS': 'primary',
|
||
'ON_HOLD': 'warning',
|
||
'FINISHED': 'success',
|
||
'CANCELLED': 'danger',
|
||
'ENTERED_IN_ERROR': 'danger',
|
||
'UNKNOWN': 'secondary',
|
||
}
|
||
return status_classes.get(status, 'secondary')
|
||
|
||
|
||
|
||
def _norm_code(s: str) -> str:
|
||
return (s or "").upper().replace(" ", "")
|
||
|
||
|
||
class Icd10SearchView(LoginRequiredMixin, ListView):
|
||
"""
|
||
Search ICD-10 by description or code.
|
||
URL params: ?q=...&mode=description|code&page=N
|
||
"""
|
||
model = Icd10
|
||
template_name = "emr/icd10_search.html"
|
||
context_object_name = "results"
|
||
paginate_by = 20
|
||
|
||
def get_queryset(self):
|
||
q = (self.request.GET.get("q") or "").strip()
|
||
mode = (self.request.GET.get("mode") or "description").strip().lower()
|
||
|
||
if not q:
|
||
return Icd10.objects.none()
|
||
|
||
try:
|
||
if mode == "code":
|
||
qn = _norm_code(q)
|
||
return (
|
||
Icd10.objects.filter(
|
||
Q(code__iexact=qn)
|
||
| Q(code__istartswith=qn)
|
||
| Q(code__icontains=qn)
|
||
)
|
||
.annotate(
|
||
exact_first=Case(
|
||
When(code__iexact=qn, then=Value(0)),
|
||
When(code__istartswith=qn, then=Value(1)),
|
||
default=Value(2),
|
||
output_field=IntegerField(),
|
||
)
|
||
)
|
||
.order_by("exact_first", "code")
|
||
)
|
||
else:
|
||
return (
|
||
Icd10.objects.filter(
|
||
Q(description__icontains=q)
|
||
| Q(section_name__icontains=q)
|
||
| Q(chapter_name__icontains=q)
|
||
)
|
||
.order_by("code")
|
||
)
|
||
except Exception as exc:
|
||
messages.error(self.request, f"Search failed: {exc}")
|
||
return Icd10.objects.none()
|
||
|
||
def get_context_data(self, **kwargs):
|
||
ctx = super().get_context_data(**kwargs)
|
||
ctx["q"] = self.request.GET.get("q", "")
|
||
ctx["mode"] = self.request.GET.get("mode", "description")
|
||
ctx["total"] = self.get_queryset().count()
|
||
ctx["error"] = None # template compatibility
|
||
return ctx
|
||
|
||
|
||
class Icd10DetailView(LoginRequiredMixin, DetailView):
|
||
"""
|
||
Exact lookup by code (e.g., A00.0).
|
||
"""
|
||
model = Icd10
|
||
template_name = "emr/icd10_detail.html"
|
||
context_object_name = "record"
|
||
slug_field = "code"
|
||
slug_url_kwarg = "code"
|
||
|
||
def get_object(self, queryset=None):
|
||
code_in = (self.kwargs.get(self.slug_url_kwarg) or "").strip()
|
||
if not code_in:
|
||
raise Http404("ICD-10 code is required.")
|
||
try:
|
||
return self.model.objects.get(code__iexact=_norm_code(code_in))
|
||
except self.model.DoesNotExist:
|
||
raise Http404("ICD-10 code not found.")
|
||
|
||
def get_context_data(self, **kwargs):
|
||
ctx = super().get_context_data(**kwargs)
|
||
record = ctx["record"]
|
||
|
||
# Collect ancestors
|
||
ancestors = []
|
||
p = record.parent
|
||
while p is not None:
|
||
ancestors.append(p)
|
||
p = p.parent
|
||
ancestors.reverse()
|
||
|
||
ctx["code"] = record.code
|
||
ctx["ancestors"] = ancestors
|
||
ctx["children"] = record.children.all().order_by("code")
|
||
return ctx
|
||
|
||
# from .utils import search_by_description, search_by_code, get_by_code
|
||
# from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||
#
|
||
# def icd10_search_view(request):
|
||
# """
|
||
# Search ICD-10 by description or code.
|
||
# ?q=...&mode=description|code
|
||
# """
|
||
# q = (request.GET.get('q') or "").strip()
|
||
# mode = (request.GET.get('mode') or "description").strip().lower()
|
||
#
|
||
# results = []
|
||
# total = 0
|
||
# page_obj = None
|
||
# error = None
|
||
#
|
||
# if q:
|
||
# try:
|
||
# if mode == "code":
|
||
# results = list(search_by_code(q) or [])
|
||
# else:
|
||
# # default to description search
|
||
# results = list(search_by_description(q) or [])
|
||
# total = len(results)
|
||
# except Exception as exc:
|
||
# error = str(exc)
|
||
# messages.error(request, f"Search failed: {error}")
|
||
# results = []
|
||
# total = 0
|
||
#
|
||
# # paginate lists (skip if it looks like a single dict/object)
|
||
# if isinstance(results, (list, tuple)):
|
||
# paginator = Paginator(results, 20) # 20 per page
|
||
# page = request.GET.get('page')
|
||
# try:
|
||
# page_obj = paginator.get_page(page)
|
||
# except (PageNotAnInteger, EmptyPage):
|
||
# page_obj = paginator.get_page(1)
|
||
# else:
|
||
# page_obj = None # single record / non-iterable
|
||
#
|
||
# context = {
|
||
# "query": q,
|
||
# "mode": mode,
|
||
# "results": results,
|
||
# "page_obj": page_obj,
|
||
# "total": total,
|
||
# "error": error,
|
||
# }
|
||
# return render(request, "emr/icd10_search.html", context)
|
||
#
|
||
# def icd10_detail_view(request, code):
|
||
# """
|
||
# Exact lookup by code (e.g., A00.0).
|
||
# """
|
||
# code = (code or "").strip()
|
||
# if not code:
|
||
# raise Http404("ICD-10 code is required.")
|
||
#
|
||
# try:
|
||
# record = get_by_code(code)
|
||
# if not record:
|
||
# raise Http404("ICD-10 code not found.")
|
||
# except Exception as exc:
|
||
# messages.error(request, f"Lookup failed: {exc}")
|
||
# raise Http404("ICD-10 code not found.")
|
||
#
|
||
# # 'record' could be a dict-like or object; normalize a bit for template
|
||
# # We won't assume exact keys; template will render keys/values safely.
|
||
# context = {"record": record, "code": code}
|
||
# return render(request, "emr/icd10_detail.html", context) |