1
0
forked from erp-dev/erp

fix: added merchant_id to printing_order and plate_order

This commit is contained in:
2026-01-14 14:26:03 +08:00
parent 3e75328156
commit fae667c965
20 changed files with 1049 additions and 17 deletions

191
docs/2026-01-13_summary.md Normal file
View File

@@ -0,0 +1,191 @@
# 2026-01-13 工作日志
## 今日目标
1. SSE 模块重构 → Notifications 通知系统(独立分支)
2. Stateflow 信号机制 + Shipment 模块开发(主分支)
---
## 已完成工作SSE 重构分支)
### A1. SSE 问题修复
- **根因**SSE 长连接占用数据库连接不释放,导致连接池耗尽
- **修复**:在 `sse/views.py` 中认证后立即 `connection.close()`
- **新增**连接超时30分钟、心跳超时2分钟、连接数上限每商户30个
### A2. Notifications 通知系统(新模块)
- **目的**:将事件发布与 SSE 渠道解耦,支持未来扩展(企业微信等)
- **文件结构**
```
notifications/
├── __init__.py
├── apps.py # 渠道注册
├── base.py # NotificationPayload + NotificationChannel 抽象基类
├── dispatcher.py # NotificationDispatcher 分发器
├── events.py # resource_changed(), object_event()
└── channels/
└── sse.py # SSE 渠道实现
```
- **核心设计**
- 业务代码调用 `notifications.events`,不关心具体渠道
- 分发器同步调用各渠道的 `send()`
- 各渠道自行决定同步/异步SSE 同步放入队列,企业微信应内部 Celery
- 新增渠道只需实现 `NotificationChannel` 并注册
- **配置**
- `SSE_ENABLED` 同时控制 SSE 端点和通知渠道
### A3. 文档整理
- **创建**
- `docs/notifications.md` — 后端通知接口文档
- `docs/notifications_frontend.md` — 前端 SSE 接入文档
- `docs/sse_refactor_execute_django_ver.md` — 技术方案文档
- **删除**(减少心智成本):
- `docs/sse.md`
- `docs/sse_event_interface.md`
- `docs/sse_refactor.md`
### A4. 兼容处理
- `sse/events.py` 改为兼容层,调用会触发 `DeprecationWarning`
- 现有业务代码无需立即修改
---
## 已完成工作(主分支)
### 1. Stateflow 信号机制
- **文件**`stateflow/signals.py`(新增)
- **功能**
- 定义 `process_completed` 信号 — 流程全部完成时触发
- 定义 `state_advanced` 信号 — 每次状态推进时触发
- `sender` 使用 `content_object.__class__`,支持按类型过滤
- 参数包含:`process_id`、`business_object`、`content_object`、`last_completed_state_id`、`last_completed_by` 等
- **文件**`stateflow/services.py`(修改)
- 在 `advance_to_next_state` 中发送信号
- 添加日志记录信号发送过程
---
### 2. Shipment 模块(新建)
- **文件结构**
```
shipment/
├── __init__.py
├── apps.py
├── models.py
├── admin.py
└── migrations/
├── 0001_initial.py
├── 0002_salesitem_shipment_nullable.py
└── 0003_salesitem_position_remark.py
```
- **模型**
- `Shipment`(出货单)— 关联客户,包含多个销售品
- `SalesItem`(销售品)— 名称、数量、单位(米/件/码/个、货位、备注、关联生产任务ID
- **设计特点**
- `SalesItem.shipment` 可空,支持"待分配"状态
- `printing_job_id` 使用整数而非外键,避免模块间强依赖
- 提供 `get_printing_job()` 方法获取关联对象,带完整 type hint
---
### 3. PrintingJob 流程完成自动创建销售品
- **文件**`printing/handlers.py`(新增)
- 监听 `process_completed` 信号sender=PrintingJob
- 从指定流程节点获取"米数"参数
- 自动创建 `SalesItem`,关联 `printing_job_id`
- **文件**`printing/apps.py`(修改)
- 在 `ready()` 中注册信号处理器
- 添加启动日志确认注册成功
- **配置项**settings.py + .env
```python
PRINTING_SALES_ITEM_SOURCE_STATE_ID = env.int('PRINTING_SALES_ITEM_SOURCE_STATE_ID') # 必须配置
PRINTING_SALES_ITEM_QUANTITY_KEY = '米数'
```
---
### 4. 其他修改
- **stateflow/services.py**
- 添加 `logging` 模块导入
- `clone_business_object` 函数暂停使用2026-01-13抛出 `NotImplementedError`
- **flower/settings.py**
- 添加 `'shipment'` 到 `INSTALLED_APPS`
- 添加销售品自动创建配置项
---
## 架构说明
### 信号流程
```
PrintingJob 流程推进完成
stateflow.services.advance_to_next_state()
发送 process_completed 信号 (sender=PrintingJob)
printing.handlers.on_printing_job_process_completed() 接收
从指定节点获取"米数"参数
创建 SalesItem待分配出货单
```
### 解耦设计
- **stateflow** 不依赖任何业务模块,只发送信号
- **printing** 监听信号并处理自己的业务逻辑
- **shipment** 被 printing 调用,但不知道调用者是谁
---
## 待办事项
### SSE 重构分支
- [ ] 合并到主分支后,在测试环境启用 SSE`SSE_ENABLED=True`
- [ ] 前端实现重连逻辑
- [ ] 监控数据库连接数,确认不再泄漏
- [ ] 逐步迁移业务代码到 `notifications.events`
### 主分支
- [ ] 在 `.env` 中配置 `PRINTING_SALES_ITEM_SOURCE_STATE_ID`
- [ ] 应用数据库迁移:`uv run python manage.py migrate shipment`
- [ ] 测试完整流程:推进 PrintingJob 直到完成,验证 SalesItem 创建
- [ ] 移除调试日志(生产环境前)
---
## 备注
### SSE 重构分支
- SSE 模块此前因数据库连接泄漏导致线上灾难,已被禁用
- Notifications 模块设计考虑了未来 Golang 迁移和企业微信扩展
- 企业微信渠道仅有示例代码,尚未实现
### 主分支
- Shipment 模块是全新创建的,需要执行迁移
- 信号处理器只在流程**全部完成**时触发,不是每次推进
- 如果指定节点的"米数"参数不存在,会跳过创建并记录警告日志

View File

@@ -0,0 +1,78 @@
# 2026-01-14 工作日志
## 已完成
### 1. 通过生产订单查询销售品 API
创建了独立的 Shipment API 模块,提供出货管理相关接口。
- **接口路径**: `GET /api/v1/shipment/sales-items/by-printing-order/<printing_order_id>/`
- **查询参数**: `include_already_has_shipment`(默认 false不包含已出货的销售品
#### 新建文件
- `api_v1/views/shipment/__init__.py`: 模块入口
- `api_v1/views/shipment/views.py`: API 视图 `SalesItemByPrintingOrderView`
- `api_v1/views/shipment/serializers.py`: 序列化器 `SalesItemSerializer`
- `api_v1/views/shipment/test_api.py`: API 测试用例8 个测试场景)
- `shipment/services.py`: 业务逻辑层 `get_sales_items_by_printing_order()`
- `docs/shipment_api.md`: API 文档
#### 修改文件
- `api_v1/urls.py`: 注册新路由
#### 业务逻辑
1. 根据 PrintingOrder ID 获取所有 PrintingJob 的 ID
2. 查询 SalesItem过滤 printing_job_id 在这些 job_ids 中
3. 根据参数决定是否过滤已关联出货单的销售品
---
### 2. Printing 模块添加 merchant 字段
为多租户支持,为 printing 模块的核心模型添加 `merchant` 外键字段:
- `PlateOrder.merchant` - 开版订单所属商户
- `PrintingOrder.merchant` - 印染订单所属商户
- `PrintingJob.merchant` - 印染任务所属商户
所有字段设置为可空(`null=True, blank=True`),以兼容现有数据。
**迁移文件**: `printing/migrations/0029_add_merchant_to_models.py`
---
### 3. 确保 API 创建时自动绑定 merchant
修复了创建 API确保新建记录时自动从当前用户获取 merchant
- `PrintingOrderService.create_printing_order()` - 自动绑定 merchant
- `PrintingJobService.create_printing_job()` - 自动绑定 merchant
- `PlateOrderViewSet.perform_create()` - 自动绑定 merchant
---
### 4. 数据补录命令
创建了 management command 用于补录历史数据的 merchant_id
```bash
# 预览(不执行)
python manage.py backfill_merchant <merchant_id> --dry-run
# 执行补录
python manage.py backfill_merchant <merchant_id>
```
**文件**: `printing/management/commands/backfill_merchant.py`
---
## 待办
---
## 备注
- API 独立于 printing 模块,避免影响现有功能
- 完整的测试覆盖正常查询、包含已出货、数据格式、404 错误、空结果、未认证
- printing 和 shipment 模块全部测试通过31个测试用例

157
docs/shipment_api.md Normal file
View File

@@ -0,0 +1,157 @@
# Shipment API 文档
出货管理模块 API 文档,包含出货单和销售品相关接口。
## 目录
- [通过生产订单查询销售品](#通过生产订单查询销售品)
---
## 通过生产订单查询销售品
查询与指定生产订单PrintingOrder关联的所有销售品SalesItem
### 接口信息
- **URL**: `/api/v1/shipment/sales-items/by-printing-order/<printing_order_id>/`
- **Method**: `GET`
- **认证**: 需要登录JWT Token
### 路径参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| printing_order_id | int | 是 | 生产订单ID |
### 查询参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| include_already_has_shipment | bool | 否 | false | 是否包含已关联出货单的销售品 |
### 业务逻辑
1. 根据 `printing_order_id` 获取该生产订单下所有 `PrintingJob` 的 ID
2. 查询 `SalesItem`,过滤 `printing_job_id` 在这些 job ID 中的记录
3. 根据 `include_already_has_shipment` 参数决定是否过滤已关联出货单的销售品:
- `false`(默认):只返回 `shipment` 为空的销售品(待出货)
- `true`:返回所有销售品(包含已出货的)
### 响应格式
```json
{
"count": 2,
"results": [
{
"id": 1,
"name": "产品A - 红色",
"quantity": "100.00",
"unit": 1,
"unit_display": "米",
"position": "A1-01",
"remark": "加急处理",
"printing_job_id": 123,
"customer_id": null,
"shipment_id": null,
"shipment_date": null,
"created_at": "2026-01-14T10:00:00Z",
"created_by_id": 1,
"created_by_name": "张三"
},
{
"id": 2,
"name": "产品B - 蓝色",
"quantity": "50.50",
"unit": 1,
"unit_display": "米",
"position": "",
"remark": "",
"printing_job_id": 124,
"customer_id": 10,
"shipment_id": 5,
"shipment_date": "2026-01-13",
"created_at": "2026-01-13T15:30:00Z",
"created_by_id": 2,
"created_by_name": "李四"
}
]
}
```
### 响应字段说明
| 字段 | 类型 | 说明 |
|------|------|------|
| count | int | 结果总数 |
| results | array | 销售品列表 |
| results[].id | int | 销售品ID |
| results[].name | string | 销售品名称 |
| results[].quantity | string | 数量Decimal保留2位小数 |
| results[].unit | int | 单位编码1=米, 2=件, 3=码, 4=个) |
| results[].unit_display | string | 单位显示名称 |
| results[].position | string | 货位(可能为空) |
| results[].remark | string | 备注(可能为空) |
| results[].printing_job_id | int/null | 关联的生产任务ID |
| results[].customer_id | int/null | 销售品级别的客户ID |
| results[].shipment_id | int/null | 关联的出货单IDnull 表示未出货 |
| results[].shipment_date | string/null | 出货日期YYYY-MM-DDnull 表示未出货 |
| results[].created_at | string | 创建时间ISO 8601 |
| results[].created_by_id | int/null | 创建人ID |
| results[].created_by_name | string/null | 创建人名称 |
### 错误响应
#### 404 Not Found - 生产订单不存在
```json
{
"detail": "生产订单 999 不存在"
}
```
#### 401 Unauthorized - 未登录
```json
{
"detail": "Authentication credentials were not provided."
}
```
### 使用示例
#### 查询待出货的销售品(默认)
```bash
curl -X GET \
'https://api.example.com/api/v1/shipment/sales-items/by-printing-order/123/' \
-H 'Authorization: Bearer <token>'
```
#### 查询所有销售品(包含已出货)
```bash
curl -X GET \
'https://api.example.com/api/v1/shipment/sales-items/by-printing-order/123/?include_already_has_shipment=true' \
-H 'Authorization: Bearer <token>'
```
---
## 单位编码对照表
| 编码 | 名称 |
|------|------|
| 1 | 米 |
| 2 | 件 |
| 3 | 码 |
| 4 | 个 |
---
## 相关模块
- `shipment/services.py`: 业务逻辑层
- `api_v1/views/shipment/`: API 视图层
- `shipment/models.py`: 数据模型SalesItem, Shipment