from rest_framework import viewsets, permissions, status from rest_framework.decorators import action from rest_framework.response import Response from django_filters.rest_framework import DjangoFilterBackend from rest_framework import filters from django.db.models import Q, Count, Sum, F from django.utils import timezone from datetime import timedelta from ..models import ( Medication, Prescription, InventoryItem, DispenseRecord, MedicationAdministration, DrugInteraction ) from .serializers import ( MedicationSerializer, PrescriptionSerializer, InventoryItemSerializer, DispenseRecordSerializer, MedicationAdministrationSerializer, DrugInteractionSerializer, PharmacyStatsSerializer, PrescriptionFillSerializer ) from core.utils import AuditLogger class BaseViewSet(viewsets.ModelViewSet): """Base ViewSet with common functionality""" permission_classes = [permissions.IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] def get_queryset(self): # Filter by tenant if user has one if hasattr(self.request.user, 'tenant') and self.request.user.tenant: return self.queryset.filter(tenant=self.request.user.tenant) return self.queryset def perform_create(self, serializer): if hasattr(self.request.user, 'tenant'): serializer.save(tenant=self.request.user.tenant) else: serializer.save() class MedicationViewSet(BaseViewSet): """ViewSet for Medication model""" queryset = Medication.objects.all() serializer_class = MedicationSerializer filterset_fields = [ 'dosage_form', 'route', 'controlled_substance', 'therapeutic_class', 'is_active', 'black_box_warning' ] search_fields = [ 'name', 'generic_name', 'brand_names', 'ndc_number', 'rxcui', 'manufacturer', 'therapeutic_class' ] ordering_fields = ['name', 'generic_name', 'created_at'] ordering = ['name'] @action(detail=False, methods=['get']) def controlled_substances(self, request): """Get controlled substances""" queryset = self.get_queryset().exclude(controlled_substance='NONE') serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def search_by_ndc(self, request): """Search medications by NDC number""" ndc = request.query_params.get('ndc') if not ndc: return Response({'error': 'NDC parameter required'}, status=status.HTTP_400_BAD_REQUEST) queryset = self.get_queryset().filter(ndc_number__icontains=ndc) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=True, methods=['get']) def interactions(self, request, pk=None): """Get drug interactions for a medication""" medication = self.get_object() interactions = DrugInteraction.objects.filter( Q(medication1=medication) | Q(medication2=medication), is_active=True ) serializer = DrugInteractionSerializer(interactions, many=True) return Response(serializer.data) class PrescriptionViewSet(BaseViewSet): """ViewSet for Prescription model""" queryset = Prescription.objects.all() serializer_class = PrescriptionSerializer filterset_fields = [ 'status', 'priority', 'patient', 'prescriber', 'medication', 'generic_substitution_allowed', 'prior_authorization_required' ] search_fields = [ 'prescription_number', 'patient__first_name', 'patient__last_name', 'patient__mrn', 'medication__name', 'prescriber__first_name', 'prescriber__last_name' ] ordering_fields = ['date_prescribed', 'date_filled', 'priority', 'status'] ordering = ['-date_prescribed'] @action(detail=False, methods=['get']) def pending(self, request): """Get pending prescriptions""" queryset = self.get_queryset().filter(status='PENDING') serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def ready_for_pickup(self, request): """Get prescriptions ready for pickup""" queryset = self.get_queryset().filter(status='READY_FOR_PICKUP') serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=True, methods=['post']) def fill(self, request, pk=None): """Fill a prescription""" prescription = self.get_object() serializer = PrescriptionFillSerializer(data=request.data) if serializer.is_valid(): # Update prescription prescription.quantity_dispensed = serializer.validated_data['quantity_dispensed'] prescription.status = 'FILLED' prescription.date_filled = timezone.now() prescription.save() # Create dispense record DispenseRecord.objects.create( prescription=prescription, quantity_dispensed=serializer.validated_data['quantity_dispensed'], lot_number=serializer.validated_data['lot_number'], expiration_date=serializer.validated_data['expiration_date'], date_dispensed=timezone.now(), dispensed_by=request.user, pharmacy_location=serializer.validated_data['pharmacy_location'], patient_counseled=serializer.validated_data['patient_counseled'], counseled_by_id=serializer.validated_data.get('counseled_by'), medication_guide_provided=serializer.validated_data['medication_guide_provided'], notes=serializer.validated_data.get('notes', ''), tenant=getattr(request.user, 'tenant', None) ) # Log the action AuditLogger.log_action( user=request.user, action='PRESCRIPTION_FILLED', model='Prescription', object_id=str(prescription.prescription_id), details={ 'prescription_number': prescription.prescription_number, 'quantity_dispensed': serializer.validated_data['quantity_dispensed'] } ) return Response({'message': 'Prescription filled successfully'}) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @action(detail=True, methods=['post']) def cancel(self, request, pk=None): """Cancel a prescription""" prescription = self.get_object() reason = request.data.get('reason', '') prescription.status = 'CANCELLED' prescription.notes = f"Cancelled: {reason}" prescription.save() # Log the action AuditLogger.log_action( user=request.user, action='PRESCRIPTION_CANCELLED', model='Prescription', object_id=str(prescription.prescription_id), details={ 'prescription_number': prescription.prescription_number, 'reason': reason } ) return Response({'message': 'Prescription cancelled successfully'}) class InventoryItemViewSet(BaseViewSet): """ViewSet for InventoryItem model""" queryset = InventoryItem.objects.all() serializer_class = InventoryItemSerializer filterset_fields = ['medication', 'status', 'supplier', 'location'] search_fields = [ 'medication__name', 'lot_number', 'supplier', 'location' ] ordering_fields = ['expiration_date', 'quantity_on_hand', 'last_counted'] ordering = ['expiration_date'] @action(detail=False, methods=['get']) def low_stock(self, request): """Get low stock items""" queryset = self.get_queryset().filter( quantity_on_hand__lte=F('reorder_level') ) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def expired(self, request): """Get expired items""" queryset = self.get_queryset().filter(expiration_date__lt=timezone.now().date()) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def expiring_soon(self, request): """Get items expiring within 30 days""" thirty_days = timezone.now().date() + timedelta(days=30) queryset = self.get_queryset().filter( expiration_date__lte=thirty_days, expiration_date__gte=timezone.now().date() ) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=True, methods=['post']) def adjust_quantity(self, request, pk=None): """Adjust inventory quantity""" item = self.get_object() adjustment = request.data.get('adjustment', 0) reason = request.data.get('reason', '') try: adjustment = int(adjustment) except (ValueError, TypeError): return Response( {'error': 'Invalid adjustment value'}, status=status.HTTP_400_BAD_REQUEST ) old_quantity = item.quantity_on_hand item.quantity_on_hand += adjustment item.save() # Log the action AuditLogger.log_action( user=request.user, action='INVENTORY_ADJUSTMENT', model='InventoryItem', object_id=str(item.item_id), details={ 'medication': item.medication.name, 'old_quantity': old_quantity, 'new_quantity': item.quantity_on_hand, 'adjustment': adjustment, 'reason': reason } ) return Response({'message': 'Inventory adjusted successfully'}) class DispenseRecordViewSet(viewsets.ReadOnlyModelViewSet): """ViewSet for DispenseRecord model (read-only)""" queryset = DispenseRecord.objects.all() serializer_class = DispenseRecordSerializer permission_classes = [permissions.IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['prescription', 'dispensed_by', 'pharmacy_location'] search_fields = [ 'prescription__prescription_number', 'prescription__patient__first_name', 'prescription__patient__last_name', 'prescription__medication__name' ] ordering_fields = ['date_dispensed'] ordering = ['-date_dispensed'] def get_queryset(self): if hasattr(self.request.user, 'tenant') and self.request.user.tenant: return self.queryset.filter(tenant=self.request.user.tenant) return self.queryset class MedicationAdministrationViewSet(BaseViewSet): """ViewSet for MedicationAdministration model""" queryset = MedicationAdministration.objects.all() serializer_class = MedicationAdministrationSerializer filterset_fields = ['patient', 'medication', 'status', 'administered_by'] search_fields = [ 'patient__first_name', 'patient__last_name', 'patient__mrn', 'medication__name' ] ordering_fields = ['scheduled_time', 'actual_time'] ordering = ['-scheduled_time'] @action(detail=False, methods=['get']) def due_now(self, request): """Get medications due for administration now""" now = timezone.now() queryset = self.get_queryset().filter( scheduled_time__lte=now, status='SCHEDULED' ) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=True, methods=['post']) def administer(self, request, pk=None): """Mark medication as administered""" administration = self.get_object() administration.status = 'ADMINISTERED' administration.actual_time = timezone.now() administration.administered_by = request.user administration.patient_response = request.data.get('patient_response', '') administration.notes = request.data.get('notes', '') administration.save() # Log the action AuditLogger.log_action( user=request.user, action='MEDICATION_ADMINISTERED', model='MedicationAdministration', object_id=str(administration.administration_id), details={ 'patient': administration.patient.get_full_name(), 'medication': administration.medication.name, 'dosage': administration.dosage_given } ) return Response({'message': 'Medication administered successfully'}) class DrugInteractionViewSet(viewsets.ReadOnlyModelViewSet): """ViewSet for DrugInteraction model (read-only)""" queryset = DrugInteraction.objects.filter(is_active=True) serializer_class = DrugInteractionSerializer permission_classes = [permissions.IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['severity', 'interaction_type', 'medication1', 'medication2'] search_fields = [ 'medication1__name', 'medication2__name', 'description' ] ordering_fields = ['severity'] ordering = ['-severity'] @action(detail=False, methods=['post']) def check_interactions(self, request): """Check for interactions between multiple medications""" medication_ids = request.data.get('medication_ids', []) if len(medication_ids) < 2: return Response( {'error': 'At least 2 medications required'}, status=status.HTTP_400_BAD_REQUEST ) interactions = [] for i, med1_id in enumerate(medication_ids): for med2_id in medication_ids[i+1:]: interaction = DrugInteraction.objects.filter( Q(medication1_id=med1_id, medication2_id=med2_id) | Q(medication1_id=med2_id, medication2_id=med1_id), is_active=True ).first() if interaction: interactions.append(interaction) serializer = self.get_serializer(interactions, many=True) return Response(serializer.data) class PharmacyStatsViewSet(viewsets.ViewSet): """ViewSet for pharmacy statistics""" permission_classes = [permissions.IsAuthenticated] @action(detail=False, methods=['get']) def dashboard(self, request): """Get pharmacy dashboard statistics""" tenant_filter = {} if hasattr(request.user, 'tenant') and request.user.tenant: tenant_filter['tenant'] = request.user.tenant today = timezone.now().date() # Prescription statistics prescriptions = Prescription.objects.filter(**tenant_filter) total_prescriptions = prescriptions.count() pending_prescriptions = prescriptions.filter(status='PENDING').count() filled_today = prescriptions.filter(date_filled__date=today).count() # Inventory statistics inventory = InventoryItem.objects.filter(**tenant_filter) low_stock_items = inventory.filter( quantity_on_hand__lte=F('reorder_level') ).count() expired_items = inventory.filter(expiration_date__lt=today).count() # Medication statistics medications = Medication.objects.filter(**tenant_filter) total_medications = medications.count() controlled_substances = medications.exclude(controlled_substance='NONE').count() # Inventory value inventory_value = inventory.aggregate( total=Sum('total_cost') )['total'] or 0 # Prescription types breakdown prescription_types = prescriptions.values('status').annotate( count=Count('id') ).order_by('-count') stats = { 'total_prescriptions': total_prescriptions, 'pending_prescriptions': pending_prescriptions, 'filled_today': filled_today, 'low_stock_items': low_stock_items, 'expired_items': expired_items, 'total_medications': total_medications, 'controlled_substances': controlled_substances, 'inventory_value': inventory_value, 'prescription_types': {item['status']: item['count'] for item in prescription_types}, 'status_breakdown': dict(prescription_types.values_list('status', 'count')) } serializer = PharmacyStatsSerializer(stats) return Response(serializer.data)