1
0
forked from erp-dev/erp

fix: appversions

This commit is contained in:
2026-07-11 00:05:05 +08:00
parent 48e4782e1e
commit e91d06e4b6
26 changed files with 2582 additions and 19 deletions

View File

@@ -0,0 +1,248 @@
# 设计师工序任务量统计 API v1 文档
面向前端和业务报表对接。接口统一前缀为 `/api/v1/`
## 接口
`GET /api/v1/settlement/workflows/designer-task-summary/`
按设计师统计工序完成任务量。该接口统计的是“工序任务量”,不是去重后的开版订单数量。
## 与旧接口区别
旧接口:
`GET /api/v1/settlement/plate-orders/designer-summary/`
- 统计口径:设计师关联的开版订单数量。
- 统计单位:去重后的 `PlateOrder`
- 时间口径:`PlateOrder.plate_date`
新接口:
`GET /api/v1/settlement/workflows/designer-task-summary/`
- 统计口径:设计师实际完成的工序任务量。
- 统计单位:未撤销的 `StateFlowRecord`,一条状态流转记录代表一次工序完成事件。
- 时间口径:优先使用参数 `完成时间`,为空或无法解析时回退 `StateFlowRecord.completed_at`
- 数量口径:读取参数 `完成数量`,有效正数按实际值,否则按 `1`
## 认证
- 标准登录认证JWT / Session。
- Agent 专用认证:请求头 `X-AGENT-SECRET: RCYH_BOT_0083`
- 普通用户必须关联员工和商户。
## 查询参数
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `date` | string | 是 | 统计截止日期,格式 `YYYY-MM-DD` |
| `state_names` | string/string[] | 否 | 参与统计的工序节点名称;不传时默认统计所有名称以 `完成` 结尾的节点;传入时按节点名称精确过滤;支持逗号分隔或重复传参 |
| `designer_names` | string/string[] | 否 | 按设计师名称筛选;支持逗号分隔或重复传参 |
请求示例:
```http
GET /api/v1/settlement/workflows/designer-task-summary/?date=2026-07-10
GET /api/v1/settlement/workflows/designer-task-summary/?date=2026-07-10&state_names=,
GET /api/v1/settlement/workflows/designer-task-summary/?date=2026-07-10&state_names=&state_names=&designer_names=
```
## 统计口径
核心链路:
```text
StateFlowRecord
-> State
-> StateLogParameterRecord
-> BusinessObject
-> PlateOrder
```
有效记录条件:
- `StateFlowRecord.is_cancelled = false`
- 不传 `state_names` 时,`State.name` 必须以 `完成` 结尾。
- 传入 `state_names` 时,`State.name` 必须在 `state_names` 范围内。
- 能够关联到当前商户下的 `PlateOrder`
- `PlateOrder.plate_type``PlateOrder.production_method` 非空。
- 同一次状态流转合并后的参数中能读取到非空 `设计师名称`
- 满足当前用户的客户可见性规则。
统计单位:
- 一条有效 `StateFlowRecord` 代表一次工序完成事件。
- 不按 `PlateOrder` 去重。
- 同一个开版单完成多个工序时,每个工序分别统计。
- 同一个开版单同一工序存在多次有效完成记录时,每次记录分别统计。
- 一次状态流转关联多条参数记录时,只累计一次,后提交的参数会覆盖同名旧参数。
任务量规则:
| `完成数量` 原始值 | 计入任务量 |
| --- | ---: |
| 参数不存在 | 1 |
| `null` | 1 |
| 空字符串 | 1 |
| `0` | 1 |
| 负数 | 1 |
| 非数字 | 1 |
| `1` | 1 |
| `3` | 3 |
| `1.5` | 1.5 |
时间规则:
- 优先使用合并参数中的 `完成时间`
- 如果 `完成时间` 为空或无法解析,使用 `StateFlowRecord.completed_at`
- `today`:有效统计时间在 `date` 当天 00:00至次日 00:00不含
- `current_month`:有效统计时间在当月 1 日 00:00`date` 次日 00:00不含
- 时间范围按 Django 当前业务时区构造。
分组维度:
- 设计师名称:`设计师名称`
- 工序节点:`State.name`
- 开版类型:`PlateOrder.plate_type-PlateOrder.production_method`
## 响应
成功响应:`200 OK`
```json
{
"data": [
{
"designer_name": "左威",
"task_count": [
{
"state_name": "画图完成",
"type": "首版-定位",
"today": 4,
"current_month": 18
},
{
"state_name": "调色完成",
"type": "首版-定位",
"today": 2,
"current_month": 10
}
],
"today": 6,
"current_month": 28
}
],
"meta": {
"date": "2026-07-10",
"state_names": ["画图完成", "调色完成"],
"state_filter": {
"mode": "exact",
"state_names": ["画图完成", "调色完成"]
},
"designer_names": [],
"designer_param_key": "设计师名称",
"quantity_param_key": "完成数量",
"time_param_key": "完成时间",
"empty_quantity_default": 1,
"time_source": "parameters.完成时间; fallback=StateFlowRecord.completed_at"
}
}
```
字段说明:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `designer_name` | string | 设计师名称 |
| `task_count` | array | 该设计师按工序和开版类型拆分的任务量 |
| `state_name` | string | 工序节点名称 |
| `type` | string | `plate_type-production_method` |
| `task_count[].today` | number | 当前分组当天任务量 |
| `task_count[].current_month` | number | 当前分组当月累计任务量 |
| `data[].today` | number | 该设计师当天全部分组任务量合计 |
| `data[].current_month` | number | 该设计师当月全部分组任务量合计 |
无数据时:
```json
{
"data": [],
"meta": {
"date": "2026-07-10",
"state_names": [],
"state_filter": {
"mode": "suffix",
"suffix": "完成"
},
"designer_names": [],
"designer_param_key": "设计师名称",
"quantity_param_key": "完成数量",
"time_param_key": "完成时间",
"empty_quantity_default": 1,
"time_source": "parameters.完成时间; fallback=StateFlowRecord.completed_at"
}
}
```
## 错误响应
缺少日期:`400 Bad Request`
```json
{"error": "缺少 date 参数"}
```
日期格式错误:`400 Bad Request`
```json
{"error": "日期格式错误,请使用 YYYY-MM-DD 格式"}
```
日期不存在:`403 Forbidden`
```json
{"error": "日期不存在"}
```
`state_names` 传入后为空:`400 Bad Request`
```json
{"error": "state_names 不能为空"}
```
`designer_names` 传入后为空:`400 Bad Request`
```json
{"error": "designer_names 不能为空"}
```
用户未关联商户:`403 Forbidden`
```json
{"error": "用户未关联商户"}
```
系统异常:`500 Internal Server Error`
```json
{"error": "获取统计数据失败"}
```
## 实现说明
核心 service`settlement.services.get_designer_workflow_task_summary(...)`
实现策略:
-`StateFlowRecord` 为主查询对象,避免参数表 join 导致重复累计。
- 预取 `StateLogParameterRecord` 后按 `created_at, id` 顺序合并参数。
- 对每个 `StateFlowRecord` 最多累计一次任务量。
- 使用 Python 安全解析 `完成数量``完成时间`
性能说明:
- 当前实现优先保证统计口径正确。
- `完成时间` 来源于 JSON 参数时不可直接依赖普通数据库索引。
- 如果后续数据量增长明显,建议将有效统计时间和任务数量冗余落地到可索引字段或报表中间表。

