docs: 2026-10-19 业务答复与需求留档——T+ 清单 v1.4、排序表、催办清单与 M4 计划
- T+ 对接数据需求清单 v1.4 与业务方答复回执 - 待确认事项排序表两版、待答复催办清单 - 剩余开发计划回填 3.3.1 答复落定状态 - 新增 M4 报表统计实施计划 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
1 parent
a536608b53
commit
c5f219423b
7 files changed
+252
No files matched your search
@@ -718,6 +718,20 @@ git commit -m "test(system): 参数体系模块用例与测试记录"
|
||||
| 6 | 调整单是否走同一审批链;能否跨月调整已锁定月份 | ④⑦ | 建议走同一审批链;「后续月份扣补」为默认路径,跨月调整需授权留痕 —— 待确认 |
|
||||
| 7 | 待办事项的范围(除审批/同步失败外还含哪些,回款未指定批次是否算待办) | ⑧ | 按 3.2(二) 默认清单起步,业务确认后扩充 |
|
||||
|
||||
### 3.3.1 答复回填(2026-10-19)
|
||||
|
||||
> 业务方 24 项答复已到(2026-10-16,回执见《待澄清问题清单_2026-10-15(1)(2).xlsx》H 列),2026-10-19 全部落实并实现;本节为 3.3 各项的落定状态,供后续引用。答复与默认不同处以答复为准;仍未答复项见 `docs/待答复催办清单_2026-10-19.md`。
|
||||
|
||||
| 3.3# | 事项 | 落定状态 |
|
||||
| --- | --- | --- |
|
||||
| 1 | 二次分配标的与余额归属(含与 #15 协助 25% 关系) | **答复仍空白;2026-10-19 已按建议默认实现分摊拆行**——分配标的=绩效金额份额(参与人份额×比例、负责人拿余额、行合计守恒、合计>100% 与负责人自分 400 拦截);协助 25% 维持仅记录不核算;业务答复后如有变更走口径调整 |
|
||||
| 2 | 二次分配审批开关默认值 | 已答复:**默认开启**,可手动关闭(已落实 V8 参数种子) |
|
||||
| 3 | 「确认回款」定义 | 已答复:需人工确认动作,可手动关闭(系统已按此预留并实现) |
|
||||
| 4 | 审批角色权限清单 | 部分答复:先预设角色,名称与职权待客户确认;现按最小授权运行(后勤=提交录入,财务=复核+导出,领导=审核只读),营销后勤暂不可见审批列表 |
|
||||
| 5 | 风险预警规则 | 已答复:方向确认参数化;首批三条(逾期 90 天/同步失败/超时 3 天)未点名,按默认起步——⑧ 工作台实现 |
|
||||
| 6 | 调整单是否走同一审批链;能否跨月调整已锁月份 | 已答复:同一审批链;跨月调整需管理员授权并留痕(已实现) |
|
||||
| 7 | 待办事项范围 | 已答复:待审批 + 同步失败 + 回款未指定批次起步——⑧ 工作台实现 |
|
||||
|
||||
### 3.4 对路线图的影响(本次已修订)
|
||||
|
||||
1.1 依赖图、1.3 范围与验收、1.4 里程碑、1.5 依据对照均已并入本批内容;第三章待 3.3 各项澄清后,按第二章同粒度补齐 M3 各增量块的任务级实施计划。
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
# M4 ⑤ 报表统计 · 任务级实施计划
|
||||
|
||||
> 立项日期 2026-10-19;分支 feat/m3-increments。依据:需规 2.4 /《需求规格说明书》FR-029~FR-032 / 开发计划第五章 1.3 验收行 / 2026-10-15 补充(四)「导出模板围绕财务复核和领导签字设计」。
|
||||
> 前置已满足:④ 台账/月结/调整单全部交付(台账即「报表 2.4.1 的数据源」,`performance_ledger.py` 注释锚点)。
|
||||
> **纯只读模块,无数据库迁移**。
|
||||
|
||||
## 〇、代码核实结论(实施时按此避坑)
|
||||
|
||||
| # | 结论 | 影响 |
|
||||
| --- | --- | --- |
|
||||
| 1 | `require_roles` 403 文案硬编码「无权访问审批管理」(`api/deps.py:135`) | 给 `require_roles` 增可选 `message` 参数(默认值不变),报表传「无权访问报表统计」 |
|
||||
| 2 | `PageQuery.page_size` 上限 200(`core/response.py:57`) | 打印视图循环翻页取全量;导出走专用无分页查询 |
|
||||
| 3 | `settlement_service._confirm_scope` 带 `settlement_id IS NULL`(可入结口径) | 报表取数**不复用**,新增同月全量生效单笔行查询 |
|
||||
| 4 | `list_adjustments` 无月份筛选、limit 200、含全部状态 | 调整单并入需新写仓储查询(月份 + status=2) |
|
||||
| 5 | `GET /system/settings` 是 AdminUser,`/public` 才无角色限制 | 打印视图页眉必须走 `getPublicSettings()` |
|
||||
| 6 | 台账 `payment_days` 可空可负(先款后货);进度登记回款无台账行(#25) | 区间桶含「≤0 天」「无天数」兜底;回款统计注明「不含进度登记回款」 |
|
||||
| 7 | 二次分摊拆行使同一回款多行(负责人+参与人,business_id 相同) | 回款金额按 receipt_id 去重;个人明细不放「关联回款金额」列 |
|
||||
| 8 | 经理聚合行 calc_type=2 的 business_id 是锚点、receipt_id=NULL、payment_days=NULL | 业务统计/个人明细/回款统计一律 `calc_type=1 AND is_active=1`,防聚合额污染 |
|
||||
| 9 | openpyxl `NUMERIC_TYPES` 含 Decimal,原样落数值不转 float | 精度红线导出侧成立;`number_format='#,##0.00'` 控显示 |
|
||||
| 10 | `test_param_registry.py` 锁 registry 19 项 | 新增参数需四处同改(本计划不新增参数,规避) |
|
||||
| 11 | `PRODUCT_CATEGORIES` 常量不含 `mixed`,台账行有 `mixed` | 标签映射额外覆盖 mixed,单测防漂移 |
|
||||
| 12 | `audit_service.audit` 可用于只读导出留痕(独立 commit) | 直接复用;上下文内 bytes 在 commit 前固化 |
|
||||
| 13 | `test_settlement_helpers.py` 直接 import `settlement_service._month_end` | 抽共享模块时保留薄委托包装 |
|
||||
| 14 | 前端无任何打印/PDF/xlsx 代码 | 打印从零建:独立顶层路由 + `@media print` |
|
||||
| 15 | Starlette 响应头 latin-1,中文文件名会 UnicodeEncodeError | 导出文件名服务端 ASCII、中文名前端 `link.download` 赋 |
|
||||
|
||||
## 一、全局口径(写进 report_service docstring)
|
||||
|
||||
1. 数据源 `performance_ledgers` 固定 `is_active=1 AND calc_type=1`;聚合行只在部门汇总作冻结对照单读;月份维度 `settlement_month`。
|
||||
2. 金额全程 `Decimal` 出参字符串;比例原样 `rate_text`,禁 float/截断。
|
||||
3. 调整单并入:仅 `status=2`,按 `(settlement_month, personnel_id)` 并入,`direction=1` 记 +、`2` 记 −;归属按实时 `manager_id`。
|
||||
4. 离职人员不按 `status` 过滤(历史业绩留在原团队);零行经理枚举仅在职经理。
|
||||
5. 汇总 = 明细合计(团队/公司基数 == 成员明细绩效合计,测试逐项断言)。
|
||||
|
||||
### 已定设计决策
|
||||
|
||||
- 部门口径 = `personnel.manager_id` 实时上溯(无独立部门表,人员模型 docstring 已声明;已入催办清单 R1 向业务确认)
|
||||
- 导出 = 后端 openpyxl + StreamingResponse(ASCII 文件名);打印 = 前端独立打印视图 + `@media print`,PDF 走浏览器另存为;不引前端 PDF/xlsx 依赖
|
||||
- 不做图表(需求无要求,不引 ECharts)
|
||||
- 区间桶 = 代码常量 + 服务端下发元数据(前端零硬编码);不进参数体系避免迁移;配置缝收在 `get_payment_day_buckets()` 单函数,升级路径:`report.payment_days.buckets` 参数 → V10 迁移种子 → init-dev → registry 测试 19→20
|
||||
- 权限 `require_roles(ROLE_ADMIN, ROLE_FINANCE, ROLE_LEADER)` 粗粒度,前后端双层(admin 显式列入)
|
||||
|
||||
## 二、后端任务
|
||||
|
||||
### T1 常量与元数据(`core/constants.py` 改 + `services/report_meta.py` 新)
|
||||
|
||||
- `MODULE_REPORT = "报表统计"`、`ACTION_EXPORT = "导出"`
|
||||
- `report_meta.py`(叶子模块,禁 app 内导入防循环依赖):`DayBucket` frozen dataclass + `PAYMENT_DAY_BUCKETS` 8 档(≤0 天(先款后货)/1-30/31-60/61-90/91-180/181-360/361+/无天数)、`CUSTOMER_TYPE_LABELS`、`PRODUCT_CATEGORY_LABELS`(含 mixed)、`REPORT_TEMPLATE_VERSION = "V1.0"`、`REPORT_EXPORT_MAX_ROWS = 20000`
|
||||
|
||||
### T2 仓储 `repositories/report_repository.py`(新)
|
||||
|
||||
| 函数 | 说明 |
|
||||
| --- | --- |
|
||||
| `list_personal_detail / count_personal_detail` | 单笔生效行分页(month/personnel/customer_type/product_category/role_code 筛选,id desc) |
|
||||
| `sum_personal_detail` | 全筛选口径 SUM(分页合计与页内合计分开,禁内存当前页求和) |
|
||||
| `list_month_receipt_rows` | 同月全量生效单笔行(部门汇总取数,不复用 _confirm_scope) |
|
||||
| `list_month_manager_aggregates` | 同月 calc_type=2 生效行(冻结对照) |
|
||||
| `list_month_adjustments` | status=2 + 月份 |
|
||||
| `list_business_stats / count / sum` | Business×Customer×Personnel 左联 + 三个 scalar_subquery(发货额 SUM(shipments.total_amount)、回款额 SUM(receipts.amount)、绩效 SUM(台账 performance_amount WHERE calc_type=1 AND is_active=1));keyword 双列 OR contains(autoescape=True) |
|
||||
| `list_payment_group_by_days` | GROUP BY (payment_days, customer_type, product_category) 出 SUM 绩效/应付/COUNT |
|
||||
| `list_payment_receipt_group_by_days` | 先 DISTINCT receipt_id 再 JOIN receipts 求和(拆行防翻倍) |
|
||||
| `list_payment_detail / count` | 区间下钻分页(闭区间 between、开区间 >=、none 桶 IS NULL),JOIN receipts 取金额与批次号 |
|
||||
|
||||
### T3 经理分成共享口径(`services/manager_share.py` 新 + `settlement_service.py` 改薄委托)
|
||||
|
||||
- 迁出(复制实现 + 薄委托,行为零变化):`month_end`、`ManagerShareParams`(含 `manager_share_params(session, month)`,锚定月末 resolve_snapshot)、`collect_team_buckets`、`company_base_of`
|
||||
- 报表侧分成额调用 `formula.manager_team_share(...)`——月结聚合行与报表汇总同源,杜绝口径漂移
|
||||
- **协调点**:84 会话已拆 settlement_service(028a94a);本任务在其基础上做,验证 `test_settlement_helpers.py` 全绿
|
||||
|
||||
### T4 服务与 schemas(`services/report_service.py` + `schemas/report.py` 新)
|
||||
|
||||
1. **个人明细**:`ReportPage[T]` = 标准分页四字段 + `summary`(全筛选合计);行含 ledger_id/business_id/business_no/人员/岗位/客户类型/产品类别/ship_date/receipt_date/payment_days/rate_text/weight_r/payment_weight/performance_amount/payable_amount;**不含回款金额列**
|
||||
2. **部门汇总**(月份必填):`collect_team_buckets` 分团队 → 团队基数/成员内联明细(personnel_id 聚合 + 台账 id 列表 + 调整 extra/deduct/net);`company` 块 5% 单列(team_base_total/company_base/company_share_amount;各团队 5% 之和 ≠ 全局 5%,UI 注明);零行经理并集枚举(在职 sales_manager JOIN business_roles + 数据中出现的 manager_id);对比 `company_base_ratio` 除零→0;冻结对照 `aggregate_ledger_id + frozen_amount`;出参 `params`(team_pct/company_pct/manager_personal_included/params_as_of)
|
||||
3. **业务统计**:分页 + summary;回款/绩效为该业务累计值(不随日期筛选变,页面 hint)
|
||||
4. **回款统计**:SQL 精确值聚合 → Python 按 `PAYMENT_DAY_BUCKETS` 分桶(恒含全桶、空桶 0);正负绩效直接求和不 ABS;出参 buckets/by_customer_type/by_product_category/totals
|
||||
5. **区间下钻**:`GET /reports/payment-detail`(bucket 必填,分页)
|
||||
6. **导出编排** `export_report(session, code, q, *, operator, meta)`:先 count 判上限(超限 400 中文提示按月缩小);`audit_service.audit(MODULE_REPORT, ACTION_EXPORT, ...)` 包取数+构建;出参 `(bytes, filename)` 在 commit 语义闭合前固化
|
||||
|
||||
### T5 Excel 模板(`services/report_export.py` 新,纯函数)
|
||||
|
||||
- 普通 `Workbook`(上限封顶 2 万行内存可承受);版式:公司页眉行(logo `read_logo` try/except 降级纯文本)→ 标题(合并居中)→ 筛选条件行 → 导出时间/人/模板版本 → 表头(加粗底纹边框 + freeze_panes)→ 数据行 → 合计行(service 侧 Decimal SUM quantize HALF_UP,**不写 Excel 公式**)→ 空两行 → 财务复核栏 / 领导签字栏 / 日期栏
|
||||
- 精度:金额 Decimal 直接写 + `#,##0.00`;比例 `'0.00"%"'`;`rate_text` 原样文本;日期 `date` + `YYYY-MM-DD`
|
||||
- 防公式注入:文本以 `= + - @ \t` 开头前置 `'`;CJK 列宽;部门汇总/回款统计多 sheet(各带页眉与签字栏);空数据允许(表头 + 提示行 + 签字栏)
|
||||
|
||||
### T6 API 与路由(`api/v1/reports.py` 新 + `router.py` 改 + `deps.py` 改)
|
||||
|
||||
| 方法 | 路径 | 入参(Query 蛇形) |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/v1/reports/personal-detail` | pageNum/pageSize + settlement_month, personnel_id, customer_type, product_category, role_code |
|
||||
| GET | `/api/v1/reports/department-summary` | settlement_month(必填), manager_id |
|
||||
| GET | `/api/v1/reports/business-stats` | pageNum/pageSize + keyword, customer_id, owner_personnel_id, status, date_from, date_to |
|
||||
| GET | `/api/v1/reports/payment-stats` | settlement_month, date_from, date_to, customer_type, product_category |
|
||||
| GET | `/api/v1/reports/payment-detail` | pageNum/pageSize + bucket(必填) + 同上 |
|
||||
| GET | `/api/v1/reports/{code}/export` | code ∈ 四表,参数同对应页 → StreamingResponse xlsx |
|
||||
|
||||
- 全部 `def` 同步路由 + `Annotated[User, Depends(require_roles(ROLE_ADMIN, ROLE_FINANCE, ROLE_LEADER, message="无权访问报表统计"))]`
|
||||
- Content-Disposition 文件名 ASCII(`personal_detail_20261019.xlsx`),沿用 `system/params.py` 导出先例
|
||||
|
||||
### T7 后端文档(`backend/README.md` 改)
|
||||
|
||||
报表接口表 + 「报表口径」小节(生效行/调整单并入/回款去重/不含进度登记回款/区间桶常量与升级路径)。
|
||||
|
||||
## 三、前端任务
|
||||
|
||||
### T8 API 与常量(`api/reports.ts` 新 + `utils/constants.ts` 改)
|
||||
|
||||
类型逐字段对齐(金额 string);`exportReport(code, params)` 裸 axios `responseType:'blob'`;MODULE_OPTIONS 与后端 `core/constants.py` 全量对齐
|
||||
|
||||
### T9 四个页面(`views/reports/{personal-detail,department-summary,business-stats,payment-stats}/index.vue` 新)
|
||||
|
||||
统一骨架:`filters(reactive)` + `usePagedList`(非分页表 ref+手动 load)+ `el-table stripe` + `AppPagination` + 工具条「导出 Excel」「打印」+ `handledError`;日期区间 `el-date-picker value-format="YYYY-MM-DD"`(必写);下拉复用 `useDropdownOptions`
|
||||
|
||||
| 页面 | 路由 order | 关键列与下钻 |
|
||||
| --- | --- | --- |
|
||||
| 个人绩效明细表 | `/reports/personal-detail` 19 | 需规 10 列 + 页脚全筛选合计(注明非当前页);expand 懒加载 `getLedger(ledger_id)` 快照下钻 |
|
||||
| 部门绩效汇总表 | `/reports/department-summary` 20 | 团队表 + 全公司 5% 卡片 + 参数取值提示;展开成员 → 跨页下钻个人明细(router.push query)→ 台账下钻复用 `listLedger` |
|
||||
| 业务统计表 | `/reports/business-stats` 21 | 发货额/回款额/绩效三列;expand 两小表(复用 `listReceipts`/`listLedger`);hint「累计值」 |
|
||||
| 回款统计表 | `/reports/payment-stats` 22 | buckets 元数据渲染(空桶 0)+ 桶展开下钻 `payment-detail` + 客户类型/产品类别对比小表 |
|
||||
|
||||
路由 meta:`{ title, group: '报表统计', order: 19..22, allowedRoles: [ROLE_ADMIN, 'finance', 'leader'] }`(admin 显式列入)
|
||||
|
||||
### T10 打印视图(`views/reports/print/index.vue` 新 + `router/index.ts` 改)
|
||||
|
||||
- 顶层路由 `/reports/print/:code`(不挂 BasicLayout),`menu:false` + allowedRoles 同上
|
||||
- 循环翻页取全量(pageSize 200、上限 2000 截断提示);页眉 `getPublicSettings()`(非管理员拿不到 `/system/settings`)
|
||||
- `window.print()`(按钮区 `@media print { display:none }`);`@page A4 landscape`;`thead { display: table-header-group }` 每页带表头;打印态白底黑字沿用 `--ys-*`
|
||||
|
||||
### T11 前端文档(`frontend/README.md` 改)
|
||||
|
||||
报表页与打印视图小节 + 权限矩阵
|
||||
|
||||
## 四、测试(模块整体完成时统一编写)
|
||||
|
||||
### T12 后端(`tests/unit/test_report_helpers.py` + `tests/integration/test_reports.py` 新)
|
||||
|
||||
- 单元:分桶边界(0/1/30/31/60/61/180/181/360/361/负值/NULL);标签键集合 ⊇ CUSTOMER_TYPES ∪ PRODUCT_CATEGORIES ∪ {"mixed"};Excel 结构断言(load_workbook 读回:页眉/合计/签字栏/number_format/注入前缀/空数据);占比除零→0
|
||||
- 集成(factories 造数):① 个人明细对拍 `/calc/ledger`、summary==全量和;② 部门汇总成员合计==团队基数==公司基数、`aggregate_amount==manager_team_share` 预期、生成月结后 computed==frozen;③ 无团队成员经理 0 行、离职成员计入原团队;④ 调整单生效行并入(status≠2 不计);⑤ 回款统计空桶恒在、负绩效计入、**分摊拆行金额只计一次**;⑥ 业务统计排除 calc_type=2;⑦ 导出 200/400/审计留痕(成功与失败);⑧ 普通 user 403(文案「无权访问报表统计」)
|
||||
|
||||
### T13 e2e(`frontend/e2e/report_flow.py` 新)
|
||||
|
||||
菜单可见 → 四页筛选与数字 → `page.expect_download()` 断言导出 → 打印视图注入 `window.print` 桩断言 → console 无错;造数用 API + 登录 token(远未来月份隔离,参照 calc_settlement_flow.py)
|
||||
|
||||
### T14 收尾
|
||||
|
||||
回归(pytest 全量 + ruff + type-check + build + e2e report_flow 与复跑两条既有脚本);三 README 更新;提交拆分 `feat(report): 报表统计后端取数与导出` → `feat(report): 报表统计前端四页与打印视图` → `test(report): 报表模块用例与测试记录`
|
||||
|
||||
## 五、开放项(不阻塞,随催办确认)
|
||||
|
||||
| 事项 | 默认 |
|
||||
| --- | --- |
|
||||
| 部门口径(R1) | manager_id 实时上溯 |
|
||||
| 区间档位(R2) | 8 档代码常量 |
|
||||
| 签字栏要素(R3) | 页眉/标题/筛选/时间人/版本/复核/签字/日期 |
|
||||
| 导出上限 | 20000 行超限 400 |
|
||||
| 打印上限 | 2000 行截断提示 |
|
||||
| Excel logo | 有则嵌、无则纯文本 |
|
||||
Reference in new issue
Block a user