From 5b07332015c8a4036977131e8c37dcf85a88698c Mon Sep 17 00:00:00 2001 From: colaftc Date: Wed, 7 Jan 2026 15:06:55 +0800 Subject: [PATCH] feat: added new api for query batch_advance_records --- api_v2/tests.py | 138 +++++++++++++++++ api_v2/urls.py | 2 + api_v2/views/__init__.py | 2 + api_v2/views/printing.py | 113 ++++++++++++++ ...v2_printing_order_batch_advance_records.md | 145 ++++++++++++++++++ 5 files changed, 400 insertions(+) create mode 100644 docs/api_v2_printing_order_batch_advance_records.md diff --git a/api_v2/tests.py b/api_v2/tests.py index f3e7f30..47551a8 100644 --- a/api_v2/tests.py +++ b/api_v2/tests.py @@ -1516,3 +1516,141 @@ class MyVisiblePagesAPITest(TestCase): self.client.logout() response = self.client.get('/api/v2/me/visible-pages/') self.assertEqual(response.status_code, 401) + + +class PrintingOrderBatchAdvanceRecordsAPITest(TestCase): + """测试印染订单批量推进记录查询 API""" + + def setUp(self): + self.client = APIClient() + self.merchant = basic_models.Merchant.objects.create( + name='测试商户', + type=basic_models.MerchantTypeEnum.FACTORY, + ) + self.user = get_user_model().objects.create_user(username='testuser', password='pass12345') + self.client.force_authenticate(user=self.user) + + # 创建客户 + self.customer = basic_models.Customer.objects.create( + merchant=self.merchant, + name='测试客户' + ) + + # 创建产品类别和产品 + self.category = basic_models.ProductCategory.objects.create( + merchant=self.merchant, + name='测试类别' + ) + self.product = basic_models.Product.objects.create( + merchant=self.merchant, + name='测试产品', + category=self.category, + unit=basic_models.ProductUnitEnum.METER + ) + + # 创建印染订单 + self.printing_order = printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='测试面料', + width='150cm' + ) + + # 创建印染明细 + self.job1 = printing_models.PrintingJob.objects.create( + printing_order=self.printing_order, + product=self.product, + quantity=100, + unit='米' + ) + self.job2 = printing_models.PrintingJob.objects.create( + printing_order=self.printing_order, + product=self.product, + quantity=200, + unit='米' + ) + + # 创建流程状态 + self.state = stateflow_models.State.objects.create( + name='染色', + description='染色工序' + ) + + # 创建批量推进记录 + self.record = printing_models.PrintingJobBatchAdvanceRecord.objects.create( + printing_order=self.printing_order, + state=self.state, + created_by=self.user, + parameters={'temperature': '25.5', 'operator': '张三'} + ) + self.record.printing_jobs.add(self.job1, self.job2) + + def test_get_batch_advance_records_success(self): + """测试成功获取批量推进记录""" + response = self.client.get(f'/api/v2/printing-orders/{self.printing_order.id}/batch-advance-records/') + self.assertEqual(response.status_code, 200) + + data = response.data + self.assertEqual(data['printing_order_id'], self.printing_order.id) + self.assertEqual(data['total_count'], 1) + self.assertEqual(len(data['records']), 1) + + record = data['records'][0] + self.assertEqual(record['id'], self.record.id) + self.assertEqual(record['state_id'], self.state.id) + self.assertEqual(record['state_name'], '染色') + self.assertEqual(record['created_by'], self.user.id) + self.assertEqual(record['created_by_username'], 'testuser') + self.assertEqual(record['parameters'], {'temperature': '25.5', 'operator': '张三'}) + self.assertEqual(record['printing_job_count'], 2) + self.assertIn(self.job1.id, record['printing_job_ids']) + self.assertIn(self.job2.id, record['printing_job_ids']) + + def test_get_batch_advance_records_empty(self): + """测试没有批量推进记录时返回空列表""" + # 创建另一个没有记录的订单 + another_order = printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='测试面料2', + width='160cm' + ) + + response = self.client.get(f'/api/v2/printing-orders/{another_order.id}/batch-advance-records/') + self.assertEqual(response.status_code, 200) + + data = response.data + self.assertEqual(data['printing_order_id'], another_order.id) + self.assertEqual(data['total_count'], 0) + self.assertEqual(data['records'], []) + + def test_get_batch_advance_records_order_not_found(self): + """测试订单不存在时返回 404""" + response = self.client.get('/api/v2/printing-orders/99999/batch-advance-records/') + self.assertEqual(response.status_code, 404) + self.assertIn('error', response.data) + + def test_get_batch_advance_records_multiple_records(self): + """测试多条批量推进记录按时间倒序返回""" + # 创建第二条记录 + state2 = stateflow_models.State.objects.create(name='整理', description='整理工序') + record2 = printing_models.PrintingJobBatchAdvanceRecord.objects.create( + printing_order=self.printing_order, + state=state2, + created_by=self.user, + parameters={'notes': '完成'} + ) + record2.printing_jobs.add(self.job1) + + response = self.client.get(f'/api/v2/printing-orders/{self.printing_order.id}/batch-advance-records/') + self.assertEqual(response.status_code, 200) + + data = response.data + self.assertEqual(data['total_count'], 2) + # 最新的记录应该在前面 + self.assertEqual(data['records'][0]['id'], record2.id) + self.assertEqual(data['records'][1]['id'], self.record.id) + + def test_get_batch_advance_records_unauthenticated(self): + """测试未登录时返回 401""" + self.client.logout() + response = self.client.get(f'/api/v2/printing-orders/{self.printing_order.id}/batch-advance-records/') + self.assertEqual(response.status_code, 401) diff --git a/api_v2/urls.py b/api_v2/urls.py index b4b83bb..9203364 100644 --- a/api_v2/urls.py +++ b/api_v2/urls.py @@ -11,6 +11,7 @@ from api_v2.views import ( PlateOrderByProcessView, PlateOrderByStateStatusView, PlateOrderBatchUpdateView, + PrintingOrderBatchAdvanceRecordsView, BusinessObjectCloneView, ) from api_v2.views.basic_info import CustomerEmployeeBindingView, MyVisiblePagesView @@ -24,6 +25,7 @@ urlpatterns = [ path('printing-jobs/by-customer/', PrintingJobByCustomerView.as_view(), name='api_v2_printing_job_by_customer'), path('printing-jobs/batch-advance/preview/', PrintingJobBatchAdvancePreviewView.as_view(), name='api_v2_printing_job_batch_advance_preview'), path('printing-jobs/batch-advance/', PrintingJobBatchAdvanceSubmitView.as_view(), name='api_v2_printing_job_batch_advance_submit'), + path('printing-orders//batch-advance-records/', PrintingOrderBatchAdvanceRecordsView.as_view(), name='api_v2_printing_order_batch_advance_records'), path('plate-orders/by-process-node/', PlateOrderByProcessNodeView.as_view(), name='api_v2_plate_order_by_process_node'), path('plate-orders/by-process/', PlateOrderByProcessView.as_view(), name='api_v2_plate_order_by_process'), path('plate-orders/by-state-status/', PlateOrderByStateStatusView.as_view(), name='api_v2_plate_order_by_state_status'), diff --git a/api_v2/views/__init__.py b/api_v2/views/__init__.py index c93987d..d1292ea 100644 --- a/api_v2/views/__init__.py +++ b/api_v2/views/__init__.py @@ -13,6 +13,7 @@ from .printing import ( PlateOrderByProcessView, PlateOrderByStateStatusView, PlateOrderBatchUpdateView, + PrintingOrderBatchAdvanceRecordsView, ) from .stateflow import BusinessObjectCloneView @@ -28,6 +29,7 @@ __all__ = [ 'PlateOrderByProcessView', 'PlateOrderByStateStatusView', 'PlateOrderBatchUpdateView', + 'PrintingOrderBatchAdvanceRecordsView', 'BusinessObjectCloneView', ] diff --git a/api_v2/views/printing.py b/api_v2/views/printing.py index bafcbfd..1356b59 100644 --- a/api_v2/views/printing.py +++ b/api_v2/views/printing.py @@ -1345,3 +1345,116 @@ class PlateOrderBatchUpdateView(APIView): "plate_order_ids": found_ids, } ) + + +class BatchAdvanceRecordSerializer(serializers.ModelSerializer): + """批量推进记录序列化器""" + + state_id = serializers.IntegerField(source='state.id', read_only=True) + state_name = serializers.CharField(source='state.name', read_only=True) + created_by_username = serializers.CharField(source='created_by.username', read_only=True, allow_null=True) + created_by_name = serializers.SerializerMethodField() + printing_job_ids = serializers.SerializerMethodField() + printing_job_count = serializers.SerializerMethodField() + + class Meta: + model = printing_models.PrintingJobBatchAdvanceRecord + fields = [ + 'id', + 'printing_order', + 'state', + 'state_id', + 'state_name', + 'created_by', + 'created_by_username', + 'created_by_name', + 'parameters', + 'printing_job_ids', + 'printing_job_count', + 'created_at', + ] + read_only_fields = fields + + def get_created_by_name(self, obj): + """获取操作人员工姓名""" + user = getattr(obj, 'created_by', None) + if not user: + return None + emp = getattr(user, 'employee', None) + return getattr(emp, 'name', None) + + def get_printing_job_ids(self, obj): + """获取涉及的印染明细 ID 列表""" + # 使用预取的数据避免 N+1 + if hasattr(obj, '_prefetched_objects_cache') and 'printing_jobs' in obj._prefetched_objects_cache: + return [job.id for job in obj.printing_jobs.all()] + return list(obj.printing_jobs.values_list('id', flat=True)) + + def get_printing_job_count(self, obj): + """获取涉及的印染明细数量""" + if hasattr(obj, '_prefetched_objects_cache') and 'printing_jobs' in obj._prefetched_objects_cache: + return len(obj.printing_jobs.all()) + return obj.printing_jobs.count() + + +class PrintingOrderBatchAdvanceRecordsView(APIView): + """ + 获取印染订单的批量推进记录 + + 根据 printing_order_id 查询该订单下所有的批量推进审计记录。 + + GET /api/v2/printing-orders/{printing_order_id}/batch-advance-records/ + + 响应示例: + { + "printing_order_id": 55, + "records": [ + { + "id": 9, + "printing_order": 55, + "state": 7, + "state_id": 7, + "state_name": "染色", + "created_by": 1001, + "created_by_username": "factory_user", + "created_by_name": "张三", + "parameters": {"temperature": "25.5"}, + "printing_job_ids": [101, 102, 103], + "printing_job_count": 3, + "created_at": "2025-12-14T10:00:00+08:00" + } + ], + "total_count": 1 + } + """ + + permission_classes = [permissions.IsAuthenticated] + + def get(self, request, printing_order_id): + # 验证订单是否存在 + try: + printing_order = printing_models.PrintingOrder.objects.get(id=printing_order_id) + except printing_models.PrintingOrder.DoesNotExist: + return Response( + {'error': f'未找到 ID 为 {printing_order_id} 的印染订单'}, + status=status.HTTP_404_NOT_FOUND + ) + + # 查询批量推进记录,按创建时间倒序 + records = printing_models.PrintingJobBatchAdvanceRecord.objects.filter( + printing_order_id=printing_order_id + ).select_related( + 'state', + 'created_by', + 'created_by__employee', + ).prefetch_related( + 'printing_jobs', + ).order_by('-created_at') + + serializer = BatchAdvanceRecordSerializer(records, many=True) + + return Response({ + 'printing_order_id': printing_order_id, + 'records': serializer.data, + 'total_count': records.count(), + }) diff --git a/docs/api_v2_printing_order_batch_advance_records.md b/docs/api_v2_printing_order_batch_advance_records.md new file mode 100644 index 0000000..2c51db8 --- /dev/null +++ b/docs/api_v2_printing_order_batch_advance_records.md @@ -0,0 +1,145 @@ +# 获取印染订单批量推进记录 API + +## 概述 + +根据印染订单ID查询该订单下所有的批量推进审计记录。 + +- **端点**: `GET /api/v2/printing-orders/{printing_order_id}/batch-advance-records/` +- **认证**: 需要登录 +- **权限**: 任意已认证用户 + +## 背景 + +`PrintingJob`(印染订单明细)在批量推进流程时,会产生 `PrintingJobBatchAdvanceRecord` 审计记录。此 API 用于查询某个印染订单下的所有批量推进历史。 + +## 请求 + +### 路径参数 + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `printing_order_id` | int | 是 | 印染订单ID | + +### 请求示例 + +```bash +curl -X GET "http://localhost:8000/api/v2/printing-orders/55/batch-advance-records/" \ + -H "Authorization: Token " +``` + +## 响应 + +### 成功响应 (200 OK) + +```json +{ + "printing_order_id": 55, + "records": [ + { + "id": 9, + "printing_order": 55, + "state": 7, + "state_id": 7, + "state_name": "染色", + "created_by": 1001, + "created_by_username": "factory_user", + "created_by_name": "张三", + "parameters": { + "temperature": "25.5", + "operator": "张三" + }, + "printing_job_ids": [101, 102, 103], + "printing_job_count": 3, + "created_at": "2025-12-14T10:00:00+08:00" + } + ], + "total_count": 1 +} +``` + +### 响应字段说明 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `printing_order_id` | int | 印染订单ID | +| `records` | array | 批量推进记录列表(按创建时间倒序) | +| `total_count` | int | 记录总数 | + +### records 对象结构 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | int | 批量推进记录ID | +| `printing_order` | int | 印染订单ID | +| `state` | int | 完成的流程节点ID | +| `state_id` | int | 同 `state`(便于前端直接取用) | +| `state_name` | string | 流程节点名称 | +| `created_by` | int \| null | 操作人用户ID | +| `created_by_username` | string \| null | 操作人用户名 | +| `created_by_name` | string \| null | 操作人员工姓名 | +| `parameters` | object | 批量推进时提交的工艺参数 | +| `printing_job_ids` | array[int] | 涉及的印染明细ID列表 | +| `printing_job_count` | int | 涉及的印染明细数量 | +| `created_at` | string | 创建时间(ISO8601) | + +### 无记录时的响应 + +```json +{ + "printing_order_id": 100, + "records": [], + "total_count": 0 +} +``` + +### 错误响应 + +#### 订单不存在 (404 Not Found) + +```json +{ + "error": "未找到 ID 为 999 的印染订单" +} +``` + +#### 未认证 (401 Unauthorized) + +```json +{ + "detail": "Authentication credentials were not provided." +} +``` + +## 前端使用示例 + +```javascript +// 获取印染订单的批量推进记录 +async function getBatchAdvanceRecords(printingOrderId) { + const response = await fetch( + `/api/v2/printing-orders/${printingOrderId}/batch-advance-records/`, + { headers: { 'Authorization': `Token ${token}` } } + ); + const data = await response.json(); + + // 显示每次批量推进的参数和涉及的明细 + data.records.forEach(record => { + console.log(`${record.state_name}: ${record.printing_job_count} 条明细`); + console.log('参数:', record.parameters); + }); + + return data; +} +``` + +## 相关文档 + +- [批量推进记录字段说明](./batch_submit_state_params.md) - `api_v1` 中的 `batch_advance_records` 字段说明 +- 批量推进接口: `POST /api/v2/printing-jobs/batch-advance/` + +## 相关模型 + +- `printing.PrintingJobBatchAdvanceRecord` - 批量推进记录 +- `printing.PrintingOrder` - 印染订单 +- `printing.PrintingJob` - 印染明细 +- `stateflow.State` - 流程节点 +