View File

@@ -0,0 +1,183 @@
# 开版订单设计师统计 API v1 文档
面向前端和 Agent 对接。接口统一前缀为 `/api/v1/`
## 接口
`GET /api/v1/settlement/plate-orders/designer-summary/`
按设计师统计指定日期当天、以及当月截至指定日期的开版订单数量。
## 认证
- 标准登录认证JWT / Session。
- Agent 专用认证:请求头 `X-AGENT-SECRET: RCYH_BOT_0083`
- 普通用户必须关联员工和商户。
## 查询参数
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `date` | string | 是 | 统计日期,格式 `YYYY-MM-DD` |
| `state_names` | string/string[] | 否 | 参与统计的 stateflow 节点名称,默认 `画图完成`;支持逗号分隔或重复传参 |
`state_names` 示例:
```http
GET /api/v1/settlement/plate-orders/designer-summary/?date=2026-02-08
GET /api/v1/settlement/plate-orders/designer-summary/?date=2026-02-08&state_names=,
GET /api/v1/settlement/plate-orders/designer-summary/?date=2026-02-08&state_names=&state_names=
```
## 统计口径
数据来源不是 `PlateOrder.designer` 字段,而是 stateflow 参数记录。
默认口径:
- 只统计 stateflow 节点名为 `画图完成` 的记录。
-`StateLogParameterRecord.parameters["设计师名称"]` 读取设计师名称。
- 只统计未撤销的状态流转记录:`StateFlowRecord.is_cancelled = false`
- 只统计当前商户的 `PlateOrder`
- 只统计 `plate_type``production_method` 非空的 `PlateOrder`
-`plate_type-production_method` 生成类型,例如 `首版-定位`
- 同一个设计师在同一个开版单、同一个类型下出现多条参数记录时,只计 1 单。
- 如果通过 `state_names` 纳入多个节点,同一个开版单在不同节点出现不同设计师时,会分别计入对应设计师。
时间口径:
- `today``PlateOrder.plate_date``date` 当天内的去重开版单数量。
- `current_month``PlateOrder.plate_date` 在当月 1 日 00:00 到 `date` 次日 00:00 之前的去重开版单数量。
- 时间范围按 Django 当前时区构造,避免直接用 `plate_date__date` 导致数据库索引利用变差。
可见性口径:
- 超级管理员或拥有 `printing.view_all_plateorders` 权限:可看当前商户下所有客户的开版单统计。
- 普通员工:只统计其创建的客户,或 `Customer.visible_employees` 包含该员工的客户。
## 响应
成功响应:`200 OK`
```json
{
"data": [
{
"designer_name": "徐煜耀",
"plate_order_count": [
{
"type": "首版-定位",
"today": 2,
"current_month": 9
}
]
}
],
"meta": {
"state_names": ["画图完成"],
"designer_param_key": "设计师名称"
}
}
```
字段说明:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `data` | array | 设计师统计列表 |
| `designer_name` | string | 从 stateflow 参数 `设计师名称` 读取的设计师名称 |
| `plate_order_count` | array | 该设计师下按类型分组的开版单数量 |
| `type` | string | `plate_type-production_method` |
| `today` | integer | 指定日期当天数量 |
| `current_month` | integer | 当月截至指定日期的累计数量 |
| `meta.state_names` | string[] | 本次参与统计的 stateflow 节点名称 |
| `meta.designer_param_key` | string | 设计师名称来源参数 key目前固定为 `设计师名称` |
无数据时:
```json
{
"data": [],
"meta": {
"state_names": ["画图完成"],
"designer_param_key": "设计师名称"
}
}
```
## 错误响应
缺少日期:`400 Bad Request`
```json
{
"error": "缺少 date 参数"
}
```
日期格式错误:`400 Bad Request`
```json
{
"error": "日期格式错误,请使用 YYYY-MM-DD 格式"
}
```
日期不存在:`403 Forbidden`
```json
{
"error": "日期不存在"
}
```
`state_names` 传入后为空:`400 Bad Request`
```json
{
"error": "state_names 不能为空"
}
```
用户未关联商户:`403 Forbidden`
```json
{
"error": "用户未关联商户"
}
```
系统异常:`500 Internal Server Error`
```json
{
"error": "获取统计数据失败"
}
```
## 实现说明
核心 service`settlement.services.get_plate_order_summary_by_designer(...)`
核心关联链路:
```text
StateLogParameterRecord
-> StateFlowRecord
-> State
-> BusinessObject
-> PlateOrder
```
默认参数:
```python
DEFAULT_DESIGNER_SUMMARY_STATE_NAMES = ("画图完成",)
DEFAULT_DESIGNER_PARAM_KEY = "设计师名称"
```
性能说明:
- 当前实现使用 Django ORM。
- 查询使用 `plate_date >= start``plate_date < end` 的 datetime range避免 `plate_date__date`
- `StateLogParameterRecord.parameters` 当前没有 GIN 索引;如果参数记录规模明显增长,可评估重新增加 JSONB GIN 索引或将设计师统计字段冗余落地。

