94 lines
3.8 KiB
Python
94 lines
3.8 KiB
Python
"""消息通知与预警 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})
|