forked from erp-dev/erp
feat: change api/v2/plate-orders/by-process-node/ logic ***** danger
This commit is contained in:
@@ -824,6 +824,28 @@ class PlateOrderByProcessNodeV2APITest(TestCase):
|
|||||||
self.assertEqual(resp.data['count'], 2)
|
self.assertEqual(resp.data['count'], 2)
|
||||||
self.assertEqual(len(resp.data['results']), 1)
|
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):
|
class PlateOrderByProcessV2APITest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
@@ -484,8 +484,12 @@ class PlateOrderByProcessNodeView(APIView):
|
|||||||
def get(self, request):
|
def get(self, request):
|
||||||
qp = request.query_params
|
qp = request.query_params
|
||||||
process_node_id = qp.get('process_node_id')
|
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:
|
if not process_node_id:
|
||||||
return Response({'detail': 'process_node_id 为必填参数'}, status=status.HTTP_400_BAD_REQUEST)
|
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:
|
try:
|
||||||
process_node_id_int = int(process_node_id)
|
process_node_id_int = int(process_node_id)
|
||||||
@@ -545,6 +549,15 @@ class PlateOrderByProcessNodeView(APIView):
|
|||||||
)
|
)
|
||||||
).filter(completed_target=0)
|
).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:同时支持主键与 design_code icontains(不新增额外参数)
|
||||||
search = (qp.get('search') or '').strip()
|
search = (qp.get('search') or '').strip()
|
||||||
if search:
|
if search:
|
||||||
|
|||||||
84
docs/api_v2_plate_orders_by_process_node_2026-02-06.md
Normal file
84
docs/api_v2_plate_orders_by_process_node_2026-02-06.md
Normal file
@@ -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 模式:
|
||||||
|
- 目标节点前置节点全部完成(未撤销)
|
||||||
|
- 目标节点尚未完成(未撤销)
|
||||||
Reference in New Issue
Block a user