1
0
forked from erp-dev/erp

fix: printing api problem

This commit is contained in:
2026-06-28 21:39:53 +08:00
parent cb6589c884
commit cc4607725c
4 changed files with 154 additions and 3 deletions

View File

@@ -2,6 +2,7 @@
Printing API 序列化器
"""
import json
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import serializers
from rest_framework.fields import empty
@@ -149,8 +150,8 @@ class PlateImageInputSerializer(serializers.Serializer):
class PrintingOrderListSerializer(serializers.ModelSerializer):
"""印染订单列表序列化器"""
customer_name = serializers.CharField(source='customer.name', read_only=True)
customer_phone = serializers.CharField(source='customer.mobile', read_only=True)
customer_name = serializers.SerializerMethodField()
customer_phone = serializers.SerializerMethodField()
process_name = serializers.CharField(source='process.name', read_only=True)
progress = serializers.IntegerField(read_only=True)
jobs_status_summary = serializers.SerializerMethodField()
@@ -177,6 +178,24 @@ class PrintingOrderListSerializer(serializers.ModelSerializer):
]
read_only_fields = ['id', 'human_id', 'created_at', 'updated_at', 'progress', 'print_count', 'merchant_id']
def get_customer_name(self, obj):
if hasattr(obj, 'customer_name'):
return obj.customer_name
try:
customer = getattr(obj, 'customer', None)
except ObjectDoesNotExist:
return None
return getattr(customer, 'name', None)
def get_customer_phone(self, obj):
if hasattr(obj, 'customer_phone'):
return obj.customer_phone
try:
customer = getattr(obj, 'customer', None)
except ObjectDoesNotExist:
return None
return getattr(customer, 'mobile', None)
def get_jobs_status_summary(self, obj):
"""
返回订单下所有 PrintingJob 按当前状态分组的数量汇总

View File

@@ -3,6 +3,7 @@ PrintingOrder API 测试
"""
from datetime import datetime
from django.test import TestCase
from django.db import connection
from django.conf import settings
from django.utils import timezone
from rest_framework.test import APIClient
@@ -170,6 +171,57 @@ class PrintingOrderAPITestCase(TestCase):
# 分页响应格式
self.assertEqual(response.data['count'], 2)
def test_list_pagination_keeps_rows_with_dangling_customer_reference(self):
"""历史脏数据 customer_id 悬空时,列表分页不应被 INNER JOIN 推空。"""
base = timezone.now()
dangling = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='悬空客户订单',
width='150cm',
)
middle = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='中间订单',
width='150cm',
)
oldest = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='最旧订单',
width='150cm',
)
printing_models.PrintingOrder.objects.filter(id=dangling.id).update(
created_at=base + timezone.timedelta(minutes=2),
)
printing_models.PrintingOrder.objects.filter(id=middle.id).update(
created_at=base + timezone.timedelta(minutes=1),
)
printing_models.PrintingOrder.objects.filter(id=oldest.id).update(created_at=base)
missing_customer_id = 99999999
with connection.cursor() as cursor:
cursor.execute(
'UPDATE printing_printingorder SET customer_id = %s WHERE id = %s',
[missing_customer_id, dangling.id],
)
try:
response = self.client.get(
'/api/v1/printing-orders/?limit=1&offset=2&ordering=-created_at',
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 3)
self.assertEqual(len(response.data['results']), 1)
self.assertEqual(response.data['results'][0]['id'], oldest.id)
finally:
with connection.cursor() as cursor:
cursor.execute(
'UPDATE printing_printingorder SET customer_id = %s WHERE id = %s',
[self.customer.id, dangling.id],
)
def test_list_printing_orders_default_excludes_invalid(self):
"""默认不返回作废订单;显式传 is_invalid=true 时可查询作废订单"""
printing_models.PrintingOrder.objects.create(
@@ -443,6 +495,68 @@ class PrintingOrderAPITestCase(TestCase):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('已经标记', response.data['detail'])
def _create_product_for_job(self, name='测试产品'):
category = basic_models.ProductCategory.objects.create(
merchant=self.merchant,
name=f'{name}类别',
product_prefix='JOB',
)
return basic_models.Product.objects.create(
merchant=self.merchant,
category=category,
name=name,
unit=basic_models.ProductUnitEnum.METER,
)
def test_printing_order_ids_returns_job_ids(self):
"""测试获取订单下所有 PrintingJob ID。"""
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm',
)
product = self._create_product_for_job()
job1 = printing_models.PrintingJob.objects.create(
merchant=self.merchant,
printing_order=order,
product=product,
quantity=10,
unit='',
)
job2 = printing_models.PrintingJob.objects.create(
merchant=self.merchant,
printing_order=order,
product=product,
quantity=20,
unit='',
)
response = self.client.get(f'/api/v1/printing-orders/{order.id}/ids/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, {'ids': [job1.id, job2.id]})
def test_printing_order_ids_returns_empty_list_when_no_jobs(self):
"""订单存在但没有任务时返回空数组。"""
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm',
)
response = self.client.get(f'/api/v1/printing-orders/{order.id}/ids/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, {'ids': []})
def test_printing_order_ids_returns_404_when_order_missing(self):
"""订单不存在时返回 404。"""
response = self.client.get('/api/v1/printing-orders/99999999/ids/')
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_filter_by_customer(self):
"""测试按客户过滤"""
customer2 = basic_models.Customer.objects.create(

View File

@@ -24,6 +24,7 @@ from datetime import datetime, time, timedelta
from flower.viewsets import LimitedModelViewSet
from printing import models
from basic_info import models as basic_models
from basic_info.models import MerchantTypeEnum
from .serializers import (
@@ -301,8 +302,16 @@ class PrintingOrderViewSet(CustomerVisibilityFilterMixin, LimitedModelViewSet):
):
queryset = queryset.filter(is_invalid=False)
if self.action in ["list", "retrieve", "by_external_order_id"]:
if self.action == "list":
customer_qs = basic_models.Customer.objects.filter(id=OuterRef("customer_id"))
queryset = queryset.annotate(
customer_name=Subquery(customer_qs.values("name")[:1]),
customer_phone=Subquery(customer_qs.values("mobile")[:1]),
)
elif self.action in ["retrieve", "by_external_order_id"]:
queryset = queryset.select_related("customer")
if self.action in ["list", "retrieve", "by_external_order_id"]:
# 预取 printing_jobs 及其 business_object用于状态汇总统计
# 同时预取 state_logs 和 process 相关数据以减少 job.status 属性调用时的 N+1 查询
queryset = queryset.prefetch_related(
@@ -350,6 +359,15 @@ class PrintingOrderViewSet(CustomerVisibilityFilterMixin, LimitedModelViewSet):
serializer = self.get_serializer(printing_order)
return Response(serializer.data)
@action(detail=True, methods=["get"], url_path="ids")
def ids(self, request, pk=None):
"""返回该印染订单下所有 PrintingJob ID。"""
printing_order = self.get_object()
ids = list(
printing_order.printing_jobs.order_by("id").values_list("id", flat=True)
)
return Response({"ids": ids})
@action(detail=True, methods=["post"])
def invalidate(self, request, pk=None):
"""