2025-08-12 13:33:25 +03:00

428 lines
17 KiB
Python

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)