Files

94 lines
3.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""消息通知与预警 ViewSets:
1. NotificationViewSet: 查询、标记已读、全部已读
2. AlertRuleViewSet: 预警规则维护、触发全量检测
"""
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError
from django.db.models import Q
from django.utils import timezone
from asgiref.sync import sync_to_async
from apps.core.viewset import BaseTenantViewSet
from .models import Notification, AlertRule
from .serializers import NotificationSerializer, AlertRuleSerializer
from . import services as notify_services
class NotificationViewSet(BaseTenantViewSet):
model = Notification
serializer_class = NotificationSerializer
search_fields = ["title", "content"]
async def get_queryset(self):
qs = await super().get_queryset()
# 仅查全员广播或当前用户的通知
user = self.request.user
if user.is_authenticated:
qs = qs.filter(Q(recipient__isnull=True) | Q(recipient=user))
else:
# Keep the queryset safe even if this view is reused without the
# default IsAuthenticated permission.
qs = qs.none()
return qs
@action(detail=True, methods=["post"], url_path="mark-read")
async def mark_read(self, request, pk=None):
"""标为已读。"""
# Use the same tenant and recipient scope as list/retrieve. This
# makes another user's notification indistinguishable from a missing
# notification instead of allowing its state to be changed.
queryset = await self.get_queryset()
try:
notification = await sync_to_async(queryset.get)(pk=pk)
except self.model.DoesNotExist:
return Response({"detail": "通知不存在"}, status=status.HTTP_404_NOT_FOUND)
notification.is_read = True
notification.read_at = timezone.now()
await sync_to_async(notification.save)(update_fields=["is_read", "read_at"])
return Response({"ok": True, "id": notification.id, "is_read": True})
@action(detail=False, methods=["post"], url_path="mark-all-read")
async def mark_all_read(self, request):
"""全部标为已读。"""
tenant = await self.get_tenant()
if not tenant:
raise ValidationError({"tenant": "无法识别租户"})
user = request.user
if not user.is_authenticated:
return Response({"ok": True, "updated_count": 0})
updated = await sync_to_async(
self.model.objects.filter(tenant=tenant, is_read=False)
.filter(Q(recipient__isnull=True) | Q(recipient=user))
.update
)(is_read=True, read_at=timezone.now())
return Response({"ok": True, "updated_count": updated})
class AlertRuleViewSet(BaseTenantViewSet):
model = AlertRule
serializer_class = AlertRuleSerializer
search_fields = ["code", "name"]
@action(detail=False, methods=["post"], url_path="init-default")
async def init_default(self, request):
"""初始化默认预警规则。"""
tenant = await self.get_tenant()
if not tenant:
raise ValidationError({"tenant": "无法识别租户"})
n = await sync_to_async(notify_services.init_default_alert_rules)(tenant)
return Response({"ok": True, "created_count": n})
@action(detail=False, methods=["post"], url_path="run-checks")
async def run_checks(self, request):
"""主动触发全量业务智能预警扫描(库存低限、应收逾期)。"""
tenant = await self.get_tenant()
if not tenant:
raise ValidationError({"tenant": "无法识别租户"})
result = await sync_to_async(notify_services.run_all_alert_checks)(tenant)
return Response({"ok": True, "result": result})