""" Integrations views and viewsets """ import logging from rest_framework import status, viewsets from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from apps.accounts.permissions import IsPXAdmin from apps.core.services import AuditService from .models import EventMapping, InboundEvent, IntegrationConfig from .serializers import ( EventMappingSerializer, InboundEventCreateSerializer, InboundEventListSerializer, InboundEventSerializer, IntegrationConfigSerializer, ) logger = logging.getLogger('apps.integrations') class InboundEventViewSet(viewsets.ModelViewSet): """ ViewSet for Inbound Events. This is the main integration endpoint where external systems send events to trigger journey stage completions. POST /api/integrations/events/ - Create new event GET /api/integrations/events/ - List events (admin only) """ queryset = InboundEvent.objects.all() permission_classes = [IsAuthenticated] filterset_fields = ['status', 'source_system', 'event_code', 'encounter_id'] search_fields = ['encounter_id', 'patient_identifier', 'event_code'] ordering_fields = ['received_at', 'processed_at'] ordering = ['-received_at'] def get_serializer_class(self): """Return appropriate serializer based on action""" if self.action == 'create': return InboundEventCreateSerializer elif self.action == 'list': return InboundEventListSerializer return InboundEventSerializer def get_permissions(self): """ Allow event creation with API key or JWT. List/retrieve requires PX Admin. """ if self.action == 'create': # TODO: Add API key authentication for external systems return [IsAuthenticated()] return [IsPXAdmin()] def perform_create(self, serializer): """ Create event and queue for processing. The event will be processed asynchronously by Celery task. """ event = serializer.save() logger.info( f"Inbound event received: {event.source_system} - " f"{event.event_code} - {event.encounter_id}" ) # Log event receipt AuditService.log_from_request( event_type='integration_event', description=f"Integration event received: {event.event_code} for encounter {event.encounter_id}", request=self.request, content_object=event, metadata={ 'source_system': event.source_system, 'event_code': event.event_code, 'encounter_id': event.encounter_id } ) # Queue event for processing from apps.integrations.tasks import process_inbound_event process_inbound_event.delay(str(event.id)) @action(detail=False, methods=['post']) def bulk_create(self, request): """ Bulk create events. Accepts array of events: { "events": [ {"source_system": "his", "event_code": "...", ...}, {"source_system": "lab", "event_code": "...", ...} ] } """ events_data = request.data.get('events', []) if not events_data: return Response( {'error': 'No events provided'}, status=status.HTTP_400_BAD_REQUEST ) created_events = [] errors = [] for event_data in events_data: serializer = InboundEventCreateSerializer(data=event_data) if serializer.is_valid(): event = serializer.save() created_events.append(event) # Queue for processing # from apps.integrations.tasks import process_inbound_event # process_inbound_event.delay(str(event.id)) else: errors.append({ 'data': event_data, 'errors': serializer.errors }) return Response({ 'created': len(created_events), 'failed': len(errors), 'errors': errors }, status=status.HTTP_201_CREATED if created_events else status.HTTP_400_BAD_REQUEST) @action(detail=True, methods=['post'], permission_classes=[IsPXAdmin]) def reprocess(self, request, pk=None): """ Reprocess a failed event. Only PX Admins can manually trigger reprocessing. """ event = self.get_object() if event.status not in ['failed', 'ignored']: return Response( {'error': 'Only failed or ignored events can be reprocessed'}, status=status.HTTP_400_BAD_REQUEST ) # Reset status and queue for processing event.status = 'pending' event.error = '' event.save() # Queue for processing # from apps.integrations.tasks import process_inbound_event # process_inbound_event.delay(str(event.id)) logger.info(f"Event {event.id} queued for reprocessing") return Response({'message': 'Event queued for reprocessing'}) class IntegrationConfigViewSet(viewsets.ModelViewSet): """ ViewSet for Integration Configurations. Permissions: - Only PX Admins can manage integration configurations """ queryset = IntegrationConfig.objects.all() serializer_class = IntegrationConfigSerializer permission_classes = [IsPXAdmin] filterset_fields = ['source_system', 'is_active'] search_fields = ['name', 'description'] ordering_fields = ['name', 'created_at'] ordering = ['name'] def get_queryset(self): return super().get_queryset().prefetch_related('event_mappings') class EventMappingViewSet(viewsets.ModelViewSet): """ ViewSet for Event Mappings. Permissions: - Only PX Admins can manage event mappings """ queryset = EventMapping.objects.all() serializer_class = EventMappingSerializer permission_classes = [IsPXAdmin] filterset_fields = ['integration_config', 'is_active'] search_fields = ['external_event_code', 'internal_event_code'] ordering_fields = ['external_event_code'] ordering = ['integration_config', 'external_event_code'] def get_queryset(self): return super().get_queryset().select_related('integration_config')