From a9f1e8f18aac5af721d629b241c9d517a28d8725 Mon Sep 17 00:00:00 2001 From: colaftc Date: Fri, 6 Feb 2026 19:30:57 +0800 Subject: [PATCH] feat: change api/v2/plate-orders/by-process-node/ logic ***** danger --- api_v2/tests.py | 22 +++++ api_v2/views/printing.py | 13 +++ ...plate_orders_by_process_node_2026-02-06.md | 84 +++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 docs/api_v2_plate_orders_by_process_node_2026-02-06.md diff --git a/api_v2/tests.py b/api_v2/tests.py index e9ebbc2..9cb8090 100644 --- a/api_v2/tests.py +++ b/api_v2/tests.py @@ -824,6 +824,28 @@ class PlateOrderByProcessNodeV2APITest(TestCase): self.assertEqual(resp.data['count'], 2) self.assertEqual(len(resp.data['results']), 1) + def test_param_filter_requires_both_key_and_value(self): + resp = self.client.get(self.url, {'process_node_id': self.node2.id, 'param_key': 'temperature'}) + self.assertEqual(resp.status_code, 400) + self.assertIn('param_key', resp.data.get('detail', '')) + + resp = self.client.get(self.url, {'process_node_id': self.node2.id, 'param_value': '30'}) + self.assertEqual(resp.status_code, 400) + self.assertIn('param_value', resp.data.get('detail', '')) + + def test_param_filter_ignores_cancelled_state_logs(self): + resp = self.client.get( + self.url, + { + 'process_node_id': self.node2.id, + 'param_key': 'temperature', + 'param_value': '30', + }, + ) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.data['count'], 0) + self.assertEqual(resp.data['results'], []) + class PlateOrderByProcessV2APITest(TestCase): def setUp(self): diff --git a/api_v2/views/printing.py b/api_v2/views/printing.py index ea33197..98e21b7 100644 --- a/api_v2/views/printing.py +++ b/api_v2/views/printing.py @@ -484,8 +484,12 @@ class PlateOrderByProcessNodeView(APIView): def get(self, request): qp = request.query_params process_node_id = qp.get('process_node_id') + param_key = (qp.get('param_key') or '').strip() + param_value = (qp.get('param_value') or '').strip() if not process_node_id: return Response({'detail': 'process_node_id 为必填参数'}, status=status.HTTP_400_BAD_REQUEST) + if (param_key and not param_value) or (param_value and not param_key): + return Response({'detail': 'param_key 与 param_value 必须同时提供'}, status=status.HTTP_400_BAD_REQUEST) try: process_node_id_int = int(process_node_id) @@ -545,6 +549,15 @@ class PlateOrderByProcessNodeView(APIView): ) ).filter(completed_target=0) + if param_key and param_value: + matching_logs = stateflow_models.StateFlowRecord.objects.filter( + business_object_id=OuterRef('business_object_id'), + state_id=target_state_id, + is_cancelled=False, + parameter_records__parameters__contains={param_key: param_value}, + ) + queryset = queryset.annotate(_has_state_param=Exists(matching_logs)).filter(_has_state_param=True) + # search:同时支持主键与 design_code icontains(不新增额外参数) search = (qp.get('search') or '').strip() if search: diff --git a/docs/api_v2_plate_orders_by_process_node_2026-02-06.md b/docs/api_v2_plate_orders_by_process_node_2026-02-06.md new file mode 100644 index 0000000..5026497 --- /dev/null +++ b/docs/api_v2_plate_orders_by_process_node_2026-02-06.md @@ -0,0 +1,84 @@ +# API 文档:按流程节点查询开版订单(v2) + +日期:2026-02-06 + +## 接口信息 +- Method:GET +- Path:/api/v2/plate-orders/by-process-node/ +- 权限:已登录 + 印染工厂用户(IsPrintingFactory) + +## 查询参数 +必填: +- process_node_id:int,流程节点 ID(ProcessNode.id) + +可选: +- search:string + - 纯数字:同时匹配 id 精确 + design_code 模糊 + - 非纯数字:design_code 模糊 +- ordering:string,默认 -created_at + - 可选字段:id / created_at / updated_at / design_code +- limit:int,分页大小(默认 20) +- offset:int,分页起始偏移 +- param_key:string,与 param_value 搭配使用 +- param_value:string,与 param_key 搭配使用 + +### param_key / param_value 过滤说明 +- 必须成对提供,否则返回 400。 +- 过滤语义:存在任意一次“未撤销”的目标节点状态日志(StateFlowRecord.is_cancelled=false),其参数记录包含 {param_key: param_value} 即命中。 + +## 返回结构 +``` +{ + "process_node": { + "id": 1, + "process_id": 10, + "state_id": 5, + "state_name": "调色", + "order": 1 + }, + "parameters": [ + {"key": "temperature", "value": null} + ], + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "id": 100, + "original_id": null, + "design_code": "PO-001", + "plate_image": [{"file_id": 1, "name": "封面", "path": "/media/xxx", "url": "..."}], + "customer": 200, + "customer_name": "客户A", + "style_name": "款式A", + "urgency_level": "正常", + "is_invalid": false, + "business_object_id": 300, + "created_by": 400, + "process_parameters": [ + {"key": "temperature", "value": "30"} + ], + "created_at": "2026-02-06T10:00:00+08:00", + "updated_at": "2026-02-06T10:00:00+08:00" + } + ] +} +``` + +## 字段说明(results[*]) +- plate_image:仅返回第一个图片条目(最多 1 个),来源为 PlateOrder.plate_image。 +- process_parameters:订单维度参数,按当前节点的参数模板输出 key/value。 + - 若该订单未提交过该节点参数,则 value 为 null。 + +## 校验与错误 +- 缺少 process_node_id:400 +- process_node_id 非数字:400 +- process_node_id 不存在:404 +- ordering 不在允许范围:400 +- param_key/param_value 只提供一个:400 + +## 备注(查询流程) +- 起点为 PlateOrder 查询,再通过 business_object 的状态日志进行筛选。 +- “当前处于该节点”的判定采用 NEXT 模式: + - 目标节点前置节点全部完成(未撤销) + - 目标节点尚未完成(未撤销)