1
0
forked from erp-dev/erp
This commit is contained in:
2026-07-05 13:53:17 +08:00
parent 4538e51ad5
commit e1cc6df122
24 changed files with 3030 additions and 495 deletions

View File

@@ -27,6 +27,8 @@ from .mission import (
MissionCategoryListCreateView,
MissionDetailView,
MissionListCreateView,
MissionMyStatusCountsView,
MissionMyView,
MissionReopenView,
MissionReplyListCreateView,
MissionReplyRejectView,
@@ -79,6 +81,8 @@ __all__ = [
'MissionCategoryListCreateView',
'MissionDetailView',
'MissionListCreateView',
'MissionMyStatusCountsView',
'MissionMyView',
'MissionReopenView',
'MissionReplyListCreateView',
'MissionReplyRejectView',

View File

@@ -1,13 +1,19 @@
from datetime import datetime, time, timedelta
from django.contrib.contenttypes.models import ContentType
from django.core.cache import cache
from django.db import IntegrityError
from django.db.models import Exists, OuterRef, Prefetch, Q
from django.db.models import Count, Exists, OuterRef, Prefetch, Q
from django.db.models import ProtectedError
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django.utils.dateparse import parse_date, parse_datetime
from drf_spectacular.utils import extend_schema
from rest_framework import permissions, serializers, status
from rest_framework.response import Response
from rest_framework.views import APIView
from flower.viewsets import LimitedLimitOffsetPagination
from mission import models as mission_models
from mission import services as mission_services
@@ -47,6 +53,11 @@ class MissionWriteSerializer(serializers.Serializer):
required=False,
allow_empty=True,
)
employee_type_ids = serializers.ListField(
child=serializers.IntegerField(min_value=1),
required=False,
allow_empty=True,
)
def __init__(self, *args, **kwargs):
self.is_create = kwargs.pop("is_create", False)
@@ -358,6 +369,27 @@ def _parse_bool_query_param(request, param_name: str, default: bool) -> bool:
raise serializers.ValidationError({param_name: "必须是布尔值"})
def _parse_date_or_datetime_query_param(value: str | None) -> tuple[datetime | None, bool]:
raw_value = (value or "").strip()
if not raw_value:
return None, False
tz = timezone.get_current_timezone()
date_value = parse_date(raw_value)
if date_value is not None:
return timezone.make_aware(datetime.combine(date_value, time.min), tz), False
datetime_value = parse_datetime(raw_value)
if datetime_value is not None:
if timezone.is_naive(datetime_value):
datetime_value = timezone.make_aware(datetime_value, tz)
else:
datetime_value = timezone.localtime(datetime_value, tz)
return datetime_value, True
return None, False
def _mission_queryset_for_employee(employee, *, include_details: bool = True):
has_ending_reply_subquery = mission_models.MissionReply.objects.filter(
mission_id=OuterRef("pk"),
@@ -402,6 +434,7 @@ class MissionCategoryListCreateView(APIView):
queryset = _mission_category_queryset_for_employee(employee)
return Response(MissionCategorySerializer(queryset, many=True).data)
@extend_schema(request=MissionWriteSerializer, responses={201: MissionSerializer})
def post(self, request):
employee = _get_employee(request)
serializer = MissionCategoryWriteSerializer(data=request.data, context={"employee": employee})
@@ -466,6 +499,7 @@ class MissionCategoryDetailView(APIView):
class MissionListCreateView(APIView):
permission_classes = [permissions.IsAuthenticated]
pagination_class = LimitedLimitOffsetPagination
def get(self, request):
employee = _get_employee(request)
@@ -482,7 +516,10 @@ class MissionListCreateView(APIView):
if request.query_params.get("content_id"):
queryset = queryset.filter(content_id=request.query_params["content_id"])
return Response(MissionSerializer(queryset, many=True).data)
paginator = self.pagination_class()
page = paginator.paginate_queryset(queryset, request, view=self)
serializer = MissionSerializer(page, many=True)
return paginator.get_paginated_response(serializer.data)
def post(self, request):
employee = _get_employee(request)
@@ -498,6 +535,7 @@ class MissionListCreateView(APIView):
content_id=data.get("content_id"),
extra=data.get("extra"),
participant_ids=data.get("participant_ids"),
employee_type_ids=data.get("employee_type_ids"),
notify_if_unreplied=data.get("notify_if_unreplied", False),
unreplied_notify_interval_minutes=data.get("unreplied_notify_interval_minutes"),
unreplied_notify_max_count=data.get("unreplied_notify_max_count", 5),
@@ -507,6 +545,65 @@ class MissionListCreateView(APIView):
return Response(MissionSerializer(mission).data, status=status.HTTP_201_CREATED)
class MissionMyView(APIView):
permission_classes = [permissions.IsAuthenticated]
pagination_class = LimitedLimitOffsetPagination
def get(self, request):
employee = _get_employee(request)
queryset = (
_mission_queryset_for_employee(employee)
.filter(Q(creator=employee) | Q(participants__employee=employee))
.distinct()
)
if _parse_bool_query_param(request, "created_by_me", default=False):
queryset = queryset.filter(creator=employee)
for field in ["is_completed", "is_cancelled", "is_urgent"]:
if request.query_params.get(field) is not None:
queryset = queryset.filter(**{field: _parse_bool_query_param(request, field, default=False)})
created_at_from = request.query_params.get("created_at_from")
if created_at_from is not None:
dt, _has_time = _parse_date_or_datetime_query_param(created_at_from)
if dt is None:
raise serializers.ValidationError({"created_at_from": "必须是日期或日期时间"})
queryset = queryset.filter(created_at__gte=dt)
created_at_to = request.query_params.get("created_at_to")
if created_at_to is not None:
dt, has_time = _parse_date_or_datetime_query_param(created_at_to)
if dt is None:
raise serializers.ValidationError({"created_at_to": "必须是日期或日期时间"})
if has_time:
queryset = queryset.filter(created_at__lte=dt)
else:
queryset = queryset.filter(created_at__lt=dt + timedelta(days=1))
paginator = self.pagination_class()
page = paginator.paginate_queryset(queryset, request, view=self)
serializer = MissionSerializer(page, many=True)
return paginator.get_paginated_response(serializer.data)
class MissionMyStatusCountsView(APIView):
permission_classes = [permissions.IsAuthenticated]
def get(self, request):
employee = _get_employee(request)
queryset = mission_models.Mission.objects.filter(merchant=employee.merchant).filter(
Q(creator=employee) | Q(participants__employee=employee)
)
counts = queryset.aggregate(
total=Count("id", distinct=True),
open=Count("id", filter=Q(is_completed=False, is_cancelled=False), distinct=True),
completed=Count("id", filter=Q(is_completed=True, is_cancelled=False), distinct=True),
cancelled=Count("id", filter=Q(is_cancelled=True), distinct=True),
)
return Response(counts)
class MissionByPrintingOrderView(APIView):
permission_classes = [permissions.IsAuthenticated]
@@ -568,6 +665,7 @@ class MissionDetailView(APIView):
mission = self.get_object(request, mission_id)
return Response(MissionSerializer(mission).data)
@extend_schema(request=MissionWriteSerializer, responses=MissionSerializer)
def patch(self, request, mission_id):
employee = _get_employee(request)
mission = self.get_object(request, mission_id)
@@ -589,6 +687,7 @@ class MissionDetailView(APIView):
extra=(data["extra"] if "extra" in data else mission_services.UNSET),
update_content_object=("content_type" in request.data or "content_id" in request.data),
participant_ids=data.get("participant_ids"),
employee_type_ids=data.get("employee_type_ids"),
notify_if_unreplied=(
data["notify_if_unreplied"]
if "notify_if_unreplied" in data