3057 lines
103 KiB
Python
3057 lines
103 KiB
Python
"""
|
|
Views for inpatients app.
|
|
"""
|
|
from django.conf.locale import te
|
|
from django.shortcuts import render, get_object_or_404, redirect
|
|
from django.contrib.auth.decorators import login_required, permission_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
|
|
from django.http import JsonResponse, HttpResponse
|
|
from django.db.models import Q, Count, Sum, F, ExpressionWrapper, fields
|
|
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.utils.translation import gettext_lazy as _
|
|
from core.utils import AuditLogger
|
|
from .forms import *
|
|
from .models import *
|
|
|
|
|
|
class InpatientDashboardView(LoginRequiredMixin, ListView):
|
|
"""
|
|
Main dashboard for inpatient management.
|
|
"""
|
|
template_name = 'inpatients/dashboard.html'
|
|
context_object_name = 'wards'
|
|
|
|
def get_queryset(self):
|
|
"""Get wards for current tenant."""
|
|
return Ward.objects.filter(
|
|
tenant=self.request.user.tenant,
|
|
is_active=True
|
|
).select_related('nurse_manager').prefetch_related('beds')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
tenant = self.request.user.tenant
|
|
|
|
# Dashboard statistics
|
|
context.update({
|
|
'total_beds': Bed.objects.filter(ward__tenant=tenant).count(),
|
|
'occupied_beds': Bed.objects.filter(ward__tenant=tenant, status='OCCUPIED').count(),
|
|
'available_beds': Bed.objects.filter(ward__tenant=tenant, status='AVAILABLE').count(),
|
|
'maintenance_beds': Bed.objects.filter(ward__tenant=tenant, status='MAINTENANCE').count(),
|
|
'active_admissions': Admission.objects.filter(tenant=tenant, status='ADMITTED').count(),
|
|
'pending_transfers': Transfer.objects.filter(
|
|
admission__tenant=tenant,
|
|
status__in=['REQUESTED', 'APPROVED', 'SCHEDULED']
|
|
).count(),
|
|
'pending_surgeries': SurgerySchedule.objects.filter(
|
|
admission__tenant=tenant,
|
|
status__in=['SCHEDULED', 'CONFIRMED']
|
|
).count(),
|
|
'pending_discharges': Admission.objects.filter(
|
|
tenant=tenant,
|
|
status='READY_FOR_DISCHARGE'
|
|
),
|
|
})
|
|
|
|
# Recent admissions
|
|
context['recent_admissions'] = Admission.objects.filter(
|
|
tenant=tenant
|
|
).select_related(
|
|
'patient', 'admitting_physician', 'current_ward', 'current_bed'
|
|
).order_by('-admission_datetime')[:5]
|
|
|
|
# Upcoming transfers
|
|
context['upcoming_transfers'] = Transfer.objects.filter(
|
|
admission__tenant=tenant,
|
|
status__in=['APPROVED', 'SCHEDULED']
|
|
).select_related(
|
|
'patient', 'from_ward', 'to_ward', 'requested_by'
|
|
).order_by('requested_datetime')[:5]
|
|
|
|
# Upcoming surgeries
|
|
context['upcoming_surgeries'] = SurgerySchedule.objects.filter(
|
|
admission__tenant=tenant,
|
|
status__in=['SCHEDULED', 'CONFIRMED'],
|
|
scheduled_date__gte=timezone.now().date()
|
|
).select_related('patient', 'primary_surgeon').order_by('scheduled_date', 'scheduled_start_time')[:5]
|
|
|
|
return context
|
|
|
|
class WardListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List view for wards.
|
|
"""
|
|
model = Ward
|
|
template_name = 'inpatients/wards/ward_list.html'
|
|
context_object_name = 'wards'
|
|
paginate_by = 20
|
|
|
|
def get_queryset(self):
|
|
"""Filter wards by tenant and search query."""
|
|
queryset = Ward.objects.filter(
|
|
tenant=self.request.user.tenant
|
|
).select_related('nurse_manager').annotate(
|
|
bed_count=Count('beds'),
|
|
occupied_bed_count=Count('beds', filter=Q(beds__status='OCCUPIED'))
|
|
)
|
|
|
|
# Handle search query
|
|
search_query = self.request.GET.get('search', '')
|
|
if search_query:
|
|
queryset = queryset.filter(
|
|
Q(name__icontains=search_query) |
|
|
Q(description__icontains=search_query) |
|
|
Q(ward_type__icontains=search_query) |
|
|
Q(building__icontains=search_query) |
|
|
Q(floor__icontains=search_query) |
|
|
Q(nurse_manager__first_name__icontains=search_query) |
|
|
Q(nurse_manager__last_name__icontains=search_query)
|
|
)
|
|
|
|
# Handle filter by ward_type
|
|
ward_type = self.request.GET.get('ward_type', '')
|
|
if ward_type:
|
|
queryset = queryset.filter(ward_type=ward_type)
|
|
|
|
# Handle filter by building
|
|
building = self.request.GET.get('building', '')
|
|
if building:
|
|
queryset = queryset.filter(building=building)
|
|
|
|
# Handle filter by floor
|
|
floor = self.request.GET.get('floor', '')
|
|
if floor:
|
|
queryset = queryset.filter(floor=floor)
|
|
|
|
# Handle filter by active status
|
|
is_active = self.request.GET.get('is_active', '')
|
|
if is_active == 'true':
|
|
queryset = queryset.filter(is_active=True)
|
|
elif is_active == 'false':
|
|
queryset = queryset.filter(is_active=False)
|
|
|
|
# Handle sort
|
|
sort_by = self.request.GET.get('sort', 'name')
|
|
if sort_by.startswith('-'):
|
|
sort_field = sort_by[1:]
|
|
sort_dir = '-'
|
|
else:
|
|
sort_field = sort_by
|
|
sort_dir = ''
|
|
|
|
if sort_field == 'occupancy':
|
|
# Calculate occupancy percentage for sorting
|
|
queryset = queryset.annotate(
|
|
occupancy=ExpressionWrapper(
|
|
(F('occupied_bed_count') * 100.0) / F('bed_count'),
|
|
output_field=fields.FloatField()
|
|
)
|
|
)
|
|
queryset = queryset.order_by(f'{sort_dir}occupancy')
|
|
else:
|
|
queryset = queryset.order_by(sort_by)
|
|
|
|
return queryset
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
tenant = self.request.user.tenant
|
|
|
|
# Get unique values for filters
|
|
context['ward_types'] = Ward.objects.filter(
|
|
tenant=tenant
|
|
).values_list('ward_type', flat=True).distinct()
|
|
print(context['ward_types'])
|
|
|
|
context['buildings'] = Ward.objects.filter(
|
|
tenant=tenant
|
|
).exclude(building__isnull=True).exclude(building='').values_list(
|
|
'building', flat=True
|
|
).distinct()
|
|
|
|
context['floors'] = Ward.objects.filter(
|
|
tenant=tenant
|
|
).exclude(floor__isnull=True).exclude(floor='').values_list(
|
|
'floor', flat=True
|
|
).distinct()
|
|
|
|
# Add search query to context
|
|
context['search_query'] = self.request.GET.get('search', '')
|
|
context['ward_type_filter'] = self.request.GET.get('ward_type', '')
|
|
context['building_filter'] = self.request.GET.get('building', '')
|
|
context['floor_filter'] = self.request.GET.get('floor', '')
|
|
context['is_active_filter'] = self.request.GET.get('is_active', '')
|
|
context['sort_by'] = self.request.GET.get('sort', 'name')
|
|
|
|
return context
|
|
|
|
|
|
class WardDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Detail view for a ward.
|
|
"""
|
|
model = Ward
|
|
template_name = 'inpatients/wards/ward_detail.html'
|
|
context_object_name = 'ward'
|
|
|
|
def get_queryset(self):
|
|
"""Filter wards by tenant."""
|
|
return Ward.objects.filter(
|
|
tenant=self.request.user.tenant
|
|
).select_related('nurse_manager')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
ward = self.get_object()
|
|
|
|
# Get beds for this ward with patient information
|
|
context['beds'] = Bed.objects.filter(
|
|
ward=ward
|
|
).select_related(
|
|
'current_patient', 'current_admission'
|
|
).order_by('room_number', 'bed_number')
|
|
|
|
# Group beds by room for display
|
|
rooms = {}
|
|
for bed in context['beds']:
|
|
room_num = bed.room_number
|
|
if room_num not in rooms:
|
|
rooms[room_num] = []
|
|
rooms[room_num].append(bed)
|
|
context['rooms'] = rooms
|
|
|
|
# Get ward statistics
|
|
context['total_beds'] = context['beds'].count()
|
|
context['available_beds'] = context['beds'].filter(status='AVAILABLE').count()
|
|
context['occupied_beds'] = context['beds'].filter(status='OCCUPIED').count()
|
|
context['maintenance_beds'] = context['beds'].filter(
|
|
status__in=['MAINTENANCE', 'OUT_OF_ORDER', 'CLEANING']
|
|
).count()
|
|
|
|
if context['total_beds'] > 0:
|
|
context['occupancy_rate'] = (context['occupied_beds'] / context['total_beds']) * 100
|
|
else:
|
|
context['occupancy_rate'] = 0
|
|
|
|
# Get recent admissions to this ward
|
|
context['recent_admissions'] = Admission.objects.filter(
|
|
Q(current_ward=ward) | Q(current_bed__ward=ward),
|
|
status__in=['ADMITTED', 'READY_FOR_DISCHARGE']
|
|
).select_related(
|
|
'patient', 'admitting_physician'
|
|
).order_by('-admission_datetime')[:10]
|
|
|
|
return context
|
|
|
|
|
|
class WardCreateView(LoginRequiredMixin, CreateView):
|
|
"""
|
|
Create view for a ward.
|
|
"""
|
|
model = Ward
|
|
form_class = WardForm
|
|
template_name = 'inpatients/wards/ward_form.html'
|
|
permission_required = 'inpatients.add_ward'
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['user'] = self.request.user
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
form.instance.tenant = self.request.user.tenant
|
|
form.instance.created_by = self.request.user
|
|
messages.success(self.request, _('Ward created successfully'))
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse('inpatients:ward_detail', kwargs={'pk': self.object.pk})
|
|
|
|
|
|
class WardUpdateView(LoginRequiredMixin, UpdateView):
|
|
"""
|
|
Update view for a ward.
|
|
"""
|
|
model = Ward
|
|
form_class = WardForm
|
|
template_name = 'inpatients/wards/ward_form.html'
|
|
permission_required = 'inpatients.change_ward'
|
|
|
|
def get_queryset(self):
|
|
"""Filter wards by tenant."""
|
|
return Ward.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['user'] = self.request.user
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
messages.success(self.request, _('Ward updated successfully'))
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse('inpatients:ward_detail', kwargs={'pk': self.object.pk})
|
|
|
|
|
|
class WardDeleteView(LoginRequiredMixin, DeleteView):
|
|
"""
|
|
Delete view for a ward.
|
|
"""
|
|
model = Ward
|
|
template_name = 'inpatients/wards/ward_confirm_delete.html'
|
|
permission_required = 'inpatients.delete_ward'
|
|
success_url = reverse_lazy('inpatients:ward_list')
|
|
|
|
def get_queryset(self):
|
|
"""Filter wards by tenant."""
|
|
return Ward.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
ward = self.get_object()
|
|
|
|
# Check if there are beds in this ward
|
|
if ward.beds.exists():
|
|
messages.error(request, _('Cannot delete ward with beds. Remove beds first.'))
|
|
return redirect('inpatients:ward_detail', pk=ward.pk)
|
|
|
|
messages.success(request, _('Ward deleted successfully'))
|
|
return super().delete(request, *args, **kwargs)
|
|
|
|
|
|
class BedListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List view for beds.
|
|
"""
|
|
model = Bed
|
|
template_name = 'inpatients/beds/bed_list.html'
|
|
context_object_name = 'beds'
|
|
paginate_by = 20
|
|
|
|
def get_queryset(self):
|
|
"""Filter beds by tenant and search query."""
|
|
queryset = Bed.objects.filter(
|
|
ward__tenant=self.request.user.tenant
|
|
).select_related('ward', 'current_patient')
|
|
|
|
# Handle search query
|
|
search_query = self.request.GET.get('search', '')
|
|
if search_query:
|
|
queryset = queryset.filter(
|
|
Q(bed_number__icontains=search_query) |
|
|
Q(room_number__icontains=search_query) |
|
|
Q(ward__name__icontains=search_query) |
|
|
Q(current_patient__first_name__icontains=search_query) |
|
|
Q(current_patient__last_name__icontains=search_query)
|
|
)
|
|
|
|
# Handle filter by ward
|
|
ward_id = self.request.GET.get('ward', '')
|
|
if ward_id:
|
|
queryset = queryset.filter(ward_id=ward_id)
|
|
|
|
# Handle filter by bed_type
|
|
bed_type = self.request.GET.get('bed_type', '')
|
|
if bed_type:
|
|
queryset = queryset.filter(bed_type=bed_type)
|
|
|
|
# Handle filter by room_type
|
|
room_type = self.request.GET.get('room_type', '')
|
|
if room_type:
|
|
queryset = queryset.filter(room_type=room_type)
|
|
|
|
# Handle filter by status
|
|
status = self.request.GET.get('status', '')
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
|
|
# Handle sort
|
|
sort_by = self.request.GET.get('sort', 'ward__name,room_number,bed_number')
|
|
if ',' in sort_by:
|
|
# Multiple sort fields
|
|
sort_fields = sort_by.split(',')
|
|
queryset = queryset.order_by(*sort_fields)
|
|
else:
|
|
queryset = queryset.order_by(sort_by)
|
|
|
|
return queryset
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
tenant = self.request.user.tenant
|
|
|
|
# Get wards for filter dropdown
|
|
context['wards'] = Ward.objects.filter(
|
|
tenant=tenant,
|
|
is_active=True
|
|
).order_by('name')
|
|
|
|
# Get bed types for filter dropdown
|
|
context['bed_types'] = Bed.BED_TYPE_CHOICES
|
|
|
|
# Get room types for filter dropdown
|
|
context['room_types'] = Bed.ROOM_TYPE_CHOICES
|
|
|
|
# Get statuses for filter dropdown
|
|
context['statuses'] = Bed.STATUS_CHOICES
|
|
|
|
# Add search query to context
|
|
context['search_query'] = self.request.GET.get('search', '')
|
|
context['ward_filter'] = self.request.GET.get('ward', '')
|
|
context['bed_type_filter'] = self.request.GET.get('bed_type', '')
|
|
context['room_type_filter'] = self.request.GET.get('room_type', '')
|
|
context['status_filter'] = self.request.GET.get('status', '')
|
|
context['sort_by'] = self.request.GET.get('sort', 'ward__name,room_number,bed_number')
|
|
|
|
return context
|
|
|
|
|
|
class BedDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Detail view for a bed.
|
|
"""
|
|
model = Bed
|
|
template_name = 'inpatients/beds/bed_detail.html'
|
|
context_object_name = 'bed'
|
|
|
|
def get_queryset(self):
|
|
"""Filter beds by tenant."""
|
|
return Bed.objects.filter(
|
|
ward__tenant=self.request.user.tenant
|
|
).select_related(
|
|
'ward', 'current_patient', 'current_admission',
|
|
'cleaned_by', 'blocked_by', 'created_by'
|
|
)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
bed = self.get_object()
|
|
|
|
# Get bed history - admissions that used this bed
|
|
context['admission_history'] = Admission.objects.filter(
|
|
Q(current_bed=bed) | Q(current_bed=bed)
|
|
).select_related(
|
|
'patient', 'admitting_physician'
|
|
).order_by('-admission_datetime')[:10]
|
|
|
|
# Get maintenance history if available
|
|
# This would require a model to track maintenance events
|
|
|
|
return context
|
|
|
|
|
|
class BedCreateView(LoginRequiredMixin, CreateView):
|
|
"""
|
|
Create view for a bed.
|
|
"""
|
|
model = Bed
|
|
form_class = BedForm
|
|
template_name = 'inpatients/beds/bed_form.html'
|
|
permission_required = 'inpatients.add_bed'
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['user'] = self.request.user
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
form.instance.created_by = self.request.user
|
|
messages.success(self.request, _('Bed created successfully'))
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse('inpatients:bed_detail', kwargs={'pk': self.object.pk})
|
|
|
|
|
|
class BedUpdateView(LoginRequiredMixin, UpdateView):
|
|
"""
|
|
Update view for a bed.
|
|
"""
|
|
model = Bed
|
|
form_class = BedForm
|
|
template_name = 'inpatients/beds/bed_form.html'
|
|
permission_required = 'inpatients.change_bed'
|
|
|
|
def get_queryset(self):
|
|
"""Filter beds by tenant."""
|
|
return Bed.objects.filter(ward__tenant=self.request.user.tenant)
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['user'] = self.request.user
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
messages.success(self.request, _('Bed updated successfully'))
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse('inpatients:bed_detail', kwargs={'pk': self.object.pk})
|
|
|
|
|
|
class BedDeleteView(LoginRequiredMixin, DeleteView):
|
|
"""
|
|
Delete view for a bed.
|
|
"""
|
|
model = Bed
|
|
template_name = 'inpatients/beds/bed_confirm_delete.html'
|
|
permission_required = 'inpatients.delete_bed'
|
|
|
|
def get_queryset(self):
|
|
"""Filter beds by tenant."""
|
|
return Bed.objects.filter(ward__tenant=self.request.user.tenant)
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
bed = self.get_object()
|
|
|
|
# Check if the bed is occupied
|
|
if bed.status == 'OCCUPIED':
|
|
messages.error(request, _('Cannot delete an occupied bed.'))
|
|
return redirect('inpatients:bed_detail', pk=bed.pk)
|
|
|
|
messages.success(request, _('Bed deleted successfully'))
|
|
return super().delete(request, *args, **kwargs)
|
|
|
|
def get_success_url(self):
|
|
return reverse('inpatients:ward_detail', kwargs={'pk': self.object.ward.pk})
|
|
|
|
|
|
class BedManagementView(LoginRequiredMixin, ListView):
|
|
"""
|
|
Bed management view with real-time status.
|
|
"""
|
|
model = Bed
|
|
template_name = 'inpatients/beds/bed_management.html'
|
|
context_object_name = 'beds'
|
|
paginate_by = 50
|
|
|
|
def get_queryset(self):
|
|
queryset = Bed.objects.filter(ward__tenant=self.request.user.tenant)
|
|
|
|
# Filter by ward
|
|
ward_id = self.request.GET.get('ward')
|
|
if ward_id:
|
|
queryset = queryset.filter(ward_id=ward_id)
|
|
|
|
# Filter by status
|
|
status = self.request.GET.get('status')
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
|
|
# Filter by bed type
|
|
bed_type = self.request.GET.get('bed_type')
|
|
if bed_type:
|
|
queryset = queryset.filter(bed_type=bed_type)
|
|
|
|
# Search functionality
|
|
search = self.request.GET.get('search')
|
|
if search:
|
|
queryset = queryset.filter(
|
|
Q(bed_number__icontains=search) |
|
|
Q(room_number__icontains=search) |
|
|
Q(current_patient__first_name__icontains=search) |
|
|
Q(current_patient__last_name__icontains=search) |
|
|
Q(current_patient__mrn__icontains=search)
|
|
)
|
|
|
|
return queryset.select_related(
|
|
'ward', 'current_patient', 'current_admission'
|
|
).order_by('ward__name', 'room_number', 'bed_number')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
tenant = self.request.user.tenant
|
|
wards = Ward.objects.filter(tenant=tenant, is_active=True)
|
|
|
|
occupancy_rates = []
|
|
for ward in wards:
|
|
try:
|
|
occupancy_rates.append(ward.occupancy_rate)
|
|
except Exception:
|
|
pass # Optional: handle edge cases if any
|
|
|
|
average_occupancy = (
|
|
sum(occupancy_rates) / len(occupancy_rates)
|
|
if occupancy_rates else 0
|
|
)
|
|
context.update({
|
|
'wards': Ward.objects.filter(tenant=tenant, is_active=True),
|
|
'bed_statuses': Bed.STATUS_CHOICES,
|
|
'bed_types': Bed.BED_TYPE_CHOICES,
|
|
'total_beds': Bed.objects.filter(ward__tenant=tenant).count(),
|
|
'available_beds': Bed.objects.filter(ward__tenant=tenant, status='AVAILABLE').count(),
|
|
'occupied_beds': Bed.objects.filter(ward__tenant=tenant, status='OCCUPIED').count(),
|
|
'maintenance_beds': Bed.objects.filter(ward__tenant=tenant, status='MAINTENANCE').count(),
|
|
'occupancy_rate': average_occupancy,
|
|
})
|
|
return context
|
|
|
|
|
|
class AdmissionListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List view for admissions.
|
|
"""
|
|
model = Admission
|
|
template_name = 'inpatients/admissions/admission_list.html'
|
|
context_object_name = 'admissions'
|
|
paginate_by = 25
|
|
|
|
def get_queryset(self):
|
|
queryset = Admission.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
# Filter by status
|
|
status = self.request.GET.get('status')
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
|
|
# Filter by ward
|
|
ward_id = self.request.GET.get('ward')
|
|
if ward_id:
|
|
queryset = queryset.filter(current_ward_id=ward_id)
|
|
|
|
# Filter by admission type
|
|
admission_type = self.request.GET.get('admission_type')
|
|
if admission_type:
|
|
queryset = queryset.filter(admission_type=admission_type)
|
|
|
|
# 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(chief_complaint__icontains=search) |
|
|
Q(admitting_diagnosis__icontains=search)
|
|
)
|
|
|
|
return queryset.select_related(
|
|
'patient', 'current_ward', 'current_bed', 'attending_physician'
|
|
).order_by('-admission_datetime')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
tenant = self.request.user.tenant
|
|
total_admissions = Admission.objects.filter(tenant=tenant).count()
|
|
active_admissions = Admission.objects.filter(tenant=tenant, status='ADMITTED').count()
|
|
context.update({
|
|
'admission_statuses': Admission.STATUS_CHOICES,
|
|
'admission_types': Admission.ADMISSION_TYPE_CHOICES,
|
|
'wards': Ward.objects.filter(tenant=tenant, is_active=True),
|
|
'total_admissions': total_admissions,
|
|
'active_admissions': active_admissions,
|
|
})
|
|
return context
|
|
|
|
|
|
class AdmissionDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Detail view for admission.
|
|
"""
|
|
model = Admission
|
|
template_name = 'inpatients/admissions/admission_detail.html'
|
|
context_object_name = 'admission'
|
|
|
|
def get_queryset(self):
|
|
return Admission.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
admission = self.object
|
|
|
|
# Get related transfers
|
|
context['transfers'] = admission.transfers.all().order_by('-requested_datetime')
|
|
|
|
# Get surgery schedules
|
|
context['surgeries'] = admission.surgeries.all().order_by('scheduled_date')
|
|
|
|
# Check if discharge summary exists
|
|
context['has_discharge_summary'] = hasattr(admission, 'discharge_summary')
|
|
|
|
return context
|
|
|
|
|
|
class AdmissionCreateView(LoginRequiredMixin, CreateView):
|
|
"""
|
|
Create view for an admission.
|
|
"""
|
|
model = Admission
|
|
form_class = AdmissionForm
|
|
template_name = 'inpatients/admissions/admission_form.html'
|
|
permission_required = 'inpatients.add_admission'
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['user'] = self.request.user
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
form.instance.tenant = self.request.user.tenant
|
|
form.instance.created_by = self.request.user
|
|
|
|
# Create the admission
|
|
response = super().form_valid(form)
|
|
|
|
# If admission status is ADMITTED, assign the bed
|
|
if form.instance.status == 'ADMITTED' and form.instance.initial_bed:
|
|
form.instance.initial_bed.assign_patient(form.instance.patient, form.instance)
|
|
messages.success(self.request, _('Patient admitted and assigned to bed successfully'))
|
|
else:
|
|
messages.success(self.request, _('Admission created successfully'))
|
|
|
|
return response
|
|
|
|
def get_success_url(self):
|
|
return reverse('inpatients:admission_detail', kwargs={'pk': self.object.pk})
|
|
|
|
|
|
class AdmissionUpdateView(LoginRequiredMixin, UpdateView):
|
|
"""
|
|
Update view for an admission.
|
|
"""
|
|
model = Admission
|
|
form_class = AdmissionForm
|
|
template_name = 'inpatients/admissions/admission_form.html'
|
|
permission_required = 'inpatients.change_admission'
|
|
|
|
def get_queryset(self):
|
|
"""Filter admissions by tenant."""
|
|
return Admission.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['user'] = self.request.user
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
# Check if status is changing to ADMITTED
|
|
old_status = self.get_object().status
|
|
new_status = form.instance.status
|
|
|
|
response = super().form_valid(form)
|
|
|
|
# If changing to ADMITTED, assign the bed
|
|
if old_status != 'ADMITTED' and new_status == 'ADMITTED' and form.instance.initial_bed:
|
|
form.instance.initial_bed.assign_patient(form.instance.patient, form.instance)
|
|
messages.success(self.request, _('Patient admitted and assigned to bed successfully'))
|
|
else:
|
|
messages.success(self.request, _('Admission updated successfully'))
|
|
|
|
return response
|
|
|
|
def get_success_url(self):
|
|
return reverse('inpatients:admission_detail', kwargs={'pk': self.object.pk})
|
|
|
|
|
|
class TransferManagementView(LoginRequiredMixin, ListView):
|
|
"""
|
|
Transfer management view.
|
|
"""
|
|
model = Transfer
|
|
template_name = 'inpatients/transfers/transfer_management.html'
|
|
context_object_name = 'transfers'
|
|
paginate_by = 25
|
|
|
|
def get_queryset(self):
|
|
tenant = self.request.user.tenant
|
|
queryset = Transfer.objects.filter(admission__tenant=tenant)
|
|
|
|
# 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
|
|
date_filter = self.request.GET.get('date_filter')
|
|
if date_filter == 'today':
|
|
queryset = queryset.filter(requested_datetime__date=timezone.now().date())
|
|
elif date_filter == 'pending':
|
|
queryset = queryset.filter(status__in=['REQUESTED', 'APPROVED', 'SCHEDULED'])
|
|
|
|
# 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(reason__icontains=search)
|
|
)
|
|
|
|
return queryset.select_related(
|
|
'patient', 'admission', 'from_ward', 'to_ward', 'from_bed', 'to_bed',
|
|
'requested_by', 'approved_by'
|
|
).order_by('-requested_datetime')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context.update({
|
|
'transfer_statuses': Transfer._meta.get_field('status').choices,
|
|
'priorities': Transfer._meta.get_field('priority').choices,
|
|
})
|
|
return context
|
|
|
|
|
|
class SurgeryScheduleView(LoginRequiredMixin, ListView):
|
|
"""
|
|
Surgery schedule view.
|
|
"""
|
|
model = SurgerySchedule
|
|
template_name = 'inpatients/surgery_schedule.html'
|
|
context_object_name = 'surgeries'
|
|
paginate_by = 25
|
|
|
|
def get_queryset(self):
|
|
queryset = SurgerySchedule.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
# Filter by date
|
|
date_filter = self.request.GET.get('date_filter')
|
|
if date_filter == 'today':
|
|
queryset = queryset.filter(scheduled_date=timezone.now().date())
|
|
elif date_filter == 'tomorrow':
|
|
queryset = queryset.filter(scheduled_date=timezone.now().date() + timedelta(days=1))
|
|
elif date_filter == 'this_week':
|
|
start_week = timezone.now().date() - timedelta(days=timezone.now().weekday())
|
|
end_week = start_week + timedelta(days=6)
|
|
queryset = queryset.filter(scheduled_date__range=[start_week, end_week])
|
|
|
|
# Filter by status
|
|
status = self.request.GET.get('status')
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
|
|
# Filter by surgery type
|
|
surgery_type = self.request.GET.get('surgery_type')
|
|
if surgery_type:
|
|
queryset = queryset.filter(surgery_type=surgery_type)
|
|
|
|
# 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(procedure_name__icontains=search) |
|
|
Q(primary_surgeon__first_name__icontains=search) |
|
|
Q(primary_surgeon__last_name__icontains=search)
|
|
)
|
|
|
|
return queryset.select_related(
|
|
'patient', 'admission', 'primary_surgeon', 'anesthesiologist'
|
|
).order_by('scheduled_date', 'scheduled_start_time')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context.update({
|
|
'surgery_statuses': SurgerySchedule._meta.get_field('status').choices,
|
|
'surgery_types': SurgerySchedule._meta.get_field('surgery_type').choices,
|
|
})
|
|
return context
|
|
|
|
|
|
class SurgeryDetailView(LoginRequiredMixin, DetailView):
|
|
model = SurgerySchedule
|
|
template_name = 'inpatients/surgeries/surgery_detail.html'
|
|
context_object_name = 'surgery'
|
|
|
|
def get_queryset(self):
|
|
tenant = self.request.user.tenant
|
|
queryset = get_object_or_404(SurgerySchedule, pk=self.kwargs['pk'], tenant=tenant)
|
|
return queryset
|
|
|
|
|
|
class SurgeryScheduleListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List view for surgery schedules.
|
|
"""
|
|
model = SurgerySchedule
|
|
template_name = 'inpatients/surgeries/surgery_schedule.html'
|
|
context_object_name = 'surgeries'
|
|
paginate_by = 20
|
|
|
|
def get_queryset(self):
|
|
"""Filter surgeries by tenant and search query."""
|
|
queryset = SurgerySchedule.objects.filter(
|
|
admission__tenant=self.request.user.tenant
|
|
).select_related(
|
|
'patient', 'admission', 'primary_surgeon',
|
|
'anesthesiologist',
|
|
)
|
|
|
|
# Handle search query
|
|
search_query = self.request.GET.get('search', '')
|
|
if search_query:
|
|
queryset = queryset.filter(
|
|
Q(surgery_name__icontains=search_query) |
|
|
Q(patient__first_name__icontains=search_query) |
|
|
Q(patient__last_name__icontains=search_query) |
|
|
Q(surgeon__first_name__icontains=search_query) |
|
|
Q(surgeon__last_name__icontains=search_query) |
|
|
Q(description__icontains=search_query)
|
|
)
|
|
|
|
# Handle filter by status
|
|
status = self.request.GET.get('status', '')
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
|
|
# Handle filter by priority
|
|
priority = self.request.GET.get('priority', '')
|
|
if priority:
|
|
queryset = queryset.filter(priority=priority)
|
|
|
|
# Handle filter by surgeon
|
|
surgeon_id = self.request.GET.get('surgeon', '')
|
|
if surgeon_id:
|
|
queryset = queryset.filter(surgeon_id=surgeon_id)
|
|
|
|
# Handle filter by operating_room
|
|
operating_room_id = self.request.GET.get('operating_room', '')
|
|
if operating_room_id:
|
|
queryset = queryset.filter(operating_room_id=operating_room_id)
|
|
|
|
# Handle filter by date range
|
|
date_from = self.request.GET.get('date_from', '')
|
|
if date_from:
|
|
queryset = queryset.filter(scheduled_date__gte=date_from)
|
|
|
|
date_to = self.request.GET.get('date_to', '')
|
|
if date_to:
|
|
queryset = queryset.filter(scheduled_date__lte=date_to)
|
|
|
|
# Handle sort
|
|
sort_by = self.request.GET.get('sort', 'scheduled_date,scheduled_start_time')
|
|
if ',' in sort_by:
|
|
# Multiple sort fields
|
|
sort_fields = sort_by.split(',')
|
|
queryset = queryset.order_by(*sort_fields)
|
|
else:
|
|
queryset = queryset.order_by(sort_by)
|
|
|
|
return queryset
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
tenant = self.request.user.tenant
|
|
|
|
# Get statuses for filter dropdown
|
|
context['statuses'] = SurgerySchedule.STATUS_CHOICES
|
|
|
|
# Get priorities for filter dropdown
|
|
context['priorities'] = SurgerySchedule.PRIORITY_CHOICES
|
|
|
|
# Get surgeons for filter dropdown
|
|
context['surgeons'] = User.objects.filter(
|
|
tenant=tenant,
|
|
is_active=True,
|
|
employee_profile__role__in=['SURGEON', 'PHYSICIAN_ASSISTANT']
|
|
).order_by('last_name', 'first_name')
|
|
|
|
# Get operating rooms for filter dropdown
|
|
try:
|
|
from operating_theatre.models import OperatingRoom
|
|
context['operating_rooms'] = OperatingRoom.objects.filter(
|
|
tenant=tenant,
|
|
is_active=True
|
|
).order_by('room_name')
|
|
except ImportError:
|
|
context['operating_rooms'] = []
|
|
|
|
# Add search query to context
|
|
context['search_query'] = self.request.GET.get('search', '')
|
|
context['status_filter'] = self.request.GET.get('status', '')
|
|
context['priority_filter'] = self.request.GET.get('priority', '')
|
|
context['surgeon_filter'] = self.request.GET.get('surgeon', '')
|
|
context['operating_room_filter'] = self.request.GET.get('operating_room', '')
|
|
context['date_from'] = self.request.GET.get('date_from', '')
|
|
context['date_to'] = self.request.GET.get('date_to', '')
|
|
context['sort_by'] = self.request.GET.get('sort', 'scheduled_date,scheduled_time')
|
|
|
|
return context
|
|
|
|
|
|
class SurgeryScheduleDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Detail view for a surgery schedule.
|
|
"""
|
|
model = SurgerySchedule
|
|
template_name = 'inpatients/surgeries/surgery_detail.html'
|
|
# context_object_name = 'surgery'
|
|
|
|
def get_queryset(self):
|
|
tenant = self.request.user.tenant
|
|
"""Filter surgeries by tenant."""
|
|
return SurgerySchedule.objects.filter(
|
|
admission__tenant=tenant
|
|
).select_related(
|
|
'patient', 'admission', 'primary_surgeon',
|
|
'anesthesiologist',
|
|
)
|
|
|
|
|
|
class SurgeryScheduleCreateView(LoginRequiredMixin, CreateView):
|
|
"""
|
|
Create view for a surgery schedule.
|
|
"""
|
|
model = SurgerySchedule
|
|
form_class = SurgeryScheduleForm
|
|
template_name = 'inpatients/surgeries/surgery_form.html'
|
|
permission_required = 'inpatients.add_surgeryschedule'
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['user'] = self.request.user
|
|
|
|
# If we have an admission ID in the URL, pre-fill the form
|
|
admission_id = self.kwargs.get('admission_id')
|
|
if admission_id:
|
|
try:
|
|
admission = Admission.objects.get(
|
|
pk=admission_id,
|
|
tenant=self.request.user.tenant,
|
|
status__in=['ADMITTED', 'TRANSFERRED']
|
|
)
|
|
|
|
kwargs['initial'] = {
|
|
'admission': admission,
|
|
'patient': admission.patient
|
|
}
|
|
except Admission.DoesNotExist:
|
|
pass
|
|
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
form.instance.created_by = self.request.user
|
|
messages.success(self.request, _('Surgery scheduled successfully'))
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse('inpatients:surgery_detail', kwargs={'pk': self.object.pk})
|
|
|
|
|
|
class SurgeryScheduleUpdateView(LoginRequiredMixin, UpdateView):
|
|
"""
|
|
Update view for a surgery schedule.
|
|
"""
|
|
model = SurgerySchedule
|
|
form_class = SurgeryScheduleForm
|
|
template_name = 'inpatients/surgeries/surgery_form.html'
|
|
permission_required = 'inpatients.change_surgeryschedule'
|
|
|
|
def get_queryset(self):
|
|
"""Filter surgeries by tenant."""
|
|
return SurgerySchedule.objects.filter(admission__tenant=self.request.user.tenant)
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['user'] = self.request.user
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
messages.success(self.request, _('Surgery schedule updated successfully'))
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse('inpatients:surgery_detail', kwargs={'pk': self.object.pk})
|
|
|
|
|
|
@login_required
|
|
def ward_stats(request):
|
|
"""
|
|
HTMX endpoint for ward statistics.
|
|
"""
|
|
tenant = request.user.tenant
|
|
|
|
stats = {
|
|
'total_beds': Bed.objects.filter(ward__tenant=tenant).count(),
|
|
'occupied_beds': Bed.objects.filter(ward__tenant=tenant, status='OCCUPIED').count(),
|
|
'available_beds': Bed.objects.filter(ward__tenant=tenant, status='AVAILABLE').count(),
|
|
'maintenance_beds': Bed.objects.filter(ward__tenant=tenant, status='MAINTENANCE').count(),
|
|
}
|
|
|
|
if stats['total_beds'] > 0:
|
|
stats['occupancy_rate'] = (stats['occupied_beds'] / stats['total_beds']) * 100
|
|
else:
|
|
stats['occupancy_rate'] = 0
|
|
|
|
return render(request, 'inpatients/partials/ward_stats.html', {'stats': stats})
|
|
|
|
|
|
@login_required
|
|
def bed_grid(request):
|
|
"""
|
|
HTMX endpoint for bed grid view.
|
|
"""
|
|
ward_id = request.GET.get('ward_id')
|
|
|
|
if ward_id:
|
|
beds = Bed.objects.filter(
|
|
ward_id=ward_id,
|
|
ward__tenant=request.user.tenant
|
|
).select_related('current_patient', 'current_admission').order_by('room_number', 'bed_number')
|
|
else:
|
|
beds = Bed.objects.filter(
|
|
ward__tenant=request.user.tenant
|
|
).select_related('ward', 'current_patient', 'current_admission').order_by(
|
|
'ward__name', 'room_number', 'bed_number'
|
|
)
|
|
|
|
return render(request, 'inpatients/partials/bed_grid.html', {'beds': beds})
|
|
|
|
|
|
@login_required
|
|
def admission_search(request):
|
|
"""
|
|
HTMX endpoint for admission search.
|
|
"""
|
|
search = request.GET.get('search', '')
|
|
status = request.GET.get('status', '')
|
|
ward_id = request.GET.get('ward', '')
|
|
|
|
queryset = Admission.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 ward_id:
|
|
queryset = queryset.filter(current_ward_id=ward_id)
|
|
|
|
admissions = queryset.select_related(
|
|
'patient', 'current_ward', 'current_bed', 'attending_physician'
|
|
).order_by('-admission_datetime')[:20]
|
|
|
|
return render(request, 'inpatients/partials/admission_list.html', {'admissions': admissions})
|
|
|
|
|
|
@login_required
|
|
def surgery_calendar(request):
|
|
"""
|
|
HTMX endpoint for surgery calendar view.
|
|
"""
|
|
tenant = request.user.tenant
|
|
# Get date range parameters
|
|
start_date_str = request.GET.get('start_date')
|
|
end_date_str = request.GET.get('end_date')
|
|
|
|
# Default to current week if not specified
|
|
if not start_date_str:
|
|
today = timezone.now().date()
|
|
start_date = today - timedelta(days=today.weekday()) # Monday of current week
|
|
end_date = start_date + timedelta(days=6) # Sunday of current week
|
|
else:
|
|
try:
|
|
start_date = datetime.strptime(start_date_str, '%Y-%m-%d').date()
|
|
end_date = datetime.strptime(end_date_str, '%Y-%m-%d').date() if end_date_str else start_date + timedelta(days=6)
|
|
except ValueError:
|
|
# Handle invalid date format
|
|
today = timezone.now().date()
|
|
start_date = today - timedelta(days=today.weekday())
|
|
end_date = start_date + timedelta(days=6)
|
|
|
|
# Get surgeries for the date range
|
|
surgeries = SurgerySchedule.objects.filter(
|
|
tenant=tenant,
|
|
scheduled_date__range=[start_date, end_date]
|
|
).select_related(
|
|
'patient', 'primary_surgeon', 'anesthesiologist'
|
|
).order_by('scheduled_date', 'scheduled_start_time')
|
|
|
|
# Group surgeries by date and operating room
|
|
calendar_data = {}
|
|
for day in range((end_date - start_date).days + 1):
|
|
current_date = start_date + timedelta(days=day)
|
|
calendar_data[current_date] = {}
|
|
|
|
for surgery in surgeries:
|
|
if surgery.scheduled_date not in calendar_data:
|
|
continue
|
|
|
|
if surgery.operating_room not in calendar_data[surgery.scheduled_date]:
|
|
calendar_data[surgery.scheduled_date][surgery.operating_room] = []
|
|
|
|
calendar_data[surgery.scheduled_date][surgery.operating_room].append(surgery)
|
|
|
|
context = {
|
|
'calendar_data': calendar_data,
|
|
'start_date': start_date,
|
|
'end_date': end_date,
|
|
'date_range': [(start_date + timedelta(days=i)) for i in range((end_date - start_date).days + 1)],
|
|
'operating_rooms': sorted(set(surgery.operating_room for surgery in surgeries))
|
|
}
|
|
|
|
return render(request, 'inpatients/partials/surgery_calendar.html', context)
|
|
|
|
|
|
@login_required
|
|
def transfer_patient(request, admission_id):
|
|
"""
|
|
HTMX endpoint for patient transfer.
|
|
"""
|
|
if request.method == 'POST':
|
|
admission = get_object_or_404(Admission, id=admission_id, tenant=request.user.tenant)
|
|
|
|
to_ward_id = request.POST.get('to_ward')
|
|
to_bed_id = request.POST.get('to_bed')
|
|
reason = request.POST.get('reason')
|
|
priority = request.POST.get('priority', 'ROUTINE')
|
|
|
|
if to_ward_id:
|
|
to_ward = get_object_or_404(Ward, id=to_ward_id, tenant=request.user.tenant)
|
|
|
|
# Create transfer request
|
|
transfer = Transfer(
|
|
admission=admission,
|
|
patient=admission.patient,
|
|
transfer_type='INTERNAL',
|
|
from_ward=admission.current_ward,
|
|
from_bed=admission.current_bed,
|
|
to_ward=to_ward,
|
|
reason=reason,
|
|
priority=priority,
|
|
requested_datetime=timezone.now(),
|
|
requested_by=request.user,
|
|
status='REQUESTED'
|
|
)
|
|
|
|
if to_bed_id:
|
|
to_bed = get_object_or_404(Bed, id=to_bed_id, ward=to_ward, status='AVAILABLE')
|
|
transfer.to_bed = to_bed
|
|
|
|
transfer.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_event(
|
|
actor=request.user,
|
|
action='TRANSFER_REQUESTED',
|
|
target=admission,
|
|
target_repr=str(admission),
|
|
description=f"Transfer requested for {admission.patient.get_full_name()} from {admission.current_ward.name} to {to_ward.name}"
|
|
)
|
|
|
|
messages.success(request, f"Transfer request created for {admission.patient.get_full_name()}")
|
|
return redirect('inpatients:admission_detail', pk=admission_id)
|
|
else:
|
|
messages.error(request, "Destination ward is required")
|
|
return redirect('inpatients:admission_detail', pk=admission_id)
|
|
|
|
# GET request - show form
|
|
admission = get_object_or_404(Admission, id=admission_id, tenant=request.user.tenant)
|
|
wards = Ward.objects.filter(
|
|
tenant=request.user.tenant,
|
|
is_active=True,
|
|
is_accepting_admissions=True
|
|
).exclude(id=admission.current_ward_id if admission.current_ward else None)
|
|
|
|
context = {
|
|
'admission': admission,
|
|
'wards': wards,
|
|
'priorities': Transfer._meta.get_field('priority').choices
|
|
}
|
|
|
|
return render(request, 'inpatients/patient_transfer.html', context)
|
|
|
|
|
|
@login_required
|
|
def approve_transfer(request, transfer_id):
|
|
"""
|
|
HTMX endpoint for approving a transfer.
|
|
"""
|
|
print("transfer clicked")
|
|
transfer = get_object_or_404(Transfer, id=transfer_id, admission__tenant=request.user.tenant)
|
|
|
|
if request.method == 'POST':
|
|
if transfer.status != 'REQUESTED':
|
|
messages.error(request, f"Transfer cannot be approved. Current status: {transfer.get_status_display()}")
|
|
return redirect('inpatients:transfer_management')
|
|
|
|
scheduled_datetime = request.POST.get('scheduled_datetime')
|
|
|
|
transfer.status = 'APPROVED'
|
|
transfer.approved_by = request.user
|
|
|
|
if scheduled_datetime:
|
|
try:
|
|
transfer.scheduled_datetime = datetime.strptime(scheduled_datetime, '%Y-%m-%dT%H:%M')
|
|
except ValueError:
|
|
messages.error(request, "Invalid scheduled date/time format")
|
|
return redirect('inpatients:transfer_management')
|
|
|
|
transfer.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_event(
|
|
actor=request.user,
|
|
action='TRANSFER_APPROVED',
|
|
target=transfer,
|
|
target_repr=str(transfer),
|
|
description=f"Transfer approved for {transfer.patient.get_full_name()} from {transfer.from_ward.name} to {transfer.to_ward.name}"
|
|
)
|
|
|
|
messages.success(request, f"Transfer approved for {transfer.patient.get_full_name()}")
|
|
return redirect('inpatients:transfer_management')
|
|
|
|
# GET request - show form
|
|
context = {
|
|
'transfer': transfer
|
|
}
|
|
|
|
return render(request, 'inpatients/partials/approve_transfer_form.html', context)
|
|
|
|
|
|
@login_required
|
|
def complete_transfer(request, transfer_id):
|
|
"""
|
|
HTMX endpoint for completing a transfer.
|
|
"""
|
|
transfer = get_object_or_404(Transfer, id=transfer_id, admission__tenant=request.user.tenant)
|
|
|
|
if request.method == 'POST':
|
|
if transfer.status not in ['APPROVED', 'SCHEDULED']:
|
|
messages.error(request, f"Transfer cannot be completed. Current status: {transfer.get_status_display()}")
|
|
return redirect('inpatients:transfer_management')
|
|
|
|
if not transfer.to_bed:
|
|
messages.error(request, "Destination bed must be assigned before completing transfer")
|
|
return redirect('inpatients:transfer_management')
|
|
|
|
# Update transfer
|
|
transfer.status = 'COMPLETED'
|
|
transfer.actual_datetime = timezone.now()
|
|
transfer.completed_by = request.user
|
|
transfer.save()
|
|
|
|
# Update admission
|
|
admission = transfer.admission
|
|
|
|
# Free up the old bed
|
|
if admission.current_bed:
|
|
old_bed = admission.current_bed
|
|
old_bed.status = 'AVAILABLE'
|
|
old_bed.current_patient = None
|
|
old_bed.current_admission = None
|
|
old_bed.occupied_since = None
|
|
old_bed.save()
|
|
|
|
# Assign new bed
|
|
new_bed = transfer.to_bed
|
|
new_bed.status = 'OCCUPIED'
|
|
new_bed.current_patient = admission.patient
|
|
new_bed.current_admission = admission
|
|
new_bed.occupied_since = timezone.now()
|
|
new_bed.save()
|
|
|
|
# Update admission with new location
|
|
admission.current_ward = transfer.to_ward
|
|
admission.current_bed = new_bed
|
|
admission.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_event(
|
|
actor=request.user,
|
|
action='TRANSFER_COMPLETED',
|
|
target=transfer,
|
|
target_repr=str(transfer),
|
|
description=f"Transfer completed for {transfer.patient.get_full_name()} from {transfer.from_ward.name} to {transfer.to_ward.name}"
|
|
)
|
|
|
|
messages.success(request, f"Transfer completed for {transfer.patient.get_full_name()}")
|
|
return redirect('inpatients:transfer_management')
|
|
|
|
# GET request - show form
|
|
context = {
|
|
'transfer': transfer
|
|
}
|
|
|
|
return render(request, 'inpatients/partials/complete_transfer_form.html', context)
|
|
|
|
|
|
@login_required
|
|
def update_bed_status(request, bed_id):
|
|
"""
|
|
HTMX endpoint for updating bed status.
|
|
"""
|
|
bed = get_object_or_404(Bed, id=bed_id, ward__tenant=request.user.tenant)
|
|
|
|
if request.method == 'POST':
|
|
new_status = request.POST.get('status')
|
|
notes = request.POST.get('notes', '')
|
|
|
|
if new_status and new_status in dict(Bed._meta.get_field('status').choices):
|
|
old_status = bed.status
|
|
bed.status = new_status
|
|
|
|
# Handle special cases
|
|
if new_status == 'CLEANING':
|
|
bed.last_cleaned = None
|
|
elif new_status == 'AVAILABLE' and old_status == 'CLEANING':
|
|
bed.last_cleaned = timezone.now()
|
|
bed.cleaned_by = request.user
|
|
elif new_status == 'MAINTENANCE':
|
|
bed.last_maintenance = timezone.now()
|
|
|
|
if notes:
|
|
bed.notes = notes
|
|
|
|
bed.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_event(
|
|
actor=request.user,
|
|
action='BED_STATUS_UPDATED',
|
|
target=bed,
|
|
target_repr=str(bed),
|
|
description=f"Bed status updated from {dict(Bed._meta.get_field('status').choices)[old_status]} to {dict(Bed._meta.get_field('status').choices)[new_status]}"
|
|
)
|
|
|
|
messages.success(request, f"Bed status updated to {dict(Bed._meta.get_field('status').choices)[new_status]}")
|
|
else:
|
|
messages.error(request, "Invalid bed status")
|
|
|
|
return redirect('inpatients:bed_management')
|
|
|
|
# GET request - show form
|
|
context = {
|
|
'bed': bed,
|
|
'statuses': Bed._meta.get_field('status').choices
|
|
}
|
|
|
|
return render(request, 'inpatients/partials/update_bed_status_form.html', context)
|
|
|
|
|
|
@login_required
|
|
def get_available_beds(request):
|
|
"""
|
|
AJAX view to get available beds for a ward.
|
|
"""
|
|
ward_id = request.GET.get('ward_id')
|
|
|
|
if not ward_id:
|
|
return JsonResponse({'error': 'Ward ID is required'}, status=400)
|
|
|
|
try:
|
|
ward = Ward.objects.get(
|
|
pk=ward_id,
|
|
tenant=request.user.tenant,
|
|
is_active=True
|
|
)
|
|
except Ward.DoesNotExist:
|
|
return JsonResponse({'error': 'Ward not found'}, status=404)
|
|
|
|
# Get available beds for this ward
|
|
beds = Bed.objects.filter(
|
|
ward=ward,
|
|
status='AVAILABLE'
|
|
).order_by('room_number', 'bed_number')
|
|
|
|
beds_data = [
|
|
{
|
|
'id': bed.id,
|
|
'name': f"Room {bed.room_number}, Bed {bed.bed_number}",
|
|
'bed_type': bed.get_bed_type_display(),
|
|
'room_type': bed.get_room_type_display()
|
|
}
|
|
for bed in beds
|
|
]
|
|
|
|
return JsonResponse({'beds': beds_data})
|
|
|
|
|
|
@login_required
|
|
def bed_status_board(request):
|
|
"""
|
|
View for the bed status board.
|
|
"""
|
|
tenant = request.user.tenant
|
|
|
|
# Get all wards for this tenant
|
|
wards = Ward.objects.filter(
|
|
tenant=tenant,
|
|
is_active=True
|
|
).order_by('name')
|
|
|
|
# Build ward and bed data
|
|
ward_data = []
|
|
|
|
for ward in wards:
|
|
beds = Bed.objects.filter(ward=ward).order_by('room_number', 'bed_number')
|
|
|
|
# Group beds by room
|
|
rooms = {}
|
|
for bed in beds:
|
|
room_num = bed.room_number
|
|
if room_num not in rooms:
|
|
rooms[room_num] = []
|
|
rooms[room_num].append(bed)
|
|
|
|
# Sort rooms by room number
|
|
sorted_rooms = sorted(rooms.items())
|
|
|
|
ward_data.append({
|
|
'ward': ward,
|
|
'rooms': sorted_rooms,
|
|
'total_beds': beds.count(),
|
|
'available_beds': beds.filter(status='AVAILABLE').count(),
|
|
'occupied_beds': beds.filter(status='OCCUPIED').count()
|
|
})
|
|
|
|
return render(request, 'inpatients/bed_status_board.html', {
|
|
'ward_data': ward_data
|
|
})
|
|
|
|
|
|
@login_required
|
|
def clean_bed(request, pk):
|
|
"""
|
|
Mark a bed as cleaned.
|
|
"""
|
|
bed = get_object_or_404(
|
|
Bed,
|
|
pk=pk,
|
|
ward__tenant=request.user.tenant
|
|
)
|
|
|
|
# Only beds with status CLEANING can be marked as cleaned
|
|
if bed.status != 'CLEANING':
|
|
messages.error(request, _('Only beds with status "Being Cleaned" can be marked as cleaned'))
|
|
return redirect('inpatients:bed_detail', pk=bed.pk)
|
|
|
|
if request.method == 'POST':
|
|
cleaning_level = request.POST.get('cleaning_level', 'ROUTINE')
|
|
|
|
bed.mark_cleaned(request.user, cleaning_level)
|
|
messages.success(request, _('Bed marked as cleaned successfully'))
|
|
|
|
# Redirect to referring page if available
|
|
return redirect(request.POST.get('next', 'inpatients:bed_detail'), pk=bed.pk)
|
|
|
|
return render(request, 'inpatients/clean_bed.html', {
|
|
'bed': bed,
|
|
'cleaning_levels': Bed.CLEANING_LEVEL_CHOICES,
|
|
'next': request.GET.get('next', reverse('inpatients:bed_detail', kwargs={'pk': bed.pk}))
|
|
})
|
|
|
|
|
|
@login_required
|
|
# @permission_required('inpatients.change_bed')
|
|
def block_bed(request, pk):
|
|
"""
|
|
Block a bed from being used.
|
|
"""
|
|
bed = get_object_or_404(
|
|
Bed,
|
|
pk=pk,
|
|
ward__tenant=request.user.tenant
|
|
)
|
|
|
|
# Only available beds can be blocked
|
|
if bed.status != 'AVAILABLE':
|
|
messages.error(request, _('Only available beds can be blocked'))
|
|
return redirect('inpatients:bed_detail', pk=bed.pk)
|
|
|
|
if request.method == 'POST':
|
|
reason = request.POST.get('reason')
|
|
blocked_until = request.POST.get('blocked_until')
|
|
|
|
if not reason:
|
|
messages.error(request, _('Reason is required'))
|
|
return redirect('inpatients:block_bed', pk=bed.pk)
|
|
|
|
bed.status = 'BLOCKED'
|
|
bed.blocked_reason = reason
|
|
bed.blocked_by = request.user
|
|
|
|
if blocked_until:
|
|
try:
|
|
bed.blocked_until = timezone.datetime.fromisoformat(blocked_until)
|
|
except ValueError:
|
|
messages.error(request, _('Invalid date format'))
|
|
return redirect('inpatients:block_bed', pk=bed.pk)
|
|
|
|
bed.save()
|
|
|
|
messages.success(request, _('Bed blocked successfully'))
|
|
return redirect('inpatients:bed_detail', pk=bed.pk)
|
|
|
|
return render(request, 'inpatients/block_bed.html', {
|
|
'bed': bed
|
|
})
|
|
|
|
|
|
@login_required
|
|
# @permission_required('inpatients.change_bed')
|
|
def unblock_bed(request, pk):
|
|
"""
|
|
Unblock a bed.
|
|
"""
|
|
bed = get_object_or_404(
|
|
Bed,
|
|
pk=pk,
|
|
ward__tenant=request.user.tenant
|
|
)
|
|
|
|
# Only blocked beds can be unblocked
|
|
if bed.status != 'BLOCKED':
|
|
messages.error(request, _('Only blocked beds can be unblocked'))
|
|
return redirect('inpatients:bed_detail', pk=bed.pk)
|
|
|
|
bed.status = 'AVAILABLE'
|
|
bed.blocked_reason = None
|
|
bed.blocked_until = None
|
|
bed.save()
|
|
|
|
messages.success(request, _('Bed unblocked successfully'))
|
|
return redirect('inpatients:bed_detail', pk=bed.pk)
|
|
|
|
|
|
@login_required
|
|
# @permission_required('inpatients.change_bed')
|
|
def maintenance_bed(request, pk):
|
|
"""
|
|
Mark a bed for maintenance.
|
|
"""
|
|
bed = get_object_or_404(
|
|
Bed,
|
|
pk=pk,
|
|
ward__tenant=request.user.tenant
|
|
)
|
|
|
|
# Only available beds can be marked for maintenance
|
|
if bed.status != 'AVAILABLE':
|
|
messages.error(request, _('Only available beds can be marked for maintenance'))
|
|
return redirect('inpatients:bed_detail', pk=bed.pk)
|
|
|
|
if request.method == 'POST':
|
|
notes = request.POST.get('notes')
|
|
|
|
bed.mark_maintenance(notes)
|
|
messages.success(request, _('Bed marked for maintenance successfully'))
|
|
return redirect('inpatients:bed_detail', pk=bed.pk)
|
|
|
|
return render(request, 'inpatients/maintenance_bed.html', {
|
|
'bed': bed
|
|
})
|
|
|
|
#
|
|
# # inpatients/views.py
|
|
# from django.shortcuts import render, get_object_or_404, redirect
|
|
# from django.urls import reverse, reverse_lazy
|
|
# from django.contrib import messages
|
|
# from django.contrib.auth.decorators import login_required, permission_required
|
|
# from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
|
# from django.views.generic import (
|
|
# ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView
|
|
# )
|
|
# from django.utils import timezone
|
|
# from django.utils.translation import gettext_lazy as _
|
|
# from django.db.models import Q, Count, Sum, F, ExpressionWrapper, fields
|
|
# from django.http import JsonResponse
|
|
# from django.core.paginator import Paginator
|
|
#
|
|
# from accounts.models import User
|
|
# from patients.models import PatientProfile
|
|
# from .models import Ward, Bed, Admission, Transfer, DischargeSummary, SurgerySchedule
|
|
# from .forms import (
|
|
# WardForm, BedForm, AdmissionForm, TransferForm,
|
|
# DischargeSummaryForm, SurgeryScheduleForm
|
|
# )
|
|
#
|
|
#
|
|
|
|
#
|
|
#
|
|
# class WardListView(LoginRequiredMixin, ListView):
|
|
# """
|
|
# List view for wards.
|
|
# """
|
|
# model = Ward
|
|
# template_name = 'inpatients/ward_list.html'
|
|
# context_object_name = 'wards'
|
|
# paginate_by = 20
|
|
#
|
|
# def get_queryset(self):
|
|
# """Filter wards by tenant and search query."""
|
|
# queryset = Ward.objects.filter(
|
|
# tenant=self.request.user.tenant
|
|
# ).select_related('nurse_manager').annotate(
|
|
# bed_count=Count('beds'),
|
|
# occupied_bed_count=Count('beds', filter=Q(beds__status='OCCUPIED'))
|
|
# )
|
|
#
|
|
# # Handle search query
|
|
# search_query = self.request.GET.get('search', '')
|
|
# if search_query:
|
|
# queryset = queryset.filter(
|
|
# Q(name__icontains=search_query) |
|
|
# Q(description__icontains=search_query) |
|
|
# Q(ward_type__icontains=search_query) |
|
|
# Q(building__icontains=search_query) |
|
|
# Q(floor__icontains=search_query) |
|
|
# Q(nurse_manager__first_name__icontains=search_query) |
|
|
# Q(nurse_manager__last_name__icontains=search_query)
|
|
# )
|
|
#
|
|
# # Handle filter by ward_type
|
|
# ward_type = self.request.GET.get('ward_type', '')
|
|
# if ward_type:
|
|
# queryset = queryset.filter(ward_type=ward_type)
|
|
#
|
|
# # Handle filter by building
|
|
# building = self.request.GET.get('building', '')
|
|
# if building:
|
|
# queryset = queryset.filter(building=building)
|
|
#
|
|
# # Handle filter by floor
|
|
# floor = self.request.GET.get('floor', '')
|
|
# if floor:
|
|
# queryset = queryset.filter(floor=floor)
|
|
#
|
|
# # Handle filter by active status
|
|
# is_active = self.request.GET.get('is_active', '')
|
|
# if is_active == 'true':
|
|
# queryset = queryset.filter(is_active=True)
|
|
# elif is_active == 'false':
|
|
# queryset = queryset.filter(is_active=False)
|
|
#
|
|
# # Handle sort
|
|
# sort_by = self.request.GET.get('sort', 'name')
|
|
# if sort_by.startswith('-'):
|
|
# sort_field = sort_by[1:]
|
|
# sort_dir = '-'
|
|
# else:
|
|
# sort_field = sort_by
|
|
# sort_dir = ''
|
|
#
|
|
# if sort_field == 'occupancy':
|
|
# # Calculate occupancy percentage for sorting
|
|
# queryset = queryset.annotate(
|
|
# occupancy=ExpressionWrapper(
|
|
# (F('occupied_bed_count') * 100.0) / F('bed_count'),
|
|
# output_field=fields.FloatField()
|
|
# )
|
|
# )
|
|
# queryset = queryset.order_by(f'{sort_dir}occupancy')
|
|
# else:
|
|
# queryset = queryset.order_by(sort_by)
|
|
#
|
|
# return queryset
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# tenant = self.request.user.tenant
|
|
#
|
|
# # Get unique values for filters
|
|
# context['ward_types'] = Ward.objects.filter(
|
|
# tenant=tenant
|
|
# ).values_list('ward_type', flat=True).distinct()
|
|
#
|
|
# context['buildings'] = Ward.objects.filter(
|
|
# tenant=tenant
|
|
# ).exclude(building__isnull=True).exclude(building='').values_list(
|
|
# 'building', flat=True
|
|
# ).distinct()
|
|
#
|
|
# context['floors'] = Ward.objects.filter(
|
|
# tenant=tenant
|
|
# ).exclude(floor__isnull=True).exclude(floor='').values_list(
|
|
# 'floor', flat=True
|
|
# ).distinct()
|
|
#
|
|
# # Add search query to context
|
|
# context['search_query'] = self.request.GET.get('search', '')
|
|
# context['ward_type_filter'] = self.request.GET.get('ward_type', '')
|
|
# context['building_filter'] = self.request.GET.get('building', '')
|
|
# context['floor_filter'] = self.request.GET.get('floor', '')
|
|
# context['is_active_filter'] = self.request.GET.get('is_active', '')
|
|
# context['sort_by'] = self.request.GET.get('sort', 'name')
|
|
#
|
|
# return context
|
|
#
|
|
#
|
|
# class WardDetailView(LoginRequiredMixin, DetailView):
|
|
# """
|
|
# Detail view for a ward.
|
|
# """
|
|
# model = Ward
|
|
# template_name = 'inpatients/ward_detail.html'
|
|
# context_object_name = 'ward'
|
|
#
|
|
# def get_queryset(self):
|
|
# """Filter wards by tenant."""
|
|
# return Ward.objects.filter(
|
|
# tenant=self.request.user.tenant
|
|
# ).select_related('nurse_manager')
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# ward = self.get_object()
|
|
#
|
|
# # Get beds for this ward with patient information
|
|
# context['beds'] = Bed.objects.filter(
|
|
# ward=ward
|
|
# ).select_related(
|
|
# 'current_patient', 'current_admission'
|
|
# ).order_by('room_number', 'bed_number')
|
|
#
|
|
# # Group beds by room for display
|
|
# rooms = {}
|
|
# for bed in context['beds']:
|
|
# room_num = bed.room_number
|
|
# if room_num not in rooms:
|
|
# rooms[room_num] = []
|
|
# rooms[room_num].append(bed)
|
|
# context['rooms'] = rooms
|
|
#
|
|
# # Get ward statistics
|
|
# context['total_beds'] = context['beds'].count()
|
|
# context['available_beds'] = context['beds'].filter(status='AVAILABLE').count()
|
|
# context['occupied_beds'] = context['beds'].filter(status='OCCUPIED').count()
|
|
# context['maintenance_beds'] = context['beds'].filter(
|
|
# status__in=['MAINTENANCE', 'OUT_OF_ORDER', 'CLEANING']
|
|
# ).count()
|
|
#
|
|
# if context['total_beds'] > 0:
|
|
# context['occupancy_rate'] = (context['occupied_beds'] / context['total_beds']) * 100
|
|
# else:
|
|
# context['occupancy_rate'] = 0
|
|
#
|
|
# # Get recent admissions to this ward
|
|
# context['recent_admissions'] = Admission.objects.filter(
|
|
# Q(initial_ward=ward) | Q(current_bed__ward=ward),
|
|
# status__in=['ADMITTED', 'READY_FOR_DISCHARGE']
|
|
# ).select_related(
|
|
# 'patient', 'admitting_physician'
|
|
# ).order_by('-admitted_at')[:10]
|
|
#
|
|
# return context
|
|
#
|
|
#
|
|
# class WardCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
# """
|
|
# Create view for a ward.
|
|
# """
|
|
# model = Ward
|
|
# form_class = WardForm
|
|
# template_name = 'inpatients/ward_form.html'
|
|
# permission_required = 'inpatients.add_ward'
|
|
#
|
|
# def get_form_kwargs(self):
|
|
# kwargs = super().get_form_kwargs()
|
|
# kwargs['user'] = self.request.user
|
|
# return kwargs
|
|
#
|
|
# def form_valid(self, form):
|
|
# form.instance.tenant = self.request.user.tenant
|
|
# form.instance.created_by = self.request.user
|
|
# messages.success(self.request, _('Ward created successfully'))
|
|
# return super().form_valid(form)
|
|
#
|
|
# def get_success_url(self):
|
|
# return reverse('inpatients:ward_detail', kwargs={'pk': self.object.pk})
|
|
#
|
|
#
|
|
# class WardUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
# """
|
|
# Update view for a ward.
|
|
# """
|
|
# model = Ward
|
|
# form_class = WardForm
|
|
# template_name = 'inpatients/ward_form.html'
|
|
# permission_required = 'inpatients.change_ward'
|
|
#
|
|
# def get_queryset(self):
|
|
# """Filter wards by tenant."""
|
|
# return Ward.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_form_kwargs(self):
|
|
# kwargs = super().get_form_kwargs()
|
|
# kwargs['user'] = self.request.user
|
|
# return kwargs
|
|
#
|
|
# def form_valid(self, form):
|
|
# messages.success(self.request, _('Ward updated successfully'))
|
|
# return super().form_valid(form)
|
|
#
|
|
# def get_success_url(self):
|
|
# return reverse('inpatients:ward_detail', kwargs={'pk': self.object.pk})
|
|
#
|
|
#
|
|
|
|
#
|
|
#
|
|
# class BedListView(LoginRequiredMixin, ListView):
|
|
# """
|
|
# List view for beds.
|
|
# """
|
|
# model = Bed
|
|
# template_name = 'inpatients/bed_list.html'
|
|
# context_object_name = 'beds'
|
|
# paginate_by = 20
|
|
#
|
|
# def get_queryset(self):
|
|
# """Filter beds by tenant and search query."""
|
|
# queryset = Bed.objects.filter(
|
|
# ward__tenant=self.request.user.tenant
|
|
# ).select_related('ward', 'current_patient')
|
|
#
|
|
# # Handle search query
|
|
# search_query = self.request.GET.get('search', '')
|
|
# if search_query:
|
|
# queryset = queryset.filter(
|
|
# Q(bed_number__icontains=search_query) |
|
|
# Q(room_number__icontains=search_query) |
|
|
# Q(ward__name__icontains=search_query) |
|
|
# Q(current_patient__first_name__icontains=search_query) |
|
|
# Q(current_patient__last_name__icontains=search_query)
|
|
# )
|
|
#
|
|
# # Handle filter by ward
|
|
# ward_id = self.request.GET.get('ward', '')
|
|
# if ward_id:
|
|
# queryset = queryset.filter(ward_id=ward_id)
|
|
#
|
|
# # Handle filter by bed_type
|
|
# bed_type = self.request.GET.get('bed_type', '')
|
|
# if bed_type:
|
|
# queryset = queryset.filter(bed_type=bed_type)
|
|
#
|
|
# # Handle filter by room_type
|
|
# room_type = self.request.GET.get('room_type', '')
|
|
# if room_type:
|
|
# queryset = queryset.filter(room_type=room_type)
|
|
#
|
|
# # Handle filter by status
|
|
# status = self.request.GET.get('status', '')
|
|
# if status:
|
|
# queryset = queryset.filter(status=status)
|
|
#
|
|
# # Handle sort
|
|
# sort_by = self.request.GET.get('sort', 'ward__name,room_number,bed_number')
|
|
# if ',' in sort_by:
|
|
# # Multiple sort fields
|
|
# sort_fields = sort_by.split(',')
|
|
# queryset = queryset.order_by(*sort_fields)
|
|
# else:
|
|
# queryset = queryset.order_by(sort_by)
|
|
#
|
|
# return queryset
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# tenant = self.request.user.tenant
|
|
#
|
|
# # Get wards for filter dropdown
|
|
# context['wards'] = Ward.objects.filter(
|
|
# tenant=tenant,
|
|
# is_active=True
|
|
# ).order_by('name')
|
|
#
|
|
# # Get bed types for filter dropdown
|
|
# context['bed_types'] = Bed.BED_TYPE_CHOICES
|
|
#
|
|
# # Get room types for filter dropdown
|
|
# context['room_types'] = Bed.ROOM_TYPE_CHOICES
|
|
#
|
|
# # Get statuses for filter dropdown
|
|
# context['statuses'] = Bed.STATUS_CHOICES
|
|
#
|
|
# # Add search query to context
|
|
# context['search_query'] = self.request.GET.get('search', '')
|
|
# context['ward_filter'] = self.request.GET.get('ward', '')
|
|
# context['bed_type_filter'] = self.request.GET.get('bed_type', '')
|
|
# context['room_type_filter'] = self.request.GET.get('room_type', '')
|
|
# context['status_filter'] = self.request.GET.get('status', '')
|
|
# context['sort_by'] = self.request.GET.get('sort', 'ward__name,room_number,bed_number')
|
|
#
|
|
# return context
|
|
#
|
|
#
|
|
# class BedDetailView(LoginRequiredMixin, DetailView):
|
|
# """
|
|
# Detail view for a bed.
|
|
# """
|
|
# model = Bed
|
|
# template_name = 'inpatients/bed_detail.html'
|
|
# context_object_name = 'bed'
|
|
#
|
|
# def get_queryset(self):
|
|
# """Filter beds by tenant."""
|
|
# return Bed.objects.filter(
|
|
# ward__tenant=self.request.user.tenant
|
|
# ).select_related(
|
|
# 'ward', 'current_patient', 'current_admission',
|
|
# 'cleaned_by', 'blocked_by', 'created_by'
|
|
# )
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# bed = self.get_object()
|
|
#
|
|
# # Get bed history - admissions that used this bed
|
|
# context['admission_history'] = Admission.objects.filter(
|
|
# Q(initial_bed=bed) | Q(current_bed=bed)
|
|
# ).select_related(
|
|
# 'patient', 'admitting_physician'
|
|
# ).order_by('-admitted_at')[:10]
|
|
#
|
|
# # Get maintenance history if available
|
|
# # This would require a model to track maintenance events
|
|
#
|
|
# return context
|
|
#
|
|
#
|
|
# class BedCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
# """
|
|
# Create view for a bed.
|
|
# """
|
|
# model = Bed
|
|
# form_class = BedForm
|
|
# template_name = 'inpatients/bed_form.html'
|
|
# permission_required = 'inpatients.add_bed'
|
|
#
|
|
# def get_form_kwargs(self):
|
|
# kwargs = super().get_form_kwargs()
|
|
# kwargs['user'] = self.request.user
|
|
# return kwargs
|
|
#
|
|
# def form_valid(self, form):
|
|
# form.instance.created_by = self.request.user
|
|
# messages.success(self.request, _('Bed created successfully'))
|
|
# return super().form_valid(form)
|
|
#
|
|
# def get_success_url(self):
|
|
# return reverse('inpatients:bed_detail', kwargs={'pk': self.object.pk})
|
|
#
|
|
#
|
|
# class BedUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
# """
|
|
# Update view for a bed.
|
|
# """
|
|
# model = Bed
|
|
# form_class = BedForm
|
|
# template_name = 'inpatients/bed_form.html'
|
|
# permission_required = 'inpatients.change_bed'
|
|
#
|
|
# def get_queryset(self):
|
|
# """Filter beds by tenant."""
|
|
# return Bed.objects.filter(ward__tenant=self.request.user.tenant)
|
|
#
|
|
# def get_form_kwargs(self):
|
|
# kwargs = super().get_form_kwargs()
|
|
# kwargs['user'] = self.request.user
|
|
# return kwargs
|
|
#
|
|
# def form_valid(self, form):
|
|
# messages.success(self.request, _('Bed updated successfully'))
|
|
# return super().form_valid(form)
|
|
#
|
|
# def get_success_url(self):
|
|
# return reverse('inpatients:bed_detail', kwargs={'pk': self.object.pk})
|
|
#
|
|
#
|
|
# class BedDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
|
|
# """
|
|
# Delete view for a bed.
|
|
# """
|
|
# model = Bed
|
|
# template_name = 'inpatients/bed_confirm_delete.html'
|
|
# permission_required = 'inpatients.delete_bed'
|
|
#
|
|
# def get_queryset(self):
|
|
# """Filter beds by tenant."""
|
|
# return Bed.objects.filter(ward__tenant=self.request.user.tenant)
|
|
#
|
|
# def delete(self, request, *args, **kwargs):
|
|
# bed = self.get_object()
|
|
#
|
|
# # Check if the bed is occupied
|
|
# if bed.status == 'OCCUPIED':
|
|
# messages.error(request, _('Cannot delete an occupied bed.'))
|
|
# return redirect('inpatients:bed_detail', pk=bed.pk)
|
|
#
|
|
# messages.success(request, _('Bed deleted successfully'))
|
|
# return super().delete(request, *args, **kwargs)
|
|
#
|
|
# def get_success_url(self):
|
|
# return reverse('inpatients:ward_detail', kwargs={'pk': self.object.ward.pk})
|
|
#
|
|
#
|
|
# class AdmissionListView(LoginRequiredMixin, ListView):
|
|
# """
|
|
# List view for admissions.
|
|
# """
|
|
# model = Admission
|
|
# template_name = 'inpatients/admission_list.html'
|
|
# context_object_name = 'admissions'
|
|
# paginate_by = 20
|
|
#
|
|
# def get_queryset(self):
|
|
# """Filter admissions by tenant and search query."""
|
|
# queryset = Admission.objects.filter(
|
|
# tenant=self.request.user.tenant
|
|
# ).select_related(
|
|
# 'patient', 'admitting_physician', 'initial_ward', 'initial_bed'
|
|
# )
|
|
#
|
|
# # Handle search query
|
|
# search_query = self.request.GET.get('search', '')
|
|
# if search_query:
|
|
# queryset = queryset.filter(
|
|
# Q(admission_number__icontains=search_query) |
|
|
# Q(patient__first_name__icontains=search_query) |
|
|
# Q(patient__last_name__icontains=search_query) |
|
|
# Q(patient__id_number__icontains=search_query) |
|
|
# Q(admitting_diagnosis__icontains=search_query)
|
|
# )
|
|
#
|
|
# # Handle filter by status
|
|
# status = self.request.GET.get('status', '')
|
|
# if status:
|
|
# queryset = queryset.filter(status=status)
|
|
#
|
|
# # Handle filter by admission_type
|
|
# admission_type = self.request.GET.get('admission_type', '')
|
|
# if admission_type:
|
|
# queryset = queryset.filter(admission_type=admission_type)
|
|
#
|
|
# # Handle filter by priority
|
|
# priority = self.request.GET.get('priority', '')
|
|
# if priority:
|
|
# queryset = queryset.filter(priority=priority)
|
|
#
|
|
# # Handle filter by ward
|
|
# ward_id = self.request.GET.get('ward', '')
|
|
# if ward_id:
|
|
# # This requires a subquery to get current ward
|
|
# queryset = queryset.filter(
|
|
# Q(initial_ward_id=ward_id) | Q(current_bed__ward_id=ward_id)
|
|
# )
|
|
#
|
|
# # Handle filter by admitting physician
|
|
# physician_id = self.request.GET.get('physician', '')
|
|
# if physician_id:
|
|
# queryset = queryset.filter(
|
|
# Q(admitting_physician_id=physician_id) | Q(attending_physician_id=physician_id)
|
|
# )
|
|
#
|
|
# # Handle filter by date range
|
|
# date_from = self.request.GET.get('date_from', '')
|
|
# if date_from:
|
|
# queryset = queryset.filter(admitted_at__gte=date_from)
|
|
#
|
|
# date_to = self.request.GET.get('date_to', '')
|
|
# if date_to:
|
|
# queryset = queryset.filter(admitted_at__lte=date_to)
|
|
#
|
|
# # Handle sort
|
|
# sort_by = self.request.GET.get('sort', '-admitted_at')
|
|
# queryset = queryset.order_by(sort_by)
|
|
#
|
|
# return queryset
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# tenant = self.request.user.tenant
|
|
#
|
|
# # Get statuses for filter dropdown
|
|
# context['statuses'] = Admission.STATUS_CHOICES
|
|
#
|
|
# # Get admission types for filter dropdown
|
|
# context['admission_types'] = Admission.ADMISSION_TYPE_CHOICES
|
|
#
|
|
# # Get priorities for filter dropdown
|
|
# context['priorities'] = Admission.PRIORITY_CHOICES
|
|
#
|
|
# # Get wards for filter dropdown
|
|
# context['wards'] = Ward.objects.filter(
|
|
# tenant=tenant,
|
|
# is_active=True
|
|
# ).order_by('name')
|
|
#
|
|
# # Get physicians for filter dropdown
|
|
# context['physicians'] = User.objects.filter(
|
|
# tenant=tenant,
|
|
# is_active=True,
|
|
# role__in=['DOCTOR', 'SPECIALIST']
|
|
# ).order_by('last_name', 'first_name')
|
|
#
|
|
# # Add search query to context
|
|
# context['search_query'] = self.request.GET.get('search', '')
|
|
# context['status_filter'] = self.request.GET.get('status', '')
|
|
# context['admission_type_filter'] = self.request.GET.get('admission_type', '')
|
|
# context['priority_filter'] = self.request.GET.get('priority', '')
|
|
# context['ward_filter'] = self.request.GET.get('ward', '')
|
|
# context['physician_filter'] = self.request.GET.get('physician', '')
|
|
# context['date_from'] = self.request.GET.get('date_from', '')
|
|
# context['date_to'] = self.request.GET.get('date_to', '')
|
|
# context['sort_by'] = self.request.GET.get('sort', '-admitted_at')
|
|
#
|
|
# return context
|
|
#
|
|
#
|
|
# class AdmissionDetailView(LoginRequiredMixin, DetailView):
|
|
# """
|
|
# Detail view for an admission.
|
|
# """
|
|
# model = Admission
|
|
# template_name = 'inpatients/admission_detail.html'
|
|
# context_object_name = 'admission'
|
|
#
|
|
# def get_queryset(self):
|
|
# """Filter admissions by tenant."""
|
|
# return Admission.objects.filter(
|
|
# tenant=self.request.user.tenant
|
|
# ).select_related(
|
|
# 'patient', 'admitting_physician', 'attending_physician',
|
|
# 'initial_ward', 'initial_bed', 'discharge_summary',
|
|
# 'created_by'
|
|
# )
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# admission = self.get_object()
|
|
#
|
|
# # Get transfers for this admission
|
|
# context['transfers'] = Transfer.objects.filter(
|
|
# admission=admission
|
|
# ).select_related(
|
|
# 'from_ward', 'to_ward', 'from_bed', 'to_bed',
|
|
# 'requested_by', 'approved_by'
|
|
# ).order_by('-requested_at')
|
|
#
|
|
# # Get scheduled surgeries for this admission
|
|
# context['surgeries'] = SurgerySchedule.objects.filter(
|
|
# admission=admission
|
|
# ).select_related(
|
|
# 'surgeon', 'anesthesiologist', 'operating_room'
|
|
# ).order_by('scheduled_date', 'scheduled_time')
|
|
#
|
|
# # Get current bed if any
|
|
# if hasattr(admission, 'current_bed'):
|
|
# context['current_bed'] = admission.current_bed
|
|
# context['current_ward'] = admission.current_bed.ward
|
|
# else:
|
|
# # Fall back to initial bed/ward
|
|
# context['current_bed'] = admission.initial_bed
|
|
# context['current_ward'] = admission.initial_ward
|
|
#
|
|
# # Check if a discharge summary exists
|
|
# context['has_discharge_summary'] = hasattr(admission, 'discharge_summary')
|
|
#
|
|
# return context
|
|
#
|
|
#
|
|
|
|
|
|
|
|
@login_required
|
|
@permission_required('inpatients.change_admission')
|
|
def discharge_patient(request, admission_id):
|
|
print("function is right")
|
|
print(admission_id)
|
|
"""
|
|
View to discharge a patient.
|
|
"""
|
|
admission = get_object_or_404(
|
|
Admission,
|
|
pk=admission_id,
|
|
tenant=request.user.tenant
|
|
)
|
|
print(admission.status)
|
|
|
|
# Only admitted patients can be discharged
|
|
if not admission.status == 'ADMITTED':
|
|
messages.error(request, _('Only admitted patients or patients ready for discharge can be discharged'))
|
|
return redirect('inpatients:admission_detail', pk=admission.pk)
|
|
|
|
if request.method == 'POST':
|
|
summary_form = DischargeSummaryForm(
|
|
request.POST,
|
|
user=request.user,
|
|
admission=admission
|
|
)
|
|
|
|
if summary_form.is_valid():
|
|
summary = summary_form.save(commit=False)
|
|
summary.patient = admission.patient
|
|
summary.admission = admission
|
|
summary.created_by = request.user
|
|
summary.save()
|
|
|
|
# Link summary to admission and discharge
|
|
admission.discharge_summary = summary
|
|
# admission.status = 'DISCHARGED'
|
|
|
|
messages.success(request, _('Patient discharged successfully'))
|
|
return redirect('inpatients:admission_detail', pk=admission.pk)
|
|
else:
|
|
initial = {
|
|
'patient': admission.patient,
|
|
'admission': admission,
|
|
'discharge_diagnosis': admission.admitting_diagnosis,
|
|
'doctor_name': request.user_employee_profile.get_full_name() if request.user_employee_profile.role in ['DOCTOR', 'SPECIALIST'] else ''
|
|
}
|
|
summary_form = DischargeSummaryForm(
|
|
initial=initial,
|
|
user=request.user,
|
|
# admission=admission
|
|
)
|
|
|
|
return render(request, 'inpatients/discharges/discharge_planning.html', {
|
|
'admission': admission,
|
|
'form': summary_form
|
|
})
|
|
|
|
|
|
@login_required
|
|
@permission_required('inpatients.change_admission')
|
|
def mark_ready_for_discharge(request, pk):
|
|
"""
|
|
Mark a patient as ready for discharge.
|
|
"""
|
|
admission = get_object_or_404(
|
|
Admission,
|
|
pk=pk,
|
|
tenant=request.user.tenant
|
|
)
|
|
|
|
# Only admitted patients can be marked ready for discharge
|
|
if admission.status != 'ADMITTED':
|
|
messages.error(request, _('Only admitted patients can be marked ready for discharge'))
|
|
return redirect('inpatients:admission_detail', pk=admission.pk)
|
|
|
|
admission.mark_ready_for_discharge()
|
|
messages.success(request, _('Patient marked ready for discharge'))
|
|
return redirect('inpatients:admission_detail', pk=admission.pk)
|
|
|
|
|
|
class TransferListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List view for transfers.
|
|
"""
|
|
model = Transfer
|
|
template_name = 'inpatients/transfer_list.html'
|
|
context_object_name = 'transfers'
|
|
paginate_by = 20
|
|
|
|
def get_queryset(self):
|
|
"""Filter transfers by tenant and search query."""
|
|
queryset = Transfer.objects.filter(
|
|
admission__tenant=self.request.user.tenant
|
|
).select_related(
|
|
'patient', 'admission', 'from_ward', 'to_ward',
|
|
'from_bed', 'to_bed', 'requested_by', 'approved_by'
|
|
)
|
|
|
|
# Handle search query
|
|
search_query = self.request.GET.get('search', '')
|
|
if search_query:
|
|
queryset = queryset.filter(
|
|
Q(transfer_number__icontains=search_query) |
|
|
Q(patient__first_name__icontains=search_query) |
|
|
Q(patient__last_name__icontains=search_query) |
|
|
Q(admission__admission_number__icontains=search_query) |
|
|
Q(reason__icontains=search_query)
|
|
)
|
|
|
|
# Handle filter by status
|
|
status = self.request.GET.get('status', '')
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
|
|
# Handle filter by transfer_type
|
|
transfer_type = self.request.GET.get('transfer_type', '')
|
|
if transfer_type:
|
|
queryset = queryset.filter(transfer_type=transfer_type)
|
|
|
|
# Handle filter by priority
|
|
priority = self.request.GET.get('priority', '')
|
|
if priority:
|
|
queryset = queryset.filter(priority=priority)
|
|
|
|
# Handle filter by from_ward
|
|
from_ward = self.request.GET.get('from_ward', '')
|
|
if from_ward:
|
|
queryset = queryset.filter(from_ward_id=from_ward)
|
|
|
|
# Handle filter by to_ward
|
|
to_ward = self.request.GET.get('to_ward', '')
|
|
if to_ward:
|
|
queryset = queryset.filter(to_ward_id=to_ward)
|
|
|
|
# Handle filter by date range
|
|
date_from = self.request.GET.get('date_from', '')
|
|
if date_from:
|
|
queryset = queryset.filter(requested_at__gte=date_from)
|
|
|
|
date_to = self.request.GET.get('date_to', '')
|
|
if date_to:
|
|
queryset = queryset.filter(requested_at__lte=date_to)
|
|
|
|
# Handle sort
|
|
sort_by = self.request.GET.get('sort', '-requested_at')
|
|
queryset = queryset.order_by(sort_by)
|
|
|
|
return queryset
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
tenant = self.request.user.tenant
|
|
|
|
# Get statuses for filter dropdown
|
|
context['statuses'] = Transfer.STATUS_CHOICES
|
|
|
|
# Get transfer types for filter dropdown
|
|
context['transfer_types'] = Transfer.TRANSFER_TYPE_CHOICES
|
|
|
|
# Get priorities for filter dropdown
|
|
context['priorities'] = Transfer.PRIORITY_CHOICES
|
|
|
|
# Get wards for filter dropdown
|
|
context['wards'] = Ward.objects.filter(
|
|
tenant=tenant,
|
|
is_active=True
|
|
).order_by('name')
|
|
|
|
# Add search query to context
|
|
context['search_query'] = self.request.GET.get('search', '')
|
|
context['status_filter'] = self.request.GET.get('status', '')
|
|
context['transfer_type_filter'] = self.request.GET.get('transfer_type', '')
|
|
context['priority_filter'] = self.request.GET.get('priority', '')
|
|
context['from_ward_filter'] = self.request.GET.get('from_ward', '')
|
|
context['to_ward_filter'] = self.request.GET.get('to_ward', '')
|
|
context['date_from'] = self.request.GET.get('date_from', '')
|
|
context['date_to'] = self.request.GET.get('date_to', '')
|
|
context['sort_by'] = self.request.GET.get('sort', '-requested_at')
|
|
|
|
return context
|
|
|
|
|
|
class TransferDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Detail view for a transfer.
|
|
"""
|
|
model = Transfer
|
|
template_name = 'inpatients/transfer_detail.html'
|
|
context_object_name = 'transfer'
|
|
|
|
def get_queryset(self):
|
|
"""Filter transfers by tenant."""
|
|
return Transfer.objects.filter(
|
|
admission__tenant=self.request.user.tenant
|
|
).select_related(
|
|
'patient', 'admission', 'from_ward', 'to_ward',
|
|
'from_bed', 'to_bed', 'requested_by', 'approved_by'
|
|
)
|
|
|
|
|
|
class TransferCreateView(LoginRequiredMixin, CreateView):
|
|
"""
|
|
Create view for a transfer.
|
|
"""
|
|
model = Transfer
|
|
form_class = TransferForm
|
|
template_name = 'inpatients/transfer_form.html'
|
|
permission_required = 'inpatients.add_transfer'
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['user'] = self.request.user
|
|
|
|
# If we have an admission ID in the URL, pre-fill the form
|
|
admission_id = self.kwargs.get('admission_id')
|
|
if admission_id:
|
|
try:
|
|
admission = Admission.objects.get(
|
|
pk=admission_id,
|
|
tenant=self.request.user.tenant,
|
|
status='ADMITTED'
|
|
)
|
|
|
|
# Determine current bed and ward
|
|
if hasattr(admission, 'current_bed') and admission.current_bed:
|
|
current_bed = admission.current_bed
|
|
current_ward = current_bed.ward
|
|
else:
|
|
current_bed = admission.initial_bed
|
|
current_ward = admission.initial_ward
|
|
|
|
kwargs['initial'] = {
|
|
'admission': admission,
|
|
'patient': admission.patient,
|
|
'from_bed': current_bed,
|
|
'from_ward': current_ward
|
|
}
|
|
except Admission.DoesNotExist:
|
|
pass
|
|
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
form.instance.requested_by = self.request.user
|
|
messages.success(self.request, _('Transfer request created successfully'))
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse('inpatients:transfer_detail', kwargs={'pk': self.object.pk})
|
|
|
|
|
|
class TransferUpdateView(LoginRequiredMixin, UpdateView):
|
|
"""
|
|
Update view for a transfer.
|
|
"""
|
|
model = Transfer
|
|
form_class = TransferForm
|
|
template_name = 'inpatients/transfer_form.html'
|
|
permission_required = 'inpatients.change_transfer'
|
|
|
|
def get_queryset(self):
|
|
"""Filter transfers by tenant."""
|
|
return Transfer.objects.filter(admission__tenant=self.request.user.tenant)
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['user'] = self.request.user
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
messages.success(self.request, _('Transfer updated successfully'))
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse('inpatients:transfer_detail', kwargs={'pk': self.object.pk})
|
|
|
|
#
|
|
# @login_required
|
|
# @permission_required('inpatients.change_transfer')
|
|
# def approve_transfer(request, pk):
|
|
# """
|
|
# Approve a transfer request.
|
|
# """
|
|
# transfer = get_object_or_404(
|
|
# Transfer,
|
|
# pk=pk,
|
|
# admission__tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# # Only requested transfers can be approved
|
|
# if transfer.status != 'REQUESTED':
|
|
# messages.error(request, _('Only requested transfers can be approved'))
|
|
# return redirect('inpatients:transfer_detail', pk=transfer.pk)
|
|
#
|
|
# if request.method == 'POST':
|
|
# scheduled_time = request.POST.get('scheduled_time')
|
|
#
|
|
# try:
|
|
# transfer.approve(request.user, scheduled_time)
|
|
# messages.success(request, _('Transfer approved successfully'))
|
|
# except ValueError as e:
|
|
# messages.error(request, str(e))
|
|
#
|
|
# return redirect('inpatients:transfer_detail', pk=transfer.pk)
|
|
#
|
|
# return render(request, 'inpatients/approve_transfer.html', {
|
|
# 'transfer': transfer
|
|
# })
|
|
#
|
|
#
|
|
# @login_required
|
|
# @permission_required('inpatients.change_transfer')
|
|
# def complete_transfer(request, pk):
|
|
# """
|
|
# Complete a transfer.
|
|
# """
|
|
# transfer = get_object_or_404(
|
|
# Transfer,
|
|
# pk=pk,
|
|
# admission__tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# # Only approved or scheduled transfers can be completed
|
|
# if transfer.status not in ['APPROVED', 'SCHEDULED', 'IN_PROGRESS']:
|
|
# messages.error(request, _('Only approved, scheduled, or in-progress transfers can be completed'))
|
|
# return redirect('inpatients:transfer_detail', pk=transfer.pk)
|
|
#
|
|
# try:
|
|
# transfer.complete()
|
|
# messages.success(request, _('Transfer completed successfully'))
|
|
# except ValueError as e:
|
|
# messages.error(request, str(e))
|
|
#
|
|
# return redirect('inpatients:transfer_detail', pk=transfer.pk)
|
|
#
|
|
#
|
|
@login_required
|
|
# @permission_required('inpatients.change_transfer')
|
|
def cancel_transfer(request, pk):
|
|
"""
|
|
Cancel a transfer.
|
|
"""
|
|
transfer = get_object_or_404(
|
|
Transfer,
|
|
pk=pk,
|
|
admission__tenant=request.user.tenant
|
|
)
|
|
|
|
# Cannot cancel completed or already cancelled transfers
|
|
if transfer.status in ['COMPLETED', 'CANCELLED']:
|
|
messages.error(request, _('Cannot cancel a completed or already cancelled transfer'))
|
|
return redirect('inpatients:transfer_detail', pk=transfer.pk)
|
|
|
|
if request.method == 'POST':
|
|
reason = request.POST.get('reason')
|
|
|
|
transfer.status = 'CANCELLED'
|
|
if reason:
|
|
transfer.notes = (
|
|
transfer.notes or "") + f"\n\nCancellation Reason ({timezone.now().strftime('%Y-%m-%d %H:%M')}):\n{reason}"
|
|
transfer.save()
|
|
|
|
messages.success(request, _('Transfer cancelled successfully'))
|
|
return redirect('inpatients:transfer_detail', pk=transfer.pk)
|
|
|
|
return render(request, 'inpatients/transfers/cancel_transfer.html', {
|
|
'transfer': transfer
|
|
})
|
|
|
|
|
|
@login_required
|
|
# @permission_required('inpatients.change_surgeryschedule')
|
|
def mark_surgery_completed(request, pk):
|
|
"""
|
|
Mark a surgery as completed.
|
|
"""
|
|
surgery = get_object_or_404(
|
|
SurgerySchedule,
|
|
pk=pk,
|
|
admission__tenant=request.user.tenant
|
|
)
|
|
|
|
# Only scheduled, confirmed, or in-progress surgeries can be completed
|
|
if surgery.status not in ['SCHEDULED', 'CONFIRMED', 'IN_PROGRESS']:
|
|
messages.error(request, _('Only scheduled, confirmed, or in-progress surgeries can be marked as completed'))
|
|
return redirect('inpatients:surgery_detail', pk=surgery.pk)
|
|
|
|
if request.method == 'POST':
|
|
notes = request.POST.get('notes')
|
|
|
|
try:
|
|
surgery.mark_completed(notes)
|
|
messages.success(request, _('Surgery marked as completed successfully'))
|
|
except ValueError as e:
|
|
messages.error(request, str(e))
|
|
|
|
return redirect('inpatients:surgery_detail', pk=surgery.pk)
|
|
|
|
return render(request, 'inpatients/complete_surgery.html', {
|
|
'surgery': surgery
|
|
})
|
|
|
|
|
|
@login_required
|
|
# @permission_required('inpatients.change_surgeryschedule')
|
|
def reschedule_surgery(request, pk):
|
|
"""
|
|
Reschedule a surgery.
|
|
"""
|
|
surgery = get_object_or_404(
|
|
SurgerySchedule,
|
|
pk=pk,
|
|
admission__tenant=request.user.tenant
|
|
)
|
|
|
|
# Cannot reschedule completed or cancelled surgeries
|
|
if surgery.status in ['COMPLETED', 'CANCELLED']:
|
|
messages.error(request, _('Cannot reschedule completed or cancelled surgeries'))
|
|
return redirect('inpatients:surgery_detail', pk=surgery.pk)
|
|
|
|
if request.method == 'POST':
|
|
new_date = request.POST.get('scheduled_date')
|
|
new_time = request.POST.get('scheduled_time')
|
|
reason = request.POST.get('reason')
|
|
|
|
try:
|
|
surgery.reschedule(new_date, new_time, reason)
|
|
messages.success(request, _('Surgery rescheduled successfully'))
|
|
except ValueError as e:
|
|
messages.error(request, str(e))
|
|
|
|
return redirect('inpatients:surgery_detail', pk=surgery.pk)
|
|
|
|
return render(request, 'inpatients/reschedule_surgery.html', {
|
|
'surgery': surgery
|
|
})
|
|
|
|
|
|
@login_required
|
|
# @permission_required('inpatients.change_surgeryschedule')
|
|
def cancel_surgery(request, pk):
|
|
"""
|
|
Cancel a surgery.
|
|
"""
|
|
surgery = get_object_or_404(
|
|
SurgerySchedule,
|
|
pk=pk,
|
|
admission__tenant=request.user.tenant
|
|
)
|
|
|
|
# Cannot cancel completed or already cancelled surgeries
|
|
if surgery.status in ['COMPLETED', 'CANCELLED']:
|
|
messages.error(request, _('Cannot cancel a completed or already cancelled surgery'))
|
|
return redirect('inpatients:surgery_detail', pk=surgery.pk)
|
|
|
|
if request.method == 'POST':
|
|
reason = request.POST.get('reason')
|
|
|
|
surgery.status = 'CANCELLED'
|
|
if reason:
|
|
surgery.notes = (surgery.notes or "") + f"\n\nCancellation Reason ({timezone.now().strftime('%Y-%m-%d %H:%M')}):\n{reason}"
|
|
|
|
surgery.save()
|
|
|
|
messages.success(request, _('Surgery cancelled successfully'))
|
|
return redirect('inpatients:surgery_list')
|
|
|
|
return render(request, 'inpatients/surgeries/surgery_schedule.html', {
|
|
'surgery': surgery
|
|
})
|
|
#
|
|
#
|
|
# @login_required
|
|
# def get_available_beds(request):
|
|
# """
|
|
# AJAX view to get available beds for a ward.
|
|
# """
|
|
# ward_id = request.GET.get('ward_id')
|
|
#
|
|
# if not ward_id:
|
|
# return JsonResponse({'error': 'Ward ID is required'}, status=400)
|
|
#
|
|
# try:
|
|
# ward = Ward.objects.get(
|
|
# pk=ward_id,
|
|
# tenant=request.user.tenant,
|
|
# is_active=True
|
|
# )
|
|
# except Ward.DoesNotExist:
|
|
# return JsonResponse({'error': 'Ward not found'}, status=404)
|
|
#
|
|
# # Get available beds for this ward
|
|
# beds = Bed.objects.filter(
|
|
# ward=ward,
|
|
# status='AVAILABLE'
|
|
# ).order_by('room_number', 'bed_number')
|
|
#
|
|
# beds_data = [
|
|
# {
|
|
# 'id': bed.id,
|
|
# 'name': f"Room {bed.room_number}, Bed {bed.bed_number}",
|
|
# 'bed_type': bed.get_bed_type_display(),
|
|
# 'room_type': bed.get_room_type_display()
|
|
# }
|
|
# for bed in beds
|
|
# ]
|
|
#
|
|
# return JsonResponse({'beds': beds_data})
|
|
#
|
|
#
|
|
# @login_required
|
|
# def bed_status_board(request):
|
|
# """
|
|
# View for the bed status board.
|
|
# """
|
|
# tenant = request.user.tenant
|
|
#
|
|
# # Get all wards for this tenant
|
|
# wards = Ward.objects.filter(
|
|
# tenant=tenant,
|
|
# is_active=True
|
|
# ).order_by('name')
|
|
#
|
|
# # Build ward and bed data
|
|
# ward_data = []
|
|
#
|
|
# for ward in wards:
|
|
# beds = Bed.objects.filter(ward=ward).order_by('room_number', 'bed_number')
|
|
#
|
|
# # Group beds by room
|
|
# rooms = {}
|
|
# for bed in beds:
|
|
# room_num = bed.room_number
|
|
# if room_num not in rooms:
|
|
# rooms[room_num] = []
|
|
# rooms[room_num].append(bed)
|
|
#
|
|
# # Sort rooms by room number
|
|
# sorted_rooms = sorted(rooms.items())
|
|
#
|
|
# ward_data.append({
|
|
# 'ward': ward,
|
|
# 'rooms': sorted_rooms,
|
|
# 'total_beds': beds.count(),
|
|
# 'available_beds': beds.filter(status='AVAILABLE').count(),
|
|
# 'occupied_beds': beds.filter(status='OCCUPIED').count()
|
|
# })
|
|
#
|
|
# return render(request, 'inpatients/bed_status_board.html', {
|
|
# 'ward_data': ward_data
|
|
# })
|
|
#
|
|
#
|
|
# @login_required
|
|
# def clean_bed(request, pk):
|
|
# """
|
|
# Mark a bed as cleaned.
|
|
# """
|
|
# bed = get_object_or_404(
|
|
# Bed,
|
|
# pk=pk,
|
|
# ward__tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# # Only beds with status CLEANING can be marked as cleaned
|
|
# if bed.status != 'CLEANING':
|
|
# messages.error(request, _('Only beds with status "Being Cleaned" can be marked as cleaned'))
|
|
# return redirect('inpatients:bed_detail', pk=bed.pk)
|
|
#
|
|
# if request.method == 'POST':
|
|
# cleaning_level = request.POST.get('cleaning_level', 'ROUTINE')
|
|
#
|
|
# bed.mark_cleaned(request.user, cleaning_level)
|
|
# messages.success(request, _('Bed marked as cleaned successfully'))
|
|
#
|
|
# # Redirect to referring page if available
|
|
# return redirect(request.POST.get('next', 'inpatients:bed_detail'), pk=bed.pk)
|
|
#
|
|
# return render(request, 'inpatients/clean_bed.html', {
|
|
# 'bed': bed,
|
|
# 'cleaning_levels': Bed.CLEANING_LEVEL_CHOICES,
|
|
# 'next': request.GET.get('next', reverse('inpatients:bed_detail', kwargs={'pk': bed.pk}))
|
|
# })
|
|
#
|
|
#
|
|
# @login_required
|
|
# @permission_required('inpatients.change_bed')
|
|
# def block_bed(request, pk):
|
|
# """
|
|
# Block a bed from being used.
|
|
# """
|
|
# bed = get_object_or_404(
|
|
# Bed,
|
|
# pk=pk,
|
|
# ward__tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# # Only available beds can be blocked
|
|
# if bed.status != 'AVAILABLE':
|
|
# messages.error(request, _('Only available beds can be blocked'))
|
|
# return redirect('inpatients:bed_detail', pk=bed.pk)
|
|
#
|
|
# if request.method == 'POST':
|
|
# reason = request.POST.get('reason')
|
|
# blocked_until = request.POST.get('blocked_until')
|
|
#
|
|
# if not reason:
|
|
# messages.error(request, _('Reason is required'))
|
|
# return redirect('inpatients:block_bed', pk=bed.pk)
|
|
#
|
|
# bed.status = 'BLOCKED'
|
|
# bed.blocked_reason = reason
|
|
# bed.blocked_by = request.user
|
|
#
|
|
# if blocked_until:
|
|
# try:
|
|
# bed.blocked_until = timezone.datetime.fromisoformat(blocked_until)
|
|
# except ValueError:
|
|
# messages.error(request, _('Invalid date format'))
|
|
# return redirect('inpatients:block_bed', pk=bed.pk)
|
|
#
|
|
# bed.save()
|
|
#
|
|
# messages.success(request, _('Bed blocked successfully'))
|
|
# return redirect('inpatients:bed_detail', pk=bed.pk)
|
|
#
|
|
# return render(request, 'inpatients/block_bed.html', {
|
|
# 'bed': bed
|
|
# })
|
|
#
|
|
#
|
|
# @login_required
|
|
# @permission_required('inpatients.change_bed')
|
|
# def unblock_bed(request, pk):
|
|
# """
|
|
# Unblock a bed.
|
|
# """
|
|
# bed = get_object_or_404(
|
|
# Bed,
|
|
# pk=pk,
|
|
# ward__tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# # Only blocked beds can be unblocked
|
|
# if bed.status != 'BLOCKED':
|
|
# messages.error(request, _('Only blocked beds can be unblocked'))
|
|
# return redirect('inpatients:bed_detail', pk=bed.pk)
|
|
#
|
|
# bed.status = 'AVAILABLE'
|
|
# bed.blocked_reason = None
|
|
# bed.blocked_until = None
|
|
# bed.save()
|
|
#
|
|
# messages.success(request, _('Bed unblocked successfully'))
|
|
# return redirect('inpatients:bed_detail', pk=bed.pk)
|
|
#
|
|
#
|
|
# @login_required
|
|
# @permission_required('inpatients.change_bed')
|
|
# def maintenance_bed(request, pk):
|
|
# """
|
|
# Mark a bed for maintenance.
|
|
# """
|
|
# bed = get_object_or_404(
|
|
# Bed,
|
|
# pk=pk,
|
|
# ward__tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# # Only available beds can be marked for maintenance
|
|
# if bed.status != 'AVAILABLE':
|
|
# messages.error(request, _('Only available beds can be marked for maintenance'))
|
|
# return redirect('inpatients:bed_detail', pk=bed.pk)
|
|
#
|
|
# if request.method == 'POST':
|
|
# notes = request.POST.get('notes')
|
|
#
|
|
# # bed.mark_maintenance(notes)
|
|
# bed.status = 'MAINTENANCE'
|
|
# bed.notes = notes
|
|
# bed.save()
|
|
# messages.success(request, _('Bed marked for maintenance successfully'))
|
|
# return redirect('inpatients:bed_detail', pk=bed.pk)
|
|
#
|
|
# return render(request, 'inpatients/maintenance_bed.html', {
|
|
# 'bed': bed
|
|
# })
|
|
#
|
|
#
|
|
@login_required
|
|
def inpatient_stats(request):
|
|
"""
|
|
View for inpatient statistics.
|
|
"""
|
|
tenant = request.user.tenant
|
|
|
|
# Get date range for stats
|
|
date_from = request.GET.get('date_from')
|
|
date_to = request.GET.get('date_to')
|
|
|
|
# Default to last 30 days if not specified
|
|
if not date_from:
|
|
date_from = (timezone.now() - timezone.timedelta(days=30)).date().isoformat()
|
|
if not date_to:
|
|
date_to = timezone.now().date().isoformat()
|
|
|
|
# Query parameters
|
|
date_range_filter = Q(
|
|
admitted_at__date__gte=date_from,
|
|
admitted_at__date__lte=date_to
|
|
)
|
|
|
|
# Basic stats
|
|
total_admissions = Admission.objects.filter(
|
|
tenant=tenant,
|
|
**date_range_filter.children
|
|
).count()
|
|
|
|
avg_length_of_stay = Admission.objects.filter(
|
|
tenant=tenant,
|
|
status='DISCHARGED',
|
|
admitted_at__isnull=False,
|
|
discharged_at__isnull=False,
|
|
**date_range_filter.children
|
|
).aggregate(
|
|
avg_los=models.Avg(
|
|
ExpressionWrapper(
|
|
F('discharged_at') - F('admitted_at'),
|
|
output_field=fields.DurationField()
|
|
)
|
|
)
|
|
)['avg_los']
|
|
|
|
if avg_length_of_stay:
|
|
avg_length_of_stay = avg_length_of_stay.total_seconds() / (3600 * 24) # Convert to days
|
|
else:
|
|
avg_length_of_stay = 0
|
|
|
|
# Admissions by type
|
|
admissions_by_type = Admission.objects.filter(
|
|
tenant=tenant,
|
|
**date_range_filter.children
|
|
).values('admission_type').annotate(
|
|
count=Count('id')
|
|
).order_by('-count')
|
|
|
|
# Admissions by ward
|
|
admissions_by_ward = Admission.objects.filter(
|
|
tenant=tenant,
|
|
**date_range_filter.children
|
|
).values('initial_ward__name').annotate(
|
|
count=Count('id')
|
|
).order_by('-count')
|
|
|
|
# Bed occupancy over time
|
|
beds_timeline = []
|
|
|
|
# Occupancy by ward
|
|
occupancy_by_ward = []
|
|
wards = Ward.objects.filter(tenant=tenant, is_active=True)
|
|
|
|
for ward in wards:
|
|
total_beds = ward.beds.count()
|
|
occupied_beds = ward.beds.filter(status='OCCUPIED').count()
|
|
|
|
if total_beds > 0:
|
|
occupancy_rate = (occupied_beds / total_beds) * 100
|
|
else:
|
|
occupancy_rate = 0
|
|
|
|
occupancy_by_ward.append({
|
|
'ward': ward,
|
|
'total_beds': total_beds,
|
|
'occupied_beds': occupied_beds,
|
|
'occupancy_rate': occupancy_rate
|
|
})
|
|
|
|
return render(request, 'inpatients/inpatient_stats.html', {
|
|
'total_admissions': total_admissions,
|
|
'avg_length_of_stay': avg_length_of_stay,
|
|
'admissions_by_type': admissions_by_type,
|
|
'admissions_by_ward': admissions_by_ward,
|
|
'beds_timeline': beds_timeline,
|
|
'occupancy_by_ward': occupancy_by_ward,
|
|
'date_from': date_from,
|
|
'date_to': date_to
|
|
})
|
|
|