# 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. 生产约束:索引变更需使用并发建索引方式并在低峰执行