forked from erp-dev/erp
feat: added print_count api, added merchant setting model, added manual mode setting for purchase order
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
# PlateOrder 文件上传时返回 401 错误修复报告
|
||||
|
||||
> **更新(2025-11)**:`PlateOrder.plate_image` 现已改为 JSON 字段,仅接受通过 `/api/v1/upload/` 上传后的 `file_id` 引用,不再直接处理 multipart/form-data。以下内容保留旧问题的排查过程以供参考。
|
||||
|
||||
## 📋 问题描述
|
||||
|
||||
**症状:**
|
||||
|
||||
@@ -38,8 +38,8 @@ GET /api/v1/plate-orders/
|
||||
| style_name | string | 否 | 款式名称(模糊查询) |
|
||||
| plate_date_from | date | 否 | 开版日期起始(YYYY-MM-DD) |
|
||||
| plate_date_to | date | 否 | 开版日期结束(YYYY-MM-DD) |
|
||||
| required_completion_date_from | date | 否 | 要求完成日期起始 |
|
||||
| required_completion_date_to | date | 否 | 要求完成日期结束 |
|
||||
| required_completion_date_from | datetime | 否 | 要求完成时间起始(ISO8601,例:2025-11-20T08:00:00Z) |
|
||||
| required_completion_date_to | datetime | 否 | 要求完成时间结束(ISO8601) |
|
||||
| created_date_from | date | 否 | 创建日期起始 |
|
||||
| created_date_to | date | 否 | 创建日期结束 |
|
||||
| search | string | 否 | 全文搜索(搜索设计编号、款式名称、客户名称、面料) |
|
||||
@@ -75,7 +75,7 @@ GET /api/v1/plate-orders/
|
||||
"style_name": "花朵印染",
|
||||
"fabric": "棉布",
|
||||
"width": "150cm",
|
||||
"required_completion_date": "2025-11-20",
|
||||
"required_completion_date": "2025-11-20T18:00:00Z",
|
||||
"completion_date": null,
|
||||
"is_ordered": false,
|
||||
"process": 1,
|
||||
@@ -112,8 +112,24 @@ GET /api/v1/plate-orders/{id}/
|
||||
"plate_type": "圆网",
|
||||
"plate_date": "2025-11-10T10:00:00Z",
|
||||
"plate_method": "手工",
|
||||
"plate_image": "/media/plate_images/design001.jpg",
|
||||
"plate_image_url": "http://example.com/media/plate_images/design001.jpg",
|
||||
"plate_image": [
|
||||
{
|
||||
"file_id": 12,
|
||||
"name": "主图",
|
||||
"url": "https://example.com/media/uploads/2025/11/plate-a.jpg",
|
||||
"path": "uploads/2025/11/plate-a.jpg"
|
||||
},
|
||||
{
|
||||
"file_id": 13,
|
||||
"name": "细节图",
|
||||
"url": "https://example.com/media/uploads/2025/11/plate-b.jpg",
|
||||
"path": "uploads/2025/11/plate-b.jpg"
|
||||
}
|
||||
],
|
||||
"plate_image_url": [
|
||||
"https://example.com/media/uploads/2025/11/plate-a.jpg",
|
||||
"https://example.com/media/uploads/2025/11/plate-b.jpg"
|
||||
],
|
||||
"plate_notes": "注意色彩还原",
|
||||
"reprint_reason": null,
|
||||
"urgency_level": "加急",
|
||||
@@ -140,7 +156,7 @@ GET /api/v1/plate-orders/{id}/
|
||||
"difficulty_rating": "中等",
|
||||
"sample_meter": "5米",
|
||||
"required_sample_meters": 10.00,
|
||||
"required_completion_date": "2025-11-20",
|
||||
"required_completion_date": "2025-11-20T18:00:00Z",
|
||||
"completion_date": null,
|
||||
"approval_result": "通过",
|
||||
"is_ordered": false,
|
||||
@@ -181,7 +197,11 @@ Content-Type: application/json
|
||||
"salesperson": 3,
|
||||
"merchandiser": 4,
|
||||
"designer": 8,
|
||||
"required_completion_date": "2025-11-25",
|
||||
"plate_image": [
|
||||
{"file_id": 21, "name": "主图"},
|
||||
{"file_id": 22, "name": "细节图"}
|
||||
],
|
||||
"required_completion_date": "2025-11-25T06:30:00Z",
|
||||
"is_mark_frame": false,
|
||||
"plate_method": "机器",
|
||||
"production_method": "小批量",
|
||||
@@ -197,7 +217,7 @@ Content-Type: application/json
|
||||
- `plate_type`: 版型
|
||||
- `plate_date`: 开版日期
|
||||
- `plate_method`: 开版方式
|
||||
- `plate_image`: 开版图片(文件上传)
|
||||
- `plate_image`: 开版图片引用列表(数组,元素包含 `file_id` 与可选 `name`,需要先通过 `/api/v1/upload/` 上传文件)
|
||||
- `plate_notes`: 打版注意事项
|
||||
- `reprint_reason`: 复版原因
|
||||
- `urgency_level`: 紧急程度
|
||||
@@ -218,7 +238,7 @@ Content-Type: application/json
|
||||
- `difficulty_rating`: 难度评级
|
||||
- `sample_meter`: 米样
|
||||
- `required_sample_meters`: 所需样品米数
|
||||
- `required_completion_date`: 要求完成时间
|
||||
- `required_completion_date`: 要求完成时间(日期+具体时间,UTC 推荐)
|
||||
- `completion_date`: 完成时间
|
||||
- `approval_result`: 审批结果
|
||||
- `is_ordered`: 是否已下单
|
||||
@@ -576,7 +596,7 @@ GET /api/v1/plate-orders/{id}/timeline/
|
||||
| plate_type | string | 版型(首版/复版等) |
|
||||
| plate_date | datetime | 开版时间 |
|
||||
| plate_method | string | 开版方式 |
|
||||
| plate_image | file | 开版图片 |
|
||||
| plate_image | array<object> | 开版图片引用列表(通过 `UploadedFile` ID 关联) |
|
||||
| plate_notes | text | 打版注意事项 |
|
||||
| reprint_reason | text | 复版原因 |
|
||||
| urgency_level | string | 紧急程度 |
|
||||
@@ -599,7 +619,7 @@ GET /api/v1/plate-orders/{id}/timeline/
|
||||
| difficulty_rating | string | 难度评级 |
|
||||
| sample_meter | string | 米样 |
|
||||
| required_sample_meters | decimal | 所需样品米数 |
|
||||
| required_completion_date | date | 要求完成时间 |
|
||||
| required_completion_date | datetime | 要求完成时间(ISO8601 字符串) |
|
||||
| completion_date | datetime | 完成时间 |
|
||||
| approval_result | string | 审批结果 |
|
||||
| is_ordered | boolean | 是否已下单 |
|
||||
@@ -647,7 +667,7 @@ curl -X POST "http://api.example.com/api/v1/plate-orders/" \
|
||||
"style_name": "新款印染",
|
||||
"fabric": "纯棉",
|
||||
"urgency_level": "加急",
|
||||
"required_completion_date": "2025-11-20",
|
||||
"required_completion_date": "2025-11-20T18:00:00Z",
|
||||
"process": 1
|
||||
}'
|
||||
```
|
||||
@@ -709,7 +729,7 @@ curl -X GET "http://api.example.com/api/v1/plate-orders/1/timeline/" \
|
||||
2. **权限控制**: 作废和恢复操作需要特定权限
|
||||
3. **日期格式**: 所有日期字段使用 ISO 8601 格式(YYYY-MM-DD 或 YYYY-MM-DDTHH:MM:SSZ)
|
||||
4. **分页**: 列表接口默认支持分页,使用 `limit` 和 `offset` 参数控制
|
||||
5. **文件上传**: `plate_image` 字段需要使用 multipart/form-data 格式上传
|
||||
5. **文件引用**: 需先调用 `/api/v1/upload/` 上传文件并获取 `file_id`,再通过 `plate_image` 数组提交引用(无需 multipart)
|
||||
6. **自动创建流程实例**: 创建开版订单时,如果提供了 `process` 字段,系统会自动创建对应的 BusinessObject 流程实例。如果不提供则使用默认流程
|
||||
7. **流程验证**: 提供的 `process` ID 必须在系统中存在,否则会返回验证错误
|
||||
8. **流程操作**:
|
||||
|
||||
@@ -11,18 +11,17 @@
|
||||
```json
|
||||
{
|
||||
"supplier": 1,
|
||||
"warehouse": 2,
|
||||
"warehouse_id": 2,
|
||||
"order_date": "2025-11-26",
|
||||
"total_amount": "1200.50",
|
||||
"remarks": "测试采购单",
|
||||
"items": [
|
||||
{
|
||||
"product_id": 10,
|
||||
"quantities": ["10.5", "6.0"]
|
||||
},
|
||||
{
|
||||
"product_id": 18,
|
||||
"quantities": ["3.25"]
|
||||
"quantity": 120,
|
||||
"num_of_rolls": 3,
|
||||
"price": "12.50",
|
||||
"unit": "米",
|
||||
"empty_diff_percent": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -31,15 +30,23 @@
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| supplier | integer | ✅ | 供应商 ID,必须隶属于当前商户 |
|
||||
| warehouse | integer | ✅ | 入库仓库 ID |
|
||||
| warehouse / warehouse_id | integer | ✅ | 入库仓库 ID(两字段二选一,推荐 `warehouse_id`) |
|
||||
| order_date | string (date) | ✅ | 订单日期(`YYYY-MM-DD`) |
|
||||
| total_amount | string/number | ✅ | 采购总金额 |
|
||||
| remarks | string | 否 | 备注 |
|
||||
| items | array | ✅ | 入库明细,结构需满足 `StockFlowService` 的要求(目前仅支持严谨模式:product_id + quantities) |
|
||||
| items | array | ✅ | 入库明细,根据仓库模式提供不同字段 |
|
||||
|
||||
> `items` 内部字段会被直接传给 `StockFlowService`,因此:
|
||||
> - 严谨/严进模式:`{'product_id': 1, 'quantities': ['10', '5']}`
|
||||
> - 宽松模式/严进严出出库暂未开放,后续按需扩展。
|
||||
#### items 结构说明
|
||||
|
||||
- **宽进仓(UNRESTRICTED)**
|
||||
- 必填:`product_id`、`quantity`、`num_of_rolls`
|
||||
- `quantity_of_rolls` 会自动置空,并以 `{value, num_of_rolls}` 的形式传递给 `StockFlowService.stock_in` 的宽松模式。
|
||||
|
||||
- **严进仓(RESTRICT_IN / RESTRICT_IN_OUT)**
|
||||
- 必填:`product_id`、`numbers`(数组)
|
||||
- 后端会以数组长度设置 `num_of_rolls`,把所有数值拼为 `quantity_of_rolls="10,5,8"`,同时求和得到 `quantity`,并生成 `{'quantities': ['10','5','8']}` 传递给严谨模式。
|
||||
|
||||
公共可选字段:`price`、`unit`、`color`、`empty_diff_percent`、`batch_number`、`remarks`。缺省时默认使用 0 或产品单位。
|
||||
⚠️ 当仓库模式与 items 字段不匹配(例如严进仓缺少 `numbers`、宽进仓提供 `numbers`)时将返回 `400`,提示“仓库为 ×× 模式,items[n] 需要提供 …”。
|
||||
|
||||
### 响应
|
||||
|
||||
@@ -56,21 +63,20 @@
|
||||
|
||||
| 状态码 | 示例 | 说明 |
|
||||
|--------|------|------|
|
||||
| 400 | `{"error": "缺少供应商 ID"}` | 请求缺失关键字段 |
|
||||
| 400 | `{"error": "供应商 99 不存在"}` | 提供的供应商不属于当前商户 |
|
||||
| 400 | `{"error": "缺少仓库 ID"}` | 请求缺失关键字段 |
|
||||
| 400 | `{"error": "仓库为严进模式,items[0] 需要提供 numbers 数组"}` | 参数与仓库模式不匹配 |
|
||||
| 400 | `{"error": "供应商 99 不存在"}` | 供应商不属于当前商户 |
|
||||
| 403 | `{"error": "无权限访问"}` | 当前用户无员工信息 |
|
||||
|
||||
### 关联任务(business/tasks.py)
|
||||
|
||||
`create_purchase_order_stock_entries` 任务会接收 `purchase_order_id`、`warehouse_id`、`items` 等信息,并通过 `StockFlowService.stock_in` 创建入库记录。
|
||||
`create_purchase_order_stock_entries` 任务会接收 `purchase_order_id`、`warehouse_id`、已转换好的 `items` 信息,并通过 `StockFlowService.stock_in` 创建入库记录。
|
||||
|
||||
- 任务日志示例:`采购单 35 入库任务完成`
|
||||
- 返回 payload 包含 `stock_change_record_id`、`created_details_count`
|
||||
|
||||
### 测试
|
||||
|
||||
`api_v1/tests.py` 中新增 `PurchaseOrderAPITestCase`,通过 eager Celery 设置验证:
|
||||
|
||||
1. API 请求返回 201
|
||||
2. Celery 任务被成功调用(patch `create_purchase_order_stock_entries.delay` 断言参数)
|
||||
`api_v1/tests.py` 中的 `PurchaseOrderAPITestCase` 覆盖宽进/严进模式、模式不匹配和未登录场景;
|
||||
`business/tests.py` 中的 `PurchaseOrderServiceTestCase`、`PurchaseOrderStockTaskTestCase` 验证 service 层逻辑与 Celery 任务(通过 mock `StockFlowService`)。
|
||||
|
||||
|
||||
54
docs/print_count_delta.md
Normal file
54
docs/print_count_delta.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# 打印次数增量 API
|
||||
|
||||
该接口用于为支持 `print_count` 字段的业务对象递增打印次数。目前支持:
|
||||
|
||||
- `printing_order` → `printing.models.PrintingOrder`
|
||||
- `plate_order` → `printing.models.PlateOrder`
|
||||
|
||||
未来可以在后端枚举中扩展更多对象类型(例如采购单等)。
|
||||
|
||||
## Endpoint
|
||||
|
||||
- **Method**: `POST`
|
||||
- **Path**: `/api/v1/print-count/delta/`
|
||||
- **Auth**: 需要登录(仅校验是否认证)
|
||||
- **Content-Type**: `application/json`
|
||||
|
||||
## 请求参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `object_type` | string | 是 | 对象类型,`printing_order` 或 `plate_order` |
|
||||
| `object_id` | integer | 是 | 目标对象的 ID |
|
||||
| `delta` | integer/string | 否 | 增量值,默认 1;若传入小于 1 或无法转换为整数,则自动回退为 1 |
|
||||
|
||||
## 响应
|
||||
|
||||
```json
|
||||
{
|
||||
"object_type": "printing_order",
|
||||
"object_id": 12,
|
||||
"delta": 3,
|
||||
"print_count": 5
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
- `delta`: 实际生效的增量(若请求非法会改为 1)
|
||||
- `print_count`: 更新后的打印次数
|
||||
|
||||
## 错误响应
|
||||
|
||||
| HTTP 状态码 | 场景 | 响应 |
|
||||
|-------------|------|------|
|
||||
| 400 | `object_type` 不受支持 | `{"detail": "不支持的对象类型"}` |
|
||||
| 404 | `object_id` 与 `object_type` 匹配的对象不存在 | `{"detail": "指定对象不存在"}` |
|
||||
| 401 | 未认证访问 | DRF 默认未认证响应 |
|
||||
|
||||
## 扩展说明
|
||||
|
||||
- `delta` 仅做加法,不提供回退逻辑。
|
||||
- 所有增量操作在数据库事务中执行,可避免并发更新冲突。
|
||||
- 新的对象类型只需在后端的 `PrintCountObjectType` 枚举和映射表中补充即可。
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
| 调色评级 | ✅ | ❌ | **需添加** `color_matching_rating` | CharField(20) | 调色质量评级 |
|
||||
| 套样评级 | ✅ | ❌ | **需添加** `sample_rating` | CharField(20) | 套样质量评级 |
|
||||
| 难度评级 | ✅ | ❌ | **需添加** `difficulty_rating` | CharField(20) | 难度评级 |
|
||||
| 要求完成时间 | ✅ | ❌ | **需添加** `required_completion_date` | DateField | 要求完成日期 |
|
||||
| 要求完成时间 | ✅ | ❌ | **需添加** `required_completion_date` | DateTimeField | 要求完成日期+时间 |
|
||||
| 布料 | ✅ | ❌ | **需添加** `fabric` | CharField(100) | 布料信息 |
|
||||
| 幅宽 | ✅ | ❌ | **需添加** `width` | CharField(50) | 幅宽 |
|
||||
| 款号名称 | ✅ | ❌ | **需添加** `style_name` | CharField(100) | 款号名称 |
|
||||
|
||||
Reference in New Issue
Block a user