1
0
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:
2025-11-28 15:01:11 +08:00
parent 53f2a24b20
commit 0c62398120
36 changed files with 1634 additions and 478 deletions

View File

@@ -1,5 +1,7 @@
# PlateOrder 文件上传时返回 401 错误修复报告
> **更新2025-11**`PlateOrder.plate_image` 现已改为 JSON 字段,仅接受通过 `/api/v1/upload/` 上传后的 `file_id` 引用,不再直接处理 multipart/form-data。以下内容保留旧问题的排查过程以供参考。
## 📋 问题描述
**症状:**

View File

@@ -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 | 否 | 要求完成时间起始ISO86012025-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. **流程操作**:

View File

@@ -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
View 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` 枚举和映射表中补充即可。

View File

@@ -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) | 款号名称 |