201 lines
6.5 KiB
Python
201 lines
6.5 KiB
Python
"""
|
|
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')
|