1
0
forked from erp-dev/erp

feat: batch update for plate order

This commit is contained in:
2025-12-18 17:36:43 +08:00
parent 98d797221a
commit 1a6ce32441
33 changed files with 32087 additions and 8 deletions

View File

@@ -0,0 +1,5 @@
"""MingdaoYun related API views."""
from .plate_order_staging import MDYPlateOrderStagingViewSet
__all__ = ["MDYPlateOrderStagingViewSet"]

View File

@@ -0,0 +1,102 @@
"""开版暂存(明道云)查询 API。
需求:
- 默认按 -ctime 排序
- 支持 raw__62d52f4b8d2972284492dd0e设计编号过滤
- 支持 LimitOffset 分页
- 返回时把 raw / related 两个 JSONField 做“可读化/碾平”
"""
from __future__ import annotations
from typing import Any
from django.db.models import F
from rest_framework import serializers, viewsets
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.permissions import DjangoModelPermissions
from api_v1.models import MDYPlateOrderStaging
from flower.utils.mingdaoyun.mappings import plate_order_field_definitions
from flower.utils.mingdaoyun.relations import flatten_row_by_field_definitions, normalize_mdy_value
_PLATE_ORDER_DESIGN_NO_CONTROL_ID = "62d52f4b8d2972284492dd0e"
class MDYPlateOrderStagingSerializer(serializers.ModelSerializer):
"""把 raw/related 输出为扁平的可读结构。"""
design_no = serializers.SerializerMethodField()
raw = serializers.SerializerMethodField()
related = serializers.SerializerMethodField()
class Meta:
model = MDYPlateOrderStaging
fields = [
"id",
"mdy_rowid",
"ctime",
"utime",
"created_at",
"updated_at",
"design_no",
"raw",
"related",
]
def get_design_no(self, obj: MDYPlateOrderStaging) -> Any:
if isinstance(obj.raw, dict):
return obj.raw.get(_PLATE_ORDER_DESIGN_NO_CONTROL_ID)
return None
def get_raw(self, obj: MDYPlateOrderStaging) -> dict[str, Any]:
if not isinstance(obj.raw, dict):
return {}
# 1) 已知字段:按映射转换为中文 key更可读
out = flatten_row_by_field_definitions(obj.raw, plate_order_field_definitions, include_system_keys=True)
# 2) 未映射字段:为了排查方便,不静默丢弃,保留 controlId 原样
known_control_ids = set(plate_order_field_definitions.keys())
for k, v in obj.raw.items():
if k in {"rowid", "ctime", "utime"}:
continue
if k in known_control_ids:
continue
out[str(k)] = normalize_mdy_value(v)
return out
def get_related(self, obj: MDYPlateOrderStaging) -> list[dict[str, Any]]:
if not obj.related:
return []
if not isinstance(obj.related, list):
return []
out: list[dict[str, Any]] = []
for item in obj.related:
if isinstance(item, dict):
out.append({k: normalize_mdy_value(v) for k, v in item.items()})
else:
# 兜底:若数据被写成非 dict 元素,仍然返回原值,便于排查
out.append({"value": item})
return out
class MDYPlateOrderStagingViewSet(viewsets.ReadOnlyModelViewSet):
"""开版暂存数据查询(只读)。"""
serializer_class = MDYPlateOrderStagingSerializer
# permission_classes = [DjangoModelPermissions]
pagination_class = LimitOffsetPagination
def get_queryset(self):
qs = MDYPlateOrderStaging.objects.all()
# 支持按 raw 中的“设计编号/订单id”过滤按你要求的 query param 名)
design_no = self.request.query_params.get(f"raw__{_PLATE_ORDER_DESIGN_NO_CONTROL_ID}")
if design_no:
qs = qs.filter(**{f"raw__{_PLATE_ORDER_DESIGN_NO_CONTROL_ID}": design_no})
# 默认 -ctimectime 为空的放最后,避免老数据(未回填)跑到前面
return qs.order_by(F("ctime").desc(nulls_last=True), F("id").desc())

View File

