forked from erp-dev/erp
fix: printing api problem
This commit is contained in:
@@ -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 按当前状态分组的数量汇总
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user