1
0
forked from erp-dev/erp

feat: added plate order clone

This commit is contained in:
2025-12-16 08:42:53 +08:00
parent 4c9b8e6a42
commit 0638dd1ad3
25 changed files with 46174 additions and 8 deletions

View File

@@ -0,0 +1,160 @@
### 文档说明
本文档描述 `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]
}
```
### Response200
返回下一待执行节点(统一)及其工艺参数定义:
```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`)。
### Response200
```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后端在批量记录表上做去重

View File

@@ -0,0 +1,138 @@
### 文档说明
本文档描述 `api_v2` 的 **BusinessObject 克隆**接口与约束,用于在不影响原对象的情况下,克隆一个“流程实例数据完全一致”的新对象(包含日志与工艺参数历史),同时要求绑定到新的业务对象(`content_type + object_id`)。
---
## 1. 背景与目的
`stateflow.BusinessObject` 通过:
- `process` 指定流程模板;
- `state_logs (StateFlowRecord)` 记录每一步完成情况;
- `StateLogParameterRecord` 记录每次提交的工艺参数 JSON
- `content_type + object_id`GenericForeignKey可选关联到“订单/工单/任务”等业务对象。
克隆的目的:
- **完整复制流程实例的历史数据**(含已撤销记录、含参数记录)用于“另起一份流程实例继续/重跑/复盘”;
- **不影响原对象**
- **必须绑定新的业务对象 ID**,否则克隆出的对象与原对象在业务关联上无差异,容易造成误用。
---
## 2. API 概览
### 2.1 Endpoint
- **POST** `/api/v2/stateflow/business-objects/clone/`
### 2.2 权限
- 需要登录:`IsAuthenticated`
- 鉴权方式:项目统一 JWT同其他 api_v2 端点)
### 2.3 请求参数(必须)
请求体 JSON
- `business_object_id`:源 BusinessObject 的 ID
- `content_type`:源对象的 `content_type_id`(仅用于一致性校验)
- `object_id`:克隆后新对象要绑定的业务对象 ID必须是新的
> 说明:
> - **content_type 仅做 “与源对象是否一致” 校验**,不做额外业务校验。
> - **object_id 必须与源对象不同**;否则克隆没有业务意义,后端会拒绝。
### 2.4 响应
成功:
- 返回 `business_object_id`(克隆后新对象 ID
---
## 3. 请求/响应示例
### 3.1 成功克隆
请求:
```json
{
"business_object_id": 123,
"content_type": 9,
"object_id": 456
}
```
响应200
```json
{
"business_object_id": 789
}
```
含义:
- `123` 是源流程实例
- `789` 是新的流程实例(日志与参数完整复制)
- 新实例会绑定到新的业务对象:`content_type_id=9, object_id=456`
---
## 4. 重要校验规则与错误码
### 4.1 404源对象不存在
`business_object_id` 不存在时:
- 返回 **404**
```json
{ "detail": "business_object 不存在" }
```
### 4.2 400content_type 不一致
当请求体 `content_type` 与源对象的 `content_type_id` 不一致:
- 返回 **400**
```json
{ "detail": "content_type 与源对象不一致,拒绝克隆" }
```
### 4.3 400object_id 相关校验失败
当源对象存在 `content_type_id`(即已绑定业务对象)时:
- **必须提供** `object_id`
-`object_id` **必须与源对象不同**
可能返回的 400
```json
{ "detail": "必须提供新的 object_id" }
```
```json
{ "detail": "object_id 必须与源对象不同" }
```
### 4.4 400源对象未绑定业务对象但传了 object_id
当源对象 `content_type_id``null` 时,要求 `object_id` 也为 `null`
```json
{ "detail": "源对象未绑定 content_typeobject_id 必须为空" }
```
---
## 5. 克隆范围与语义(后端实现约定)
后端通过 `stateflow.services.clone_business_object(...)` 实现克隆,核心语义:
- **克隆 BusinessObject 的基础字段**`name / process / description / content_type`
- **克隆后的 object_id 使用请求体传入的 `object_id`**
- **复制所有 StateFlowRecordstate_logs**
- 包含已撤销记录(`is_cancelled=True`
- 保持 `completed_at / cancelled_at` 等关键字段一致
- **复制每条 StateFlowRecord 的 StateLogParameterRecordparameter_records**
- 复制 `parameters`(深拷贝 JSON
- 复制 `remark`
- 保持 `created_at/updated_at` 一致
- **时间戳一致性(严格)**
- `BusinessObject.created_at/updated_at` 也会被同步为源对象值(通过 update 绕过 auto_now/auto_now_add
- **互不影响**
- 修改克隆对象的参数记录/日志不会影响源对象(复制的是新记录)
---
## 6. 与测试的对应关系
强校验与数据一致性主要由 **service 层严格测试**保证:
- `stateflow/tests/test_clone_business_object.py`
API 层仅做轻量测试:
- `api_v2/tests.py`(成功/404/401/ct不一致
---
## 7. 变更提示(给前端/调用方)
如果你之前使用旧版接口仅传 `business_object_id`
- 现在必须补齐 `content_type` 与新的 `object_id`
- 并且 `object_id` 必须是“新业务对象”的 id例如新订单、复制出来的订单等

View File

@@ -0,0 +1,77 @@
### 背景
`PrintingJob` 在业务上等同于“印染订单明细”。当使用批量推进接口(例如 `api_v2` 的批量推进)对多个明细用同一份参数推进流程时,需要在后端保留一条**批量推进审计记录**,并在 `api_v1` 的 PrintingJob 相关接口返回该记录,方便前端展示“这条明细曾经参与过哪些批量推进、当时提交了哪些工艺参数”。
### 变更点概述api_v1
`api_v1` 的 PrintingJob 相关序列化输出中新增字段:
- **`batch_advance_records`**始终存在key 永远输出)
- 若该 job 没有批量推进记录:返回 `[]`
- 若存在记录:返回记录数组(按创建时间倒序)
> 该字段是新增字段,不会影响老客户端;对前端而言可以直接依赖该 key 的存在性,无需做 “undefined/null” 兼容。
### 返回字段结构batch_advance_records
每条记录PrintingJobBatchAdvanceRecord包含
- `id`: 批量推进记录 ID
- `printing_order`: 主订单 ID
- `state`: 本次批量推进“完成”的流程节点 ID即推进时的 next_pending_state
- `state_id`: 同 `state`(便于前端直接取用)
- `state_name`: 节点名称
- `created_by`: 操作人用户 ID可能为 null
- `created_by_username`: 操作人用户名(可能为 null
- `created_by_name`: 操作人员工姓名(若用户有 employee则返回 employee.name否则 null
- `parameters`: 本次批量推进提交的参数 JSON与推进接口 `parameters` 完全一致)
- `created_at`: 记录创建时间ISO8601
### 示例(无记录)
```json
{
"id": 101,
"printing_order": 55,
"product": 1,
"quantity": 10,
"unit": "米",
"batch_advance_records": []
}
```
### 示例(有记录)
```json
{
"id": 101,
"printing_order": 55,
"product": 1,
"quantity": 10,
"unit": "米",
"batch_advance_records": [
{
"id": 9,
"printing_order": 55,
"state": 7,
"state_id": 7,
"state_name": "染色",
"created_by": 1001,
"created_by_username": "factory_user",
"created_by_name": "张三",
"parameters": {
"temperature": "25.5",
"operator": "张三"
},
"created_at": "2025-12-14T10:00:00+08:00"
}
]
}
```
### 相关实现位置
- **serializer 增字段**`api_v1/views/printing/serializers.py`
- `PrintingJobListSerializer.batch_advance_records`
- `PrintingJobDetailSerializer.batch_advance_records`
- `PrintingJobCreateUpdateSerializer.batch_advance_records`
- `PrintingJobBatchAdvanceRecordSerializer`
- **避免 N+1**`api_v1/views/printing/views.py``PrintingJobViewSet.get_queryset()` 预取 `batch_advance_records`
### 注意事项
- 记录中的 `parameters` 为**批量提交时的原始参数**,不会自动与 stateflow 的参数历史做合并;若需要查看某次推进在 stateflow 中落地的参数记录,请以 stateflow 日志为准。
- 当前文档只描述 `api_v1` 输出字段;批量推进行为本身由 `api_v2` 的批量推进接口创建审计记录。