forked from erp-dev/erp
feat: batch update for plate order
This commit is contained in:
@@ -10,6 +10,7 @@ from .printing import (
|
||||
PrintingJobBatchAdvanceSubmitView,
|
||||
PlateOrderByProcessNodeView,
|
||||
PlateOrderByProcessView,
|
||||
PlateOrderBatchUpdateView,
|
||||
)
|
||||
from .stateflow import BusinessObjectCloneView
|
||||
|
||||
@@ -21,6 +22,7 @@ __all__ = [
|
||||
'PrintingJobBatchAdvanceSubmitView',
|
||||
'PlateOrderByProcessNodeView',
|
||||
'PlateOrderByProcessView',
|
||||
'PlateOrderBatchUpdateView',
|
||||
'BusinessObjectCloneView',
|
||||
]
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import datetime
|
||||
|
||||
from django.utils import timezone
|
||||
from django.db import transaction
|
||||
from django.db import models as django_models
|
||||
from django.db.models import Q, Count, CharField, Prefetch
|
||||
from django.db.models.functions import Cast, Coalesce
|
||||
from rest_framework import serializers, status, permissions
|
||||
@@ -41,6 +42,7 @@ class PrintingJobV2Serializer(serializers.ModelSerializer):
|
||||
model = printing_models.PrintingJob
|
||||
fields = [
|
||||
'id',
|
||||
'original_id',
|
||||
'printing_order',
|
||||
'product',
|
||||
'work_state',
|
||||
@@ -385,6 +387,7 @@ class PlateOrderByProcessNodeSerializer(serializers.ModelSerializer):
|
||||
model = printing_models.PlateOrder
|
||||
fields = [
|
||||
'id',
|
||||
'original_id',
|
||||
'design_code',
|
||||
'customer',
|
||||
'customer_name',
|
||||
@@ -627,6 +630,7 @@ class PlateOrderByProcessSerializer(serializers.ModelSerializer):
|
||||
model = printing_models.PlateOrder
|
||||
fields = [
|
||||
'id',
|
||||
'original_id',
|
||||
'design_code',
|
||||
'customer',
|
||||
'customer_name',
|
||||
@@ -886,3 +890,193 @@ class PlateOrderByProcessView(APIView):
|
||||
'previous': paginator.get_previous_link() if page is not None else None,
|
||||
'results': srz.data,
|
||||
})
|
||||
|
||||
|
||||
# 允许批量更新的字段白名单:只改这里即可增减
|
||||
PLATE_ORDER_BATCH_UPDATE_ALLOWED_FIELDS = [
|
||||
"original_id",
|
||||
"design_code",
|
||||
"plate_type",
|
||||
"plate_date",
|
||||
"plate_method",
|
||||
"image_name",
|
||||
"plate_notes",
|
||||
"reprint_reason",
|
||||
"urgency_level",
|
||||
"is_invalid",
|
||||
"customer",
|
||||
"area",
|
||||
"default_address",
|
||||
"salesperson",
|
||||
"merchandiser",
|
||||
"designer",
|
||||
"style_name",
|
||||
"fabric",
|
||||
"fabric_source",
|
||||
"width",
|
||||
"production_method",
|
||||
"is_mark_frame",
|
||||
"drawing_rating",
|
||||
"color_matching_rating",
|
||||
"sample_rating",
|
||||
"difficulty_rating",
|
||||
"sample_meter",
|
||||
"required_sample_meters",
|
||||
"required_completion_date",
|
||||
"completion_date",
|
||||
"approval_result",
|
||||
"is_ordered",
|
||||
"customer_feedback",
|
||||
]
|
||||
|
||||
|
||||
class PlateOrderBatchUpdateDataSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
PlateOrder 批量更新允许字段(白名单)。
|
||||
|
||||
说明:
|
||||
- 不允许更新 process/business_object/created_by 等会触发流程副作用或越权的字段
|
||||
- plate_image 属于结构化字段(且 v1 有额外转换逻辑),暂不纳入批量更新,避免前端误用
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = printing_models.PlateOrder
|
||||
fields = PLATE_ORDER_BATCH_UPDATE_ALLOWED_FIELDS
|
||||
|
||||
|
||||
class PlateOrderBatchUpdateRequestSerializer(serializers.Serializer):
|
||||
plate_order_ids = serializers.ListField(
|
||||
child=serializers.IntegerField(min_value=1),
|
||||
allow_empty=False,
|
||||
help_text="需要批量更新的 PlateOrder id 列表(同一组 data 会应用到所有 id)",
|
||||
)
|
||||
data = serializers.DictField(
|
||||
child=serializers.JSONField(),
|
||||
allow_empty=False,
|
||||
help_text='需要更新的字段集合,例如 {"urgency_level": "加急", "designer": 123}',
|
||||
)
|
||||
dry_run = serializers.BooleanField(
|
||||
required=False,
|
||||
default=False,
|
||||
help_text="仅校验与预览,不实际写库",
|
||||
)
|
||||
|
||||
def validate_plate_order_ids(self, value):
|
||||
# 去重保持稳定性(前端可能重复传)
|
||||
deduped = list(dict.fromkeys(value))
|
||||
if not deduped:
|
||||
raise serializers.ValidationError("plate_order_ids 不能为空")
|
||||
return deduped
|
||||
|
||||
def validate_data(self, value):
|
||||
data_srz = PlateOrderBatchUpdateDataSerializer(data=value, partial=True)
|
||||
data_srz.is_valid(raise_exception=True)
|
||||
if not data_srz.validated_data:
|
||||
raise serializers.ValidationError({"detail": "data 不能为空"})
|
||||
return data_srz.validated_data
|
||||
|
||||
|
||||
class PlateOrderBatchUpdateView(APIView):
|
||||
"""
|
||||
PlateOrder 批量更新(同一份 data 应用到多个 plate_order)。
|
||||
|
||||
POST /api/v2/plate-orders/batch-update/
|
||||
Body:
|
||||
{
|
||||
"plate_order_ids": [1, 2, 3],
|
||||
"data": {"urgency_level": "加急", "designer": 10},
|
||||
"dry_run": false
|
||||
}
|
||||
|
||||
规则:
|
||||
- 全成功/全失败(任意 id 不存在直接 400,不做部分更新)
|
||||
- 需要 printing.change_plateorder 权限
|
||||
- 如包含 is_invalid:
|
||||
- is_invalid=true 需要 printing.can_invalidate_plateorder
|
||||
- is_invalid=false 需要 printing.can_activate_plateorder
|
||||
"""
|
||||
|
||||
permission_classes = [permissions.IsAuthenticated, IsPrintingFactory]
|
||||
|
||||
def post(self, request):
|
||||
# 注意:Django 会缓存 has_perm 结果(user._perm_cache)。
|
||||
# 在测试中 APIClient.force_authenticate 会复用同一个 user 实例,
|
||||
# 可能导致“中途赋权后第二次请求仍判定无权限”的假阴性;这里主动清理缓存更稳妥。
|
||||
for cache_attr in ("_perm_cache", "_user_perm_cache", "_group_perm_cache"):
|
||||
if hasattr(request.user, cache_attr):
|
||||
delattr(request.user, cache_attr)
|
||||
|
||||
if not request.user.has_perm("printing.change_plateorder"):
|
||||
return Response({"detail": "您没有权限批量更新开版订单"}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# 先对 data 做“字段白名单”校验(保证错误输出为顶层 detail,便于前端/测试消费)
|
||||
raw_data = request.data.get("data") if isinstance(request.data, dict) else None
|
||||
if isinstance(raw_data, dict):
|
||||
allowed = set(PLATE_ORDER_BATCH_UPDATE_ALLOWED_FIELDS)
|
||||
unknown = sorted(set(raw_data.keys()) - allowed)
|
||||
if unknown:
|
||||
return Response(
|
||||
{"detail": f"不支持批量更新字段: {', '.join(unknown)}", "allowed_fields": sorted(allowed)},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
srz = PlateOrderBatchUpdateRequestSerializer(data=request.data)
|
||||
srz.is_valid(raise_exception=True)
|
||||
payload = srz.validated_data
|
||||
|
||||
plate_order_ids: list[int] = payload["plate_order_ids"]
|
||||
data: dict = payload["data"]
|
||||
dry_run: bool = payload.get("dry_run", False)
|
||||
|
||||
# is_invalid 权限语义:沿用 v1 的作废/恢复权限
|
||||
if "is_invalid" in data:
|
||||
if data["is_invalid"] is True and not request.user.has_perm("printing.can_invalidate_plateorder"):
|
||||
return Response({"detail": "您没有权限作废开版订单"}, status=status.HTTP_403_FORBIDDEN)
|
||||
if data["is_invalid"] is False and not request.user.has_perm("printing.can_activate_plateorder"):
|
||||
return Response({"detail": "您没有权限恢复开版订单"}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
qs = printing_models.PlateOrder.objects.filter(id__in=plate_order_ids)
|
||||
found_ids = list(qs.values_list("id", flat=True))
|
||||
found_set = set(found_ids)
|
||||
missing_ids = [str(i) for i in plate_order_ids if i not in found_set]
|
||||
if missing_ids:
|
||||
return Response({"detail": f"以下 PlateOrder 不存在: {', '.join(missing_ids)}"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
# 返回/展示时保持与入参一致的顺序
|
||||
found_ids = [i for i in plate_order_ids if i in found_set]
|
||||
|
||||
# 将 validated_data 转换为 queryset.update 可用的 kwargs(处理 FK -> *_id)
|
||||
update_kwargs: dict = {}
|
||||
for k, v in data.items():
|
||||
try:
|
||||
field = printing_models.PlateOrder._meta.get_field(k)
|
||||
except Exception:
|
||||
update_kwargs[k] = v
|
||||
continue
|
||||
|
||||
if isinstance(field, django_models.ForeignKey):
|
||||
update_kwargs[f"{k}_id"] = v.pk if v is not None else None
|
||||
else:
|
||||
update_kwargs[k] = v
|
||||
|
||||
update_kwargs["updated_at"] = timezone.now()
|
||||
|
||||
if dry_run:
|
||||
return Response(
|
||||
{
|
||||
"dry_run": True,
|
||||
"plate_order_ids": found_ids,
|
||||
"matched_count": len(found_ids),
|
||||
"data": request.data.get("data") or {},
|
||||
}
|
||||
)
|
||||
|
||||
with transaction.atomic():
|
||||
updated_count = qs.update(**update_kwargs)
|
||||
|
||||
return Response(
|
||||
{
|
||||
"detail": "批量更新成功",
|
||||
"updated_count": updated_count,
|
||||
"plate_order_ids": found_ids,
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user