599 lines
23 KiB
Python
599 lines
23 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
|
|
from django.utils import timezone
|
|
from datetime import timedelta
|
|
from django.contrib.auth import get_user_model
|
|
|
|
from ..models import (
|
|
Message, MessageRecipient, NotificationTemplate, AlertRule,
|
|
AlertInstance, CommunicationChannel, DeliveryLog
|
|
)
|
|
from .serializers import (
|
|
MessageSerializer, MessageRecipientSerializer, NotificationTemplateSerializer,
|
|
AlertRuleSerializer, AlertInstanceSerializer, CommunicationChannelSerializer,
|
|
DeliveryLogSerializer, CommunicationsStatsSerializer, MessageCreateSerializer,
|
|
BulkMessageSerializer, AlertCreateSerializer, AlertAcknowledgeSerializer,
|
|
AlertResolveSerializer, NotificationSendSerializer, ChannelTestSerializer
|
|
)
|
|
from core.utils import AuditLogger
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
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 CommunicationChannelViewSet(BaseViewSet):
|
|
"""ViewSet for CommunicationChannel model"""
|
|
queryset = CommunicationChannel.objects.all()
|
|
serializer_class = CommunicationChannelSerializer
|
|
filterset_fields = ['channel_type', 'is_active', 'is_default']
|
|
search_fields = ['name', 'description']
|
|
ordering_fields = ['name', 'priority']
|
|
ordering = ['priority', 'name']
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def active(self, request):
|
|
"""Get active channels"""
|
|
queryset = self.get_queryset().filter(is_active=True)
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
return Response(serializer.data)
|
|
|
|
@action(detail=True, methods=['post'])
|
|
def test(self, request, pk=None):
|
|
"""Test communication channel"""
|
|
channel = self.get_object()
|
|
serializer = ChannelTestSerializer(data=request.data)
|
|
|
|
if serializer.is_valid():
|
|
try:
|
|
# Mock channel test - in real implementation, would test actual channel
|
|
test_result = {
|
|
'success': True,
|
|
'message': 'Channel test successful',
|
|
'response_time': 0.25,
|
|
'test_recipient': serializer.validated_data['test_recipient']
|
|
}
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='CHANNEL_TESTED',
|
|
model='CommunicationChannel',
|
|
object_id=str(channel.channel_id),
|
|
details={
|
|
'channel_name': channel.name,
|
|
'test_result': 'SUCCESS'
|
|
}
|
|
)
|
|
|
|
return Response(test_result)
|
|
|
|
except Exception as e:
|
|
return Response({
|
|
'success': False,
|
|
'message': f'Channel test failed: {str(e)}',
|
|
'error': str(e)
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
class NotificationTemplateViewSet(BaseViewSet):
|
|
"""ViewSet for NotificationTemplate model"""
|
|
queryset = NotificationTemplate.objects.all()
|
|
serializer_class = NotificationTemplateSerializer
|
|
filterset_fields = ['template_type', 'is_active']
|
|
search_fields = ['name', 'description', 'subject_template']
|
|
ordering_fields = ['name', 'template_type']
|
|
ordering = ['name']
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def active(self, request):
|
|
"""Get active templates"""
|
|
queryset = self.get_queryset().filter(is_active=True)
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
return Response(serializer.data)
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def by_type(self, request):
|
|
"""Get templates by type"""
|
|
template_type = request.query_params.get('type')
|
|
if template_type:
|
|
queryset = self.get_queryset().filter(
|
|
template_type=template_type,
|
|
is_active=True
|
|
)
|
|
else:
|
|
queryset = self.get_queryset().filter(is_active=True)
|
|
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
return Response(serializer.data)
|
|
|
|
|
|
class MessageViewSet(BaseViewSet):
|
|
"""ViewSet for Message model"""
|
|
queryset = Message.objects.all()
|
|
serializer_class = MessageSerializer
|
|
filterset_fields = ['sender', 'message_type', 'priority', 'is_draft']
|
|
search_fields = ['subject', 'body', 'sender__first_name', 'sender__last_name']
|
|
ordering_fields = ['created_at', 'scheduled_at', 'sent_at']
|
|
ordering = ['-created_at']
|
|
|
|
@action(detail=False, methods=['post'])
|
|
def create_message(self, request):
|
|
"""Create and send a message"""
|
|
serializer = MessageCreateSerializer(data=request.data)
|
|
|
|
if serializer.is_valid():
|
|
# Create message
|
|
message = Message.objects.create(
|
|
sender=request.user,
|
|
subject=serializer.validated_data['subject'],
|
|
body=serializer.validated_data['body'],
|
|
message_type=serializer.validated_data['message_type'],
|
|
priority=serializer.validated_data['priority'],
|
|
scheduled_at=serializer.validated_data.get('scheduled_at'),
|
|
expires_at=serializer.validated_data.get('expires_at'),
|
|
is_draft=serializer.validated_data.get('is_draft', False),
|
|
tenant=getattr(request.user, 'tenant', None)
|
|
)
|
|
|
|
# Create recipients
|
|
recipients = User.objects.filter(id__in=serializer.validated_data['recipients'])
|
|
for recipient in recipients:
|
|
MessageRecipient.objects.create(
|
|
message=message,
|
|
recipient=recipient,
|
|
status='PENDING',
|
|
tenant=getattr(request.user, 'tenant', None)
|
|
)
|
|
|
|
# Send immediately if not draft and not scheduled
|
|
if not message.is_draft and not message.scheduled_at:
|
|
message.sent_at = timezone.now()
|
|
message.save()
|
|
|
|
# Update recipient status to sent
|
|
MessageRecipient.objects.filter(message=message).update(
|
|
status='SENT',
|
|
delivered_at=timezone.now()
|
|
)
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='MESSAGE_CREATED',
|
|
model='Message',
|
|
object_id=str(message.message_id),
|
|
details={
|
|
'subject': message.subject,
|
|
'recipient_count': len(serializer.validated_data['recipients']),
|
|
'is_draft': message.is_draft
|
|
}
|
|
)
|
|
|
|
return Response({
|
|
'message': 'Message created successfully',
|
|
'message_data': MessageSerializer(message).data
|
|
})
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
@action(detail=False, methods=['post'])
|
|
def bulk_message(self, request):
|
|
"""Send bulk message to groups"""
|
|
serializer = BulkMessageSerializer(data=request.data)
|
|
|
|
if serializer.is_valid():
|
|
# Get recipients based on groups
|
|
recipients = set()
|
|
for group in serializer.validated_data['recipient_groups']:
|
|
if group == 'ALL_STAFF':
|
|
recipients.update(User.objects.filter(is_active=True))
|
|
elif group == 'DOCTORS':
|
|
# Mock - in real implementation, would filter by role
|
|
recipients.update(User.objects.filter(is_active=True)[:10])
|
|
elif group == 'NURSES':
|
|
recipients.update(User.objects.filter(is_active=True)[10:20])
|
|
# Add more group logic as needed
|
|
|
|
# Create message
|
|
message = Message.objects.create(
|
|
sender=request.user,
|
|
subject=serializer.validated_data['subject'],
|
|
body=serializer.validated_data['body'],
|
|
message_type=serializer.validated_data['message_type'],
|
|
priority=serializer.validated_data['priority'],
|
|
sent_at=timezone.now(),
|
|
tenant=getattr(request.user, 'tenant', None)
|
|
)
|
|
|
|
# Create recipients
|
|
for recipient in recipients:
|
|
MessageRecipient.objects.create(
|
|
message=message,
|
|
recipient=recipient,
|
|
status='SENT',
|
|
delivered_at=timezone.now(),
|
|
tenant=getattr(request.user, 'tenant', None)
|
|
)
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='BULK_MESSAGE_SENT',
|
|
model='Message',
|
|
object_id=str(message.message_id),
|
|
details={
|
|
'subject': message.subject,
|
|
'recipient_count': len(recipients),
|
|
'groups': serializer.validated_data['recipient_groups']
|
|
}
|
|
)
|
|
|
|
return Response({
|
|
'message': 'Bulk message sent successfully',
|
|
'recipient_count': len(recipients),
|
|
'message_data': MessageSerializer(message).data
|
|
})
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def inbox(self, request):
|
|
"""Get user's inbox messages"""
|
|
queryset = self.get_queryset().filter(
|
|
recipients__recipient=request.user
|
|
).distinct()
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
return Response(serializer.data)
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def sent(self, request):
|
|
"""Get user's sent messages"""
|
|
queryset = self.get_queryset().filter(sender=request.user)
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
return Response(serializer.data)
|
|
|
|
@action(detail=True, methods=['post'])
|
|
def mark_read(self, request, pk=None):
|
|
"""Mark message as read"""
|
|
message = self.get_object()
|
|
|
|
try:
|
|
recipient = MessageRecipient.objects.get(
|
|
message=message,
|
|
recipient=request.user
|
|
)
|
|
recipient.status = 'READ'
|
|
recipient.read_at = timezone.now()
|
|
recipient.save()
|
|
|
|
return Response({'message': 'Message marked as read'})
|
|
|
|
except MessageRecipient.DoesNotExist:
|
|
return Response(
|
|
{'error': 'You are not a recipient of this message'},
|
|
status=status.HTTP_403_FORBIDDEN
|
|
)
|
|
|
|
|
|
class AlertRuleViewSet(BaseViewSet):
|
|
"""ViewSet for AlertRule model"""
|
|
queryset = AlertRule.objects.all()
|
|
serializer_class = AlertRuleSerializer
|
|
filterset_fields = ['rule_type', 'severity', 'is_active']
|
|
search_fields = ['name', 'description']
|
|
ordering_fields = ['name', 'severity']
|
|
ordering = ['name']
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def active(self, request):
|
|
"""Get active alert rules"""
|
|
queryset = self.get_queryset().filter(is_active=True)
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
return Response(serializer.data)
|
|
|
|
|
|
class AlertInstanceViewSet(BaseViewSet):
|
|
"""ViewSet for AlertInstance model"""
|
|
queryset = AlertInstance.objects.all()
|
|
serializer_class = AlertInstanceSerializer
|
|
filterset_fields = ['rule', 'severity', 'status']
|
|
search_fields = ['title', 'description', 'rule__name']
|
|
ordering_fields = ['triggered_at', 'severity']
|
|
ordering = ['-triggered_at']
|
|
|
|
@action(detail=False, methods=['post'])
|
|
def create_alert(self, request):
|
|
"""Create a new alert instance"""
|
|
serializer = AlertCreateSerializer(data=request.data)
|
|
|
|
if serializer.is_valid():
|
|
rule = AlertRule.objects.get(id=serializer.validated_data['rule_id'])
|
|
|
|
# Create alert instance
|
|
alert = AlertInstance.objects.create(
|
|
rule=rule,
|
|
title=serializer.validated_data['title'],
|
|
description=serializer.validated_data['description'],
|
|
severity=serializer.validated_data['severity'],
|
|
status='ACTIVE',
|
|
triggered_at=timezone.now(),
|
|
data=serializer.validated_data.get('data', {}),
|
|
tenant=getattr(request.user, 'tenant', None)
|
|
)
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='ALERT_CREATED',
|
|
model='AlertInstance',
|
|
object_id=str(alert.alert_id),
|
|
details={
|
|
'rule_name': rule.name,
|
|
'title': alert.title,
|
|
'severity': alert.severity
|
|
}
|
|
)
|
|
|
|
return Response({
|
|
'message': 'Alert created successfully',
|
|
'alert': AlertInstanceSerializer(alert).data
|
|
})
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def active(self, request):
|
|
"""Get active alerts"""
|
|
queryset = self.get_queryset().filter(status='ACTIVE')
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
return Response(serializer.data)
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def critical(self, request):
|
|
"""Get critical alerts"""
|
|
queryset = self.get_queryset().filter(
|
|
severity='CRITICAL',
|
|
status__in=['ACTIVE', 'ACKNOWLEDGED']
|
|
)
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
return Response(serializer.data)
|
|
|
|
@action(detail=True, methods=['post'])
|
|
def acknowledge(self, request, pk=None):
|
|
"""Acknowledge an alert"""
|
|
alert = self.get_object()
|
|
serializer = AlertAcknowledgeSerializer(data=request.data)
|
|
|
|
if serializer.is_valid():
|
|
alert.status = 'ACKNOWLEDGED'
|
|
alert.acknowledged_at = timezone.now()
|
|
alert.acknowledged_by = request.user
|
|
alert.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='ALERT_ACKNOWLEDGED',
|
|
model='AlertInstance',
|
|
object_id=str(alert.alert_id),
|
|
details={
|
|
'title': alert.title,
|
|
'notes': serializer.validated_data.get('notes', '')
|
|
}
|
|
)
|
|
|
|
return Response({'message': 'Alert acknowledged successfully'})
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
@action(detail=True, methods=['post'])
|
|
def resolve(self, request, pk=None):
|
|
"""Resolve an alert"""
|
|
alert = self.get_object()
|
|
serializer = AlertResolveSerializer(data=request.data)
|
|
|
|
if serializer.is_valid():
|
|
alert.status = 'RESOLVED'
|
|
alert.resolved_at = timezone.now()
|
|
alert.resolved_by = request.user
|
|
alert.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='ALERT_RESOLVED',
|
|
model='AlertInstance',
|
|
object_id=str(alert.alert_id),
|
|
details={
|
|
'title': alert.title,
|
|
'resolution_notes': serializer.validated_data['resolution_notes']
|
|
}
|
|
)
|
|
|
|
return Response({'message': 'Alert resolved successfully'})
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
class MessageRecipientViewSet(viewsets.ReadOnlyModelViewSet):
|
|
"""ViewSet for MessageRecipient model (read-only)"""
|
|
queryset = MessageRecipient.objects.all()
|
|
serializer_class = MessageRecipientSerializer
|
|
permission_classes = [permissions.IsAuthenticated]
|
|
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
|
filterset_fields = ['message', 'recipient', 'status']
|
|
search_fields = ['recipient__first_name', 'recipient__last_name']
|
|
ordering_fields = ['delivered_at', 'read_at']
|
|
ordering = ['-delivered_at']
|
|
|
|
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 DeliveryLogViewSet(viewsets.ReadOnlyModelViewSet):
|
|
"""ViewSet for DeliveryLog model (read-only)"""
|
|
queryset = DeliveryLog.objects.all()
|
|
serializer_class = DeliveryLogSerializer
|
|
permission_classes = [permissions.IsAuthenticated]
|
|
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
|
filterset_fields = ['message', 'channel', 'status']
|
|
search_fields = ['error_message']
|
|
ordering_fields = ['last_attempt_at', 'delivered_at']
|
|
ordering = ['-last_attempt_at']
|
|
|
|
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 CommunicationsStatsViewSet(viewsets.ViewSet):
|
|
"""ViewSet for communications statistics"""
|
|
permission_classes = [permissions.IsAuthenticated]
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def dashboard(self, request):
|
|
"""Get communications dashboard statistics"""
|
|
tenant_filter = {}
|
|
if hasattr(request.user, 'tenant') and request.user.tenant:
|
|
tenant_filter['tenant'] = request.user.tenant
|
|
|
|
today = timezone.now().date()
|
|
|
|
# Message statistics
|
|
messages = Message.objects.filter(**tenant_filter)
|
|
total_messages = messages.count()
|
|
messages_today = messages.filter(created_at__date=today).count()
|
|
pending_messages = messages.filter(
|
|
is_draft=False,
|
|
sent_at__isnull=True
|
|
).count()
|
|
|
|
# Delivery statistics
|
|
recipients = MessageRecipient.objects.filter(**tenant_filter)
|
|
delivered_messages = recipients.filter(status='DELIVERED').count()
|
|
failed_messages = recipients.filter(status='FAILED').count()
|
|
|
|
# Alert statistics
|
|
alerts = AlertInstance.objects.filter(**tenant_filter)
|
|
active_alerts = alerts.filter(status='ACTIVE').count()
|
|
|
|
# Delivery rate
|
|
total_sent = recipients.filter(status__in=['DELIVERED', 'FAILED']).count()
|
|
delivery_rate = (delivered_messages / total_sent * 100) if total_sent > 0 else 0
|
|
|
|
# Message types breakdown
|
|
message_types = messages.values('message_type').annotate(
|
|
count=Count('id')
|
|
).order_by('-count')
|
|
|
|
# Channel usage (mock data)
|
|
channels = CommunicationChannel.objects.filter(**tenant_filter, is_active=True)
|
|
channel_usage = {}
|
|
for channel in channels:
|
|
# Mock usage data
|
|
usage_count = DeliveryLog.objects.filter(
|
|
channel=channel,
|
|
last_attempt_at__date=today
|
|
).count()
|
|
channel_usage[channel.name] = usage_count
|
|
|
|
stats = {
|
|
'total_messages': total_messages,
|
|
'messages_today': messages_today,
|
|
'pending_messages': pending_messages,
|
|
'delivered_messages': delivered_messages,
|
|
'failed_messages': failed_messages,
|
|
'active_alerts': active_alerts,
|
|
'delivery_rate': round(delivery_rate, 1),
|
|
'message_types': {item['message_type']: item['count'] for item in message_types},
|
|
'channel_usage': channel_usage
|
|
}
|
|
|
|
serializer = CommunicationsStatsSerializer(stats)
|
|
return Response(serializer.data)
|
|
|
|
@action(detail=False, methods=['post'])
|
|
def send_notification(self, request):
|
|
"""Send notification using template"""
|
|
serializer = NotificationSendSerializer(data=request.data)
|
|
|
|
if serializer.is_valid():
|
|
template = NotificationTemplate.objects.get(
|
|
id=serializer.validated_data['template_id']
|
|
)
|
|
recipients = User.objects.filter(
|
|
id__in=serializer.validated_data['recipients']
|
|
)
|
|
variables = serializer.validated_data.get('variables', {})
|
|
|
|
# Create message using template
|
|
subject = template.subject_template.format(**variables) if variables else template.subject_template
|
|
body = template.body_template.format(**variables) if variables else template.body_template
|
|
|
|
message = Message.objects.create(
|
|
sender=request.user,
|
|
subject=subject,
|
|
body=body,
|
|
message_type=template.template_type,
|
|
priority='NORMAL',
|
|
sent_at=timezone.now(),
|
|
tenant=getattr(request.user, 'tenant', None)
|
|
)
|
|
|
|
# Create recipients
|
|
for recipient in recipients:
|
|
MessageRecipient.objects.create(
|
|
message=message,
|
|
recipient=recipient,
|
|
status='SENT',
|
|
delivered_at=timezone.now(),
|
|
tenant=getattr(request.user, 'tenant', None)
|
|
)
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='NOTIFICATION_SENT',
|
|
model='Message',
|
|
object_id=str(message.message_id),
|
|
details={
|
|
'template_name': template.name,
|
|
'recipient_count': len(recipients)
|
|
}
|
|
)
|
|
|
|
return Response({
|
|
'message': 'Notification sent successfully',
|
|
'recipient_count': len(recipients),
|
|
'message_data': MessageSerializer(message).data
|
|
})
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|