forked from erp-dev/erp
feat: batch update for plate order
This commit is contained in:
59
docs/2025-12-17.md
Normal file
59
docs/2025-12-17.md
Normal file
@@ -0,0 +1,59 @@
|
||||
### 2025-12-17 工作总结(flower.utils 重构)
|
||||
|
||||
#### 目标
|
||||
- **降低 `flower.utils` 单文件复杂度**:将明道云/同步相关能力拆分为可维护的模块化结构。
|
||||
- **保持向后兼容**:不改动现有调用方(例如 `api_v1/tasks.py` 中 `from flower.utils import fetch_products_from_mingdaoyun`)。
|
||||
- **为后续“开版数据表”关联查询打基础**:沉淀 worksheetId、rowId 查询封装、类型定义与测试。
|
||||
|
||||
#### 主要改动
|
||||
- **`flower.utils` 目录化(从单文件改为包)**
|
||||
- 将原 `flower/utils.py` 拆分为 `flower/utils/` 包,并删除旧文件。
|
||||
- 新增 `flower/utils/__init__.py` 作为兼容入口,继续导出原先常用符号。
|
||||
- 明道云相关实现统一放到 `flower/utils/mingdaoyun/`。
|
||||
|
||||
- **明道云客户端与拉取函数模块化**
|
||||
- 新增客户端:`flower/utils/mingdaoyun/client.py`
|
||||
- `MingDaoYunClient`(aiohttp 异步客户端)
|
||||
- `get_default_mingdaoyun_client()`(复用现有 appKey/sign 的默认构造)
|
||||
- 新增拉取函数:`flower/utils/mingdaoyun/fetch.py`
|
||||
- `fetch_products_from_mingdaoyun` / `fetch_customers_from_mingdaoyun`(保持原行为)
|
||||
- `fetch_plate_orders_from_mingdaoyun`:开版表拉取(先返回原始 rows dict,不做字段映射)
|
||||
- `fetch_row_by_rowid_from_mingdaoyun`:按 rowId 等值 filters 查询单条记录(后续关联表查询的通用工具)
|
||||
|
||||
- **明道云映射与类型定义沉淀**
|
||||
- `flower/utils/mingdaoyun/mappings.py`
|
||||
- 维护 worksheetId 常量与映射:产品/客户/面料/开版表
|
||||
- 补充开版表关联数据 worksheetId:画图/调色/套纸样/改图/配色/照图开发
|
||||
- 新增 `plate_order_related_worksheet_map` 与 `plate_order_related_worksheet_map_cn`,便于后续跨表关联查询定位目标表
|
||||
- `flower/utils/mingdaoyun/models.py`
|
||||
- Pydantic 类型:`Product/Customer/Fabric` 等
|
||||
- 通用结构:`MDYRelationItem/MDYAttachmentItem/MDYCollaboratorItem`
|
||||
- 开版表类型占位:`MDYPlateOrder`(仅用于后续解析阶段)
|
||||
- `flower/utils/mingdaoyun/parsers.py`
|
||||
- `pick_product/pick_customer/pick_fabric`:保持现有同步风格的字段提取方法
|
||||
|
||||
#### 测试与质量保障(与 utils 重构直接相关)
|
||||
- **新增独立单元测试**:`api_v1/test_mingdaoyun_utils.py`
|
||||
- 使用 mock,避免真实网络请求。
|
||||
- 覆盖:
|
||||
- `pick_*` 解析与类型转换
|
||||
- `fetch_*` 请求 payload 组装与返回解析
|
||||
- `fetch_row_by_rowid_from_mingdaoyun`(以产品表 `spmx` 为例的 rowId 查询)
|
||||
- `MingDaoYunClient.post` 的认证参数合并与 header 透传
|
||||
- **运行方式**(项目约定):
|
||||
- `uv run python manage.py test api_v1.test_mingdaoyun_utils`
|
||||
|
||||
#### 配套改动(为开版同步做隔离暂存,不影响业务数据)
|
||||
- **新增暂存模型**:`api_v1.models.MDYPlateOrderStaging`
|
||||
- **仅保留**:`mdy_rowid`(唯一 + 索引)与 `raw(JSON)`(整行原始数据)
|
||||
- 迁移:`api_v1/migrations/0004_mdy_plate_order_staging.py`
|
||||
- admin 注册:`api_v1/admin.py`
|
||||
|
||||
#### 穿插的测试修复(独立简述)
|
||||
- **问题**:`api_v1` 打印相关测试大量失败,根因是 `stateflow.services.advance_to_next_state` 对 `BusinessObject.content_type/object_id` 的强约束与部分“非标准创建路径”不兼容。
|
||||
- **修复**:在 `advance_to_next_state` 内加入“自愈绑定”逻辑:当未绑定/解析不到 `content_object` 时,尝试通过 `business_object.printing_job / plate_order` 反向一对一关系推断真实业务对象并补齐绑定,再继续推进。
|
||||
- **结果**:`uv run manage.py test api_v1` 全绿(`265 tests`,`skipped=2`,`expected failures=1`)。
|
||||
|
||||
#### 后续建议
|
||||
- 将明道云 `appKey/sign` 从硬编码迁移到环境变量或 Django settings(避免泄露与便于多环境配置)。
|
||||
- 在“开版表关联查询”落地时,基于 `plate_order_related_worksheet_map(_cn)` + `fetch_row_by_rowid_from_mingdaoyun` 补齐跨表解析与字段属性化提取策略。
|
||||
112
docs/api_v1_mdy_plate_order_staging.md
Normal file
112
docs/api_v1_mdy_plate_order_staging.md
Normal file
@@ -0,0 +1,112 @@
|
||||
## API:开版暂存(明道云)查询
|
||||
|
||||
### 概述
|
||||
该接口用于查询 `api_v1.models.MDYPlateOrderStaging` 中的“开版暂存”数据。
|
||||
|
||||
- **只读**:仅提供 `list/retrieve`(不支持创建/更新/删除)
|
||||
- **分页**:LimitOffset(`limit` / `offset`)
|
||||
- **默认排序**:按 `ctime` 倒序(`-ctime`),并将 `ctime = null` 的记录排在最后
|
||||
- **过滤**:支持按明道云字段 `62d52f4b8d2972284492dd0e`(设计编号/订单id)精确匹配
|
||||
- **返回结构**:`raw` 与 `related` 两个 JSONField 在输出时会做“可读化/碾平”处理
|
||||
|
||||
### Endpoint
|
||||
- **List**:`GET /api/v1/mdy-plate-order-staging/`
|
||||
- **Detail**:`GET /api/v1/mdy-plate-order-staging/{id}/`
|
||||
|
||||
### 权限与认证
|
||||
- 当前接口权限行为 **遵循项目 DRF 全局配置**。
|
||||
- 如启用 `DjangoModelPermissions`,则需要用户具备 `api_v1.view_mdyplateorderstaging` 权限。
|
||||
|
||||
### Query Params
|
||||
#### 分页
|
||||
- **limit**:返回条数
|
||||
- **offset**:偏移量
|
||||
|
||||
#### 过滤
|
||||
- **raw__62d52f4b8d2972284492dd0e**:设计编号/订单id(精确匹配)
|
||||
|
||||
示例:
|
||||
|
||||
```http
|
||||
GET /api/v1/mdy-plate-order-staging/?limit=20&offset=0&raw__62d52f4b8d2972284492dd0e=82724
|
||||
```
|
||||
|
||||
### 默认排序规则
|
||||
接口固定按以下规则排序(不开放 `ordering=` 参数):
|
||||
|
||||
1. `ctime` 倒序:`-ctime`(`nulls_last=True`)
|
||||
2. `id` 倒序:`-id`
|
||||
|
||||
### 返回字段说明(results[] 内的对象)
|
||||
- **id**:本地数据库主键
|
||||
- **mdy_rowid**:明道云 rowid(唯一)
|
||||
- **ctime**:明道云系统字段 ctime(模型字段,便于排序)
|
||||
- **utime**:明道云系统字段 utime(模型字段)
|
||||
- **created_at / updated_at**:本地记录创建/更新时间(ModelBase)
|
||||
- **design_no**:便捷字段,等价于 `raw["62d52f4b8d2972284492dd0e"]`
|
||||
- **raw**:对 `MDYPlateOrderStaging.raw` 的“可读化/碾平”输出
|
||||
- **related**:对 `MDYPlateOrderStaging.related` 的“碾平”输出(list[dict])
|
||||
|
||||
### raw / related 的碾平规则
|
||||
#### raw
|
||||
输出时会把明道云 `controlId -> 字段中文名`,并做基础 normalize:
|
||||
|
||||
- **已知字段映射**:来自 `flower.utils.mingdaoyun.mappings.plate_order_field_definitions`
|
||||
- 输出 key 使用字段中文名(例如 `设计编号`、`款号名称`)
|
||||
- value 会尝试把字符串 JSON(如 `"[]"`、`"[{...}]"`)解析成 Python/JSON 对象
|
||||
- **系统字段**:若 `raw` 内存在 `rowid/ctime/utime`,会一并输出
|
||||
- **未映射字段不丢失**:如果 `raw` 中存在未在 `plate_order_field_definitions` 里声明的字段,仍会保留,key 使用原始 `controlId` 字符串
|
||||
|
||||
#### related
|
||||
`related` 在入库阶段已经是“单条关联记录一个 dict”的结构(`list[dict]`)。
|
||||
|
||||
输出时:
|
||||
- 对每个 dict 的 value 做一次 normalize(同上)
|
||||
- 如果出现非 dict 的异常元素,兜底返回 `{ "value": <原值> }` 便于排查
|
||||
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": 123,
|
||||
"mdy_rowid": "66xxxxxxxxxxxxxxxxxxxxxx",
|
||||
"ctime": "2025-12-17T03:20:00Z",
|
||||
"utime": "2025-12-17T03:21:00Z",
|
||||
"created_at": "2025-12-17T03:30:00Z",
|
||||
"updated_at": "2025-12-17T03:30:00Z",
|
||||
"design_no": "82724",
|
||||
"raw": {
|
||||
"rowid": "66xxxxxxxxxxxxxxxxxxxxxx",
|
||||
"ctime": "2025-12-17 11:20:00",
|
||||
"utime": "2025-12-17 11:21:00",
|
||||
"设计编号": "82724",
|
||||
"款号名称": "某款号",
|
||||
"62d52f4b8d2972284492dd99": "(未映射字段示例:以 controlId 原样返回)"
|
||||
},
|
||||
"related": [
|
||||
{
|
||||
"source": "drawing",
|
||||
"source_name": "画图",
|
||||
"worksheet_id": "668ba100fb551c850214066b",
|
||||
"rowid": "66yyyyyyyyyyyyyyyyyyyyyy",
|
||||
"设计师名字": "张三",
|
||||
"电脑位置": "D:/xxx"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 性能说明
|
||||
- 过滤字段 `raw__62d52f4b8d2972284492dd0e` 对应 PostgreSQL 表达式索引(见迁移 `api_v1/migrations/0006_mdy_plate_order_staging_raw_id_index.py`),用于加速按设计编号检索。
|
||||
|
||||
### 实现位置
|
||||
- View/Serializer:`api_v1/views/mingdaoyun/plate_order_staging.py`
|
||||
- 路由注册:`api_v1/urls.py`(`mdy-plate-order-staging`)
|
||||
- 字段映射:`flower/utils/mingdaoyun/mappings.py`(`plate_order_field_definitions`)
|
||||
- 碾平工具:`flower/utils/mingdaoyun/relations.py`(`flatten_row_by_field_definitions` / `normalize_mdy_value`)
|
||||
156
docs/api_v2_plate_orders_batch_update.md
Normal file
156
docs/api_v2_plate_orders_batch_update.md
Normal file
@@ -0,0 +1,156 @@
|
||||
## API v2:PlateOrder 批量更新(batch-update)
|
||||
|
||||
### 1. 概述
|
||||
用于把**同一份更新数据**(`data`)批量应用到多条开版订单(`PlateOrder`)。
|
||||
|
||||
- **一致性**:全成功 / 全失败(任意 id 不存在或校验失败则全部不更新)
|
||||
- **字段控制**:仅允许更新“白名单字段”(服务端控制,前端不可越权更新)
|
||||
- **不触发流程副作用**:内部使用 `queryset.update()` 批量写库,不会触发 `save()` 逻辑(例如自动创建/更新流程实例等)
|
||||
- **支持 dry_run**:只校验与预览,不实际写库
|
||||
|
||||
### 2. Endpoint
|
||||
- **Method**:POST
|
||||
- **Path**:`/api/v2/plate-orders/batch-update/`
|
||||
- **Content-Type**:`application/json`
|
||||
|
||||
### 3. 权限要求
|
||||
- 需要登录(`IsAuthenticated`)
|
||||
- 需要工厂用户(`IsPrintingFactory`)
|
||||
- 需要权限:`printing.change_plateorder`
|
||||
- 若 `data` 包含 `is_invalid`:
|
||||
- `is_invalid=true` 额外需要 `printing.can_invalidate_plateorder`
|
||||
- `is_invalid=false` 额外需要 `printing.can_activate_plateorder`
|
||||
|
||||
### 4. 请求参数(Body)
|
||||
|
||||
#### 4.1 JSON Schema
|
||||
```json
|
||||
{
|
||||
"plate_order_ids": [1, 2, 3],
|
||||
"data": {
|
||||
"urgency_level": "加急",
|
||||
"designer": 10
|
||||
},
|
||||
"dry_run": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 字段说明
|
||||
- **plate_order_ids**:`int[]`(必填)
|
||||
- 需要更新的 PlateOrder id 列表
|
||||
- 会自动去重(保持首个出现顺序)
|
||||
- 任意一个 id 不存在:直接 400(不做部分更新)
|
||||
- **data**:`object`(必填)
|
||||
- 仅包含需要更新的字段键值对(字段必须在“白名单字段”中)
|
||||
- 只要 `data` 校验失败:直接 400,且不会更新任何订单
|
||||
- **dry_run**:`boolean`(可选,默认 `false`)
|
||||
- `true`:仅校验、返回匹配到的 id 与原始 data,不写库
|
||||
|
||||
### 5. 允许批量更新的字段(白名单)
|
||||
> 只允许更新以下字段;其它字段(例如 `process`/`business_object`/`created_by`/`plate_image` 等)都会被拒绝。
|
||||
|
||||
| 字段名 | 类型 | 示例 | 备注 |
|
||||
|---|---|---|---|
|
||||
| original_id | int \| null | 12345 | 克隆来源订单ID |
|
||||
| design_code | string \| null | "D20251218" | 设计编号 |
|
||||
| plate_type | string \| null | "首版" | 起版情况 |
|
||||
| plate_date | datetime \| null | "2025-12-18T10:00:00Z" | 下版时间 |
|
||||
| plate_method | string \| null | "圆网" | 开版方式 |
|
||||
| image_name | string \| null | "图A" | 图片名称 |
|
||||
| plate_notes | string \| null | "注意事项" | 打版注意事项 |
|
||||
| reprint_reason | string \| null | "复版原因" | 复版原因 |
|
||||
| urgency_level | string | "正常"/"加急" | 紧急程度 |
|
||||
| is_invalid | boolean | true/false | 作废/恢复(有额外权限要求) |
|
||||
| customer | int | 100 | 客户ID(不可为 null) |
|
||||
| area | string \| null | "广州" | 区域 |
|
||||
| default_address | string \| null | "XX路" | 默认地址 |
|
||||
| salesperson | int \| null | 11 | 销售员 Employee ID |
|
||||
| merchandiser | int \| null | 12 | 跟单员 Employee ID |
|
||||
| designer | int \| null | 13 | 设计师 Employee ID |
|
||||
| style_name | string \| null | "S1" | 款号名称 |
|
||||
| fabric | string \| null | "棉" | 布料 |
|
||||
| fabric_source | string \| null | "现货" | 布料来源 |
|
||||
| width | string \| null | "150cm" | 幅宽 |
|
||||
| production_method | string \| null | "现货" | 做货方式 |
|
||||
| is_mark_frame | boolean | true/false | 是否套唛架 |
|
||||
| drawing_rating | string \| null | "A" | 画图评级 |
|
||||
| color_matching_rating | string \| null | "B" | 调色评级 |
|
||||
| sample_rating | string \| null | "C" | 套样评级 |
|
||||
| difficulty_rating | string \| null | "简单" | 难度评级 |
|
||||
| sample_meter | string \| null | "5米" | 米样(文本) |
|
||||
| required_sample_meters | number \| null | 12.5 | 客户要求米样米数(Decimal) |
|
||||
| required_completion_date | datetime \| null | "2025-12-20T00:00:00Z" | 要求完成时间 |
|
||||
| completion_date | datetime \| null | "2025-12-19T12:00:00Z" | 完成时间 |
|
||||
| approval_result | string \| null | "通过" | 审批结果 |
|
||||
| is_ordered | boolean | true/false | 是否已下单 |
|
||||
| customer_feedback | string \| null | "请改色" | 客户修改意见 |
|
||||
|
||||
> datetime 建议使用 ISO 8601 字符串(Django/DRF 默认可解析)。
|
||||
|
||||
### 6. 响应
|
||||
|
||||
#### 6.1 成功(dry_run=false)
|
||||
- **HTTP 200**
|
||||
```json
|
||||
{
|
||||
"detail": "批量更新成功",
|
||||
"updated_count": 3,
|
||||
"plate_order_ids": [1, 2, 3]
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.2 成功(dry_run=true)
|
||||
- **HTTP 200**
|
||||
```json
|
||||
{
|
||||
"dry_run": true,
|
||||
"plate_order_ids": [1, 2, 3],
|
||||
"matched_count": 3,
|
||||
"data": {
|
||||
"urgency_level": "加急",
|
||||
"designer": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. 错误返回
|
||||
|
||||
#### 7.1 字段不在白名单
|
||||
- **HTTP 400**
|
||||
```json
|
||||
{
|
||||
"detail": "不支持批量更新字段: process",
|
||||
"allowed_fields": ["..." ]
|
||||
}
|
||||
```
|
||||
|
||||
#### 7.2 部分 id 不存在
|
||||
- **HTTP 400**
|
||||
```json
|
||||
{
|
||||
"detail": "以下 PlateOrder 不存在: 999999"
|
||||
}
|
||||
```
|
||||
|
||||
#### 7.3 权限不足
|
||||
- **HTTP 403**
|
||||
```json
|
||||
{ "detail": "您没有权限批量更新开版订单" }
|
||||
```
|
||||
或(作废/恢复权限不足):
|
||||
```json
|
||||
{ "detail": "您没有权限作废开版订单" }
|
||||
```
|
||||
|
||||
#### 7.4 字段类型/外键校验失败(DRF 校验错误)
|
||||
- **HTTP 400**(典型结构示例)
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"designer": ["Invalid pk \"999\" - object does not exist."]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8. 使用建议
|
||||
- 批量更新是“同一份 data 应用到多个订单”。如果每个订单需要不同字段值,请拆成多次调用或使用单条更新接口。
|
||||
Reference in New Issue
Block a user