2853 lines
94 KiB
Python
2853 lines
94 KiB
Python
"""
|
|
Laboratory app views with healthcare-focused CRUD operations.
|
|
Implements appropriate access patterns for clinical laboratory workflows.
|
|
"""
|
|
|
|
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, PermissionRequiredMixin
|
|
from django.views.generic import (
|
|
ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView
|
|
)
|
|
from django.http import JsonResponse, HttpResponse
|
|
from django.db.models import Q, Count, Avg, Sum, F
|
|
from django.utils import timezone
|
|
from django.contrib import messages
|
|
from django.urls import reverse_lazy, reverse
|
|
from django.core.paginator import Paginator
|
|
from django.template.loader import render_to_string
|
|
from datetime import datetime, timedelta, date
|
|
import json
|
|
|
|
from core.utils import AuditLogger
|
|
from .models import (
|
|
LabTest, LabOrder, Specimen, LabResult, QualityControl, ReferenceRange
|
|
)
|
|
from .forms import (
|
|
LabTestForm, LabOrderForm, SpecimenForm, LabResultForm,
|
|
QualityControlForm, ReferenceRangeForm
|
|
)
|
|
|
|
|
|
class LaboratoryDashboardView(LoginRequiredMixin, TemplateView):
|
|
"""
|
|
Main laboratory dashboard with key metrics and recent activity.
|
|
"""
|
|
template_name = 'laboratory/dashboard.html'
|
|
|
|
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({
|
|
'pending_orders': LabOrder.objects.filter(
|
|
tenant=tenant,
|
|
status='PENDING'
|
|
).count(),
|
|
'in_progress_orders': LabOrder.objects.filter(
|
|
tenant=tenant,
|
|
status='IN_PROGRESS'
|
|
).count(),
|
|
'specimens_collected': Specimen.objects.filter(
|
|
order__tenant=tenant,
|
|
collected_datetime__date=today
|
|
).count(),
|
|
'results_pending': LabResult.objects.filter(
|
|
order__tenant=tenant,
|
|
status='PENDING'
|
|
).count(),
|
|
'results_completed_today': LabResult.objects.filter(
|
|
order__tenant=tenant,
|
|
analyzed_datetime__date=today,
|
|
status='VERIFIED'
|
|
).count(),
|
|
'qc_tests_today': QualityControl.objects.filter(
|
|
tenant=tenant,
|
|
run_datetime=today
|
|
).count(),
|
|
'total_tests_available': LabTest.objects.filter(
|
|
tenant=tenant,
|
|
is_active=True
|
|
).count(),
|
|
'critical_results': LabResult.objects.filter(
|
|
order__tenant=tenant,
|
|
is_critical=True,
|
|
status='VERIFIED',
|
|
analyzed_datetime__date=today
|
|
).count(),
|
|
})
|
|
|
|
# Recent orders
|
|
context['recent_orders'] = LabOrder.objects.filter(
|
|
tenant=tenant
|
|
).select_related('patient', 'ordering_provider').order_by('-order_datetime')[:10]
|
|
|
|
# Recent results
|
|
context['recent_results'] = LabResult.objects.filter(
|
|
order__tenant=tenant,
|
|
status='VERIFIED'
|
|
).select_related('order', 'test').order_by('-analyzed_datetime')[:10]
|
|
|
|
return context
|
|
|
|
|
|
class LabTestListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List all laboratory tests with filtering and search.
|
|
"""
|
|
model = LabTest
|
|
template_name = 'laboratory/tests/lab_test_list.html'
|
|
context_object_name = 'lab_tests'
|
|
paginate_by = 25
|
|
|
|
def get_queryset(self):
|
|
queryset = LabTest.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
# Search functionality
|
|
search = self.request.GET.get('search')
|
|
if search:
|
|
queryset = queryset.filter(
|
|
Q(test_name__icontains=search) |
|
|
Q(test_code__icontains=search) |
|
|
Q(test_description__icontains=search)
|
|
)
|
|
|
|
# Filter by category
|
|
category = self.request.GET.get('category')
|
|
if category:
|
|
queryset = queryset.filter(test_category=category)
|
|
|
|
# Filter by active status
|
|
active_only = self.request.GET.get('active_only')
|
|
if active_only:
|
|
queryset = queryset.filter(is_active=True)
|
|
|
|
return queryset.order_by('test_name')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context.update({
|
|
'categories': LabTest._meta.get_field('test_category').choices,
|
|
'search_query': self.request.GET.get('search', ''),
|
|
})
|
|
return context
|
|
|
|
|
|
class LabTestDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Display detailed information about a laboratory test.
|
|
"""
|
|
model = LabTest
|
|
template_name = 'laboratory/tests/lab_test_detail.html'
|
|
context_object_name = 'lab_test'
|
|
|
|
def get_queryset(self):
|
|
return LabTest.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
lab_test = self.object
|
|
|
|
# Get reference ranges for this test
|
|
context['reference_ranges'] = lab_test.reference_ranges.all().order_by('age_min', 'gender')
|
|
|
|
# Get recent orders for this test
|
|
context['recent_orders'] = LabOrder.objects.filter(
|
|
tests=lab_test,
|
|
tenant=self.request.user.tenant
|
|
).select_related('patient').order_by('-order_datetime')[:10]
|
|
|
|
# Get quality control records
|
|
context['qc_records'] = QualityControl.objects.filter(
|
|
test=lab_test,
|
|
tenant=self.request.user.tenant
|
|
).order_by('-created_at')[:5]
|
|
|
|
return context
|
|
|
|
|
|
class LabTestCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
"""
|
|
Create a new laboratory test.
|
|
"""
|
|
model = LabTest
|
|
form_class = LabTestForm
|
|
template_name = 'laboratory/tests/lab_test_form.html'
|
|
permission_required = 'laboratory.add_labtest'
|
|
success_url = reverse_lazy('laboratory:lab_test_list')
|
|
|
|
def form_valid(self, form):
|
|
form.instance.tenant = self.request.user.tenant
|
|
response = super().form_valid(form)
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=self.request.user,
|
|
action='LAB_TEST_CREATED',
|
|
model='LabTest',
|
|
object_id=str(self.object.test_id),
|
|
details={
|
|
'test_name': self.object.test_name,
|
|
'test_code': self.object.test_code
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, f'Lab test "{self.object.test_name}" created successfully.')
|
|
return response
|
|
|
|
|
|
class LabTestUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
"""
|
|
Update an existing laboratory test.
|
|
"""
|
|
model = LabTest
|
|
form_class = LabTestForm
|
|
template_name = 'laboratory/tests/lab_test_form.html'
|
|
permission_required = 'laboratory.change_labtest'
|
|
|
|
def get_queryset(self):
|
|
return LabTest.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
def get_success_url(self):
|
|
return reverse('laboratory:lab_test_detail', kwargs={'pk': self.object.pk})
|
|
|
|
def form_valid(self, form):
|
|
response = super().form_valid(form)
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=self.request.user,
|
|
action='LAB_TEST_UPDATED',
|
|
model='LabTest',
|
|
object_id=str(self.object.test_id),
|
|
details={
|
|
'test_name': self.object.test_name,
|
|
'changes': form.changed_data
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, f'Lab test "{self.object.test_name}" updated successfully.')
|
|
return response
|
|
|
|
|
|
class LabTestDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
|
|
"""
|
|
Delete a laboratory test (soft delete by deactivating).
|
|
"""
|
|
model = LabTest
|
|
template_name = 'laboratory/tests/lab_test_confirm_delete.html'
|
|
permission_required = 'laboratory.delete_labtest'
|
|
success_url = reverse_lazy('laboratory:lab_test_list')
|
|
|
|
def get_queryset(self):
|
|
return LabTest.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
|
|
# Soft delete by deactivating instead of actual deletion
|
|
self.object.is_active = False
|
|
self.object.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='LAB_TEST_DEACTIVATED',
|
|
model='LabTest',
|
|
object_id=str(self.object.test_id),
|
|
details={'test_name': self.object.test_name}
|
|
)
|
|
|
|
messages.success(request, f'Lab test "{self.object.test_name}" deactivated successfully.')
|
|
return redirect(self.success_url)
|
|
|
|
|
|
class ReferenceRangeListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List all reference ranges with filtering.
|
|
"""
|
|
model = ReferenceRange
|
|
template_name = 'laboratory/reference_ranges/reference_range_list.html'
|
|
context_object_name = 'reference_ranges'
|
|
paginate_by = 25
|
|
|
|
def get_queryset(self):
|
|
queryset = ReferenceRange.objects.filter(test__tenant=self.request.user.tenant)
|
|
|
|
# Filter by test
|
|
test_id = self.request.GET.get('test')
|
|
if test_id:
|
|
queryset = queryset.filter(test_id=test_id)
|
|
|
|
# Filter by gender
|
|
gender = self.request.GET.get('gender')
|
|
if gender:
|
|
queryset = queryset.filter(gender=gender)
|
|
|
|
return queryset.select_related('test').order_by('test__test_name', 'age_min')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context.update({
|
|
'lab_tests': LabTest.objects.filter(
|
|
tenant=self.request.user.tenant,
|
|
is_active=True
|
|
).order_by('test_name'),
|
|
'genders': ReferenceRange.Gender.choices,
|
|
})
|
|
return context
|
|
|
|
|
|
class ReferenceRangeDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Display detailed information about a reference range.
|
|
"""
|
|
model = ReferenceRange
|
|
template_name = 'laboratory/reference_ranges/reference_range_detail.html'
|
|
context_object_name = 'reference_range'
|
|
|
|
def get_queryset(self):
|
|
return ReferenceRange.objects.filter(test__tenant=self.request.user.tenant)
|
|
|
|
|
|
class ReferenceRangeCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
"""
|
|
Create a new reference range.
|
|
"""
|
|
model = ReferenceRange
|
|
form_class = ReferenceRangeForm
|
|
template_name = 'laboratory/reference_ranges/reference_range_form.html'
|
|
permission_required = 'laboratory.add_referencerange'
|
|
success_url = reverse_lazy('laboratory:reference_range_list')
|
|
|
|
def form_valid(self, form):
|
|
form.instance.tenant = self.request.user.tenant
|
|
response = super().form_valid(form)
|
|
|
|
# Log the action
|
|
AuditLogger.log_event(
|
|
user=self.request.user,
|
|
action='REFERENCE_RANGE_CREATED',
|
|
model='ReferenceRange',
|
|
object_id=str(self.object.id),
|
|
details={
|
|
'test_name': self.object.test.test_name,
|
|
'gender': self.object.gender,
|
|
'age_range': f"{self.object.age_min}-{self.object.age_max}"
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, 'Reference range created successfully.')
|
|
return response
|
|
|
|
|
|
class ReferenceRangeUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
"""
|
|
Update an existing reference range.
|
|
"""
|
|
model = ReferenceRange
|
|
form_class = ReferenceRangeForm
|
|
template_name = 'laboratory/reference_ranges/reference_range_form.html'
|
|
permission_required = 'laboratory.change_referencerange'
|
|
|
|
def get_queryset(self):
|
|
return ReferenceRange.objects.filter(test__tenant=self.request.user.tenant)
|
|
|
|
def get_success_url(self):
|
|
return reverse('laboratory:reference_range_detail', kwargs={'pk': self.object.pk})
|
|
|
|
def form_valid(self, form):
|
|
response = super().form_valid(form)
|
|
|
|
# Log the action
|
|
AuditLogger.log_event(
|
|
user=self.request.user,
|
|
action='REFERENCE_RANGE_UPDATED',
|
|
model='ReferenceRange',
|
|
object_id=str(self.object.id),
|
|
details={
|
|
'test_name': self.object.test.test_name,
|
|
'changes': form.changed_data
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, 'Reference range updated successfully.')
|
|
return response
|
|
|
|
|
|
class ReferenceRangeDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
|
|
"""
|
|
Delete a reference range.
|
|
"""
|
|
model = ReferenceRange
|
|
template_name = 'laboratory/reference_ranges/reference_range_confirm_delete.html'
|
|
permission_required = 'laboratory.delete_referencerange'
|
|
success_url = reverse_lazy('laboratory:reference_range_list')
|
|
|
|
def get_queryset(self):
|
|
return ReferenceRange.objects.filter(test__tenant=self.request.user.tenant)
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
test_name = self.object.test.test_name
|
|
|
|
response = super().delete(request, *args, **kwargs)
|
|
|
|
# Log the action
|
|
AuditLogger.log_event(
|
|
user=request.user,
|
|
action='REFERENCE_RANGE_DELETED',
|
|
model='ReferenceRange',
|
|
object_id=str(self.object.id),
|
|
details={'test_name': test_name}
|
|
)
|
|
|
|
messages.success(request, 'Reference range deleted successfully.')
|
|
return response
|
|
|
|
|
|
class LabOrderListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List all laboratory orders with filtering and search.
|
|
"""
|
|
model = LabOrder
|
|
template_name = 'laboratory/orders/lab_order_list.html'
|
|
context_object_name = 'orders'
|
|
paginate_by = 25
|
|
|
|
def get_queryset(self):
|
|
queryset = LabOrder.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(tests__test_name__icontains=search)
|
|
)
|
|
|
|
# Filter by status
|
|
status = self.request.GET.get('status')
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
|
|
# Filter by priority
|
|
priority = self.request.GET.get('priority')
|
|
if priority:
|
|
queryset = queryset.filter(priority=priority)
|
|
|
|
# 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(order_datetime__date__gte=date_from)
|
|
if date_to:
|
|
queryset = queryset.filter(order_datetime__date__lte=date_to)
|
|
|
|
return queryset.select_related(
|
|
'patient', 'ordering_provider'
|
|
).order_by('-order_datetime')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context.update({
|
|
'statuses': LabOrder._meta.get_field('status').choices,
|
|
'priorities': LabOrder._meta.get_field('priority').choices,
|
|
})
|
|
return context
|
|
|
|
|
|
class LabOrderDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Display detailed information about a laboratory order.
|
|
"""
|
|
model = LabOrder
|
|
template_name = 'laboratory/orders/lab_order_detail.html'
|
|
context_object_name = 'lab_order'
|
|
|
|
def get_queryset(self):
|
|
return LabOrder.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
lab_order = self.object
|
|
|
|
# Get specimens for this order
|
|
context['specimens'] = lab_order.specimens.all().order_by('-collected_datetime')
|
|
|
|
# Get results for this order
|
|
context['results'] = lab_order.results.all().order_by('-created_at')
|
|
|
|
return context
|
|
|
|
|
|
class LabOrderCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
"""
|
|
Create a new laboratory order.
|
|
"""
|
|
model = LabOrder
|
|
form_class = LabOrderForm
|
|
template_name = 'laboratory/orders/lab_order_form.html'
|
|
permission_required = 'laboratory.add_laborder'
|
|
success_url = reverse_lazy('laboratory:lab_order_list')
|
|
|
|
def form_valid(self, form):
|
|
form.instance.tenant = self.request.user.tenant
|
|
form.instance.ordering_provider = self.request.user
|
|
form.instance.order_datetime = timezone.now()
|
|
response = super().form_valid(form)
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=self.request.user,
|
|
action='LAB_ORDER_CREATED',
|
|
model='LabOrder',
|
|
object_id=str(self.object.order_id),
|
|
details={
|
|
'patient_name': f"{self.object.patient.first_name} {self.object.patient.last_name}",
|
|
'test_name': self.object.test.test_name,
|
|
'priority': self.object.priority
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, 'Laboratory order created successfully.')
|
|
return response
|
|
|
|
|
|
class LabOrderUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
"""
|
|
Update laboratory order (limited to status and notes only).
|
|
"""
|
|
model = LabOrder
|
|
fields = ['status', 'notes'] # Restricted fields for clinical orders
|
|
template_name = 'laboratory/orders/lab_order_form.html'
|
|
permission_required = 'laboratory.change_laborder'
|
|
|
|
def get_queryset(self):
|
|
return LabOrder.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
def get_success_url(self):
|
|
return reverse('laboratory:lab_order_detail', kwargs={'pk': self.object.pk})
|
|
|
|
def form_valid(self, form):
|
|
response = super().form_valid(form)
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=self.request.user,
|
|
action='LAB_ORDER_UPDATED',
|
|
model='LabOrder',
|
|
object_id=str(self.object.order_id),
|
|
details={
|
|
'patient_name': f"{self.object.patient.first_name} {self.object.patient.last_name}",
|
|
'changes': form.changed_data
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, 'Laboratory order updated successfully.')
|
|
return response
|
|
|
|
|
|
class SpecimenListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List all specimens with filtering and search.
|
|
"""
|
|
model = Specimen
|
|
template_name = 'laboratory/specimens/specimen_list.html'
|
|
context_object_name = 'specimens'
|
|
paginate_by = 25
|
|
|
|
def get_queryset(self):
|
|
queryset = Specimen.objects.filter(order__tenant=self.request.user.tenant)
|
|
|
|
# Search functionality
|
|
search = self.request.GET.get('search')
|
|
if search:
|
|
queryset = queryset.filter(
|
|
Q(specimen_id__icontains=search) |
|
|
Q(order__patient__first_name__icontains=search) |
|
|
Q(order__patient__last_name__icontains=search) |
|
|
Q(order__patient__mrn__icontains=search)
|
|
)
|
|
|
|
# Filter by status
|
|
status = self.request.GET.get('status')
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
|
|
# Filter by specimen type
|
|
specimen_type = self.request.GET.get('specimen_type')
|
|
if specimen_type:
|
|
queryset = queryset.filter(specimen_type=specimen_type)
|
|
|
|
return queryset.select_related('order__patient').order_by('-collected_datetime')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context.update({
|
|
'statuses': Specimen._meta.get_field('status').choices,
|
|
'specimen_types': Specimen._meta.get_field('specimen_type').choices,
|
|
})
|
|
return context
|
|
|
|
|
|
class SpecimenDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Display detailed information about a specimen.
|
|
"""
|
|
model = Specimen
|
|
template_name = 'laboratory/specimens/specimen_detail.html'
|
|
context_object_name = 'specimen'
|
|
|
|
def get_queryset(self):
|
|
return Specimen.objects.filter(order__tenant=self.request.user.tenant)
|
|
|
|
|
|
class SpecimenCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
"""
|
|
Create a new specimen record.
|
|
"""
|
|
model = Specimen
|
|
form_class = SpecimenForm
|
|
template_name = 'laboratory/specimens/specimen_form.html'
|
|
permission_required = 'laboratory.add_specimen'
|
|
success_url = reverse_lazy('laboratory:specimen_list')
|
|
|
|
def form_valid(self, form):
|
|
form.instance.tenant = self.request.user.tenant
|
|
form.instance.collected_by = self.request.user
|
|
response = super().form_valid(form)
|
|
|
|
# Log the action
|
|
AuditLogger.log_event(
|
|
user=self.request.user,
|
|
action='SPECIMEN_COLLECTED',
|
|
model='Specimen',
|
|
object_id=str(self.object.specimen_id),
|
|
details={
|
|
'patient_name': f"{self.object.order.patient.first_name} {self.object.order.patient.last_name}",
|
|
'specimen_type': self.object.specimen_type
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, 'Specimen record created successfully.')
|
|
return response
|
|
|
|
|
|
class SpecimenUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
"""
|
|
Update specimen (limited to status and processing notes).
|
|
"""
|
|
model = Specimen
|
|
fields = ['status'] # Restricted fields
|
|
template_name = 'laboratory/specimens/specimen_form.html'
|
|
permission_required = 'laboratory.change_specimen'
|
|
|
|
def get_queryset(self):
|
|
return Specimen.objects.filter(order__tenant=self.request.user.tenant)
|
|
|
|
def get_success_url(self):
|
|
return reverse('laboratory:specimen_detail', kwargs={'pk': self.object.pk})
|
|
|
|
def form_valid(self, form):
|
|
response = super().form_valid(form)
|
|
|
|
# Log the action
|
|
AuditLogger.log_event(
|
|
user=self.request.user,
|
|
action='SPECIMEN_UPDATED',
|
|
model='Specimen',
|
|
object_id=str(self.object.specimen_id),
|
|
details={
|
|
'patient_name': f"{self.object.order.patient.first_name} {self.object.order.patient.last_name}",
|
|
'changes': form.changed_data
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, 'Specimen updated successfully.')
|
|
return response
|
|
|
|
|
|
class LabResultListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List all laboratory results with filtering and search.
|
|
"""
|
|
model = LabResult
|
|
template_name = 'laboratory/results/lab_result_list.html'
|
|
context_object_name = 'lab_results'
|
|
paginate_by = 25
|
|
|
|
def get_queryset(self):
|
|
tenant = self.request.user.tenant
|
|
queryset = LabResult.objects.filter(order__tenant=tenant)
|
|
|
|
# Search functionality
|
|
search = self.request.GET.get('search')
|
|
if search:
|
|
queryset = queryset.filter(
|
|
Q(order__patient__first_name__icontains=search) |
|
|
Q(order__patient__last_name__icontains=search) |
|
|
Q(order__patient__mrn__icontains=search) |
|
|
Q(test__test_name__icontains=search)
|
|
)
|
|
|
|
# Filter by status
|
|
status = self.request.GET.get('status')
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
|
|
# Filter by critical results
|
|
critical_only = self.request.GET.get('critical_only')
|
|
if critical_only:
|
|
queryset = queryset.filter(is_critical=True)
|
|
|
|
return queryset.select_related(
|
|
'order__patient', 'test', 'verified_by'
|
|
).order_by('-created_at')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context.update({
|
|
'statuses': LabResult._meta.get_field('status').choices,
|
|
})
|
|
return context
|
|
|
|
|
|
class LabResultDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Display detailed information about a laboratory result.
|
|
"""
|
|
model = LabResult
|
|
template_name = 'laboratory/results/lab_result_detail.html'
|
|
context_object_name = 'lab_result'
|
|
|
|
def get_queryset(self):
|
|
tenant = self.request.user.tenant
|
|
return LabResult.objects.filter(order__tenant=tenant)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
tenant = self.request.user.tenant
|
|
context = super().get_context_data(**kwargs)
|
|
lab_result = self.object
|
|
|
|
# Get applicable reference ranges
|
|
patient = lab_result.order.patient
|
|
context['reference_ranges'] = ReferenceRange.objects.filter(
|
|
test=lab_result.test,
|
|
test__tenant=tenant,
|
|
age_min__lte=patient.age if hasattr(patient, 'age') else 999,
|
|
age_max__gte=patient.age if hasattr(patient, 'age') else 0,
|
|
).filter(
|
|
Q(gender=patient.gender) | Q(gender='BOTH')
|
|
)
|
|
|
|
return context
|
|
|
|
|
|
class LabResultCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
"""
|
|
Create a new laboratory result.
|
|
"""
|
|
model = LabResult
|
|
form_class = LabResultForm
|
|
template_name = 'laboratory/results/lab_result_form.html'
|
|
permission_required = 'laboratory.add_labresult'
|
|
success_url = reverse_lazy('laboratory:lab_result_list')
|
|
|
|
def form_valid(self, form):
|
|
form.instance.tenant = self.request.user.tenant
|
|
form.instance.result_datetime = timezone.now()
|
|
response = super().form_valid(form)
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=self.request.user,
|
|
action='LAB_RESULT_ENTERED',
|
|
model='LabResult',
|
|
object_id=str(self.object.result_id),
|
|
details={
|
|
'patient_name': f"{self.object.order.patient.first_name} {self.object.order.patient.last_name}",
|
|
'test_name': self.object.test.test_name,
|
|
'is_critical': self.object.is_critical
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, 'Laboratory result entered successfully.')
|
|
return response
|
|
|
|
|
|
# Note: No UpdateView or DeleteView for LabResult - Append-only for clinical records
|
|
# Results can only be verified or corrected through addendum process
|
|
|
|
|
|
class QualityControlListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List all quality control records.
|
|
"""
|
|
model = QualityControl
|
|
template_name = 'laboratory/quality_control/qc_sample_list.html'
|
|
context_object_name = 'qc_samples'
|
|
paginate_by = 25
|
|
|
|
def get_queryset(self):
|
|
queryset = QualityControl.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
# Filter by test
|
|
test_id = self.request.GET.get('test')
|
|
if test_id:
|
|
queryset = queryset.filter(test_id=test_id)
|
|
|
|
# Filter by result
|
|
result = self.request.GET.get('result')
|
|
if result:
|
|
queryset = queryset.filter(result=result)
|
|
|
|
return queryset.select_related('test', 'performed_by')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context.update({
|
|
'lab_tests': LabTest.objects.filter(
|
|
tenant=self.request.user.tenant,
|
|
is_active=True
|
|
).order_by('test_name'),
|
|
'results': QualityControl._meta.get_field('result').choices,
|
|
})
|
|
return context
|
|
|
|
|
|
class QualityControlDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Display detailed information about a quality control record.
|
|
"""
|
|
model = QualityControl
|
|
template_name = 'laboratory/quality_control/qc_sample_detail.html'
|
|
context_object_name = 'qc_record'
|
|
|
|
def get_queryset(self):
|
|
return QualityControl.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
|
|
class QualityControlCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
"""
|
|
Create a new quality control record.
|
|
"""
|
|
model = QualityControl
|
|
form_class = QualityControlForm
|
|
template_name = 'laboratory/quality_control/qc_sample_form.html'
|
|
permission_required = 'laboratory.add_qualitycontrol'
|
|
success_url = reverse_lazy('laboratory:quality_control_list')
|
|
|
|
def form_valid(self, form):
|
|
form.instance.tenant = self.request.user.tenant
|
|
form.instance.performed_by = self.request.user
|
|
response = super().form_valid(form)
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=self.request.user,
|
|
action='QC_TEST_PERFORMED',
|
|
model='QualityControl',
|
|
object_id=str(self.object.id),
|
|
details={
|
|
'test_name': self.object.test.test_name,
|
|
'result': self.object.result
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, 'Quality control record created successfully.')
|
|
return response
|
|
|
|
|
|
class QualityControlUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
"""
|
|
Update quality control record (limited fields).
|
|
"""
|
|
model = QualityControl
|
|
fields = ['result', 'comments'] # Limited fields for operational data
|
|
template_name = 'laboratory/quality_control_form.html'
|
|
permission_required = 'laboratory.change_qualitycontrol'
|
|
|
|
def get_queryset(self):
|
|
return QualityControl.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
def get_success_url(self):
|
|
return reverse('laboratory:quality_control_detail', kwargs={'pk': self.object.pk})
|
|
|
|
def form_valid(self, form):
|
|
response = super().form_valid(form)
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=self.request.user,
|
|
action='QC_TEST_UPDATED',
|
|
model='QualityControl',
|
|
object_id=str(self.object.id),
|
|
details={
|
|
'test_name': self.object.test.test_name,
|
|
'changes': form.changed_data
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, 'Quality control record updated successfully.')
|
|
return response
|
|
|
|
|
|
@login_required
|
|
def laboratory_stats(request):
|
|
"""
|
|
HTMX endpoint for laboratory statistics.
|
|
"""
|
|
tenant = request.user.tenant
|
|
today = timezone.now().date()
|
|
|
|
stats = {
|
|
'pending_orders': LabOrder.objects.filter(
|
|
tenant=tenant,
|
|
status='PENDING'
|
|
).count(),
|
|
'in_progress_orders': LabOrder.objects.filter(
|
|
tenant=tenant,
|
|
status='IN_PROGRESS'
|
|
).count(),
|
|
'results_pending': LabResult.objects.filter(
|
|
order__tenant=tenant,
|
|
status='PENDING'
|
|
).count(),
|
|
'critical_results': LabResult.objects.filter(
|
|
order__tenant=tenant,
|
|
is_critical=True,
|
|
status='VERIFIED',
|
|
created_at__date=today
|
|
).count(),
|
|
'specimens_collected_today': LabResult.objects.filter(
|
|
order__tenant=tenant,
|
|
specimen__status='COLLECTED',
|
|
specimen__collected_datetime__date=today
|
|
).count(),
|
|
}
|
|
|
|
return render(request, 'laboratory/partials/lab_stats.html', {'stats': stats})
|
|
|
|
|
|
@login_required
|
|
def order_search(request):
|
|
"""
|
|
HTMX endpoint for order search.
|
|
"""
|
|
search = request.GET.get('search', '')
|
|
status = request.GET.get('status', '')
|
|
|
|
queryset = LabOrder.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(test__test_name__icontains=search)
|
|
)
|
|
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
|
|
orders = queryset.select_related(
|
|
'patient', 'test', 'ordering_provider'
|
|
).order_by('-order_datetime')[:20]
|
|
|
|
return render(request, 'laboratory/partials/order_list.html', {'orders': orders})
|
|
|
|
|
|
@login_required
|
|
def receive_specimen(request, order_id):
|
|
"""
|
|
Mark specimen as received for processing.
|
|
"""
|
|
if request.method == 'POST':
|
|
order = get_object_or_404(
|
|
LabOrder,
|
|
id=order_id,
|
|
tenant=request.user.tenant
|
|
)
|
|
|
|
# Update specimen status
|
|
specimens = order.specimens.filter(status='COLLECTED')
|
|
specimens.update(
|
|
status='RECEIVED',
|
|
received_datetime=timezone.now(),
|
|
received_by=request.user
|
|
)
|
|
|
|
# Update order status
|
|
order.status = 'IN_PROGRESS'
|
|
order.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='SPECIMEN_RECEIVED',
|
|
model='LabOrder',
|
|
object_id=str(order.order_id),
|
|
details={
|
|
'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
|
|
'specimens_count': specimens.count()
|
|
}
|
|
)
|
|
|
|
messages.success(request, 'Specimen(s) marked as received.')
|
|
|
|
if request.headers.get('HX-Request'):
|
|
return render(request, 'laboratory/partials/order_status.html', {'order': order})
|
|
|
|
return redirect('laboratory:lab_order_detail', pk=order.pk)
|
|
|
|
return JsonResponse({'success': False})
|
|
|
|
|
|
@login_required
|
|
def start_processing(request, order_id):
|
|
"""
|
|
Start processing laboratory order.
|
|
"""
|
|
if request.method == 'POST':
|
|
order = get_object_or_404(
|
|
LabOrder,
|
|
id=order_id,
|
|
tenant=request.user.tenant
|
|
)
|
|
|
|
order.status = 'IN_PROGRESS'
|
|
order.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='LAB_PROCESSING_STARTED',
|
|
model='LabOrder',
|
|
object_id=str(order.order_id),
|
|
details={
|
|
'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
|
|
'test_name': order.test.test_name
|
|
}
|
|
)
|
|
|
|
messages.success(request, 'Laboratory processing started.')
|
|
|
|
if request.headers.get('HX-Request'):
|
|
return render(request, 'laboratory/partials/order_status.html', {'order': order})
|
|
|
|
return redirect('laboratory:lab_order_detail', pk=order.pk)
|
|
|
|
return JsonResponse({'success': False})
|
|
|
|
|
|
@login_required
|
|
def reject_specimen(request, specimen_id):
|
|
"""
|
|
Reject a specimen with reason.
|
|
"""
|
|
if request.method == 'POST':
|
|
specimen = get_object_or_404(
|
|
Specimen,
|
|
id=specimen_id,
|
|
tenant=request.user.tenant
|
|
)
|
|
|
|
rejection_reason = request.POST.get('rejection_reason', '')
|
|
|
|
specimen.status = 'REJECTED'
|
|
specimen.rejection_reason = rejection_reason
|
|
specimen.rejected_by = request.user
|
|
specimen.rejected_datetime = timezone.now()
|
|
specimen.save()
|
|
|
|
# Update order status
|
|
specimen.order.status = 'CANCELLED'
|
|
specimen.order.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='SPECIMEN_REJECTED',
|
|
model='Specimen',
|
|
object_id=str(specimen.specimen_id),
|
|
details={
|
|
'patient_name': f"{specimen.order.patient.first_name} {specimen.order.patient.last_name}",
|
|
'rejection_reason': rejection_reason
|
|
}
|
|
)
|
|
|
|
messages.warning(request, 'Specimen rejected.')
|
|
|
|
if request.headers.get('HX-Request'):
|
|
return render(request, 'laboratory/partials/specimen_status.html', {'specimen': specimen})
|
|
|
|
return redirect('laboratory:specimen_detail', pk=specimen.pk)
|
|
|
|
return JsonResponse({'success': False})
|
|
|
|
|
|
@login_required
|
|
def schedule_collection(request, order_id):
|
|
"""
|
|
Schedule specimen collection for an order.
|
|
"""
|
|
if request.method == 'POST':
|
|
order = get_object_or_404(
|
|
LabOrder,
|
|
id=order_id,
|
|
tenant=request.user.tenant
|
|
)
|
|
|
|
collection_datetime = request.POST.get('collection_datetime')
|
|
if collection_datetime:
|
|
collection_datetime = timezone.datetime.fromisoformat(collection_datetime)
|
|
|
|
order.scheduled_collection = collection_datetime
|
|
order.status = 'SCHEDULED'
|
|
order.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='COLLECTION_SCHEDULED',
|
|
model='LabOrder',
|
|
object_id=str(order.order_id),
|
|
details={
|
|
'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
|
|
'scheduled_time': collection_datetime.isoformat()
|
|
}
|
|
)
|
|
|
|
messages.success(request, 'Collection scheduled successfully.')
|
|
|
|
if request.headers.get('HX-Request'):
|
|
return render(request, 'laboratory/partials/order_status.html', {'order': order})
|
|
|
|
return redirect('laboratory:lab_order_detail', pk=order.pk)
|
|
|
|
return JsonResponse({'success': False})
|
|
|
|
|
|
@login_required
|
|
def mark_collected(request, order_id):
|
|
"""
|
|
Mark specimen as collected.
|
|
"""
|
|
if request.method == 'POST':
|
|
order = get_object_or_404(
|
|
LabOrder,
|
|
id=order_id,
|
|
tenant=request.user.tenant
|
|
)
|
|
|
|
# Create specimen record if it doesn't exist
|
|
specimen, created = Specimen.objects.get_or_create(
|
|
order=order,
|
|
tenant=request.user.tenant,
|
|
defaults={
|
|
'specimen_type': 'BLOOD', # Default, should be specified
|
|
'collection_datetime': timezone.now(),
|
|
'collected_by': request.user,
|
|
'status': 'COLLECTED'
|
|
}
|
|
)
|
|
|
|
if not created:
|
|
specimen.status = 'COLLECTED'
|
|
specimen.collection_datetime = timezone.now()
|
|
specimen.collected_by = request.user
|
|
specimen.save()
|
|
|
|
# Update order status
|
|
order.status = 'COLLECTED'
|
|
order.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_event(
|
|
user=request.user,
|
|
action='SPECIMEN_COLLECTED',
|
|
model='LabOrder',
|
|
object_id=str(order.order_id),
|
|
details={
|
|
'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
|
|
'specimen_type': specimen.specimen_type
|
|
}
|
|
)
|
|
|
|
messages.success(request, 'Specimen marked as collected.')
|
|
|
|
if request.headers.get('HX-Request'):
|
|
return render(request, 'laboratory/partials/order_status.html', {'order': order})
|
|
|
|
return redirect('laboratory:lab_order_detail', pk=order.pk)
|
|
|
|
return JsonResponse({'success': False})
|
|
|
|
#
|
|
# """
|
|
# Laboratory app views with healthcare-focused CRUD operations.
|
|
# Implements appropriate access patterns for clinical laboratory workflows.
|
|
# """
|
|
#
|
|
# 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, PermissionRequiredMixin
|
|
# from django.views.generic import (
|
|
# ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView
|
|
# )
|
|
# from django.http import JsonResponse, HttpResponse
|
|
# from django.db.models import Q, Count, Avg, Sum, F
|
|
# from django.utils import timezone
|
|
# from django.contrib import messages
|
|
# from django.urls import reverse_lazy, reverse
|
|
# from django.core.paginator import Paginator
|
|
# from django.template.loader import render_to_string
|
|
# from datetime import datetime, timedelta, date
|
|
# import json
|
|
#
|
|
# from core.utils import AuditLogger
|
|
# from .models import (
|
|
# LabTest, LabOrder, Specimen, LabResult, QualityControl, ReferenceRange
|
|
# )
|
|
# from .forms import (
|
|
# LabTestForm, LabOrderForm, SpecimenForm, LabResultForm,
|
|
# QualityControlForm, ReferenceRangeForm
|
|
# )
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # DASHBOARD AND OVERVIEW VIEWS
|
|
# # ============================================================================
|
|
#
|
|
# class LaboratoryDashboardView(LoginRequiredMixin, TemplateView):
|
|
# """
|
|
# Main laboratory dashboard with key metrics and recent activity.
|
|
# """
|
|
# template_name = 'laboratory/dashboard.html'
|
|
#
|
|
# 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({
|
|
# 'pending_orders': LabOrder.objects.filter(
|
|
# tenant=tenant,
|
|
# status='PENDING'
|
|
# ).count(),
|
|
# 'in_progress_orders': LabOrder.objects.filter(
|
|
# tenant=tenant,
|
|
# status='IN_PROGRESS'
|
|
# ).count(),
|
|
# 'specimens_collected': Specimen.objects.filter(
|
|
# tenant=tenant,
|
|
# collection_datetime__date=today
|
|
# ).count(),
|
|
# 'results_pending': LabResult.objects.filter(
|
|
# tenant=tenant,
|
|
# status='PENDING'
|
|
# ).count(),
|
|
# 'results_completed_today': LabResult.objects.filter(
|
|
# tenant=tenant,
|
|
# result_datetime__date=today,
|
|
# status='VERIFIED'
|
|
# ).count(),
|
|
# 'qc_tests_today': QualityControl.objects.filter(
|
|
# tenant=tenant,
|
|
# test_date=today
|
|
# ).count(),
|
|
# 'total_tests_available': LabTest.objects.filter(
|
|
# tenant=tenant,
|
|
# is_active=True
|
|
# ).count(),
|
|
# 'critical_results': LabResult.objects.filter(
|
|
# tenant=tenant,
|
|
# is_critical=True,
|
|
# status='VERIFIED',
|
|
# result_datetime__date=today
|
|
# ).count(),
|
|
# })
|
|
#
|
|
# # Recent orders
|
|
# context['recent_orders'] = LabOrder.objects.filter(
|
|
# tenant=tenant
|
|
# ).select_related('patient', 'ordering_provider').order_by('-order_datetime')[:10]
|
|
#
|
|
# # Recent results
|
|
# context['recent_results'] = LabResult.objects.filter(
|
|
# tenant=tenant,
|
|
# status='VERIFIED'
|
|
# ).select_related('order', 'test').order_by('-result_datetime')[:10]
|
|
#
|
|
# return context
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # LAB TEST VIEWS (FULL CRUD - Master Data)
|
|
# # ============================================================================
|
|
#
|
|
# class LabTestListView(LoginRequiredMixin, ListView):
|
|
# """
|
|
# List all laboratory tests with filtering and search.
|
|
# """
|
|
# model = LabTest
|
|
# template_name = 'laboratory/lab_test_list.html'
|
|
# context_object_name = 'lab_tests'
|
|
# paginate_by = 25
|
|
#
|
|
# def get_queryset(self):
|
|
# queryset = LabTest.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# # Search functionality
|
|
# search = self.request.GET.get('search')
|
|
# if search:
|
|
# queryset = queryset.filter(
|
|
# Q(test_name__icontains=search) |
|
|
# Q(test_code__icontains=search) |
|
|
# Q(test_description__icontains=search)
|
|
# )
|
|
#
|
|
# # Filter by category
|
|
# category = self.request.GET.get('category')
|
|
# if category:
|
|
# queryset = queryset.filter(category=category)
|
|
#
|
|
# # Filter by active status
|
|
# active_only = self.request.GET.get('active_only')
|
|
# if active_only:
|
|
# queryset = queryset.filter(is_active=True)
|
|
#
|
|
# return queryset.order_by('test_name')
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context.update({
|
|
# 'categories': LabTest._meta.get_field('category').choices,
|
|
# 'search_query': self.request.GET.get('search', ''),
|
|
# })
|
|
# return context
|
|
#
|
|
#
|
|
# class LabTestDetailView(LoginRequiredMixin, DetailView):
|
|
# """
|
|
# Display detailed information about a laboratory test.
|
|
# """
|
|
# model = LabTest
|
|
# template_name = 'laboratory/lab_test_detail.html'
|
|
# context_object_name = 'lab_test'
|
|
#
|
|
# def get_queryset(self):
|
|
# return LabTest.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# lab_test = self.object
|
|
#
|
|
# # Get reference ranges for this test
|
|
# context['reference_ranges'] = lab_test.reference_ranges.all().order_by('age_min', 'gender')
|
|
#
|
|
# # Get recent orders for this test
|
|
# context['recent_orders'] = LabOrder.objects.filter(
|
|
# test=lab_test,
|
|
# tenant=self.request.user.tenant
|
|
# ).select_related('patient').order_by('-order_datetime')[:10]
|
|
#
|
|
# # Get quality control records
|
|
# context['qc_records'] = QualityControl.objects.filter(
|
|
# test=lab_test,
|
|
# tenant=self.request.user.tenant
|
|
# ).order_by('-test_date')[:5]
|
|
#
|
|
# return context
|
|
#
|
|
#
|
|
# class LabTestCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
# """
|
|
# Create a new laboratory test.
|
|
# """
|
|
# model = LabTest
|
|
# form_class = LabTestForm
|
|
# template_name = 'laboratory/lab_test_form.html'
|
|
# permission_required = 'laboratory.add_labtest'
|
|
# success_url = reverse_lazy('laboratory:lab_test_list')
|
|
#
|
|
# def form_valid(self, form):
|
|
# form.instance.tenant = self.request.user.tenant
|
|
# response = super().form_valid(form)
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=self.request.user,
|
|
# action='LAB_TEST_CREATED',
|
|
# model='LabTest',
|
|
# object_id=str(self.object.test_id),
|
|
# details={
|
|
# 'test_name': self.object.test_name,
|
|
# 'test_code': self.object.test_code
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(self.request, f'Lab test "{self.object.test_name}" created successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# class LabTestUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
# """
|
|
# Update an existing laboratory test.
|
|
# """
|
|
# model = LabTest
|
|
# form_class = LabTestForm
|
|
# template_name = 'laboratory/lab_test_form.html'
|
|
# permission_required = 'laboratory.change_labtest'
|
|
#
|
|
# def get_queryset(self):
|
|
# return LabTest.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_success_url(self):
|
|
# return reverse('laboratory:lab_test_detail', kwargs={'pk': self.object.pk})
|
|
#
|
|
# def form_valid(self, form):
|
|
# response = super().form_valid(form)
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=self.request.user,
|
|
# action='LAB_TEST_UPDATED',
|
|
# model='LabTest',
|
|
# object_id=str(self.object.test_id),
|
|
# details={
|
|
# 'test_name': self.object.test_name,
|
|
# 'changes': form.changed_data
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(self.request, f'Lab test "{self.object.test_name}" updated successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# class LabTestDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
|
|
# """
|
|
# Delete a laboratory test (soft delete by deactivating).
|
|
# """
|
|
# model = LabTest
|
|
# template_name = 'laboratory/lab_test_confirm_delete.html'
|
|
# permission_required = 'laboratory.delete_labtest'
|
|
# success_url = reverse_lazy('laboratory:lab_test_list')
|
|
#
|
|
# def get_queryset(self):
|
|
# return LabTest.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def delete(self, request, *args, **kwargs):
|
|
# self.object = self.get_object()
|
|
#
|
|
# # Soft delete by deactivating instead of actual deletion
|
|
# self.object.is_active = False
|
|
# self.object.save()
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=request.user,
|
|
# action='LAB_TEST_DEACTIVATED',
|
|
# model='LabTest',
|
|
# object_id=str(self.object.test_id),
|
|
# details={'test_name': self.object.test_name}
|
|
# )
|
|
#
|
|
# messages.success(request, f'Lab test "{self.object.test_name}" deactivated successfully.')
|
|
# return redirect(self.success_url)
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # REFERENCE RANGE VIEWS (FULL CRUD - Reference Data)
|
|
# # ============================================================================
|
|
#
|
|
# class ReferenceRangeListView(LoginRequiredMixin, ListView):
|
|
# """
|
|
# List all reference ranges with filtering.
|
|
# """
|
|
# model = ReferenceRange
|
|
# template_name = 'laboratory/reference_range_list.html'
|
|
# context_object_name = 'reference_ranges'
|
|
# paginate_by = 25
|
|
#
|
|
# def get_queryset(self):
|
|
# queryset = ReferenceRange.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# # Filter by test
|
|
# test_id = self.request.GET.get('test')
|
|
# if test_id:
|
|
# queryset = queryset.filter(test_id=test_id)
|
|
#
|
|
# # Filter by gender
|
|
# gender = self.request.GET.get('gender')
|
|
# if gender:
|
|
# queryset = queryset.filter(gender=gender)
|
|
#
|
|
# return queryset.select_related('test').order_by('test__test_name', 'age_min')
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context.update({
|
|
# 'lab_tests': LabTest.objects.filter(
|
|
# tenant=self.request.user.tenant,
|
|
# is_active=True
|
|
# ).order_by('test_name'),
|
|
# 'genders': ReferenceRange._meta.get_field('gender').choices,
|
|
# })
|
|
# return context
|
|
#
|
|
#
|
|
# class ReferenceRangeDetailView(LoginRequiredMixin, DetailView):
|
|
# """
|
|
# Display detailed information about a reference range.
|
|
# """
|
|
# model = ReferenceRange
|
|
# template_name = 'laboratory/reference_range_detail.html'
|
|
# context_object_name = 'reference_range'
|
|
#
|
|
# def get_queryset(self):
|
|
# return ReferenceRange.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
#
|
|
# class ReferenceRangeCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
# """
|
|
# Create a new reference range.
|
|
# """
|
|
# model = ReferenceRange
|
|
# form_class = ReferenceRangeForm
|
|
# template_name = 'laboratory/reference_range_form.html'
|
|
# permission_required = 'laboratory.add_referencerange'
|
|
# success_url = reverse_lazy('laboratory:reference_range_list')
|
|
#
|
|
# def form_valid(self, form):
|
|
# form.instance.tenant = self.request.user.tenant
|
|
# response = super().form_valid(form)
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=self.request.user,
|
|
# action='REFERENCE_RANGE_CREATED',
|
|
# model='ReferenceRange',
|
|
# object_id=str(self.object.id),
|
|
# details={
|
|
# 'test_name': self.object.test.test_name,
|
|
# 'gender': self.object.gender,
|
|
# 'age_range': f"{self.object.age_min}-{self.object.age_max}"
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(self.request, 'Reference range created successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# class ReferenceRangeUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
# """
|
|
# Update an existing reference range.
|
|
# """
|
|
# model = ReferenceRange
|
|
# form_class = ReferenceRangeForm
|
|
# template_name = 'laboratory/reference_range_form.html'
|
|
# permission_required = 'laboratory.change_referencerange'
|
|
#
|
|
# def get_queryset(self):
|
|
# return ReferenceRange.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_success_url(self):
|
|
# return reverse('laboratory:reference_range_detail', kwargs={'pk': self.object.pk})
|
|
#
|
|
# def form_valid(self, form):
|
|
# response = super().form_valid(form)
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=self.request.user,
|
|
# action='REFERENCE_RANGE_UPDATED',
|
|
# model='ReferenceRange',
|
|
# object_id=str(self.object.id),
|
|
# details={
|
|
# 'test_name': self.object.test.test_name,
|
|
# 'changes': form.changed_data
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(self.request, 'Reference range updated successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# class ReferenceRangeDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
|
|
# """
|
|
# Delete a reference range.
|
|
# """
|
|
# model = ReferenceRange
|
|
# template_name = 'laboratory/reference_range_confirm_delete.html'
|
|
# permission_required = 'laboratory.delete_referencerange'
|
|
# success_url = reverse_lazy('laboratory:reference_range_list')
|
|
#
|
|
# def get_queryset(self):
|
|
# return ReferenceRange.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def delete(self, request, *args, **kwargs):
|
|
# self.object = self.get_object()
|
|
# test_name = self.object.test.test_name
|
|
#
|
|
# response = super().delete(request, *args, **kwargs)
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=request.user,
|
|
# action='REFERENCE_RANGE_DELETED',
|
|
# model='ReferenceRange',
|
|
# object_id=str(self.object.id),
|
|
# details={'test_name': test_name}
|
|
# )
|
|
#
|
|
# messages.success(request, 'Reference range deleted successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # LAB ORDER VIEWS (RESTRICTED CRUD - Clinical Orders)
|
|
# # ============================================================================
|
|
#
|
|
# class LabOrderListView(LoginRequiredMixin, ListView):
|
|
# """
|
|
# List all laboratory orders with filtering and search.
|
|
# """
|
|
# model = LabOrder
|
|
# template_name = 'laboratory/lab_order_list.html'
|
|
# context_object_name = 'lab_orders'
|
|
# paginate_by = 25
|
|
#
|
|
# def get_queryset(self):
|
|
# queryset = LabOrder.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(test__test_name__icontains=search)
|
|
# )
|
|
#
|
|
# # Filter by status
|
|
# status = self.request.GET.get('status')
|
|
# if status:
|
|
# queryset = queryset.filter(status=status)
|
|
#
|
|
# # Filter by priority
|
|
# priority = self.request.GET.get('priority')
|
|
# if priority:
|
|
# queryset = queryset.filter(priority=priority)
|
|
#
|
|
# # 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(order_datetime__date__gte=date_from)
|
|
# if date_to:
|
|
# queryset = queryset.filter(order_datetime__date__lte=date_to)
|
|
#
|
|
# return queryset.select_related(
|
|
# 'patient', 'test', 'ordering_provider'
|
|
# ).order_by('-order_datetime')
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context.update({
|
|
# 'statuses': LabOrder._meta.get_field('status').choices,
|
|
# 'priorities': LabOrder._meta.get_field('priority').choices,
|
|
# })
|
|
# return context
|
|
#
|
|
#
|
|
# class LabOrderDetailView(LoginRequiredMixin, DetailView):
|
|
# """
|
|
# Display detailed information about a laboratory order.
|
|
# """
|
|
# model = LabOrder
|
|
# template_name = 'laboratory/lab_order_detail.html'
|
|
# context_object_name = 'lab_order'
|
|
#
|
|
# def get_queryset(self):
|
|
# return LabOrder.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# lab_order = self.object
|
|
#
|
|
# # Get specimens for this order
|
|
# context['specimens'] = lab_order.specimens.all().order_by('-collection_datetime')
|
|
#
|
|
# # Get results for this order
|
|
# context['results'] = lab_order.results.all().order_by('-result_datetime')
|
|
#
|
|
# return context
|
|
#
|
|
#
|
|
# class LabOrderCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
# """
|
|
# Create a new laboratory order.
|
|
# """
|
|
# model = LabOrder
|
|
# form_class = LabOrderForm
|
|
# template_name = 'laboratory/lab_order_form.html'
|
|
# permission_required = 'laboratory.add_laborder'
|
|
# success_url = reverse_lazy('laboratory:lab_order_list')
|
|
#
|
|
# def form_valid(self, form):
|
|
# form.instance.tenant = self.request.user.tenant
|
|
# form.instance.ordering_provider = self.request.user
|
|
# form.instance.order_datetime = timezone.now()
|
|
# response = super().form_valid(form)
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=self.request.user,
|
|
# action='LAB_ORDER_CREATED',
|
|
# model='LabOrder',
|
|
# object_id=str(self.object.order_id),
|
|
# details={
|
|
# 'patient_name': f"{self.object.patient.first_name} {self.object.patient.last_name}",
|
|
# 'test_name': self.object.test.test_name,
|
|
# 'priority': self.object.priority
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(self.request, 'Laboratory order created successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# class LabOrderUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
# """
|
|
# Update laboratory order (limited to status and notes only).
|
|
# """
|
|
# model = LabOrder
|
|
# fields = ['status', 'notes'] # Restricted fields for clinical orders
|
|
# template_name = 'laboratory/lab_order_update_form.html'
|
|
# permission_required = 'laboratory.change_laborder'
|
|
#
|
|
# def get_queryset(self):
|
|
# return LabOrder.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_success_url(self):
|
|
# return reverse('laboratory:lab_order_detail', kwargs={'pk': self.object.pk})
|
|
#
|
|
# def form_valid(self, form):
|
|
# response = super().form_valid(form)
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=self.request.user,
|
|
# action='LAB_ORDER_UPDATED',
|
|
# model='LabOrder',
|
|
# object_id=str(self.object.order_id),
|
|
# details={
|
|
# 'patient_name': f"{self.object.patient.first_name} {self.object.patient.last_name}",
|
|
# 'changes': form.changed_data
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(self.request, 'Laboratory order updated successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # SPECIMEN VIEWS (RESTRICTED CRUD - Clinical Data)
|
|
# # ============================================================================
|
|
#
|
|
# class SpecimenListView(LoginRequiredMixin, ListView):
|
|
# """
|
|
# List all specimens with filtering and search.
|
|
# """
|
|
# model = Specimen
|
|
# template_name = 'laboratory/specimen_list.html'
|
|
# context_object_name = 'specimens'
|
|
# paginate_by = 25
|
|
#
|
|
# def get_queryset(self):
|
|
# queryset = Specimen.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# # Search functionality
|
|
# search = self.request.GET.get('search')
|
|
# if search:
|
|
# queryset = queryset.filter(
|
|
# Q(specimen_id__icontains=search) |
|
|
# Q(order__patient__first_name__icontains=search) |
|
|
# Q(order__patient__last_name__icontains=search) |
|
|
# Q(order__patient__mrn__icontains=search)
|
|
# )
|
|
#
|
|
# # Filter by status
|
|
# status = self.request.GET.get('status')
|
|
# if status:
|
|
# queryset = queryset.filter(status=status)
|
|
#
|
|
# # Filter by specimen type
|
|
# specimen_type = self.request.GET.get('specimen_type')
|
|
# if specimen_type:
|
|
# queryset = queryset.filter(specimen_type=specimen_type)
|
|
#
|
|
# return queryset.select_related('order__patient', 'order__test').order_by('-collection_datetime')
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context.update({
|
|
# 'statuses': Specimen._meta.get_field('status').choices,
|
|
# 'specimen_types': Specimen._meta.get_field('specimen_type').choices,
|
|
# })
|
|
# return context
|
|
#
|
|
#
|
|
# class SpecimenDetailView(LoginRequiredMixin, DetailView):
|
|
# """
|
|
# Display detailed information about a specimen.
|
|
# """
|
|
# model = Specimen
|
|
# template_name = 'laboratory/specimen_detail.html'
|
|
# context_object_name = 'specimen'
|
|
#
|
|
# def get_queryset(self):
|
|
# return Specimen.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
#
|
|
# class SpecimenCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
# """
|
|
# Create a new specimen record.
|
|
# """
|
|
# model = Specimen
|
|
# form_class = SpecimenForm
|
|
# template_name = 'laboratory/specimen_form.html'
|
|
# permission_required = 'laboratory.add_specimen'
|
|
# success_url = reverse_lazy('laboratory:specimen_list')
|
|
#
|
|
# def form_valid(self, form):
|
|
# form.instance.tenant = self.request.user.tenant
|
|
# form.instance.collected_by = self.request.user
|
|
# response = super().form_valid(form)
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=self.request.user,
|
|
# action='SPECIMEN_COLLECTED',
|
|
# model='Specimen',
|
|
# object_id=str(self.object.specimen_id),
|
|
# details={
|
|
# 'patient_name': f"{self.object.order.patient.first_name} {self.object.order.patient.last_name}",
|
|
# 'specimen_type': self.object.specimen_type
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(self.request, 'Specimen record created successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# class SpecimenUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
# """
|
|
# Update specimen (limited to status and processing notes).
|
|
# """
|
|
# model = Specimen
|
|
# fields = ['status', 'processing_notes'] # Restricted fields
|
|
# template_name = 'laboratory/specimen_update_form.html'
|
|
# permission_required = 'laboratory.change_specimen'
|
|
#
|
|
# def get_queryset(self):
|
|
# return Specimen.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_success_url(self):
|
|
# return reverse('laboratory:specimen_detail', kwargs={'pk': self.object.pk})
|
|
#
|
|
# def form_valid(self, form):
|
|
# response = super().form_valid(form)
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=self.request.user,
|
|
# action='SPECIMEN_UPDATED',
|
|
# model='Specimen',
|
|
# object_id=str(self.object.specimen_id),
|
|
# details={
|
|
# 'patient_name': f"{self.object.order.patient.first_name} {self.object.order.patient.last_name}",
|
|
# 'changes': form.changed_data
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(self.request, 'Specimen updated successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # LAB RESULT VIEWS (APPEND-ONLY - Clinical Records)
|
|
# # ============================================================================
|
|
#
|
|
# class LabResultListView(LoginRequiredMixin, ListView):
|
|
# """
|
|
# List all laboratory results with filtering and search.
|
|
# """
|
|
# model = LabResult
|
|
# template_name = 'laboratory/lab_result_list.html'
|
|
# context_object_name = 'lab_results'
|
|
# paginate_by = 25
|
|
#
|
|
# def get_queryset(self):
|
|
# queryset = LabResult.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# # Search functionality
|
|
# search = self.request.GET.get('search')
|
|
# if search:
|
|
# queryset = queryset.filter(
|
|
# Q(order__patient__first_name__icontains=search) |
|
|
# Q(order__patient__last_name__icontains=search) |
|
|
# Q(order__patient__mrn__icontains=search) |
|
|
# Q(test__test_name__icontains=search)
|
|
# )
|
|
#
|
|
# # Filter by status
|
|
# status = self.request.GET.get('status')
|
|
# if status:
|
|
# queryset = queryset.filter(status=status)
|
|
#
|
|
# # Filter by critical results
|
|
# critical_only = self.request.GET.get('critical_only')
|
|
# if critical_only:
|
|
# queryset = queryset.filter(is_critical=True)
|
|
#
|
|
# return queryset.select_related(
|
|
# 'order__patient', 'test', 'verified_by'
|
|
# ).order_by('-result_datetime')
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context.update({
|
|
# 'statuses': LabResult._meta.get_field('status').choices,
|
|
# })
|
|
# return context
|
|
#
|
|
#
|
|
# class LabResultDetailView(LoginRequiredMixin, DetailView):
|
|
# """
|
|
# Display detailed information about a laboratory result.
|
|
# """
|
|
# model = LabResult
|
|
# template_name = 'laboratory/lab_result_detail.html'
|
|
# context_object_name = 'lab_result'
|
|
#
|
|
# def get_queryset(self):
|
|
# return LabResult.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# lab_result = self.object
|
|
#
|
|
# # Get applicable reference ranges
|
|
# patient = lab_result.order.patient
|
|
# context['reference_ranges'] = ReferenceRange.objects.filter(
|
|
# test=lab_result.test,
|
|
# tenant=self.request.user.tenant,
|
|
# age_min__lte=patient.age if hasattr(patient, 'age') else 999,
|
|
# age_max__gte=patient.age if hasattr(patient, 'age') else 0,
|
|
# ).filter(
|
|
# Q(gender=patient.gender) | Q(gender='BOTH')
|
|
# )
|
|
#
|
|
# return context
|
|
#
|
|
#
|
|
# class LabResultCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
# """
|
|
# Create a new laboratory result.
|
|
# """
|
|
# model = LabResult
|
|
# form_class = LabResultForm
|
|
# template_name = 'laboratory/lab_result_form.html'
|
|
# permission_required = 'laboratory.add_labresult'
|
|
# success_url = reverse_lazy('laboratory:lab_result_list')
|
|
#
|
|
# def form_valid(self, form):
|
|
# form.instance.tenant = self.request.user.tenant
|
|
# form.instance.result_datetime = timezone.now()
|
|
# response = super().form_valid(form)
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=self.request.user,
|
|
# action='LAB_RESULT_ENTERED',
|
|
# model='LabResult',
|
|
# object_id=str(self.object.result_id),
|
|
# details={
|
|
# 'patient_name': f"{self.object.order.patient.first_name} {self.object.order.patient.last_name}",
|
|
# 'test_name': self.object.test.test_name,
|
|
# 'is_critical': self.object.is_critical
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(self.request, 'Laboratory result entered successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# # Note: No UpdateView or DeleteView for LabResult - Append-only for clinical records
|
|
# # Results can only be verified or corrected through addendum process
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # QUALITY CONTROL VIEWS (LIMITED CRUD - Operational Data)
|
|
# # ============================================================================
|
|
#
|
|
# class QualityControlListView(LoginRequiredMixin, ListView):
|
|
# """
|
|
# List all quality control records.
|
|
# """
|
|
# model = QualityControl
|
|
# template_name = 'laboratory/quality_control_list.html'
|
|
# context_object_name = 'qc_records'
|
|
# paginate_by = 25
|
|
#
|
|
# def get_queryset(self):
|
|
# queryset = QualityControl.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# # Filter by test
|
|
# test_id = self.request.GET.get('test')
|
|
# if test_id:
|
|
# queryset = queryset.filter(test_id=test_id)
|
|
#
|
|
# # Filter by result
|
|
# result = self.request.GET.get('result')
|
|
# if result:
|
|
# queryset = queryset.filter(result=result)
|
|
#
|
|
# return queryset.select_related('test', 'performed_by').order_by('-test_date')
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context.update({
|
|
# 'lab_tests': LabTest.objects.filter(
|
|
# tenant=self.request.user.tenant,
|
|
# is_active=True
|
|
# ).order_by('test_name'),
|
|
# 'results': QualityControl._meta.get_field('result').choices,
|
|
# })
|
|
# return context
|
|
#
|
|
#
|
|
# class QualityControlDetailView(LoginRequiredMixin, DetailView):
|
|
# """
|
|
# Display detailed information about a quality control record.
|
|
# """
|
|
# model = QualityControl
|
|
# template_name = 'laboratory/quality_control_detail.html'
|
|
# context_object_name = 'qc_record'
|
|
#
|
|
# def get_queryset(self):
|
|
# return QualityControl.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
#
|
|
# class QualityControlCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
# """
|
|
# Create a new quality control record.
|
|
# """
|
|
# model = QualityControl
|
|
# form_class = QualityControlForm
|
|
# template_name = 'laboratory/quality_control_form.html'
|
|
# permission_required = 'laboratory.add_qualitycontrol'
|
|
# success_url = reverse_lazy('laboratory:quality_control_list')
|
|
#
|
|
# def form_valid(self, form):
|
|
# form.instance.tenant = self.request.user.tenant
|
|
# form.instance.performed_by = self.request.user
|
|
# response = super().form_valid(form)
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=self.request.user,
|
|
# action='QC_TEST_PERFORMED',
|
|
# model='QualityControl',
|
|
# object_id=str(self.object.id),
|
|
# details={
|
|
# 'test_name': self.object.test.test_name,
|
|
# 'result': self.object.result
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(self.request, 'Quality control record created successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# class QualityControlUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
# """
|
|
# Update quality control record (limited fields).
|
|
# """
|
|
# model = QualityControl
|
|
# fields = ['result', 'comments'] # Limited fields for operational data
|
|
# template_name = 'laboratory/quality_control_form.html'
|
|
# permission_required = 'laboratory.change_qualitycontrol'
|
|
#
|
|
# def get_queryset(self):
|
|
# return QualityControl.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_success_url(self):
|
|
# return reverse('laboratory:quality_control_detail', kwargs={'pk': self.object.pk})
|
|
#
|
|
# def form_valid(self, form):
|
|
# response = super().form_valid(form)
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=self.request.user,
|
|
# action='QC_TEST_UPDATED',
|
|
# model='QualityControl',
|
|
# object_id=str(self.object.id),
|
|
# details={
|
|
# 'test_name': self.object.test.test_name,
|
|
# 'changes': form.changed_data
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(self.request, 'Quality control record updated successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # HTMX VIEWS FOR REAL-TIME UPDATES
|
|
# # ============================================================================
|
|
#
|
|
# @login_required
|
|
# def laboratory_stats(request):
|
|
# """
|
|
# HTMX endpoint for laboratory statistics.
|
|
# """
|
|
# tenant = request.user.tenant
|
|
# today = timezone.now().date()
|
|
#
|
|
# stats = {
|
|
# 'pending_orders': LabOrder.objects.filter(
|
|
# tenant=tenant,
|
|
# status='PENDING'
|
|
# ).count(),
|
|
# 'in_progress_orders': LabOrder.objects.filter(
|
|
# tenant=tenant,
|
|
# status='IN_PROGRESS'
|
|
# ).count(),
|
|
# 'results_pending': LabResult.objects.filter(
|
|
# tenant=tenant,
|
|
# status='PENDING'
|
|
# ).count(),
|
|
# 'critical_results': LabResult.objects.filter(
|
|
# tenant=tenant,
|
|
# is_critical=True,
|
|
# status='VERIFIED',
|
|
# result_datetime__date=today
|
|
# ).count(),
|
|
# }
|
|
#
|
|
# return render(request, 'laboratory/partials/laboratory_stats.html', {'stats': stats})
|
|
#
|
|
#
|
|
# @login_required
|
|
# def order_search(request):
|
|
# """
|
|
# HTMX endpoint for order search.
|
|
# """
|
|
# search = request.GET.get('search', '')
|
|
# status = request.GET.get('status', '')
|
|
#
|
|
# queryset = LabOrder.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(test__test_name__icontains=search)
|
|
# )
|
|
#
|
|
# if status:
|
|
# queryset = queryset.filter(status=status)
|
|
#
|
|
# orders = queryset.select_related(
|
|
# 'patient', 'test', 'ordering_provider'
|
|
# ).order_by('-order_datetime')[:20]
|
|
#
|
|
# return render(request, 'laboratory/partials/order_list.html', {'orders': orders})
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # ACTION VIEWS
|
|
# # ============================================================================
|
|
#
|
|
# @login_required
|
|
# def receive_specimen(request, order_id):
|
|
# """
|
|
# Mark specimen as received for processing.
|
|
# """
|
|
# if request.method == 'POST':
|
|
# order = get_object_or_404(
|
|
# LabOrder,
|
|
# id=order_id,
|
|
# tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# # Update specimen status
|
|
# specimens = order.specimens.filter(status='COLLECTED')
|
|
# specimens.update(
|
|
# status='RECEIVED',
|
|
# received_datetime=timezone.now(),
|
|
# received_by=request.user
|
|
# )
|
|
#
|
|
# # Update order status
|
|
# order.status = 'IN_PROGRESS'
|
|
# order.save()
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=request.user,
|
|
# action='SPECIMEN_RECEIVED',
|
|
# model='LabOrder',
|
|
# object_id=str(order.order_id),
|
|
# details={
|
|
# 'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
|
|
# 'specimens_count': specimens.count()
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(request, 'Specimen(s) marked as received.')
|
|
#
|
|
# if request.headers.get('HX-Request'):
|
|
# return render(request, 'laboratory/partials/order_status.html', {'order': order})
|
|
#
|
|
# return redirect('laboratory:lab_order_detail', pk=order.pk)
|
|
#
|
|
# return JsonResponse({'success': False})
|
|
#
|
|
#
|
|
# @login_required
|
|
# def start_processing(request, order_id):
|
|
# """
|
|
# Start processing laboratory order.
|
|
# """
|
|
# if request.method == 'POST':
|
|
# order = get_object_or_404(
|
|
# LabOrder,
|
|
# id=order_id,
|
|
# tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# order.status = 'IN_PROGRESS'
|
|
# order.save()
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=request.user,
|
|
# action='LAB_PROCESSING_STARTED',
|
|
# model='LabOrder',
|
|
# object_id=str(order.order_id),
|
|
# details={
|
|
# 'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
|
|
# 'test_name': order.test.test_name
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(request, 'Laboratory processing started.')
|
|
#
|
|
# if request.headers.get('HX-Request'):
|
|
# return render(request, 'laboratory/partials/order_status.html', {'order': order})
|
|
#
|
|
# return redirect('laboratory:lab_order_detail', pk=order.pk)
|
|
#
|
|
# return JsonResponse({'success': False})
|
|
#
|
|
#
|
|
# @login_required
|
|
# def reject_specimen(request, specimen_id):
|
|
# """
|
|
# Reject a specimen with reason.
|
|
# """
|
|
# if request.method == 'POST':
|
|
# specimen = get_object_or_404(
|
|
# Specimen,
|
|
# id=specimen_id,
|
|
# tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# rejection_reason = request.POST.get('rejection_reason', '')
|
|
#
|
|
# specimen.status = 'REJECTED'
|
|
# specimen.rejection_reason = rejection_reason
|
|
# specimen.rejected_by = request.user
|
|
# specimen.rejected_datetime = timezone.now()
|
|
# specimen.save()
|
|
#
|
|
# # Update order status
|
|
# specimen.order.status = 'CANCELLED'
|
|
# specimen.order.save()
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=request.user,
|
|
# action='SPECIMEN_REJECTED',
|
|
# model='Specimen',
|
|
# object_id=str(specimen.specimen_id),
|
|
# details={
|
|
# 'patient_name': f"{specimen.order.patient.first_name} {specimen.order.patient.last_name}",
|
|
# 'rejection_reason': rejection_reason
|
|
# }
|
|
# )
|
|
#
|
|
# messages.warning(request, 'Specimen rejected.')
|
|
#
|
|
# if request.headers.get('HX-Request'):
|
|
# return render(request, 'laboratory/partials/specimen_status.html', {'specimen': specimen})
|
|
#
|
|
# return redirect('laboratory:specimen_detail', pk=specimen.pk)
|
|
#
|
|
# return JsonResponse({'success': False})
|
|
#
|
|
#
|
|
# @login_required
|
|
# def schedule_collection(request, order_id):
|
|
# """
|
|
# Schedule specimen collection for an order.
|
|
# """
|
|
# if request.method == 'POST':
|
|
# order = get_object_or_404(
|
|
# LabOrder,
|
|
# id=order_id,
|
|
# tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# collection_datetime = request.POST.get('collection_datetime')
|
|
# if collection_datetime:
|
|
# collection_datetime = timezone.datetime.fromisoformat(collection_datetime)
|
|
#
|
|
# order.scheduled_collection = collection_datetime
|
|
# order.status = 'SCHEDULED'
|
|
# order.save()
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=request.user,
|
|
# action='COLLECTION_SCHEDULED',
|
|
# model='LabOrder',
|
|
# object_id=str(order.order_id),
|
|
# details={
|
|
# 'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
|
|
# 'scheduled_time': collection_datetime.isoformat()
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(request, 'Collection scheduled successfully.')
|
|
#
|
|
# if request.headers.get('HX-Request'):
|
|
# return render(request, 'laboratory/partials/order_status.html', {'order': order})
|
|
#
|
|
# return redirect('laboratory:lab_order_detail', pk=order.pk)
|
|
#
|
|
# return JsonResponse({'success': False})
|
|
#
|
|
#
|
|
# @login_required
|
|
# def mark_collected(request, order_id):
|
|
# """
|
|
# Mark specimen as collected.
|
|
# """
|
|
# if request.method == 'POST':
|
|
# order = get_object_or_404(
|
|
# LabOrder,
|
|
# id=order_id,
|
|
# tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# # Create specimen record if it doesn't exist
|
|
# specimen, created = Specimen.objects.get_or_create(
|
|
# order=order,
|
|
# tenant=request.user.tenant,
|
|
# defaults={
|
|
# 'specimen_type': 'BLOOD', # Default, should be specified
|
|
# 'collection_datetime': timezone.now(),
|
|
# 'collected_by': request.user,
|
|
# 'status': 'COLLECTED'
|
|
# }
|
|
# )
|
|
#
|
|
# if not created:
|
|
# specimen.status = 'COLLECTED'
|
|
# specimen.collection_datetime = timezone.now()
|
|
# specimen.collected_by = request.user
|
|
# specimen.save()
|
|
#
|
|
# # Update order status
|
|
# order.status = 'COLLECTED'
|
|
# order.save()
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=request.user,
|
|
# action='SPECIMEN_COLLECTED',
|
|
# model='LabOrder',
|
|
# object_id=str(order.order_id),
|
|
# details={
|
|
# 'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
|
|
# 'specimen_type': specimen.specimen_type
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(request, 'Specimen marked as collected.')
|
|
#
|
|
# if request.headers.get('HX-Request'):
|
|
# return render(request, 'laboratory/partials/order_status.html', {'order': order})
|
|
#
|
|
# return redirect('laboratory:lab_order_detail', pk=order.pk)
|
|
#
|
|
# return JsonResponse({'success': False})
|
|
#
|
|
#
|
|
#
|
|
@login_required
|
|
def verify_result(request, result_id):
|
|
"""
|
|
Verify a laboratory result.
|
|
"""
|
|
if request.method == 'POST':
|
|
result = get_object_or_404(
|
|
LabResult,
|
|
id=result_id,
|
|
order__tenant=request.user.tenant
|
|
)
|
|
|
|
if result.status != 'PENDING':
|
|
return JsonResponse({'success': False, 'error': 'Result is not pending verification'})
|
|
|
|
result.status = 'VERIFIED'
|
|
result.verified_by = request.user
|
|
result.verified_datetime = timezone.now()
|
|
result.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='LAB_RESULT_VERIFIED',
|
|
model='LabResult',
|
|
object_id=str(result.result_id),
|
|
details={
|
|
'patient_name': f"{result.order.patient.first_name} {result.order.patient.last_name}",
|
|
'test_name': result.test.test_name,
|
|
'result_value': result.value
|
|
}
|
|
)
|
|
|
|
messages.success(request, 'Result verified successfully.')
|
|
|
|
if request.headers.get('HX-Request'):
|
|
return render(request, 'laboratory/partials/result_row.html', {'result': result})
|
|
|
|
return redirect('laboratory:lab_result_list')
|
|
|
|
return JsonResponse({'success': False})
|
|
|
|
|
|
@login_required
|
|
def review_result(request, result_id):
|
|
"""
|
|
Mark a laboratory result as reviewed.
|
|
"""
|
|
if request.method == 'POST':
|
|
result = get_object_or_404(
|
|
LabResult,
|
|
id=result_id,
|
|
order__tenant=request.user.tenant
|
|
)
|
|
|
|
# Add reviewed status or field if it exists in your model
|
|
# For now, we'll use a custom field or status
|
|
if hasattr(result, 'reviewed_by'):
|
|
result.reviewed_by = request.user
|
|
result.reviewed_datetime = timezone.now()
|
|
result.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='LAB_RESULT_REVIEWED',
|
|
model='LabResult',
|
|
object_id=str(result.result_id),
|
|
details={
|
|
'patient_name': f"{result.order.patient.first_name} {result.order.patient.last_name}",
|
|
'test_name': result.test.test_name,
|
|
'result_value': result.value
|
|
}
|
|
)
|
|
|
|
return JsonResponse({'success': True, 'message': 'Result marked as reviewed'})
|
|
else:
|
|
return JsonResponse({'success': False, 'error': 'Review functionality not available'})
|
|
|
|
return JsonResponse({'success': False, 'error': 'Invalid request method'})
|
|
|
|
|
|
@login_required
|
|
def email_result(request, result_id):
|
|
"""
|
|
Email a laboratory result to specified address.
|
|
"""
|
|
if request.method == 'POST':
|
|
result = get_object_or_404(
|
|
LabResult,
|
|
id=result_id,
|
|
order__tenant=request.user.tenant
|
|
)
|
|
|
|
email = request.POST.get('email') or request.GET.get('email')
|
|
if not email:
|
|
return JsonResponse({'success': False, 'error': 'Email address is required'})
|
|
|
|
# Here you would implement actual email sending
|
|
# For now, we'll simulate success
|
|
try:
|
|
# TODO: Implement actual email sending logic
|
|
# from django.core.mail import send_mail
|
|
# send_mail(
|
|
# f'Laboratory Result: {result.test.test_name}',
|
|
# f'Patient: {result.order.patient.get_full_name()}\nResult: {result.value} {result.unit}\nStatus: {result.get_status_display()}',
|
|
# 'noreply@hospital.com',
|
|
# [email],
|
|
# fail_silently=False,
|
|
# )
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='LAB_RESULT_EMAILED',
|
|
model='LabResult',
|
|
object_id=str(result.result_id),
|
|
details={
|
|
'patient_name': f"{result.order.patient.first_name} {result.order.patient.last_name}",
|
|
'test_name': result.test.test_name,
|
|
'email_recipient': email
|
|
}
|
|
)
|
|
|
|
return JsonResponse({'success': True, 'message': f'Result emailed to {email}'})
|
|
|
|
except Exception as e:
|
|
return JsonResponse({'success': False, 'error': f'Failed to send email: {str(e)}'})
|
|
|
|
return JsonResponse({'success': False, 'error': 'Invalid request method'})
|
|
|
|
|
|
@login_required
|
|
def notify_critical(request, result_id):
|
|
"""
|
|
Send critical result notification to ordering provider.
|
|
"""
|
|
if request.method == 'POST':
|
|
result = get_object_or_404(
|
|
LabResult,
|
|
id=result_id,
|
|
order__tenant=request.user.tenant
|
|
)
|
|
|
|
if result.critical_flag != 'CRITICAL':
|
|
return JsonResponse({'success': False, 'error': 'Result is not critical'})
|
|
|
|
# Here you would implement actual notification sending
|
|
# For now, we'll simulate success
|
|
try:
|
|
# TODO: Implement actual notification logic (email, SMS, etc.)
|
|
# This could send notifications to:
|
|
# - Ordering provider
|
|
# - Primary care physician
|
|
# - Critical result notification system
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='CRITICAL_RESULT_NOTIFIED',
|
|
model='LabResult',
|
|
object_id=str(result.result_id),
|
|
details={
|
|
'patient_name': f"{result.order.patient.first_name} {result.order.patient.last_name}",
|
|
'test_name': result.test.test_name,
|
|
'result_value': result.value,
|
|
'ordering_provider': result.order.ordering_provider.get_full_name() if result.order.ordering_provider else 'Unknown'
|
|
}
|
|
)
|
|
|
|
return JsonResponse({'success': True, 'message': 'Critical result notification sent'})
|
|
|
|
except Exception as e:
|
|
return JsonResponse({'success': False, 'error': f'Failed to send notification: {str(e)}'})
|
|
|
|
return JsonResponse({'success': False, 'error': 'Invalid request method'})
|
|
|
|
|
|
@login_required
|
|
def bulk_verify_results(request):
|
|
"""
|
|
Bulk verify multiple laboratory results.
|
|
"""
|
|
if request.method == 'POST':
|
|
result_ids = request.POST.getlist('result_ids[]')
|
|
tenant = request.user.tenant
|
|
|
|
results = LabResult.objects.filter(
|
|
id__in=result_ids,
|
|
order__tenant=tenant,
|
|
status='PENDING'
|
|
)
|
|
|
|
verified_count = results.update(
|
|
status='VERIFIED',
|
|
verified_by=request.user,
|
|
verified_datetime=timezone.now()
|
|
)
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='LAB_RESULTS_BULK_VERIFIED',
|
|
model='LabResult',
|
|
object_id=','.join(result_ids),
|
|
details={
|
|
'verified_count': verified_count,
|
|
'total_requested': len(result_ids)
|
|
}
|
|
)
|
|
|
|
messages.success(request, f'{verified_count} results verified successfully.')
|
|
|
|
if request.headers.get('HX-Request'):
|
|
return JsonResponse({'success': True, 'verified_count': verified_count})
|
|
|
|
return redirect('laboratory:lab_result_list')
|
|
|
|
return JsonResponse({'success': False})
|
|
|
|
|
|
@login_required
|
|
def bulk_release_results(request):
|
|
"""
|
|
Bulk release multiple laboratory results.
|
|
"""
|
|
if request.method == 'POST':
|
|
result_ids = request.POST.getlist('result_ids[]')
|
|
tenant = request.user.tenant
|
|
|
|
results = LabResult.objects.filter(
|
|
id__in=result_ids,
|
|
order__tenant=tenant,
|
|
status='VERIFIED'
|
|
)
|
|
|
|
released_count = results.update(status='RELEASED')
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='LAB_RESULTS_BULK_RELEASED',
|
|
model='LabResult',
|
|
object_id=','.join(result_ids),
|
|
details={
|
|
'released_count': released_count,
|
|
'total_requested': len(result_ids)
|
|
}
|
|
)
|
|
|
|
messages.success(request, f'{released_count} results released successfully.')
|
|
|
|
if request.headers.get('HX-Request'):
|
|
return JsonResponse({'success': True, 'released_count': released_count})
|
|
|
|
return redirect('laboratory:lab_result_list')
|
|
|
|
return JsonResponse({'success': False})
|
|
|
|
|
|
@login_required
|
|
def print_result_reports(request):
|
|
"""
|
|
Print reports for selected laboratory results.
|
|
"""
|
|
ids = request.GET.get('ids', '')
|
|
if not ids:
|
|
return HttpResponse('No results selected', status=400)
|
|
|
|
result_ids = ids.split(',')
|
|
tenant = request.user.tenant
|
|
|
|
results = LabResult.objects.filter(
|
|
id__in=result_ids,
|
|
order__tenant=tenant
|
|
).select_related('order__patient', 'test', 'verified_by')
|
|
|
|
# Generate PDF or HTML report
|
|
context = {
|
|
'results': results,
|
|
'generated_at': timezone.now(),
|
|
'generated_by': request.user
|
|
}
|
|
|
|
return render(request, 'laboratory/reports/result_reports.html', context)
|
|
|
|
|
|
@login_required
|
|
def export_results(request):
|
|
"""
|
|
Export laboratory results to CSV/Excel.
|
|
"""
|
|
tenant = request.user.tenant
|
|
|
|
# Get filter parameters
|
|
status = request.GET.get('status')
|
|
date_from = request.GET.get('date_from')
|
|
date_to = request.GET.get('date_to')
|
|
|
|
queryset = LabResult.objects.filter(order__tenant=tenant)
|
|
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
if date_from:
|
|
queryset = queryset.filter(result_date__gte=date_from)
|
|
if date_to:
|
|
queryset = queryset.filter(result_date__lte=date_to)
|
|
|
|
results = queryset.select_related('order__patient', 'test', 'verified_by')
|
|
|
|
# Generate CSV response
|
|
import csv
|
|
from django.http import HttpResponse
|
|
|
|
response = HttpResponse(content_type='text/csv')
|
|
response['Content-Disposition'] = 'attachment; filename="lab_results.csv"'
|
|
|
|
writer = csv.writer(response)
|
|
writer.writerow([
|
|
'Result ID', 'Patient Name', 'Patient ID', 'Test Name', 'Result Value',
|
|
'Unit', 'Reference Range', 'Status', 'Result Date', 'Technologist',
|
|
'Verified By', 'Verified Date'
|
|
])
|
|
|
|
for result in results:
|
|
writer.writerow([
|
|
result.result_id,
|
|
result.order.patient.get_full_name(),
|
|
result.order.patient.patient_id,
|
|
result.test.test_name,
|
|
result.value,
|
|
result.unit or '',
|
|
result.reference_range or '',
|
|
result.get_status_display(),
|
|
result.result_date.strftime('%Y-%m-%d %H:%M'),
|
|
result.technologist.get_full_name() if result.technologist else '',
|
|
result.verified_by.get_full_name() if result.verified_by else '',
|
|
result.verified_datetime.strftime('%Y-%m-%d %H:%M') if result.verified_datetime else ''
|
|
])
|
|
|
|
return response
|
|
|
|
|
|
@login_required
|
|
def htmx_result_stats(request):
|
|
"""
|
|
HTMX endpoint for result statistics.
|
|
"""
|
|
tenant = request.user.tenant
|
|
|
|
stats = {
|
|
'total_results': LabResult.objects.filter(order__tenant=tenant).count(),
|
|
'pending_review': LabResult.objects.filter(order__tenant=tenant, status='PENDING').count(),
|
|
'critical_results': LabResult.objects.filter(order__tenant=tenant, is_critical=True).count(),
|
|
'verified_results': LabResult.objects.filter(order__tenant=tenant, status='VERIFIED').count(),
|
|
}
|
|
|
|
return render(request, 'laboratory/partials/result_stats.html', {'stats': stats})
|
|
|
|
|
|
@login_required
|
|
def htmx_filter_results(request):
|
|
"""
|
|
HTMX endpoint for filtering results.
|
|
"""
|
|
tenant = request.user.tenant
|
|
status = request.GET.get('status')
|
|
critical = request.GET.get('critical')
|
|
date_period = request.GET.get('date')
|
|
|
|
queryset = LabResult.objects.filter(order__tenant=tenant)
|
|
|
|
# Apply filters
|
|
if status and status != 'all':
|
|
queryset = queryset.filter(status=status)
|
|
if critical and critical != 'all':
|
|
queryset = queryset.filter(critical_flag=critical)
|
|
|
|
# Date filtering
|
|
if date_period and date_period != 'all':
|
|
today = timezone.now().date()
|
|
if date_period == 'today':
|
|
queryset = queryset.filter(result_date__date=today)
|
|
elif date_period == 'week':
|
|
week_ago = today - timedelta(days=7)
|
|
queryset = queryset.filter(result_date__date__gte=week_ago)
|
|
elif date_period == 'month':
|
|
month_ago = today - timedelta(days=30)
|
|
queryset = queryset.filter(result_date__date__gte=month_ago)
|
|
|
|
results = queryset.select_related(
|
|
'order__patient', 'test', 'verified_by'
|
|
).order_by('-created_at')[:50] # Limit for performance
|
|
|
|
return render(request, 'laboratory/partials/result_list.html', {'object_list': results})
|
|
|
|
|
|
@login_required
|
|
def htmx_search_results(request):
|
|
"""
|
|
HTMX endpoint for searching results.
|
|
"""
|
|
tenant = request.user.tenant
|
|
search_term = request.GET.get('search', '').strip()
|
|
|
|
if not search_term:
|
|
return render(request, 'laboratory/partials/result_list.html', {'object_list': []})
|
|
|
|
queryset = LabResult.objects.filter(order__tenant=tenant).filter(
|
|
Q(order__patient__first_name__icontains=search_term) |
|
|
Q(order__patient__last_name__icontains=search_term) |
|
|
Q(order__patient__mrn__icontains=search_term) |
|
|
Q(test__test_name__icontains=search_term) |
|
|
Q(result_value__icontains=search_term)
|
|
).select_related(
|
|
'order__patient', 'test', 'verified_by'
|
|
).order_by('-analyzed_datetime')[:50]
|
|
|
|
return render(request, 'laboratory/partials/result_list.html', {'object_list': queryset})
|
|
|
|
|
|
@login_required
|
|
def check_critical_results(request):
|
|
"""
|
|
API endpoint to check for critical results that need attention.
|
|
Referenced in dashboard template.
|
|
"""
|
|
tenant = request.user.tenant
|
|
|
|
# Get critical results that haven't been called yet
|
|
critical_results = LabResult.objects.filter(
|
|
order__tenant=tenant,
|
|
is_critical=True,
|
|
critical_called=False,
|
|
status='VERIFIED'
|
|
).count()
|
|
|
|
return JsonResponse({
|
|
'critical_count': critical_results,
|
|
'success': True
|
|
})
|