2025-10-06 15:25:37 +03:00

3046 lines
104 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, DurationField, Avg
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')[:10]
# 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')[:10]
# 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
).values_list(
'building', flat=True
).distinct()
context['floors'] = Ward.objects.filter(
tenant=tenant
).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_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()
context['reserved_beds'] = context['beds'].filter(status='RESERVED').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_admission')
# 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.BedType.choices
# Get room types for filter dropdown
context['room_types'] = Bed.RoomType.choices
# Get statuses for filter dropdown
context['statuses'] = Bed.BedStatus.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_admission',
'cleaned_by', 'blocked_by', 'created_by'
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
bed = self.get_object()
tenant = self.request.user.tenant
# Get bed history - admissions that used this bed
context['admission_history'] = Admission.objects.filter(
current_bed=bed).select_related(
'patient', 'admitting_physician',
).order_by('-admission_datetime')
# Get wards for transfer modal
context['wards'] = Ward.objects.filter(
tenant=tenant,
is_active=True
).exclude(pk=bed.ward.pk).order_by('name')
# Bed stats
admissions = Admission.objects.filter(current_bed=bed)
total_admissions = admissions.count()
avg_stay_days = 0
if total_admissions > 0:
avg_duration = admissions.aggregate(avg_duration=Avg(ExpressionWrapper(F('discharge_datetime') - F('admission_datetime'), output_field=DurationField())))['avg_duration']
avg_stay_days = avg_duration.total_seconds() / 86400 if avg_duration else 0
context['bed_stats'] = {
'occupancy_rate': 0, # Placeholder - would need more complex calculation
'total_admissions': total_admissions,
'avg_stay_days': avg_stay_days,
}
# Placeholder for maintenance schedule
context['maintenance_schedule'] = []
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_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.BedStatus.choices,
'bed_types': Bed.BedType.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.AdmissionStatus.choices,
'admission_types': Admission.AdmissionType.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
@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(),
'reserved_beds': Bed.objects.filter(ward__tenant=tenant, status='RESERVED').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_admission').order_by('room_number', 'bed_number')
else:
beds = Bed.objects.filter(
ward__tenant=request.user.tenant
).select_related('ward', '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 transfer_patient(request, admission_id):
"""
HTMX endpoint for patient transfer.
"""
tenant = request.user.tenant
if request.method == 'POST':
admission = get_object_or_404(Admission, id=admission_id, tenant=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=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=tenant)
wards = Ward.objects.filter(
tenant=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/transfers/transfer_form.html', context)
@login_required
def approve_transfer(request, transfer_id):
"""
HTMX endpoint for approving a transfer.
"""
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(
user=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 cancel_transfer(request, transfer_id):
"""
Cancel a transfer.
"""
transfer = get_object_or_404(
Transfer,
pk=transfer_id,
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_management')
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_management')
return render(request, 'inpatients/transfers/cancel_transfer.html', {
'transfer': transfer
})
@login_required
def reject_transfer(request, transfer_id):
"""
Cancel a transfer.
"""
transfer = get_object_or_404(
Transfer,
pk=transfer_id,
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_management')
if request.method == 'POST':
reason = request.POST.get('reason')
transfer.status = 'REJECTED'
if reason:
transfer.notes = (
transfer.notes or "") + f"\n\nRejection Reason ({timezone.now().strftime('%Y-%m-%d %H:%M')}):\n{reason}"
transfer.save()
messages.success(request, _('Transfer rejected successfully'))
return redirect('inpatients:transfer_management')
return render(request, 'inpatients/transfers/cancel_transfer.html', {
'transfer': transfer
})
@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/partials/update_bed_status_form.html', {
'ward_data': ward_data
})
@login_required
def bed_details(request, bed_id):
tenant = request.user.tenant
context = {}
bed = get_object_or_404(Bed, ward__tenant=tenant,id=bed_id)
# Bed stats
admissions = Admission.objects.filter(current_bed=bed)
total_admissions = admissions.count()
avg_stay_days = 0
if total_admissions > 0:
avg_duration = admissions.aggregate(avg_duration=Avg(ExpressionWrapper(F('discharge_datetime') - F('admission_datetime'), output_field=DurationField())))['avg_duration']
avg_stay_days = avg_duration.total_seconds() / 86400 if avg_duration else 0
context['bed_stats'] = {
'occupancy_rate': bed.occupancy_rate if hasattr(bed, 'occupancy_rate') else 0,
'total_admissions': total_admissions,
'avg_stay_days': avg_stay_days,
}
# Placeholder for maintenance schedule
# context['maintenance_schedule'] = []
return render(request, 'inpatients/partials/bed_details.html', {'object':bed,'context':context})
@login_required
def refresh_bed_history(request, bed_id):
"""
HTMX endpoint to refresh bed history.
"""
bed = get_object_or_404(Bed, id=bed_id, ward__tenant=request.user.tenant)
# Get bed history - admissions that used this bed
admission_history = Admission.objects.filter(
current_bed=bed
).select_related(
'patient', 'admitting_physician',
).order_by('-admission_datetime')
return render(request, 'inpatients/partials/bed_history.html', {
'admission_history': admission_history
})
@login_required
def assign_patient_to_bed(request, bed_id):
"""
View to assign a patient to a bed.
"""
bed = get_object_or_404(Bed, id=bed_id, ward__tenant=request.user.tenant)
if request.method == 'POST':
patient_id = request.POST.get('patient_id')
if patient_id:
patient = get_object_or_404(PatientProfile, id=patient_id, tenant=request.user.tenant)
bed.current_patient = patient
bed.status = 'OCCUPIED'
bed.occupied_since = timezone.now()
bed.save()
messages.success(request, 'Patient assigned to bed successfully.')
return redirect('inpatients:bed_detail', pk=bed.pk)
# GET: show form with patient search
patients = PatientProfile.objects.filter(tenant=request.user.tenant).order_by('-created_at')[:20]
return render(request, 'inpatients/assign_patient.html', {'bed': bed, 'patients': patients})
@login_required
def bed_utilization(request, bed_id):
"""
View for bed utilization chart data.
"""
bed = get_object_or_404(Bed, id=bed_id, ward__tenant=request.user.tenant)
# Generate last 30 days of data
end_date = timezone.now().date()
start_date = end_date - timedelta(days=29)
labels = []
data_points = []
current_date = start_date
while current_date <= end_date:
labels.append(current_date.strftime('%b %d'))
# Calculate occupancy for this date
# This is a simplified calculation - in a real system you'd track historical occupancy
# For now, we'll use a random-ish calculation based on bed admissions
admissions_on_date = Admission.objects.filter(
current_bed=bed,
admission_datetime__date=current_date
).count()
# Simple heuristic: if there were admissions, assume some occupancy
occupancy_rate = min(admissions_on_date * 20 + 30, 100) # Cap at 100%
data_points.append(occupancy_rate)
current_date += timedelta(days=1)
data = {
'labels': labels,
'data': data_points
}
return JsonResponse(data)
# HTMX Endpoints for Bed Management
@login_required
def htmx_bed_management_stats(request):
"""
HTMX endpoint for real-time bed management statistics.
"""
tenant = request.user.tenant
stats = {
'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(),
}
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/bed_stats.html', {'stats': stats})
@login_required
def htmx_filter_beds(request):
"""
HTMX endpoint for filtering beds based on criteria.
"""
tenant = request.user.tenant
# Get filter parameters
ward_id = request.GET.get('ward')
status = request.GET.get('status')
bed_type = request.GET.get('bed_type')
search = request.GET.get('search', '')
# Build queryset
queryset = Bed.objects.filter(ward__tenant=tenant)
if ward_id:
queryset = queryset.filter(ward_id=ward_id)
if status:
queryset = queryset.filter(status=status)
if bed_type:
queryset = queryset.filter(bed_type=bed_type)
if search:
queryset = queryset.filter(
Q(bed_number__icontains=search) |
Q(room_number__icontains=search) |
Q(current_admission__patient__first_name__icontains=search) |
Q(current_admission__patient__last_name__icontains=search)
)
beds = queryset.select_related(
'ward', 'current_admission__patient'
).order_by('ward__name', 'room_number', 'bed_number')
# Group beds by ward
wards_data = {}
for bed in beds:
ward_name = bed.ward.name
if ward_name not in wards_data:
wards_data[ward_name] = {
'ward': bed.ward,
'beds': []
}
wards_data[ward_name]['beds'].append(bed)
return render(request, 'inpatients/partials/filtered_beds.html', {
'wards_data': wards_data
})
@login_required
def htmx_bed_details_modal(request, bed_id):
"""
HTMX endpoint for bed details modal content.
"""
bed = get_object_or_404(Bed, id=bed_id, ward__tenant=request.user.tenant)
# Get bed history
admission_history = Admission.objects.filter(
current_bed=bed
).select_related(
'patient', 'admitting_physician'
).order_by('-admission_datetime')[:10]
# Calculate bed stats
total_admissions = admission_history.count()
avg_stay_days = 0
if total_admissions > 0:
avg_duration = admission_history.aggregate(
avg_duration=Avg(
ExpressionWrapper(
F('discharge_datetime') - F('admission_datetime'),
output_field=DurationField()
)
)
)['avg_duration']
avg_stay_days = avg_duration.total_seconds() / 86400 if avg_duration else 0
bed_stats = {
'total_admissions': total_admissions,
'avg_stay_days': avg_stay_days,
}
return render(request, 'inpatients/partials/bed_details_modal.html', {
'bed': bed,
'admission_history': admission_history,
'bed_stats': bed_stats
})
@login_required
def htmx_update_bed_status_form(request, bed_id):
"""
HTMX endpoint for bed status update form.
"""
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.BedStatus.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.BedStatus.choices)[old_status]} to {dict(Bed.BedStatus.choices)[new_status]}"
)
# Return success response with updated bed card
return render(request, 'inpatients/partials/bed_card.html', {'bed': bed})
else:
# Return error
return render(request, 'inpatients/partials/bed_status_form.html', {
'bed': bed,
'statuses': Bed.BedStatus.choices,
'error': 'Invalid bed status'
})
# GET request - show form
return render(request, 'inpatients/partials/bed_status_form.html', {
'bed': bed,
'statuses': Bed.BedStatus.choices
})
@login_required
def htmx_bulk_bed_actions(request):
"""
HTMX endpoint for bulk bed actions.
"""
if request.method == 'POST':
action = request.POST.get('action')
bed_ids = request.POST.getlist('bed_ids')
if not bed_ids:
return JsonResponse({'error': 'No beds selected'}, status=400)
beds = Bed.objects.filter(
id__in=bed_ids,
ward__tenant=request.user.tenant
)
if action == 'mark_cleaning':
beds.filter(status='AVAILABLE').update(status='CLEANING')
message = f'{beds.count()} beds marked for cleaning'
elif action == 'mark_available':
beds.filter(status__in=['CLEANING', 'MAINTENANCE']).update(
status='AVAILABLE',
last_cleaned=timezone.now(),
cleaned_by=request.user
)
message = f'{beds.count()} beds marked as available'
elif action == 'mark_maintenance':
beds.filter(status='AVAILABLE').update(
status='MAINTENANCE',
last_maintenance=timezone.now()
)
message = f'{beds.count()} beds marked for maintenance'
else:
return JsonResponse({'error': 'Invalid action'}, status=400)
return render(request, 'inpatients/partials/bulk_action_success.html', {
'message': message
})
return JsonResponse({'error': 'Method not allowed'}, status=405)
@login_required
def htmx_export_bed_data(request):
"""
HTMX endpoint for exporting bed data.
"""
tenant = request.user.tenant
format_type = request.GET.get('format', 'csv')
beds = Bed.objects.filter(ward__tenant=tenant).select_related(
'ward', 'current_admission__patient'
).order_by('ward__name', 'room_number', 'bed_number')
if format_type == 'csv':
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="bed_data.csv"'
import csv
writer = csv.writer(response)
writer.writerow([
'Ward', 'Room', 'Bed Number', 'Bed Type', 'Status',
'Current Patient', 'Occupied Since'
])
for bed in beds:
writer.writerow([
bed.ward.name,
bed.room_number,
bed.bed_number,
bed.get_bed_type_display(),
bed.get_status_display(),
bed.current_admission.patient.get_full_name() if bed.current_admission else '',
bed.occupied_since.strftime('%Y-%m-%d %H:%M') if bed.occupied_since else ''
])
return response
# For now, just return a success message for other formats
return render(request, 'inpatients/partials/export_success.html', {
'message': f'Bed data exported successfully in {format_type.upper()} format'
})
@login_required
def htmx_schedule_maintenance(request):
"""
HTMX endpoint for scheduling maintenance.
"""
if request.method == 'POST':
bed_ids = request.POST.getlist('bed_ids')
maintenance_type = request.POST.get('maintenance_type', 'ROUTINE')
scheduled_date = request.POST.get('scheduled_date')
notes = request.POST.get('notes', '')
if not bed_ids:
return render(request, 'inpatients/partials/maintenance_form.html', {
'error': 'No beds selected'
})
beds = Bed.objects.filter(
id__in=bed_ids,
ward__tenant=request.user.tenant,
status='AVAILABLE'
)
# Update beds to maintenance status
beds.update(
status='MAINTENANCE',
last_maintenance=timezone.now(),
notes=notes
)
# Here you would typically create maintenance records
# For now, we'll just return success
return render(request, 'inpatients/partials/maintenance_success.html', {
'message': f'Maintenance scheduled for {beds.count()} beds',
'scheduled_date': scheduled_date,
'maintenance_type': maintenance_type
})
# GET request - show form
return render(request, 'inpatients/partials/maintenance_form.html', {
'maintenance_types': [
('ROUTINE', 'Routine Maintenance'),
('DEEP_CLEAN', 'Deep Cleaning'),
('REPAIR', 'Repair'),
('INSPECTION', 'Inspection')
]
})
@login_required
def htmx_view_alerts(request):
"""
HTMX endpoint for viewing bed-related alerts.
"""
tenant = request.user.tenant
# Get beds that need attention
alerts = []
# Beds that have been in maintenance too long
maintenance_beds = Bed.objects.filter(
ward__tenant=tenant,
status='MAINTENANCE',
last_maintenance__lt=timezone.now() - timedelta(days=7)
)
for bed in maintenance_beds:
alerts.append({
'type': 'warning',
'message': f'Bed {bed.bed_number} in {bed.ward.name} has been in maintenance for over 7 days',
'bed': bed
})
# Beds that haven't been cleaned recently
dirty_beds = Bed.objects.filter(
ward__tenant=tenant,
status='AVAILABLE',
last_cleaned__lt=timezone.now() - timedelta(days=3)
)
for bed in dirty_beds:
alerts.append({
'type': 'info',
'message': f'Bed {bed.bed_number} in {bed.ward.name} may need cleaning',
'bed': bed
})
# High occupancy wards
wards = Ward.objects.filter(tenant=tenant, is_active=True)
for ward in wards:
try:
if ward.occupancy_rate > 90:
alerts.append({
'type': 'danger',
'message': f'{ward.name} is at {ward.occupancy_rate:.0f}% occupancy',
'ward': ward
})
except:
pass
return render(request, 'inpatients/partials/bed_alerts.html', {
'alerts': alerts
})
@login_required
def update_bed_status(request, bed_id):
"""
View to update 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.BedStatus.choices):
old_status = bed.status
bed.status = new_status
# Handle special cases
if 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.BedStatus.choices)[old_status]} to {dict(Bed.BedStatus.choices)[new_status]}"
)
messages.success(request, f"Bed status updated to {bed.get_status_display()}")
else:
messages.error(request, "Invalid bed status")
return redirect('inpatients:bed_detail', pk=bed.pk)
return redirect('inpatients:bed_detail', pk=bed.pk)
@login_required
def refresh_bed_history(request, bed_id):
"""
HTMX endpoint to refresh bed history.
"""
bed = get_object_or_404(Bed, id=bed_id, ward__tenant=request.user.tenant)
# Get bed history - admissions that used this bed
admission_history = Admission.objects.filter(
current_bed=bed
).select_related(
'patient', 'admitting_physician',
).order_by('-admission_datetime')
return render(request, 'inpatients/partials/bed_history.html', {
'admission_history': admission_history
})
@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.CleaningLevel.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
# 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/admissions/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',
'current_ward', 'current_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_datetime')
# Get scheduled surgeries for this admission
# context['surgeries'] = SurgerySchedule.objects.filter(
# admission=admission
# ).select_related(
# 'primary_surgeon', 'anesthesiologist',
# ).order_by('scheduled_date', 'scheduled_start_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
class DischargeSummaryListView(LoginRequiredMixin, ListView):
"""
List view for discharge summaries.
"""
model = DischargeSummary
template_name = 'inpatients/discharges/discharge_list.html'
context_object_name = 'discharge_summaries'
paginate_by = 20
def get_queryset(self):
"""Filter discharge summaries by tenant and search query."""
queryset = DischargeSummary.objects.filter(
admission__tenant=self.request.user.tenant
).select_related(
'admission', 'admission__patient', 'discharging_physician'
).order_by('-discharge_date')
# Handle search query
search_query = self.request.GET.get('search', '')
if search_query:
queryset = queryset.filter(
Q(admission__patient__first_name__icontains=search_query) |
Q(admission__patient__last_name__icontains=search_query) |
Q(admission__patient__mrn__icontains=search_query) |
Q(admission_diagnosis__icontains=search_query) |
Q(final_diagnosis__icontains=search_query)
)
# Handle filter by status (e.g., completed)
completed = self.request.GET.get('completed', '')
if completed == 'true':
queryset = queryset.filter(summary_completed=True)
elif completed == 'false':
queryset = queryset.filter(summary_completed=False)
# Handle filter by date range
date_from = self.request.GET.get('date_from', '')
if date_from:
queryset = queryset.filter(discharge_date__gte=date_from)
date_to = self.request.GET.get('date_to', '')
if date_to:
queryset = queryset.filter(discharge_date__lte=date_to)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'search_query': self.request.GET.get('search', ''),
'completed_filter': self.request.GET.get('completed', ''),
'date_from': self.request.GET.get('date_from', ''),
'date_to': self.request.GET.get('date_to', ''),
})
return context
class DischargeSummaryDetailView(LoginRequiredMixin, DetailView):
"""
Detail view for a discharge summary.
"""
model = DischargeSummary
template_name = 'inpatients/discharges/discharge_detail.html'
context_object_name = 'discharge_summary'
def get_queryset(self):
"""Filter discharge summaries by tenant."""
return DischargeSummary.objects.filter(
admission__tenant=self.request.user.tenant
).select_related(
'admission', 'admission__patient', 'discharging_physician', 'primary_nurse'
)
class DischargeSummaryCreateView(LoginRequiredMixin, CreateView):
"""
Create view for a discharge summary.
"""
model = DischargeSummary
form_class = DischargeSummaryForm
template_name = 'inpatients/discharges/discharge_form.html'
permission_required = 'inpatients.add_dischargesummary' # Assuming permission exists
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, 'Discharge summary created successfully')
return super().form_valid(form)
def get_success_url(self):
return reverse('inpatients:discharge_summary_detail', kwargs={'pk': self.object.pk})
class DischargeSummaryUpdateView(LoginRequiredMixin, UpdateView):
"""
Update view for a discharge summary.
"""
model = DischargeSummary
form_class = DischargeSummaryForm
template_name = 'inpatients/discharges/discharge_form.html'
permission_required = 'inpatients.change_dischargesummary'
def get_queryset(self):
"""Filter discharge summaries by tenant."""
return DischargeSummary.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, 'Discharge summary updated successfully')
return super().form_valid(form)
def get_success_url(self):
return reverse('inpatients:discharge_summary_detail', kwargs={'pk': self.object.pk})
class DischargeSummaryDeleteView(LoginRequiredMixin, DeleteView):
"""
Delete view for a discharge summary.
"""
model = DischargeSummary
template_name = 'inpatients/discharges/discharge_confirm_delete.html'
permission_required = 'inpatients.delete_dischargesummary'
success_url = reverse_lazy('inpatients:discharge_summary_list')
context_object_name = 'discharge_summary'
def get_queryset(self):
"""Filter discharge summaries by tenant."""
return DischargeSummary.objects.filter(admission__tenant=self.request.user.tenant)
def delete(self, request, *args, **kwargs):
summary = self.get_object()
messages.success(request, 'Discharge summary deleted successfully')
return super().delete(request, *args, **kwargs)
@login_required
# @permission_required('inpatients.change_admission')
def discharge_patient(request, admission_id):
"""
View to discharge a patient.
"""
admission = get_object_or_404(
Admission,
pk=admission_id,
tenant=request.user.tenant
)
# 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': admission.admitting_physician.get_full_name()
}
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.TransferStatus.choices
# Get transfer types for filter dropdown
context['transfer_types'] = Transfer.TransferType.choices
# Get priorities for filter dropdown
context['priorities'] = Transfer.TransferPriority.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_surgeryschedule')
@login_required
# @permission_required('inpatients.change_surgeryschedule')
@login_required
# @permission_required('inpatients.change_surgeryschedule')
@login_required
# @permission_required('inpatients.change_surgeryschedule')
@login_required
# @permission_required('inpatients.change_surgeryschedule')
@login_required
# @permission_required('inpatients.change_surgeryschedule')
@login_required
# @permission_required('inpatients.change_surgeryschedule')
@login_required
# @permission_required('inpatients.change_surgeryschedule')
@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
})
# class DischargeManagement(LoginRequiredMixin, ListView):
# model = DischargeSummary