forked from erp-dev/erp
187 lines
4.7 KiB
Markdown
187 lines
4.7 KiB
Markdown
# Settlement Service 设计文档
|
||
|
||
## 概述
|
||
本文档描述 settlement 模块的 service 层设计,遵循职责分离原则,每个函数职责单一、可测试。
|
||
|
||
## 函数列表
|
||
|
||
### 1. `get_plate_order_summary_by_customer`
|
||
|
||
获取按客户分组的开版订单统计。
|
||
|
||
**参数**:
|
||
- `merchant_id: int` - 商户ID
|
||
- `settlement_date: datetime.date` - 统计日期
|
||
- `user: User | None` - 当前用户(用于客户可见性过滤)
|
||
|
||
**返回**:
|
||
- `list[dict]` - 客户统计列表
|
||
|
||
**职责**:
|
||
- 参数验证
|
||
- 调用底层数据查询函数
|
||
- 应用客户可见性过滤
|
||
- 数据格式化
|
||
|
||
**参数校验规则**:
|
||
- `merchant_id` 必须是正整数
|
||
- `settlement_date` 必须是 `date` 或 `datetime` 类型(`datetime` 会自动转换为 `date`)
|
||
- 参数非法时抛出 `ValueError`
|
||
|
||
---
|
||
|
||
### 2. `_get_plate_order_queryset`
|
||
|
||
获取开版订单的基础查询集。
|
||
|
||
**参数**:
|
||
- `merchant_id: int` - 商户ID
|
||
- `user: User | None` - 当前用户(用于客户可见性过滤)
|
||
|
||
**返回**:
|
||
- `QuerySet[PlateOrder]` - 过滤后的查询集
|
||
|
||
**职责**:
|
||
- 应用商户隔离
|
||
- 应用客户可见性过滤
|
||
- 过滤 `plate_type` 不为空的订单
|
||
- 过滤 `production_method` 不为空的订单
|
||
- 优化查询(select_related)
|
||
|
||
---
|
||
|
||
### 3. `_get_month_date_range`
|
||
|
||
获取从月初到指定日期的日期范围。
|
||
|
||
**参数**:
|
||
- `settlement_date: datetime.date` - 统计日期
|
||
|
||
**返回**:
|
||
- `tuple[date, date]` - (月初日期, 统计日期)
|
||
|
||
**职责**:
|
||
- 计算当月第一天
|
||
- 返回日期范围元组
|
||
|
||
---
|
||
|
||
### 4. `_aggregate_plate_orders_by_customer_and_type`
|
||
|
||
按客户和类型分组聚合订单数据。
|
||
|
||
**参数**:
|
||
- `queryset: QuerySet[PlateOrder]` - 基础查询集
|
||
- `settlement_date: datetime.date` - 统计日期
|
||
|
||
**返回**:
|
||
- `QuerySet[PlateOrder]` - 添加了聚合标注的查询集
|
||
|
||
**职责**:
|
||
- 按 customer_id、customer_name、plate_type、production_method 分组
|
||
- 计算今日数量(条件聚合)
|
||
- 计算本月累计数量(条件聚合)
|
||
- 生成 type 字段(plate_type + '-' + production_method)
|
||
|
||
---
|
||
|
||
### 5. `_format_plate_order_summary`
|
||
|
||
格式化聚合结果为 API 返回格式。
|
||
|
||
**参数**:
|
||
- `aggregated_data: QuerySet[PlateOrder]` - 聚合后的查询集
|
||
|
||
**返回**:
|
||
- `list[dict]` - 格式化后的数据
|
||
|
||
**职责**:
|
||
- 遍历聚合结果
|
||
- 按客户分组
|
||
- 返回客户ID(`client_id`)和客户名称(`client_name`)
|
||
- 过滤全0数据
|
||
- 生成最终的 API 返回格式
|
||
|
||
---
|
||
|
||
### 6. `_filter_zero_data`
|
||
|
||
过滤全0数据。
|
||
|
||
**参数**:
|
||
- `plate_order_counts: list[dict]` - 订单统计列表
|
||
|
||
**返回**:
|
||
- `list[dict]` - 过滤后的列表
|
||
|
||
**职责**:
|
||
- 移除 today 和 current_month 都为 0 的数据
|
||
|
||
---
|
||
|
||
## 数据流程
|
||
|
||
```
|
||
get_plate_order_summary_by_customer
|
||
↓
|
||
_get_plate_order_queryset (获取基础查询集)
|
||
↓
|
||
_aggregate_plate_orders_by_customer_and_type (分组聚合)
|
||
↓
|
||
_get_month_date_range (获取日期范围)
|
||
↓
|
||
_format_plate_order_summary (格式化结果)
|
||
↓
|
||
_filter_zero_data (过滤全0数据)
|
||
```
|
||
|
||
## 过滤规则
|
||
|
||
1. **商户隔离**: 只查询指定商户的订单
|
||
2. **plate_type 过滤**: `plate_type` 为空的订单不纳入统计
|
||
3. **production_method 过滤**: `production_method` 为空的订单不纳入统计
|
||
4. **客户可见性**: 非超级用户只能看到自己创建的客户或被授权可见的客户
|
||
5. **全0数据过滤**: today 和 current_month 都为 0 的类型组合不返回
|
||
6. **客户分组稳健性**: 以 customer_id 分组,避免同名客户被合并
|
||
|
||
## type 组合规则
|
||
|
||
**格式**: `{plate_type}-{production_method}`
|
||
|
||
**plate_type 可能的值**:
|
||
- `首版`
|
||
- `修改`
|
||
|
||
**production_method 可能的值**:
|
||
- `定位`
|
||
- `匹布`
|
||
|
||
**组合示例**:
|
||
- `首版-定位`
|
||
- `首版-匹布`
|
||
- `修改-定位`
|
||
- `修改-匹布`
|
||
|
||
## 测试策略
|
||
|
||
每个函数都应该有独立的单元测试:
|
||
- `_get_plate_order_queryset`: 测试商户隔离、客户可见性过滤、plate_type 和 production_method 过滤
|
||
- `_get_month_date_range`: 测试日期范围计算
|
||
- `_aggregate_plate_orders_by_customer_and_type`: 测试分组聚合逻辑
|
||
- `_format_plate_order_summary`: 测试数据格式化
|
||
- `_filter_zero_data`: 测试全0数据过滤
|
||
- `get_plate_order_summary_by_customer`: 集成测试
|
||
|
||
## 性能优化
|
||
|
||
1. 使用 `select_related` 减少查询次数
|
||
2. 使用 `annotate` 和聚合函数避免 N+1 查询
|
||
3. 使用条件聚合(Case/When)一次查询获取 today 和 current_month
|
||
|
||
## PostgreSQL 优化评估记录
|
||
|
||
1. 已评估数据库层优化(索引、视图、物化视图)
|
||
2. 当前决策:暂不实施结构性优化,优先保持线上稳定性
|
||
3. 后续若优化,优先级为:索引 > 表达式索引 > 物化视图
|
||
4. 生产约束:索引变更需使用并发建索引方式并在低峰执行
|