### 文档说明
本文档描述 `api_v2` 中 **PrintingJob 批量推进流程状态**的两个接口（preview + submit），用于前端生成批量表单与提交批量推进。

### 背景与术语
- **PrintingJob**：概念上等同于“订单明细对象”，归属一个 `PrintingOrder`。
- **Stateflow**：PrintingJob 通过 `business_object`（`stateflow.BusinessObject`）承载流程实例；推进逻辑复用 `stateflow.services.advance_to_next_state(...)`。
- **批量推进**：对多个 PrintingJob（要求同一订单、同一待执行节点）使用同一份工艺参数推进一个节点。

### 权限与鉴权
- **鉴权**：需要登录（`IsAuthenticated`），使用系统统一 JWT 认证。
- **业务权限**：需要印染工厂身份（`IsPrintingFactory`），即 `request.user.employee.merchant.type == FACTORY`。

### 路由与代码位置
- **Base**：`/api/v2/`
- **实现文件**：`api_v2/views/printing.py`
- **路由定义**：`api_v2/urls.py`

### 统一校验规则（preview 与 submit 共用）
批量推进（或预览）必须同时满足：
- **ID 存在**：`printing_job_ids` 全部存在，否则 400。
- **同一订单**：所有 job 必须属于同一个 `printing_order`，否则 400。
- **必须绑定流程实例**：每个 job 必须有 `business_object`，否则 400。
- **同一待执行节点**：所有 job 的 `next_pending_state` 必须一致，否则 400。
- **可推进**：任一 job 的 `next_pending_state` 为 `None`（流程已完成或无节点）则 400。

> 备注：当前 **不做 preview→submit 竞态处理**（例如 preview 通过后，期间有人推进了其中部分 job）。submit 会重新校验；如需更好的用户体验，后续可引入 snapshot token / 幂等键。

---

## 批量推进预览（用于生成批量表单）
### Endpoint
- **POST** `api/v2/printing-jobs/batch-advance/preview/`

### Request Body
```json
{
  "printing_job_ids": [101, 102, 103]
}
```

### Response（200）
返回下一待执行节点（统一）及其工艺参数定义：
```json
{
  "printing_order_id": 55,
  "printing_job_ids": [101, 102, 103],
  "next_state": {
    "id": 7,
    "name": "染色",
    "description": "染色工序",
    "order": 0,
    "parameters": [
      {
        "id": 12,
        "key": "temperature",
        "value": "",
        "attachment": null,
        "attachment_url": null,
        "description": "温度（必填）",
        "is_required": true,
        "is_image_path": false
      }
    ]
  }
}
```

### 典型错误（400）
返回 `{"detail": "<原因>"}` 或字段级错误，例如：
- 不同订单：`所选明细不属于同一个 printing_order，无法批量推进`
- 缺 business_object：`以下 printing_job 未关联流程实例（business_object），无法推进: ...`
- next_state 不一致：`所选明细当前待执行节点不一致，无法批量推进...`
- 已完成/无节点：`没有待执行节点（流程已完成或无节点）`

---

## 批量推进提交（全成功/全失败）
### Endpoint
- **POST** `api/v2/printing-jobs/batch-advance/`

### 语义（重要）
- **事务全成功/全失败**：接口使用数据库事务包裹：
  - 创建批量推进记录（审计表）
  - 逐个调用 `stateflow.services.advance_to_next_state(...)` 推进每个 job 的 `business_object`
  - 任意一个 job 推进失败：**整体回滚**（不会产生任何 stateflow 日志，也不会产生批量记录）

### Request Body
```json
{
  "printing_job_ids": [101, 102, 103],
  "parameters": {
    "temperature": "25.5",
    "operator": "张三"
  }
}
```
说明：
- `parameters` 会作为 `**kwargs` 传入 `stateflow.services.advance_to_next_state(...)`。
- 若下一节点存在必填参数（`is_required=true`），则必须提供对应 key，否则返回 400（错误信息形如 `缺失必填参数: temperature`）。

### Response（200）
```json
{
  "detail": "已完成状态: 染色",
  "batch_id": 9,
  "printing_order_id": 55,
  "printing_job_ids": [101, 102, 103],
  "jobs": [
    {
      "id": 101,
      "printing_order": 55,
      "product": { "id": 1, "...": "..." },
      "work_state": 0,
      "quantity": 10,
      "width": "150cm",
      "fabric": "棉",
      "unit": "米",
      "size": null,
      "pieces": null,
      "description": null,
      "business_object_id": 888,
      "created_at": "2025-12-12T10:00:00+08:00",
      "updated_at": "2025-12-12T10:01:00+08:00",
      "billed_quantity": "0.00"
    }
  ]
}
```
说明：
- `jobs` 使用 `PrintingJobV2Serializer`：其中 `product` 为嵌套对象（不是纯 ID）。

### 典型错误（400）
- 参数缺失：`{"detail": "缺失必填参数: temperature"}`
- 不满足一致性校验：同 preview 的错误列表。

---

## 批量推进审计表：PrintingJobBatchAdvanceRecord
### 模型
`printing.models.PrintingJobBatchAdvanceRecord`

### 字段（核心）
- `created_at` / `updated_at`：来自 `ModelBase`
- `created_by`：操作人（可空，删除用户后保留记录）
- `printing_order`：所属主订单
- `state`：本次被“完成”的流程节点（推进时的 next_pending_state）
- `parameters`：本次批量推进提交的参数（JSON）
- `printing_jobs`：本次批量推进涉及的明细集合（ManyToMany）

### 迁移
- `printing/migrations/0024_printingjobbatchadvancerecord.py`

---

## 前端联调建议
- 先调用 preview 获取 `next_state.parameters` 生成批量表单（必填项根据 `is_required`）。
- 表单提交调用 submit；如返回 400，提示 `detail` 并建议用户刷新列表后重试。
- 当前未实现竞态 token/幂等键；如需要防重复提交，建议后续加 `Idempotency-Key`（前端生成 UUID，后端在批量记录表上做去重）。


