1
0
forked from erp-dev/erp

feat: added jobs_status_summary to printing orders api

This commit is contained in:
2026-01-05 19:35:45 +08:00
parent d13d402d98
commit 970bbcc070
3 changed files with 147 additions and 0 deletions

View File

@@ -137,6 +137,7 @@ class PrintingOrderListSerializer(serializers.ModelSerializer):
customer_phone = serializers.CharField(source='customer.mobile', read_only=True)
process_name = serializers.CharField(source='process.name', read_only=True)
progress = serializers.IntegerField(read_only=True)
jobs_status_summary = serializers.SerializerMethodField()
class Meta:
model = models.PrintingOrder
@@ -145,10 +146,38 @@ class PrintingOrderListSerializer(serializers.ModelSerializer):
'fabric', 'width', 'is_urgent', 'area', 'address', 'curve',
'is_fabric_received', 'outgoing_date', 'is_invalid', 'new_curve',
'process', 'process_name', 'progress', 'position', 'print_count',
'jobs_status_summary',
'created_at', 'updated_at',
]
read_only_fields = ['id', 'human_id', 'created_at', 'updated_at', 'progress', 'print_count']
def get_jobs_status_summary(self, obj):
"""
返回订单下所有 PrintingJob 按当前状态分组的数量汇总
返回格式: [{"state_name": "进度一", "state_id": 1, "count": 3}, ...]
- state_name: 状态名称(已完成的返回"已完成"
- state_id: 状态ID已完成的返回 None
- count: 该状态下的 job 数量
"""
# 使用预取的 printing_jobs避免 N+1
jobs = getattr(obj, '_prefetched_objects_cache', {}).get('printing_jobs') or obj.printing_jobs.all()
from collections import Counter
status_counter = Counter()
for job in jobs:
# 获取 job 的当前状态显示名称
status_name = job.status # 调用 @property返回状态名或"已完成"
status_id = job.status_id # 状态ID已完成时为 None
status_counter[(status_name, status_id)] += 1
# 转换为列表格式
return [
{'state_name': name, 'state_id': sid, 'count': count}
for (name, sid), count in status_counter.items()
]
class PrintingOrderDetailSerializer(serializers.ModelSerializer):
"""印染订单详情序列化器"""

View File

@@ -18,6 +18,10 @@ class PrintingOrderAPITestCase(TestCase):
"""测试 PrintingOrder API"""
def setUp(self):
# 清除缓存,避免 cache_page 装饰器导致的测试干扰
from django.core.cache import cache
cache.clear()
self.client = APIClient()
# 创建商户
@@ -539,3 +543,98 @@ class PrintingOrderAPITestCase(TestCase):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('已开始', str(response.data))
def test_jobs_status_summary_in_list(self):
"""测试订单列表包含 jobs_status_summary 字段PrintingJob 状态汇总)"""
# 创建订单
order = printing_models.PrintingOrder.objects.create(
customer=self.customer,
fabric='测试布料',
width='150cm',
process=self.process,
)
# 创建产品类别和产品
category = basic_models.ProductCategory.objects.create(
name='测试类别',
merchant=self.merchant,
)
product = basic_models.Product.objects.create(
name='测试产品',
category=category,
merchant=self.merchant,
)
# 创建 3 个任务
jobs = []
for i in range(3):
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-test-{i}',
process=self.process,
)
job = printing_models.PrintingJob.objects.create(
printing_order=order,
product=product,
quantity=10,
unit='',
business_object=business_object,
)
jobs.append(job)
# 推进第一个任务的状态(完成第一个节点)
from stateflow.services import advance_to_next_state
advance_to_next_state(jobs[0].business_object, self.user)
# 推进第二个任务的状态两次(完成前两个节点)
advance_to_next_state(jobs[1].business_object, self.user)
advance_to_next_state(jobs[1].business_object, self.user)
# 第三个任务保持初始状态(待印染)
# 获取订单列表
response = self.client.get('/api/v1/printing-orders/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 检查 jobs_status_summary 字段存在
self.assertIn('jobs_status_summary', response.data['results'][0])
summary = response.data['results'][0]['jobs_status_summary']
# 检查返回格式(列表)
self.assertIsInstance(summary, list)
# 汇总结果应该包含不同状态的计数
# - job0: 完成了第一个节点,下一个待执行是 "印染中"
# - job1: 完成了前两个节点,下一个待执行是 "已完成"
# - job2: 未开始,下一个待执行是 "待印染"
state_names = {item['state_name']: item['count'] for item in summary}
# 验证有3个任务被正确统计
total_count = sum(item['count'] for item in summary)
self.assertEqual(total_count, 3)
# 验证返回的每个项都包含必要字段
for item in summary:
self.assertIn('state_name', item)
self.assertIn('state_id', item)
self.assertIn('count', item)
def test_jobs_status_summary_empty_when_no_jobs(self):
"""测试没有任务时 jobs_status_summary 为空列表"""
order = printing_models.PrintingOrder.objects.create(
customer=self.customer,
fabric='无任务订单',
width='150cm',
process=self.process,
)
# 获取特定订单详情(避免被其他测试数据影响)
response = self.client.get(f'/api/v1/printing-orders/{order.id}/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 详情接口不包含 jobs_status_summary这是列表专用字段
# 改用列表接口并按特定条件过滤
response = self.client.get('/api/v1/printing-orders/', {'fabric': '无任务订单'})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 1)
summary = response.data['results'][0]['jobs_status_summary']
self.assertEqual(summary, [])

View File

@@ -123,6 +123,10 @@ class PrintingOrderViewSet(LimitedModelViewSet):
- created_date_to: 创建日期结束
- search: 全文搜索(客户名称、面料、地区、工艺)
- ordering: 排序字段
返回字段说明:
- jobs_status_summary: 订单下所有印染任务按当前状态分组的数量汇总
格式: [{"state_name": "进度一", "state_id": 1, "count": 3}, ...]
"""
queryset = models.PrintingOrder.objects.all()
permission_classes = [DjangoModelPermissions]
@@ -157,6 +161,21 @@ class PrintingOrderViewSet(LimitedModelViewSet):
queryset = super().get_queryset()
if self.action in ['list', 'retrieve']:
queryset = queryset.select_related('customer')
# 预取 printing_jobs 及其 business_object用于状态汇总统计
# 同时预取 state_logs 和 process 相关数据以减少 job.status 属性调用时的 N+1 查询
queryset = queryset.prefetch_related(
Prefetch(
'printing_jobs',
queryset=models.PrintingJob.objects.select_related(
'business_object',
'business_object__process',
).prefetch_related(
'business_object__state_logs',
'business_object__process__process_nodes',
'business_object__process__process_nodes__state',
)
)
)
return queryset
def destroy(self, request, *args, **kwargs):