功能测试报告_汇总表「公式扩页 + 生成件重算」_2026-09-18
一、功能说明
修复《道路运输量汇总表》生成件的三条主线问题(用户 2026-09-18 提出):
| 主线 |
现象 |
根因 |
本次处置 |
| 1 |
城市客运 / 中口径明细 8 月整列为空、累计=1~7 月 |
SummaryWorkbookFiller.ensureMonthFormulas() 的**页范围**只含 6 个台账页,漏了这两页(同比公式用的页范围却是含的,所以出现"同比列有公式、当月值列空白") |
页范围改用 isMonthLedgerSheet(含 城市客运 / 中口径明细 / 公路总客运 / 中口径客运量) |
| 2 |
公交/出租/网约车/城市分市州的**全省行**停在上一期 |
全省行是 =C9+C13+…(Σ17市州)公式,但生成件**不带缓存值**,预览/二次处理只读到母版上一期的旧值 |
生成收尾新增 recalcWorkbookFormulas(),逐格 evaluateFormulaCell 把结果写回缓存值;失败兜底 fullCalcOnLoad=true |
| 3(部分) |
中口径排名数据不对 |
K3/O3 是 SUM(K4:K20) 公式(同样无缓存值);M3/Q3 同比是写死的库内全省口径 |
本次只解决"SUM 无缓存值";写死同比口径 + loadMidClassMap 缺两维**留待下一步**(改动会动口径,需先确认) |
改动文件:
- traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/SummaryWorkbookFiller.java
- ensureMonthFormulas() 页范围修正;新增派生页结构性常量行按月沿用;新增 isDerivedFormulaSheet() / structuralConstant();移除已无用的 isFilledLedgerSheet();加 logger。
- traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
- 新增 recalcWorkbookFormulas(XSSFWorkbook),并在 exportSummaryWorkbook() 写文件前调用。
二、测试环境
- 后端 Spring Boot(8090)、前端 Vue(8080),
start-dev.ps1 启动;MySQL 127.0.0.1:3308 / traffic_audit。
- 母版:
docs/生成汇总大表/2026年8月道路运输量汇总表.xlsx(同期备份母版 _备份_2026年8月… 不存在,日志 WARN,属已知背景)。
- 报表期:
2026-08;入口:GET /api/report/export/summaryWorkbook?period=2026-08&mode=month。
- 产物:
_tmp/_qa_out/gen_2026-08_after.xlsx(1,329,471 B,生成耗时 12.6 s)。
- 证据:
_tmp/_qa_out/{verify2.txt, verify3.txt, verify5.txt, diff_old_new.txt, after_cols.txt, xml_check.txt, v_cmp.txt}。
- 后端日志:
汇总工作簿公式重算完成:成功 40713 格,失败 0 格、派生页结构性常量行按月沿用 4 格(第 8 月)。
三、用例表
正向用例
| # |
输入 |
预期 |
实际 |
结论 |
| TC1 |
生成 2026-08 汇总表,检查「城市客运」Q 列(8 月当月值) |
每行应有当月值公式(跨表引用右移) |
Q4~Q39 共 36 格全部有公式,如 Q9 = =公交!Q16+出租!Q16+网约车!Q16+'轨道、轮渡'!Q9 |
通过 |
| TC2 |
同上,检查「中口径明细」Q 列 |
180 行有当月值(公式或结构性常量) |
180 格有值(176 公式 + 4 常量) |
通过 |
| TC3 |
抽查「中口径明细」结构性常量行(武汉 城际城乡巡游出租/网约车 = 0) |
按月沿用 0,不留空 |
Q13=0.0、Q14=0.0、Q107=0.0、Q108=0.0,日志记「沿用 4 格」 |
通过 |
| TC4 |
核对全省行 = Σ17 市州(独立求和 vs 缓存值) |
两者相等 |
公交 12315.37 = 12315.37;出租 5786.81 = 5786.81;网约车 4997.74 = 4997.74 |
通过 |
| TC5 |
检查 8 月累计列(S) |
应含 8 月(=C+E+…+Q) |
公交 S5=110776.34、出租 S5=48069.02、网约车 S5=33864.94、中口径明细 S5=31528.06(改动前分别停在 1~7 月) |
通过 |
| TC6 |
检查「中口径排名」全省行 |
=SUM(K4:K20) 应有缓存值 = Σ17市州 |
K3 缓存 24481.7407(与手工 Σ17 市州一致)、O3 缓存 1131273.449 |
通过 |
| TC7 |
检查公式是否被破坏(重算是否误用 evaluateInCell) |
公式与缓存值并存(<f> + <v>) |
XML 全部为 <c ...><f>…</f><v>…</v></c>,公式保留 |
通过 |
| TC8 |
检查「城市汇总」「中口径分析」等跨页页 |
随报表期刷新,不受本次改动影响 |
城市汇总 总客运量 269628.07(1-8月),母版为 237412.33(1-7月) |
通过 |
负向 / 边界用例
| # |
输入 |
预期 |
实际 |
结论 |
| TC9 |
母版同期备份(_备份_2026年8月…)缺失 |
不中断生成,记 WARN |
日志 汇总工作簿未找到同期备份母版…,正常出件 |
通过(已知背景) |
| TC10 |
公式重算遇到不支持函数/外部引用 |
跳过该格并计数,不中断导出 |
本次失败 0 格;代码保留逐格 try-catch + 失败样例日志 |
通过(未触发) |
| TC11 |
派生页「全月同常量」判定被误用于台账页 |
台账页不得沿用上月数值(防"结转") |
structuralConstant() 仅在 isDerivedFormulaSheet() 为真时生效(6 个台账页不适用) |
通过(逻辑隔离) |
| TC12 |
重算误伤数值格(非公式格) |
只处理 CellType.FORMULA 的格 |
遍历时跳过所有非公式格 |
通过 |
入库抽查(库 → 汇总表逐项对齐)
| 对象 |
库(city_bus_monthly 2026-08) |
生成件 |
结论 |
| 武汉市 客运量 |
3856.83 |
公交页 Q9 = 3856.83 |
一致 |
| 黄石市 客运量 |
879.12 |
公交页 Q13 = 879.12 |
一致 |
| 17 市州合计 |
12315.37 |
公交页 Q5(全省行缓存)= 12315.37 |
一致 |
| 武汉市 城市内客运量 |
3837.20 |
公交页 Q11 = 3837.20(城市客运页 Q6 引用它) |
一致 |
四、回归比对(改动前 vs 改动后)
逐格对比改动前后两次生成件(diff_old_new.txt):
| 页 |
差异格数 |
| 城市客运 |
36 |
| 中口径明细 |
180 |
| 其余 17 页(含货运、公交、出租、网约车、班线包车、排名页、跨页合成页) |
0 |
| 合计 |
216 |
差异全部落在本次目标页,且都是「空 → 公式」;其它页逐格一致,无回归。
五、发现的问题与处置
FormulaEvaluator.evaluate(Cell) 无副作用(POI 4.1.0):首次实现用了 evaluate(),日志显示"成功 40713 格"但文件里没有缓存值(<f> 无 <v>)。改用 evaluateFormulaCell(Cell) 后写入正常。已把结论写进代码注释。
- 中口径排名同比仍为写死值:SUMMARY 里
M3=0.7424 / Q3=0.2317 仍是母版/库内全省口径,与 Σ17 市州(24481.74)口径不一致 —— 需按第 3 步统一(涉及口径判定,未在本次改动)。
- 独立《生成_公路旅客中口径明细》口径缺口:
loadMidClassMap() 只装"公路班线 + 城际城乡公交"两维,缺巡游出租、网约车 —— 未改,留待第 3 步。
- 库内 h2031「全省」行 ≠ Σ17 市州(1-8 月 27524.49 vs 24481.74,差 3042.75):属业务口径问题,建议业务侧核对,不阻塞。
六、结论
- 用户报的"8 月没数据 / 全省行停在上一期"两条主线**已修复并经回归验证**:城市客运、中口径明细 8 月列补齐;全省行、累计列、排名 SUM 均带正确缓存值。
- 改动面可控(216 格,全部命中目标页),无回归。
- 第 3 步(中口径排名同比口径统一 +
loadMidClassMap 补两维)待用户确认后实施。