@@ -0,0 +1,101 @@
"""MDYPlateOrderStaging 查询 API 测试"""
from datetime import timedelta
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.test import TestCase
from django.utils import timezone
from rest_framework.test import APIClient
from api_v1.models import MDYPlateOrderStaging
User = get_user_model()
class MDYPlateOrderStagingAPITestCase(TestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user(
username="testuser",
password="testpass123",
email="test@example.com",
)
self.client.force_authenticate(user=self.user)
view_perm = Permission.objects.get(codename="view_mdyplateorderstaging")
self.user.user_permissions.add(view_perm)
now = timezone.now()
# older
self.row1 = MDYPlateOrderStaging.objects.create(
mdy_rowid="row1",
ctime=now - timedelta(days=1),
utime=now - timedelta(days=1, minutes=1),
raw={
"rowid": "row1",
"ctime": "2025-01-01 00:00:00",
"utime": "2025-01-01 00:01:00",
"62d52f4b8d2972284492dd0e": "A001", # 设计编号
"62d52f4b8d2972284492dd2c": "款号A", # 款号名称
},
related=[
{
"rowid": "rel1",
"source": "drawing",
"source_name": "画图",
"设计师名字": "张三",
}
],
)
# newer
self.row2 = MDYPlateOrderStaging.objects.create(
mdy_rowid="row2",
ctime=now,
utime=now,
raw={
"rowid": "row2",
"ctime": "2025-01-02 00:00:00",
"utime": "2025-01-02 00:01:00",
"62d52f4b8d2972284492dd0e": "A002", # 设计编号
"62d52f4b8d2972284492dd2c": "款号B", # 款号名称
},
related=[],
)
def test_list_default_order_and_flatten(self):
resp = self.client.get("/api/v1/mdy-plate-order-staging/?limit=10&offset=0")
self.assertEqual(resp.status_code, 200)
payload = resp.json()
self.assertEqual(payload["count"], 2)
first = payload["results"][0]
self.assertEqual(first["mdy_rowid"], "row2") # -ctime 排序
# raw 已按字段定义“可读化”
self.assertIn("raw", first)
self.assertIn("设计编号", first["raw"])
self.assertEqual(first["raw"]["设计编号"], "A002")
self.assertEqual(first["raw"]["款号名称"], "款号B")
# related 以 list[dict] 返回
self.assertIn("related", first)
self.assertEqual(first["related"], [])
def test_filter_by_raw_design_no(self):
resp = self.client.get(
"/api/v1/mdy-plate-order-staging/?limit=10&offset=0&raw__62d52f4b8d2972284492dd0e=A001"
)
self.assertEqual(resp.status_code, 200)
payload = resp.json()
self.assertEqual(payload["count"], 1)
self.assertEqual(payload["results"][0]["mdy_rowid"], "row1")
self.assertEqual(payload["results"][0]["raw"]["设计编号"], "A001")
self.assertEqual(payload["results"][0]["related"][0]["source"], "drawing")

View File

@@ -236,7 +236,7 @@ class PrintingJobListSerializer(serializers.ModelSerializer):
class Meta:
model = models.PrintingJob
fields = [
'id', 'printing_order', 'printing_order_id', 'product', 'product_name',
'id', 'original_id', 'printing_order', 'printing_order_id', 'product', 'product_name',
'quantity', 'unit', 'size', 'pieces', 'description',
'work_state', 'work_state_display',
'status', 'is_completed', 'business_object_id',
@@ -280,7 +280,7 @@ class PrintingJobDetailSerializer(serializers.ModelSerializer):
class Meta:
model = models.PrintingJob
fields = [
'id', 'printing_order', 'printing_order_id', 'product', 'product_name', 'product_code',
'id', 'original_id', 'printing_order', 'printing_order_id', 'product', 'product_name', 'product_code',
'quantity', 'unit', 'size', 'pieces', 'description',
'work_state', 'work_state_display',
'status', 'status_id', 'is_completed', 'has_started', 'business_object_id',
@@ -312,7 +312,7 @@ class PrintingJobCreateUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = models.PrintingJob
fields = [
'id', 'printing_order', 'product', 'quantity', 'unit', 'size', 'pieces', 'description',
'id', 'original_id', 'printing_order', 'product', 'quantity', 'unit', 'size', 'pieces', 'description',
'work_state',
'batch_advance_records',
]
@@ -431,7 +431,7 @@ class PlateOrderListSerializer(PlateOrderDesignCodeMixin, serializers.ModelSeria
model = models.PlateOrder
fields = [
'id', 'design_code', 'plate_type', 'plate_date', 'plate_method',
'id', 'original_id', 'design_code', 'plate_type', 'plate_date', 'plate_method',
'plate_image', 'plate_image_url', 'image_name', 'plate_notes', 'reprint_reason',
'urgency_level', 'is_invalid',
'customer', 'customer_name', 'area', 'default_address',
@@ -504,7 +504,7 @@ class PlateOrderDetailSerializer(PlateOrderDesignCodeMixin, serializers.ModelSer
class Meta:
model = models.PlateOrder
fields = [
'id', 'design_code', 'plate_type', 'plate_date', 'plate_method',
'id', 'original_id', 'design_code', 'plate_type', 'plate_date', 'plate_method',
'plate_image', 'plate_image_url', 'image_name', 'plate_notes', 'reprint_reason',
'urgency_level', 'is_invalid',
'customer', 'customer_name', 'customer_phone', 'area', 'default_address',
@@ -562,7 +562,7 @@ class PlateOrderCreateUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = models.PlateOrder
fields = [
"id", "design_code", "plate_type", "plate_date", "plate_method",
"id", "original_id", "design_code", "plate_type", "plate_date", "plate_method",
"plate_image", "image_name", "plate_notes", "reprint_reason",
"urgency_level", "is_invalid",
"customer", "area", "default_address",