Marwan Alwali ab2c4a36c5 update
2025-10-02 10:13:03 +03:00

2364 lines
80 KiB
Python

"""
Views for Pharmacy app.
"""
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy, reverse
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.http import JsonResponse, HttpResponse
from django.db.models import Q, Count, Avg, F, Case, When, IntegerField, Max, Sum
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 core.models import AuditLogEntry
from core.utils import AuditLogger
from .models import (
Medication, Prescription, MedicationInventoryItem, DispenseRecord,
MedicationAdministration, DrugInteraction
)
from .forms import (
MedicationForm, PrescriptionForm, MedicationInventoryItemForm, DispenseRecordForm,
MedicationAdministrationForm, DrugInteractionForm, PharmacySearchForm
)
class PharmacyDashboardView(LoginRequiredMixin, ListView):
"""
Main dashboard for pharmacy management.
"""
template_name = 'pharmacy/dashboard.html'
context_object_name = 'prescriptions'
def get_queryset(self):
"""Get recent prescriptions for current tenant."""
return Prescription.objects.filter(
tenant=self.request.user.tenant,
status__in=['PENDING', 'VERIFIED', 'IN_PROGRESS']
).select_related('patient', 'prescriber').order_by('-created_at')[:10]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
tenant = self.request.user.tenant
today = timezone.now().date()
# Dashboard statistics
context.update({
'pending_prescriptions': Prescription.objects.filter(
tenant=tenant,
status='PENDING'
).count(),
'pending_dispensing': Prescription.objects.filter(
tenant=tenant,
status='VERIFIED'
).count(),
# 'low_stock_items': InventoryItem.objects.filter(
# tenant=tenant,
# quantity_available__lte=F('minimum_stock_level')
# ).count(),
# 'expired_medications': InventoryItem.objects.filter(
# tenant=tenant,
# expiration_date__lte=today
# ).count(),
'todays_dispensed': DispenseRecord.objects.filter(
prescription__tenant=tenant,
date_dispensed__date=today
).count(),
'drug_interactions': DrugInteraction.objects.filter(
tenant=tenant,
severity__in=['HIGH', 'CRITICAL']
).count(),
# 'pending_orders': PharmacyOrder.objects.filter(
# tenant=tenant,
# status__in=['PENDING', 'ORDERED']
# ).count(),
'total_medications': Medication.objects.filter(tenant=tenant).count(),
})
return context
class PrescriptionListView(LoginRequiredMixin, ListView):
"""
List view for prescriptions.
"""
model = Prescription
template_name = 'pharmacy/prescription_list.html'
context_object_name = 'prescriptions'
paginate_by = 25
def get_queryset(self):
queryset = Prescription.objects.filter(tenant=self.request.user.tenant)
# Search functionality
search = self.request.GET.get('search')
if search:
queryset = queryset.filter(
Q(patient__first_name__icontains=search) |
Q(patient__last_name__icontains=search) |
Q(patient__mrn__icontains=search) |
Q(medication__name__icontains=search) |
Q(prescribing_provider__first_name__icontains=search) |
Q(prescribing_provider__last_name__icontains=search)
)
# Filter by status
status = self.request.GET.get('status')
if status:
queryset = queryset.filter(status=status)
# Filter by priority
priority = self.request.GET.get('priority')
if priority:
queryset = queryset.filter(priority=priority)
# Filter by provider
provider_id = self.request.GET.get('provider')
if provider_id:
queryset = queryset.filter(prescriber__provider_id=provider_id)
# Filter by date range
date_from = self.request.GET.get('date_from')
date_to = self.request.GET.get('date_to')
if date_from:
queryset = queryset.filter(prescribed_datetime__date__gte=date_from)
if date_to:
queryset = queryset.filter(prescribed_datetime__date__lte=date_to)
return queryset.select_related(
'patient', 'medication', 'prescriber'
).order_by('-date_prescribed')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'prescription_statuses': Prescription._meta.get_field('status').choices,
# 'priorities': Prescription._meta.get_field('priority').choices,
})
return context
class PrescriptionCreateView(LoginRequiredMixin, CreateView):
"""
Create view for prescription.
"""
model = Prescription
form_class = PrescriptionForm
template_name = 'pharmacy/prescriptions/prescription_form.html'
success_url = reverse_lazy('pharmacy:prescription_list')
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
response = super().form_valid(form)
# Create audit log
AuditLogEntry.objects.create(
tenant=self.request.user.tenant,
user=self.request.user,
action='CREATE',
model_name='Prescription',
object_id=self.object.id,
changes={
'patient': str(self.object.patient),
'medication': str(self.object.medication)
}
)
messages.success(self.request, 'Prescription created successfully.')
return response
class PrescriptionDetailView(LoginRequiredMixin, DetailView):
"""
Detail view for prescription.
"""
model = Prescription
template_name = 'pharmacy/prescriptions/prescription_detail.html'
context_object_name = 'prescription'
def get_queryset(self):
return Prescription.objects.filter(tenant=self.request.user.tenant)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
prescription = self.object
tenant = self.request.user.tenant
# Get related data
context.update({
'dispense_records': prescription.dispense_records.all().order_by('-date_dispensed'),
'drug_interactions': DrugInteraction.objects.filter(
Q(medication_1=prescription.medication) | Q(medication_2=prescription.medication),
tenant=tenant
),
})
return context
class MedicationCreateView(LoginRequiredMixin, CreateView):
"""
Create view for medication.
"""
model = Medication
form_class = MedicationForm
template_name = 'pharmacy/medications/medication_form.html'
success_url = reverse_lazy('pharmacy:medication_list')
def form_valid(self, form):
form.instance.tenant = self.request.user.tenant
form.instance.created_by = self.request.user
response = super().form_valid(form)
# Create audit log
AuditLogEntry.objects.create(
tenant=self.request.user.tenant,
user=self.request.user,
action='CREATE',
model_name='Medication',
object_id=self.object.id,
changes={'name': self.object.name}
)
messages.success(self.request, f'Medication "{self.object.name}" created successfully.')
return response
class MedicationUpdateView(LoginRequiredMixin, UpdateView):
"""
Update view for medication.
"""
model = Medication
form_class = MedicationForm
template_name = 'pharmacy/medications/medication_form.html'
def get_queryset(self):
return Medication.objects.filter(tenant=self.request.user.tenant)
def get_success_url(self):
return reverse('pharmacy:medication_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
response = super().form_valid(form)
# Create audit log
AuditLogEntry.objects.create(
tenant=self.request.user.tenant,
user=self.request.user,
action='UPDATE',
model_name='Medication',
object_id=self.object.id,
changes={'name': self.object.name}
)
messages.success(self.request, f'Medication "{self.object.name}" updated successfully.')
return response
class MedicationDeleteView(LoginRequiredMixin, DeleteView):
"""
Delete view for medication (soft delete).
"""
model = Medication
template_name = 'pharmacy/medications/medication_confirm_delete.html'
success_url = reverse_lazy('pharmacy:medication_list')
def get_queryset(self):
return Medication.objects.filter(tenant=self.request.user.tenant)
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
# Check if medication has active prescriptions
active_prescriptions = Prescription.objects.filter(
medication=self.object,
status='ACTIVE'
).count()
if active_prescriptions > 0:
messages.error(
request,
f'Cannot delete medication "{self.object.name}". It has {active_prescriptions} active prescriptions.'
)
return redirect('pharmacy:medication_detail', pk=self.object.pk)
# Soft delete
self.object.is_active = False
self.object.save()
# Create audit log
AuditLogEntry.objects.create(
tenant=request.user.tenant,
user=request.user,
action='DELETE',
model_name='Medication',
object_id=self.object.id,
changes={'name': self.object.name, 'is_active': False}
)
messages.success(request, f'Medication "{self.object.name}" deactivated successfully.')
return redirect(self.success_url)
class MedicationDetailView(LoginRequiredMixin, DetailView):
"""
Detail view for medication with related prescriptions and inventory.
"""
model = Medication
template_name = 'pharmacy/medications/medication_detail.html'
context_object_name = 'medication'
def get_queryset(self):
return Medication.objects.filter(tenant=self.request.user.tenant)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
medication = self.object
# Related prescriptions
context['recent_prescriptions'] = Prescription.objects.filter(
medication=medication,
patient__tenant=self.request.user.tenant
).select_related('patient', 'prescriber').order_by('-date_prescribed')[:10]
# Inventory items
context['inventory_items'] = InventoryItem.objects.filter(
medication=medication,
status="ACTIVE"
).order_by('expiration_date')
# Drug interactions
context['interactions'] = DrugInteraction.objects.filter(
Q(medication_1=medication) | Q(medication_2=medication),
is_active=True
).select_related('medication_1', 'medication_2')
# Statistics
context['total_prescriptions'] = Prescription.objects.filter(
medication=medication,
patient__tenant=self.request.user.tenant
).count()
context['active_prescriptions'] = Prescription.objects.filter(
medication=medication,
patient__tenant=self.request.user.tenant,
status='ACTIVE'
).count()
context['total_inventory'] = InventoryItem.objects.filter(
medication=medication,
status="ACTIVE"
).aggregate(total=Sum('quantity_on_hand'))['total'] or 0
return context
class MedicationListView(LoginRequiredMixin, ListView):
"""
List view for medications.
"""
model = Medication
template_name = 'pharmacy/medications/medication_list.html'
context_object_name = 'medications'
paginate_by = 25
def get_queryset(self):
queryset = Medication.objects.filter(tenant=self.request.user.tenant)
# Search functionality
search = self.request.GET.get('search')
if search:
queryset = queryset.filter(
Q(name__icontains=search) |
Q(generic_name__icontains=search) |
Q(brand_name__icontains=search) |
Q(ndc_number__icontains=search)
)
# Filter by medication type
medication_type = self.request.GET.get('medication_type')
if medication_type:
queryset = queryset.filter(medication_type=medication_type)
# Filter by controlled substance
controlled_substance = self.request.GET.get('controlled_substance')
if controlled_substance:
queryset = queryset.filter(controlled_substance_schedule=controlled_substance)
# Filter by active status
active_only = self.request.GET.get('active_only')
if active_only:
queryset = queryset.filter(is_active=True)
return queryset.order_by('brand_name')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'medication_types': Medication.DosageForm.choices,
'controlled_schedules': Medication.ControlledSubstanceSchedule.choices,
})
return context
class InventoryItemListView(LoginRequiredMixin, ListView):
"""
List view for inventory items with search and filtering.
"""
model = MedicationInventoryItem
template_name = 'pharmacy/inventory/inventory_list.html'
context_object_name = 'inventory_items'
paginate_by = 20
def get_queryset(self):
queryset = MedicationInventoryItem.objects.filter(
medication__tenant=self.request.user.tenant
).select_related('medication').order_by('medication__generic_name', 'expiration_date')
# Apply search filters
form = InventoryItemForm(data=self.request.GET)
if form.is_valid():
search = form.cleaned_data.get('search')
if search:
queryset = queryset.filter(
Q(medication__name__icontains=search) |
Q(lot_number__icontains=search) |
Q(supplier__icontains=search) |
Q(location__icontains=search)
)
medication = form.cleaned_data.get('medication')
if medication:
queryset = queryset.filter(medication=medication)
location = form.cleaned_data.get('location')
if location:
queryset = queryset.filter(location__icontains=location)
low_stock = form.cleaned_data.get('low_stock')
if low_stock:
queryset = queryset.filter(quantity_on_hand__lte=F('reorder_point'))
expiring_soon = form.cleaned_data.get('expiring_soon')
if expiring_soon:
threshold = timezone.now().date() + timedelta(days=30)
queryset = queryset.filter(expiration_date__lte=threshold)
is_active = form.cleaned_data.get('is_active')
if is_active == 'true':
queryset = queryset.filter(is_active=True)
elif is_active == 'false':
queryset = queryset.filter(is_active=False)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['search_form'] = InventoryItemForm(
data=self.request.GET,
# user=self.request.user
)
return context
class InventoryItemDetailView(LoginRequiredMixin, DetailView):
"""
Detail view for inventory item with dispense history.
"""
model = MedicationInventoryItem
template_name = 'pharmacy/inventory/inventory_detail.html'
context_object_name = 'inventory_item'
def get_queryset(self):
return MedicationInventoryItem.objects.filter(
medication__tenant=self.request.user.tenant
).select_related('medication')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
inventory_item = self.object
# Dispense history
context['dispense_records'] = DispenseRecord.objects.filter(
inventory_item=inventory_item
).select_related(
'prescription__patient', 'dispensed_by'
).order_by('-date_dispensed')
# Calculate total dispensed
context['total_dispensed'] = DispenseRecord.objects.filter(
inventory_item=inventory_item
).aggregate(total=Sum('quantity_dispensed'))['total'] or 0
# Days until expiry
if inventory_item.expiration_date:
days_until_expiry = (inventory_item.expiration_date - timezone.now().date()).days
context['days_until_expiry'] = days_until_expiry
context['is_expiring_soon'] = days_until_expiry <= 30
# Low stock alert
context['is_low_stock'] = inventory_item.quantity_on_hand <= inventory_item.reorder_point
return context
class InventoryItemCreateView(LoginRequiredMixin, CreateView):
"""
Create view for inventory item.
"""
model = MedicationInventoryItem
form_class = MedicationInventoryItemForm
template_name = 'pharmacy/inventory/inventory_form.html'
success_url = reverse_lazy('pharmacy:inventory_list')
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
response = super().form_valid(form)
# Create audit log
AuditLogEntry.objects.create(
tenant=self.request.user.tenant,
user=self.request.user,
action='CREATE',
model_name='InventoryItem',
object_id=self.object.id,
changes={
'medication': str(self.object.medication),
'lot_number': self.object.lot_number,
'quantity': self.object.quantity_on_hand
}
)
messages.success(self.request, 'Inventory item created successfully.')
return response
class InventoryItemUpdateView(LoginRequiredMixin, UpdateView):
"""
Update view for inventory item.
"""
model = MedicationInventoryItem
form_class = MedicationInventoryItemForm
template_name = 'pharmacy/inventory/inventory_form.html'
def get_queryset(self):
return InventoryItem.objects.filter(medication__tenant=self.request.user.tenant)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def get_success_url(self):
return reverse('pharmacy:inventory_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
response = super().form_valid(form)
# Create audit log
AuditLogEntry.objects.create(
tenant=self.request.user.tenant,
user=self.request.user,
action='UPDATE',
model_name='InventoryItem',
object_id=self.object.id,
changes={'quantity': self.object.quantity_on_hand}
)
messages.success(self.request, 'Inventory item updated successfully.')
return response
class DispenseRecordListView(LoginRequiredMixin, ListView):
"""
List view for dispense records.
"""
model = DispenseRecord
template_name = 'pharmacy/dispense_record_list.html'
context_object_name = 'dispense_records'
paginate_by = 25
def get_queryset(self):
queryset = DispenseRecord.objects.filter(tenant=self.request.user.tenant)
# Search functionality
search = self.request.GET.get('search')
if search:
queryset = queryset.filter(
Q(prescription__patient__first_name__icontains=search) |
Q(prescription__patient__last_name__icontains=search) |
Q(prescription__patient__mrn__icontains=search) |
Q(prescription__medication__name__icontains=search)
)
# Filter by date range
date_from = self.request.GET.get('date_from')
date_to = self.request.GET.get('date_to')
if date_from:
queryset = queryset.filter(dispensed_datetime__date__gte=date_from)
if date_to:
queryset = queryset.filter(dispensed_datetime__date__lte=date_to)
# Filter by pharmacist
pharmacist_id = self.request.GET.get('pharmacist')
if pharmacist_id:
queryset = queryset.filter(dispensed_by_id=pharmacist_id)
return queryset.select_related(
'prescription__patient', 'prescription__medication', 'dispensed_by'
).order_by('-dispensed_datetime')
class DrugInteractionListView(LoginRequiredMixin, ListView):
"""
List view for drug interactions.
"""
model = DrugInteraction
template_name = 'pharmacy/interactions/drug_interaction_list.html'
context_object_name = 'interactions'
paginate_by = 25
def get_queryset(self):
queryset = DrugInteraction.objects.filter(tenant=self.request.user.tenant)
# Search functionality
search = self.request.GET.get('search')
if search:
queryset = queryset.filter(
Q(medication_a__name__icontains=search) |
Q(medication_b__name__icontains=search) |
Q(interaction_description__icontains=search)
)
# Filter by severity
severity = self.request.GET.get('severity')
if severity:
queryset = queryset.filter(severity=severity)
# Filter by interaction type
interaction_type = self.request.GET.get('interaction_type')
if interaction_type:
queryset = queryset.filter(interaction_type=interaction_type)
return queryset.select_related('medication_1', 'medication_2').order_by('-severity', 'medication_1')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'severities': DrugInteraction._meta.get_field('severity').choices,
'interaction_types': DrugInteraction._meta.get_field('interaction_type').choices,
})
return context
# Medication Administration Views
class MedicationAdministrationListView(LoginRequiredMixin, ListView):
"""
List view for medication administration records.
"""
model = MedicationAdministration
template_name = 'pharmacy/administration_list.html'
context_object_name = 'administrations'
paginate_by = 25
def get_queryset(self):
queryset = MedicationAdministration.objects.filter(
encounter__tenant=self.request.user.tenant
).select_related(
'prescription__patient', 'prescription__medication', 'administered_by'
).order_by('-scheduled_datetime')
# Search functionality
search = self.request.GET.get('search')
if search:
queryset = queryset.filter(
Q(prescription__patient__first_name__icontains=search) |
Q(prescription__patient__last_name__icontains=search) |
Q(prescription__patient__mrn__icontains=search) |
Q(prescription__medication__generic_name__icontains=search) |
Q(prescription__medication__brand_name__icontains=search)
)
# Filter by status
status = self.request.GET.get('status')
if status:
queryset = queryset.filter(status=status)
# Filter by route
route = self.request.GET.get('route')
if route:
queryset = queryset.filter(route_given=route)
# Filter by date range
date_from = self.request.GET.get('date_from')
date_to = self.request.GET.get('date_to')
if date_from:
queryset = queryset.filter(scheduled_datetime__date__gte=date_from)
if date_to:
queryset = queryset.filter(scheduled_datetime__date__lte=date_to)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
tenant = self.request.user.tenant
today = timezone.now().date()
# Statistics
context['todays_administrations'] = MedicationAdministration.objects.filter(
encounter__tenant=tenant,
scheduled_datetime__date=today
).count()
context['given_today'] = MedicationAdministration.objects.filter(
encounter__tenant=tenant,
scheduled_datetime__date=today,
status='GIVEN'
).count()
context['pending_administrations'] = MedicationAdministration.objects.filter(
encounter__tenant=tenant,
scheduled_datetime__gte=timezone.now(),
status='SCHEDULED'
).count()
context['overdue_administrations'] = MedicationAdministration.objects.filter(
encounter__tenant=tenant,
scheduled_datetime__lt=timezone.now(),
status='SCHEDULED'
).count()
return context
class MedicationAdministrationDetailView(LoginRequiredMixin, DetailView):
"""
Detail view for medication administration record.
"""
model = MedicationAdministration
template_name = 'pharmacy/administration_detail.html'
context_object_name = 'administration'
def get_queryset(self):
return MedicationAdministration.objects.filter(
tenant=self.request.user.tenant
).select_related(
'prescription__patient', 'prescription__medication',
'administered_by', 'witnessed_by', 'double_checked_by'
)
class MedicationAdministrationCreateView(LoginRequiredMixin, CreateView):
"""
Create view for medication administration record.
"""
model = MedicationAdministration
form_class = MedicationAdministrationForm
template_name = 'pharmacy/administration_form.html'
success_url = reverse_lazy('pharmacy:administration_list')
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
response = super().form_valid(form)
# Create audit log
AuditLogEntry.objects.create(
tenant=self.request.user.tenant,
user=self.request.user,
action='CREATE',
model_name='MedicationAdministration',
object_id=self.object.id,
changes={
'prescription': str(self.object.prescription),
'status': self.object.status
}
)
messages.success(self.request, 'Medication administration recorded successfully.')
return response
class MedicationAdministrationUpdateView(LoginRequiredMixin, UpdateView):
"""
Update view for medication administration record.
"""
model = MedicationAdministration
form_class = MedicationAdministrationForm
template_name = 'pharmacy/administration_form.html'
def get_queryset(self):
return MedicationAdministration.objects.filter(tenant=self.request.user.tenant)
def get_success_url(self):
return reverse('pharmacy:administration_detail', kwargs={'pk': self.object.pk})
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
response = super().form_valid(form)
# Create audit log
AuditLogEntry.objects.create(
tenant=self.request.user.tenant,
user=self.request.user,
action='UPDATE',
model_name='MedicationAdministration',
object_id=self.object.id,
changes={'status': self.object.status}
)
messages.success(self.request, 'Medication administration updated successfully.')
return response
@login_required
def pharmacy_stats(request):
"""
HTMX endpoint for pharmacy statistics.
"""
tenant = request.user.tenant
today = timezone.now().date()
stats = {
'pending_prescriptions': Prescription.objects.filter(
tenant=tenant,
status='PENDING'
).count(),
'pending_dispensing': Prescription.objects.filter(
tenant=tenant,
status='VERIFIED'
).count(),
'low_stock_items': InventoryItem.objects.filter(
tenant=tenant,
quantity_on_hand__lte=F('reorder_point')
).count(),
'expired_medications': InventoryItem.objects.filter(
tenant=tenant,
expiration_date__lte=today
).count(),
}
return render(request, 'pharmacy/partials/pharmacy_stats.html', {'stats': stats})
@login_required
def prescription_search(request):
"""
HTMX endpoint for prescription search.
"""
search = request.GET.get('search', '')
status = request.GET.get('status', '')
priority = request.GET.get('priority', '')
queryset = Prescription.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(medication__name__icontains=search)
)
if status:
queryset = queryset.filter(status=status)
if priority:
queryset = queryset.filter(priority=priority)
prescriptions = queryset.select_related(
'patient', 'medication', 'prescribing_provider'
).order_by('-prescribed_datetime')[:20]
return render(request, 'pharmacy/prescriptions/prescription_list.html', {'prescriptions': prescriptions})
@login_required
def inventory_alerts(request):
"""
HTMX endpoint for inventory alerts.
"""
tenant = request.user.tenant
today = timezone.now().date()
thirty_days = today + timedelta(days=30)
# Low stock items
low_stock = InventoryItem.objects.filter(
tenant=tenant,
quantity_on_hand__lte=F('reorder_point')
).select_related('medication')[:10]
# Expiring items
expiring = InventoryItem.objects.filter(
tenant=tenant,
expiration_date__lte=thirty_days,
expiration_date__gt=today
).select_related('medication')[:10]
# Expired items
expired = InventoryItem.objects.filter(
tenant=tenant,
expiration_date__lte=today
).select_related('medication')[:10]
return render(request, 'pharmacy/partials/inventory_alerts.html', {
'low_stock': low_stock,
'expiring': expiring,
'expired': expired
})
@login_required
def drug_interaction_check(request, prescription_id):
"""
HTMX endpoint for drug interaction check.
"""
prescription = get_object_or_404(
Prescription,
id=prescription_id,
tenant=request.user.tenant
)
# Check for interactions with this medication
interactions = DrugInteraction.objects.filter(
Q(medication_a=prescription.medication) | Q(medication_b=prescription.medication),
tenant=request.user.tenant
).select_related('medication_a', 'medication_b')
return render(request, 'pharmacy/drug_interactions/drug_interactions.html', {
'prescription': prescription,
'interactions': interactions
})
@login_required
def verify_prescription(request, prescription_id):
"""
Verify a prescription.
"""
if request.method == 'POST':
prescription = get_object_or_404(
Prescription,
id=prescription_id,
tenant=request.user.tenant
)
prescription.status = 'VERIFIED'
prescription.verified_by = request.user
prescription.verified_datetime = timezone.now()
prescription.save()
# Log the action
AuditLogger.log_action(
user=request.user,
action='VERIFY_PRESCRIPTION',
model_name='Prescription',
object_id=prescription.id,
details=f'Prescription verified for {prescription.patient.get_full_name()}'
)
messages.success(request, 'Prescription verified successfully.')
if request.headers.get('HX-Request'):
return JsonResponse({'status': 'success'})
return redirect('pharmacy:prescription_detail', pk=prescription.id)
return JsonResponse({'status': 'error'})
@login_required
def dispense_medication(request, prescription_id):
"""
Dispense medication for a prescription.
"""
if request.method == 'POST':
prescription = get_object_or_404(
Prescription,
id=prescription_id,
tenant=request.user.tenant
)
quantity_dispensed = request.POST.get('quantity_dispensed')
lot_number = request.POST.get('lot_number')
# Create dispense record
dispense_record = DispenseRecord.objects.create(
tenant=request.user.tenant,
prescription=prescription,
quantity_dispensed=quantity_dispensed,
dispensed_by=request.user,
dispensed_datetime=timezone.now(),
lot_number=lot_number
)
# Update prescription status
prescription.status = 'DISPENSED'
prescription.save()
# Update inventory
try:
inventory_item = InventoryItem.objects.get(
tenant=request.user.tenant,
medication=prescription.medication,
lot_number=lot_number
)
inventory_item.current_stock -= int(quantity_dispensed)
inventory_item.save()
except InventoryItem.DoesNotExist:
pass
# Log the action
AuditLogger.log_action(
user=request.user,
action='DISPENSE_MEDICATION',
model_name='DispenseRecord',
object_id=dispense_record.id,
details=f'Dispensed {quantity_dispensed} units of {prescription.medication.name}'
)
messages.success(request, 'Medication dispensed successfully.')
if request.headers.get('HX-Request'):
return JsonResponse({'status': 'success'})
return redirect('pharmacy:prescription_detail', pk=prescription.id)
return JsonResponse({'status': 'error'})
@login_required
def update_inventory(request, item_id):
"""
Update inventory item stock.
"""
if request.method == 'POST':
item = get_object_or_404(
InventoryItem,
id=item_id,
tenant=request.user.tenant
)
new_stock = request.POST.get('new_stock')
adjustment_reason = request.POST.get('adjustment_reason', '')
old_stock = item.current_stock
item.current_stock = int(new_stock)
item.last_updated = timezone.now()
item.save()
# Log the action
AuditLogger.log_event(
user=request.user,
action='UPDATE_INVENTORY',
model_name='InventoryItem',
object_id=item.id,
details=f'Stock updated from {old_stock} to {new_stock}. Reason: {adjustment_reason}'
)
messages.success(request, 'Inventory updated successfully.')
if request.headers.get('HX-Request'):
return JsonResponse({'status': 'success'})
return redirect('pharmacy:inventory_list')
return JsonResponse({'status': 'error'})
def get_medication_info(request, pk):
medication = get_object_or_404(Medication, pk=pk)
medication_info = {
'generic_name': medication.generic_name,
'brand_name': medication.brand_name,
}
return JsonResponse(medication_info)
#
# """
# Views for Pharmacy app with comprehensive CRUD operations following healthcare best practices.
# """
#
# from django.shortcuts import render, get_object_or_404, redirect
# from django.contrib.auth.decorators import login_required
# from django.contrib.auth.mixins import LoginRequiredMixin
# from django.contrib import messages
# from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
# from django.urls import reverse_lazy, reverse
# from django.db.models import Q, Count, Sum, Avg, F
# from django.http import JsonResponse, HttpResponse
# from django.utils import timezone
# from django.core.paginator import Paginator
# from datetime import datetime, timedelta
# import csv
#
# from .models import *
# from .forms import *
# from core.models import AuditLogEntry
# from patients.models import PatientProfile
#
#
# class PharmacyDashboardView(LoginRequiredMixin, ListView):
# """
# Pharmacy dashboard with comprehensive statistics and recent activity.
# """
# template_name = 'pharmacy/dashboard.html'
# context_object_name = 'recent_prescriptions'
# paginate_by = 10
#
# def get_queryset(self):
# return Prescription.objects.filter(
# patient__tenant=self.request.user.tenant
# ).select_related(
# 'patient', 'medication', 'prescriber'
# ).order_by('-prescribed_date')[:10]
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# user = self.request.user
#
# # Basic statistics
# context['total_medications'] = Medication.objects.filter(
# tenant=user.tenant, is_active=True
# ).count()
#
# context['active_prescriptions'] = Prescription.objects.filter(
# patient__tenant=user.tenant,
# status='ACTIVE'
# ).count()
#
# context['pending_prescriptions'] = Prescription.objects.filter(
# patient__tenant=user.tenant,
# status='PENDING'
# ).count()
#
# context['low_stock_items'] = InventoryItem.objects.filter(
# medication__tenant=user.tenant,
# is_active=True,
# quantity_on_hand__lte=F('reorder_point')
# ).count()
#
# # Today's statistics
# today = timezone.now().date()
# context['prescriptions_today'] = Prescription.objects.filter(
# patient__tenant=user.tenant,
# prescribed_date__date=today
# ).count()
#
# context['dispensed_today'] = DispenseRecord.objects.filter(
# prescription__patient__tenant=user.tenant,
# dispensed_date__date=today
# ).count()
#
# context['administered_today'] = MedicationAdministration.objects.filter(
# patient__tenant=user.tenant,
# administered_date=today,
# status='GIVEN'
# ).count()
#
# # Expiring medications (next 30 days)
# expiry_threshold = today + timedelta(days=30)
# context['expiring_medications'] = InventoryItem.objects.filter(
# medication__tenant=user.tenant,
# is_active=True,
# expiry_date__lte=expiry_threshold,
# expiry_date__gt=today
# ).count()
#
# # Recent activity
# context['recent_dispenses'] = DispenseRecord.objects.filter(
# prescription__patient__tenant=user.tenant
# ).select_related(
# 'prescription__patient', 'prescription__medication', 'dispensed_by'
# ).order_by('-dispensed_date')[:5]
#
# context['recent_administrations'] = MedicationAdministration.objects.filter(
# patient__tenant=user.tenant
# ).select_related(
# 'patient', 'medication', 'administered_by'
# ).order_by('-administered_date')[:5]
#
# # Drug interactions count
# context['drug_interactions'] = DrugInteraction.objects.filter(
# medication_1__tenant=user.tenant,
# is_active=True
# ).count()
#
# return context
#
#
# # Medication Views
# class MedicationListView(LoginRequiredMixin, ListView):
# """
# List view for medications with search and filtering.
# """
# model = Medication
# template_name = 'pharmacy/medication_list.html'
# context_object_name = 'medications'
# paginate_by = 20
#
# def get_queryset(self):
# queryset = Medication.objects.filter(
# tenant=self.request.user.tenant
# ).order_by('name')
#
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(name__icontains=search) |
# Q(generic_name__icontains=search) |
# Q(brand_name__icontains=search) |
# Q(ndc_number__icontains=search)
# )
#
# drug_class = self.request.GET.get('drug_class')
# if drug_class:
# queryset = queryset.filter(drug_class__icontains=drug_class)
#
# 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)
#
# return queryset
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['search_form'] = PharmacySearchForm(
# data=self.request.GET,
# user=self.request.user
# )
# return context
#
#
# class MedicationDetailView(LoginRequiredMixin, DetailView):
# """
# Detail view for medication with related prescriptions and inventory.
# """
# model = Medication
# template_name = 'pharmacy/medication_detail.html'
# context_object_name = 'medication'
#
# def get_queryset(self):
# return Medication.objects.filter(tenant=self.request.user.tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# medication = self.object
#
# # Related prescriptions
# context['recent_prescriptions'] = Prescription.objects.filter(
# medication=medication,
# patient__tenant=self.request.user.tenant
# ).select_related('patient', 'prescriber').order_by('-prescribed_date')[:10]
#
# # Inventory items
# context['inventory_items'] = InventoryItem.objects.filter(
# medication=medication,
# is_active=True
# ).order_by('expiry_date')
#
# # Drug interactions
# context['interactions'] = DrugInteraction.objects.filter(
# Q(medication_1=medication) | Q(medication_2=medication),
# is_active=True
# ).select_related('medication_1', 'medication_2')
#
# # Statistics
# context['total_prescriptions'] = Prescription.objects.filter(
# medication=medication,
# patient__tenant=self.request.user.tenant
# ).count()
#
# context['active_prescriptions'] = Prescription.objects.filter(
# medication=medication,
# patient__tenant=self.request.user.tenant,
# status='ACTIVE'
# ).count()
#
# context['total_inventory'] = InventoryItem.objects.filter(
# medication=medication,
# is_active=True
# ).aggregate(total=Sum('quantity_on_hand'))['total'] or 0
#
# return context
#
#
# class MedicationCreateView(LoginRequiredMixin, CreateView):
# """
# Create view for medication.
# """
# model = Medication
# form_class = MedicationForm
# template_name = 'pharmacy/medication_form.html'
# success_url = reverse_lazy('pharmacy:medication_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# form.instance.created_by = self.request.user
# response = super().form_valid(form)
#
# # Create audit log
# AuditLogEntry.objects.create(
# tenant=self.request.user.tenant,
# user=self.request.user,
# action='CREATE',
# model_name='Medication',
# object_id=self.object.id,
# changes={'name': self.object.name}
# )
#
# messages.success(self.request, f'Medication "{self.object.name}" created successfully.')
# return response
#
#
# class MedicationUpdateView(LoginRequiredMixin, UpdateView):
# """
# Update view for medication.
# """
# model = Medication
# form_class = MedicationForm
# template_name = 'pharmacy/medication_form.html'
#
# def get_queryset(self):
# return Medication.objects.filter(tenant=self.request.user.tenant)
#
# def get_success_url(self):
# return reverse('pharmacy:medication_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Create audit log
# AuditLogEntry.objects.create(
# tenant=self.request.user.tenant,
# user=self.request.user,
# action='UPDATE',
# model_name='Medication',
# object_id=self.object.id,
# changes={'name': self.object.name}
# )
#
# messages.success(self.request, f'Medication "{self.object.name}" updated successfully.')
# return response
#
#
# class MedicationDeleteView(LoginRequiredMixin, DeleteView):
# """
# Delete view for medication (soft delete).
# """
# model = Medication
# template_name = 'pharmacy/medication_confirm_delete.html'
# success_url = reverse_lazy('pharmacy:medication_list')
#
# def get_queryset(self):
# return Medication.objects.filter(tenant=self.request.user.tenant)
#
# def delete(self, request, *args, **kwargs):
# self.object = self.get_object()
#
# # Check if medication has active prescriptions
# active_prescriptions = Prescription.objects.filter(
# medication=self.object,
# status='ACTIVE'
# ).count()
#
# if active_prescriptions > 0:
# messages.error(
# request,
# f'Cannot delete medication "{self.object.name}". It has {active_prescriptions} active prescriptions.'
# )
# return redirect('pharmacy:medication_detail', pk=self.object.pk)
#
# # Soft delete
# self.object.is_active = False
# self.object.save()
#
# # Create audit log
# AuditLogEntry.objects.create(
# tenant=request.user.tenant,
# user=request.user,
# action='DELETE',
# model_name='Medication',
# object_id=self.object.id,
# changes={'name': self.object.name, 'is_active': False}
# )
#
# messages.success(request, f'Medication "{self.object.name}" deactivated successfully.')
# return redirect(self.success_url)
#
#
# # Prescription Views
# class PrescriptionListView(LoginRequiredMixin, ListView):
# """
# List view for prescriptions with search and filtering.
# """
# model = Prescription
# template_name = 'pharmacy/prescription_list.html'
# context_object_name = 'prescriptions'
# paginate_by = 20
#
# def get_queryset(self):
# queryset = Prescription.objects.filter(
# patient__tenant=self.request.user.tenant
# ).select_related(
# 'patient', 'medication', 'prescriber'
# ).order_by('-prescribed_date')
#
# # Apply search filters
# form = PharmacySearchForm(data=self.request.GET, user=self.request.user)
# if form.is_valid():
# search = form.cleaned_data.get('search')
# if search:
# queryset = queryset.filter(
# Q(patient__first_name__icontains=search) |
# Q(patient__last_name__icontains=search) |
# Q(medication__name__icontains=search) |
# Q(prescriber__first_name__icontains=search) |
# Q(prescriber__last_name__icontains=search)
# )
#
# patient = form.cleaned_data.get('patient')
# if patient:
# queryset = queryset.filter(patient=patient)
#
# medication = form.cleaned_data.get('medication')
# if medication:
# queryset = queryset.filter(medication=medication)
#
# prescriber = form.cleaned_data.get('prescriber')
# if prescriber:
# queryset = queryset.filter(prescriber=prescriber)
#
# status = form.cleaned_data.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# priority = form.cleaned_data.get('priority')
# if priority:
# queryset = queryset.filter(priority=priority)
#
# date_from = form.cleaned_data.get('date_from')
# if date_from:
# queryset = queryset.filter(prescribed_date__date__gte=date_from)
#
# date_to = form.cleaned_data.get('date_to')
# if date_to:
# queryset = queryset.filter(prescribed_date__date__lte=date_to)
#
# return queryset
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['search_form'] = PharmacySearchForm(
# data=self.request.GET,
# user=self.request.user
# )
# return context
#
#
# class PrescriptionDetailView(LoginRequiredMixin, DetailView):
# """
# Detail view for prescription with dispense records.
# """
# model = Prescription
# template_name = 'pharmacy/prescription_detail.html'
# context_object_name = 'prescription'
#
# def get_queryset(self):
# return Prescription.objects.filter(
# patient__tenant=self.request.user.tenant
# ).select_related('patient', 'medication', 'prescriber')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# prescription = self.object
#
# # Dispense records
# context['dispense_records'] = DispenseRecord.objects.filter(
# prescription=prescription
# ).select_related('inventory_item', 'dispensed_by').order_by('-dispensed_date')
#
# # Available inventory
# context['available_inventory'] = InventoryItem.objects.filter(
# medication=prescription.medication,
# is_active=True,
# quantity_on_hand__gt=0
# ).order_by('expiry_date')
#
# # Calculate dispensed quantity
# context['total_dispensed'] = DispenseRecord.objects.filter(
# prescription=prescription
# ).aggregate(total=Sum('quantity_dispensed'))['total'] or 0
#
# context['remaining_quantity'] = prescription.quantity - context['total_dispensed']
#
# return context
#
#
# class PrescriptionCreateView(LoginRequiredMixin, CreateView):
# """
# Create view for prescription.
# """
# model = Prescription
# form_class = PrescriptionForm
# template_name = 'pharmacy/prescription_form.html'
# success_url = reverse_lazy('pharmacy:prescription_list')
#
# 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
# response = super().form_valid(form)
#
# # Create audit log
# AuditLogEntry.objects.create(
# tenant=self.request.user.tenant,
# user=self.request.user,
# action='CREATE',
# model_name='Prescription',
# object_id=self.object.id,
# changes={
# 'patient': str(self.object.patient),
# 'medication': str(self.object.medication)
# }
# )
#
# messages.success(self.request, 'Prescription created successfully.')
# return response
#
#
# class PrescriptionUpdateView(LoginRequiredMixin, UpdateView):
# """
# Update view for prescription (limited updates).
# """
# model = Prescription
# form_class = PrescriptionForm
# template_name = 'pharmacy/prescription_form.html'
#
# def get_queryset(self):
# return Prescription.objects.filter(patient__tenant=self.request.user.tenant)
#
# def get_form_kwargs(self):
# kwargs = super().get_form_kwargs()
# kwargs['user'] = self.request.user
# return kwargs
#
# def get_success_url(self):
# return reverse('pharmacy:prescription_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# # Restrict updates for filled prescriptions
# if self.object.status == 'FILLED':
# messages.error(self.request, 'Cannot update filled prescriptions.')
# return redirect('pharmacy:prescription_detail', pk=self.object.pk)
#
# response = super().form_valid(form)
#
# # Create audit log
# AuditLogEntry.objects.create(
# tenant=self.request.user.tenant,
# user=self.request.user,
# action='UPDATE',
# model_name='Prescription',
# object_id=self.object.id,
# changes={'status': self.object.status}
# )
#
# messages.success(self.request, 'Prescription updated successfully.')
# return response
#
#
# # Inventory Views
# class InventoryItemListView(LoginRequiredMixin, ListView):
# """
# List view for inventory items with search and filtering.
# """
# model = InventoryItem
# template_name = 'pharmacy/inventory_list.html'
# context_object_name = 'inventory_items'
# paginate_by = 20
#
# def get_queryset(self):
# queryset = InventoryItem.objects.filter(
# medication__tenant=self.request.user.tenant
# ).select_related('medication').order_by('medication__name', 'expiry_date')
#
# # Apply search filters
# form = InventorySearchForm(data=self.request.GET, user=self.request.user)
# if form.is_valid():
# search = form.cleaned_data.get('search')
# if search:
# queryset = queryset.filter(
# Q(medication__name__icontains=search) |
# Q(lot_number__icontains=search) |
# Q(supplier__icontains=search) |
# Q(location__icontains=search)
# )
#
# medication = form.cleaned_data.get('medication')
# if medication:
# queryset = queryset.filter(medication=medication)
#
# location = form.cleaned_data.get('location')
# if location:
# queryset = queryset.filter(location__icontains=location)
#
# low_stock = form.cleaned_data.get('low_stock')
# if low_stock:
# queryset = queryset.filter(quantity_on_hand__lte=F('reorder_point'))
#
# expiring_soon = form.cleaned_data.get('expiring_soon')
# if expiring_soon:
# threshold = timezone.now().date() + timedelta(days=30)
# queryset = queryset.filter(expiry_date__lte=threshold)
#
# is_active = form.cleaned_data.get('is_active')
# if is_active == 'true':
# queryset = queryset.filter(is_active=True)
# elif is_active == 'false':
# queryset = queryset.filter(is_active=False)
#
# return queryset
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['search_form'] = InventorySearchForm(
# data=self.request.GET,
# user=self.request.user
# )
# return context
#
#
# class InventoryItemDetailView(LoginRequiredMixin, DetailView):
# """
# Detail view for inventory item with dispense history.
# """
# model = InventoryItem
# template_name = 'pharmacy/inventory_detail.html'
# context_object_name = 'inventory_item'
#
# def get_queryset(self):
# return InventoryItem.objects.filter(
# medication__tenant=self.request.user.tenant
# ).select_related('medication')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# inventory_item = self.object
#
# # Dispense history
# context['dispense_records'] = DispenseRecord.objects.filter(
# inventory_item=inventory_item
# ).select_related(
# 'prescription__patient', 'dispensed_by'
# ).order_by('-dispensed_date')
#
# # Calculate total dispensed
# context['total_dispensed'] = DispenseRecord.objects.filter(
# inventory_item=inventory_item
# ).aggregate(total=Sum('quantity_dispensed'))['total'] or 0
#
# # Days until expiry
# if inventory_item.expiry_date:
# days_until_expiry = (inventory_item.expiry_date - timezone.now().date()).days
# context['days_until_expiry'] = days_until_expiry
# context['is_expiring_soon'] = days_until_expiry <= 30
#
# # Low stock alert
# context['is_low_stock'] = inventory_item.quantity_on_hand <= inventory_item.reorder_point
#
# return context
#
#
# class InventoryItemCreateView(LoginRequiredMixin, CreateView):
# """
# Create view for inventory item.
# """
# model = InventoryItem
# form_class = InventoryItemForm
# template_name = 'pharmacy/inventory_form.html'
# success_url = reverse_lazy('pharmacy:inventory_list')
#
# 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
# response = super().form_valid(form)
#
# # Create audit log
# AuditLogEntry.objects.create(
# tenant=self.request.user.tenant,
# user=self.request.user,
# action='CREATE',
# model_name='InventoryItem',
# object_id=self.object.id,
# changes={
# 'medication': str(self.object.medication),
# 'lot_number': self.object.lot_number,
# 'quantity': self.object.quantity_on_hand
# }
# )
#
# messages.success(self.request, 'Inventory item created successfully.')
# return response
#
#
# class InventoryItemUpdateView(LoginRequiredMixin, UpdateView):
# """
# Update view for inventory item.
# """
# model = InventoryItem
# form_class = InventoryItemForm
# template_name = 'pharmacy/inventory_form.html'
#
# def get_queryset(self):
# return InventoryItem.objects.filter(medication__tenant=self.request.user.tenant)
#
# def get_form_kwargs(self):
# kwargs = super().get_form_kwargs()
# kwargs['user'] = self.request.user
# return kwargs
#
# def get_success_url(self):
# return reverse('pharmacy:inventory_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Create audit log
# AuditLogEntry.objects.create(
# tenant=self.request.user.tenant,
# user=self.request.user,
# action='UPDATE',
# model_name='InventoryItem',
# object_id=self.object.id,
# changes={'quantity': self.object.quantity_on_hand}
# )
#
# messages.success(self.request, 'Inventory item updated successfully.')
# return response
#
#
# # Dispense Record Views
# class DispenseRecordListView(LoginRequiredMixin, ListView):
# """
# List view for dispense records with search and filtering.
# """
# model = DispenseRecord
# template_name = 'pharmacy/dispense_list.html'
# context_object_name = 'dispense_records'
# paginate_by = 20
#
# def get_queryset(self):
# queryset = DispenseRecord.objects.filter(
# prescription__patient__tenant=self.request.user.tenant
# ).select_related(
# 'prescription__patient', 'prescription__medication',
# 'inventory_item', 'dispensed_by'
# ).order_by('-dispensed_date')
#
# # Apply search filters
# form = DispenseSearchForm(data=self.request.GET, user=self.request.user)
# if form.is_valid():
# search = form.cleaned_data.get('search')
# if search:
# queryset = queryset.filter(
# Q(prescription__patient__first_name__icontains=search) |
# Q(prescription__patient__last_name__icontains=search) |
# Q(prescription__medication__name__icontains=search)
# )
#
# patient = form.cleaned_data.get('patient')
# if patient:
# queryset = queryset.filter(prescription__patient=patient)
#
# medication = form.cleaned_data.get('medication')
# if medication:
# queryset = queryset.filter(prescription__medication=medication)
#
# dispensed_by = form.cleaned_data.get('dispensed_by')
# if dispensed_by:
# queryset = queryset.filter(dispensed_by=dispensed_by)
#
# date_from = form.cleaned_data.get('date_from')
# if date_from:
# queryset = queryset.filter(dispensed_date__date__gte=date_from)
#
# date_to = form.cleaned_data.get('date_to')
# if date_to:
# queryset = queryset.filter(dispensed_date__date__lte=date_to)
#
# return queryset
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['search_form'] = DispenseSearchForm(
# data=self.request.GET,
# user=self.request.user
# )
# return context
#
#
# class DispenseRecordDetailView(LoginRequiredMixin, DetailView):
# """
# Detail view for dispense record.
# """
# model = DispenseRecord
# template_name = 'pharmacy/dispense_detail.html'
# context_object_name = 'dispense_record'
#
# def get_queryset(self):
# return DispenseRecord.objects.filter(
# prescription__patient__tenant=self.request.user.tenant
# ).select_related(
# 'prescription__patient', 'prescription__medication',
# 'inventory_item', 'dispensed_by'
# )
#
#
# class DispenseRecordCreateView(LoginRequiredMixin, CreateView):
# """
# Create view for dispense record.
# """
# model = DispenseRecord
# form_class = DispenseRecordForm
# template_name = 'pharmacy/dispense_form.html'
# success_url = reverse_lazy('pharmacy:dispense_list')
#
# def get_form_kwargs(self):
# kwargs = super().get_form_kwargs()
# kwargs['user'] = self.request.user
# return kwargs
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Update inventory quantity
# inventory_item = self.object.inventory_item
# inventory_item.quantity_on_hand -= self.object.quantity_dispensed
# inventory_item.save()
#
# # Update prescription status if fully dispensed
# prescription = self.object.prescription
# total_dispensed = DispenseRecord.objects.filter(
# prescription=prescription
# ).aggregate(total=Sum('quantity_dispensed'))['total'] or 0
#
# if total_dispensed >= prescription.quantity:
# prescription.status = 'FILLED'
# prescription.save()
#
# # Create audit log
# AuditLogEntry.objects.create(
# tenant=self.request.user.tenant,
# user=self.request.user,
# action='CREATE',
# model_name='DispenseRecord',
# object_id=self.object.id,
# changes={
# 'prescription': str(self.object.prescription),
# 'quantity': self.object.quantity_dispensed
# }
# )
#
# messages.success(self.request, 'Medication dispensed successfully.')
# return response
#
#
# # Medication Administration Views
# class MedicationAdministrationListView(LoginRequiredMixin, ListView):
# """
# List view for medication administration records.
# """
# model = MedicationAdministration
# template_name = 'pharmacy/administration_list.html'
# context_object_name = 'administrations'
# paginate_by = 20
#
# def get_queryset(self):
# queryset = MedicationAdministration.objects.filter(
# patient__tenant=self.request.user.tenant
# ).select_related(
# 'patient', 'medication', 'administered_by'
# ).order_by('-administered_date', '-actual_time')
#
# # Apply search filters
# form = AdministrationSearchForm(data=self.request.GET, user=self.request.user)
# if form.is_valid():
# search = form.cleaned_data.get('search')
# if search:
# queryset = queryset.filter(
# Q(patient__first_name__icontains=search) |
# Q(patient__last_name__icontains=search) |
# Q(medication__name__icontains=search)
# )
#
# patient = form.cleaned_data.get('patient')
# if patient:
# queryset = queryset.filter(patient=patient)
#
# medication = form.cleaned_data.get('medication')
# if medication:
# queryset = queryset.filter(medication=medication)
#
# administered_by = form.cleaned_data.get('administered_by')
# if administered_by:
# queryset = queryset.filter(administered_by=administered_by)
#
# status = form.cleaned_data.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# route = form.cleaned_data.get('route')
# if route:
# queryset = queryset.filter(route=route)
#
# date_from = form.cleaned_data.get('date_from')
# if date_from:
# queryset = queryset.filter(administered_date__gte=date_from)
#
# date_to = form.cleaned_data.get('date_to')
# if date_to:
# queryset = queryset.filter(administered_date__lte=date_to)
#
# return queryset
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['search_form'] = AdministrationSearchForm(
# data=self.request.GET,
# user=self.request.user
# )
# return context
#
#
# class MedicationAdministrationCreateView(LoginRequiredMixin, CreateView):
# """
# Create view for medication administration.
# """
# model = MedicationAdministration
# form_class = MedicationAdministrationForm
# template_name = 'pharmacy/administration_form.html'
# success_url = reverse_lazy('pharmacy:administration_list')
#
# def get_form_kwargs(self):
# kwargs = super().get_form_kwargs()
# kwargs['user'] = self.request.user
# return kwargs
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Create audit log
# AuditLogEntry.objects.create(
# tenant=self.request.user.tenant,
# user=self.request.user,
# action='CREATE',
# model_name='MedicationAdministration',
# object_id=self.object.id,
# changes={
# 'patient': str(self.object.patient),
# 'medication': str(self.object.medication),
# 'status': self.object.status
# }
# )
#
# messages.success(self.request, 'Medication administration recorded successfully.')
# return response
#
#
# # Drug Interaction Views
# class DrugInteractionListView(LoginRequiredMixin, ListView):
# """
# List view for drug interactions.
# """
# model = DrugInteraction
# template_name = 'pharmacy/interaction_list.html'
# context_object_name = 'interactions'
# paginate_by = 20
#
# def get_queryset(self):
# queryset = DrugInteraction.objects.filter(
# medication_1__tenant=self.request.user.tenant
# ).select_related('medication_1', 'medication_2').order_by('severity', 'medication_1__name')
#
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(medication_1__name__icontains=search) |
# Q(medication_2__name__icontains=search) |
# Q(description__icontains=search)
# )
#
# severity = self.request.GET.get('severity')
# if severity:
# queryset = queryset.filter(severity=severity)
#
# 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)
#
# return queryset
#
#
class DrugInteractionCreateView(LoginRequiredMixin, CreateView):
"""
Create view for drug interaction.
"""
model = DrugInteraction
form_class = DrugInteractionForm
template_name = 'pharmacy/interaction_form.html'
success_url = reverse_lazy('pharmacy:interaction_list')
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
response = super().form_valid(form)
# Create audit log
AuditLogEntry.objects.create(
tenant=self.request.user.tenant,
user=self.request.user,
action='CREATE',
model_name='DrugInteraction',
object_id=self.object.id,
changes={
'medication_1': str(self.object.medication_1),
'medication_2': str(self.object.medication_2),
'severity': self.object.severity
}
)
messages.success(self.request, 'Drug interaction created successfully.')
return response
class DrugInteractionDetailView(LoginRequiredMixin, DetailView):
model = DrugInteraction
template_name = 'pharmacy/interactions/drug_interaction_detail.html'
context_object_name = 'interaction'
# HTMX Views
@login_required
def htmx_pharmacy_stats(request):
"""
HTMX view for real-time pharmacy statistics.
"""
user = request.user
today = timezone.now().date()
stats = {
'total_medications': Medication.objects.filter(
tenant=user.tenant, is_active=True
).count(),
'active_prescriptions': Prescription.objects.filter(
patient__tenant=user.tenant, status='ACTIVE'
).count(),
'pending_prescriptions': Prescription.objects.filter(
patient__tenant=user.tenant, status='PENDING'
).count(),
'low_stock_items': InventoryItem.objects.filter(
medication__tenant=user.tenant,
is_active=True,
quantity_on_hand__lte=F('reorder_point')
).count(),
'prescriptions_today': Prescription.objects.filter(
patient__tenant=user.tenant,
prescribed_date__date=today
).count(),
'dispensed_today': DispenseRecord.objects.filter(
prescription__patient__tenant=user.tenant,
dispensed_date__date=today
).count(),
}
return render(request, 'pharmacy/partials/pharmacy_stats.html', {'stats': stats})
@login_required
def htmx_prescription_search(request):
"""
HTMX view for prescription search.
"""
search = request.GET.get('search', '')
prescriptions = Prescription.objects.filter(
patient__tenant=request.user.tenant
).select_related('patient', 'medication', 'prescriber')
if search:
prescriptions = prescriptions.filter(
Q(patient__first_name__icontains=search) |
Q(patient__last_name__icontains=search) |
Q(medication__name__icontains=search)
)
prescriptions = prescriptions.order_by('-prescribed_date')[:10]
return render(request, 'pharmacy/partials/prescription_list.html', {
'prescriptions': prescriptions
})
# Action Views
@login_required
def fill_prescription(request, pk):
"""
Action view to fill a prescription.
"""
prescription = get_object_or_404(
Prescription,
pk=pk,
patient__tenant=request.user.tenant
)
if prescription.status != 'ACTIVE':
messages.error(request, 'Only active prescriptions can be filled.')
return redirect('pharmacy:prescription_detail', pk=pk)
# Check inventory availability
available_inventory = InventoryItem.objects.filter(
medication=prescription.medication,
is_active=True,
quantity_on_hand__gte=prescription.quantity
).first()
if not available_inventory:
messages.error(request, 'Insufficient inventory to fill this prescription.')
return redirect('pharmacy:prescription_detail', pk=pk)
if request.method == 'POST':
# Create dispense record
dispense_record = DispenseRecord.objects.create(
prescription=prescription,
inventory_item=available_inventory,
quantity_dispensed=prescription.quantity,
dispensed_by=request.user,
dispensed_date=timezone.now(),
patient_counseled=True
)
# Update inventory
available_inventory.quantity_on_hand -= prescription.quantity
available_inventory.save()
# Update prescription status
prescription.status = 'FILLED'
prescription.save()
# Create audit log
AuditLogEntry.objects.create(
tenant=request.user.tenant,
user=request.user,
action='UPDATE',
model_name='Prescription',
object_id=prescription.id,
changes={'status': 'FILLED', 'filled_by': str(request.user)}
)
messages.success(request, 'Prescription filled successfully.')
return redirect('pharmacy:prescription_detail', pk=pk)
return render(request, 'pharmacy/fill_prescription.html', {
'prescription': prescription,
'available_inventory': available_inventory
})
@login_required
def cancel_prescription(request, pk):
"""
Action view to cancel a prescription.
"""
prescription = get_object_or_404(
Prescription,
pk=pk,
patient__tenant=request.user.tenant
)
if prescription.status in ['FILLED', 'CANCELLED']:
messages.error(request, 'Cannot cancel filled or already cancelled prescriptions.')
return redirect('pharmacy:prescription_detail', pk=pk)
if request.method == 'POST':
prescription.status = 'CANCELLED'
prescription.save()
# Create audit log
AuditLogEntry.objects.create(
tenant=request.user.tenant,
user=request.user,
action='UPDATE',
model_name='Prescription',
object_id=prescription.id,
changes={'status': 'CANCELLED', 'cancelled_by': str(request.user)}
)
messages.success(request, 'Prescription cancelled successfully.')
return redirect('pharmacy:prescription_detail', pk=pk)
return render(request, 'pharmacy/cancel_prescription.html', {
'prescription': prescription
})
@login_required
def adjust_inventory(request, pk):
"""
Action view to adjust inventory quantity.
"""
inventory_item = get_object_or_404(
InventoryItem,
pk=pk,
medication__tenant=request.user.tenant
)
if request.method == 'POST':
adjustment = int(request.POST.get('adjustment', 0))
reason = request.POST.get('reason', '')
if adjustment == 0:
messages.error(request, 'Adjustment amount cannot be zero.')
return redirect('pharmacy:inventory_detail', pk=pk)
old_quantity = inventory_item.quantity_on_hand
inventory_item.quantity_on_hand += adjustment
if inventory_item.quantity_on_hand < 0:
messages.error(request, 'Adjustment would result in negative inventory.')
return redirect('pharmacy:inventory_detail', pk=pk)
inventory_item.save()
# Create audit log
AuditLogEntry.objects.create(
tenant=request.user.tenant,
user=request.user,
action='UPDATE',
model_name='InventoryItem',
object_id=inventory_item.id,
changes={
'old_quantity': old_quantity,
'new_quantity': inventory_item.quantity_on_hand,
'adjustment': adjustment,
'reason': reason
}
)
messages.success(request, f'Inventory adjusted by {adjustment} units.')
return redirect('pharmacy:inventory_detail', pk=pk)
return render(request, 'pharmacy/adjust_inventory.html', {
'inventory_item': inventory_item
})
#
#
# # Export Views
# @login_required
# def export_prescriptions_csv(request):
# """
# Export prescriptions to CSV.
# """
# response = HttpResponse(content_type='text/csv')
# response['Content-Disposition'] = 'attachment; filename="prescriptions.csv"'
#
# writer = csv.writer(response)
# writer.writerow([
# 'Patient', 'Medication', 'Prescriber', 'Quantity', 'Status',
# 'Prescribed Date', 'Priority'
# ])
#
# prescriptions = Prescription.objects.filter(
# patient__tenant=request.user.tenant
# ).select_related('patient', 'medication', 'prescriber')
#
# for prescription in prescriptions:
# writer.writerow([
# f"{prescription.patient.first_name} {prescription.patient.last_name}",
# prescription.medication.name,
# f"{prescription.prescriber.first_name} {prescription.prescriber.last_name}",
# prescription.quantity,
# prescription.get_status_display(),
# prescription.prescribed_date.strftime('%Y-%m-%d %H:%M'),
# prescription.get_priority_display()
# ])
#
# return response
#
#
# @login_required
# def export_inventory_csv(request):
# """
# Export inventory to CSV.
# """
# response = HttpResponse(content_type='text/csv')
# response['Content-Disposition'] = 'attachment; filename="inventory.csv"'
#
# writer = csv.writer(response)
# writer.writerow([
# 'Medication', 'Lot Number', 'Quantity', 'Unit Cost', 'Expiry Date',
# 'Location', 'Reorder Point', 'Status'
# ])
#
# inventory_items = InventoryItem.objects.filter(
# medication__tenant=request.user.tenant
# ).select_related('medication')
#
# for item in inventory_items:
# writer.writerow([
# item.medication.name,
# item.lot_number,
# item.quantity_on_hand,
# item.unit_cost,
# item.expiry_date.strftime('%Y-%m-%d') if item.expiry_date else '',
# item.location,
# item.reorder_point,
# 'Active' if item.is_active else 'Inactive'
# ])
#
# return response
#
def save_prescription_draft(request, pk):
prescription = get_object_or_404(Prescription, pk=pk)
prescription.status = "DRAFT"
prescription.save()
return redirect('pharmacy:prescription_detail', pk=pk)