# 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`: 出货日期时间范围（支持传 `YYYY-MM-DD` 或日期时间；`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`: 件数范围
    - `include_sales_order_bound`: 是否返回 `is_sales_order_bound` 字段，默认 `true`；传 `false`/`0`/`no` 时不返回
    - `search`: 全文搜索 (产品名称, 单位, 尺寸, 备注)
    - `ordering`: 排序字段。
  - **响应**: `PrintingJobListSerializer` 列表。
    - **重要变更**: 列表中已**包含** `business_object_id` 字段。
    - **字段**: `is_sales_order_bound` 表示该印染任务是否已被销售单明细绑定。

- **GET** `/api/v1/printing-jobs/{id}/`
  - **描述**: 获取单个印染任务详情。
  - **查询参数**:
    - `include_sales_order_bound`: 是否返回 `is_sales_order_bound` 字段，默认 `true`；传 `false`/`0`/`no` 时不返回
  - **响应**: `PrintingJobDetailSerializer`。
    - **重要变更**: `business_object_id` 字段现在会安全地返回 `null` 而不是报错（当 `business_object` 不存在时）。
    - **字段**: `is_sales_order_bound` 表示该印染任务是否已被销售单明细绑定。

- **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`。

- **POST** `/api/v1/printing-jobs/mark-production-completed/`
  - **描述**: 显式将指定的 `PrintingJob` 标记为“已完成生产”。
  - **后续动作**: 成功后会触发 `printing_job_production_completed` 领域信号，并由异步 Celery task 发送企业微信通知。
  - **请求体**:
    ```json
    {
      "printing_job_id": 123
    }
    ```
  - **成功响应**: `200 OK`
    ```json
    {
      "message": "OK"
    }
    ```
  - **失败响应**:
    - `400 Bad Request` - 缺少 `printing_job_id`
    - `403 Forbidden` - 当前用户未关联商户，或试图操作其他商户的 `PrintingJob`
    - `404 Not Found` - `PrintingJob` 不存在

### 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`: 要求完成时间范围（ISO8601，含具体时间）
    - `created_date_from`/`to`: 创建日期范围
    - `search`: 全文搜索 (设计编号, 款式名称, 客户名称, 面料)
    - `ordering`: 排序字段。
  - **响应**: `PlateOrderListSerializer` 列表。

- **GET** `/api/v1/plate-orders/{id}/`
  - **描述**: 获取单个开版订单详情。
  - **响应**: `PlateOrderDetailSerializer`。
    - **重要变更**: `business_object_id` 字段现在会安全地返回 `null` 而不是报错。

- **POST** `/api/v1/plate-orders/`
  - **描述**: 创建一个新的开版订单。
  - **请求体**: `PlateOrderCreateUpdateSerializer`。
  - **字段补充**: `image_name`（可空字符串）用于记录上传的开版图片名称，即使未上传文件也可单独填写。

- **PUT/PATCH** `/api/v1/plate-orders/{id}/`
  - **描述**: 完全或部分更新一个开版订单。
  - **请求体**: `PlateOrderCreateUpdateSerializer`。
  - **字段补充**: 支持更新 `image_name`，便于修正或补录图片名称。

- **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
  })
});
```
