# Stateflow：按“已完成状态节点 + 工艺参数”查询 BusinessObject（V1）

## 用途/背景

在 `stateflow` 中，业务流程实例由 `BusinessObject` 表示；每次完成某个状态节点会生成一条 `StateFlowRecord`（状态流转记录），并可在该记录下写入一条或多条 `StateLogParameterRecord.parameters`（JSON 工艺参数记录）。

本接口用于在 **不依赖具体业务模块**（如 printing/shipment）的情况下，按以下条件反查 `BusinessObject` 列表：

- 已完成某个状态节点（`state_id`）
- 且在该次完成该节点的日志下，存在工艺参数 `param_key=param_value`

典型场景：
- 找到所有 **已完成**“画图中”节点，且“设计师名称”为“AAA”的 `BusinessObject`
- 再通过 `content_type/object_id` 追溯到实际业务对象（如 `printing.plateorder`）

---

## 接口定义

- **Method**: `GET`
- **Path**: `/api/v1/stateflow/business-objects/`
- **Auth**: `IsAuthenticated`
- **Pagination**: `LimitOffsetPagination`（字段：`count / next / previous / results`）

---

## 查询参数（Query Params）

### 必填组合（本能力的核心）

当你希望启用“按已完成节点 + 参数”过滤时，下面三个参数必须 **同时提供**：

- **state_id**: `int`  
  - 语义：已完成该状态节点（仅匹配 `StateFlowRecord.is_cancelled=False`）
- **param_key**: `string`  
  - 语义：工艺参数键（来自 `StateLogParameterRecord.parameters` 的 key）
- **param_value**: `string`  
  - 语义：工艺参数值（来自 `StateLogParameterRecord.parameters` 的 value）

**重要约束**：
- 如果提供了 `state_id`，但缺少 `param_key` 或 `param_value`，出于避免“误放宽查询范围”的考虑，接口会 **返回空结果**（`count=0`）。

### 常用配套过滤（推荐一起使用）

- **content_type_str**: `app_label.model`  
  - 示例：`printing.plateorder`、`auth.user`
  - 用途：将查询限定到某类业务对象，避免在全表上做参数查询

### 其他已有过滤/排序（原有能力）

以下能力来自 `BusinessObjectFilterSet` 与 DRF 内置 backends：

- **name**: 名称模糊搜索（icontains）
- **process**: 按流程 ID 过滤
- **process_name**: 流程名称模糊搜索（icontains）
- **overall_status**: `in_progress` / `completed`
- **has_content_object**: `true/false`
- **search**: 全文搜索（`name/description`）
- **ordering**: 排序字段（`id/name/created_at/updated_at`），默认 `-created_at`
- **limit / offset**: 分页参数

---

## 过滤语义说明（非常重要）

### 1) “处于某状态”的含义

本接口中的“处于某状态”指：

- **已经完成**该状态节点（存在 `StateFlowRecord(state_id=..., is_cancelled=False)`）

而不是“当前正在该节点”（当前节点在系统中是通过流程推导得到，不是直接落库字段）。

### 2) 参数匹配的绑定范围

参数匹配是 **绑定在同一次完成该节点的 state_log 下**：

- `StateFlowRecord(state_id=画图中)` 这条记录下
- 存在某条 `StateLogParameterRecord.parameters` 包含 `{param_key: param_value}`

换句话说：不会使用 “整个 BusinessObject 的所有参数汇总” 来做匹配。

### 3) 已撤销记录的处理

用于过滤的状态完成记录会排除撤销状态：

- 仅匹配 `StateFlowRecord.is_cancelled=False`
- 如果某条记录被 `step_back` 撤销，则不会参与命中

---

## 返回结构（BusinessObject list）

成功响应为分页结构：

- **count**: 总数
- **next/previous**: 翻页链接
- **results**: 列表项（每项为 `BusinessObjectListSerializer` 输出）

`results[*]` 字段（与当前实现一致）：

- **id**: BO ID
- **name**: BO 名称
- **process**: 流程 ID
- **process_name**: 流程名称
- **current_state_name**: 当前状态名称（按系统 current_state 策略推导）
- **overall_status**: `in_progress` / `completed`
- **progress_percentage**: 进度百分比（浮点数，已 round）
- **content_type**: ContentType ID（可空）
- **object_id**: 关联对象 ID（可空）
- **content_type_name**: `app_label.model` 字符串（可空）
- **description**: 描述
- **created_at / updated_at**: 时间戳

---

## 示例

### 示例 1：查找“画图中 + 设计师=AAA”的开版单（plateorder）

请求：

```http
GET /api/v1/stateflow/business-objects/?content_type_str=printing.plateorder&state_id=123&param_key=设计师名称&param_value=AAA&limit=50&offset=0
Authorization: Bearer <token>
```

### 示例 2：只按类型过滤（不启用 state+param）

请求：

```http
GET /api/v1/stateflow/business-objects/?content_type_str=printing.plateorder&limit=50&offset=0
Authorization: Bearer <token>
```

### 示例 3：错误用法（只给 state_id）

由于缺少 `param_key/param_value`，会返回空结果（`count=0`）：

```http
GET /api/v1/stateflow/business-objects/?state_id=123&limit=50&offset=0
Authorization: Bearer <token>
```

---

## 错误响应

- **401 Unauthorized**：未登录
- 其它错误码沿用现有 `BusinessObjectViewSet` 行为（本过滤能力本身不额外引入新的 400 约束；缺参会返回空列表）

---

## 性能/索引建议（JSONB）

本查询的关键条件是：

- `StateLogParameterRecord.parameters` 的 JSON 包含匹配（Django：`parameters__contains={key: value}`）
- PostgreSQL 对应：`parameters @> '{"key":"value"}'::jsonb`

为提升性能，项目已为 `StateLogParameterRecord.parameters` 增加：

- **GIN(jsonb_path_ops)** 索引（迁移：`stateflow/migrations/0022_statelogparameterrecord_parameters_gin_index.py`）

建议：
- 线上启用前确保已执行迁移
- 数据量较大时可用 `EXPLAIN (ANALYZE, BUFFERS)` 验证查询是否命中 GIN 索引

