## 背景与问题

系统中 `PlateOrder` / `PrintingJob` 都通过 `business_object`（`stateflow.BusinessObject`）承载流程推进数据：
- `BusinessObject.process` 决定了流程节点序列
- `BusinessObject.state_logs` 记录了推进轨迹（`StateFlowRecord`，支持撤销）

但在现有 API 中（PUT/PATCH 更新）：
- `PlateOrder.process`（整型流程 ID）允许被更新
- `PrintingOrder.process`（外键流程）允许在“所有 jobs 未开始”时被更新
- `PrintingJob` 允许修改 `printing_order`（从而间接改变其应当使用的流程）

这些更新行为会导致一个严重一致性漏洞：
> 业务对象的“当前流程字段”发生变化，但其已绑定的 `BusinessObject.process` 仍指向旧流程，导致后续状态推进与展示出现错乱。

---

## 修正案（最小改动原则）

目标：
- 只在必要处拦截/修复更新行为
- 将核心逻辑集中到 `stateflow/services.py`（跨模块统一）
- 不删除旧的 BusinessObject（后续可通过“无反向引用”识别悬空 BO）

核心做法：
- 新增 `stateflow.services.relink_business_object_for_instance(...)`
  - 当流程发生变化时，创建并绑定一个新的 BusinessObject（新流程）
  - 若旧 BO 存在“未撤销进度”，则拒绝（返回 None，由调用方转 400）
- 内部新增 `_can_relink_business_object(...)`
  - 判定口径与 `has_started` 保持一致：只要存在 `is_cancelled=False` 的记录即视为已开始

---

## 规则（对外行为）

### PlateOrder（更新 process）

- **process 未变化**：不触发 relink
- **process 变化**：
  - 若已存在“未撤销进度”（has_started=True）：返回 400
  - 否则：创建新 BO 并绑定；旧 BO 保留

### PrintingOrder（更新 process）

- **process=None**：非法，返回 400
- **任意 job 已存在未撤销进度**：整笔订单不允许改流程，返回 400
- **所有 jobs 均未开始**：允许改流程，并对订单下所有 jobs 进行 relink（创建新 BO 并绑定）

### PrintingJob（更新 printing_order）

- 不允许通过更新接口修改 `printing_order` 绑定关系：返回 400
  - 避免绕过 `PrintingOrder` 的流程一致性约束

---

## 影响与收益

- **收益**：保证 `process` 与 `BusinessObject.process` 的一致性，避免状态推进/展示错乱
- **可追溯性**：旧 BO 不删除，未来可实现“悬空 BO 查看/排查”
- **风险控制**：仅在“无有效进度”时允许换流程，避免对已开始流程造成破坏

## 涉及代码文件

- stateflow/services.py
- api_v1/views/printing/serializers.py
- api_v1/views/printing/services.py
- api_v1/views/printing/test_plate_order_api.py
- api_v1/views/printing/test_api.py
- api_v1/views/printing/test_printing_job_api.py