From 1af18c9388aed460e4f918dcd142f4493c377c89 Mon Sep 17 00:00:00 2001 From: colaftc Date: Fri, 30 Jan 2026 21:08:38 +0800 Subject: [PATCH] fix: order_quantity --- api_v1/tests.py | 12 +++++++++ api_v1/views/printing/test_api.py | 27 +++++++++++++++++++ api_v1/views/printing/test_plate_order_api.py | 9 +++++++ api_v1/views/printing/views.py | 12 +++++++++ business/services.py | 18 +++++++++++++ 5 files changed, 78 insertions(+) diff --git a/api_v1/tests.py b/api_v1/tests.py index db71be6..fbc7ad1 100644 --- a/api_v1/tests.py +++ b/api_v1/tests.py @@ -507,6 +507,18 @@ class SalesOrderAPITestCase(TestCase): expected_human_id = f"XS{order.created_at.strftime('%Y%m%d')}{order.id:06d}" self.assertEqual(response.data['human_id'], expected_human_id) + def test_get_sales_order_detail_includes_order_quantity_on_items(self): + payload = copy.deepcopy(self.strict_in_payload) + payload['items'][0]['order_quantity'] = 88 + order_id = self._create_sales_order(payload) + + response = self.client.get(f'/api/v1/sales-orders/{order_id}/') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('items', response.data) + self.assertEqual(len(response.data['items']), 1) + self.assertIn('order_quantity', response.data['items'][0]) + self.assertEqual(response.data['items'][0]['order_quantity'], 88) + def test_update_sales_order_rejects_non_pending(self): order_id = self._create_sales_order(self.strict_in_payload) self.client.post( diff --git a/api_v1/views/printing/test_api.py b/api_v1/views/printing/test_api.py index 3fe4fc7..2cd1cff 100644 --- a/api_v1/views/printing/test_api.py +++ b/api_v1/views/printing/test_api.py @@ -169,6 +169,33 @@ class PrintingOrderAPITestCase(TestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) # 分页响应格式 self.assertEqual(response.data['count'], 2) + + def test_list_printing_orders_default_excludes_invalid(self): + """默认不返回作废订单;显式传 is_invalid=true 时可查询作废订单""" + printing_models.PrintingOrder.objects.create( + merchant=self.merchant, + customer=self.customer, + fabric='正常订单', + width='150cm', + is_invalid=False, + ) + printing_models.PrintingOrder.objects.create( + merchant=self.merchant, + customer=self.customer, + fabric='作废订单', + width='150cm', + is_invalid=True, + ) + + response = self.client.get('/api/v1/printing-orders/') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['count'], 1) + self.assertEqual(response.data['results'][0]['is_invalid'], False) + + response = self.client.get('/api/v1/printing-orders/?is_invalid=true') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['count'], 1) + self.assertEqual(response.data['results'][0]['is_invalid'], True) def test_retrieve_printing_order(self): """测试获取订单详情""" diff --git a/api_v1/views/printing/test_plate_order_api.py b/api_v1/views/printing/test_plate_order_api.py index c2829dd..1f7b405 100644 --- a/api_v1/views/printing/test_plate_order_api.py +++ b/api_v1/views/printing/test_plate_order_api.py @@ -540,6 +540,15 @@ class PlateOrderAPITestCase(TestCase): else: self.assertEqual(len(response.data), 1) self.assertEqual(response.data[0]['is_invalid'], False) + + response = self.client.get('/api/v1/plate-orders/?is_invalid=true') + self.assertEqual(response.status_code, status.HTTP_200_OK) + if isinstance(response.data, dict): + self.assertEqual(response.data['count'], 1) + self.assertEqual(response.data['results'][0]['is_invalid'], True) + else: + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0]['is_invalid'], True) def test_filter_by_plate_at_from(self): """测试按开版时间起始筛选(带时分秒)""" diff --git a/api_v1/views/printing/views.py b/api_v1/views/printing/views.py index 3295cce..2619e2a 100644 --- a/api_v1/views/printing/views.py +++ b/api_v1/views/printing/views.py @@ -224,6 +224,12 @@ class PrintingOrderViewSet(CustomerVisibilityFilterMixin, LimitedModelViewSet): def get_queryset(self): """优化查询""" queryset = super().get_queryset() + + # 默认不返回作废订单(仅 list);如显式传入 is_invalid 参数,则交由 FilterSet 处理 + # 注意:不能影响 activate/invalidate 等 action,否则会因 get_object() 被过滤而 404。 + if self.action == 'list' and self.request and 'is_invalid' not in self.request.query_params: + queryset = queryset.filter(is_invalid=False) + if self.action in ['list', 'retrieve']: queryset = queryset.select_related('customer') # 预取 printing_jobs 及其 business_object,用于状态汇总统计 @@ -798,6 +804,12 @@ class PlateOrderViewSet(CustomerVisibilityFilterMixin, LimitedModelViewSet): def get_queryset(self): """优化查询""" queryset = super().get_queryset() + + # 默认不返回作废订单(仅 list);如显式传入 is_invalid 参数,则交由 FilterSet 处理 + # 注意:不能影响 activate/invalidate 等 action,否则会因 get_object() 被过滤而 404。 + if self.action == 'list' and self.request and 'is_invalid' not in self.request.query_params: + queryset = queryset.filter(is_invalid=False) + if self.action in ['list', 'retrieve']: queryset = queryset.select_related('customer', 'salesperson', 'merchandiser', 'business_object') # 为搜索提供 design_code 的兜底(为空时使用主键字符串) diff --git a/business/services.py b/business/services.py index 85cd83a..78b181f 100644 --- a/business/services.py +++ b/business/services.py @@ -349,6 +349,7 @@ def create_sales_order( num_of_rolls=item_data['num_of_rolls'], consume_detail_ids=item_data.get('consume_detail_ids'), batch_number=item_data.get('batch_number'), + order_quantity=item_data.get('order_quantity'), remarks=item_data.get('remarks'), printing_job=item_data.get('printing_job'), ) @@ -418,6 +419,7 @@ def update_sales_order( num_of_rolls=item_data['num_of_rolls'], consume_detail_ids=item_data.get('consume_detail_ids'), batch_number=item_data.get('batch_number'), + order_quantity=item_data.get('order_quantity'), remarks=item_data.get('remarks'), printing_job=item_data.get('printing_job'), ) @@ -1059,6 +1061,10 @@ def _normalize_order_items( batch_number = raw_item.get('batch_number') remarks = raw_item.get('remarks') spec = raw_item.get('spec') + order_quantity = _to_non_negative_int_or_none( + raw_item.get('order_quantity'), + f'items[{index}].order_quantity', + ) unit = raw_item.get('unit') or product.get_unit_display() or '米' printing_job = None raw_printing_job_id = raw_item.get('printing_job') or raw_item.get('printing_job_id') @@ -1154,6 +1160,7 @@ def _normalize_order_items( 'batch_number': batch_number, 'remarks': remarks, 'spec': spec, + 'order_quantity': order_quantity, 'consume_detail_ids': consume_detail_ids_str, 'printing_job': printing_job, }) @@ -1755,6 +1762,17 @@ def _to_positive_int(value, field_name: str) -> int: return int(decimal_value) +def _to_non_negative_int_or_none(value, field_name: str) -> int | None: + if value in (None, ''): + return None + decimal_value = _to_decimal(value, field_name) + if decimal_value < 0: + raise ValueError(f'{field_name} 不能小于 0') + if decimal_value != decimal_value.to_integral_value(): + raise ValueError(f'{field_name} 必须为整数') + return int(decimal_value) + + # ==================== Statement Builders ==================== _STATEMENT_TWO_PLACES = Decimal('0.01')