forked from erp-dev/erp
feat: added plate order clone
This commit is contained in:
160
docs/api_v2_advance_printing_jobs_api.md
Normal file
160
docs/api_v2_advance_printing_jobs_api.md
Normal 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]
|
||||
}
|
||||
```
|
||||
|
||||
### 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,后端在批量记录表上做去重)。
|
||||
|
||||
|
||||
138
docs/api_v2_stateflow_business_object_clone.md
Normal file
138
docs/api_v2_stateflow_business_object_clone.md
Normal 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 400:content_type 不一致
|
||||
当请求体 `content_type` 与源对象的 `content_type_id` 不一致:
|
||||
- 返回 **400**
|
||||
```json
|
||||
{ "detail": "content_type 与源对象不一致,拒绝克隆" }
|
||||
```
|
||||
|
||||
### 4.3 400:object_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_type,object_id 必须为空" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 克隆范围与语义(后端实现约定)
|
||||
后端通过 `stateflow.services.clone_business_object(...)` 实现克隆,核心语义:
|
||||
- **克隆 BusinessObject 的基础字段**:`name / process / description / content_type`;
|
||||
- **克隆后的 object_id 使用请求体传入的 `object_id`**;
|
||||
- **复制所有 StateFlowRecord(state_logs)**:
|
||||
- 包含已撤销记录(`is_cancelled=True`)
|
||||
- 保持 `completed_at / cancelled_at` 等关键字段一致
|
||||
- **复制每条 StateFlowRecord 的 StateLogParameterRecord(parameter_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(例如新订单、复制出来的订单等)。
|
||||
|
||||
|
||||
77
docs/batch_submit_state_params.md
Normal file
77
docs/batch_submit_state_params.md
Normal 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` 的批量推进接口创建审计记录。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user