""" 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)