forked from erp-dev/erp
feat: upload api
This commit is contained in:
384
api_v1/views/printing/API.md
Normal file
384
api_v1/views/printing/API.md
Normal file
@@ -0,0 +1,384 @@
|
||||
# Printing API 文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档详细描述了 `printing` 模块下的所有 API 接口,包括印染订单 (`PrintingOrder`)、印染任务 (`PrintingJob`) 和开版订单 (`PlateOrder`) 的管理。
|
||||
|
||||
## 基础 URL
|
||||
|
||||
所有 `printing` 相关 API 都在以下基础路径下:
|
||||
|
||||
```
|
||||
/api/v1/
|
||||
```
|
||||
|
||||
## 认证与权限
|
||||
|
||||
- 所有接口都需要 JWT 认证。
|
||||
- `PrintingOrder` 和 `PrintingJob` 接口需要用户是**印染工厂**类型商户 (`MerchantTypeEnum.FACTORY`)。
|
||||
- `PlateOrder` 接口无此限制,但作废/恢复等操作需要特定权限。
|
||||
|
||||
---
|
||||
|
||||
## 1. Printing Orders API (印染订单)
|
||||
|
||||
**基础路径**: `/api/v1/printing-orders/`
|
||||
|
||||
管理印染生产的主订单。
|
||||
|
||||
### 1.1. 标准操作 (CRUD)
|
||||
|
||||
- **GET** `/api/v1/printing-orders/`
|
||||
- **描述**: 获取印染订单列表。
|
||||
- **查询参数**:
|
||||
- `customer`: 客户ID
|
||||
- `customer_name`: 客户名称 (模糊查询)
|
||||
- `customer_phone`: 客户电话 (模糊查询)
|
||||
- `fabric`: 面料 (模糊查询)
|
||||
- `is_urgent`: 是否紧急 (`true`/`false`)
|
||||
- `is_fabric_received`: 布料是否已收 (`true`/`false`)
|
||||
- `is_invalid`: 是否作废 (`true`/`false`)
|
||||
- `area`: 地区 (模糊查询)
|
||||
- `outgoing_date_from`/`to`: 出货日期范围
|
||||
- `created_date_from`/`to`: 创建日期范围
|
||||
- `search`: 全文搜索 (客户名称, 面料, 地区, 工艺)
|
||||
- `ordering`: 排序字段 (e.g., `-created_at`)
|
||||
- **响应**: `PrintingOrderListSerializer` 列表。
|
||||
|
||||
- **GET** `/api/v1/printing-orders/{id}/`
|
||||
- **描述**: 获取单个印染订单详情。
|
||||
- **响应**: `PrintingOrderDetailSerializer`。
|
||||
|
||||
- **POST** `/api/v1/printing-orders/`
|
||||
- **描述**: 创建一个新的印染订单。
|
||||
- **请求体**: `PrintingOrderCreateUpdateSerializer`。
|
||||
|
||||
- **PUT/PATCH** `/api/v1/printing-orders/{id}/`
|
||||
- **描述**: 完全或部分更新一个印染订单。
|
||||
- **请求体**: `PrintingOrderCreateUpdateSerializer`。
|
||||
- **注意**: 如果订单下已有开始的 `PrintingJob`,则无法修改 `process` 字段。
|
||||
|
||||
- **DELETE** `/api/v1/printing-orders/{id}/`
|
||||
- **描述**: **已禁用**。请使用 `invalidate` 接口作废订单。
|
||||
|
||||
### 1.2. 自定义操作 (Custom Actions)
|
||||
|
||||
- **POST** `/api/v1/printing-orders/{id}/invalidate/`
|
||||
- **描述**: 作废订单。需要 `printing.can_invalidate_printingorder` 权限。
|
||||
|
||||
- **POST** `/api/v1/printing-orders/{id}/activate/`
|
||||
- **描述**: 恢复已作废的订单。需要 `printing.can_activate_printingorder` 权限。
|
||||
|
||||
- **POST** `/api/v1/printing-orders/{id}/mark_fabric_received/`
|
||||
- **描述**: 标记订单的布料已收到。
|
||||
|
||||
---
|
||||
|
||||
## 2. Printing Jobs API (印染任务)
|
||||
|
||||
**基础路径**: `/api/v1/printing-jobs/`
|
||||
|
||||
管理印染订单下的具体生产任务(款式明细)。
|
||||
|
||||
### 2.1. 标准操作 (CRUD)
|
||||
|
||||
- **GET** `/api/v1/printing-jobs/`
|
||||
- **描述**: 获取印染任务列表。
|
||||
- **查询参数**:
|
||||
- `printing_order`: 印染订单ID
|
||||
- `product`: 产品ID
|
||||
- `product_name`: 产品名称 (模糊查询)
|
||||
- `unit`: 单位 (模糊查询)
|
||||
- `quantity_min`/`max`: 数量范围
|
||||
- `pieces_min`/`max`: 件数范围
|
||||
- `search`: 全文搜索 (产品名称, 单位, 尺寸, 备注)
|
||||
- `ordering`: 排序字段。
|
||||
- **响应**: `PrintingJobListSerializer` 列表。
|
||||
- **重要变更**: 列表中已**包含** `business_object_id` 字段。
|
||||
|
||||
- **GET** `/api/v1/printing-jobs/{id}/`
|
||||
- **描述**: 获取单个印染任务详情。
|
||||
- **响应**: `PrintingJobDetailSerializer`。
|
||||
- **重要变更**: `business_object_id` 字段现在会安全地返回 `null` 而不是报错(当 `business_object` 不存在时)。
|
||||
|
||||
- **POST** `/api/v1/printing-jobs/`
|
||||
- **描述**: 创建一个新的印染任务。
|
||||
- **请求体**: `PrintingJobCreateUpdateSerializer`。
|
||||
|
||||
- **PUT/PATCH** `/api/v1/printing-jobs/{id}/`
|
||||
- **描述**: 完全或部分更新一个印染任务。
|
||||
- **请求体**: `PrintingJobCreateUpdateSerializer`。
|
||||
|
||||
- **DELETE** `/api/v1/printing-jobs/{id}/`
|
||||
- **描述**: **已禁用**。
|
||||
|
||||
### 2.2. 状态流转操作 (便捷包装)
|
||||
|
||||
这些接口是对 `stateflow` 模块的包装,前端只需提供 `printing_job` 的 ID。
|
||||
|
||||
**重要变更 (v2)**: 现在支持传递状态参数!
|
||||
|
||||
- **POST** `/api/v1/printing-jobs/{id}/advance-to-next-state/`
|
||||
- **描述**: 将任务推进到下一个流程状态。
|
||||
- **请求体**: (可选,但如果下一个状态有必填参数,则必须提供)
|
||||
```json
|
||||
{
|
||||
"parameters": {
|
||||
"temperature": "25.5",
|
||||
"operator": "张三",
|
||||
"humidity": "60%"
|
||||
}
|
||||
}
|
||||
```
|
||||
- **参数说明**:
|
||||
- `parameters`: 可选的字典,包含状态参数的键值对
|
||||
- 如果流程节点定义了必填参数(`is_required=True`),则必须在 `parameters` 中提供
|
||||
- 参数值统一为字符串类型
|
||||
- **成功响应**: `200 OK`
|
||||
```json
|
||||
{
|
||||
"detail": "已完成状态: 待印染",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"status": "印染中",
|
||||
"business_object_id": 10,
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
- **失败响应**:
|
||||
- `400 Bad Request` - 缺少必填参数
|
||||
```json
|
||||
{
|
||||
"detail": "缺失必填参数: temperature, operator"
|
||||
}
|
||||
```
|
||||
- `400 Bad Request` - 流程已完成
|
||||
```json
|
||||
{
|
||||
"detail": "流程已完成,无法继续推进"
|
||||
}
|
||||
```
|
||||
- `400 Bad Request` - 没有关联的流程实例
|
||||
```json
|
||||
{
|
||||
"detail": "该任务没有关联的流程实例"
|
||||
}
|
||||
```
|
||||
|
||||
- **POST** `/api/v1/printing-jobs/{id}/step-back-one-state/`
|
||||
- **描述**: 将任务回退到上一个流程状态。
|
||||
- **响应**: 成功时返回 `200 OK`,包含 `detail` 和 `data`。失败时返回 `400 Bad Request`。
|
||||
|
||||
### 2.3. 状态查询操作
|
||||
|
||||
- **GET** `/api/v1/printing-jobs/{id}/completed-states/`
|
||||
- **描述**: 查询已完成的流程节点列表。
|
||||
- **查询参数**:
|
||||
- `include_cancelled`: 是否包含已撤销的记录 (`true`/`false`)。
|
||||
- **响应**: 包含状态日志列表。
|
||||
|
||||
- **GET** `/api/v1/printing-jobs/{id}/timeline/`
|
||||
- **描述**: 获取完整的流程时间线,包含所有节点及其状态 (`not_started`, `in_progress`, `completed`)。
|
||||
|
||||
---
|
||||
|
||||
## 3. Plate Orders API (开版订单)
|
||||
|
||||
**基础路径**: `/api/v1/plate-orders/`
|
||||
|
||||
管理设计、打样、开版的订单。
|
||||
|
||||
### 3.1. 标准操作 (CRUD)
|
||||
|
||||
- **GET** `/api/v1/plate-orders/`
|
||||
- **描述**: 获取开版订单列表。
|
||||
- **查询参数**:
|
||||
- `customer_name`/`phone`: 客户名称/电话 (模糊查询)
|
||||
- `salesperson`/`merchandiser`: 业务员/跟单员 ID
|
||||
- `plate_type`, `urgency_level`, `fabric`, `style_name`: 模糊查询
|
||||
- `is_invalid`, `is_ordered`, `is_mark_frame`: 布尔过滤
|
||||
- `plate_date_from`/`to`: 开版日期范围
|
||||
- `required_completion_date_from`/`to`: 要求完成日期范围
|
||||
- `created_date_from`/`to`: 创建日期范围
|
||||
- `search`: 全文搜索 (设计编号, 款式名称, 客户名称, 面料)
|
||||
- `ordering`: 排序字段。
|
||||
- **响应**: `PlateOrderListSerializer` 列表。
|
||||
|
||||
- **GET** `/api/v1/plate-orders/{id}/`
|
||||
- **描述**: 获取单个开版订单详情。
|
||||
- **响应**: `PlateOrderDetailSerializer`。
|
||||
- **重要变更**: `business_object_id` 字段现在会安全地返回 `null` 而不是报错。
|
||||
|
||||
- **POST** `/api/v1/plate-orders/`
|
||||
- **描述**: 创建一个新的开版订单。
|
||||
- **请求体**: `PlateOrderCreateUpdateSerializer`。
|
||||
|
||||
- **PUT/PATCH** `/api/v1/plate-orders/{id}/`
|
||||
- **描述**: 完全或部分更新一个开版订单。
|
||||
- **请求体**: `PlateOrderCreateUpdateSerializer`。
|
||||
|
||||
- **DELETE** `/api/v1/plate-orders/{id}/`
|
||||
- **描述**: **已禁用**。
|
||||
|
||||
### 3.2. 自定义操作
|
||||
|
||||
- **POST** `/api/v1/plate-orders/{id}/invalidate/`
|
||||
- **描述**: 作废开版订单。需要 `printing.can_invalidate_plateorder` 权限。
|
||||
|
||||
- **POST** `/api/v1/plate-orders/{id}/activate/`
|
||||
- **描述**: 恢复已作废的开版订单。需要 `printing.can_activate_plateorder` 权限。
|
||||
|
||||
### 3.3. 状态流转与查询操作
|
||||
|
||||
与 `PrintingJob` 的接口类似,提供对 `stateflow` 的便捷包装。
|
||||
|
||||
**重要变更 (v2)**: 现在支持传递状态参数!
|
||||
|
||||
- **POST** `/api/v1/plate-orders/{id}/advance-to-next-state/`
|
||||
- **描述**: 推进到下一个流程状态。
|
||||
- **请求体**: (可选,但如果下一个状态有必填参数,则必须提供)
|
||||
```json
|
||||
{
|
||||
"parameters": {
|
||||
"inspector": "李四",
|
||||
"quality_score": "95",
|
||||
"notes": "质量良好"
|
||||
}
|
||||
}
|
||||
```
|
||||
- **参数说明**:
|
||||
- `parameters`: 可选的字典,包含状态参数的键值对
|
||||
- 如果流程节点定义了必填参数(`is_required=True`),则必须在 `parameters` 中提供
|
||||
- 参数值统一为字符串类型
|
||||
- **成功响应**: `200 OK`
|
||||
```json
|
||||
{
|
||||
"detail": "已完成状态: 待画图",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"status": "画图完成",
|
||||
"business_object_id": 20,
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
- **失败响应**:
|
||||
- `400 Bad Request` - 缺少必填参数
|
||||
```json
|
||||
{
|
||||
"detail": "缺失必填参数: inspector"
|
||||
}
|
||||
```
|
||||
- `400 Bad Request` - 流程已完成或没有关联的流程实例
|
||||
|
||||
- **POST** `/api/v1/plate-orders/{id}/step-back-one-state/`
|
||||
- **描述**: 回退到上一个流程状态。
|
||||
|
||||
- **GET** `/api/v1/plate-orders/{id}/completed-states/`
|
||||
- **描述**: 查询已完成的流程节点列表。
|
||||
|
||||
- **GET** `/api/v1/plate-orders/{id}/timeline/`
|
||||
- **描述**: 获取完整的流程时间线。
|
||||
|
||||
---
|
||||
|
||||
## 4. 重要变更说明 (v2)
|
||||
|
||||
### 4.1. 新增功能
|
||||
|
||||
#### 4.1.1. 状态推进接口支持参数传递
|
||||
|
||||
**影响接口**:
|
||||
- `POST /api/v1/printing-jobs/{id}/advance-to-next-state/`
|
||||
- `POST /api/v1/plate-orders/{id}/advance-to-next-state/`
|
||||
|
||||
**变更内容**:
|
||||
- 这两个接口现在支持在请求体中传递 `parameters` 字典
|
||||
- `parameters` 是可选的,但如果流程节点定义了必填参数,则必须提供
|
||||
- 参数格式与 `stateflow` 模块的原始接口保持一致
|
||||
|
||||
**迁移指南**:
|
||||
- 旧代码(无参数):
|
||||
```javascript
|
||||
POST /api/v1/printing-jobs/123/advance-to-next-state/
|
||||
// 空请求体
|
||||
```
|
||||
|
||||
- 新代码(带参数):
|
||||
```javascript
|
||||
POST /api/v1/printing-jobs/123/advance-to-next-state/
|
||||
{
|
||||
"parameters": {
|
||||
"temperature": "25.5",
|
||||
"operator": "张三"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **向后兼容**: 如果流程节点没有必填参数,可以不传 `parameters`,保持原有调用方式。
|
||||
|
||||
#### 4.1.2. business_object_id 字段增强
|
||||
|
||||
**影响接口**:
|
||||
- `GET /api/v1/printing-jobs/` (列表)
|
||||
- `GET /api/v1/printing-jobs/{id}/` (详情)
|
||||
- `GET /api/v1/plate-orders/{id}/` (详情)
|
||||
|
||||
**变更内容**:
|
||||
- `PrintingJobListSerializer` 现在**包含** `business_object_id` 字段(之前缺失)
|
||||
- `PrintingJobDetailSerializer` 和 `PlateOrderDetailSerializer` 的 `business_object_id` 字段已修复
|
||||
- 当 `business_object` 不存在时,返回 `null` 而不是报错
|
||||
|
||||
**迁移指南**:
|
||||
- 旧代码可能需要处理字段缺失或错误
|
||||
- 新代码可以安全地访问 `business_object_id`,值可能为 `null` 或整数
|
||||
|
||||
### 4.2. Bug 修复
|
||||
|
||||
1. **修复了 `business_object_id` 字段在关联对象不存在时抛出异常的问题**
|
||||
- 之前: 使用 `source='business_object.id'` 导致 `AttributeError`
|
||||
- 现在: 使用 `SerializerMethodField` 安全返回 `null`
|
||||
|
||||
2. **修复了状态推进接口无法传递参数的问题**
|
||||
- 之前: 参数被忽略,导致有必填参数的流程无法推进
|
||||
- 现在: 参数正确传递到底层 `stateflow` 服务
|
||||
|
||||
### 4.3. 测试覆盖
|
||||
|
||||
新增测试用例:
|
||||
- `test_advance_with_parameters`: 测试带参数推进
|
||||
- `test_advance_with_required_parameters_missing`: 测试缺少必填参数的错误处理
|
||||
- `test_advance_with_partial_required_parameters`: 测试部分提供必填参数的错误处理
|
||||
|
||||
### 4.4. 对前端的影响
|
||||
|
||||
**必须更新的地方**:
|
||||
1. 如果前端代码访问 `PrintingJob` 列表的 `business_object_id`,现在会正常返回(之前可能报错或不存在)。
|
||||
2. 如果前端需要在推进状态时传递参数,现在可以在请求体中添加 `parameters` 字段。
|
||||
|
||||
**可选更新的地方**:
|
||||
1. 建议添加错误处理逻辑,当推进失败时显示具体的缺失参数信息。
|
||||
2. 建议在推进前查询节点的参数要求(通过 `stateflow` 的 `next_pending_state` 接口)。
|
||||
|
||||
**示例代码**:
|
||||
```javascript
|
||||
// 推进前查询下一个节点的参数要求
|
||||
const response = await fetch(`/api/v1/stateflow/business-objects/${businessObjectId}/next_pending_state/`);
|
||||
const nextState = await response.json();
|
||||
|
||||
if (nextState.parameters && nextState.parameters.length > 0) {
|
||||
// 显示参数输入表单
|
||||
const requiredParams = nextState.parameters.filter(p => p.is_required);
|
||||
// ... 收集用户输入
|
||||
}
|
||||
|
||||
// 推进状态(带参数)
|
||||
const advanceResponse = await fetch(`/api/v1/printing-jobs/${jobId}/advance-to-next-state/`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
parameters: collectedParameters
|
||||
})
|
||||
});
|
||||
```
|
||||
@@ -105,16 +105,21 @@ class PrintingJobListSerializer(serializers.ModelSerializer):
|
||||
product_name = serializers.CharField(source='product.name', read_only=True)
|
||||
status = serializers.CharField(read_only=True)
|
||||
is_completed = serializers.BooleanField(read_only=True)
|
||||
business_object_id = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = models.PrintingJob
|
||||
fields = [
|
||||
'id', 'printing_order', 'printing_order_id', 'product', 'product_name',
|
||||
'quantity', 'unit', 'size', 'pieces', 'description',
|
||||
'status', 'is_completed',
|
||||
'status', 'is_completed', 'business_object_id',
|
||||
'created_at', 'updated_at'
|
||||
]
|
||||
read_only_fields = ['id', 'created_at', 'updated_at', 'status', 'is_completed']
|
||||
read_only_fields = ['id', 'created_at', 'updated_at', 'status', 'is_completed', 'business_object_id']
|
||||
|
||||
def get_business_object_id(self, obj):
|
||||
"""安全地获取 business_object_id"""
|
||||
return obj.business_object.id if obj.business_object else None
|
||||
|
||||
|
||||
class PrintingJobDetailSerializer(serializers.ModelSerializer):
|
||||
@@ -126,7 +131,7 @@ class PrintingJobDetailSerializer(serializers.ModelSerializer):
|
||||
status_id = serializers.IntegerField(read_only=True)
|
||||
is_completed = serializers.BooleanField(read_only=True)
|
||||
has_started = serializers.BooleanField(read_only=True)
|
||||
business_object_id = serializers.IntegerField(source='business_object.id', read_only=True)
|
||||
business_object_id = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = models.PrintingJob
|
||||
@@ -140,6 +145,10 @@ class PrintingJobDetailSerializer(serializers.ModelSerializer):
|
||||
'id', 'created_at', 'updated_at',
|
||||
'status', 'status_id', 'is_completed', 'has_started', 'business_object_id'
|
||||
]
|
||||
|
||||
def get_business_object_id(self, obj):
|
||||
"""安全地获取 business_object_id"""
|
||||
return obj.business_object.id if obj.business_object else None
|
||||
|
||||
|
||||
class PrintingJobCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
@@ -242,7 +251,7 @@ class PlateOrderDetailSerializer(serializers.ModelSerializer):
|
||||
is_completed = serializers.BooleanField(read_only=True)
|
||||
has_started = serializers.BooleanField(read_only=True)
|
||||
progress_percentage = serializers.IntegerField(read_only=True)
|
||||
business_object_id = serializers.IntegerField(source="business_object.id", read_only=True)
|
||||
business_object_id = serializers.SerializerMethodField()
|
||||
plate_image_url = serializers.SerializerMethodField()
|
||||
process_name = serializers.SerializerMethodField()
|
||||
|
||||
@@ -272,6 +281,10 @@ class PlateOrderDetailSerializer(serializers.ModelSerializer):
|
||||
'created_at', 'updated_at'
|
||||
]
|
||||
|
||||
def get_business_object_id(self, obj):
|
||||
"""安全地获取 business_object_id"""
|
||||
return obj.business_object.id if obj.business_object else None
|
||||
|
||||
def get_plate_image_url(self, obj):
|
||||
"""获取图片完整URL"""
|
||||
if obj.plate_image:
|
||||
|
||||
@@ -418,20 +418,23 @@ class PrintingJobAPITestCase(TestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('quantity', response.data)
|
||||
|
||||
def test_validate_pieces_positive(self):
|
||||
"""测试件数必须大于0"""
|
||||
def test_validate_pieces_can_be_zero(self):
|
||||
"""测试件数可以为0或null"""
|
||||
data = {
|
||||
'printing_order': self.printing_order.id,
|
||||
'product': self.product.id,
|
||||
'quantity': 100,
|
||||
'unit': '米',
|
||||
'size': '50*60',
|
||||
'pieces': 0 # 无效件数
|
||||
'pieces': 0 # 允许为0
|
||||
}
|
||||
|
||||
response = self.client.post('/api/v1/printing-jobs/', data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('pieces', response.data)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
# 验证创建成功
|
||||
job = printing_models.PrintingJob.objects.get(id=response.data['id'])
|
||||
self.assertEqual(job.pieces, 0)
|
||||
|
||||
def test_create_job_with_business_object(self):
|
||||
"""测试创建任务自动创建 BusinessObject"""
|
||||
@@ -583,6 +586,153 @@ class PrintingJobAPITestCase(TestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('没有关联的流程实例', response.data['detail'])
|
||||
|
||||
def test_advance_with_parameters(self):
|
||||
"""测试推进到下一个状态 - 带参数"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
# 创建 BusinessObject
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=self.process,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
# 推进到下一个状态,带参数
|
||||
data = {
|
||||
'parameters': {
|
||||
'temperature': '25.5',
|
||||
'operator': '张三',
|
||||
'humidity': '60%'
|
||||
}
|
||||
}
|
||||
response = self.client.post(
|
||||
f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/',
|
||||
data,
|
||||
format='json'
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertIn('detail', response.data)
|
||||
self.assertIn('data', response.data)
|
||||
|
||||
# 验证参数已保存
|
||||
job.refresh_from_db()
|
||||
state_log = business_object.state_logs.filter(state=self.state1, is_cancelled=False).first()
|
||||
self.assertIsNotNone(state_log)
|
||||
|
||||
# 验证参数记录
|
||||
param_record = state_log.parameter_records.first()
|
||||
self.assertIsNotNone(param_record)
|
||||
self.assertEqual(param_record.parameters['temperature'], '25.5')
|
||||
self.assertEqual(param_record.parameters['operator'], '张三')
|
||||
self.assertEqual(param_record.parameters['humidity'], '60%')
|
||||
|
||||
def test_advance_with_required_parameters_missing(self):
|
||||
"""测试推进到下一个状态 - 缺少必填参数"""
|
||||
# 创建带必填参数的状态
|
||||
param1 = stateflow_models.StateParameter.objects.create(
|
||||
key='temperature',
|
||||
is_required=True,
|
||||
description='温度'
|
||||
)
|
||||
param2 = stateflow_models.StateParameter.objects.create(
|
||||
key='operator',
|
||||
is_required=True,
|
||||
description='操作员'
|
||||
)
|
||||
|
||||
state_with_params = stateflow_models.State.objects.create(name='带参数状态')
|
||||
state_with_params.parameters.add(param1, param2)
|
||||
|
||||
# 创建新流程,第一个状态是带必填参数的
|
||||
process_with_params = stateflow_models.Process.objects.create(name='带参数流程')
|
||||
process_with_params.replace_nodes([state_with_params, self.state2])
|
||||
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
# 创建 BusinessObject
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=process_with_params,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
# 尝试推进但不提供必填参数
|
||||
response = self.client.post(f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/')
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('缺失必填参数', response.data['detail'])
|
||||
self.assertIn('temperature', response.data['detail'])
|
||||
self.assertIn('operator', response.data['detail'])
|
||||
|
||||
def test_advance_with_partial_required_parameters(self):
|
||||
"""测试推进到下一个状态 - 只提供部分必填参数"""
|
||||
# 创建带必填参数的状态
|
||||
param1 = stateflow_models.StateParameter.objects.create(
|
||||
key='temperature',
|
||||
is_required=True,
|
||||
description='温度'
|
||||
)
|
||||
param2 = stateflow_models.StateParameter.objects.create(
|
||||
key='operator',
|
||||
is_required=True,
|
||||
description='操作员'
|
||||
)
|
||||
|
||||
state_with_params = stateflow_models.State.objects.create(name='带参数状态')
|
||||
state_with_params.parameters.add(param1, param2)
|
||||
|
||||
# 创建新流程
|
||||
process_with_params = stateflow_models.Process.objects.create(name='带参数流程')
|
||||
process_with_params.replace_nodes([state_with_params, self.state2])
|
||||
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
# 创建 BusinessObject
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=process_with_params,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
# 只提供一个必填参数
|
||||
data = {
|
||||
'parameters': {
|
||||
'temperature': '25.5'
|
||||
}
|
||||
}
|
||||
response = self.client.post(
|
||||
f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/',
|
||||
data,
|
||||
format='json'
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('缺失必填参数', response.data['detail'])
|
||||
self.assertIn('operator', response.data['detail'])
|
||||
self.assertNotIn('temperature', response.data['detail']) # temperature 已提供
|
||||
|
||||
def test_advance_through_all_states(self):
|
||||
"""测试推进到完成所有状态"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
|
||||
@@ -318,6 +318,16 @@ class PrintingJobViewSet(viewsets.ModelViewSet):
|
||||
|
||||
将印染任务推进到下一个流程状态
|
||||
|
||||
请求体:
|
||||
{
|
||||
"parameters": {
|
||||
"temperature": "25.5",
|
||||
"operator": "张三"
|
||||
}
|
||||
}
|
||||
|
||||
参数可选,但如果下一个状态有必填参数,则必须提供
|
||||
|
||||
注意:这是对 stateflow.BusinessObject.advance 接口的便捷包装,
|
||||
自动通过 printing_job 查找关联的 business_object 并调用状态流转服务。
|
||||
前端只需提供 printing_job 的 ID,无需关心 business_object_id。
|
||||
@@ -330,10 +340,13 @@ class PrintingJobViewSet(viewsets.ModelViewSet):
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# 获取请求参数
|
||||
parameters = request.data.get('parameters', {})
|
||||
|
||||
# 调用 stateflow 统一服务进行状态流转
|
||||
from stateflow import services as stateflow_services
|
||||
success, message, state_log = stateflow_services.advance_to_next_state(
|
||||
job.business_object, request.user
|
||||
job.business_object, request.user, **parameters
|
||||
)
|
||||
|
||||
if not success:
|
||||
@@ -680,6 +693,16 @@ class PlateOrderViewSet(viewsets.ModelViewSet):
|
||||
|
||||
将开版订单推进到下一个流程状态
|
||||
|
||||
请求体:
|
||||
{
|
||||
"parameters": {
|
||||
"inspector": "李四",
|
||||
"quality_score": "95"
|
||||
}
|
||||
}
|
||||
|
||||
参数可选,但如果下一个状态有必填参数,则必须提供
|
||||
|
||||
注意:这是对 stateflow.BusinessObject.advance 接口的便捷包装,
|
||||
自动通过 plate_order 查找关联的 business_object 并调用状态流转服务。
|
||||
"""
|
||||
@@ -691,10 +714,13 @@ class PlateOrderViewSet(viewsets.ModelViewSet):
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# 获取请求参数
|
||||
parameters = request.data.get('parameters', {})
|
||||
|
||||
# 调用 stateflow 统一服务进行状态流转
|
||||
from stateflow import services as stateflow_services
|
||||
success, message, state_log = stateflow_services.advance_to_next_state(
|
||||
plate_order.business_object, request.user
|
||||
plate_order.business_object, request.user, **parameters
|
||||
)
|
||||
|
||||
if not success:
|
||||
|
||||
Reference in New Issue
Block a user