View File

@@ -159,6 +159,28 @@
}
```
### ShipmentPrintingJobSummary
出货单/送货单关联生产任务的精简 DTO仅用于快速跳转生产任务详情。
```json
{
"id": 123,
"printing_order_id": 456,
"external_order_id": "KD20453713",
"customer_name": "客户A"
}
```
字段说明:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | integer | `PrintingJob.id` |
| `printing_order_id` | integer | 关联的 `PrintingOrder.id` |
| `external_order_id` | string/null | 关联生产订单的外部订单编号 |
| `customer_name` | string/null | 关联生产订单客户名称 |
`shipments` 内元素为送货单内出货单摘要:
```json
@@ -166,6 +188,14 @@
"id": 1,
"customer": 10,
"customer_name": "客户A",
"address_id": 5,
"address": "杭州市测试路 1 号",
"contact_name": "张三",
"contact_phone": "13800138000",
"area": "华东",
"coordinates": "120.1551,30.2741",
"geo_coordinates": null,
"extra": {"dock": "A"},
"fabric": "面料信息",
"order_description": "订单描述",
"shipment_date": "2026-01-14",
@@ -176,6 +206,8 @@
}
```
地址说明:送货单本身不保存地址;一个送货单可包含多个出货单,每个出货单可能有不同地址。送货单返回的 `shipments[]` 中地址字段均来自对应 `Shipment` 的地址快照字段,`address_id` 仅表示关联的客户地址 ID。
## 出货单接口
### 查询出货单列表
@@ -314,6 +346,30 @@
响应:`200 Shipment`
### 查询出货单关联生产任务
`GET /api/v1/shipment/shipments/{id}/printing-jobs/`
用于快速获取该出货单关联的生产任务列表,不返回销售品明细。
关联路径:`Shipment -> SalesItem.printing_job_id -> PrintingJob`
查询参数:
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `limit` | integer | 否 | 分页大小 |
| `offset` | integer | 否 | 分页偏移 |
响应:分页 `ShipmentPrintingJobSummary[]`
说明:
- 仅统计未软删除销售品:`SalesItem.delete_at IS NULL`
- 相同 `printing_job_id` 会去重。
- 默认按 `PrintingJob.id` 升序。
- 如需销售品明细,调用 `GET /api/v1/shipment/shipments/{id}/`
### 更新出货单
`PATCH /api/v1/shipment/shipments/{id}/`
@@ -473,6 +529,31 @@
响应:`200 ShipmentDelivery`
### 查询送货单关联生产任务
`GET /api/v1/shipment/deliveries/{id}/printing-jobs/`
用于快速获取该送货单下所有出货单关联的生产任务列表,不返回销售品明细。
关联路径:`ShipmentDelivery -> Shipments -> SalesItem.printing_job_id -> PrintingJob`
查询参数:
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `limit` | integer | 否 | 分页大小 |
| `offset` | integer | 否 | 分页偏移 |
响应:分页 `ShipmentPrintingJobSummary[]`
说明:
- 仅统计未软删除销售品:`SalesItem.delete_at IS NULL`
- 同一个生产任务通过多个销售品或多个出货单关联时只返回一次。
- 默认按 `PrintingJob.id` 升序。
- 如需送货单内出货单摘要,调用 `GET /api/v1/shipment/deliveries/{id}/`
- 如需某个出货单的销售品明细,调用 `GET /api/v1/shipment/shipments/{shipment_id}/`
### 更新送货单
`PATCH /api/v1/shipment/deliveries/{id}/`

View File

@@ -0,0 +1,197 @@
# 送货单状态流转 API v1 说明
本文档说明送货单状态枚举、状态流转规则,以及如何通过 API 将送货单切换到“送货中/已送达/已取消”。
接口统一前缀为 `/api/v1/`
## 状态枚举
`ShipmentDeliveryStatus`
| 值 | 枚举名 | 展示文案 | 说明 |
| --- | --- | --- | --- |
| `1` | `PENDING` | 待送货 | 送货单已创建,尚未开始送货 |
| `2` | `IN_TRANSIT` | 送货中 | 已开始送货,系统会记录 `started_at` |
| `3` | `DELIVERED` | 已送达 | 已完成送达,系统会记录 `delivered_at` |
| `4` | `CANCELLED` | 已取消 | 送货单已取消,系统会记录 `cancelled_at` |
## 状态流转规则
当前允许的正常流转:
```text
待送货(1) -> 送货中(2) -> 已送达(3)
```
取消流转:
```text
待送货(1) -> 已取消(4)
送货中(2) -> 已取消(4)
已送达(3) -> 已取消(4)
```
注意:取消不通过通用 `/status/` 接口完成,而是通过独立 `/cancel/` 接口。
不允许的流转:
| 当前状态 | 目标状态 | 结果 |
| --- | --- | --- |
| 待送货(1) | 已送达(3) | 不允许,必须先切到送货中 |
| 送货中(2) | 待送货(1) | 不允许回退 |
| 已送达(3) | 待送货(1)/送货中(2) | 不允许回退 |
| 已取消(4) | 任意状态 | 不允许恢复 |
| 任意状态 | 已取消(4) via `/status/` | 不允许,请使用 `/cancel/` |
如果传入的目标状态等于当前状态,后端会幂等返回当前送货单,不重复更新时间字段。
## 修改为送货中
`POST /api/v1/shipment/deliveries/{id}/status/`
请求体:
```json
{
"status": 2
}
```
成功响应:`200 ShipmentDelivery`
关键字段示例:
```json
{
"id": 1,
"status": 2,
"status_display": "送货中",
"started_at": "2026-07-10T14:30:00+08:00",
"delivered_at": null,
"cancelled_at": null,
"operator_id": 10,
"operator_name": "操作人"
}
```
行为说明:
- 仅允许当前状态为 `待送货(1)` 时切换到 `送货中(2)`
- 成功后自动写入 `started_at`
- 成功后自动写入当前操作人为 `operator`
## 修改为已送达
`POST /api/v1/shipment/deliveries/{id}/status/`
请求体:
```json
{
"status": 3
}
```
成功响应:`200 ShipmentDelivery`
关键字段示例:
```json
{
"id": 1,
"status": 3,
"status_display": "已送达",
"started_at": "2026-07-10T14:30:00+08:00",
"delivered_at": "2026-07-10T16:30:00+08:00",
"cancelled_at": null,
"operator_id": 10,
"operator_name": "操作人"
}
```
行为说明:
- 仅允许当前状态为 `送货中(2)` 时切换到 `已送达(3)`
- 成功后自动写入 `delivered_at`
- 成功后自动写入当前操作人为 `operator`
## 取消送货单
`POST /api/v1/shipment/deliveries/{id}/cancel/`
请求体:
```json
{}
```
成功响应:`200 ShipmentDelivery`
关键字段示例:
```json
{
"id": 1,
"status": 4,
"status_display": "已取消",
"cancelled_at": "2026-07-10T17:00:00+08:00",
"cancelled_by_id": 1,
"cancelled_by_name": "取消人",
"operator_id": 10,
"operator_name": "操作人"
}
```
权限要求:需要 `shipment.cancel_shipmentdelivery`
行为说明:
- 取消接口是独立接口,不使用 `/status/`
- 成功后自动写入 `cancelled_at`
- 成功后自动写入 `cancelled_by`
- 成功后自动写入当前操作人为 `operator`
- 对已经取消的送货单再次调用取消接口是幂等的。
## 错误响应
目标状态非法:`400 Bad Request`
```json
{
"status": ["\"4\" 不是合法选项。"]
}
```
说明:`/status/` 接口只接受 `1/2/3`,取消请使用 `/cancel/`
不允许的状态流转:`400 Bad Request`
```json
{
"detail": "不允许将送货单状态从 待送货 修改为 已送达"
}
```
送货单不存在或无权访问:`404 Not Found`
```json
{
"detail": "未找到送货单"
}
```
取消权限不足:`403 Forbidden`
```json
{
"detail": "没有权限取消送货单"
}
```
## 前端建议
- “开始送货”按钮:调用 `/status/`,传 `status=2`
- “确认送达”按钮:调用 `/status/`,传 `status=3`
- “取消送货单”按钮:调用 `/cancel/`
- 不要通过 `/status/``status=4`
- 不要尝试状态回退;当前业务不支持从 `送货中/已送达/已取消` 回退。