补齐 REV004 字典绑定矩阵与字典种子脚本 #5

Open
tangweijie wants to merge 26 commits from feat/rev004-dict-alignment-docs into main
230 changed files with 79928 additions and 2085 deletions

1
.gitignore vendored
View File

@ -42,3 +42,4 @@ build/
.env*
temp_mermaid_*
.codex
.omx/

View File

@ -36,6 +36,55 @@
- frontend lane 只改 `water-frontend`
- verify lane 负责样本、日志、验收结论和基线固定
## specs/ 与 docs/design/ 生命周期
本仓库维护两套文档体系,有明确的「一次性 ↔ 持续维护」分工。
### specs/ — 过程工件(建设期蓝图,建成后封存)
`specs/<编号>-<feature>/` 是 feature 建设阶段的工作台,每个目录包含:
- `spec.md`:功能规格(一次性,建成后不维护)
- `plan.md`:实施计划(一次性)
- `tasks.md`:任务拆分(一次性)
- `research.md`:调研记录(一次性)
- `verification.md`:验收记录(建成后封存)
- `data-model.md`:建设期数据模型草稿。建成后正式维护口已移交 `docs/design/03_Technical_Design/01_Database_Design.md`
- `contracts/`:建设期接口契约草稿。建成后正式维护口已移交 `docs/design/03_Technical_Design/03_Interface_Design.md`
**规则**
- `spec.md` / `plan.md` / `tasks.md` / `research.md` / `verification.md` 在 feature 建设期间活跃编辑,建成后封存不再修改。
- `data-model.md``contracts/` 是临时草稿feature 建成后数据模型和接口的唯一真源是 `docs/design/` 下的主文档。
- 禁止在两个体系里并行维护同一份数据模型或接口定义。
- 禁止创建同一个 feature 的多个 spec 目录(如 `003``006` 都是 REV-006发现重复必须合并。
### docs/design/ — 正式交付文档(唯一真源,持续维护)
`docs/design/` 是正式交付的「建成物」描述,所有模块的最终定义收敛在此:
- `02_Detailed_Design/12_REV_Detailed.md`REV 模块的功能描述唯一真源
- `03_Technical_Design/01_Database_Design.md`:表结构的唯一真源
- `03_Technical_Design/03_Interface_Design.md`:接口定义的唯一真源
**规则**feature 建成后,所有后续维护、修订、查阅原则上以 `docs/design/` 为准,`specs/` 只作为历史追溯参考。
### docs/evidence/ — 验证证据(按模块组织)
证据文件按模块分子目录存放:
- `docs/evidence/rev004-accounting/`
- `docs/evidence/rev005-invoice/`
- `docs/evidence/rev006-reminder/`
- `docs/evidence/rev007-statistics/`
禁止在 `docs/evidence/` 根目录平铺散文件。新证据必须归入对应模块子目录。
### docs/guides/ — 系统级指南
`docs/guides/` 只保留系统级指南(如 Speckit 工作流、系统能力地图、后端现状等)。模块专项操作指南归入 `docs/evidence/<模块>/`
---
本文件用于指导通用代码代理(包括 Codex 类代理)在本仓库中的工作方式。
## 项目定位
@ -55,18 +104,19 @@
```text
/
├── docs/design/00_Management/ # 项目管理、进度跟踪、交付规范、编写指南
├── docs/design/01_Overview/ # 总体设计:系统概述、系统架构、概要设计、系统图谱
├── docs/design/02_Detailed_Design/ # 详细设计主详设、模块设计、CA 安装设计
├── docs/design/03_Technical_Design/ # 技术专项:数据库、表结构、接口、安全、部署、加密
├── docs/design/04_Appendix/ # 附录与归档资料
├── .claude/ # Claude Code 相关配置
├── .omc/ # 项目记忆与代理状态
├── .zed/ # Zed 项目配置
├── assets/ # 图片、模板等静态资源
├── docs/ # 研究资料、映射文档、使用指南
├── scripts/ # 文档处理与导出脚本
└── infra/ # 辅助基础设施
├── specs/ # 过程工件feature 建设期蓝图,建成后封存
├── docs/design/00_Management/ # 项目管理、进度跟踪、交付规范、编写指南
├── docs/design/01_Overview/ # 总体设计:系统概述、系统架构、概要设计、系统图谱
├── docs/design/02_Detailed_Design/ # 详细设计:主详设、模块设计(功能描述唯一真源)
├── docs/design/03_Technical_Design/ # 技术专项:数据库、接口、安全、部署(表结构/接口定义唯一真源)
├── docs/design/04_Appendix/ # 附录与归档资料
├── docs/evidence/ # 验证证据(按模块分子目录)
├── docs/guides/ # 系统级指南
├── .claude/ # Claude Code 相关配置
├── .codex/ # Codex 相关配置
├── assets/ # 图片、模板等静态资源
├── scripts/ # 文档处理与导出脚本
└── infra/ # 辅助基础设施
```
## 当前文档组织原则
@ -82,7 +132,7 @@
- `docs/design/03_Technical_Design/04_Security_Design.md`:安全设计主文档
- `docs/design/03_Technical_Design/05_Deployment_Design.md`:部署设计主文档
如果已有主文档可以承载内容,**优先编辑现有文件,而不是新增“新-xxx”“最终版”“修订版”之类文件**。
如果已有主文档可以承载内容,**优先编辑现有文件,而不是新增"新-xxx""最终版""修订版"之类文件**。
### Archive 仅作归档
@ -124,7 +174,7 @@
- 风格要求专业、克制、可交付,避免口语化和宣传化表述
- 不写与当前项目无关的泛化模板内容
### 以“对齐现状”为第一原则
### 以"对齐现状"为第一原则
所有修改必须优先对齐以下事实来源:
@ -146,7 +196,7 @@
### 不要过度发挥
- 不要凭空补充不存在的子系统、模块、接口、表
- 不要为了“显得完整”加入大量无依据的实现细节
- 不要为了"显得完整"加入大量无依据的实现细节
- 不要默认加入大段代码示例、部署脚本、DDL、伪代码除非用户明确要求
- 不要创建多余术语体系;优先沿用仓库现有命名
@ -168,7 +218,7 @@
**福建水务营收系统**
除引用原始资料外,不要混用“营业收费系统”“数智营收管理系统”“客户服务平台”等作为正式系统名称。
除引用原始资料外,不要混用"营业收费系统""数智营收管理系统""客户服务平台"等作为正式系统名称。
### 数据库口径
@ -186,7 +236,7 @@
### 图表规范
- 所有正式图表优先使用 Mermaid
- 图表必须与正文一致,不能“图一套、文一套”
- 图表必须与正文一致,不能"图一套、文一套"
- Mermaid 节点命名尽量避免容易导致解析异常的写法
- 调整图表时,同时检查导出可用性和可读性
@ -302,7 +352,7 @@ npm run marksman:server
- 擅自扩展需求的产品经理
- 无依据发明实现细节的方案生成器
- 动辄新建文件的“版本制造机”
- 动辄新建文件的"版本制造机"
## 最终目标

View File

@ -118,6 +118,15 @@
| 2026-03-26 | 方案二整体部署方案独立成文 | 1新增 `docs/design/03_Technical_Design/08_Integrated_Deployment_Design_PlanB.md`,将已采纳 PostgreSQL 16 方案二后的整体部署方案独立成文2结合当前前后端分层部署形态补齐应用、数据库、中间件、静态存储的一体化部署结构3新增软件拓扑图、网络拓扑图、主机角色划分、资源配置建议、网络需求和端口访问建议4`07_PostgreSQL16_DR_Resource_Application.md` 增加独立文档引用说明,在技术专项目录增加入口。 | 用户明确要求不要仅在 `07` 文档内保留方案二内容,而是需要形成独立文件,并作为已采纳数据库方案后的整体部署方案提交评审。 | 正面影响,数据库资源申请专题与整体部署方案实现职责分离;后续甲方可分别评审“数据库容灾资源申请”和“系统整体部署方案”,减少口径混杂并提升部署审批的可读性。 |
| 2026-04-02 | 方案二整体部署文档按网络图口径对齐 | 1`docs/design/03_Technical_Design/08_Integrated_Deployment_Design_PlanB.md` 的网络分区口径统一为“办公区 / 公网区域 / 互联网区DMZ / 内网区”2`Nginx` 入口、业务应用节点、`SFTP/FTP` 文件交换服务器的部署区域说明统一调整为与评审图一致3同步修正网络拓扑图、访问链路、带宽/端口、资源角色及结论段落中的命名与边界表述。 | 用户提供最新部署图,要求正式文档向图片口径靠拢,消除 `内网 Nginx`、应用区/DMZ、办公区等描述不一致问题。 | 正面影响,整体部署方案的图文一致性显著提升,便于甲方按统一网络边界和主机分区口径开展安全评审与资源审批。 |
| 2026-04-03 | REV-004 全量账务领域设计骨架落地 | 1新增 `docs/evidence/rev004-accounting/REV004_FULL_ACCOUNTING_DOMAIN_DESIGN.md`将“完整承载旧系统账务业务”的目标建模路线落为单一设计底稿2统一旧系统账务对象、目标分层、正式业务实体、关系图、目标表设计骨架、迁移与兼容策略、逐步细化计划3明确该文档作为后续正式主文档回写前的统一骨架不与现有 `REV-004` 一期 spec 冲突。 | 用户提出当前目标不是“一期最小闭环”,而是“新系统完整承载旧系统账务业务”,需要先搭建设计骨架再逐步细化并统一口径。 | 正面影响,后续关于账务实体、台账迁移、查询兼容、审批流和数据库设计的讨论有了统一底稿,可显著降低“继续沿用一期口径”和“直接机械平移旧表”之间的反复摇摆。 |
| 2026-04-03 | REV-004 详设正式回写首批收口RWB-01 | 1`RWB-01` 规则生成正式详设快照 `docs/design/04_Appendix/Archive/08_Formal_Doc_Snapshots/RWB-01/2026-04-03-RWB-01-12_REV_Detailed.md`,基线对应 docs 仓 commit `9d2ecf1`2重写 `docs/design/02_Detailed_Design/12_REV_Detailed.md``REV-004 账务处理` 章节,改为“当前正式边界 + 全量目标边界”双层写法,并纳入全量账务对象、审批分层、迁移承接和现状/目标边界3保持 `IF-REV-007` 当前统一入口事实,不将目标态误写为已全部落地实现。 | 用户同意从回写准备进入 `RWB-01`,要求先把 `12_REV_Detailed.md``REV-004` 正式详设改写为可承接全量账务领域的正式入口。 | 正面影响,`REV-004` 正式详设已从“一期五类场景”提升为“可承接全量账务对象的正式入口”,为后续数据库设计、接口设计与迁移专题回写建立了稳定锚点,同时保留了现状/目标分层表达,降低将目标态误判为当前实现的风险。 |
| 2026-04-03 | REV-004 数据库专项正式回写首批收口RWB-02 | 1`RWB-02` 规则生成数据库专项快照 `docs/design/04_Appendix/Archive/08_Formal_Doc_Snapshots/RWB-02/2026-04-03-RWB-02-01_Database_Design.md`,基线对应 docs 仓 commit `9d2ecf1`2更新 `docs/design/03_Technical_Design/01_Database_Design.md``SYS-002 开账、收费与票据表``biz_operat_log*`、历史台账迁移口径、索引关注点与归档补充,将 `REV-004` 从“一期不新增细表”扩展为“统一骨架 + 目标对象 + 查询投影 / 历史映射”的数据库专项口径3正式命名 `AccountingWorkflowRef``AccountingLegacyMapping` 为目标数据库承接对象,但仍保持“待补字段关注点”写法,不误写成已落地真实表。 | 用户同意进入 `RWB-02` 正式回写,要求先把数据库专项调整为能够承接全量账务领域的正式数据库设计入口。 | 正面影响,数据库专项已从“只描述现有骨架表”提升为“同时约束统一骨架、目标对象、历史映射和查询投影”的正式口径,为后续接口设计回写和实现落表讨论建立了稳定边界,同时避免把目标对象误判为已全部物理落地。 |
| 2026-04-03 | REV-004 接口专项正式回写首批收口RWB-03 | 1`RWB-03` 规则生成接口专项快照 `docs/design/04_Appendix/Archive/08_Formal_Doc_Snapshots/RWB-03/2026-04-03-RWB-03-03_Interface_Design.md`,基线对应 docs 仓 commit `9d2ecf1`2更新 `docs/design/03_Technical_Design/03_Interface_Design.md``IF-REV-007`、其请求/响应参数、数据对象与表口径、历史查询与迁移校验接口口径、实现状态说明,将 `REV-004` 从“一期五类场景统一入口”扩展为“当前统一入口 + 全量账务对象承接边界 + 历史查询分层”的正式接口口径3保持专属接口仍为后续方向不误写成当前 backend 已完整落地能力。 | 用户同意进入 `RWB-03` 正式回写,要求先把接口专项调整为能够承接全量账务领域的正式接口设计入口。 | 正面影响,接口专项已从“一期统一处理接口说明”提升为“统一入口、对象承接边界、历史查询出口、实现状态分层”并存的正式口径,为后续总体设计回写和实现拆接口讨论建立了稳定约束,同时避免把目标专属接口误判为当前既成事实。 |
| 2026-04-03 | REV-004 总体摘要正式回写收口RWB-04 | 1`RWB-04` 规则生成总体摘要快照 `docs/design/04_Appendix/Archive/08_Formal_Doc_Snapshots/RWB-04/2026-04-03-RWB-04-03_Summary_Design.md`,基线对应 docs 仓 commit `9d2ecf1`2更新 `docs/design/01_Overview/03_Summary_Design.md` 中总体接口定义、营收业务系统外部接口、营收核心模块群概述、模块列表与 `REV-004` 模块摘要,使 REV-004 从“一期五类处理摘要”收敛为“当前统一入口 + 全量账务对象目标边界”的概要口径3继续保留当前已落地事实与目标设计边界分层表达不将专属接口或独立对象实现写成已全部落地。 | 用户同意进入 `RWB-04`,要求完成 REV-004 全量账务领域在总体摘要层的正式收口。 | 正面影响,概要、详设、数据库、接口四层文档现已围绕 REV-004 形成一致口径;后续再讨论实现拆分、迁移验收与对象落表时,可直接基于统一的正式文档体系推进,显著降低跨层级表述不一致风险。 |
| 2026-04-03 | REV-004 实现分发清单与 Agent 交接模板落地 | 1新增 `docs/evidence/rev004-accounting/REV004_IMPLEMENTATION_HANDOFF.md`,将 REV-004 全量账务领域的后端、前端、联调任务拆分为可直接分发的实现清单2同步整理对象范围、统一入口约束、历史只读/映射层边界、建议任务包与给其他 Agent 的提示模板3保持该文档定位为实现交接指南不替代正式设计真源。 | 用户需要一份可直接分发给前后端实现 Agent 的功能清单和对应需求,用于后续协作执行。 | 正面影响,后续多 Agent 协作不再需要重复从正式文档中人工提炼任务;实现范围、分工边界与交接提示可直接复用,降低理解偏差和重复沟通成本。 |
| 2026-04-07 | REV-004 前端实现正式 handoff 落地 | 1新增 `docs/evidence/rev004-accounting/REV004_FRONTEND_IMPLEMENTATION_HANDOFF.md`,将已批准的 REV-004 前端实现方案转为正式交接文档2明确本轮仅覆盖管理后台不纳入微网厅/客户端实现3补齐四张正式执行表页面清单、路由清单、通用组件清单、lane ownership 清单,并固化 `../water-frontend` 目录下的 `omx team` 执行边界。 | 用户要求把已达成共识的 REV-004 前端实现计划转为正式 handoff Markdown并细化为可直接执行的正式表格。 | 正面影响,前端实现协作从“计划草案”升级为“执行交接文档”;后续在 `water-frontend` 中可直接按页面清单、路由清单、组件清单和 lane ownership 推进,降低前端实现阶段的范围漂移和分工冲突。 |
| 2026-04-07 | REV-004 前端 team prompts 落地 | 1新增 `docs/evidence/rev004-accounting/REV004_FRONTEND_OMX_TEAM_PROMPTS.md`,将 leader 启动提示与 Lane A~F 执行 prompt 固化为可直接用于 `../water-frontend``omx team` 文本2保持本轮范围仅覆盖管理后台不纳入微网厅/客户端实现3同步固化启动顺序、lane 职责边界与最终统一输出要求。 | 用户要求直接生成可在前端目录执行的 `omx team` prompts用于下一步正式启动多 lane 实现。 | 正面影响,前端执行已从“交接文档阶段”进入“可直接开干的 prompt 阶段”leader 与各 lane 不再需要临时编写任务说明,可直接按固定文本启动团队执行,降低启动成本与分工歧义。 |
| 2026-04-07 | REV-004 前端 team prompts 落地 | 1新增 `docs/evidence/rev004-accounting/REV004_FRONTEND_TEAM_PROMPTS.md`,将 leader 启动提示与 Lane A~F 执行 prompt 固化为可直接用于 `../water-frontend``omx team` 文本2保持本轮范围仅覆盖管理后台不纳入微网厅/客户端实现3同步固化启动顺序、lane 职责边界与最终统一输出要求。 | 用户要求直接生成可在前端目录执行的 `omx team` prompts用于下一步正式启动多 lane 实现。 | 正面影响,前端执行已从“交接文档阶段”进入“可直接开干的 prompt 阶段”leader 与各 lane 不再需要临时编写任务说明,可直接按固定文本启动团队执行,降低启动成本与分工歧义。 |
| 2026-03-18 | REV-005 统计模板补齐 | 1`specs/002-rev005-invoice-flow/verification.md``T055``T060``T061``T062``T063` 新增可直接填写的样本记录模板2将 SC-001 ~ SC-004 的建议统计口径细化为表格字段与待补说明避免后续只剩抽象待办3保持“模板已补齐但实际统计结果仍待联调/测试环境补录”的真实状态,不虚构样本结果。 | 用户继续推进 REV-005希望把剩余统计类待办进一步收敛成可执行模板便于后续直接补录真实样本而不是重新设计统计格式。 | 正面影响REV-005 当前已具备统一的统计与日志抽样记录模板;后续补 `T055``T060 ~ T063` 时可直接按模板填充真实环境数据,减少再次整理验证文档结构的成本。 |
| 2026-03-18 | REV-005 verify 执行入口补齐 | 1`specs/002-rev005-invoice-flow/verification.md` 补齐 `/business/invoice/apply``/query``/query/compensate``/write-back``/customer/query``/customer/download``/customer/push``/invalidate``/red-ink` 的最小请求模板2继续补齐 `T055``T060 ~ T063` 的执行命令草稿与样本采集顺序,明确仅作为测试/联调环境占位模板真实地址、鉴权信息、业务主键与统计结果均待后续替换和回填3同步 `03_Task_Checklist.md`,将 verify 阶段推进到“替换真实环境参数即可执行”的状态。 | 用户继续推进 REV-005希望不要停留在抽象验证建议而是把剩余 verify 工作推进到可直接执行、可直接补样本的程度。 | 正面影响REV-005 当前已具备统一的验证入口、请求模板、命令草稿与采样顺序;后续在测试或联调环境中可直接替换参数发起请求并回填 `T055``T060 ~ T063` 的真实结果,减少重复梳理接口和命令的成本。 |
@ -137,8 +146,8 @@
| 2026-03-16 | REV-005 后台发票申请与校验闭环US1 | 1`backend/sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/invoice/InvoiceController.java` 增加后台发票申请入口2`service/invoice/InvoiceServiceImpl.java` 实现客户、客户开票信息、账单与税率校验确保仅已收费未开票账单允许申请3补齐 `applicationNo``custId + chargeIds` 幂等控制、申请单号生成与申请记录落库4执行 `mvn -f backend/sw-business/pom.xml -pl sw-business-server -am -DskipTests compile` 最小编译验证并通过。 | 用户要求继续推进 REV-005 implement优先完成后台发票申请、开票校验与 US1 收尾,不中断当前实现链路。 | 正面影响REV-005 已形成“后台申请入口 + 关键校验 + 幂等受理 + 最小编译验证”闭环,后续可在此基础上继续推进 SYS-008 调用、开票结果回写、账单状态联动与电子发票推送下载能力。 |
| 2026-03-12 | OMX 任务路由样例落地 | 1新增 `docs/design/00_Management/17_OMX_Task_Routing_Examples.md`,给出 `REV-004``REV-003` 与正式文档修订三类任务的 leader / explorer / executor / verifier 分工模板2补充各 lane 的提示词模板、推荐执行顺序与不建议的并行方式3更新 `00_Management/README.md` 收录该文档入口。 | 用户希望在治理层之外,再拿到针对当前项目可以直接复用的多 Agent 分工模板,而不是只看抽象原则。 | 正面影响OMX 从“有规则”变成“可直接照着分工执行”;后续在 `REV-004``REV-003` 等闭环中可直接套用 lane 拆分,减少 leader 临时编排成本。 |
| 2026-03-12 | OMX 多 Agent 治理层落地 | 1在根 `AGENTS.md` 中新增 OMX 多 Agent 协作补充,明确分层 AGENTS、任务路由矩阵、写冲突规则、范围控制基线与 worktree/tmux 约定2新增 `backend/AGENTS.md`,固化 backend 开发边界、最小实现策略与 BPM 接入规则3新增 `docs/design/04_Appendix/Archive/AGENTS.md`,明确 Archive 默认只读与来源回写规则4新增 `docs/design/00_Management/16_OMX_Multi_Agent_Guide.md`,统一 leader / writer / executor / verifier 分工与协作拓扑5更新 `00_Management/README.md` 收录新指南入口。 | 用户明确准备采用 `oh-my-codex` 进行多 Agent 开发,需要对现有 AGENTS 体系做分层化和路由化改造,避免现有规则在 Team Mode 下失效。 | 正面影响,当前项目从“单 agent 规则完备”提升为“多 agent 可控协作”;后续接入 OMX 时,目录级职责、写权限边界、范围基线与会话约定已经成型,可显著降低并发改稿和多 worker 写冲突风险。 |
| 2026-03-12 | REV-004 一期执行手册与 worktree/tmux 启动脚本落地 | 1新增 `docs/guides/REV004_ACCOUNTING_EXECUTION_PLAYBOOK.md`,将 `REV-004` 一期的范围、现状差距、最小改动方案、任务拆解、Codex Prompt 与验收清单固化为可执行手册2新增 `scripts/start-backend-codex-session.sh`,支持在 `backend/` 上创建或复用 worktree并拉起 `tmux + codex` 三窗口开发会话3更新 `scripts/README.md` 记录脚本用途。 | 用户要求不仅给建议,还要把“如何规划执行、如何进入 worktree、如何在 tmux 场景下协作”落成可直接使用的资产。 | 正面影响,开发启动动作从“口头建议”变为“可复制执行”;后续围绕 `REV-004/REV-003/REV-008` 的闭环开发可直接复用该脚本和手册,降低 worktree、tmux、Codex 协作的起步成本与操作偏差。 |
| 2026-03-13 | REV-004 一期范围正式文档收敛US1 | 1更新 `docs/design/02_Detailed_Design/12_REV_Detailed.md`,将 REV-004 一期范围收敛为水量调整、金额调整、退款、冲正、坏账申请五类场景并明确共性能力优先顺序、排除项与审批边界2重写 `docs/guides/REV004_ACCOUNTING_EXECUTION_PLAYBOOK.md`,将其从 backend 执行导向收敛为正式文档修订执行手册3同步 `docs/design/01_Overview/03_Summary_Design.md``specs/001-rev004-accounting/contracts/rev004-traceability-matrix.md` 的范围摘要与追溯说明4执行目标文档 `make validate-file``make check-links` 校验并通过。 | 用户要求在 implement 阶段先完成 REV-004 一期正式文档范围收敛,不把 backend 代码实现写成本轮已完成内容。 | 正面影响REV-004 一期边界、验收入口与追溯关系已在正式文档体系内闭环,后续 US2/US3 可直接在稳定范围上继续对齐接口、数据库与台账,不再受早期执行手册偏代码导向表述干扰。 |
| 2026-03-12 | REV-004 一期执行手册与 worktree/tmux 启动脚本落地 | 1新增 `docs/evidence/rev004-accounting/REV004_ACCOUNTING_EXECUTION_PLAYBOOK.md`,将 `REV-004` 一期的范围、现状差距、最小改动方案、任务拆解、Codex Prompt 与验收清单固化为可执行手册2新增 `scripts/start-backend-codex-session.sh`,支持在 `backend/` 上创建或复用 worktree并拉起 `tmux + codex` 三窗口开发会话3更新 `scripts/README.md` 记录脚本用途。 | 用户要求不仅给建议,还要把“如何规划执行、如何进入 worktree、如何在 tmux 场景下协作”落成可直接使用的资产。 | 正面影响,开发启动动作从“口头建议”变为“可复制执行”;后续围绕 `REV-004/REV-003/REV-008` 的闭环开发可直接复用该脚本和手册,降低 worktree、tmux、Codex 协作的起步成本与操作偏差。 |
| 2026-03-13 | REV-004 一期范围正式文档收敛US1 | 1更新 `docs/design/02_Detailed_Design/12_REV_Detailed.md`,将 REV-004 一期范围收敛为水量调整、金额调整、退款、冲正、坏账申请五类场景并明确共性能力优先顺序、排除项与审批边界2重写 `docs/evidence/rev004-accounting/REV004_ACCOUNTING_EXECUTION_PLAYBOOK.md`,将其从 backend 执行导向收敛为正式文档修订执行手册3同步 `docs/design/01_Overview/03_Summary_Design.md``specs/001-rev004-accounting/contracts/rev004-traceability-matrix.md` 的范围摘要与追溯说明4执行目标文档 `make validate-file``make check-links` 校验并通过。 | 用户要求在 implement 阶段先完成 REV-004 一期正式文档范围收敛,不把 backend 代码实现写成本轮已完成内容。 | 正面影响REV-004 一期边界、验收入口与追溯关系已在正式文档体系内闭环,后续 US2/US3 可直接在稳定范围上继续对齐接口、数据库与台账,不再受早期执行手册偏代码导向表述干扰。 |
| 2026-03-18 | 完成全仓旧模块编号残留审计 | 1新增 `docs/design/00_Management/18_Old_Module_Number_Audit.md`,对 `docs/design``docs/guides``specs` 中的 `METER-004``INST-004``INST-005` 及相关旧模块语义进行全仓审计2将命中项分为“必须修”“可保留历史描述”“Archive 原始资料”“合法保留的接口编号/子能力表达”四类3更新 `docs/design/00_Management/README.md` 收录审计清单入口。 | 用户要求执行全仓旧编号残留审计,明确哪些需要继续修、哪些应作为历史资料保留。 | 正面影响,后续旧编号治理从“按检索临时判断”转为“按清单分类处理”;正式文档、管理台账与 Archive 的处理边界更加清晰。 |
| 2026-03-18 | 旧模块编号残留收口与追溯链接修正 | 1更新 `docs/design/02_Detailed_Design/03_CA_Esignature_Supplement.md`,将文档定位从旧 `INST-004` 修正为 `INST-002 工程管理` 下的“合同签署与电子签章能力”专项补充2修正 `docs/design/02_Detailed_Design/02_Module_Traceability_Index.md``INST-002``INST-003` 的章节锚点确保跳转与当前三段式模块结构一致3复核 `docs/design/01_Overview/03_Summary_Design.md` 的主口径章节,确认残留 `IF-METER-004``IF-INST-004/005` 均为接口编号而非旧模块编号。 | 用户要求继续收口“旧编号视为错误”的相关正式文档,并区分必须修项与可保留历史描述。 | 正面影响,正式文档层的模块编号、追溯入口与专项补充挂接关系进一步统一,评审和检索时不会再因旧 `INST-004/005` 模块语义误跳转。 |
| 2026-03-18 | 基于整体架构图补齐模块清单并对齐详细设计承接关系 | 1新增 `docs/design/01_Overview/05_Module_Inventory.md`将整体架构图中的分层、子系统、模块与当前详设承接关系整理为统一清单2更新 `docs/design/01_Overview/README.md``docs/design/02_Detailed_Design/README.md`明确该清单作为模块枚举与承接核对入口3更新 `docs/design/02_Detailed_Design/01_Detailed_Design.md``docs/design/02_Detailed_Design/02_Module_Traceability_Index.md`,补充架构图模块清单与当前详设之间的映射说明,明确 `WECHAT-*``MOBILE-*``WORK-*` 等模块当前按业务域正文或协同能力承接。 | 用户要求根据 `output/preview/福建水务营收系统整体架构图.html` 形成模块清单,并以此为基线对齐 `02_Detailed_Design` 目录。 | 正面影响,架构图中的模块枚举与详细设计现状之间建立了稳定映射,后续评审时可直接判断“哪些模块已有正文、哪些是协同承接、哪些仍停留在架构层列示”,减少按图追文时的歧义。 |
@ -331,7 +340,7 @@
### 2026-03-18 更新
- 完成 `REV-006` Speckit feature `006-reminder-event-design` 的 implement 阶段文档收口,统一 `12_REV_Detailed.md``03_Interface_Design.md` 与治理台账口径。
- 完成 `REV-006` Speckit feature `003-rev006-reminder-event-design` 的 implement 阶段文档收口,统一 `12_REV_Detailed.md``03_Interface_Design.md` 与治理台账口径。
- `REV-006` 正式业务接口编号确定为 `IF-REV-013`,不再复用 `IF-REV-009`;催缴结果状态统一为 `PENDING``SUCCESS``FAIL``MANUAL_VERIFIED` 四态。
- 明确旧“催缴记录 / 停水记录 / 预存短信记录”按历史只读口径承接,不新增同名在线主表;停复水在本轮仅保留联动边界、处置引用和追溯关系。
- 启动并完成 `REV-007` Speckit feature `004-rev007-revenue-statistics-design``specify -> plan -> tasks` 工件链,形成统计主题、维度、指标、`IF-REV-010` 与数据库承接口径的正式设计基线。
@ -344,7 +353,7 @@
### 2026-03-19 更新
- 完成 `REV-006` 当前轮次治理文档二次对齐:在 `15_SYS002_Requirement_Breakdown.md` 明确 `SYS002-REQ-011` 继续维持“未见实现”判断,并补记 `specs/006-reminder-event-design/` 工件基线与后续研发切入建议。
- 完成 `REV-006` 当前轮次治理文档二次对齐:在 `15_SYS002_Requirement_Breakdown.md` 明确 `SYS002-REQ-011` 继续维持“未见实现”判断,并补记 `specs/003-rev006-reminder-event-design/` 工件基线与后续研发切入建议。
- 同步回写 `03_Task_Checklist.md` 与本进度文档,补充本轮 implement 阶段的治理动作、验证动作与台账一致性说明,避免将文档收口误写为 backend 已实现。
### 2026-03-24 更新

View File

@ -196,7 +196,7 @@
### 📋 REV-004 执行资产落地
- [x] **完成 REV-004 一期执行手册与启动脚本落地** ✅ (2026-03-12)
- [x] 新增 `docs/guides/REV004_ACCOUNTING_EXECUTION_PLAYBOOK.md`,固化范围、最小改动方案、任务拆解与验收清单 ✅
- [x] 新增 `docs/evidence/rev004-accounting/REV004_ACCOUNTING_EXECUTION_PLAYBOOK.md`,固化范围、最小改动方案、任务拆解与验收清单 ✅
- [x] 新增 `scripts/start-backend-codex-session.sh`,支持 backend worktree + tmux + Codex 启动 ✅
- [x] 在 `scripts/README.md` 登记启动脚本用途 ✅
- [x] 在 `01_Project_Progress.md` 记录本次执行资产落地 ✅
@ -571,7 +571,7 @@
- [x] 更新 `docs/design/03_Technical_Design/01_Database_Design.md`,明确历史只读保留策略、最小查询字段与处置引用边界 ✅
- [x] 更新 `docs/design/00_Management/15_SYS002_Requirement_Breakdown.md``01_Project_Progress.md` 与本任务清单,完成 implement 阶段治理回写 ✅
- [x] **完成 REV-006 implement 阶段治理文档二次对齐** ✅ (2026-03-19)
- [x] 在 `15_SYS002_Requirement_Breakdown.md` 明确 `SYS002-REQ-011` 继续维持“未见实现”,并补记 `specs/006-reminder-event-design/` 工件基线 ✅
- [x] 在 `15_SYS002_Requirement_Breakdown.md` 明确 `SYS002-REQ-011` 继续维持“未见实现”,并补记 `specs/003-rev006-reminder-event-design/` 工件基线 ✅
- [x] 在 `01_Project_Progress.md` 更正 REV-006 feature 编号与本轮治理回写记录,避免误导为 backend 已实现 ✅
- [x] 在治理任务建议中补充 `make validate-file``make check-links``make validate-mermaid` 与台账同步动作 ✅

View File

@ -130,7 +130,7 @@
| `REV-003` 营业收费 | 部分实现 | 建议围绕“柜台班结”“红冲历史查询”等缺口拆小 feature |
| `REV-004` 账务处理 | 已实现 | 以规则收口、二期扩展或历史台账映射为主 |
| `REV-005` 发票与税务处理 | 已实现 | 以验收归档和细粒度对象补证为主 |
| `REV-006` 催缴与通知 | 未见实现 | 已完成 Speckit `specify -> plan -> tasks` 工件并形成 `specs/006-reminder-event-design/` 基线;当前进入 `implement` 阶段文档收口与台账同步backend 仍未见明确催缴/通知业务实现骨架。 |
| `REV-006` 催缴与通知 | 未见实现 | 已完成 Speckit `specify -> plan -> tasks` 工件并形成 `specs/003-rev006-reminder-event-design/` 基线;当前进入 `implement` 阶段文档收口与台账同步backend 仍未见明确催缴/通知业务实现骨架。 |
| `REV-007` 统计分析 | 未见实现 | 已完成 Speckit `specify -> plan -> tasks`,当前进入 `implement` 阶段的正式文档收口与治理同步。 |
| `REV-008` 代收与银行业务 | 已实现 | 以跨系统口径收口和扩展台账补证为主 |
| `REV-009` 业务参数配置 | 已实现 | 以参数边界收口和治理能力补充为主 |
@ -152,7 +152,7 @@
| `REV-003` | 是 | `rev003-redflush-history-query` | 中 | 聚焦红冲历史查询与台账承接 |
| `REV-004` | 否 | `rev004-adjustment-followup` | 中 | 仅在补二期或精细对象时使用 |
| `REV-005` | 否 | `rev005-detail-reconciliation` | 中 | 仅在补发票细表/批次缺口时使用 |
| `REV-006` | 是 | `rev006-reminder-event-design` | 最高 | 已完成 `spec/plan/research/data-model/contracts/quickstart/tasks` 工件链,当前重点为按 `IF-REV-013` 四态口径推进正式文档 implement 收口并形成后续研发切入点。 |
| `REV-006` | 是 | `rev003-rev006-reminder-event-design` | 最高 | 已完成 `spec/plan/research/data-model/contracts/quickstart/tasks` 工件链,当前重点为按 `IF-REV-013` 四态口径推进正式文档 implement 收口并形成后续研发切入点。 |
| `REV-006` | 是 | `rev006-notice-result-writeback` | 高 | 可作为 `REV-006` 拆分子 feature |
| `REV-007` | 是 | `rev007-revenue-statistics-design` | 高 | 已启动并完成第一轮设计工件,当前建议继续收口正式文档并形成后续研发切入点 |
| `REV-007` | 是 | `rev007-channel-analysis-query` | 中 | 适合作为统计分析子 feature |
@ -233,13 +233,13 @@
- 标题:`SYS-002补齐催缴事件与通知协同设计`
- 对应需求:`SYS002-REQ-011`
- feature`rev006-reminder-event-design`(治理映射名:`sys002-reminder-event-design`
- feature`rev003-rev006-reminder-event-design`(治理映射名:`sys002-reminder-event-design`
- 优先级:`高`
- 当前实现状态:`未见实现`
- Story 描述:
- 目标:补齐欠费催缴对象生成、通知触发、通知结果回写与催缴追踪闭环。
- 事实来源:`12_REV_Detailed.md` 中 REV-006、`03_Interface_Design.md``IF-EXT-008`
- 当前判断:当前代码检索范围内未见明确的催缴、通知、消息协同控制器或服务骨架;但本轮已完成 `specs/006-reminder-event-design/` 工件链,并在正式文档中统一 `IF-REV-013`、四态状态集、历史只读查询口径和停复水联动边界。
- 当前判断:当前代码检索范围内未见明确的催缴、通知、消息协同控制器或服务骨架;但本轮已完成 `specs/003-rev006-reminder-event-design/` 工件链,并在正式文档中统一 `IF-REV-013`、四态状态集、历史只读查询口径和停复水联动边界。
- 验收要点:明确催缴触发时机、催缴对象筛选条件、通知方式、回写字段、四态状态语义与失败重试机制。
- 推荐 Task
- `T-011-01` 对齐催缴业务场景、触发条件与对象筛选规则

View File

@ -878,6 +878,7 @@ graph TB
| IF-REV-004 | 抄表数据提交接口 | 提交人工或远传抄表数据并触发校验 | 手机抄表APP/集抄系统 | HTTPS REST | 抄表任务ID、水表ID、读数、图片证据、GPS位置 | 上传结果、校验状态、异常标记 |
| IF-REV-005 | 账单生成接口 | 根据抄表结果、水价模板和费用组成生成账单 | 开账任务/批量任务 | HTTPS REST | 抄表批次、账期、客户范围、应收日期 | 账单结果、失败清单、生成汇总 |
| IF-REV-006 | 缴费处理接口 | 创建收费记录并核销账单 | 柜台/线上渠道 | HTTPS REST | 客户ID、账单ID列表、支付方式、实收金额 | 收费结果、核销状态、交易流水 |
| IF-REV-007 | 账务处理接口 | 当前统一承接账务调整、退款/冲正、坏账申请及全量账务对象通用处理申请 | 柜台/管理后台/协同任务 | HTTPS REST | 账单ID、对象类型、处理类型、原因、审批边界、原交易号 | 处理结果、审批状态、账单回写状态、结果对象编号 |
| IF-REV-008 | 发票申请接口 | 发起电子发票申请并接收票据状态回写 | 柜台/客户渠道 | HTTPS REST | 客户ID、账单ID列表、开票抬头、税号、邮箱 | 发票申请结果、票据状态、下载地址 |
| IF-REV-011 | 银行代收协同接口 | 发起代扣、回盘、对账、结算协同 | 银行代收模块/SYS-009 | HTTPS REST / 文件交换 | 批次号、渠道编码、账期、账单明细 | 批次状态、回盘结果、对账差异、结算结果 |
| IF-REV-012 | 业务参数配置接口 | 查询和维护价格模板、优惠方案、业务参数配置 | 管理后台/参数管理端 | HTTPS REST | 参数分类、模板编码、站点范围 | 参数明细、模板信息、更新结果 |
@ -1749,7 +1750,7 @@ graph TD
- **客户资料管理**:客户档案建立、信息维护、分组管理
- **抄表开账**:抄表数据录入、复核确认、自动开账
- **营业收费**:柜台收费、移动收费、在线缴费
- **账务处理**一期先聚焦水量调整、金额调整、退款、冲正、坏账申请,统一经 `IF-REV-007` 承接,并按共性能力先统一、场景能力再分批推进
- **账务处理**当前仍由 `IF-REV-007` 统一承接账务处理入口,统一覆盖账单修正、退款/冲正、坏账申请,并向预存退款、红冲、价差调整、违约金减免、分账调整、退款账、跨周期水量等全量账务对象边界扩展
- **发票管理**:发票开具、查询、重开、作废
- **催缴管理**:欠费统计、催缴通知、停水管理
- **统计分析**:多维度数据统计和报表分析
@ -1805,6 +1806,7 @@ graph TD
| IF-REV-004 | 抄表数据提交接口 | 提交人工或远传抄表数据并触发校验 | 手机抄表APP/集抄系统 | HTTPS REST | 抄表任务ID、水表ID、读数、图片证据、GPS位置 | 上传结果、校验状态、异常标记 |
| IF-REV-005 | 账单生成接口 | 根据抄表结果、水价模板和费用组成生成账单 | 开账任务/批量任务 | HTTPS REST | 抄表批次、账期、客户范围、应收日期 | 账单结果、失败清单、生成汇总 |
| IF-REV-006 | 缴费处理接口 | 创建收费记录并核销账单 | 柜台/线上渠道 | HTTPS REST | 客户ID、账单ID列表、支付方式、实收金额 | 收费结果、核销状态、交易流水 |
| IF-REV-007 | 账务处理接口 | 当前统一承接账务调整、退款/冲正、坏账申请及全量账务对象通用处理申请 | 柜台/管理后台/协同任务 | HTTPS REST | 账单ID、对象类型、处理类型、原因、审批边界、原交易号 | 处理结果、审批状态、账单回写状态、结果对象编号 |
| IF-REV-008 | 发票申请接口 | 发起电子发票申请并接收票据状态回写 | 柜台/客户渠道 | HTTPS REST | 客户ID、账单ID列表、开票抬头、税号、邮箱 | 发票申请结果、票据状态、下载地址 |
| IF-REV-011 | 银行代收协同接口 | 发起代扣、回盘、对账、结算协同 | 银行代收模块/SYS-009 | HTTPS REST / 文件交换 | 批次号、渠道编码、账期、账单明细 | 批次状态、回盘结果、对账差异、结算结果 |
| IF-REV-012 | 业务参数配置接口 | 查询和维护价格模板、优惠方案、业务参数配置 | 管理后台/参数管理端 | HTTPS REST | 参数分类、模板编码、站点范围 | 参数明细、模板信息、更新结果 |
@ -1975,7 +1977,7 @@ graph TB
| REV-001 | 客户资料管理 | 客户档案管理、客户分组、信息变更 | 自行开发 |
| REV-002 | 抄表开账 | 抄表录入、复核开账、异常处理 | 自行开发 |
| REV-003 | 营业收费 | 柜台收费、移动收费、在线缴费 | 自行开发 |
| REV-004 | 账务处理 | 账务调整、退款处理、坏账管理 | 自行开发 |
| REV-004 | 账务处理 | 统一账务处理、退款/冲正、红冲/价差/分账/坏账及历史账务承接 | 自行开发 |
| REV-005 | 发票管理 | 发票开具、查询管理、电子发票 | 自行开发 |
| REV-006 | 催缴管理 | 欠费催缴、短信通知、停水管理 | 自行开发 |
| REV-007 | 统计分析 | 多维度数据统计和报表分析功能 | 自行开发 |
@ -2334,14 +2336,14 @@ flowchart TD
**功能概述:**
&emsp;&emsp;负责处理各类复杂的账务调整、退款、坏账等业务,确保账务的准确性和合规性
&emsp;&emsp;负责统一承接营收核心链路中的账务处理能力。当前正式口径仍以 `IF-REV-007` 作为统一入口,承接账单修正、退款/冲正、坏账申请等处理;目标设计边界进一步覆盖预存退款、红冲、已销调整、价差调整、违约金减免、分账调整、退款账结果与跨周期水量等全量账务对象,并保持当前事实与目标设计分层表达
**核心功能:**
- **未销账调整**: 对未支付账单进行调整
- **分账调整**: 将一笔总账单拆分为多笔子账单
- **预付款退款**: 处理客户预付款的退还流程
- **呆坏账处理**: 对长期无法收回的欠款进行核销
- **统一账务处理入口**: 通过 `IF-REV-007` 统一受理账务对象处理申请,返回处理结果、审批边界与账单回写状态
- **账务对象承接**: 在概要层承接预存退款、红冲、已销调整、价差调整、违约金减免、分账调整、坏账等对象族语义
- **结果与追溯**: 保留退款账结果、跨周期水量、历史账务查询、审批留痕与原单据追溯边界
- **分层边界控制**: 当前已落地能力继续保留统一入口事实;专属接口、独立对象实现和 BPM 细节作为后续方向,不在概要层误写成既成事实
#### REV-005: 发票管理

View File

@ -294,74 +294,93 @@ flowchart TD
### 功能说明
REV-004 一期仅覆盖水量调整、金额调整、退款、冲正、坏账申请五类场景,统一挂靠 `IF-REV-007` 作为账务处理入口,目标是在既有正式文档体系内先收敛范围、承接口径、留痕要求与审批边界
REV-004 当前正式口径仍以统一账务处理能力为核心,继续由 `IF-REV-007` 承接当前在线处理入口,统一处理账单承接、原交易校验、处理结果表达、操作留痕与审批边界。当前正式实现边界仍以营业账主明细、支付交易校验、操作日志与价格/方案重算支撑为基础,不将尚未落地为独立物理表族的对象误写成已实现事实
本阶段按“共性能力先统一、场景能力再分批”组织:先统一账单承接、原交易校验、结果表达、操作留痕与审批边界,再分别展开五类场景。违约金减免、分账调整、价差调整、跨周期水量、预存退款细表等内容仅作为旧系统迁移语义或后续扩展参考,不作为一期新增独立范围。
在全量目标设计下REV-004 不再只覆盖一期五类场景,而是统一承接全量账务对象族:`PrepaidRefund``RedinkRecord``WrittenoffAdjust``PriceDiffAdjust``LateFeeReduce``BadDebtRecord``SplitAdjust`、特殊开账 / 特账承接方式、`RefundBill``CrossCycleWaterRecord`。其中,特账按特殊开账类型挂接 `Charge` / `ChargeDetail` 主模型承接,`RefundBill` 作为退款处理结果对象保留,`CrossCycleWaterRecord` 作为账单重算和阶梯累计支撑对象保留IC 卡账务当前仍按历史只读 + 映射层承接,不纳入在线主写范围。
### 业务流程
```mermaid
flowchart TD
A[发起账务调整申请] --> B[校验账单状态与权限]
B --> C{是否通过}
C -->|否| D[驳回并记录原因]
C -->|是| E[执行重算或退款冲正]
E --> F[更新账单与明细状态]
F --> G[写入操作日志与审批留痕]
G --> H[返回处理结果]
A[提交账务处理或账务对象申请] --> B[校验账单、原交易、权限与来源依据]
B --> C{是否需要审批}
C -->|是| D[进入审批留痕与待审状态]
D --> E{审批是否通过}
E -->|否| F[驳回并记录原因]
E -->|是| G[执行重算、退款、红冲、核销修正或结果生成]
C -->|否| G
G --> H[回写账单、结果对象与关联状态]
H --> I[记录操作日志、审批引用与历史映射]
I --> J[返回处理结果]
```
### 关键规则
1. 一期场景严格限定为水量调整、金额调整、退款、冲正、坏账申请,不扩展到其他接口族或独立账务台账重构。
2. 所有场景均以 `biz_charge` / `biz_charge_detail` 为主承接对象,并通过 `biz_operat_log` / `biz_operat_log_detail` 记录处理依据、前后变化和责任归属。
3. 退款、冲正必须联动 `bk_transaction``bk_transaction_callback``bk_transaction_exception` 等原支付流水及渠道状态校验,不允许仅依据账单状态直接处理。
4. 接口结果统一返回 `resultStatus``writeBackStatus`,其中 `resultStatus` 表示处理结论,`writeBackStatus` 表示账单状态回写结论,两者不得混用。
5. 审批相关内容一期仅保留 `approvalRequired``PENDING_APPROVAL` 与审批边界说明,不展开完整 BPM 流程、节点、流转规则或审批回写实现细节。
6. 对于当前未见明确独立实体表的特账、跨周期水量、退款账等对象,文档以“业务处理场景”表述,不强行落为已实现表。
1. 当前正式边界仍以 `IF-REV-007` 统一承接在线账务处理入口;目标设计边界扩展为覆盖全量账务对象族,但不把未来专属接口和独立表族误写成当前全部已落地事实。
2. 所有在线处理场景均以 `biz_charge` / `biz_charge_detail` 为统一账单承接骨架,并通过 `biz_operat_log` / `biz_operat_log_detail` 记录处理依据、前后变化、附件和责任归属;独立业务对象在详设层保留为正式业务语义,不再简单压平为单一“金额调整”子类型。
3. 退款、红冲、冲正及其他资金回退相关场景,必须联动 `bk_transaction``bk_transaction_callback``bk_transaction_exception` 等原支付流水和渠道状态校验,不允许仅依据账单状态直接处理。
4. 接口结果至少统一表达 `resultStatus``writeBackStatus`;涉及审批的场景还应明确 `approvalRequired``approvalStatus` 等边界,但当前正式文档仍不展开完整 BPM 节点、流转规则和引擎实现细节。
5. 审批承接按对象分层:`PrepaidRefund``BadDebtRecord` 倾向独立审批流对象;`WrittenoffAdjust``PriceDiffAdjust``LateFeeReduce``SplitAdjust``RedinkRecord` 当前先保留审批能力位或可升级边界;`RefundBill``CrossCycleWaterRecord` 不单独承接审批流。
6. 特账不是单独的在线主写账务实体,而是特殊开账类型;应通过开账来源类型、费用类型、原因编码和留痕信息挂接在 `Charge` / `ChargeDetail` 主模型中承接。
7. 旧系统账务对象必须按“统一骨架、独立业务实体、查询投影、历史映射”四类分层承接实时收费、对账日志等查询类对象不强行回写为在线主写对象IC 卡账务保持历史只读 + 映射层口径。
8. 所有账务处理场景都必须保留与原单据、原账单、原交易、审批痕迹和经办留痕的追溯关系,避免迁移后出现对账、审计和责任链断裂。
### 核心数据
- `biz_charge``biz_charge_detail`:账务调整的核心对象,承接调整前后账单主明细状态。
- `bk_transaction``bk_transaction_callback``bk_transaction_exception`:退款、冲正场景的原交易校验与异常追溯对象。
- 价格调整/优惠相关表:用于重算账单或差额追溯。
- `biz_operat_log``biz_operat_log_detail`:操作与变更留痕,记录字段差异、处理说明、附件依据与责任归属。
- **当前统一骨架对象**`biz_charge``biz_charge_detail`,承接账务处理前后账单主明细状态和费用结果。
- **当前交易校验对象**`bk_transaction``bk_transaction_callback``bk_transaction_exception`,承接退款、红冲、冲正等资金相关场景的原交易核验与异常追溯。
- **当前/目标留痕对象**`biz_operat_log``biz_operat_log_detail`,记录字段差异、处理说明、附件依据、责任人和操作时间。
- **目标正式业务对象**`PrepaidRefund``RedinkRecord``WrittenoffAdjust``PriceDiffAdjust``LateFeeReduce``BadDebtRecord``SplitAdjust``RefundBill``CrossCycleWaterRecord`;其中特账按特殊开账类型挂接 `Charge` / `ChargeDetail` 承接。
- **目标审批引用对象**`AccountingWorkflowRef`,用于统一承接 `approvalRequired``approvalStatus`、旧 `TaskId` / `StepId` / `FlowRemark` 与后续审批引用。
- **目标历史映射对象**`AccountingLegacyMapping`,用于保留旧单据标识、旧账单标识、迁移批次、承接方式和历史追溯关系。
### 主要场景
| 场景 | 说明 | 控制要点 |
|---|---|---|
| 水量调整 | 更正异常水量 | 需复核原因、附件和原抄表依据 |
| 金额调整 | 更正账单金额 | 需记录依据、差异金额和审批边界 |
| 退款 | 退回客户支付资金或预存款 | 需校验原交易、退款余额与幂等性 |
| 冲正 | 修正误收/误核销记录 | 需关联原交易与账单状态 |
| 坏账申请 | 对长期欠费进行分类处理 | 需结合账龄、客户状态与审批边界 |
| 场景对象 | 场景说明 | 当前正式边界 | 控制要点 |
|---|---|---|---|
| `PrepaidRefund` | 预存退款申请与处理 | 当前仍通过统一账务处理能力承接 | 校验原支付、退款余额、审批边界和退款结果回写 |
| `RedinkRecord` | 红冲 / 冲正记录 | 当前保留统一处理入口与历史查询能力 | 必须关联原交易、原收费记录和红冲原因 |
| `WrittenoffAdjust` | 已收费后修正 | 当前按统一调账场景承接 | 保留原账单、调整原因和前后差异 |
| `PriceDiffAdjust` | 调价差额重算 | 当前以重算与差额修正场景表达 | 保留原价格口径、新价格口径和生效时间 |
| `LateFeeReduce` | 违约金减免 | 当前以滞纳金修正场景表达 | 支持减免原因、减免金额和审批边界 |
| `BadDebtRecord` | 呆坏账申请与生效 | 当前为统一账务处理中的独立场景 | 保留账龄、客户状态、审批结果和核销状态 |
| `SplitAdjust` | 分账调整 | 当前作为费用组成重分摊场景表达 | 支持按水量 / 按费用组成等分摊策略 |
| 特殊开账 / 特账 | 特殊开账形成的收费结果 | 当前挂接开账主模型,不单列独立表族 | 保留特殊来源类型、费用类型、原因编码与留痕 |
| `RefundBill` | 退款处理结果对象 | 当前不单独作为在线提交入口 | 关联退款申请、支付退款结果与账单状态 |
| `CrossCycleWaterRecord` | 跨周期水量支撑对象 | 当前作为重算和阶梯累计支撑语义 | 保留跨期水量、累计逻辑和关联场景来源 |
### 迁移补充(旧系统承接)
| 旧账务对象 | 当前承接方式 | 迁移口径 |
|---|---|---|
| 预存退款 / 预存退款详情 | 作为账务处理场景承接 | 保留申请单、原支付引用、退款结果与审批留痕 |
| 已销调整汇总 / 明细 | 作为已收费后修正场景承接 | 保留原账单、调整原因、前后差异、处理结果 |
| 价差调整汇总 / 明细 | 作为重算与差额修正场景承接 | 保留原价格口径、新价格口径、差额和生效时间 |
| 分账调整汇总 / 明细 | 作为费用组成重分摊场景承接 | 保留原分摊结果、调整后结果、责任人和审批链 |
| 账单-违约金减免 | 作为滞纳金修正场景承接 | 保留减免原因、减免金额、审批结果和生效时间 |
| 账单-呆坏账 | 作为坏账申请与生效场景承接 | 保留账龄、申请原因、审批结果、核销状态 |
| 旧账务对象 | 目标分层 | 当前承接方式 | 迁移口径 |
|---|---|---|---|
| 预存退款 / 预存退款详情 | `L2` 独立业务层 | 在线主模型 + 独立业务语义 | 保留申请单、原支付引用、退款结果、审批留痕 |
| 已销调整汇总 / 明细 | `L2` 独立业务层 | 在线主模型 + 场景承接 | 保留原账单、调整原因、前后差异、处理结果 |
| 价差调整汇总 / 明细 | `L2` 独立业务层 | 在线主模型 + 重算差额场景 | 保留原价格口径、新价格口径、差额和生效时间 |
| 分账调整汇总 / 明细 | `L2` 独立业务层 | 在线主模型 + 费用重分摊场景 | 保留原分摊结果、调整后结果、策略类型和责任链 |
| 账单-违约金减免 | `L2` 独立业务层 | 在线主模型 + 减免场景 | 保留减免原因、减免金额、审批结果和生效时间 |
| 账单-呆坏账 | `L2` 独立业务层 | 在线主模型 + 坏账申请场景 | 保留账龄、申请原因、审批结果、核销状态 |
| 特账 / 特殊开账 | `L2` 独立业务层(挂主开账模型) | 开账主模型扩展承接 | 保留特殊来源、费用类型、原因编码和关联账单 |
| 退款账 | `L2` 独立业务层(结果对象) | 退款结果对象承接 | 保留退款申请、退款结果、账单/支付关联 |
| 跨周期水量 | `L2` 基础支撑对象 | 重算与阶梯累计支撑对象 | 保留跨期水量、账单重算来源和累计口径 |
| 实时收费汇总 / 日志 / 明细、对账日志 | `L3` 查询投影层 | `projection-only` | 保留历史查询、报表和对账出口,不强接在线主写 |
| IC 卡账务表族 | `L4` 历史映射层 | `history-readonly + mapping-only` | 保留历史只读查询、旧主键映射和迁移追溯 |
1. P0 阶段不要求为每一类旧账务台账都新增独立实体表,但必须在业务对象和历史查询层形成可追溯闭环。
1. 正式详设层不要求把每一类旧账务对象都立即落为已实现独立物理表,但必须先明确其在目标模型中的层级、角色和承接方式
2. 旧系统精细台账迁移后至少保留:原单据标识、原账单标识、处理类型、处理原因、处理前后金额/水量、申请/审批/生效时间、经办人与附件依据。
3. 与支付、发票、渠道回调强关联的处理场景,必须保留与原交易、原发票、原收费记录的关联关系,避免后续对账和审计断链。
### 接口映射
- `IF-REV-007`:账务调整、退款、冲正、坏账等处理入口。
- `IF-REV-006`:与收费核销状态联动,确保调账后账单状态一致。
- `IF-REV-007`:当前正式统一账务处理入口,继续承接调账、退款、冲正、坏账等在线处理场景。
- `IF-REV-006`:与收费核销状态联动,确保调账后账单状态、核销状态和结果回写一致。
- 查询类对象后续可按“业务对象查询接口”和“历史只读 / 投影查询接口”分层拆分,但当前正式文档暂不新增专属接口编号。
- 特殊开账 / 特账继续挂接 `IF-REV-005` 与开账主模型边界承接,不在本节单独定义新的在线处理入口。
### 落地边界
- **已落地**:营业账主明细、操作日志、价格/方案相关重算支撑。
- **部分落地**:精细化账务对象更多表现为流程与场景,并未在 backend 中全部体现为独立表族
- **文档先行**:特账、退款账、跨周期水量等对象保留业务语义,不宣称为已实现独立表
- **已落地**:营业账主明细、支付交易校验、操作日志、价格/方案相关重算支撑,已构成当前统一账务处理骨架
- **部分落地**:精细化账务对象目前更多表现为处理场景、查询口径和留痕规则,尚未在 backend 中全部体现为独立表族或专属接口
- **目标设计**后续将按正式业务对象、审批引用、历史映射和查询投影逐步回写数据库设计、接口设计和总体设计IC 卡账务继续固定为历史只读 + 映射层,不纳入在线主写范围
<a id="mod-rev-005"></a>

View File

@ -1078,14 +1078,18 @@ retrieval_priority: P0
| 字段名 | 说明 |
| :--- | :--- |
| `id` | 主键 |
| `code` | 账单编号 |
| `code` | 账单编号 / 账务主单编号 |
| `cust_id` | 客户 ID |
| `record_id` | 抄表记录或开账来源 ID |
| `bill_period` | 账单期间 |
| `total_amount` | 账单总金额 |
| `charge_status` | 收费状态 |
| `charge_status` | 收费状态 / 账务处理结果状态关注字段 |
| `due_date` | 应缴日期 |
> 当前真实表事实:`biz_charge` 仍是当前 backend 已落地的营业账主表。
>
> `RWB-02` 目标承接口径:除继续承接 REV-002 开账结果外,`biz_charge` 同时作为 REV-004 全量账务领域的统一骨架主对象,负责承接账单主状态、账务处理主结果、来源单据引用、账单结果回写和查询主入口;对尚未独立落表的目标业务对象,本节仅定义“承接边界 / 待补字段组 / 历史映射要求”,不误写为已存在独立物理表。
### biz_charge_detail (营业账明细表)
| 字段名 | 说明 |
| :--- | :--- |
@ -1096,6 +1100,10 @@ retrieval_priority: P0
| `unit_price` | 单价 |
| `detail_amount` | 明细金额 |
> 当前真实表事实:`biz_charge_detail` 仍是当前 backend 已落地的营业账明细表。
>
> `RWB-02` 目标承接口径:`biz_charge_detail` 除继续承接账单费用明细外,还应承接价差调整、违约金减免、分账调整、特殊开账 / 特账等费用差异结果的明细表达;当前数据库专项只冻结“由明细层承接差异结果”的方向,不在本轮直接展开独立实体表 DDL。
#### REV-002 账单生成承接口径
| 对象 | 正式口径 | 承接说明 |
@ -1112,8 +1120,23 @@ retrieval_priority: P0
> 当前 backend 证据:`ChargeServiceImpl.generateSingleChargeWithCache` 成功路径已执行 `chargeMapper.insert(charge)``chargeDetailService.insertChargeDetail(detail)``updateReadingDataCheckState(readingDataId, 1)`,说明现有实现已能把按 `readingDataIds` 复核/开账的结果落入 `biz_charge``biz_charge_detail`
>
> 当前承接缺口:接口层返回仍为成功条数字符串,失败阻断主要依赖日志与布尔值,且仅支持 `ACTUAL_USAGE` 结算方式;`biz_charge` / `biz_charge_detail` 的主明细结果、失败对象范围和结构化原因尚未提升为正式 `IF-REV-005` 契约返回。
>
> REV-004 承接口径:水量调整、金额调整、退款、冲正、坏账申请统一以 `biz_charge``biz_charge_detail` 作为账单主明细承接对象;当前数据库主文档不新增独立账务细表来承接一期场景。
#### REV-004 全量账务数据库承接口径
| 目标对象 | 当前数据库口径 | 目标承接方式 | 当前写法约束 |
| :--- | :--- | :--- | :--- |
| `PrepaidRefund` | 当前作为统一账务处理场景 | `biz_charge` / `biz_charge_detail` + 留痕 + 目标结果对象 | 当前不宣称已存在独立退款主表 |
| `RedinkRecord` | 当前以账务处理场景 + 历史查询表达 | 账单结果 + 原交易校验 + 历史只读查询 | 当前不宣称已落地独立红冲表族 |
| `WrittenoffAdjust` | 当前作为已收费后修正场景 | 统一骨架 + 差异结果 + 留痕 | 当前不下沉独立 DDL |
| `PriceDiffAdjust` | 当前作为重算差额场景 | 统一骨架 + 价格规则来源 + 明细差异表达 | 当前先冻结承接方向 |
| `LateFeeReduce` | 当前作为滞纳金修正场景 | 统一骨架 + 费用组成差异 + 审批留痕 | 当前先写待补字段关注点 |
| `BadDebtRecord` | 当前作为坏账申请场景 | 统一骨架 + 审批引用 + 历史追溯 | 当前不误写为已存在坏账主表 |
| `SplitAdjust` | 当前作为费用重分摊场景 | 统一骨架 + 明细差异 + 策略类型字段组 | 当前只冻结设计方向 |
| 特殊开账 / 特账 | 当前由开账同模型承接 | `biz_charge` / `biz_charge_detail` 同模型承接 | 不新增平行“特账表” |
| `RefundBill` | 当前作为退款结果语义 | 退款结果对象 + 历史映射 / 查询出口 | 当前不作为独立提交表宣称已落地 |
| `CrossCycleWaterRecord` | 当前作为重算支撑语义 | 账单重算基础数据对象 + 查询支撑 | 当前不写成强流程主表 |
| `AccountingWorkflowRef` | 当前数据库主文档未单独命名 | 目标审批引用对象 | 当前仅建立数据库承接边界和待补字段关注点 |
| `AccountingLegacyMapping` | 当前数据库主文档未单独命名 | 目标历史映射对象 | 当前仅建立历史映射承接边界和待补字段关注点 |
### biz_collection / biz_withholding (托收与代扣主表)
| 表名 | 关键字段 | 说明 |
@ -1121,17 +1144,18 @@ retrieval_priority: P0
| `biz_collection` | `code`, `cust_id`, `charge_id`, `collection_status` | 托收主表 |
| `biz_withholding` | `code`, `cust_id`, `charge_id`, `withholding_status` | 代扣主表 |
### biz_invoice / biz_invoice_taxrate (发票主表与税率表)
### biz_invoice / biz_invoice_record / biz_invoice_taxrate开票配置表 / 发票记录表 / 税率表)
| 表名 | 关键字段 | 说明 |
| :--- | :--- | :--- |
| `biz_invoice` | `code`, `cust_id`, `charge_id`, `invoice_status`, `invoice_amount` | 发票主表,统一承接申请单号、账单关联、受理号、开票结果与电子票地址等核心状态 |
| `biz_invoice` | `supplier`, `limit`, `auto_invoice`, `invoice_type`, `status` | 开票配置表(供应商、限额、自动开票开关)|
| `biz_invoice_record` | `application_no`, `cust_id`, `invoice_status`, `invoice_amount` | 发票申请/记录主表,统一承接申请单号、账单关联、受理号、开票结果与电子票地址等核心状态 |
| `biz_invoice_taxrate` | `tax_code`, `tax_name`, `tax_rate`, `status` | 税率基础配置 |
#### REV-005 发票承接口径
| 对象 | 已有字段 | 待补字段 | 仅快照/历史只读字段 |
| :--- | :--- | :--- | :--- |
| `biz_invoice` 发票申请/结果主对象 | `code``cust_id``charge_id``invoice_status``invoice_amount` | `application_no``sys_request_no``invoice_type``invoice_title``tax_no``email``mobile``source_channel``fail_reason``invoice_code``invoice_number``file_url``last_try_time``next_try_time``try_count``push_status``charge_ids_snapshot``charge_bind_status`、作废原因/备注、红冲原因/备注、原票/红票关联标识、作废/红冲申请来源、补偿查询/结果回写上下文 | 旧开票批次号、旧配置版本号、旧平台扩展回执 |
| `biz_invoice_record` 发票申请/结果主对象 | `code``cust_id``charge_id``invoice_status``invoice_amount` | `application_no``sys_request_no``invoice_type``invoice_title``tax_no``email``mobile``source_channel``fail_reason``invoice_code``invoice_number``file_url``last_try_time``next_try_time``try_count``push_status``charge_ids_snapshot``charge_bind_status`、作废原因/备注、红冲原因/备注、原票/红票关联标识、作废/红冲申请来源、补偿查询/结果回写上下文 | 旧开票批次号、旧配置版本号、旧平台扩展回执 |
| `biz_cust_invoice` 客户开票信息 | `cust_id``invoice_title``tax_no``email``mobile` | 企业/个人抬头类型、默认推送方式等扩展属性按后续实现补齐 | 旧抬头版本、历史修改快照 |
| `biz_invoice_taxrate` 税率配置 | `tax_code``tax_name``tax_rate``status` | 税率生效区间、适用票种范围等扩展控制字段 | 旧税率版本快照 |
| 账单-发票关系快照 | 当前主模型通过 `biz_invoice``biz_charge*` 关联承接 | `charge_ids_snapshot`、账单集合来源、客户侧身份匹配结果、操作留痕标识 | 旧营业账开票关系表、旧发票明细表 |
@ -1142,9 +1166,11 @@ retrieval_priority: P0
| `biz_operat_log` | `biz_type`, `biz_id`, `operate_user`, `operate_time` | 业务操作主日志 |
| `biz_operat_log_detail` | `log_id`, `field_name`, `before_value`, `after_value` | 字段级变更留痕 |
> REV-004 留痕口径:`biz_operat_log*` 统一承接账务处理的一期留痕,至少覆盖处理类型、目标账单、原交易引用、处理前后差异、原因说明、附件依据与操作人
> REV-004 留痕口径:`biz_operat_log*` 继续作为当前正式留痕主入口,但承接范围从“一期账务处理留痕”扩展为“全量账务操作留痕 + 审批痕迹 + 原单据关联 + 历史追溯”
>
> 边界说明:旧数据字典中的“跨周期水量、特账、红冲、已销调整、呆坏账、实时收费日志”等对象,在当前 backend 范围内未全部识别到独立实体表,数据库专项中统一按业务处理场景描述,不误写为已明确落地的真实表。
> 数据库关注点:至少覆盖处理类型、目标账单、原交易引用、处理前后差异、原因说明、附件依据、审批状态、审批引用、操作人和操作时间。
>
> 边界说明:旧数据字典中的“跨周期水量、特账、红冲、已销调整、呆坏账、实时收费日志”等对象,在当前 backend 范围内未全部识别到独立实体表;数据库专项中统一按“在线骨架承接 / 历史只读保留 / 映射对象追溯”三层口径描述,不误写为已明确落地的真实表。
### 旧系统历史台账迁移与只读查询口径
@ -1153,22 +1179,32 @@ retrieval_priority: P0
| 旧对象/旧菜单 | 当前主口径 | 承接方式 | 数据策略 |
| :--- | :--- | :--- | :--- |
| 开账记录、特殊开账 | `biz_charge``biz_charge_detail``biz_operat_log*` | 统一纳入营业账主表与明细表,特殊开账按来源类型、业务类型、依据说明留痕,不单设平行“特殊开账表” | 在线保留 |
| 预存退款、已销调整、价差调整、分账调整、违约金减免、呆坏账新发生业务 | `biz_charge*` + 留痕骨架 + 目标对象语义 | 新发生业务优先走统一骨架承接,数据库文档保留目标对象命名和待补字段组,但不误写为已全部落地物理表 | 在线保留 |
| 柜台收费、实时收费、代收代扣 | `biz_collection``biz_withholding``bk_transaction*``bk_withholding_*` | 统一纳入收费主模型与渠道交易模型,柜台班结/实时收费日志按交易结果和操作留痕归并 | 在线保留 |
| 发票申请、开票结果、票据税率 | `biz_invoice`、`biz_invoice_taxrate``biz_cust_invoice` | 统一纳入发票主表、税率表和客户开票信息,不为旧“营业账开票表”机械复制新在线表 | 在线保留 |
| 发票申请、开票结果、票据税率 | `biz_invoice_record`(业务记录)、`biz_invoice`(开票配置)`biz_invoice_taxrate``biz_cust_invoice` | 统一纳入发票主表、税率表和客户开票信息,不为旧“营业账开票表”机械复制新在线表 | 在线保留 |
| 微网厅业务字段、页面配置、微信参数 | `biz_business_types``biz_business_datas``biz_page_settings*``biz_parameter_settings``sys_wechat_app_settings` | 统一归并为业务类型、页面配置和渠道参数模型 | 在线保留 |
| 办理附件、电子档案 | `biz_content``biz_content_attach` | 当前新增与迁移后新增资料统一按资料主表与附件表承接 | 在线保留 |
#### 历史只读保留范围
#### 查询投影 / 历史映射保留范围
| 历史对象 | 当前正式口径 | 保留策略 | 查询要求 |
| :--- | :--- | :--- | :--- |
| 红冲记录、红冲原因、红冲前后账务快照 | 账单状态 + 账务处理场景 + 操作留痕 | 保留历史只读,不强制拆为新主库独立实体表 | 至少支持原单号、红冲单号、金额、原因、经办人、时间查询 |
| 预存退款、已销调整、价差调整、分账调整、违约金减免、呆坏账明细 | `REV-004` 账务处理业务场景 | 旧细粒度台账以历史只读方式保留;新发生业务由流程与日志承接 | 至少支持汇总、明细、状态、审批结果和关联账单查询 |
| 红冲记录、红冲原因、红冲前后账务快照 | 账单状态 + 账务处理场景 + 操作留痕 | 保留历史只读,不强制拆为新主库独立实体表;后续可作为 `RedinkRecord` 查询投影出口 | 至少支持原单号、红冲单号、金额、原因、经办人、时间查询 |
| 预存退款、已销调整、价差调整、分账调整、违约金减免、呆坏账明细 | `REV-004` 账务处理业务场景 | 旧细粒度台账以历史只读方式保留;新发生业务由统一骨架、目标对象语义和留痕承接 | 至少支持汇总、明细、状态、审批结果和关联账单查询 |
| 退款账结果、跨周期水量、疑难重笔账 | 结果对象 / 基础支撑对象 / 历史问题单据 | 保留为查询投影或历史只读出口,不强行落为在线主写对象 | 至少支持结果状态、来源单据、时间、关联账单查询 |
| 柜台结账、打印记录、补打记录 | 收费结果 + 打印/操作留痕 | 旧查询菜单作为历史只读能力保留,不单独建设新结账台账表 | 至少支持班次、营业所、柜员、打印类型、结账状态查询 |
| 发票明细、营业账开票关系、开票配置快照 | 发票主模型 + 历史关系快照 | 旧细表和配置快照按历史只读保留;新在线模型只保留开票必需主数据 | 至少支持发票号、账单号、开票批次、配置版本和结果状态查询 |
| 催缴记录、停水记录、预存短信记录 | 催缴/停复水/消息联动业务场景 | 旧记录菜单按历史只读保留,与 `IF-REV-013` 的任务结果、通知链路和工单处置引用分层承接,不新增同名在线主表 | 至少支持客户、账期、催缴方式、执行结果、发送对象、发送时间、关联账单、处置引用查询 |
| IC 卡账务表族 | 当前冻结为历史只读 + 映射层 | 保留 `history-readonly + mapping-only`,不进入在线主写数据库口径 | 至少支持卡号、客户、金额、时间、旧单据标识查询 |
| 水表检定、领用、出库、退库、报废单据 | `METER-003` 生命周期场景 | 当前在线主表承接水表状态,旧单据与检定证书按历史只读保留 | 至少支持表号、仓库、单据类型、检定结论、证书编号查询 |
#### 目标引用对象与待补字段关注点
| 目标对象 | 当前数据库状态 | 待补字段关注点 | 说明 |
| :--- | :--- | :--- | :--- |
| `AccountingWorkflowRef` | 当前主文档未单列真实表 | `accounting_case_id``workflow_type``approval_required``approval_status``legacy_task_id``legacy_step_id``legacy_flow_remark` | 当前只建立数据库专题锚点,不宣称已存在真实表 |
| `AccountingLegacyMapping` | 当前主文档未单列真实表 | `legacy_object_type``legacy_object_id``new_object_type``new_object_id``mapping_mode``migration_batch_no``trace_status` | 当前只建立历史映射承接边界,不宣称已存在真实表 |
#### 迁移验收最低对账口径
| 对账主题 | 最低核对维度 | 主口径来源 | 验收要求 |
@ -1177,13 +1213,14 @@ retrieval_priority: P0
| 缴费记录 | 收费笔数、实收金额、渠道、核销状态 | `biz_collection``bk_transaction*` | 支持按渠道、日期、营业所汇总比对 |
| 发票记录 | 开票笔数、金额、状态、票据类型 | `biz_invoice*` + 历史开票关系 | 支持按发票状态、开票日期比对 |
| 红冲与账务调整 | 调整笔数、调整金额、处理结果 | 账务处理结果 + 历史台账 | 支持汇总与单据级差异定位 |
| 退款与坏账结果 | 结果笔数、结果金额、审批状态、关联账单 | 统一骨架结果 + 历史只读台账 | 支持按对象类型、状态、账期、时间定位 |
| 催缴与停复水 | 通知笔数、执行结果、停复水状态 | `IF-REV-013` 任务结果 + 历史记录 | 支持按账期、客户、执行状态、处置引用比对 |
| 电子档案 | 附件数、附件类型、关联业务单数 | `biz_content_attach` + 历史档案目录 | 支持按业务类型、上传时间、来源系统比对 |
#### 设计约束
- 不以“旧菜单一项对应一张新表”为原则,优先复用当前已确认的 `biz_*``bk_*` 在线主模型。
- 对 backend 当前未识别到独立实体表的旧细粒度台账,仅写为“历史只读查询对象”,不误写为“已存在新在线表”。
- 对 backend 当前未识别到独立实体表的旧细粒度台账,仅写为“历史只读查询对象”或“目标对象 / 待补字段关注点”,不误写为“已存在新在线表”。
- 历史只读对象必须保留原系统关键标识(原单号、原客户号、原批次号或原附件标识)以支撑迁移验收与问题追溯。
- 涉及历史附件、影像、高拍仪资料时,正式数据库设计只约束“资料元数据 + 文件引用”口径,不在本轮臆造新的文件表族。
- `REV-006` 的正式结果状态按 `PENDING``SUCCESS``FAIL``MANUAL_VERIFIED` 四态统一,数据库设计仅约束查询与追溯口径,不反推为已存在独立催缴结果主表。
@ -1536,7 +1573,7 @@ retrieval_priority: P0
| start_time | timestamp(6) | Y | | 开始时间 |
| end_time | timestamp(6) | Y | | 结束时间 |
| work_duration | int4 | Y | 0 | 工作时长(分钟) |
| work_status | int2 | N | 0 | 工单状态0-待处理1-处理中2-已完成 |
| work_status | int2 | N | 0 | 工单状态0-未处理(工单创建,待审核/待处理1-已审核(审核通过/无需审批待完成2-已完成处理成功且已回写完成3-已撤销(工单已撤销) |
| work_result | varchar(500) | Y | | 工作结果 |
| completion_photos | text | Y | | 完成照片 |
| customer_signature | varchar(500) | Y | | 客户签名 |
@ -1991,6 +2028,16 @@ CREATE INDEX idx_bk_withholding_batch_date ON bk_withholding_batch(batch_date, b
CREATE INDEX idx_bk_reconcile_batch_bill_date ON bk_reconcile_batch(bill_date, reconcile_status);
```
> `RWB-02` 索引关注点:在保持上述现有真实索引口径不变的前提下,后续应重点关注以下账务查询路径:
>
> - 账务主单 / 账单结果查询:`cust_id + bill_period``charge_status + due_date``code + tenant_id`
> - 账务明细差异查询:`charge_id + cost_component_code`,必要时补“差异类型 / 结果类型”类字段索引
> - 原单据 / 原交易追溯:`biz_order_no``trade_no`、原单号 / 原申请单号 / 原结果单号
> - 审批引用查询:后续若引入 `AccountingWorkflowRef`,应优先关注 `accounting_case_id``approval_status``workflow_type`
> - 历史映射查询:后续若引入 `AccountingLegacyMapping`,应优先关注 `legacy_object_type + legacy_object_id``new_object_type + new_object_id``migration_batch_no`
>
> 当前数据库文档仅冻结索引关注点,不在本轮直接新增未落地表的 SQL 索引语句。
## 分区表设计
### 日志表分区策略
@ -2020,6 +2067,12 @@ CREATE TABLE biz_price_adjustment_history_y2024 PARTITION OF biz_price_adjustmen
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
```
> `RWB-02` 历史数据关注点:对 REV-004 全量账务领域,后续应把“历史只读台账、查询投影、映射关系”与“在线主写骨架”分开管理。
>
> - 历史只读对象红冲记录、旧退款账、旧分账调整、旧价差调整、IC 卡账务等按历史查询保留,不并入在线主写分区策略
> - 查询投影对象:实时收费、对账日志、结果投影可按时间或账期做轻量分区/归档优化
> - 映射对象:若后续引入 `AccountingLegacyMapping`,优先按迁移批次号和时间做归档/检索设计
## 查询优化建议
### 多租户查询优化
@ -2058,6 +2111,12 @@ SELECT * FROM dept_tree;
- **归档周期**: 每月执行一次归档作业
- **存储方式**: 使用列式存储优化查询性能
### REV-004 历史账务归档补充
- **在线骨架对象**`biz_charge``biz_charge_detail``biz_operat_log*` 保留当前在线口径,优先服务当前账务处理、审计与查询。
- **历史只读对象**旧红冲、旧退款账、旧价差调整、旧分账调整、IC 卡账务等优先走历史只读归档,不反向要求在线主表一比一复刻。
- **查询投影对象**:实时收费、对账日志、账务结果投影可按账期或时间做归档分层,确保老数据查询不拖累在线主写。
- **映射对象**:后续若落地 `AccountingLegacyMapping`,应按迁移批次、对象类型、时间维度设计归档与抽检策略。
### 历史数据处理
```sql
-- 创建归档表

View File

@ -442,14 +442,16 @@ retrieval_priority: P0
| 归属模块 | REV-004 |
| 请求方式 | POST |
| 请求路径 | `/admin-api/revenue/accounting/adjust` |
| 功能描述 | 发起水量调整、金额调整、退款、冲正、坏账申请五类账务处理,并统一返回处理结果、审批边界与账单回写状态 |
| 功能描述 | 当前继续作为 REV-004 统一账务处理入口,统一承接账单修正、退款/冲正、坏账申请以及全量账务对象的通用处理申请,并返回处理结果、审批边界与账单回写状态 |
| 核心表 | `biz_charge``biz_charge_detail``biz_operat_log``bk_transaction*` |
边界约束:
- 一期仅覆盖 `USAGE``AMOUNT``REFUND``REVERSE``BAD_DEBT` 五类 `adjustType`
- 退款、冲正必须提供 `sourceTradeNo` 并完成原交易校验;其他场景不得误用支付流水替代业务依据。
- 审批相关内容仅保留 `approvalRequired``PENDING_APPROVAL` 与边界说明,不展开 BPM 节点与审批回写实现细节。
- `resultStatus``writeBackStatus` 必须分离表达,前者表示处理结论,后者表示账单状态回写结论。
- 当前正式事实仍以 `IF-REV-007` 为统一入口,不直接新增一整套新的正式专属接口编号族。
- 当前统一入口需能够覆盖 `PrepaidRefund``RedinkRecord``WrittenoffAdjust``PriceDiffAdjust``LateFeeReduce``BadDebtRecord``SplitAdjust`、特殊开账 / 特账、`RefundBill``CrossCycleWaterRecord` 的统一提交或统一受理边界;其中结果对象和基础支撑对象可仅保留受理/查询语义。
- 退款、红冲、冲正等资金回退相关场景必须提供 `sourceTradeNo` 并完成原交易校验;其他场景不得误用支付流水替代业务依据。
- 审批相关内容当前仍仅保留 `approvalRequired``approvalStatus` 与边界说明,不展开 BPM 节点、审批回写 API 与流程引擎实现细节。
- `resultStatus``writeBackStatus` 必须分离表达,前者表示处理结论,后者表示账单状态回写结论;后续若拆专属接口,也不得破坏该双状态语义。
- 专属接口方向当前只允许写成“后续建议拆分方向”,不得误写为当前 backend 已完整落地能力。
### IF-REV-008 发票申请接口
@ -833,12 +835,15 @@ retrieval_priority: P0
| 字段 | 类型 | 必填 | 说明 | 主要来源/去向 |
|------|------|------|------|---------------|
| chargeId | Long | 是 | 目标账单 ID | `biz_charge.id` |
| adjustType | String | 是 | 调整类型:`USAGE``AMOUNT``REFUND``REVERSE``BAD_DEBT` | 业务类型 |
| objectType | String | 是 | 账务对象类型:`PREPAID_REFUND``REDINK_RECORD``WRITTENOFF_ADJUST``PRICE_DIFF_ADJUST``LATE_FEE_REDUCE``BAD_DEBT_RECORD``SPLIT_ADJUST``SPECIAL_BILLING``REFUND_BILL``CROSS_CYCLE_WATER` | 统一对象语义 |
| adjustType | String | 否 | 当前统一入口处理类型:`USAGE``AMOUNT``REFUND``REVERSE``BAD_DEBT` 等 | 业务类型 / 兼容字段 |
| adjustAmount | Decimal | 否 | 调整金额 | `biz_charge_detail` / 业务计算 |
| adjustUsage | Decimal | 否 | 调整水量 | 业务计算 |
| sourceTradeNo | String | 否 | 原交易流水号,退款/冲正场景使用 | `bk_transaction.trade_no` |
| sourceTradeNo | String | 否 | 原交易流水号,退款/红冲/冲正场景使用 | `bk_transaction.trade_no` |
| relatedBizNo | String | 否 | 原业务单号/结果单号/关联单号 | 历史追溯 / 业务流水 |
| reasonCode | String | 是 | 调整原因编码 | 业务字典 |
| remark | String | 否 | 调整说明 | `biz_operat_log.remark` |
| approvalRequired | Boolean | 否 | 是否触发审批能力位 | 审批边界判断 |
| remark | String | 否 | 处理说明 | `biz_operat_log.remark` |
| attachmentList | Array<String> | 否 | 依据附件 | 附件系统 |
| operatorId | Long | 是 | 操作人 ID | 操作上下文 |
@ -846,13 +851,22 @@ retrieval_priority: P0
| 字段 | 类型 | 说明 | 主要来源 |
|------|------|------|----------|
| adjustmentNo | String | 调整业务编号 | 业务流水 |
| adjustmentNo | String | 调整业务编号 / 统一处理流水号 | 业务流水 |
| chargeId | Long | 目标账单 ID | `biz_charge.id` |
| resultStatus | String | 处理状态:`SUCCESS``PENDING_APPROVAL``FAIL` | 业务状态 |
| objectType | String | 实际承接的账务对象类型 | 业务状态 |
| resultStatus | String | 处理状态:`SUCCESS``PENDING_APPROVAL``FAIL``PARTIAL_SUCCESS` 等 | 业务状态 |
| writeBackStatus | String | 账单回写状态 | 业务状态 |
| approvalRequired | Boolean | 是否进入审批 | 流程判断 |
| approvalStatus | String | 审批状态:`PENDING_APPROVAL``APPROVED``REJECTED` 等 | 审批边界 |
| resultObjectNo | String | 结果对象编号/结果单号 | 业务流水 / 结果对象 |
| msg | String | 处理说明 | 返回消息 |
> 参数口径补充:
>
> - `objectType` 用于统一表达全量账务对象语义;当前若 backend 尚未完整开放所有对象类型,可按“已开放对象 + 后续扩展对象”分批实现,但正式接口文档已固定该统一表达方向。
> - `adjustType` 当前保留作为统一入口兼容字段,后续若拆专属接口,可逐步从“处理类型”过渡到“对象类型 + 结果语义”表达。
> - `approvalRequired``approvalStatus` 仅用于承接审批能力位和审批状态边界,不代表当前已落地完整 BPM API。
### IF-REV-008 发票申请接口
#### 请求参数
@ -1543,6 +1557,7 @@ sequenceDiagram
| 客户与账户 | `biz_cust``biz_account``biz_cust_contact` | 用于客户查询、账户绑定、资料维护 |
| 客户扩展关系 | `biz_cust_meter``biz_cust_invoice``biz_cust_app_binds``biz_cust_collection_rel``biz_cust_withholding_rel` | 用于客户关联对象查询与服务协同 |
| 抄表与开账 | `biz_meter_book``biz_meter_read``biz_reading_data``biz_last_reading``biz_charge``biz_charge_detail` | 用于抄表任务、账单生成、费用明细查询 |
| 账务处理与结果对象 | `biz_charge``biz_charge_detail``biz_operat_log``bk_transaction*`、目标对象 `AccountingWorkflowRef``AccountingLegacyMapping` | 用于 REV-004 统一处理入口、审批边界、结果对象表达与历史追溯 |
| 价格与参数 | `biz_price_*``biz_cost_component``biz_parameter_settings``biz_page_settings*` | 用于价格模板、业务参数、页面配置 |
| 收费与票据 | `biz_collection``biz_withholding``biz_invoice``biz_invoice_taxrate` | 用于收费、代扣、发票申请与回写 |
| 办理与资料 | `biz_process*``biz_business_datas``biz_content*` | 用于业务办理与进度跟踪 |
@ -1552,7 +1567,8 @@ sequenceDiagram
1. 不再使用旧稿中的 `customer_*``water_*``billing_*``thirdpay_*``service_*` 作为 SYS-002 正式接口数据口径。
2. 若历史资料中仍存在旧命名,仅作为来源参考,不作为正式交付口径。
3. 对 backend 中尚未明确存在独立实体表的对象,例如部分精细账务台账、红冲明细、价差调整明细等,本文仅描述为业务处理场景,不强写为既有独立接口对象。
3. 对 backend 中尚未明确存在独立实体表的对象,例如部分精细账务台账、红冲明细、价差调整明细等,本文可正式命名其“接口对象语义”,但不误写为当前已完整落地的独立专属接口。
4. `AccountingWorkflowRef``AccountingLegacyMapping` 当前属于目标接口对象 / 目标数据对象命名,用于表达审批引用和历史映射边界,不表示已存在独立对外接口编号。
## 跨系统报文映射表
@ -1731,16 +1747,30 @@ sequenceDiagram
- 历史查询接口一律只读,不承担迁移修正、补写或状态变更职责。
- 不为迁移场景发明新的独立接口编号体系,统一挂靠既有 `IF-REV-*``IF-CS-*``IF-METER-*` 接口族扩展查询能力。
- 对 backend 当前未识别到独立实体表的旧细粒度对象,仅要求接口返回“历史摘要 + 原始标识 + 状态映射”,不强行承诺独立在线业务对象。
- 全量账务对象的查询出口按“两层口径”组织:一类为业务对象查询出口,一类为历史只读 / 投影查询出口。
- 对 backend 当前未识别到独立实体表的旧细粒度对象,接口至少返回“对象语义 + 历史摘要 + 原始标识 + 状态映射”,不强行承诺独立在线业务对象。
- 迁移验收接口必须同时支持“汇总对账”和“明细追溯”两类能力,避免只能看总数、无法定位差异。
### 全量账务对象查询出口分层
| 对象类型 | 当前挂靠接口 | 查询层级 | 最低返回要求 | 说明 |
|---------|--------------|----------|--------------|------|
| `PrepaidRefund``WrittenoffAdjust``PriceDiffAdjust``LateFeeReduce``BadDebtRecord``SplitAdjust` | `IF-REV-007` | 业务对象查询出口 | 对象类型、业务编号、关联账单、金额/水量差异、状态、审批状态、处理时间 | 当前继续挂统一入口查询语义,后续可拆专属查询接口 |
| `RedinkRecord` | `IF-REV-007` | 业务对象查询出口 + 历史只读出口 | 红冲单号、原单号、金额、原因、审批状态、处理时间 | 当前由统一入口和历史查询共同承接 |
| 特殊开账 / 特账 | `IF-REV-005``IF-CS-002` | 账单结果 / 历史账单查询出口 | 来源类型、费用类型、账单号、账期、金额、状态 | 继续挂开账主模型与历史账单查询 |
| `RefundBill` | `IF-REV-007``IF-CS-002` | 结果对象查询出口 | 结果单号、退款申请号、支付结果、关联账单、状态 | 当前不单列提交接口,但应保留查询出口 |
| `CrossCycleWaterRecord` | `IF-REV-007``IF-CS-002` | 基础支撑 / 历史查询出口 | 来源账单、跨期水量、账期范围、累计口径、关联场景 | 当前不作为强流程提交接口,仅保留查询语义 |
| 实时收费、对账日志、柜台结账 | `IF-CS-002``IF-REV-011` | 历史只读 / 投影查询出口 | 原流水号、渠道、金额、时间、柜员/营业所、状态 | 查询类对象不转为在线主写接口 |
| IC 卡账务表族 | 历史查询扩展接口 | 历史只读 / 映射查询出口 | 卡号、客户号、金额、时间、旧单据标识、映射状态 | 当前仅保留历史只读 + 映射层查询 |
### 最小历史查询保留集
| 查询主题 | 挂靠接口族 | 主要数据来源 | 最低返回要求 | 说明 |
|---------|------------|--------------|--------------|------|
| 历史账单、特殊开账 | `IF-CS-002``IF-REV-005` | `biz_charge*` + 历史账单来源 | 原账单号、新账单号、客户号、账期、金额、状态、来源类型 | 支撑账单迁移核查与客户侧历史查询 |
| 缴费记录、柜台结账、实时收费 | `IF-CS-002``IF-REV-006``IF-REV-011` | `biz_collection``bk_transaction*` + 历史收费记录 | 原流水号、渠道、实收金额、收费时间、柜员/营业所、核销状态 | 支撑渠道对账、柜面核查和历史收据核对 |
| 红冲与账务调整 | `IF-REV-007` | 操作留痕、流程结果 + 历史调整台账 | 调整类型、关联原单号、调整金额、原因、审批状态、处理时间 | 支撑预存退款、已销调整、价差调整、分账调整、呆坏账查询 |
| 红冲与账务调整 | `IF-REV-007` | 操作留痕、流程结果 + 历史调整台账 | 对象类型、关联原单号、调整金额、原因、审批状态、处理时间 | 支撑预存退款、已销调整、价差调整、分账调整、呆坏账查询 |
| 退款结果与跨周期支撑对象 | `IF-REV-007``IF-CS-002` | 统一结果对象 + 历史只读台账 | 结果单号、来源单号、关联账单、结果状态、时间 | 支撑退款账、跨周期水量、疑难账追溯 |
| 发票与开票关系 | `IF-REV-008``IF-CS-004` | `biz_invoice*` + 历史开票关系快照 | 发票号、申请单号、关联账单、票据状态、票据类型、文件地址 | 支撑发票结果核查与历史补打定位 |
| 催缴、停复水、预存短信 | `IF-REV-013``IF-METER-002` | 通知结果、流程工单 + 历史催缴记录 | 客户号、账期、催缴方式、消息状态、停复水状态、执行人、执行时间、处置引用 | 支撑催缴处置闭环核查 |
| 业务进度与电子档案 | `IF-CS-006` | `biz_process*``biz_content*` + 历史附件目录 | 业务单号、节点状态、处理轨迹、附件数量、附件元数据 | 支撑微网厅与柜台办理进度、电子档案查询 |
@ -1753,6 +1783,7 @@ sequenceDiagram
|---------|--------------|--------------|----------|
| 开账迁移核对 | `IF-REV-005``IF-CS-002` | 账期、营业所、客户类型、账单状态 | 同时提供汇总金额/笔数与可追溯明细清单 |
| 收费迁移核对 | `IF-REV-006``IF-REV-011``IF-CS-002` | 日期、渠道、营业所、收费状态 | 同时提供实收金额、流水数量和异常流水列表 |
| 账务对象迁移核对 | `IF-REV-007` | 对象类型、状态、审批状态、账期、时间 | 同时提供对象汇总、结果状态和单据级明细定位 |
| 发票迁移核对 | `IF-REV-008``IF-CS-004` | 开票日期、票据类型、开票状态 | 同时提供开票汇总、失败原因和票据明细定位 |
| 催缴与停复水核对 | `IF-REV-013``IF-METER-002` | 账期、催缴方式、执行状态、处理人 | 同时提供任务统计、执行明细与处置引用 |
| 业务办理与档案核对 | `IF-CS-006` | 业务类型、流程状态、附件类型、创建时间 | 同时提供流程轨迹、附件元数据和缺失标记 |
@ -1761,9 +1792,10 @@ sequenceDiagram
### 接口约束补充
- 历史查询结果应优先返回原系统标识与新系统标识的映射关系,例如原单号、原批次号、原附件标识、当前业务单号。
- 明细查询接口应支持按客户号、证件号、手机号、表号、账期、营业所、渠道、业务单号等组合检索,满足迁移验收定位需求。
- 明细查询接口应支持按客户号、证件号、手机号、表号、账期、营业所、渠道、业务单号、对象类型等组合检索,满足迁移验收定位需求。
- 若历史附件不直接由当前业务服务托管,接口至少返回附件元数据、来源系统、文件引用地址和可访问状态。
- 历史查询接口的导出能力属于查询扩展能力,不应与在线交易接口共用“状态变更”动作。
- 当前若尚未拆出专属账务对象查询接口,应在统一入口或查询扩展接口中保留 `objectType` 维度,避免后续迁移验收无法区分对象类别。
<a id="sec-status"></a>
## 实现状态说明
@ -1775,6 +1807,7 @@ sequenceDiagram
- 客户与账户查询、维护接口
- 抄表任务与抄表数据接口
- 账单生成与收费核销接口
- `IF-REV-007` 作为当前统一账务处理入口的正式接口位置
- 银行代收、代扣、交易回调、对账、结算接口
- 发票申请与票据状态回写接口的业务侧支撑
- 客户渠道的账户绑定、查询、缴费、业务办理进度接口
@ -1783,7 +1816,8 @@ sequenceDiagram
以下接口域已有主线支撑,但部分细粒度对象仍需结合后续实现继续核实:
- 账务调整、退款、坏账、冲正类精细接口
- `IF-REV-007` 对全量账务对象的完整对象化表达
- 账务调整、退款、坏账、冲正、红冲、价差调整、分账调整、违约金减免等精细对象查询能力
- 催缴管理中针对不同通知策略和停复水联动的细分接口
- 发票补开等扩展票据后处理接口
@ -1791,6 +1825,8 @@ sequenceDiagram
以下内容可保留设计说明,但当前不应直接表述为已完全落地:
- 后续专属账务对象接口方向(例如预存退款、红冲、价差调整、分账调整等专属接口)
- `AccountingWorkflowRef``AccountingLegacyMapping` 等目标对象在接口层的独立实现形态
- 历史数据字典中大量细粒度账务台账接口
- 未在 backend 当前扫描范围内明确识别到的独立业务对象接口
- 特定银行或地方平台的专有报文细节

View File

@ -0,0 +1,721 @@
---
doc_id: DT-12-REV
doc_role: module_body
authority: secondary
scope: 详细设计-营收业务
source_of_truth: false
last_reviewed: 2026-03-11
retrieval_priority: P1
---
# 福建水务营收系统详细设计-营收业务模块正文
## 章节导航(精简)
- [文档定位](#sec-position)
- [营收业务详细设计正文](#sec-content)
- [营收模块统一约束](#sec-rev-rules)
- [接口与数据追溯矩阵](#sec-rev-trace)
- [REV-001 客户资料管理](#mod-rev-001)
- [REV-002 抄表开账](#mod-rev-002)
- [REV-003 营业收费](#mod-rev-003)
- [REV-004 账务处理](#mod-rev-004)
- [REV-005 发票与税务处理](#mod-rev-005)
- [REV-006 催缴与通知](#mod-rev-006)
- [REV-007 统计分析](#mod-rev-007)
- [REV-008 代收与银行业务](#mod-rev-008)
- [REV-009 业务参数配置](#mod-rev-009)
<a id="sec-position"></a>
## 文档定位
本文档为 `01_Detailed_Design.md` 中“营收业务详细设计”章节的模块正文拆分稿,便于按模块独立维护。正式交付口径以主详设为准。
<a id="sec-content"></a>
# 营收业务详细设计
<a id="sec-rev-rules"></a>
## 营收模块统一约束
1. `SYS-002` 负责营收主流程,发票、支付结算、消息触达分别通过 `SYS-008``SYS-009``SYS-010` 协同完成,外部系统仅回写结果状态。
2. 账单、收费、发票、代扣等关键状态变更必须通过业务流程驱动,不允许绕过业务校验直接改写主业务对象。
3. 幂等控制遵循接口主键约束:支付以业务订单号为主,发票以申请单号或账单组合为主,代扣以批次号为主,消息以业务事件号为主。
4. 账务调整、发票申请、催缴触达、银行批次下发等关键动作必须写入操作留痕,满足审计与问题追踪要求。
5. 数据口径统一采用 `biz_*``bk_*` 命名体系,历史命名仅用于追溯,不作为本章节正式设计口径。
<a id="sec-rev-trace"></a>
## 接口与数据追溯矩阵
> 说明:详细字段与报文以 `../03_Technical_Design/03_Interface_Design.md` 为准,数据库字段口径以 `../03_Technical_Design/01_Database_Design.md` 为准。
| REV 模块 | 关键接口 | 核心数据域(摘要) | 主要协同对象 |
|---|---|---|---|
| REV-001 客户资料管理 | `IF-REV-001` | `biz_cust``biz_account``biz_cust_*` | 客户服务模块、报装模块 |
| REV-002 抄表开账 | `IF-REV-004``IF-REV-005` | `biz_meter_book``biz_meter_read``biz_reading_*``biz_charge*` | 抄表APP、物联网集抄 |
| REV-003 营业收费 | `IF-REV-006` | `biz_charge*``biz_collection``bk_transaction*` | `SYS-009` |
| REV-004 账务处理 | `IF-REV-007` | `biz_charge*``biz_operat_log*` | 财务与营业人员 |
| REV-005 发票与税务处理 | `IF-REV-008` | `biz_invoice*``biz_cust_invoice` | `SYS-008` |
| REV-006 催缴与通知 | `IF-REV-013` | `biz_charge*`、催缴结果留痕 | `SYS-010` |
| REV-007 统计分析 | `IF-REV-010` | 客户、抄表、收费、渠道聚合对象 | 统计分析端 |
| REV-008 代收与银行业务 | `IF-REV-011` | `bk_withholding_*``bk_reconcile_*``bk_settlement_*` | `SYS-009`、银行 |
| REV-009 业务参数配置 | `IF-REV-012` | `biz_parameter_settings``biz_page_settings*``biz_price_*` | 统一平台、营收模块 |
<a id="mod-rev-001"></a>
## REV-001 客户资料管理
### 功能说明
负责客户主档、账户主档、联系人、客户分组、客户与水表关系、客户开票信息、客户渠道绑定及托收/代扣关系维护,是抄表、收费、发票、代扣等后续业务的主数据基础。
### 关键设计
1. 客户建档覆盖立户、变更、更名、过户、销户、报停等全生命周期处理。
2. 客户资料支持按客户类型、用水性质、片区、集团客户、重点客户等维度管理。
3. 客户与水表、开票主体、托收/代扣签约关系按关联表维护,避免在主表中堆叠多类属性。
4. 客户编号、集收标记、计划用水方案等规则类数据由统一配置与客户关系表共同支撑。
### 核心数据
- `biz_cust`:客户主档。
- `biz_account`:客户账户与账户状态。
- `biz_cust_contact`:联系人及联系方式。
- `biz_cust_group`:客户分组。
- `biz_cust_meter`:客户与水表绑定关系。
- `biz_cust_invoice`:客户开票信息。
- `biz_cust_app_binds`:渠道绑定关系。
- `biz_cust_collection_rel`:客户托收关系。
- `biz_cust_withholding_rel`:客户代扣关系。
- `biz_cust_water_use_scheme``biz_cust_water_scheme_rel`:客户计划用水方案关系。
- `biz_cust_no_rule`:客户编号规则。
- `biz_cust_hub_marks`:集收号/集收标记关系。
### 迁移补充(旧系统承接)
#### 定额共享
- 旧系统在“客户资料”下提供定额主客户、子客户绑定与共享清单管理能力。
- 当前正式设计不新增并行主模型,统一归入客户与计划用水方案关系对象承接。
- 迁移时必须至少保留主客户、子客户、绑定状态、生效时间、解除时间、备注与操作留痕,确保共享清单可查询、可解绑、可追溯。
#### 定额核定
- 旧系统支持对已建立共享关系的客户执行定额核定、撤销核定和共享清单联查。
- 当前建议以客户关系对象和计划用水方案关系为主承接核定结果,不额外发明独立账务主表。
- 核定结果迁移后应支持按客户、站点、核定状态、核定时间查询,并保留核定依据、操作人和变更留痕。
#### 批量修改
- 旧系统已形成“手工修改 + 模板导入修改”的双模式客户资料维护能力。
- 当前正式设计应将其视为客户主数据治理能力的一部分,而不是临时导入工具。
- 迁移时需明确三类口径:可批量修改字段范围、导入模板校验规则、批量修改审计留痕;历史批量修改结果至少需保留任务来源、修改字段、执行结果和失败原因。
### 接口映射
- `IF-REV-001`:客户档案、账户状态、联系人与水表绑定关系查询。
- `IF-CS-001``IF-CS-002`:客户服务侧账户绑定与信息查询场景复用客户域数据。
### 落地边界
- **已落地**:客户主档、账户、联系人、分组、绑定、开票、托收/代扣关系。
- **部分落地**:客户扩展关系、集收与规则类对象已见明确关系表,但具体业务场景仍需结合流程进一步细化。
- **文档先行**:主副卡、部分复杂客户关系对象在当前 backend 中未见完全独立表族,文档中仅保留业务对象表述。
<a id="mod-rev-002"></a>
## REV-002 抄表开账
### 功能说明
负责抄表计划、册本管理、抄表录入、抄表状态跟踪、异常复核、计费计算与账单生成,是营收核心处理链路的起点。
### 业务流程
```mermaid
flowchart TD
A[制定抄表计划] --> B[生成抄表册本]
B --> C[分配抄表任务]
C --> D[人工/远传/自报抄表]
D --> E[数据校验]
E --> F{是否异常}
F -->|是| G[异常复核处理]
F -->|否| H[生成开账数据]
G --> H
H --> I[按价格模板与费用组成计费]
I --> J[生成营业账与明细]
J --> K[账单审核确认]
K --> L[进入收费/催缴/开票]
```
### 关键规则
1. 抄表数据同时校验本次读数、上次读数、用量波动和抄表状态,只有通过校验或完成异常复核的数据才能进入开账。
2. 异常抄表支持估抄、补抄、重录、人工复核;异常处理的目标是形成可继续开账或明确阻断的统一结论,而不是绕开开账规则单独落账。
3. `IF-REV-005` 的正式边界是“抄表校验完成后的账单生成”,不负责收费核销、发票开具、催缴执行和统计分析。
4. 开账计费按价格归属、价格模板、费用组成、阶梯规则、计划用水方案综合计算;价格配置缺失、费用组成不完整或规则冲突时,必须阻断生成并返回失败原因。
5. 账单生成结果统一由 `biz_charge``biz_charge_detail` 承接:主表表达客户、账期、应收日期、账单总金额和主状态,明细表表达费用组成、用量、单价和明细金额。
6. 特殊开账、无码客户开账、罚款类开账等非标准来源,仍纳入同一营业账主明细模型承接,通过来源类型、业务类型、依据说明和操作留痕区分,不单独扩展平行账表。
7. 审核通过后的营业账方可进入收费、催缴和发票流程;后续链路只能消费已生成账单结果,不反向改变本接口的生成边界。
8. 远传抄表数据可由 `SYS-006` / IoT 能力提供采集支撑,但账单生成仍归属 SYS-002。
### 开账触发与结果表达
- 触发前提:正式口径下可按抄表批次、指定客户范围或指定抄表任务范围组织生成;当前 backend 已落地的入口为按 `readingDataIds` 批量复核并开账,对应 `ChargeController.generateCheckChargeBatch``ChargeServiceImpl.generateCheckChargeBatch``ReadingDataServiceImpl.batchReCheckReadingData`
- 规则来源:价格归属决定客户适用的价格口径;价格模板、阶梯规则、费用组成和计划用水方案共同决定营业账主表金额与明细拆分方式。
- 当前承接证据:`ChargeServiceImpl.generateSingleChargeWithCache` 成功路径会写入 `biz_charge` 主表、循环写入 `biz_charge_detail` 明细,并回写抄表数据开账状态,说明现有实现已具备“按抄表数据 ID 生成营业账主明细”的 backend 基础。
- 结果表达:正式 `IF-REV-005` 应返回成功清单、失败清单、生成汇总及主明细级结果;当前 backend 返回仍为“本次复核成功X条 / 本次开账成功Y条”的字符串拼接尚未形成结构化成功/失败结果对象。
- 阻断与限制:价格模板不存在、费用调整配置缺失、结算方式非 `ACTUAL_USAGE` 等场景当前会直接阻断单条生成;其中固定水量、按人口数、最低消费等非实际水量结算方式仍未纳入当前实现。
- 下游边界:`REV-002` 只负责生成营业账结果并交由后续审核/收费链路消费,不在本章节扩展收费核销、发票申请或催缴执行细节。
### 核心数据
- `biz_meter_book`:册本与抄表计划。
- `biz_meter_read`:抄表任务状态/执行状态。
- `biz_reading_data`:抄表数据。
- `biz_last_reading`:上次抄表结果。
- `biz_reading_logs`:抄表日志与过程留痕。
- `biz_meter`:计量水表主档引用。
- `biz_charge`:营业账主表。
- `biz_charge_detail`:营业账明细。
- `biz_price_category`:价格归属。
- `biz_price_template`:价格模板。
- `biz_price_adjustment_snap`:调价快照。
- `biz_price_tier_adjustment`:阶梯规则。
- `biz_cost_component`:费用组成。
- `biz_water_use_scheme``biz_water_use_scheme_tier`:计划用水方案与阶梯。
### 迁移补充(旧系统承接)
#### 特殊开账
- 旧系统支持在非标准客户、无码客户或罚款类场景下直接开账。
- 新系统仍以 `biz_charge``biz_charge_detail` 作为开账结果承载对象,不建议额外平行建设“特殊开账账表”。
- 迁移与新建场景均需保留特殊开账来源、业务类型、经办人、依据说明、打印状态与后续收费关联,避免与普通抄表开账混淆。
#### 开账记录迁移
- 旧系统“开账记录”是历史查询与账务核对的核心入口,必须纳入迁移最小保留集。
- 迁移后的开账记录应至少支持按站点、账务年月、册本、客户、抄表员、欠费状态查询,并支持统计水量、总金额及费用构成。
- 对已收费、已作废、销户拆表、特殊开账等旧状态,不要求照搬旧表结构,但必须保留可对照的新状态映射关系。
### 接口映射
- `IF-REV-004`:抄表数据提交与异常标记。
- `IF-REV-005`:账单生成与开账结果返回。
- `IF-METER-004`:远传抄表数据接收后进入开账流程。
### 落地边界
- **已落地**:册本、抄表数据、上次抄表、抄表日志、营业账主明细、价格模板与阶梯规则。
- **部分落地**:部分异常场景对象可能仍通过状态字段和日志表承载,而非全部拆成独立业务表。
- **文档先行**:个别精细稽查、轨迹、下载同步对象当前未在本轮映射中确认为独立表。
<a id="mod-rev-003"></a>
## REV-003 营业收费
### 功能说明
支持柜台收费、预存款/余额抵扣、线上缴费回写、柜面扫码、营业网点收费及收费凭证管理,统一承接营收账单的核销处理。
### 业务流程
```mermaid
flowchart TD
A[查询客户及待缴账单] --> B[选择账单与核销方式]
B --> C[选择支付渠道]
C --> D{支付方式}
D -->|柜台现金/POS/扫码| E[现场收费]
D -->|微信/支付宝/聚合支付| F[渠道下单]
D -->|预存款/余额抵扣| G[账户余额核销]
E --> H[更新营业账状态]
F --> I[等待异步回调确认]
G --> H
I --> H
H --> J[生成收费记录与凭证]
J --> K[进入发票/对账流程]
```
### 关键规则
1. 一次缴费可对应多个账单或账单明细的组合核销。
2. 收费记录必须保留渠道、流水号、网点、操作员、终端信息。
3. 线上支付必须以回调或查询确认结果为准,不得以发起状态直接记账。
4. 支付能力由 `SYS-009` 提供SYS-002 负责账单核销与业务状态回写。
5. 当前实现侧已确认 `PayCeb` 的欠费查询、缴费处理基础闭环可用,但代理收费对账仍为预留能力;正式文档不得将实时收费对账写成已闭环能力。
### 核心数据
- `biz_charge``biz_charge_detail`:待缴与已缴账单主明细。
- `biz_collection`:托收/代收主表。
- `biz_withholding`:代扣/托收主表。
- `bk_transaction`:渠道交易流水。
- `bk_transaction_callback`:支付回调记录。
- `bk_transaction_exception`:支付异常记录。
### 迁移补充(旧系统承接)
#### 柜台结账
- 旧系统将“柜台收费”和“柜台结账”拆分为两个菜单,结账阶段包含未结/已结查询、结账红冲、追加抄表和打印动作。
- 当前设计可继续采用统一收费核销模型,但必须补出“收费记录 → 班结结果 → 打印/红冲/查询”的业务闭环,避免柜面日终处理缺口。
- 迁移时需保留结账时间、结账人、网点、收费汇总口径和结账后红冲痕迹,保证财务对账与审计连续。
#### 账单打印服务
- 旧系统存在账单打印、补打、打印次数控制与打印记录查询能力。
- 新系统不要求单独建立打印主模块,但必须明确打印模板、补打权限、打印状态与打印留痕的承接关系。
- 对历史账单迁移,应允许基于账单主明细和打印配置恢复打印视图,避免割接后只能查账不能补打。
#### 红冲记录
- 旧系统支持红冲记录查询、导出与明细展开,是收费差错追溯的重要入口。
- 当前设计可将红冲视为收费核销后的修正场景,不强制要求独立实体表,但必须提供历史只读查询口径。
- 红冲迁移最小保留信息应包括原收费记录、红冲时间、红冲金额、原因、经办人、关联账单和后续账务状态。
### 接口映射
- `IF-REV-006`:创建收费记录、执行账单核销并回写状态。
- `IF-CS-003``IF-CS-007`:客户渠道与柜面扫码支付场景复用收费核销链路。
### 落地边界
- **已落地**:营业账主明细、交易流水、回调、异常、托收/代扣主对象。
- **部分落地**:柜台班结、部分收费汇总类对象可能通过业务流程与报表实现,不一定存在独立表。
- **文档先行**:部分红冲、实时收费汇总类台账暂不表述为已确认独立实体表。
<a id="mod-rev-004"></a>
## REV-004 账务处理
### 功能说明
REV-004 一期仅覆盖水量调整、金额调整、退款、冲正、坏账申请五类场景,统一挂靠 `IF-REV-007` 作为账务处理入口,目标是在既有正式文档体系内先收敛范围、承接口径、留痕要求与审批边界。
本阶段按“共性能力先统一、场景能力再分批”组织:先统一账单承接、原交易校验、结果表达、操作留痕与审批边界,再分别展开五类场景。违约金减免、分账调整、价差调整、跨周期水量、预存退款细表等内容仅作为旧系统迁移语义或后续扩展参考,不作为一期新增独立范围。
### 业务流程
```mermaid
flowchart TD
A[发起账务调整申请] --> B[校验账单状态与权限]
B --> C{是否通过}
C -->|否| D[驳回并记录原因]
C -->|是| E[执行重算或退款冲正]
E --> F[更新账单与明细状态]
F --> G[写入操作日志与审批留痕]
G --> H[返回处理结果]
```
### 关键规则
1. 一期场景严格限定为水量调整、金额调整、退款、冲正、坏账申请,不扩展到其他接口族或独立账务台账重构。
2. 所有场景均以 `biz_charge` / `biz_charge_detail` 为主承接对象,并通过 `biz_operat_log` / `biz_operat_log_detail` 记录处理依据、前后变化和责任归属。
3. 退款、冲正必须联动 `bk_transaction``bk_transaction_callback``bk_transaction_exception` 等原支付流水及渠道状态校验,不允许仅依据账单状态直接处理。
4. 接口结果统一返回 `resultStatus``writeBackStatus`,其中 `resultStatus` 表示处理结论,`writeBackStatus` 表示账单状态回写结论,两者不得混用。
5. 审批相关内容一期仅保留 `approvalRequired``PENDING_APPROVAL` 与审批边界说明,不展开完整 BPM 流程、节点、流转规则或审批回写实现细节。
6. 对于当前未见明确独立实体表的特账、跨周期水量、退款账等对象,文档以“业务处理场景”表述,不强行落为已实现表。
### 核心数据
- `biz_charge``biz_charge_detail`:账务调整的核心对象,承接调整前后账单主明细状态。
- `bk_transaction``bk_transaction_callback``bk_transaction_exception`:退款、冲正场景的原交易校验与异常追溯对象。
- 价格调整/优惠相关表:用于重算账单或差额追溯。
- `biz_operat_log``biz_operat_log_detail`:操作与变更留痕,记录字段差异、处理说明、附件依据与责任归属。
### 主要场景
| 场景 | 说明 | 控制要点 |
|---|---|---|
| 水量调整 | 更正异常水量 | 需复核原因、附件和原抄表依据 |
| 金额调整 | 更正账单金额 | 需记录依据、差异金额和审批边界 |
| 退款 | 退回客户支付资金或预存款 | 需校验原交易、退款余额与幂等性 |
| 冲正 | 修正误收/误核销记录 | 需关联原交易与账单状态 |
| 坏账申请 | 对长期欠费进行分类处理 | 需结合账龄、客户状态与审批边界 |
### 迁移补充(旧系统承接)
| 旧账务对象 | 当前承接方式 | 迁移口径 |
|---|---|---|
| 预存退款 / 预存退款详情 | 作为账务处理场景承接 | 保留申请单、原支付引用、退款结果与审批留痕 |
| 已销调整汇总 / 明细 | 作为已收费后修正场景承接 | 保留原账单、调整原因、前后差异、处理结果 |
| 价差调整汇总 / 明细 | 作为重算与差额修正场景承接 | 保留原价格口径、新价格口径、差额和生效时间 |
| 分账调整汇总 / 明细 | 作为费用组成重分摊场景承接 | 保留原分摊结果、调整后结果、责任人和审批链 |
| 账单-违约金减免 | 作为滞纳金修正场景承接 | 保留减免原因、减免金额、审批结果和生效时间 |
| 账单-呆坏账 | 作为坏账申请与生效场景承接 | 保留账龄、申请原因、审批结果、核销状态 |
1. P0 阶段不要求为每一类旧账务台账都新增独立实体表,但必须在业务对象和历史查询层形成可追溯闭环。
2. 旧系统精细台账迁移后至少保留:原单据标识、原账单标识、处理类型、处理原因、处理前后金额/水量、申请/审批/生效时间、经办人与附件依据。
3. 与支付、发票、渠道回调强关联的处理场景,必须保留与原交易、原发票、原收费记录的关联关系,避免后续对账和审计断链。
### 接口映射
- `IF-REV-007`:账务调整、退款、冲正、坏账等处理入口。
- `IF-REV-006`:与收费核销状态联动,确保调账后账单状态一致。
### 落地边界
- **已落地**:营业账主明细、操作日志、价格/方案相关重算支撑。
- **部分落地**:精细化账务对象更多表现为流程与场景,并未在 backend 中全部体现为独立表族。
- **文档先行**:特账、退款账、跨周期水量等对象保留业务语义,不宣称为已实现独立表。
<a id="mod-rev-005"></a>
## REV-005 发票与税务处理
### 功能说明
负责发票业务闭环的业务接入与状态落账,覆盖后台发票申请、开票校验、`SYS-008` 异步协同、查询兜底、结果回写、账单关联、客户侧已开票电子发票查询/下载/推送,以及后台作废与红冲处理。
### 业务流程
```mermaid
flowchart TD
A[后台提交发票申请] --> B[校验账单、客户开票信息、税率与限额]
B --> C{是否满足开票条件}
C -->|否| D[返回不可开票原因]
C -->|是| E[生成申请单号并写入SUBMITTED]
E --> F[调用SYS-008发起异步开票]
F --> G[记录受理号并转为PENDING]
G --> H[系统轮询或后台查询兜底]
H --> I{开票结果}
I -->|成功| J[回写SUCCESS并更新账单-发票关联]
I -->|失败| K[回写FAIL并记录失败原因]
J --> L[客户侧查询/下载/推送电子发票]
```
### 状态说明
- `SUBMITTED`:后台申请已受理,已完成本地校验并生成申请单。
- `PENDING`:已提交 `SYS-008`,等待异步结果或查询补偿结果。
- `SUCCESS`:已取得有效发票代码、号码或电子票地址,且账单关联已完成更新。
- `FAIL`:开票失败,需保留失败原因、最近查询结果与后续人工核查依据。
- `INVALID`:发票已作废,作为后续能力预留状态。
- `RED_INK`:发票已红冲,作为后续能力预留状态。
### 关键规则
1. 一期采用“后台申请开票 + 客户侧查询下载推送”的入口模式,客户侧不直接发起开票申请。
2. 发票申请以客户信息、已收费未开票账单、税率配置和开票限额为基础;原始单账单不支持直接任意部分金额开票。
3. 个人与企业开票均通过客户开票信息与税率表完成合法性校验;如需多张发票,沿用拆账/分账后的账单分别开票口径。
4. `SYS-008` 采用“异步申请 + 查询兜底”模式,成功状态不得被后续失败查询结果覆盖。
5. 电子发票仅在 `SUCCESS` 且存在票据文件地址时允许客户侧下载或推送。
6. 发票作废、红冲仍由 `SYS-008` 统一承接税控侧处理,`SYS-002` 负责后台触发入口、状态校验、查询补偿、结果落账与日志留痕;当前轮次按二期范围补齐 backend 实现入口。
### 后台申请入口与校验补充
- 后台支持营业收费员、财务人员按单笔或批量已收费账单发起开票申请。
- 申请时至少校验:账单收费状态、开票状态、客户开票信息完整性、票种适配性、开票限额、账单集合是否属于同一客户。
- 企业抬头场景重点校验纳税人识别号;电子发票场景重点校验邮箱/手机号;不满足条件时直接返回不可开票原因。
- 申请成功后生成申请单号并进入 `SUBMITTED/PENDING` 状态流转,失败校验场景不进入外部协同。
- 幂等控制以 `applicationNo``custId + chargeIds` 为主,避免相同账单组合重复申请。
### SYS-008 异步协同与查询补偿
- 本地申请校验通过后,`SYS-002` 先写入 `biz_invoice` 申请记录,再向 `SYS-008` 发起异步开票请求,并记录 `sysRequestNo` 作为后续查询与回写的协同主键。
- `SYS-008` 返回“已受理”后,发票状态转为 `PENDING`;若仅完成本地受理但尚未拿到受理号,则保留 `SUBMITTED` 并进入待补偿查询状态。
- 查询补偿采用“回写优先、主动查询兜底”原则:`IF-EXT-007` 回写为首选结果来源,后台人工查询与系统定时补偿查询共用同一结果落账逻辑。
- 系统补偿查询至少保留最近查询时间、下次计划查询时间、累计查询次数、最近一次返回结果摘要与异常原因,便于问题追踪和人工核查。
- 后台按申请单号或受理号触发查询时,应同步刷新查询上下文;查询仍未取得终态时,仅更新补偿上下文,不得伪造成功或失败结论。
### 终态保护与异常核查
- `SUCCESS` 属于正常开票闭环终态,一旦已取得有效发票代码、发票号码或电子票据地址,后续失败查询结果不得覆盖成功状态。
- `FAIL` 仅在 `SYS-008` 明确返回失败结论或多次补偿查询确认失败时写入,并同步保留失败原因、最近查询结果与人工核查依据。
- 当后台人工查询、系统补偿查询与外部回写结果不一致时,应以最新有效外部凭据为准,并记录差异说明,不允许直接覆盖既有成功票据关键信息。
- 每次提交协同、主动查询、状态变更和异常分支均应写入操作留痕,确保能够追溯责任人、触发来源、状态前后值与异常说明。
### 结果回写、账单关联与客户侧消费
- `IF-EXT-007` 回写成功结果时,除更新 `biz_invoice.invoice_status``invoice_code``invoice_number``file_url` 外,还应同步刷新账单快照、账单关联状态与推送状态初值。
- 账单关联以 `biz_invoice.charge_id` + `charge_ids_snapshot` 记录本次开票覆盖账单集合,并同步把对应 `biz_charge.invoice_state` 更新为“开票完成”,保留失败原因与开票时间,便于账单明细、收费记录和客户侧结果统一展示。
- 客户侧查询仅允许按本人 `custId` 访问已存在的发票记录;可通过 `invoiceId``applicationNo``sysRequestNo` 定位,但都必须命中同一客户名下记录。
- 客户侧下载与推送前必须校验发票状态为 `SUCCESS``fileUrl` 非空;不满足条件时仅返回不可下载/推送原因,不得伪造文件地址。
- 推送动作应记录推送渠道、目标邮箱/手机号与结果状态;成功后更新 `pushStatus=PUSHED`,失败则写入 `FAIL` 并保留失败原因供人工处理。
### 核心数据
- `biz_invoice`:发票主记录。
- `biz_invoice_taxrate`:税率配置。
- `biz_cust_invoice`:客户开票信息。
### 迁移补充(旧系统承接)
- 旧系统数据字典中存在“发票明细表、营业账开票表、开票配置表”等更细粒度对象。
- 当前正式设计已明确主承接对象为 `biz_invoice``biz_invoice_taxrate``biz_cust_invoice`,但迁移时不能忽略旧开票明细和账单关联关系。
- P0 阶段建议先补三类迁移口径:账单与发票的关联关系、发票申请与结果回写记录、开票配置与税率的有效期;未确认已落地的细表对象仍按“历史只读或辅助映射”处理。
### 接口映射
- `IF-REV-008`:后台发票申请接口,负责单笔/批量申请、幂等控制与受理号生成。
- `IF-REV-009`:发票结果查询接口,负责后台按申请单号/受理号查询以及系统补偿查询。
- `IF-CS-004`:客户侧电子发票消费接口,负责已开票结果查看、下载、推送。
- `IF-EXT-007`:发票结果回写协同接口(由发票服务侧回传)。
### 落地边界
- **已落地**:发票主记录、税率配置、客户开票信息,以及一期正常开票闭环所需的后台申请、查询兜底、结果回写、账单关联与客户侧电子发票消费能力。
- **部分落地**:发票修改、开票过程留痕在后端中已有相关对象,但整套发票明细/批次类对象尚未全部确认。
- **二期补齐**:发票作废、红冲及其查询补偿仍由 `SYS-008` 统一承接,但当前轮次补齐后台触发入口、状态流转、结果回写与日志留痕,不再仅停留于文档预留。
- **文档先行**:发票明细、营业账开票关系等对象仍按设计能力描述,不表述为本轮已确认独立表。
<a id="mod-rev-006"></a>
## REV-006 催缴与通知
### 功能说明
针对欠费账单按账龄、金额、客户类别等规则生成催缴任务,通过短信、微信、站内通知等方式触达客户,并回写催缴结果;本模块同时定义催缴与停复水/工单处置之间的联动边界与追溯关系,但不展开停复水内部处置流程。
### 业务流程
```mermaid
flowchart TD
A[生成欠费客户清单] --> B[按策略分组催缴任务]
B --> C[触发IF-REV-013生成催缴任务]
C --> D[调用IF-EXT-008协同SYS-010]
D --> E[接收发送结果回写]
E --> F[更新催缴状态与后续策略]
F --> G[按联动边界挂接停复水/工单处置]
```
### 关键规则
1. 催缴策略以营业账状态、欠费金额、账龄分布、客户类别和渠道偏好为基础,支持按策略编码进行任务分组与频控。
2. 自动催缴与人工催缴可并行;自动任务用于常规批量催缴,人工任务用于补发、核查或例外处置。
3. `SYS-002` 负责催缴对象筛选、任务生成、业务事件编号、结果承接与历史查询;`SYS-010` 负责短信、微信公众号、站内信等触达执行与结果回传。
4. `REV-006` 正式结果状态固定为 `PENDING``SUCCESS``FAIL``MANUAL_VERIFIED` 四态,其中 `MANUAL_VERIFIED` 仅用于外部结果未定或需人工核查补记的场景。
5. 停复水在本模块中仅定义联动触发条件、处置引用与追溯关系,不展开停复水内部审批、派工或现场执行流程。
6. 当前后端中部分催缴汇总、停水明细对象未确认独立落表,文档中保持保守描述,不误写为已确认在线主表。
#### 催缴对象筛选、排除与频控边界
- `IF-REV-013` 任务生成前必须完成候选筛选,筛选最小维度为:欠费状态、欠费金额、账龄分组、客户类别、渠道偏好和策略编码。
- 候选对象必须以有效欠费账单为前提;以下场景不得进入正式催缴任务:已收费核销、已作废、已进入不允许催缴的处置流程、策略规则未命中。
- 触发类型按 `triggerType` 区分自动与人工;自动用于批量触发,人工用于补发、核查和例外补记,不改变正式接口编号与状态语义。
- 频控以“同客户/同策略/同渠道/同账期窗口”为最小拦截单元;命中频控时允许部分阻断,并返回被跳过对象及原因摘要。
### 核心数据
- `biz_charge``biz_charge_detail`:催缴对象来源。
- 催缴结果与通知日志:通过业务状态与消息结果联动留痕。
#### 催缴对象与规则摘要
- `Reminder Candidate`:由欠费账单、客户类别、账龄分组、欠费金额、联系方式集合和命中策略编码组成,是催缴任务的输入对象。
- `Reminder Strategy`:定义账龄规则、金额规则、客户类别规则、渠道优先级、重复触达拦截窗口和是否触发后续处置关注。
- `Reminder Task`:一次正式催缴执行单元,至少包含 `taskNo``eventNo``strategyCode``channelType``triggerType``status` 和关联账单信息;正式业务接口编号固定为 `IF-REV-013`
- `Reminder Result`:承接 `IF-EXT-008` 回传结果后由业务侧映射的正式四态结果,最少记录 `status``lastCallbackTime``failReason` 与回传摘要。
- `Disposal Link`:用于记录催缴结果与停水、复水、工单或人工跟进之间的关联引用,只承担追溯职责,不替代下游业务对象。
#### 四态与人工核查边界
- `PENDING`:已生成任务并完成外部受理或等待外部终态回传,尚未形成业务终态。
- `SUCCESS`:外部触达结果明确成功,且业务侧已完成结果承接。
- `FAIL`:外部返回明确失败或业务判定失败,必须记录失败原因。
- `MANUAL_VERIFIED`:仅用于外部结果长期未定、人工核查补记或例外核销说明场景;必须留存核查说明与核查人。
- 人工核查是状态收口手段,不得用于绕过候选筛选、排除条件或频控约束。
### 迁移补充(旧系统承接)
#### 催缴记录
- 旧系统支持催缴记录查询、导出和明细展开,记录中包含推送内容、号码、方式、结果等信息。
- 新系统可继续以消息协同结果和账单状态联动承接,但必须明确催缴记录查询口径,而不能仅保留“已发送/未发送”状态。
- 历史查询最少保留客户号、账期、催缴方式、发送对象、发送时间、执行结果、关联账单、关联处置引用等字段,并兼容四态结果或其历史映射值。
- 历史催缴记录按只读口径承接,作为查询与追溯来源,不反推为已确认在线主表。
#### 停水记录
- 停水记录不是孤立账务对象,应由催缴结果、业务处置和现场执行工单共同形成闭环。
- 迁移后需支持按客户、站点、停水原因、停水时间、复水状态查询,并能追溯到对应欠费账单和工单执行结果。
- 正式设计只定义“何时建立联动、如何保存处置引用、如何追溯关联结果”,不在 `REV-006` 中展开停复水内部流程设计。
- 停复水关联以 `Disposal Link` 的处置引用承接,最少包含任务号、处置类型、处置引用号和建联时间。
#### 预存短信
- 旧系统对预存款余额不足客户提供短信推送和发送记录查询。
- 新系统建议将其纳入催缴与通知统一策略,不再单建平行模型,但必须保留触发条件、发送内容、发送结果和补发记录。
- 该类记录与催缴记录一样,按历史只读口径承接,不表述为新增同名在线主表。
### 接口映射
- `IF-REV-013`:催缴任务生成、任务查询与结果承接接口,负责业务侧任务生成、四态状态维护和历史查询挂接。
- `IF-EXT-008`:消息协同结果回写接口(由 `SYS-010` 协同)。
### 落地边界
- **已落地**:以营业账为基础的欠费识别前提数据、催缴对象来源字段和消息协同边界约束。
- **部分落地**:催缴登记汇总、停水汇总等对象暂未在 backend 中确认独立表,当前以业务事件、操作留痕和历史查询口径承接。
- **文档先行**:复杂催缴台账、停复水统计和人工核查界面仅作为业务场景保留,不表述为 backend 已完成能力。
<a id="mod-rev-007"></a>
## REV-007 统计分析
### 功能说明
提供营收、抄表、收费、欠费、渠道、客户等多维度统计查询能力,为经营分析、业务监管和迁移核查提供统一的数据口径支撑;本模块以经营查询为主,不扩展到预测分析、专题大屏或独立 BI 平台实现。
### 关键设计
1. 统计查询按“主题 + 维度 + 指标”三层口径组织,避免仅以报表名称堆砌需求。
2. 主题范围至少包括营收汇总、收费与实收统计、欠费规模与账龄统计、客户结构统计、渠道交易统计、抄表完成率统计以及营收相关业务概览类摘要。
3. 查询维度至少包括时间区间、账期、营业所/片区、客户类别、渠道、客户/账户、状态等,并支持必要的分组汇总。
4. 指标口径需明确区分应收金额、实收金额、欠费余额、账单数、客户数、交易笔数、渠道占比、抄表完成率等相近但不等价的统计概念。
5. 导出与查询结果受数据权限控制;导出属于查询扩展能力,但不在本轮展开具体导出实现细节。
6. 重点查询可按聚合视图、汇总口径或预聚合结果承接,但不将未确认存在的统计表、专题分析表或离线数仓对象写成已实现事实。
### 核心数据
- 客户维度:`biz_cust``biz_account`
- 抄表维度:`biz_meter_book``biz_reading_data``biz_last_reading`
- 账务与收费维度:`biz_charge``biz_charge_detail`
- 收费与交易维度:`biz_collection``bk_transaction`
- 渠道维度:`bk_transaction``bk_payment_channel`
- 组织与权限维度:`system_dept`、数据权限控制结果。
#### 统计主题与口径摘要
- `Statistics Theme`:按经营主题组织查询,至少覆盖营收、收费、欠费、客户、渠道、抄表完成率和必要的业务概览。
- `Statistics Dimension`:按时间、账期、营业所/片区、客户类别、渠道、客户/账户、状态等条件筛选或分组。
- `Statistics Indicator`:至少明确应收金额、实收金额、欠费余额、账单数、客户数、交易笔数、渠道占比、完成率等指标含义和单位。
- `Aggregation Source`:统计结果以现有在线主数据聚合、视图或汇总口径承接,不反推为已存在独立统计表族。
### 接口映射
- `IF-REV-010`:营收、收费、欠费、渠道、客户等统计查询接口,承接主题查询、维度筛选、指标汇总和权限/导出边界。
### 落地边界
- **已落地**:主要统计源数据在客户、抄表、账单、收费、交易和渠道等领域均已具备,足以支撑经营统计口径设计。
- **部分落地**:部分统计结果可能依赖聚合视图、汇总查询或报表层实现,当前未见明确的 backend 统计控制器或独立统计模型入口。
- **文档先行**预测类、专题分析类深度模型、BI 大屏和独立数仓能力暂不写成后端已实现能力。
<a id="mod-rev-008"></a>
## REV-008 代收与银行业务
### 功能说明
支持银行代收、银行代扣、实时收费、夜间批量扣款、对账与结算处理,是 SYS-002 面向 `SYS-009` 支付与银行结算能力的业务承接模块。
### 业务流程
```mermaid
flowchart TD
A[生成代扣批次] --> B[校验签约与待扣账单]
B --> C[调用IF-REV-011下发批次]
C --> D[SYS-009对接银行处理]
D --> E[回写扣款结果]
E --> F[执行对账与差异识别]
F --> G{差异是否已处理}
G -->|否| H[进入人工补偿]
G -->|是| I[确认结算并更新状态]
```
### 关键规则
1. 渠道、路由、接口配置、签约、交易、回调、异常、对账、结算形成完整银行业务链条。
2. 实时收费场景由渠道交易流水驱动账单核销,批量代扣场景由签约关系与批次处理驱动。
3. 对账结果区分一致、长款、短款、失败待处理等状态,支持差异追踪与人工补偿。
4. 国密报文、批量文件、标准 API 等技术细节由 `SYS-009` 承载SYS-002 保留业务规则与状态协同。
5. 当前 backend 已确认 `BankWithholding` 六条银行入口(客户状态查询、送盘、送盘状态查询、取消送盘、回盘、回盘状态查询)已形成最小实现态闭环;`BankCollection` 平行链路、对账与结算协同仍以“部分实现或文档先行”表述,不得统一写成已闭环能力。
6. 银行代扣文件传输配置按“默认规则 + 银行通道覆盖 + 租户覆盖 + 租户-银行通道覆盖”建模,命中优先级固定为 `TENANT_CHANNEL > TENANT > CHANNEL > DEFAULT`
7. 目录字段至少区分 `send/back/reconcile/archive/localTemp` 五类阶段;上层覆盖未完整定义时按字段级回退,不允许生成空路径。
8. 路径模板仅允许 `{tenantId}``{companyId}``{channelCode}``{yyyyMMdd}``{yyyyMM}``{batchNo}``{fileName}` 七个固定变量;命中未声明变量或缺少变量取值时立即阻断文件动作。
9. `BankWithholding` 在送盘创建时固化 `sendProtocol/sendDir/sendFilePath``backProtocol/backDir`,配置切换仅影响新发起文件动作,已落库批次继续沿用原解析结果。
### 核心数据
- `bk_payment_channel`:支付渠道。
- `bk_channel_api_config`:渠道接口配置。
- `bk_channel_route_rule`:渠道路由规则。
- `bk_withholding_agreement`:代扣签约。
- `bk_withholding_batch``bk_withholding_item`:代扣批次与明细。
- `bk_reconcile_batch``bk_reconcile_diff`:对账批次与差异。
- `bk_settlement_batch`:结算批次。
- `bk_transaction``bk_transaction_callback``bk_transaction_exception`:交易、回调、异常。
- `biz_collection``biz_withholding`:代收/代扣业务主对象。
#### 文件传输配置与审计补充
- 环境默认规则通过 Spring profile + Nacos 承接,不在仓库样例中写入真实密码、私钥或证书。
- `bk_channel_api_config` 使用专用 `apiType=FILE_TRANSFER_CONFIG` 承接文件传输覆盖配置,`extParams` 记录作用域、业务类型、协议、连接引用和五类目录字段。
- `bk_withholding_batch` 固化送盘/回盘目录与协议,`bk_reconcile_batch` 固化对账阶段最终 `protocol/dir/filePath/fileName`,用于审计与问题回放。
### 迁移补充(旧系统承接)
#### 银行托收
- 旧系统“银行托收”菜单重点承接托收送盘、托收信息查询和托收结果回看。
- 当前设计已形成 `biz_collection` + `bk_*` 渠道模型,迁移时应补出“旧托收菜单 → 托收批次/交易/回盘结果”的映射,而不是按旧菜单名平移建模。
- 旧托收历史记录应至少保留送盘批次、客户范围、送盘结果、回盘结果和账单核销结果。
#### 实时收费查询与对账
- 旧系统“实时收费”更偏运营查询和渠道对账入口,不只是支付成功回写。
- 当前建议以 `bk_transaction*` 作为主承接对象,并补充按结算日期、银行/渠道、收费结果、差异状态查询和导出能力说明。
- 对旧“实时收费汇总/日志/明细”对象P0 阶段先按历史只读查询口径保留,不误写为当前已落地的独立主模型。
#### 当前实现对齐说明
- `PayCeb` 路径已具备欠费查询、缴费处理、流水唯一性校验和交易日志留痕,可作为实时收费基础闭环的实现证据。
- `BankWithholding` 路径已具备签约、解约、客户状态查询、送盘、送盘状态查询、取消送盘、回盘、回盘状态查询及对应交易留痕的实现证据,可作为代扣最小实现态闭环依据。
- `BankCollection` 路径当前仍仅能确认签约、解约与协议/交易日志处理具备实现证据。
- 对账、结算、真实银行文件解析、SFTP/文件通道联调和运行态样本补证当前仍未闭环,正式文档应继续保留为后续完善项。
### 接口映射
- `IF-REV-011`:代扣批次、对账与结算协同入口。
- `IF-EXT-001`:银行代扣批次下发与回盘协同。
- `IF-EXT-003`:银行实时收费查询、缴费与结果确认协同。
### 落地边界
- **已落地**:渠道、路由、交易、回调、异常、代扣/托收签约、解约,以及 `BankWithholding` 的客户状态查询、送盘、送盘状态查询、取消送盘、回盘、回盘状态查询和对应日志留痕等主对象已具备明确实现证据。
- **部分落地**`BankCollection` 批次、明细、送盘、回盘、状态查询、差异台账和后台资源管理入口已具备对象或骨架,但不等同于银行协同闭环全部完成;`BankWithholding` 的真实文件解析、异常补偿和运行态联调证据仍待补齐。
- **文档先行**:夜间批量代扣调度、完整对账处理、结算确认、扩展银行台账等内容不得在当前阶段写成已完成能力。
<a id="mod-rev-009"></a>
## REV-009 业务参数配置
### 功能说明
负责营收域的价格参数、客户编号规则、页面配置、打印与渠道相关业务参数配置,为客户、开账、收费、发票、催缴等模块提供统一配置支撑。
### 关键设计
1. 业务参数按租户、单位、片区、业务类别分层管理。
2. 价格体系、客户编号规则、页面字段配置、打印与通知参数统一归口维护。
3. 配置变更应具备版本化、操作留痕与生效范围控制。
### 核心数据
- `biz_parameter_settings`:业务参数配置。
- `biz_page_settings``biz_page_settings_detail`:页面配置。
- `biz_price_category``biz_price_template``biz_template_dept_rel`:价格归属与模板站点关系。
- `biz_cust_no_rule`:客户编号规则。
- `sys_wechat_app_settings`:微信/微网厅基础配置。
### 迁移补充(旧系统承接)
- 旧系统后台存在“页面配置、业务字段、微信参数、打印维护”等运营配置入口。
- 当前建议统一按业务参数、页面配置、渠道参数与打印参数归口承接,不新增“微客服后台配置”并行主文档。
- 迁移时需明确三类配置映射:客户/业务办理字段展示与校验规则、微信/微网厅基础参数、打印模板与补打策略;未确认已实现的高级灰度能力继续按“文档先行”处理。
### 接口映射
- `IF-REV-012`:查询与维护价格模板、业务参数、页面参数配置。
- `IF-UP-004`:统一平台参数字典能力协同,为营收域参数提供基础字典支撑。
### 落地边界
- **已落地**:业务参数、页面配置、价格归属与模板站点关系、客户编号规则等核心配置对象。
- **部分落地**:部分打印模板、通知策略等参数由统一平台或外部渠道参数共同承载,营收域仅保留业务侧映射。
- **文档先行**:参数灰度发布、多版本并行生效等高级治理能力当前仅保留设计语义,不宣称为独立实现模块。

View File

@ -0,0 +1,137 @@
# 柜台结清列表过滤字段对齐修复验证记录
日期2026-06-09
## 问题现象
`GET /admin-api/business/charge/counter-settle/unsettled-page` 前端传入 `custCode``chargeWay` 后,待结清分页结果未按客户编号和收费方式过滤。
追加核对 `charge` 系列前端接口口径后,又发现:
- 前端 `CounterUnsettledPageReqVO` 还声明了 `deptId`,后端待结清请求对象未接收,服务层未按站点过滤。
- 前端已结清列表传入 `settleNo`,后端 `CounterSettledPageReqVO` 未接收,`SettleRecordMapper#selectSettledPage` 未按结清单号过滤。
示例参数:
```text
pageNo=1&pageSize=10&custCode=15980151657&cashierId=&chargeWay=
```
## 根因
- 后端 `CounterUnsettledPageReqVO` 仅定义了 `cashierId`,未定义 `deptId``custCode``chargeWay`Spring MVC 绑定查询参数时会忽略这些字段。
- `CounterSettleApplicationServiceImpl#queryUnsettledItems` 只按收费员查询待结清支付主单,未继续应用客户编号、收费方式、营业站点过滤。
- 后端 `CounterSettledPageReqVO` 未定义 `settleNo`,已结清 Mapper 查询条件也未包含 `settle_no`
## 修复范围
仓库:`../water-backend`
分支:`develop`
修复前基线:`86203127e`
修改文件:
- `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/charge/vo/CounterUnsettledPageReqVO.java`
- `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/charge/vo/CounterSettledPageReqVO.java`
- `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/dal/mysql/settlerecord/SettleRecordMapper.java`
- `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/countersettle/CounterSettleApplicationServiceImpl.java`
- `sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/countersettle/CounterSettleApplicationServiceImplTest.java`
- `sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/integration/countersettle/CounterSettleIntegrationTest.java`
## 修复内容
- `CounterUnsettledPageReqVO` 新增 `deptId``custCode``chargeWay` 查询字段。
- 待结清列表在按收费员取得候选支付主单后,继续按 `custCode` 精确匹配、按 `chargeWay` 精确匹配、按账单 `deptId` 精确匹配。
- 待结清导出复用同一查询逻辑,因此同步支持相同过滤条件。
- `CounterSettledPageReqVO` 新增 `settleNo` 查询字段。
- `SettleRecordMapper#selectSettledPage` 新增 `settleNo` 精确过滤,已结清导出复用同一查询逻辑。
- 增加服务层单测覆盖 `custCode + chargeWay + deptId` 过滤、`settleNo` 参数传递和 Mapper 源码契约。
- 增加接口集成测试断言,覆盖未结账匹配、不匹配客户编号、不匹配收费方式、不匹配站点,以及已结账匹配/不匹配结清单号;该集成测试受 `REV004_IT_DB_URL` 环境变量控制。
## 验证命令与结果
### RED 验证
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend
mvn -pl sw-business/sw-business-server -Dtest=CounterSettleApplicationServiceImplTest#getUnsettledPage_shouldFilterByCustCodeAndChargeWay test
```
结果:失败,符合预期。
- 失败点:`CounterUnsettledPageReqVO` 缺少 `setCustCode(String)``setChargeWay(int)`
- 说明:证明后端请求对象没有承接过滤参数。
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend
mvn -pl sw-business/sw-business-server -Dtest=CounterSettleApplicationServiceImplTest#getSettledPage_shouldUseMapperPagedResult+getUnsettledPage_shouldFilterByCustCodeChargeWayAndDeptId test
```
结果:失败,符合预期。
- 失败点:`CounterSettledPageReqVO` 缺少 `setSettleNo(String)` / `getSettleNo()``CounterUnsettledPageReqVO` 缺少 `setDeptId(long)`
- 说明:证明后端请求对象未完整对齐前端 `charge` 系列列表筛选字段。
### 服务层回归
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend
mvn -pl sw-business/sw-business-server -Dtest=CounterSettleApplicationServiceImplTest#getUnsettledPage_shouldFilterByCustCodeAndChargeWay test
```
结果:通过。
- Surefire 汇总:`Tests run: 1, Failures: 0, Errors: 0, Skipped: 0`
- Maven 结果:`BUILD SUCCESS`
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend
mvn -pl sw-business/sw-business-server -Dtest=CounterSettleApplicationServiceImplTest#getSettledPage_shouldUseMapperPagedResult+getUnsettledPage_shouldFilterByCustCodeChargeWayAndDeptId test
```
结果:通过。
- Surefire 汇总:`Tests run: 2, Failures: 0, Errors: 0, Skipped: 0`
- Maven 结果:`BUILD SUCCESS`
### 相关服务单测
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend
mvn -pl sw-business/sw-business-server -Dtest=CounterSettleApplicationServiceImplTest test
```
结果:通过。
- Surefire 汇总:`Tests run: 22, Failures: 0, Errors: 0, Skipped: 0`
- Maven 结果:`BUILD SUCCESS`
### 编译验证
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend
mvn -pl sw-business/sw-business-server -DskipTests compile
```
结果:通过。
- Maven 结果:`BUILD SUCCESS`
### 接口集成测试
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend
mvn -pl sw-business/sw-business-server -Dtest=CounterSettleIntegrationTest#counterSettleApis_shouldSupportUnsettledConfirmSettledAndDetails test
```
结果:未执行,不作为通过项。
- Surefire 汇总:`Tests run: 1, Failures: 0, Errors: 0, Skipped: 1`
- 原因:`CounterSettleIntegrationTest` 标注 `@EnabledIfEnvironmentVariable(named = "REV004_IT_DB_URL", matches = ".+")`,当前环境未提供该变量。
## 备注
- 本次验证期间存在既有 Maven model warning、Mockito dynamic-agent warning、`MockBean` deprecation warning未导致编译或已执行单测失败。
- `cashierId` 为空时仍沿用既有逻辑:优先使用当前登录用户 ID 作为收费员过滤值。

View File

@ -0,0 +1,354 @@
# 前端 Expect CLI 测试框架接入记录
日期2026-06-08
## 范围
本记录对应 `water-frontend` 仓库新增 `expect-cli` 浏览器测试框架入口,用于在现有 Playwright 与 `node:test` 测试之外,提供 agent 驱动的浏览器测试能力。
## 前端基线
- 仓库:`water-frontend`
- 工作目录:`/Volumes/Dpan/github/water-workspace/water-frontend`
- 分支:`develop`
- 基础提交:`35fb598c9059b0ea933de533892a0473512ad662`
- 状态:测试框架接入已在工作区实现,尚未提交
## 实现内容
- 新增开发依赖:`expect-cli@^0.1.3`
- 新增脚本:
- `pnpm test:expect`
- `pnpm test:expect:smoke`
- `pnpm test:expect:watch`
- `pnpm test:expect:acceptance`
- 新增 runner`tools/expect/run-expect.mjs`
- 默认目标地址为 `http://localhost:18080`
- 支持 `EXPECT_BASE_URL` 覆盖目标地址
- 本地服务不可达时自动启动 `pnpm dev`
- 复用已有本地服务时不接管其生命周期
- 支持 `--no-dev-server` 禁止自动启动
- 新增验收场景库:`tests/expect/revenue-bugfix-clear-scope/scenarios.json`
- 覆盖 `#78``#39``#50``#53``#58/#59``#69/#76` 和基础回归。
- 场景来源为 `docs/superpowers/plans/2026-06-08-revenue-bugfix-clear-scope-acceptance-test.md`
- 使用环境占位符表达测试账号、客户和业务数据,不在前端仓硬编码 live 数据。
- 新增验收场景加载模块:`tools/expect/acceptance-scenarios.mjs`
- 验收指令默认使用严格黑盒规约:正式验收计划为 oracle不以源码、路由、store、API 封装或既有自动化测试作为通过依据。
- 通过标准固定为 `PASS / FAIL / BLOCKED`,缺数据、缺权限或业务状态不足时必须标记 `BLOCKED`
- 新增验收数据预检:
- `acceptance` 运行前检查场景所需环境变量。
- 缺少测试账号、客户或业务记录时直接输出 `BLOCKED` 并以退出码 `2` 结束,不启动浏览器,不使用占位符继续测试。
- 新增 runner/场景单元测试:
- `tests/expect/revenueBugfixAcceptanceScenarios.test.mjs`
- `tests/expect/runExpectRunnerArgs.test.mjs`
- 新增使用说明:`tests/expect/README.md`
## 验证命令
```bash
cd /Volumes/Dpan/github/water-workspace/water-frontend
node --check tools/expect/run-expect.mjs
node --check tools/expect/acceptance-scenarios.mjs
node tools/expect/run-expect.mjs --help
pnpm exec expect tui --help
node -e "const pkg=require('./package.json'); console.log(pkg.scripts['test:expect']); console.log(pkg.scripts['test:expect:smoke']); console.log(pkg.scripts['test:expect:watch']); console.log(pkg.devDependencies['expect-cli'])"
node --test tests/expect/revenueBugfixAcceptanceScenarios.test.mjs tests/expect/runExpectRunnerArgs.test.mjs
pnpm test:expect:acceptance -- --list
pnpm test:expect:acceptance -- rev-bugfix-39-counter-prepay-settlement
```
## 验证结果
- runner 语法检查:通过。
- 验收场景加载模块语法检查:通过。
- runner 帮助输出:通过,显示 `test:expect``test:expect:smoke``test:expect:watch` 用法及 `EXPECT_BASE_URL` 等环境变量。
- `expect-cli` 本地可执行性:通过,`pnpm exec expect tui --help` 成功输出 CLI 参数说明。
- `package.json` 脚本解析:通过,`test:expect``test:expect:smoke``test:expect:watch``test:expect:acceptance` 均指向 `tools/expect/run-expect.mjs`,开发依赖版本为 `^0.1.3`
- 验收场景单元测试通过8 项通过、0 项失败。
- 验收场景列表命令:通过,列出 7 个场景及各自 requiredData。
- 严格数据预检:通过。执行 `pnpm test:expect:acceptance -- rev-bugfix-39-counter-prepay-settlement` 时,由于未提供 `EXPECT_TEST_CASHIER_A_USERNAME``EXPECT_TEST_CUSTOMER_C4`,命令输出 `BLOCKED` 并以退出码 `2` 结束,未启动浏览器。
- `typecheck/vue-tsc`:按用户要求未继续执行,后续本项不作为该接入任务的默认验证。
- 浏览器实际 smoke未执行。本次完成框架接入、验收场景库和 CLI 可用性验证;真实业务验收需要测试环境提供 C1-C5、收费员 A/B、审批账号、水价模板、未结账/已结账记录等数据后运行。
## 使用入口
```bash
pnpm test:expect
pnpm test:expect:smoke
pnpm test:expect:watch -- -m "test the flow you changed"
pnpm test:expect:acceptance -- --list
pnpm test:expect:acceptance -- rev-bugfix-53-prepaid-deduction
pnpm test:expect:acceptance -- all
EXPECT_BASE_URL=http://localhost:18081 pnpm test:expect
```
## 重新测试记录2026-06-08 严格验收重跑
### 触发背景
前端工作区新增了红冲记录页面的时间范围默认时间处理:
- `src/views/operatingCharges/redReversalRecord/index.vue`
- `tests/revenue-bugs/revenueBugfixClearScope.contract.test.mjs`
### 执行命令
```bash
cd /Volumes/Dpan/github/water-workspace/water-frontend
pnpm test:expect:acceptance -- all
pnpm test:expect:acceptance -- rev-bugfix-58-59-red-reversal-record
node --test tests/revenue-bugs/revenueBugfixClearScope.contract.test.mjs
node --test tests/expect/revenueBugfixAcceptanceScenarios.test.mjs tests/expect/runExpectRunnerArgs.test.mjs
```
### 结果
- 严格全量验收:`BLOCKED`。当前 shell 未提供 `EXPECT_TEST_*` 测试账号、客户和业务记录数据runner 未启动浏览器,未使用占位符继续测试。
- `#58/#59` 红冲记录严格验收:`BLOCKED`。缺少:
- `EXPECT_TEST_CASHIER_A_USERNAME`
- `EXPECT_TEST_SETTLED_COUNTER_RECORD`
- 代码契约验证:通过,`tests/revenue-bugs/revenueBugfixClearScope.contract.test.mjs` 共 5 项通过、0 项失败。
- Expect 场景/runner 单元测试通过8 项通过、0 项失败。
- `typecheck/vue-tsc`:未执行。
### 结论
本次只确认了红冲记录代码契约和 Expect 严格验收框架本身仍可用。正式黑盒业务验收未通过也未失败,状态为 `BLOCKED`,需要提供测试账号、客户和已结账可红冲记录后重新执行。
## 收费与账务闭环场景库接入2026-06-08
### 范围
基于 `/Volumes/Dpan/github/water-workspace/docs` 中收费、结账、预存抵扣业务资料,以及 `../water-docs/specs/001-rev004-accounting/` 中账务处理一期口径,新增前端 `expect-cli` 可执行场景 suite
- `tests/expect/revenue-accounting-closures/scenarios.json`
- `tests/expect/revenue-accounting-closures/README.md`
### 场景
初版纳入 12 条场景:
- P0 小闭环:
- `revenue-charge-normal-payment`
- `revenue-charge-no-arrears-topup`
- `revenue-charge-prepay-zero-confirm`
- `revenue-charge-prepay-partial-deduction`
- `revenue-charge-prepay-full-deduction`
- P0 跨页面大闭环:
- `closure-charge-to-settlement`
- `closure-prepay-charge-to-settlement`
- `closure-settlement-to-red-reversal-record`
- P1 账务处理小闭环:
- `accounting-unsold-split-submit`
- `accounting-unsold-bad-debt-submit`
- `accounting-unsold-price-diff-submit`
- `accounting-unsold-penalty-remission-submit`
### 工具调整
- `tools/expect/acceptance-scenarios.mjs` 支持多 suite 加载。
- 场景引用支持:
- `revenue-accounting-closures:all`
- `revenue-accounting-closures:<scenario-id>`
- 全局唯一场景可直接使用 `<scenario-id>`
- `requiredData` 扩展到账务和收费闭环数据键,例如 `customerArrears``customerNoArrears``unsoldBillCustomer` 等。
### 验证命令
```bash
cd /Volumes/Dpan/github/water-workspace/water-frontend
node --test tests/expect/revenueAccountingClosureScenarios.test.mjs tests/expect/revenueBugfixAcceptanceScenarios.test.mjs tests/expect/runExpectRunnerArgs.test.mjs
node --check tools/expect/acceptance-scenarios.mjs
node --check tools/expect/run-expect.mjs
pnpm test:expect:acceptance -- --list
pnpm test:expect:acceptance -- revenue-accounting-closures:closure-charge-to-settlement
```
### 结果
- 多 suite 场景加载、schema、requiredData 映射、prompt 生成测试通过15 项通过、0 项失败。
- runner 语法检查:通过。
- 场景列表命令:通过,可列出 bugfix suite 和 `revenue-accounting-closures` suite。
- 严格业务执行:`pnpm test:expect:acceptance -- revenue-accounting-closures:closure-charge-to-settlement` 返回 `BLOCKED`,缺少 `EXPECT_TEST_CASHIER_A_USERNAME``EXPECT_TEST_CUSTOMER_ARREARS`,未启动浏览器,不作为失败或通过。
- `typecheck/vue-tsc`:未执行。
## 验证记录2026-06-08 收费与账务闭环 suite
### 执行命令
```bash
cd /Volumes/Dpan/github/water-workspace/water-frontend
node --check tools/expect/acceptance-scenarios.mjs
node --check tools/expect/run-expect.mjs
node --test tests/expect/revenueAccountingClosureScenarios.test.mjs tests/expect/revenueBugfixAcceptanceScenarios.test.mjs tests/expect/runExpectRunnerArgs.test.mjs
node --test tests/revenue-bugs/revenueBugfixClearScope.contract.test.mjs
pnpm test:expect:acceptance -- --list
pnpm test:expect:acceptance -- revenue-accounting-closures:all
pnpm test:expect:acceptance -- revenue-accounting-closures:closure-charge-to-settlement
```
### 结果
- runner 语法检查:通过。
- Expect 场景/runner 单元测试通过15 项通过、0 项失败。
- 营收缺陷代码契约测试通过5 项通过、0 项失败。
- 场景列表:通过,列出 `revenue-bugfix-clear-scope``revenue-accounting-closures` 两个 suite。
- `revenue-accounting-closures:all``BLOCKED`。缺少收费员、欠费客户、无欠费客户、预存余额客户、已结账红冲记录、管理员和未销账单客户等测试数据。
- `revenue-accounting-closures:closure-charge-to-settlement``BLOCKED`。缺少:
- `EXPECT_TEST_CASHIER_A_USERNAME`
- `EXPECT_TEST_CUSTOMER_ARREARS`
- 浏览器黑盒业务执行:未启动。阻塞发生在 requiredData 预检阶段,符合严格验收规则。
- `typecheck/vue-tsc`:未执行。
## 验证记录2026-06-08 Expect runner 与页面 smoke 重跑
### 触发背景
前端工作区继续调整了 Expect runner 可执行性,并对红冲记录页的红冲时间范围默认时间进行了补充:
- `tools/expect/run-expect.mjs`
- `tests/expect/runExpectRunnerArgs.test.mjs`
- `src/views/operatingCharges/redReversalRecord/index.vue`
- `tests/revenue-bugs/revenueBugfixClearScope.contract.test.mjs`
### 工具调整
- `tools/expect/run-expect.mjs` 改为优先调用本地 `node_modules/.bin/expect`,避免 `pnpm exec expect` 在当前工作区触发隐式 `pnpm install`
- runner 增加 `EXPECT_BIN` 覆盖入口,便于特殊环境指定 Expect 可执行文件。
- runner 过滤 npm/pnpm 转发参数中的独立 `--` 分隔符,修复 `pnpm test:expect:acceptance -- <scenario-id>` 被 Expect 识别为多余位置参数的问题。
### 执行命令
```bash
cd /Volumes/Dpan/github/water-workspace/water-frontend
node --test tests/expect/runExpectRunnerArgs.test.mjs
node --test tests/expect/revenueBugfixAcceptanceScenarios.test.mjs tests/expect/revenueAccountingClosureScenarios.test.mjs
node --test tests/revenue-bugs/revenueBugfixClearScope.contract.test.mjs
corepack pnpm@9.15.9 test:expect:acceptance -- --list
source /tmp/revenue-acceptance-env.sh
corepack pnpm@9.15.9 test:expect:acceptance -- rev-bugfix-50-cashier-filter --target unstaged --timeout 120000 --verbose
./node_modules/.bin/eslint tools/expect/run-expect.mjs tests/expect/runExpectRunnerArgs.test.mjs tests/revenue-bugs/revenueBugfixClearScope.contract.test.mjs src/views/operatingCharges/redReversalRecord/index.vue
corepack pnpm@9.15.9 ts:check
NODE_OPTIONS=--max-old-space-size=16384 corepack pnpm@9.15.9 ts:check
corepack pnpm@9.15.9 build:dev
```
### 结果
- runner 单元测试通过5 项通过、0 项失败。
- Expect 场景加载测试通过12 项通过、0 项失败。
- 营收缺陷代码契约测试通过5 项通过、0 项失败。
- 场景列表命令:通过。需使用 `corepack pnpm@9.15.9`;当前 shell 的全局 `pnpm@11.5.1` 会在脚本执行前尝试清理并重装 `node_modules`,无 TTY 时退出。
- `rev-bugfix-50-cashier-filter` 严格黑盒验收:`BLOCKED`。requiredData 环境变量已提供,但 Expect 启动 Codex ACP 后返回 `401 Unauthorized`,提示当前 Codex agent 未认证或 API key 无效;未进入浏览器业务步骤,不可判定 `PASS``FAIL`
- 页面 smokePlaywright fallback通过登录默认租户 `福建水投集团`、用户 `admin` 后访问真实菜单路由:
- `/operatingCharges/counterCheckout`:页面标题为 `营业收费管理系统 - 柜台结账`,未出现 404无 failed network request截图在 `water-frontend/test-results/counter-checkout-smoke.png`
- `/operatingCharges/redReversalRecord`:页面标题为 `营业收费管理系统 - 红冲记录`,查询区显示 `红冲时间`,表格显示柜台红冲记录字段,无 failed network request截图在 `water-frontend/test-results/red-reversal-record-smoke.png`
- 页面 smoke 备注:运行时存在既有 Vue/router warning包括未解析菜单组件 `system/userformconfig/index``pay/demo/transfer/index`,以及 `inject() can only be used inside setup()` 等;未在本次范围内修复。
- focused ESLint通过。
- `ts:check`:失败。默认堆内存运行先 OOM增大到 16G 后完成但暴露 repo-wide 既有 TypeScript 错误,涉及 mall statistics、BPM designer、pay views、system views、settings 等大量非本次修改文件。本结果不作为本次改动通过项。
- `build:dev`:通过,输出 `Build successful. Please see dist directory`。构建期间仍有既有 Vite CJS deprecation warning、SVG symbolId warning、Rollup PURE annotation warning。
### 结论
Expect runner 的本地执行入口和 pnpm 参数转发问题已修复并由单元测试覆盖。`rev-bugfix-50-cashier-filter` 严格黑盒验收当前阻塞于 Codex ACP 认证,状态为 `BLOCKED`;页面级 fallback smoke 只证明页面可登录访问和基础渲染,不替代正式黑盒业务验收。
## 修复记录2026-06-08 Expect runner 完整收敛
### 触发背景
上一轮验证后仍有三个仓库内可控问题:
- 当前 shell 的全局 `pnpm@11.5.1``node_modules/.modules.yaml` 中的 `pnpm@9.15.9` 布局不一致,未 pin 时会触发脚本前置安装/清理风险。
- runner 自动启动 dev server 时仍通过 `pnpm dev`,在 pnpm 版本不一致时可能复现同类问题。
- `tests/expect` 场景中存在 `/operating-charges/...` 旧路由元数据,而当前真实菜单路由为 `/operatingCharges/...`
- Expect 调用 Codex ACP 认证失败时输出原始栈,不够符合严格验收的 `BLOCKED` 语义。
### 实现内容
- `package.json` 新增 `packageManager: pnpm@9.15.9`
- `tools/expect/run-expect.mjs`
- dev server 启动改为直接调用本地 `node_modules/vite/bin/vite.js --mode dev`
- 保留 `EXPECT_DEV_SERVER_BIN` 用于特殊环境覆盖。
- 捕获 Expect 输出并识别 Codex ACP `401 Unauthorized` / `AcpProviderUnauthenticatedError`,转为清晰 `BLOCKED` 输出和退出码 `2`
- `tests/expect/revenue-bugfix-clear-scope/scenarios.json`
- 为 bugfix 场景补充真实页面路由元数据。
- `tests/expect/revenue-accounting-closures/scenarios.json`
- 将收费、结账、红冲场景的旧路由更新为 `/operatingCharges/counterCharging``/operatingCharges/counterCheckout``/operatingCharges/redReversalRecord`
- `tests/expect/runExpectRunnerArgs.test.mjs``tests/expect/revenueBugfixAcceptanceScenarios.test.mjs``tests/expect/revenueAccountingClosureScenarios.test.mjs` 补充回归测试覆盖上述行为。
### 执行命令
```bash
cd /Volumes/Dpan/github/water-workspace/water-frontend
node --test tests/expect/runExpectRunnerArgs.test.mjs
node --test tests/expect/revenueBugfixAcceptanceScenarios.test.mjs tests/expect/revenueAccountingClosureScenarios.test.mjs
node --test tests/expect/runExpectRunnerArgs.test.mjs tests/expect/revenueBugfixAcceptanceScenarios.test.mjs tests/expect/revenueAccountingClosureScenarios.test.mjs
node --test tests/revenue-bugs/revenueBugfixClearScope.contract.test.mjs
./node_modules/.bin/eslint tools/expect/run-expect.mjs tools/expect/acceptance-scenarios.mjs tests/expect/runExpectRunnerArgs.test.mjs tests/expect/revenueBugfixAcceptanceScenarios.test.mjs tests/expect/revenueAccountingClosureScenarios.test.mjs tests/revenue-bugs/revenueBugfixClearScope.contract.test.mjs src/views/operatingCharges/redReversalRecord/index.vue
pnpm test:expect:acceptance -- --list
pnpm test:expect:acceptance -- revenue-accounting-closures:closure-charge-to-settlement
source /tmp/revenue-acceptance-env.sh
pnpm test:expect:acceptance -- rev-bugfix-50-cashier-filter --target unstaged --timeout 120000 --verbose
pnpm build:dev
```
### 结果
- runner 单测通过9 项通过、0 项失败。
- Expect 场景测试通过14 项通过、0 项失败。
- 合并执行 Expect runner/场景测试通过23 项通过、0 项失败。
- 营收缺陷契约测试通过5 项通过、0 项失败。
- focused ESLint通过。
- `pnpm test:expect:acceptance -- --list`:通过,可在 plain `pnpm` 下列出两个 suite 的场景。
- `pnpm test:expect:acceptance -- revenue-accounting-closures:closure-charge-to-settlement`:按 requiredData 预检返回 `BLOCKED`,缺少 `EXPECT_TEST_CASHIER_A_USERNAME``EXPECT_TEST_CUSTOMER_ARREARS`,退出码 `2`
- `pnpm test:expect:acceptance -- rev-bugfix-50-cashier-filter --target unstaged --timeout 120000 --verbose`requiredData 已提供,但 Codex ACP 认证失败runner 输出清晰 `BLOCKED: Codex ACP authentication failed before browser acceptance steps ran...`,退出码 `2`,未再输出原始 ACP 栈。
- `pnpm build:dev`:通过,输出 `Build successful. Please see dist directory`。构建期间仍存在既有 Vite CJS deprecation warning、SVG symbolId warning、Rollup PURE annotation warning。
### 结论
本次已将仓库内可控问题收敛pnpm 版本、runner 启动路径、场景路由元数据、Codex ACP 认证失败语义均已有自动化测试覆盖。严格黑盒业务验收仍取决于外部 Codex ACP 认证状态;认证未完成时应按 `BLOCKED` 处理,不应判定业务 `PASS``FAIL`
## 本地 Playwright smoke 入口2026-06-08 无 ACP 场景
### 触发背景
用户本机没有 Codex ACP 认证,`pnpm test:expect:acceptance -- rev-bugfix-50-cashier-filter` 只能得到 `BLOCKED`,无法进入浏览器业务步骤。为保证前端页面链路仍可在本地验证,本次新增不依赖 ACP 的 Playwright smoke runner。
### 实现内容
- `package.json` 新增 `test:revenue:smoke`,执行 `node tools/revenue/run-local-smoke.mjs`
- `tools/revenue/run-local-smoke.mjs`
- 默认读取 `/tmp/revenue-acceptance-env.sh`,支持 `REVENUE_SMOKE_ENV_FILE` 覆盖。
- 使用 `EXPECT_BASE_URL``EXPECT_TEST_ADMIN_USERNAME``EXPECT_TEST_ADMIN_PASSWORD` 等本机环境变量登录本地前端代理 `/admin-api/system/auth/login`
- 自动复用或启动本地 Vite dev server。
- 访问 `/operatingCharges/counterCheckout``/operatingCharges/redReversalRecord`,检查页面标题/关键文案、登录状态、not-found 状态,并输出截图。
- 对登录不可达、认证被拒等数据/环境问题输出 `BLOCKED`,退出码 `2`
- 修复两个 runner 稳定性问题:`SmokeBlockedError` TDZ、业务流水号包含 `404` 时误判为 404 页。
- `tests/revenue-bugs/revenueLocalSmokeRunner.test.mjs` 覆盖 env 解析、默认配置、页面清单、BLOCKED 输出、404 误判和超时 helper。
### 执行命令
```bash
cd /Volumes/Dpan/github/water-workspace/water-frontend
node --test tests/revenue-bugs/revenueLocalSmokeRunner.test.mjs
pnpm test:revenue:smoke
node --test tests/revenue-bugs/revenueLocalSmokeRunner.test.mjs tests/revenue-bugs/revenueBugfixClearScope.contract.test.mjs
node --test tests/expect/runExpectRunnerArgs.test.mjs tests/expect/revenueBugfixAcceptanceScenarios.test.mjs tests/expect/revenueAccountingClosureScenarios.test.mjs
pnpm exec eslint tools/revenue/run-local-smoke.mjs tests/revenue-bugs/revenueLocalSmokeRunner.test.mjs
pnpm build:dev
```
### 结果
- 本地 smoke runner 单测通过9 项通过、0 项失败。
- 本地页面 smoke通过。
- `/operatingCharges/counterCheckout`:登录用户 `admin`,页面显示 `未结账``已结账``收费员`;截图为 `water-frontend/test-results/counter-checkout-local-smoke.png`
- `/operatingCharges/redReversalRecord`:页面显示 `红冲记录``红冲时间``收费员`;截图为 `water-frontend/test-results/red-reversal-record-local-smoke.png`
- 营收缺陷契约测试 + 本地 runner 单测通过14 项通过、0 项失败。
- Expect runner/场景测试通过23 项通过、0 项失败。
- focused ESLint通过。
- `pnpm build:dev`:通过,输出 `Build successful. Please see dist directory`。构建期间仍存在既有 Vite CJS deprecation warning、SVG symbolId warning、Rollup PURE annotation warning。
### 结论
本地前端页面链路现在可以通过 `pnpm test:revenue:smoke` 在无 ACP 的机器上执行。该入口证明登录、动态菜单路由、页面基础渲染和截图可用;它不替代 `expect-cli` 严格黑盒验收,也不验证 #50 收费员 A/B 业务数据筛选结果。严格业务验收仍需 ACP、其他可用 agent或人工按正式验收计划执行。

View File

@ -0,0 +1,28 @@
# Frontend Form Create Native Tag Guard Evidence
- Date: 2026-06-05
- Repository: `water-frontend`
- Branch: `develop`
- Scope: production/dev-mode build warning where Element Plus `ElSelect` dropdown renders `ElScrollbar tag="ul"` and Vue resolves it as `<Ul>`.
## Root Cause
Vue `resolveDynamicComponent('ul')` checks the app component registry before falling back to a native tag. A global component alias such as `Ul` can therefore shadow the native `ul` tag. Element Plus `ElScrollbar` uses dynamic tag rendering internally, so a polluted global registry causes its dropdown list to render as a component and emit:
`Extraneous non-props attributes (...) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.`
## Frontend Change
- Added `src/plugins/formCreate/nativeTagComponentGuard.ts`.
- Invoked the guard after `formCreate` and `FcDesigner` installation in `src/plugins/formCreate/index.ts`.
- The guard removes global component aliases matching native HTML/SVG tags after third-party plugin registration, preventing dynamic native tags such as `ul` from resolving to global Vue components.
## Verification
- `node --test tests/layout/nativeTagComponentGuard.test.mjs`: passed, 2 tests.
- `node --max-old-space-size=8192 ./node_modules/vite/bin/vite.js build --mode dev`: passed, generated `dist`.
- `node --max-old-space-size=8192 ./node_modules/vue-tsc/bin/vue-tsc.js --noEmit`: failed on existing repository-wide type errors outside this change scope, including mall statistics, BPM designer, pay pages, settings pages, and casing mismatch under `settings/waterMeter/range`.
## Notes
- The first `pnpm run` verification attempt triggered pnpm dependency-state auto-install in a non-TTY environment. `node_modules` was restored with `CI=true pnpm install --ignore-scripts`, and verification was continued by invoking local binaries directly.

View File

@ -0,0 +1,56 @@
# 客户缴费方式开户地址前端必填校验修复验证记录
日期2026-06-09
## 问题现象
客户资料页面修改缴费方式时,前端代扣/托收表单展示了“开户地址”字段,但未做必填校验;提交到后端后,后端在保存代扣或托收资料时返回“开户地址不能为空”。
## 结论
本问题应由前端补齐校验。原因是后端代扣、托收资料保存对象均将 `accountAddress` 定义为必填字段,客户缴费方式修改链路会复用这两个保存对象。
## 修复范围
仓库:`../water-frontend`
基线:`35aa0fbb`
修改文件:
- `src/views/custData/custInfo/components/PayMethodForm.vue`
- `src/views/custData/custCreate/components/WithHold.vue`
- `src/views/custData/custInfo/components/PayMethodForm.contract.test.mjs`
## 修复内容
- 客户详情“缴费方式”弹窗:
- 托收资料 `collectionFormRules` 增加 `accountAddress` 必填。
- 代扣资料 `withHoldingFormRules` 增加 `accountAddress` 必填。
- 新户建档代扣弹窗:
- `formRules` 增加 `accountAddress` 必填。
- 补充 node:test 契约测试,锁定上述规则。
## 验证命令与结果
```bash
cd /Volumes/Dpan/github/water-workspace/water-frontend
node --test src/views/custData/custInfo/components/PayMethodForm.contract.test.mjs
```
结果:通过。
- `tests 2`
- `pass 2`
- `fail 0`
```bash
cd /Volumes/Dpan/github/water-workspace/water-frontend
git diff --check -- src/views/custData/custInfo/components/PayMethodForm.vue src/views/custData/custCreate/components/WithHold.vue src/views/custData/custInfo/components/PayMethodForm.contract.test.mjs
```
结果:通过,无空白错误。
## 备注
当前前端仓库存在其他未提交的 expect 相关改动,本次未触碰。

View File

@ -0,0 +1,51 @@
# 营收明确缺陷第一批前端修复验证记录
日期2026-06-08
## 范围
本记录对应 `docs/superpowers/plans/2026-06-08-revenue-bugfix-clear-scope.md` 中前端 Task 5 至 Task 8
- `#69/#76` 未销分账、呆坏账、价差、违约金减免提交后状态提示
- `#78` 水价调整失败后重试闭环
- `#58/#59` 柜台红冲记录页面接口与查询语义
- `#39/#53` 柜台结账预存充值行展示、柜台收费预存抵扣为 0 的确认保护
## 前端基线
- 仓库:`water-frontend`
- Worktree`/Volumes/Dpan/github/water-workspace/worktrees/frontend-revenue-bugfix-clear-scope`
- 分支:`frontend-revenue-bugfix-clear-scope`
- 基础提交:`2a13e63e941e0a990f025844979847b3196effa9`
- 状态:前端修复已在 worktree 中实现,尚未提交
## 验证命令
```bash
cd /Volumes/Dpan/github/water-workspace/worktrees/frontend-revenue-bugfix-clear-scope
node --test tests/revenue-bugs/revenueBugfixClearScope.contract.test.mjs
pnpm dev --host 0.0.0.0
```
## 验证结果
- 前端合约测试通过5 项通过、0 项失败。
- Playwright 登录 smoke通过。使用默认租户 `福建水投集团`、用户 `admin` 登录到 `http://localhost:18080/home/index`
- Playwright 页面 smoke
- `/operatingCharges/redReversalRecord`:页面可打开,查询区和表格已显示 `收费员``红冲时间``红冲金额``红冲原因` 等柜台红冲语义字段。
- `/operatingCharges/counterCheckout`:页面可打开,未结账表中空收费单号显示为 `--`
- `/operatingCharges/counterCharging`:页面可打开。使用现有客户 `20260512111` 验证“预存抵扣金额为 0”确认弹窗出现且未调用 `/business/charge/update`,未提交收费。
- `/accountProcess/unsoldAdjustment`:页面可打开。
- `/settings/price/priceTemplate`:页面可打开,显示 `开始调价`
- 后端接口口径核对:
- 远端接口 `/admin-api/business/charge/counter-settle/red-flush-record-page` 返回业务 `code=404`,提示 `请求地址不存在:admin-api/business/charge/counter-settle/red-flush-record-page`
- 本地后端 worktree `backend-revenue-bugfix-clear-scope``ChargeController` 当前也未提供 `/counter-settle/red-flush-record-page``/counter-settle/red-flush-record-export`,与前端 Task 7 口径未闭合。
- 预存抵扣后端兜底校验闭合:`ChargeServiceImpl` 校验抵扣金额非负且不超过应收金额,`AccountService.decreaseDeposit()` 校验账户预存余额不足并抛错。
- `typecheck`:未执行完成。按用户要求停止并不再执行 typecheck。
- `build`:未执行。
## 备注
依赖安装采用 `pnpm install`。安装过程中 pnpm 11 提示 `@carbon/icons` 构建脚本未批准;该临时配置改动未纳入本次业务修复。
Playwright 运行时按需安装了 Chromium 单浏览器。截图保存在前端 worktree 的 `test-results/` 目录下。

View File

@ -0,0 +1,115 @@
# YW 业务流程 Expect 验收用例编制记录2026-06-09
## 背景
根据用户在 2026-06-09 提供的 5 条业务测试用例,前端仓新增 Expect 黑盒验收场景 suite
- `yw-business-flows:yw-01`:手工建档
- `yw-business-flows:yw-02`:册本调整
- `yw-business-flows:yw-03`:抄表录入
- `yw-business-flows:yw-04`:复核开账
- `yw-business-flows:yw-05`:柜台收费
前端实现位置:
- `../water-frontend/tests/expect/yw-business-flows/scenarios.json`
- `../water-frontend/tests/expect/ywBusinessFlowsScenarios.test.mjs`
- `../water-frontend/tools/expect/acceptance-scenarios.mjs`
- `../water-frontend/tests/expect/README.md`
## 执行入口
```bash
pnpm test:expect:acceptance -- yw-business-flows:all
pnpm test:expect:acceptance -- yw-business-flows:yw-01
pnpm test:expect:acceptance -- yw-05
```
## 测试数据要求
新增场景使用以下测试数据环境变量:
- `EXPECT_TEST_NEW_CUSTOMER_ARCHIVE`
- `EXPECT_TEST_AVAILABLE_METER`
- `EXPECT_TEST_INVOICE_PROFILE`
- `EXPECT_TEST_ARCHIVED_CUSTOMER`
- `EXPECT_TEST_TARGET_METER_BOOK`
- `EXPECT_TEST_METER_READ_CUSTOMER`
- `EXPECT_TEST_METER_READING_VALUE`
- `EXPECT_TEST_REVIEWABLE_METER_READ_RECORD`
- `EXPECT_TEST_BILLABLE_METER_READ_RECORD`
- `EXPECT_TEST_CHARGED_CUSTOMER`
并复用:
- `EXPECT_TEST_ADMIN_USERNAME`
- `EXPECT_TEST_CASHIER_A_USERNAME`
缺少上述数据时runner 必须在启动浏览器前返回 `BLOCKED`,不得使用占位符或编造业务记录继续执行。
## 验证结果
已执行目录契约与 runner 数据门禁验证:
```bash
node --test tests/expect/ywBusinessFlowsScenarios.test.mjs
```
结果:`pass 6 / fail 0`
```bash
pnpm test:expect:acceptance -- --list
```
结果:命令退出码 `0``yw-business-flows:yw-01``yw-business-flows:yw-05` 均已列出。
```bash
pnpm test:expect:acceptance -- yw-05 --no-dev-server
```
结果:命令退出码 `2`,按预期返回:
- `BLOCKED: missing required acceptance test data for yw-05.`
- 缺少 `EXPECT_TEST_CASHIER_A_USERNAME`
- 缺少 `EXPECT_TEST_CHARGED_CUSTOMER`
```bash
pnpm test:expect:acceptance -- yw-business-flows:all --no-dev-server
```
结果:命令退出码 `2`,按预期返回 `BLOCKED`,缺少全部 5 条 YW 用例所需的账号、客户、册本、水表、抄表、复核开账与柜台收费测试数据。
```bash
node --test tests/expect/*.test.mjs
```
结果:`pass 30 / fail 0`
### 真实浏览器验收尝试2026-06-09 12:00 CST
```bash
pnpm test:expect:acceptance -- yw-business-flows:all --no-dev-server --timeout 1800000
```
结果:命令退出码 `1`,未形成 `PASS` 业务验收结论。
- 已启动真实浏览器并进入 `yw-business-flows:yw-01` 手工建档场景。
- 浏览器侧可见记录显示已登录并访问 `http://localhost:18080/custData/custCreate`
- 运行过程中产生的主要证据:
- `/tmp/expect-artifacts/playwright/page@8c120c0eebb660ecda33437a218d685a.webm`
- `/tmp/expect-artifacts/playwright-results/result-*.json`
- `.expect/logs.md`
- ACP rollout 日志显示:用例在 `yw-01` 内部反复处理 Element Plus 下拉选择器,后续浏览器 transport 关闭。
- Expect 最终报错:
- `ACP stream inactivity timeout`
- `Streaming failed: Agent produced no output for 180s.`
- 本次运行未进入 `yw-02``yw-05` 的业务执行阶段。
- 本次运行中测试数据环境变量虽然已设置但部分值存在乱码agent 曾使用自行构造的可读测试数据继续填写。按黑盒验收规则,这不能作为有效 `PASS` 证据。
本次真实浏览器验收结论:`BLOCKED` / `FAIL-RUNNER`。阻断原因为 Expect ACP/browser transport 超时,且测试数据可读性不足;业务功能本身尚不能据此判定通过或失败。
## 当前结论
测试用例已编制为可执行 Expect 黑盒验收场景,目录加载、提示词生成和数据缺失拦截均通过。
截至本记录,真实浏览器业务验收已尝试执行,但未形成有效业务通过结论;当前阻断点为测试数据可读性不足与 Expect ACP/browser transport 超时。真实业务场景结论当前仍为 `BLOCKED`,不是 `PASS`

View File

@ -0,0 +1,71 @@
# 抄表开账 P0 保护修复验证记录
- 时间2026-06-20 15:56:43 +0800
- 仓库water-backend
- 分支develop
- 范围:普通开账前置状态校验、取消开账账单状态保护
## 修复内容
1. `ChargeServiceImpl.validateReadingDataBeforeBilling`
- 普通批量开账新增前置条件:抄表记录必须为 `CheckStateEnum.REVIEWED`
- 已开账或已有营业账记录仍优先返回重复开账错误。
2. `ChargeServiceImpl.cancelChargeByReadingDataIds`
- 空入参、无关联营业账时直接返回。
- 关联营业账存在 `PayStateEnum.PAID` 时禁止取消开账。
- 关联营业账存在 `PayStateEnum.SETTLED` 时禁止取消开账。
- 禁止场景下不删除营业账明细和营业账主记录。
## 红灯验证
新增测试后、修复实现前执行:
```bash
mvn -pl sw-business/sw-business-server -am -Dtest=ChargeServiceBillingValidationTest -Dsurefire.failIfNoSpecifiedTests=false test
```
结果:
- `cancelChargeByReadingDataIds_shouldRejectPaidCharge` 失败:当前实现未抛异常。
- `generateChargeBatch_shouldRejectMeterRecordedReadingDataWithoutReview` 失败:当前实现未在校验阶段拦截未复核记录。
## 绿灯验证
修复后执行:
```bash
mvn -pl sw-business/sw-business-server -am -Dtest=ChargeServiceBillingValidationTest -Dsurefire.failIfNoSpecifiedTests=false test
```
结果:
- Tests run: 7
- Failures: 0
- Errors: 0
- Skipped: 0
- BUILD SUCCESS
补充执行:
```bash
mvn -pl sw-business/sw-business-server -am -Dtest=ReadingDataServiceImplCheckValidationTest,ChargeServiceBillingValidationTest -Dsurefire.failIfNoSpecifiedTests=false test
```
结果:
- Tests run: 8
- Failures: 0
- Errors: 0
- Skipped: 0
- BUILD SUCCESS
编译验证:
```bash
mvn -pl sw-business/sw-business-server -am -DskipTests compile
```
结果:
- BUILD SUCCESS

View File

@ -0,0 +1,92 @@
# 抄表记录定时生成重复数据修复证据
## 背景
用户反馈:册本配置为“每月抄”,但每天定时器都会生成可修改、可抄表的抄表记录;同一客户同一账期中一条记录开账结账后,其余记录无法继续开账。
## 根因
后端存在两个抄表生成入口:
- `ReadingDataGenerateData`:调用 `ReadingDataService.generateReadingDataForAllBooks()`
- `meterReadingGenerateJob`:原先直接在 Job 内组装并插入 `biz_reading_data``biz_last_reading`,绕过服务层去重和周期判断。
`meterReadingGenerateJob` 的实现只查询 `status = 0` 的册本,没有使用 `nextReadDate` 控制当前抄表周期,也没有按 `custId + billMonth` 做重复生成保护,因此定时任务每日执行时可能持续插入同客户同账期抄表记录。
开账链路通过同客户同账期有效账单校验防止重复开账,因此同月多条抄表记录中,第一条开账后,其余记录会被“本月已开账/已缴费”规则拦截。
## 修复内容
1. 将 `meterReadingGenerateJob` 精简为统一委托 `ReadingDataService.generateReadingDataForAllBooks()`,删除 Job 内旧的直接插入抄表数据逻辑。
2. 在 `ReadingDataServiceImpl.generateReadingDataForAllBooks()` 中,当册本 `nextReadDate` 判定为非当前周期时直接跳过该册本,不再生成“非当前周期”的抄表数据。
3. 新增回归测试:
- `MeterReadingGenerateJobTest`:验证 `meterReadingGenerateJob` 委托服务层统一入口。
- `ReadingDataServiceImplGenerateTest`:验证非当前周期册本不会查询客户、不会插入抄表数据。
## 验证
执行命令:
```bash
mvn -pl sw-business/sw-business-server -am -Dtest=MeterReadingGenerateJobTest,ReadingDataServiceImplGenerateTest -Dsurefire.failIfNoSpecifiedTests=false test
```
验证结果:
- `Tests run: 2, Failures: 0, Errors: 0, Skipped: 0`
- Reactor 构建结果:`BUILD SUCCESS`
- 验证时间2026-06-20 15:09:01 +08:00
## 影响范围
- 后端模块:`sw-business-server`
- 涉及链路:抄表记录定时生成、抄表数据批量生成
- 不涉及:开账互斥规则、账单生成规则、收费结账逻辑
## 追加修复:抄表模块逻辑审查问题
### 背景
在完成重复生成修复后,继续审查抄表录入模块,发现仍存在若干会放大重复数据、筛选错误或账期误判的逻辑风险。
### 修复内容
1. `ReadingDataServiceImpl.generateReadingDataForAllBooks()` 增加 JVM 内串行保护,降低两个 XXL-JOB 入口或重复触发在同一应用实例内并发生成同客户同账期抄表记录的风险。
2. `ReadingDataMapper.selectByCustIdAndBillMonth()``selectByCustMeterIdAndBillMonth()``LastReadingMapper.selectByCustIdAndBillMonth()` 改为 `selectList + order by id desc + limit 1`,避免历史重复数据导致 `selectOne``TooManyResultsException`
3. `ReadingDataServiceImpl.filterCustMeterIdsByConditions()``meterStatus` 纳入“水表筛选条件”判断,修复只按水表状态筛选时条件被跳过的问题。
4. Excel 导入抄表账期改为优先按导入行的 `readDate` 推导账期;未传抄表日期时继续回退当前年月,避免跨月导入误查当前月记录。
### 回归测试
新增或补充以下测试:
- `ReadingDataMapperTest`
- `LastReadingMapperTest`
- `ReadingDataServiceImplFilterTest`
- `ReadingDataServiceImplImportTest`
- `ReadingDataServiceImplGenerateTest`
### 验证
执行命令:
```bash
mvn -pl sw-business/sw-business-server -am -Dtest=ReadingDataServiceImplGenerateTest,ReadingDataMapperTest,LastReadingMapperTest,ReadingDataServiceImplFilterTest,ReadingDataServiceImplImportTest -Dsurefire.failIfNoSpecifiedTests=false test
```
验证结果:
- `Tests run: 9, Failures: 0, Errors: 0, Skipped: 0`
- Reactor 构建结果:`BUILD SUCCESS`
- 验证时间2026-06-20 15:27:12 +08:00
编译验证命令:
```bash
mvn -pl sw-business/sw-business-server -am -DskipTests compile
```
验证结果:
- Reactor 构建结果:`BUILD SUCCESS`
- 验证时间2026-06-20 15:27:41 +08:00

View File

@ -0,0 +1,178 @@
# 营收明确缺陷第一批修复验证记录
日期2026-06-08
## 修复范围
- #78 水价调整失败后重试闭环
- #39 柜台预存缴费进入结账
- #50 柜台结账收费员筛选
- #53 预存抵扣校验
- #58/#59 柜台红冲记录查询
- #69/#76 待审批账务调整文案
## 后端基线
- 仓库water-backend
- Worktreebackend-revenue-bugfix-clear-scope
- 提交:`ba136759b1925789cb0adc18105d00d6928add59`
## 前端基线
- 仓库water-frontend
- Worktreefrontend-revenue-bugfix-clear-scope
- 提交:`5f7ad7754473541483b26efa324419eb7a5d1a3b`
## 验证命令与结果
### 后端 targeted tests
```bash
cd /Volumes/Dpan/github/water-workspace/worktrees/backend-revenue-bugfix-clear-scope
mvn -pl sw-business/sw-business-server -Dtest=PriceTemplateServiceImplTest,PriceTemplateAdjustmentLockRedisDAOTest,CounterSettleApplicationServiceImplTest,ChargeServiceImplCounterPrepayTest test
```
结果:通过。
- Surefire 汇总:`Tests run: 26, Failures: 0, Errors: 0, Skipped: 0`
- Maven 结果:`BUILD SUCCESS`
- 备注:执行期间存在既有 Maven model warning、Mockito dynamic-agent warning`PriceTemplateServiceImplTest` 中有一段业务代码捕获后记录的 NPE 栈日志,但测试结果为 0 failures / 0 errors。
### 后端 compile
```bash
cd /Volumes/Dpan/github/water-workspace/worktrees/backend-revenue-bugfix-clear-scope
mvn -pl sw-business/sw-business-server -DskipTests compile
```
结果:通过。
- Maven 结果:`BUILD SUCCESS`
### 前端 contract test
```bash
cd /Volumes/Dpan/github/water-workspace/worktrees/frontend-revenue-bugfix-clear-scope
node --test tests/revenue-bugs/revenueBugfixClearScope.contract.test.mjs
```
结果:通过。
- Node test 汇总:`tests 5`, `pass 5`, `fail 0`
### 前端 settings contract regression
```bash
cd /Volumes/Dpan/github/water-workspace/worktrees/frontend-revenue-bugfix-clear-scope
node --test tests/settings/priceTemplateAdjustmentErrorHandling.test.mjs
```
结果:通过。
- Node test 汇总:`tests 3`, `pass 3`, `fail 0`
### 前端 type check
```bash
pnpm ts:check
```
结果:未完成,不作为通过项。
- 首次执行长时间无诊断输出后以 `[ELIFECYCLE] Command failed.` 退出。
- 随后按用户明确指令停止继续运行 `vue-tsc` / `pnpm ts:check`
- 已终止残留 `vue-tsc` 进程。
### 前端 build
```bash
cd /Volumes/Dpan/github/water-workspace/worktrees/frontend-revenue-bugfix-clear-scope
pnpm build:dev
```
结果:通过。
- 输出:`Build successful. Please see dist directory`
- 备注:构建期间存在既有 Vite CJS deprecation warning、SVG icon symbolId warning、Rollup PURE annotation warning。
## 备注
- 前端 worktree 中存在未跟踪 `test-results/*.png` 截图产物,本次未提交。
- 本轮未处理 #70#9#70 需抓包确认提交字段;#9 需产品确认抄表状态规则。
## Jenkins 构建修复补充记录2026-06-09
### 问题定位
- Jenkins `sw-system-cloud-dev-pipeline``sw-business-server` 编译阶段报出 `*DORef` 缺失、Lombok getter/setter/log 缺失以及 `CounterSettleApplicationServiceImpl.isChargePaymentRecord(...)` 重复定义。
- 本地复现确认:`PrestorageBpmCallbackService` 已存在 `@Slf4j``log` 缺失不是独立代码问题,而是编译异常导致注解处理未完成后的级联报错。
- 修复 `CounterSettleApplicationServiceImpl` 中重复的 `isChargePaymentRecord(PaymentRecordDO)` 后,`target/generated-sources/annotations` 重新生成 `*DORef.java`Lombok 相关报错消失。
- 同一提交引入的 `Collection::stream` 代码缺少 `java.util.Collection` 导入,补齐后编译闭环通过。
### 后端修复范围
- 仓库:`../water-backend`
- 分支:`develop`
- 基线:`8e42a5123`
- 修改文件:`sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/countersettle/CounterSettleApplicationServiceImpl.java`
### 验证命令与结果
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend
mvn -pl sw-business/sw-business-server -am -DskipTests compile
```
结果:通过。
- Maven 结果:`BUILD SUCCESS`
- 说明:用于确认注解处理恢复,`*DORef` 生成文件重新出现。
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend
mvn clean package -DskipTests -T 1C -pl sw-dependencies,sw-framework,sw-gateway,sw-server,sw-module-system,sw-module-infra,sw-module-bpm,sw-module-pay,sw-module-report,sw-business,sw-business-bank,sw-module-member -am
```
结果:通过。
- Maven 结果:`BUILD SUCCESS`
- 耗时:`01:13 min (Wall Clock)`
- 备注:执行期间仍存在既有 Maven model warning、MapStruct unmapped target warning、Mockito `MockBean` deprecation warning均未导致构建失败。
## Jenkins 构建修复收敛记录2026-06-09 10:35
### 修复收敛结论
- 已确认 `8e42a5123 fix: configure sw-common annotation processors` 不是本次 Jenkins 编译失败的根因修复。
- 已将 `sw-framework/sw-common/pom.xml` 恢复到 `8e42a5123` 之前的注解处理器版本配置,避免保留错误方向的 POM 改动。
- 最终保留的真实修复为:
- 删除 `CounterSettleApplicationServiceImpl` 中重复定义的 `isChargePaymentRecord(PaymentRecordDO)`
- 补齐同文件中 `Collection::stream` 所需的 `java.util.Collection` import。
### 后端最终修复范围
- 仓库:`../water-backend`
- 分支:`develop`
- 修复前基线:`8e42a5123`
- 后端修复提交:`86203127eeb27fcb1ff8228a89a77eb3a3325c57`
- 修改文件:
- `sw-framework/sw-common/pom.xml`
- `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/countersettle/CounterSettleApplicationServiceImpl.java`
### 最终验证命令与结果
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend
mvn clean package -DskipTests -T 1C -pl sw-dependencies,sw-framework,sw-gateway,sw-server,sw-module-system,sw-module-infra,sw-module-bpm,sw-module-pay,sw-module-report,sw-business,sw-business-bank,sw-module-member -am
```
结果:通过。
- Maven 结果:`BUILD SUCCESS`
- 耗时:`01:15 min (Wall Clock)`
- 完成时间:`2026-06-09T10:35:31+08:00`
- 关键模块结果:
- `sw-common`: `SUCCESS`
- `sw-business-server`: `SUCCESS`
- `sw-server`: `SUCCESS`
- 备注:验证命令使用 `-DskipTests`,本次结论覆盖编译与打包;测试执行被跳过。

View File

@ -0,0 +1,59 @@
# 预存余额功能 — 测试与部署结果
> 2026-06-10 | 预存余额 P0-1~P1-2 功能测试验证
## 后端编译
| 项目 | 结果 |
|------|------|
| `mvn compile -pl sw-business/sw-business-server -am -o` | PASS |
| `mvn package -pl sw-business/sw-business-server -am -o -DskipTests` | PASS (147MB jar) |
## 后端单元测试
| 测试类 | 状态 | 备注 |
|--------|------|------|
| ChargeServiceCounterPaymentTest (7 tests) | 7/7 PASS | 修复了 AccountLogContext + CustBillTypeService mock |
| 其他已有单元测试 (300+ tests) | 全部 PASS | 仅 2 个已有失败 (CustServiceImplCustomerPageTest, PaymentRecordServiceImplTest) 非本次引入 |
## 新增测试代码
| 文件 | 类型 | 编译 |
|------|------|------|
| `test/.../CounterChargeFullChainIntegrationTest.java` (新增方法) | 集成测试 | PASS |
| `test/resources/sql/prestore/01_counter_topup_log_seed.sql` | SQL 种子 | N/A |
> 集成测试需要 PostgreSQL 环境运行 (`REV004_IT_DB_URL` 等环境变量)。代码已通过编译验证,实际执行待部署环境就绪。
## 前端
| 项目 | 结果 |
|------|------|
| TypeScript 类型检查 (`tsc --noEmit`) | 新文件无新增错误 |
| dist 构建 | dist/ 已存在 |
| E2E 测试代码 (2 文件) | 已创建,待后端运行后执行 |
### 新增 E2E 测试
| 文件 | 覆盖 |
|------|------|
| `tests/e2e/prestore/counterTopup.e2e.spec.ts` | counterTopup充值流程, 欠费拒绝, counterPreview余额查询 |
| `tests/e2e/prestore/prestorageAdjustBpm.e2e.spec.ts` | 预存调整列表, BPM创建, 详情查询 |
## 部署就绪项
| 产物 | 路径 |
|------|------|
| 后端 JAR | `sw-business/sw-business-server/target/sw-business-server.jar` |
| 前端 dist | `water-frontend/dist/` |
| DDL (PG) | `sql/rev005/REV006_account_log_ddl.sql` |
| DDL (MySQL) | `sql/rev005/REV006_account_log_ddl_mysql.sql` |
| DDL (MySQL) | `sql/rev005/REV006_cust_bill_type_ddl_mysql.sql` |
## 待执行项
1. 部署 PostgreSQL 并执行 DDL
2. 启动后端 (`java -jar sw-business-server.jar --spring.profiles.active=local`)
3. 启动前端 (`pnpm dev`)
4. 运行 Browser 手动冒烟 (按 plan Task 7 清单)
5. 运行 Playwright E2E 测试 (`npx playwright test tests/e2e/prestore/`)

View File

@ -0,0 +1,301 @@
# REV004 本次新增/增强接口清单
## 文档目的
整理本次 REV004 在后端新增的接口,以及对已有接口的增强点,方便前端联调、测试验收和后续文档沉淀。
---
## 一、结论概览
### 1. 本次真正新增的接口
本次**真正新增的 HTTP 路由共 4 个**,均为**分账专属接口**
1. `POST /admin-api/business/split-adjust/submit`
2. `GET /admin-api/business/split-adjust/page`
3. `GET /admin-api/business/split-adjust/detail`
4. `GET /admin-api/business/split-adjust/result`
### 2. 本次增强的已有接口
已有的 `/admin-api/business/accounting-adjust/*` 路由中,本次主要增强了:
- 未销分账提交能力
- 未销违约金减免提交/批量提交能力
- 调整详情/分页/日志/流程查询返回字段
- late-fee formal-table 查询兜底能力
---
## 二、本次新增接口
## 2.1 分账专属提交
- **方法**`POST`
- **路径**`/admin-api/business/split-adjust/submit`
- **作用**:发起分账申请
- **说明**:与旧的 `accounting-adjust/unsold-split-submit` 相比,这是更明确的分账专属入口
### 典型用途
- 前端分账弹窗直接调用
- 后续独立分账页或分账台账页调用
---
## 2.2 分账专属分页
- **方法**`GET`
- **路径**`/admin-api/business/split-adjust/page`
- **作用**:分页查询分账单列表
### 典型用途
- 分账专属列表页
- 分账查询台账
- 分账历史记录页
---
## 2.3 分账专属详情
- **方法**`GET`
- **路径**`/admin-api/business/split-adjust/detail`
- **作用**:查询分账详情
### 入参
- `splitAdjustNo`:分账单号
- `adjustmentNo`:兼容旧口径的调整单号
### 说明
优先面向新的分账编号口径,同时兼容旧的 `adjustmentNo` 查询方式。
---
## 2.4 分账执行结果
- **方法**`GET`
- **路径**`/admin-api/business/split-adjust/result`
- **作用**:查询分账执行结果
### 入参
- `splitAdjustNo`
- `adjustmentNo`
### 典型用途
- 提交后查看执行状态
- 审批通过后查看分账结果明细
- 前端结果弹窗/详情页展示
---
## 三、本次增强的已有接口
## 3.1 未销分账提交
- **方法**`POST`
- **路径**`/admin-api/business/accounting-adjust/unsold-split-submit`
### 本次增强点
新增支持:
- `splitItems`
### 说明
原先是未销调整页里的分账动作入口,本次增强后可承载更稳定的拆分明细提交语义。
---
## 3.2 未销违约金减免提交
- **方法**`POST`
- **路径**`/admin-api/business/accounting-adjust/unsold-late-fee-reduce-submit`
### 本次增强点
新增支持:
- `applicant`
- `contactMobile`
### 说明
用于补齐前端页面上申请人、联系电话等基础申请信息。
---
## 3.3 未销违约金减免批量提交
- **方法**`POST`
- **路径**`/admin-api/business/accounting-adjust/unsold-late-fee-reduce-batch-submit`
### 本次增强点
#### 外层批量字段
新增支持:
- `applyReason`
- `remark`
- `applicant`
- `contactMobile`
- `attachmentRefs`
- `lateFeeType`
#### item 明细字段
新增支持:
- `startDate`
- `endDate`
### 说明
该接口本次补齐了“按金额 / 按日期”两种减免模式所需字段,尤其适用于:
- 批量违约金减免
- 按日期区间计算减免
- 提交时保留申请人与联系电话信息
---
## 3.4 调整详情接口
- **方法**`GET`
- **路径**`/admin-api/business/accounting-adjust/get`
### 本次增强点
返回字段增强,且对于 `LATE_FEE_REDUCE`
- 当仅依赖 operat_log 无法完整聚合时
- 可回退到 formal-table
- `biz_latefee_reduce`
- `biz_latefee_reduce_detail`
- 继续组装详情返回
### 作用
保证违约金减免在 formal-table 路线下,详情页仍可稳定展示。
---
## 3.5 调整分页接口
- **方法**`GET`
- **路径**`/admin-api/business/accounting-adjust/page`
### 本次增强点
返回字段增强,便于前端稳定展示状态、原因标签、违约金减免展示字段等。
---
## 3.6 日志相关接口
### 典型路径
- `GET /admin-api/business/accounting-adjust/log-page`
- `GET /admin-api/business/accounting-adjust/log-detail`
- `GET /admin-api/business/accounting-adjust/log-process`
- `GET /admin-api/business/accounting-adjust/log-attachments`
### 本次增强点
返回字段增强,支持:
- 更完整的调整状态展示
- 附件、申请信息、原因标签等展示
- 对 formal-table / 新标准层字段的兼容展示
---
## 3.7 预存流程/附件接口
### 典型路径
- `GET /admin-api/business/accounting-adjust/prestorage-process`
- `GET /admin-api/business/accounting-adjust/prestorage-attachments`
### 本次增强点
返回字段增强,与统一标准层读模型保持一致。
---
## 四、本次重点新增/增强字段
## 4.1 提交类入参字段
### 分账相关
- `splitItems`
### 违约金减免相关
- `applicant`
- `contactMobile`
- `lateFeeType`
- `startDate`
- `endDate`
---
## 4.2 查询类返回字段
本次新增/增强的典型返回字段包括:
- `runtimeStatus`
- `reasonCodeLabel`
- `lateFeeType`
- `applicant`
- `contactMobile`
- `startDate`
- `endDate`
- `splitExecution`
### 说明
其中:
- `runtimeStatus`:统一状态展示口径
- `reasonCodeLabel`:原因编码对应的人类可读标签
- `splitExecution`:分账执行摘要/明细
- `lateFeeType/startDate/endDate`:支撑违约金减免按日期模式展示
---
## 五、推荐给前端的理解方式
## 5.1 新接口
前端如果做**分账专属页面 / 台账 / 结果页**,建议优先使用:
- `POST /business/split-adjust/submit`
- `GET /business/split-adjust/page`
- `GET /business/split-adjust/detail`
- `GET /business/split-adjust/result`
---
## 5.2 旧接口增强
前端如果仍基于原有 `accounting-adjust` 页面编排,则本次应重点关注:
- `unsold-split-submit`:支持 `splitItems`
- `unsold-late-fee-reduce-submit`:支持 `applicant/contactMobile`
- `unsold-late-fee-reduce-batch-submit`:支持 `lateFeeType/startDate/endDate` 等字段
- `page/get/log/process/detail` 等查询接口:返回字段增强
---
## 六、联调建议
## 6.1 分账
### 建议优先联调
1. `POST /business/split-adjust/submit`
2. `GET /business/split-adjust/detail`
3. `GET /business/split-adjust/result`
### 适用场景
- 分账弹窗提交
- 分账详情页
- 分账结果回显
---
## 6.2 违约金减免
### 建议重点联调
1. `POST /business/accounting-adjust/unsold-late-fee-reduce-submit`
2. `POST /business/accounting-adjust/unsold-late-fee-reduce-batch-submit`
3. `GET /business/accounting-adjust/get`
4. `GET /business/accounting-adjust/log-process`
### 建议重点验证
- `lateFeeType=1`(按金额)
- `lateFeeType=2`(按日期)
- 是否能正确展示 `applicant/contactMobile/startDate/endDate`
- 审批后详情页是否仍能查到 formal-table 路径数据
---
## 七、当前边界说明
## 7.1 已覆盖
- 分账专属接口新增
- 未销违约金减免按日期模式关键字段补齐
- late-fee formal-table 闭环
- 查询接口的部分标准层字段增强
## 7.2 未完全覆盖
- 其他对象类型(如坏账 / 核销 / 价差)尚未全部走 formal-table 同等级闭环
- 全量旧页面并未全部迁移到新接口
- 旧接口仍保留兼容口径
---
## 八、建议结论
如果前端是做:
- **分账专属页面**:优先接 `/business/split-adjust/*`
- **原未销调整页中的分账/违约金减免弹窗**:继续使用 `/business/accounting-adjust/*`,但需按本次新增字段补齐请求/展示
---

View File

@ -0,0 +1,276 @@
# REV004 前端联调测试数据说明
## 目的
为 REV004 本次新增/增强的接口提供一套**可直接用于前端联调**的测试数据,重点覆盖:
- 分账专属接口
- 未销违约金减免(成功 / 待审批)
- `accounting-adjust` 兼容查询链路
对应后端脚本:
- `water-backend/sql/rev004/REV004_accounting_adjust_frontend_debug_seed.sql`
---
## 一、数据设计原则
本次联调数据重点保证:
1. **客户 / 账户 / 营业账** 有真实关联
2. **分账主表 / 明细表 / 子账单** 有真实关联
3. **违约金减免主表 / 明细表 / 营业账 / 营业账明细** 有真实关联
4. **operat_log / operat_log_detail** 能支撑旧查询接口联调
5. 成功态、待审批态同时具备,方便前端测状态切换与展示分支
---
## 二、可直接使用的样例数据
## 2.1 分账成功样例
### 关键标识
- `splitAdjustNo = REV004-SPLIT-API-001`
- `adjustmentNo = REV004-SPLIT-API-001`
- `sourceChargeId = 920101`
- `customerCode = REV004_FE_SPLIT_CUST`
### 数据关系
- 源账单:`920101`
- 分账主单:`920301`
- 分账明细:
- `920311`
- `920312`
- `920313`
- 子账单:
- `920102`
- `920103`
- `920104`
### 业务语义
- 原账单水量:`9.000`
- 分成 3 笔
- 每笔水量:`3.000`
- 每笔金额:`60.00`
- 状态:**审批通过 + 执行成功**
### 适合联调的接口
- `GET /admin-api/business/split-adjust/page`
- `GET /admin-api/business/split-adjust/detail?splitAdjustNo=REV004-SPLIT-API-001`
- `GET /admin-api/business/split-adjust/result?splitAdjustNo=REV004-SPLIT-API-001`
- `GET /admin-api/business/accounting-adjust/get?adjustmentNo=REV004-SPLIT-API-001`
---
## 2.2 违约金减免成功样例
### 关键标识
- `adjustmentNo = REV004-LATEFEE-API-001`
- `chargeId = 920105`
- `customerCode = REV004_FE_LATE_CUST`
### 数据关系
- 客户:`920002`
- 营业账:`920105`
- 营业账明细:`9201001`
- 违约金减免主单:`920401`
- 违约金减免明细:`920411`
- request log`920511`
- approve log`920512`
### 业务语义
- 违约金减免类型:`2`(按日期)
- 起算日期:`2026-04-01`
- 截止日期:`2026-04-30`
- 理论调整前违约金:`18.00`
- 减免金额:`6.00`
- 调整后违约金:`12.00`
- 状态:**审批通过 + 执行成功**
### 适合联调的接口
- `GET /admin-api/business/accounting-adjust/get?adjustmentNo=REV004-LATEFEE-API-001`
- `GET /admin-api/business/accounting-adjust/logs?adjustmentNo=REV004-LATEFEE-API-001`
- `GET /admin-api/business/accounting-adjust/log-detail?adjustmentNo=REV004-LATEFEE-API-001`
- `GET /admin-api/business/accounting-adjust/log-process?adjustmentNo=REV004-LATEFEE-API-001`
---
## 2.3 违约金减免待审批样例
### 关键标识
- `adjustmentNo = REV004-LATEFEE-API-002`
- `chargeId = 920106`
- `customerCode = REV004_FE_PENDING_CUST`
### 数据关系
- 客户:`920003`
- 营业账:`920106`
- 营业账明细:`9201002`
- 违约金减免主单:`920402`
- 违约金减免明细:`920412`
- request log`920521`
### 业务语义
- 违约金减免类型:`1`(按金额)
- 减免金额:`5.00`
- 状态:**待审批**
### 适合联调的接口
- `GET /admin-api/business/accounting-adjust/get?adjustmentNo=REV004-LATEFEE-API-002`
- `GET /admin-api/business/accounting-adjust/page`
- `GET /admin-api/business/accounting-adjust/log-page`
---
## 三、接口调试建议
## 3.1 分账专属接口
### 分页
```http
GET /admin-api/business/split-adjust/page?pageNo=1&pageSize=10
```
### 详情
```http
GET /admin-api/business/split-adjust/detail?splitAdjustNo=REV004-SPLIT-API-001
```
### 执行结果
```http
GET /admin-api/business/split-adjust/result?splitAdjustNo=REV004-SPLIT-API-001
```
---
## 3.2 账务调整兼容查询接口
### 调整详情
```http
GET /admin-api/business/accounting-adjust/get?adjustmentNo=REV004-LATEFEE-API-001
GET /admin-api/business/accounting-adjust/get?adjustmentNo=REV004-LATEFEE-API-002
GET /admin-api/business/accounting-adjust/get?adjustmentNo=REV004-SPLIT-API-001
```
### 调整分页
```http
GET /admin-api/business/accounting-adjust/page?pageNo=1&pageSize=20
```
### 调整日志
```http
GET /admin-api/business/accounting-adjust/logs?adjustmentNo=REV004-LATEFEE-API-001
```
---
## 3.3 未销兼容查询
### 查询分账源客户
```http
GET /admin-api/business/accounting-adjust/unsold-page?pageNo=1&pageSize=20&code=REV004_FE_SPLIT_CUST
```
### 查询违约金客户
```http
GET /admin-api/business/accounting-adjust/unsold-page?pageNo=1&pageSize=20&code=REV004_FE_LATE_CUST
```
### 查询待审批违约金客户
```http
GET /admin-api/business/accounting-adjust/unsold-page?pageNo=1&pageSize=20&code=REV004_FE_PENDING_CUST
```
---
## 四、如果前端要直接提交流程
## 4.1 可用于新发起分账的源账单
- `sourceChargeId = 920101`
### 对应源账单条件
- 水量:`9.000`
- 适合拆成 3 笔,每笔 `3.000`
### 示例请求
```json
{
"sourceChargeId": 920101,
"applicant": "前端调试员",
"contactMobile": "13900009999",
"reasonCode": "1",
"remark": "前端直接调试新分账",
"attachmentRefs": ["split-fe-debug-1"],
"splitItems": [
{ "seqNo": 1, "splitWater": 3.000 },
{ "seqNo": 2, "splitWater": 3.000 },
{ "seqNo": 3, "splitWater": 3.000 }
]
}
```
---
## 4.2 可用于新发起违约金减免的源账单
### 成功样例源账单
- `chargeId = 920105`
### 待审批样例源账单
- `chargeId = 920106`
### 注意
这两笔账单都补了:
- `biz_charge.late_fee_begin_date`
- `biz_charge_detail`
- `biz_charge_detail.cost_component_code = 101`
`biz_cost_component.code = 101` 在测试库里已有:
- `penalty_coefficient = 0.0010`
因此可以支撑 `lateFeeType=2` 的按日期计算调试。
### 示例请求(按日期)
```json
{
"lateFeeType": "2",
"applicant": "前端调试员",
"contactMobile": "13800009999",
"applyReason": "1",
"remark": "前端直接调试按日期减免",
"attachmentRefs": ["latefee-fe-debug-1"],
"items": [
{
"chargeId": 920105,
"startDate": "2026-04-01",
"endDate": "2026-04-30"
}
]
}
```
---
## 五、执行方式
如需把这套联调数据写入测试库,可执行:
```bash
PGPASSWORD='Em@123456' \
psql -h 192.168.10.130 -p 5436 -U sw_system -d sw_system \
-v ON_ERROR_STOP=1 \
-f /Volumes/Dpan/github/water-workspace/water-backend/sql/rev004/REV004_accounting_adjust_frontend_debug_seed.sql
```
---
## 六、当前结论
这套数据已经覆盖了前端最常见的三类场景:
1. **分账成功态**
2. **违约金减免成功态**
3. **违约金减免待审批态**
同时兼顾:
- 新增分账专属接口
- 旧 `accounting-adjust` 兼容查询接口
- 申请态 / 审批态 / 执行态的展示联调
---

View File

@ -0,0 +1,90 @@
# REV004 当前主线真值矩阵
## 主线基线
- backend`sw-system-cloud/develop @ 9462f3a12728b83bfe31e0b74c526f4256f5b361`
- docs`fujian_water_biz_doc/main @ b5efa3b2480b1a0b8a00b728ac434f833787b6b4`
- 生成时间:`2026-04-17 15:00 +08:00`
## 一、对象总览
| 对象 | objectType / 专属域 | formal-table 状态 | 主表 / 明细表 | 主接口状态 | strict formal-first 状态 |
|---|---|---|---|---|---|
| 预存调整 | prestorage | 已独立 | `biz_prestorage_adjust` / `biz_prestorage_adjust_detail` | 已接线 | 部分完成 |
| 坏账调整 | `BAD_DEBT_RECORD` | 已独立 | `biz_bad_debt_adjust` / `biz_bad_debt_adjust_detail` | 已接线 | 主链路完成 |
| 已销调整/核销 | `WRITTENOFF_ADJUST` | 已独立 | `biz_writtenoff_adjust` / `biz_writtenoff_adjust_detail` | 已接线 | 主链路完成 |
| 违约金减免 | `LATE_FEE_REDUCE` | 已独立 | `biz_latefee_reduce` / `biz_latefee_reduce_detail` | 已接线 | 主链路完成 |
| 分账调整 | `SPLIT_ADJUST` | 已独立 | `biz_split_adjust` / `biz_split_adjust_detail` | 已接线 | 专属接口完成 |
| 价差调整 | `PRICE_DIFF_ADJUST` | 未独立 | 无 | 兼容接口 | 未完成 |
| 红冲记录 | `REDINK_RECORD` | 未独立 | 无 | 兼容接口 | 未完成 |
## 二、已独立对象明细
### 1. 预存调整(退款 / 转账)
- DDL`sql/rev004/REV004_prestorage_formal_tables_deploy.sql`
- 表:`biz_prestorage_adjust``biz_prestorage_adjust_detail`
- 接口:
- `POST /admin-api/business/accounting-adjust/prestorage-submit`
- `POST /admin-api/business/accounting-adjust/prestorage-revoke`
- `GET /admin-api/business/accounting-adjust/prestorage-process`
- `GET /admin-api/business/accounting-adjust/prestorage-attachments`
- `GET /admin-api/business/accounting-adjust/prestorage-page`
- `GET /admin-api/business/accounting-adjust/prestorage-detail`
- 当前判断:
- submit / revoke / page / detail 已是 formal-table 主链路
- `process / attachments` 仍保留 legacy / unified fallback
### 2. 坏账调整
- DDL`sql/rev004/REV004_bad_debt_formal_tables_deploy.sql`
- 表:`biz_bad_debt_adjust``biz_bad_debt_adjust_detail`
- 接线口径:统一 `accounting-adjust` submit / approve / reject / page / detail
- 当前判断:主链路已 formal-first
### 3. 已销调整 / 核销
- DDL`sql/rev004/REV004_writtenoff_formal_tables_deploy.sql`
- 表:`biz_writtenoff_adjust``biz_writtenoff_adjust_detail`
- 接口:
- `POST /admin-api/business/accounting-adjust/sold-submit`
- `POST /admin-api/business/accounting-adjust/approve`
- `POST /admin-api/business/accounting-adjust/reject`
- `GET /admin-api/business/accounting-adjust/get`
- `GET /admin-api/business/accounting-adjust/page`
- 当前判断:主链路已 formal-first且已补事务边界 / fail-fast
### 4. 违约金减免
- DDL`sql/rev004/REV004_latefee_formal_tables_deploy.sql`
- 表:`biz_latefee_reduce``biz_latefee_reduce_detail`
- 接口:
- `POST /admin-api/business/accounting-adjust/unsold-late-fee-reduce-submit`
- `POST /admin-api/business/accounting-adjust/unsold-late-fee-reduce-batch-submit`
- 当前判断formal-table 主链路已建立
### 5. 分账调整
- 表:`biz_split_adjust``biz_split_adjust_detail`
- 专属接口:
- `POST /business/split-adjust/submit`
- `GET /business/split-adjust/page`
- `GET /business/split-adjust/detail`
- `GET /business/split-adjust/result`
- 当前判断:已独立为专属对象域
- 备注:`sql/rev004/` 目录下当前未看到单独 split deploy 脚本,说明库结构来源可能更早或另有维护路径
## 三、未独立对象
### 1. 价差调整 `PRICE_DIFF_ADJUST`
- 当前仍主要走兼容接口 / 统一日志骨架
- 尚未见独立 `biz_price_diff_adjust` / `detail` 正式主从表
### 2. 红冲记录 `REDINK_RECORD`
- 当前未见独立 formal-table 主线
- 仍属于兼容口径对象
## 四、当前最适合的后续优先级
1. `PRICE_DIFF_ADJUST` formal-table
2. prestorage `process / attachments` strict formal-first 收口
3. 统一补一版“对象 → 表 → 接口 → 真值口径”对外联调说明
## 五、证据锚点
- backend 已合入PR #77merge sha=`9462f3a12728b83bfe31e0b74c526f4256f5b361`
- docs 已合入merge sha=`b5efa3b2480b1a0b8a00b728ac434f833787b6b4`
- writtenoff fresh smoke evidence`docs/evidence/rev004-writtenoff-formal-table-dev-db-apply-2026-04-17.md`
- formal-table 状态矩阵:`docs/guides/REV004_FORMAL_TABLE_STATUS_MATRIX.md`

View File

@ -0,0 +1,299 @@
# REV-004 前端实现交接清单
## 1. 文档定位
本文用于将 `REV-004` 前端实现方案转为可直接分发给前端实现 Agent 的正式 handoff 文档。
当前定位:
- 仅覆盖 **管理后台**
- 不覆盖微网厅 / 客户端实现
- 用于后续在 `../water-frontend` 目录通过 `omx team` / `$team` 推进实现
## 2. 当前统一口径
### 2.1 已确认范围
本轮前端实现只覆盖:
- REV-004 管理后台统一入口页
- REV-004 核心对象业务页
- REV-004 扩展对象业务页
- REV-004 历史核查 / 对账页
- 审批状态 / 结果展示通用组件
- 前后端联调与口径校验
### 2.2 明确不纳入本轮范围
以下内容不属于本轮前端实现:
- 微网厅页面实现
- 客户端交易页实现
- 客户端审批页实现
- 客户端对象化菜单管理能力
- 完整 BPM 流程前端实现
### 2.3 风格与实现原则
本轮采用折中策略:
- 核心操作路径尽量像旧系统
- 页面组织以当前前端风格为主
- 延续当前 **菜单级独立页面 + 查询区 + 表格区 + Dialog/Drawer 办理** 范式
- 但 REV-004 必须比当前更对象化、更清晰
- 不重做成完全不同的一套前端范式
## 3. 页面分层方案
### 3.1 统一入口层
统一入口页只负责:
- 对象导航
- 待办汇总
- 常用入口
- 状态聚合
约束:
- **不承载主办理表单**
- **不承载复杂对象编辑**
### 3.2 对象业务页层
#### 第一档独立菜单页(冻结清单)
- 预存退款
- 红冲记录
- 已销调整
- 坏账
#### 第二档共享扩展页 / 共享查询骨架(冻结清单)
- 价差调整
- 违约金减免
- 分账调整
- 特账 / 特殊开账
默认规则:
- 除第一档对象外,其余对象本轮不得新增顶层菜单
### 3.3 历史与核查层
- 退款账结果查询
- 跨周期水量查询
- 历史账务台账 / 迁移核查页
- 对账与差异定位页
## 4. 页面粒度要求
页面按以下粒度组织:
- **列表页**:查询、筛选、导出、批量操作
- **办理弹窗 / 抽屉**:新增、调整、撤销、审批动作
- **详情页 / 详情抽屉**:单据详情、留痕、审批状态、附件
- **结果页 / 查询页**:退款账、跨周期水量、历史台账、迁移核查
## 5. 通用组件要求
建议统一复用 / 补齐以下组件:
- `AccountingObjectHeader`
- `AccountingStatusPanel`
- `AccountingResultSummary`
- `AccountingApprovalBadge`
- `AccountingAttachmentPanel`
- `AccountingDiffTable`
基础模板继续沿用:
- `ContentWrap`
- 查询表单(`el-form`
- 结果表格(`EnhancedTable` / `el-table`
- `Dialog` / `Drawer`
## 6. 最小交付件
执行前或执行中,至少需要形成以下 4 类交付件:
### 6.1 页面清单
字段最小结构:
- 页面名称
- 页面类型
- 对应对象
- 当前状态(现有 / 复用 / 待新增)
### 6.2 路由清单
字段最小结构:
- 路由路径
- 页面归属
- 菜单归属
- 是否顶层菜单
### 6.3 通用组件清单
字段最小结构:
- 组件名
- 用途
- 复用页面
- 是否新建
### 6.4 lane ownership 清单
字段最小结构:
- lane 名称
- 负责目录与页面
- 负责组件
- 前置依赖
## 7. 四张正式执行表
### 7.1 页面清单(冻结版)
| 页面名称 | 页面类型 | 对应对象/用途 | 当前状态 | 目录/文件建议 | 说明 |
|---|---|---|---|---|---|
| REV-004 统一工作台 | 工作台页 | 统一入口、对象导航、待办汇总 | 待新增 | `src/views/accountProcess/index.vue` | 只做导航/聚合,不承载主办理表单 |
| 预存退款页 | 列表页 + 办理弹窗 + 详情 | `PrepaidRefund` | 现有(需口径收敛) | `src/views/accountProcess/prestorageAdjustment/index.vue` / `detail.vue` | 当前名称偏“预存调整”,需向预存退款对象口径收敛 |
| 红冲记录页 | 列表页 + 详情 | `RedinkRecord` | 现有 | `src/views/operatingCharges/redReversalRecord/index.vue` | 保留旧系统熟悉路径 |
| 已销调整页 | 列表页 + 办理弹窗 | `WrittenoffAdjust` | 现有 | `src/views/accountProcess/soldAdjustment/index.vue` | 作为第一档独立菜单页保留 |
| 坏账页 | 列表页 + 办理弹窗 | `BadDebtRecord` | 可复用 | `src/views/accountProcess/unsoldAdjustment/index.vue` + `components/BadDebtAdjustmentForm.vue` | 建议从未销调整页骨架中拆出独立坏账页 |
| 价差调整扩展页 | 共享扩展页 + 办理弹窗 | `PriceDiffAdjust` | 可复用 | `src/views/accountProcess/unsoldAdjustment/index.vue` + `components/PriceAdjustmentForm.vue` | 第二档共享扩展对象 |
| 违约金减免扩展页 | 共享扩展页 + 办理弹窗 | `LateFeeReduce` | 可复用 | `src/views/accountProcess/unsoldAdjustment/index.vue` + `components/PenaltyRemissionForm.vue` | 第二档共享扩展对象 |
| 分账调整扩展页 | 共享扩展页 + 办理弹窗 | `SplitAdjust` | 可复用 | `src/views/accountProcess/unsoldAdjustment/index.vue` + `components/SplitAdjustmentForm.vue` | 第二档共享扩展对象 |
| 特账 / 特殊开账页 | 列表页 / 查询页 | `SPECIAL_BILLING` | 现有(需边界重写) | `src/views/operatingCharges/specialAccountOpening/index.vue` | 保留“特殊开账”旧路径,但以当前前端组织为主 |
| 退款账结果查询页 | 结果页 / 查询页 | `RefundBill` | 待新增 | 建议 `src/views/accountProcess/refundBill/index.vue` | 只做查询与结果消费,不做主办理入口 |
| 跨周期水量查询页 | 查询页 | `CrossCycleWaterRecord` | 待新增 | 建议 `src/views/accountProcess/crossCycleWater/index.vue` | 只做查询/核查,不做强流程办理页 |
| 历史账务核查页 | 核查页 | 历史账务迁移核查 | 待新增 | 建议 `src/views/accountProcess/historyAudit/index.vue` | 用于现有/历史/目标口径差异定位 |
| 对账与差异定位页 | 核查页 | 结果差异、单据级定位 | 待新增 | 建议 `src/views/accountProcess/reconcileDiff/index.vue` | 偏联调与验收,不做业务办理 |
### 7.2 路由清单(建议版)
| 路由路径 | 页面归属 | 菜单归属 | 是否顶层菜单 | 当前状态 | 说明 |
|---|---|---|---|---|---|
| `/account-process` | REV-004 统一工作台 | 账务处理 | 否 | 待新增 | 工作台入口,聚合导航 |
| `/account-process/prestorage-adjustment` | 预存退款页 | 账务处理 | 是 | 现有 | 建议保留现有路径或做别名兼容 |
| `/operating-charges/red-reversal-record` | 红冲记录页 | 营业收费/账务处理 | 是 | 现有 | 可保留原有菜单认知 |
| `/account-process/sold-adjustment` | 已销调整页 | 账务处理 | 是 | 现有 | 第一档冻结对象 |
| `/account-process/bad-debt` | 坏账页 | 账务处理 | 是 | 待新增 | 从未销调整骨架中拆出 |
| `/account-process/adjustment-extension/price-diff` | 价差调整扩展页 | 账务处理扩展 | 否 | 待新增 | 第二档共享扩展对象 |
| `/account-process/adjustment-extension/late-fee-reduce` | 违约金减免扩展页 | 账务处理扩展 | 否 | 待新增 | 第二档共享扩展对象 |
| `/account-process/adjustment-extension/split-adjust` | 分账调整扩展页 | 账务处理扩展 | 否 | 待新增 | 第二档共享扩展对象 |
| `/operating-charges/special-account-opening` | 特账 / 特殊开账页 | 营业收费 | 否 | 现有 | 以特殊开账旧路径承接 |
| `/account-process/refund-bill` | 退款账结果查询页 | 历史与核查 | 否 | 待新增 | 结果对象查询出口 |
| `/account-process/cross-cycle-water` | 跨周期水量查询页 | 历史与核查 | 否 | 待新增 | 基础支撑对象查询出口 |
| `/account-process/history-audit` | 历史账务核查页 | 历史与核查 | 否 | 待新增 | 迁移核查主入口 |
| `/account-process/reconcile-diff` | 对账与差异定位页 | 历史与核查 | 否 | 待新增 | 联调/验收差异定位 |
### 7.3 通用组件清单(冻结版)
| 组件名 | 用途 | 复用页面 | 当前状态 | 说明 |
|---|---|---|---|---|
| `AccountingObjectHeader` | 对象标题、对象摘要、入口动作 | 统一工作台、对象列表页、详情页 | 待新增 | 统一对象头部表达 |
| `AccountingStatusPanel` | 结果状态 / 回写状态 / 业务状态面板 | 核心对象页、扩展对象页、核查页 | 待新增 | 状态统一展示 |
| `AccountingResultSummary` | 金额/水量/笔数汇总 | 列表页、核查页 | 待新增 | 可复用 `el-descriptions` 风格 |
| `AccountingApprovalBadge` | 审批状态 Tag / 摘要 | 对象列表页、详情页 | 待新增 | 不展开完整 BPM只展示边界 |
| `AccountingAttachmentPanel` | 附件、留痕、资料展示 | 详情页、核查页 | 待新增 | 统一附件区 |
| `AccountingDiffTable` | 金额/水量前后差异表 | 已销调整、价差调整、分账调整、核查页 | 待新增 | 差异定位核心组件 |
| `AdjustmentForm` | 预存调整/退款办理弹窗 | `prestorageAdjustment` | 现有 | 可复用并收敛命名 |
| `SoldAdjustmentForm` | 已销调整办理弹窗 | `soldAdjustment` | 现有 | 可直接复用 |
| `BadDebtAdjustmentForm` | 坏账办理弹窗 | `unsoldAdjustment` / 坏账页 | 现有(待抽出) | 建议抽离到坏账页目录 |
| `PenaltyRemissionForm` | 违约金减免办理弹窗 | 扩展对象页 | 现有(可复用) | 第二档共享组件 |
| `PriceAdjustmentForm` | 价差调整办理弹窗 | 扩展对象页 | 现有(可复用) | 第二档共享组件 |
| `SplitAdjustmentForm` | 分账调整办理弹窗 | 扩展对象页 | 现有(可复用) | 第二档共享组件 |
### 7.4 lane ownership 清单(冻结版)
| lane | 负责目录与页面 | 负责组件 | 前置依赖 | 说明 |
|---|---|---|---|---|
| Lane A | `src/views/accountProcess/index.vue`(若新建) | 导航卡片、入口汇总组件 | 无 | 统一入口只做导航/聚合 |
| Lane B | `src/views/accountProcess/prestorageAdjustment/*``src/views/operatingCharges/redReversalRecord/*``src/views/accountProcess/soldAdjustment/*`、坏账页目录 | 预存退款、红冲、已销、坏账相关组件 | 接口对象命名冻结 | 第一档独立菜单对象 |
| Lane C | 价差调整、违约金减免、分账调整、特账相关目录(待新增或共享) | `PriceAdjustmentForm``PenaltyRemissionForm``SplitAdjustmentForm`、扩展页头部组件 | Lane B 对象模式稳定 | 第二档共享扩展对象 |
| Lane D | `src/views/accountProcess/refundBill/*``src/views/accountProcess/crossCycleWater/*``src/views/accountProcess/historyAudit/*``src/views/accountProcess/reconcileDiff/*`(建议) | `AccountingResultSummary``AccountingDiffTable`、核查类组合组件 | 接口查询口径稳定 | 历史与核查层 |
| Lane E | 通用组件目录(建议 `src/components/accounting/*` | `AccountingStatusPanel``AccountingApprovalBadge``AccountingAttachmentPanel` | 状态枚举初版冻结 | 通用状态/审批/附件组件 |
| Lane F | 联调与验证脚本/样例/映射文档 | 接口映射、枚举校验、验收样例 | A/B/C/D/E 页面骨架形成 | 贯穿联调与验收 |
## 8. `omx team` 执行建议
### 8.1 执行目录
在以下目录执行:
- `../water-frontend`
### 8.2 并行顺序
- `Lane A / B / D / E` 可先并行
- `Lane C``Lane B` 的对象模式稳定后进入
- `Lane F` 最后贯穿联调与验收
### 8.3 执行前提
执行前必须明确:
- 先冻结 **IA信息架构**:页面分层、菜单分组、导航关系
- 后冻结 **DTO / 状态细节**
- 联调阶段允许通过 `Lane F` 对接口字段、审批状态、结果枚举做受控收敛
- 不允许反向推翻已冻结的页面分层
## 9. 任务包建议
### Agent A统一入口页
负责:
- 统一工作台页
- 导航聚合
- 待办与快捷入口
### Agent B核心对象页
负责:
- 预存退款
- 红冲记录
- 已销调整
- 坏账
### Agent C扩展对象页
负责:
- 价差调整
- 违约金减免
- 分账调整
- 特账 / 特殊开账
### Agent D历史核查 / 对账页
负责:
- 退款账结果查询
- 跨周期水量查询
- 历史账务核查页
- 差异定位页
### Agent E审批状态 / 结果组件
负责:
- 审批状态展示
- 结果状态展示
- 留痕 / 附件通用区
### Agent F联调与口径校验
负责:
- 接口口径映射
- 枚举统一
- 页面与接口返回一致性校验
- 验收样例整理
## 10. 验证要求
执行前 / 执行中至少检查:
1. 是否明确区分:
- 当前已有页面
- 可复用页面
- 待新增页面
2. 是否明确区分:
- 第一档独立菜单对象
- 第二档共享扩展对象
3. 统一入口是否只做导航 / 汇总,不承担主办理表单
4. 是否把目标态误写成当前已实现页面
5. `omx team` lane ownership 是否清晰,不会并行踩文件
## 11. 需求依据
- `.omx/plans/2026-04-07-rev004-frontend-implementation-plan.md`
- `.omx/specs/deep-interview-rev004-frontend-modules.md`
- `docs/design/02_Detailed_Design/12_REV_Detailed.md`
- `docs/design/03_Technical_Design/01_Database_Design.md`
- `docs/design/03_Technical_Design/03_Interface_Design.md`
- `docs/design/01_Overview/03_Summary_Design.md`
- `docs/guides/REV004_FULL_ACCOUNTING_DOMAIN_DESIGN.md`

View File

@ -0,0 +1,363 @@
# REV-004 前端 OMX Team 执行提示词
## 1. 使用说明
本文用于在 `../water-frontend` 目录直接启动 `omx team` / `$team` 时,作为 leader 与各 lane 的执行提示词模板。
适用范围:
- **仅管理后台**
- 不包含微网厅 / 客户端实现
- 需求依据以正式文档与 handoff 为准,不以旧手册直接替代正式设计
执行前固定输入依据:
- `docs/guides/REV004_FRONTEND_IMPLEMENTATION_HANDOFF.md`
- `.omx/plans/2026-04-07-rev004-frontend-implementation-plan.md`
- `.omx/specs/deep-interview-rev004-frontend-modules.md`
- `docs/design/02_Detailed_Design/12_REV_Detailed.md`
- `docs/design/03_Technical_Design/01_Database_Design.md`
- `docs/design/03_Technical_Design/03_Interface_Design.md`
- `docs/design/01_Overview/03_Summary_Design.md`
---
## 2. Leader 启动 Prompt
```text
任务REV-004 管理后台前端实现
工作目录:../water-frontend
本轮范围:
1. 统一入口页
2. 核心对象页(预存退款、红冲记录、已销调整、坏账)
3. 扩展对象页(价差调整、违约金减免、分账调整、特账/特殊开账)
4. 历史核查 / 对账页(退款账结果、跨周期水量、历史账务核查、差异定位)
5. 审批状态 / 结果展示组件
6. 联调与口径校验
严格约束:
- 仅做管理后台,不做微网厅 / 客户端
- 保持当前前端范式:菜单级独立页面 + 查询区 + 表格区 + Dialog/Drawer 办理
- 核心操作路径尽量像旧系统,但页面组织以当前前端风格为主
- 统一入口页只做导航 / 聚合 / 快捷入口,不承载主办理表单
- 第一档独立菜单对象冻结为:预存退款、红冲记录、已销调整、坏账
- 第二档共享扩展对象冻结为:价差调整、违约金减免、分账调整、特账/特殊开账
- 除第一档对象外,其余对象本轮不得新增顶层菜单
- 不得把目标态写成当前已落地事实
交付件要求:
1. 页面清单
2. 路由清单
3. 通用组件清单
4. lane ownership 清单
5. 实际代码变更
6. 验证结果
执行前提:
- 先冻结 IA页面分层、菜单分组、导航关系
- 后冻结 DTO / 状态细节
- 联调阶段允许收敛接口字段、审批状态、结果枚举,但不能反向推翻页面分层
请按以下 lanes 组织执行:
- Lane A统一入口页 / 导航聚合
- Lane B核心对象页
- Lane C扩展对象页
- Lane D历史核查 / 对账页
- Lane E审批状态 / 结果组件
- Lane F联调与口径校验
建议并行关系:
- A / B / D / E 先并行
- C 在 B 的对象模式稳定后进入
- F 最后贯穿联调与验收
最终输出:
- 各 lane 变更摘要
- 冲突与整合说明
- 验证结论
```
---
## 3. Lane A Prompt统一入口页 / 导航聚合
```text
你负责 REV-004 前端 Lane A统一入口页 / 导航聚合。
工作范围:
- `src/views/accountProcess/index.vue`(若新建)
- 导航卡片组件
- 入口汇总组件
目标:
- 建立 REV-004 管理后台统一工作台页
- 仅负责对象导航、待办汇总、快捷入口、状态聚合
- 不承载主办理表单
页面要求:
- 延续现有 ContentWrap / 查询区 / 信息卡片 / 表格摘要 风格
- 页面中明确分出:
- 核心对象入口
- 扩展对象入口
- 历史核查入口
- 审批/结果摘要入口
- 支持跳转到各对象页,不把功能都堆在一个页面内
约束:
- 不要实现对象办理弹窗
- 不要侵入 Lane B/C/D/E 的具体页面逻辑
- 允许先用 mock/占位入口,但必须标清待接页面
交付:
1. 统一入口页代码
2. 导航卡片/入口组件
3. 页面清单与路由占位建议
4. 需要 Lane B/C/D/E 配合的接口点
```
---
## 4. Lane B Prompt核心对象页
```text
你负责 REV-004 前端 Lane B核心对象页。
冻结范围:
- 预存退款
- 红冲记录
- 已销调整
- 坏账
优先目录:
- `src/views/accountProcess/prestorageAdjustment/*`
- `src/views/operatingCharges/redReversalRecord/*`
- `src/views/accountProcess/soldAdjustment/*`
- 坏账页目录(待新增,建议从 `unsoldAdjustment` 骨架拆出)
目标:
- 将第一档核心对象做成独立菜单页 / 独立对象页
- 页面模式保持现有后台风格:查询 + 表格 + 办理弹窗/详情
- 统一对象页表达:状态、审批状态、附件、留痕、关联账单
重点要求:
- `prestorageAdjustment` 要向 `PrepaidRefund` 对象语义收敛
- `redReversalRecord` 保留旧系统熟悉路径,但前端结构按现有项目规范组织
- `soldAdjustment` 作为 `WrittenoffAdjust` 主页保留
- 坏账页需明确从 `unsoldAdjustment` 中拆分,不要继续混在一个泛页面里
约束:
- 只处理第一档独立对象
- 不扩展第二档对象
- 不更改统一入口职责
交付:
1. 各对象页结构与代码调整
2. 各页复用/新增组件清单
3. 坏账页新建建议与落地文件路径
4. 与 Lane E 的组件依赖点
```
---
## 5. Lane C Prompt扩展对象页
```text
你负责 REV-004 前端 Lane C扩展对象页。
冻结范围:
- 价差调整
- 违约金减免
- 分账调整
- 特账 / 特殊开账
建议依附:
- `src/views/accountProcess/unsoldAdjustment/*`
- `src/views/operatingCharges/specialAccountOpening/*`
- 或共享“账务调整扩展页”目录
目标:
- 将第二档对象做成共享扩展页或共享查询骨架
- 不新增顶层菜单
- 复用现有弹窗:
- `PriceAdjustmentForm`
- `PenaltyRemissionForm`
- `SplitAdjustmentForm`
重点要求:
- 页面要体现对象差异,但不要每个对象都做成完整独立模块
- 特账 / 特殊开账保留旧路径,但界面边界要按当前前端组织重写
- 共享页要能通过对象类型切换,不回退成完全混杂页
约束:
- 本 lane 不得把第二档对象升为顶层菜单
- 本 lane 不负责历史核查页
- 统一状态 / 审批展示优先复用 Lane E 组件
交付:
1. 共享扩展页方案与代码
2. 对象切换策略
3. 复用组件点
4. 与 Lane B 的边界说明
```
---
## 6. Lane D Prompt历史核查 / 对账页
```text
你负责 REV-004 前端 Lane D历史核查 / 对账页。
范围:
- 退款账结果查询页
- 跨周期水量查询页
- 历史账务核查页
- 对账与差异定位页
建议目录:
- `src/views/accountProcess/refundBill/*`
- `src/views/accountProcess/crossCycleWater/*`
- `src/views/accountProcess/historyAudit/*`
- `src/views/accountProcess/reconcileDiff/*`
目标:
- 建立 REV-004 历史与核查层页面
- 页面以查询、汇总、差异定位为主
- 不做主办理动作
重点要求:
- 支持“现有 / 历史 / 目标”口径差异定位
- 支持对象类型、状态、审批状态、账期、时间、关联账单等查询维度
- 页面风格延续后台列表查询页 + 汇总区 + 详情抽屉模式
约束:
- 不要把这类页面做成办理页
- 不要侵入第一档/第二档对象主办理逻辑
- 与 Lane F 协作时,优先暴露差异定位能力
交付:
1. 历史与核查层页面代码
2. 查询字段清单
3. 差异定位展示方案
4. 对 Lane F 的验收支撑点
```
---
## 7. Lane E Prompt审批状态 / 结果组件
```text
你负责 REV-004 前端 Lane E审批状态 / 结果展示组件。
范围:
- 通用状态组件
- 审批 Badge
- 结果摘要组件
- 留痕 / 附件区组件
建议目录:
- `src/components/accounting/*`
目标:
- 统一各对象页的状态表达
- 统一审批状态展示
- 统一结果摘要、附件、留痕表达
最小组件:
- `AccountingStatusPanel`
- `AccountingApprovalBadge`
- `AccountingResultSummary`
- `AccountingAttachmentPanel`
- `AccountingDiffTable`
- (可选)`AccountingObjectHeader`
约束:
- 仅展示审批边界,不展开完整 BPM 流程前端
- 组件要能被 Lane A/B/C/D 复用
- 不直接绑定某个具体对象页逻辑
交付:
1. 通用组件代码
2. 组件 props 约定
3. 复用方式说明
4. 与各 lane 的接入示例
```
---
## 8. Lane F Prompt联调与口径校验
```text
你负责 REV-004 前端 Lane F联调与口径校验。
范围:
- 接口映射
- 枚举值对齐
- 页面与返回结构一致性校验
- 验收样例整理
目标:
- 保证前端实现与正式文档口径一致
- 检查 objectType、状态、审批状态、结果状态等枚举是否统一
- 检查各对象页和核查页是否把“目标态”误写成“已实现事实”
重点检查:
- 第一档对象页与第二档扩展页边界是否被破坏
- 统一入口是否被做成主办理页
- 历史核查页是否误入办理逻辑
- 页面清单 / 路由清单 / 组件清单 / lane ownership 清单是否完整
交付:
1. 接口映射表
2. 枚举值校验表
3. 页面一致性检查结果
4. 验收样例与问题清单
```
---
## 9. 推荐启动顺序
建议在 `../water-frontend` 中按以下顺序启动:
1. Leader 先下发统一任务说明
2. 并行启动:
- Lane A
- Lane B
- Lane D
- Lane E
3. Lane B 稳定后启动 Lane C
4. 最后启动 Lane F 贯穿联调与验收
---
## 10. Leader 启动提示(可直接复制)
```text
请在 ../water-frontend 中执行 REV-004 管理后台前端实现。
唯一需求依据:
- docs/guides/REV004_FRONTEND_IMPLEMENTATION_HANDOFF.md
- .omx/plans/2026-04-07-rev004-frontend-implementation-plan.md
- .omx/specs/deep-interview-rev004-frontend-modules.md
- REV-004 四层正式文档
范围限定:
- 仅管理后台
- 不做微网厅 / 客户端
- 不做完整 BPM 前端
执行原则:
- 保持当前前端范式
- 核心操作路径尽量像旧系统
- 统一入口只做导航 / 聚合
- 第一档对象独立菜单冻结,第二档对象不得升顶层菜单
- 先冻结 IA再收敛 DTO / 状态细节
请按 Lane A-F 分工推进,并在最后统一输出:
1. 页面清单
2. 路由清单
3. 通用组件清单
4. lane ownership 清单
5. 代码变更摘要
6. 联调问题清单
```

View File

@ -0,0 +1,149 @@
# REV004 前后端开放接口开发规范(/business 域)
## 1. 文档目的
本规范用于统一 `water-backend``water-frontend` 的开放接口风格,作为后续新增、改造、评审和验收的共同标准。
## 2. 适用范围
- 后端:`sw-business/sw-business-server/src/main/java/.../controller/admin/**`
- 前端:`water-frontend/src/api/**``water-frontend/src/views/**`
- 重点域:`/business/**`
## 3. 现状基线(截至 2026-04-23
### 3.1 导出接口现状
- `/business` 导出映射约 **73**
- `/export-excel`**58**(主流)
- `/export`**7**(次主流)
- 其他:`/export-details``/export-check``/export-failed`
- 导出实现主流为:
- `ExcelUtils.write(...)`
- `ExcelUtils.writeWithTemplate(...)`
- 前端导出主流为:
- `request.download(...)`
- 失败数据回传导出:`request.postDownload(...)`
### 3.2 当前特例(需治理)
`/business/accounting-adjust` 下存在 CSV 特例:
- `GET /business/accounting-adjust/log-export`
- `GET /business/accounting-adjust/prestorage-export`
上述接口使用手写 `writeCsv(...)`,与整体 Excel 导出主流不一致。
---
## 4. 对前端开放接口统一标准V1
### 4.1 路由命名标准
| 场景 | 标准路由 | 说明 |
|---|---|---|
| 分页查询 | `GET /page` | 返回 `PageResult` |
| 详情 | `GET /get` | 单条详情 |
| 新增 | `POST /create` | 创建资源 |
| 更新 | `PUT /update` | 更新资源 |
| 删除 | `DELETE /delete` | 单条删除 |
| 导出(推荐) | `GET /export-excel` | 统一导出命名 |
| 导出(兼容) | `GET /export` | 历史接口保留过渡 |
| 失败数据导出 | `POST /export-failed` | 适配前端失败列表回传 |
> 要求:新增接口优先 `export-excel`;历史 `export` 可保留,逐步迁移。
### 4.2 返回体标准
| 类型 | 标准 |
|---|---|
| 普通接口 | `CommonResult<T>` |
| 分页接口 | `CommonResult<PageResult<T>>`(字段固定 `list``total` |
| 导出接口 | 二进制流(不包 `CommonResult` |
### 4.3 权限与审计标准
- 导出接口权限统一:`@PreAuthorize("@ss.hasPermission('business:<module>:export')")`
- 导出接口必须标注:`@ApiAccessLog(operateType = EXPORT)`
### 4.4 参数与校验标准
- Query 分页参数统一 `*PageReqVO`
- 使用 `@Valid` / `@Validated` 触发参数校验
- 必填、范围、格式使用 jakarta validation 注解
- 日期区间优先显式双端字段;如需兼容前端 range 参数,可在 Controller 层做兼容解析
### 4.5 错误处理标准
- Controller 层禁止直抛 `IllegalArgumentException`
- 统一使用 `ServiceException + ErrorCode`
- 统一由 `GlobalExceptionHandler` 转换为 `CommonResult` 响应
- 错误信息需可读、可定位、可回溯
### 4.6 导出实现标准
- 默认导出:`ExcelUtils.write(...)`
- 模板导出:`ExcelUtils.writeWithTemplate(...)` / `ExcelUtils.writeTemplate(...)`
- 禁止新增手写 CSV 导出实现
### 4.7 前端对接标准
- 常规:`request.get/post/put/delete`
- 导出:`request.download`
- 失败导出:`request.postDownload`
- JSON 响应按 `code===200` 判成功(导出接口除外)
---
## 5. 开发与评审检查清单(必须执行)
### 5.1 新增接口检查
- [ ] 路由命名符合标准
- [ ] 权限点符合 `business:<module>:<action>`
- [ ] 参数 VO 有校验注解
- [ ] 响应类型为 `CommonResult` 或导出流
- [ ] 无 `IllegalArgumentException` 直抛
### 5.2 导出接口检查
- [ ] 使用 `GET /export-excel`(新接口)
- [ ] 使用 `ExcelUtils.*` 导出
- [ ] 标注 `@ApiAccessLog(operateType = EXPORT)`
- [ ] 具备 `...:export` 权限
- [ ] 前端 `request.download` 可直接对接
### 5.3 联调检查
- [ ] 同筛选条件下“分页结果”和“导出结果”口径一致
- [ ] 导出文件可正常打开,列头与业务语义一致
- [ ] 异常场景返回结构可被前端统一处理
---
## 6. 账务调整模块专项治理要求
### 6.1 现状
- `log-export` / `prestorage-export` 仍为 CSV 特例
### 6.2 治理目标
- 与 `/business` 主流导出对齐为 Excel 导出
- 保证前端调用方式不变或最小变更
### 6.3 推荐迁移策略
1. 保留旧路由(兼容)
2. 新增/切换到 Excel 实现(`ExcelUtils`
3. 增加 `@ApiAccessLog(EXPORT)`
4. 回归通过后,再评估是否下线 CSV 旧实现
---
## 7. 发布门禁DoD
- [ ] 编译通过
- [ ] 导出接口静态扫描通过(权限、审计、实现方式)
- [ ] 前后端 smoke 联调通过(至少 3 个页面)
- [ ] 变更说明写入对应 feature 文档或 evidence
---
## 8. 附:可直接执行的中文命令示例
```bash
# 1进入后端仓库
cd /Volumes/Dpan/github/water-workspace/water-backend
# 2扫描 /business 导出接口分布
rg -n "@GetMapping\(\"/export|@PostMapping\(\"/export|writeCsv\(|ExcelUtils\.write" sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin -S
# 3扫描是否存在 Controller 直抛 IllegalArgumentException
rg -n "throw new IllegalArgumentException" sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin -S
# 4进入前端仓库检查下载对接
cd /Volumes/Dpan/github/water-workspace/water-frontend
rg -n "request\.download\(|request\.postDownload\(" src/api -S
```

View File

@ -0,0 +1,94 @@
# REV004 联调通知(短版)
各位前端 / 测试同学REV004 本轮后端能力已合入 `develop`,现在可以开始联调:
---
## 一、本轮已可联调
### 1. 分账专属接口
- `POST /admin-api/business/split-adjust/submit`
- `GET /admin-api/business/split-adjust/page`
- `GET /admin-api/business/split-adjust/detail`
- `GET /admin-api/business/split-adjust/result`
### 2. 违约金减免
- 支持按金额 / 按日期
- 支持申请人、联系电话、申请原因、附件
- formal-table 闭环已打通
### 3. 预存转账
已补齐并可回显:
- `targetCustName`
- `targetCustAddress`
- `applicant`
- `contactMobile`
- `remainingAmount`
---
## 二、可直接用的联调样例
### 分账成功
- `splitAdjustNo = REV004-SPLIT-API-001`
- `adjustmentNo = REV004-SPLIT-API-001`
### 违约金减免成功
- `adjustmentNo = REV004-LATEFEE-API-001`
### 违约金减免待审批
- `adjustmentNo = REV004-LATEFEE-API-002`
---
## 三、测试环境
- `application-dev` 指向测试库已补齐相关表结构
- real-db canary 已通过
---
## 四、重点建议验证
1. 分账提交 → 详情 → 结果回显
2. 违约金减免按金额 / 按日期两种模式
3. 预存转账提交后分页/详情回显是否完整
4. 日志/附件/流程查询是否一致
---
## 五、当前边界
### 已覆盖
- 分账专属接口
- 违约金减免 formal-table
- 预存转账关键字段回显
### 未完全覆盖
- 预存 formal-table 还没单独建
- 坏账 / 核销 / 价差还没全部走 formal-table 路线
---
## 六、反馈方式
如果发现问题,请直接带:
- 接口路径
- 请求参数
- 返回报文
- 对应 `adjustmentNo / splitAdjustNo`
这样后端可以直接定位。
---
## 七、当前 develop 版本
当前 develop 已包含:
- PR #73late-fee formal-table 闭环
- PR #74:预存转账字段留痕与查询回显
最新 develop merge commit
- `0d1e6d1d92fab7b59afc1f046ab08527691e9811`
---

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,563 @@
# REV-004 全量账务实现分发清单
## 1. 文档目的
本文用于将 `REV-004` 全量账务领域的当前正式口径,整理为可直接分发给前后端实现 Agent 的任务清单、需求边界与协作约束,避免后续实现阶段再次回到“一期最小闭环”或“旧表整表平移”的摇摆状态。
## 2. 当前统一口径
### 2.1 当前正式事实
- 当前正式统一入口:`IF-REV-007`
- `REV-004` 已在概要、详设、数据库、接口四层正式文档中完成首轮收口
- 当前正式表达仍采用:
- 当前事实
- 目标设计
- 历史只读 / 映射层
三层分离口径
### 2.2 当前目标对象范围
当前全量账务对象范围包括:
- `PrepaidRefund`
- `RedinkRecord`
- `WrittenoffAdjust`
- `PriceDiffAdjust`
- `LateFeeReduce`
- `BadDebtRecord`
- `SplitAdjust`
- 特殊开账 / 特账
- `RefundBill`
- `CrossCycleWaterRecord`
### 2.3 当前设计约束
1. 不得把目标设计写成当前已全部实现事实。
2. 当前统一入口仍是 `IF-REV-007`,不能在未明确批准前直接改成多套正式专属接口编号。
3. 旧系统对象必须分层承接:
- 在线统一骨架
- 独立业务对象
- 查询投影对象
- 历史只读 / 映射对象
4. Archive 原始资料为证据来源,不作为当前实现修改目标。
---
## 3. 分发给后端 Agent 的功能清单
### BE-01 统一账务处理入口扩展
**目标**
扩展 `IF-REV-007`,使其从一期五类场景提升为可承接全量账务对象的统一入口。
**需求**
后端统一入口需支持以下对象类型:
- `PREPAID_REFUND`
- `REDINK_RECORD`
- `WRITTENOFF_ADJUST`
- `PRICE_DIFF_ADJUST`
- `LATE_FEE_REDUCE`
- `BAD_DEBT_RECORD`
- `SPLIT_ADJUST`
- `SPECIAL_BILLING`
- `REFUND_BILL`
- `CROSS_CYCLE_WATER`
**最小输入要求**
- `chargeId`
- `objectType`
- `adjustType`(兼容保留)
- `adjustAmount`
- `adjustUsage`
- `sourceTradeNo`
- `relatedBizNo`
- `reasonCode`
- `approvalRequired`
- `remark`
- `attachmentList`
- `operatorId`
**最小输出要求**
- `adjustmentNo`
- `chargeId`
- `objectType`
- `resultStatus`
- `writeBackStatus`
- `approvalRequired`
- `approvalStatus`
- `resultObjectNo`
- `msg`
**验收重点**
- `IF-REV-007` 仍是当前统一入口
- 不要求一次性拆专属接口
- `objectType` 成为统一对象表达核心字段
---
### BE-02 账务对象服务层分派机制
**目标**
建立 `objectType -> handler/service` 分派机制。
**需求**
至少按对象族拆分内部处理逻辑:
- 预存退款处理
- 红冲处理
- 已销调整处理
- 价差调整处理
- 违约金减免处理
- 坏账处理
- 分账调整处理
- 特账 / 特殊开账处理
- 退款结果对象处理
- 跨周期水量支撑处理
**验收重点**
- 外部继续统一入口
- 内部分派清晰
- 不再把所有对象混成单一“金额调整”逻辑块
---
### BE-03 审批边界承接
**目标**
统一承接审批能力位,而不是直接落完整 BPM。
**需求**
至少保留:
- `approvalRequired`
- `approvalStatus`
- `legacyTaskId`
- `legacyStepId`
- `legacyFlowRemark`
**当前对象分层要求**
- 倾向独立审批流对象:
- `PrepaidRefund`
- `BadDebtRecord`
- 当前先保留审批能力位:
- `WrittenoffAdjust`
- `PriceDiffAdjust`
- `LateFeeReduce`
- `SplitAdjust`
- `RedinkRecord`
- 不单独审批:
- `RefundBill`
- `CrossCycleWaterRecord`
**验收重点**
- 承接审批语义即可
- 本轮不要求直接接完整 BPM 引擎
---
### BE-04 统一留痕能力增强
**目标**
强化 `biz_operat_log` / `biz_operat_log_detail` 的账务承接能力。
**需求**
至少留痕:
- 对象类型
- 目标账单
- 原交易引用
- 原业务单号
- 原因说明
- 金额/水量前后差异
- 审批状态
- 操作人
- 操作时间
- 附件依据
**验收重点**
- 各账务对象都能统一追溯
- 能支撑历史查询和迁移核查
---
### BE-05 数据库统一骨架承接
**目标**
继续以 `biz_charge` / `biz_charge_detail` 为统一骨架,不盲目整表平移。
**需求**
后端实现需遵循:
- 新发生业务优先挂统一骨架
- 不强制每个对象立刻单独建表
- 对目标对象允许先通过:
- 统一骨架
- 目标字段组
- 历史映射
进行承接
**特别要求**
需重点考虑后续承接的目标对象:
- `AccountingWorkflowRef`
- `AccountingLegacyMapping`
---
### BE-06 历史只读 / 映射层查询承接
**目标**
旧系统细粒度台账不能丢,需要有后端查询承接。
**至少保留查询能力的对象**
- 红冲记录
- 预存退款历史
- 已销调整历史
- 价差调整历史
- 分账调整历史
- 违约金减免历史
- 坏账历史
- 退款账结果
- 跨周期水量
- 实时收费日志
- 对账日志
- IC 卡账务
**验收重点**
- 不要求这些对象都转成在线主写表
- 但必须可查、可追溯、可迁移验收
---
### BE-07 统一查询出口能力
**目标**
准备两类查询能力:
#### 第一类:业务对象查询
面向:
- `PrepaidRefund`
- `RedinkRecord`
- `WrittenoffAdjust`
- `PriceDiffAdjust`
- `LateFeeReduce`
- `BadDebtRecord`
- `SplitAdjust`
- `RefundBill`
#### 第二类:历史只读 / 投影查询
面向:
- 实时收费
- 对账日志
- 柜台结账
- IC 卡账务
- 历史账务台账
**验收重点**
- 至少按 `objectType` 可区分
- 支持汇总对账 + 明细追溯
---
## 4. 分发给前端 Agent 的功能清单
### FE-01 统一账务处理入口页面改造
**目标**
前端继续使用统一账务处理入口,但支持全量对象类型。
**需求**
至少支持:
- 对象类型选择 / 识别
- 按对象动态显示字段
- 审批状态展示
- 处理结果展示
- 账单回写状态展示
**最低对象类型支持**
- 预存退款
- 红冲
- 已销调整
- 价差调整
- 违约金减免
- 坏账
- 分账调整
- 特账
- 退款账结果查询
- 跨周期水量查询
---
### FE-02 对象查询页 / 结果页分层
**目标**
查询页按两层口径组织。
#### 业务对象查询页
至少展示:
- 单号
- 对象类型
- 关联账单
- 状态
- 审批状态
- 金额 / 水量差异
- 时间
#### 历史只读查询页
至少展示:
- 原单号
- 旧系统标识
- 来源时间
- 映射状态
- 查询摘要
**验收重点**
- 不把历史只读查询和在线处理页混成一个页面
---
### FE-03 审批状态展示
**目标**
统一展示审批边界,但不假设 BPM 已完整落地。
**需求**
至少展示:
- 是否需要审批
- 当前审批状态
- 审批意见摘要(如有)
- 历史流程引用摘要(如有)
---
### FE-04 统一结果表达
**目标**
在 REV-004 页面中统一表达处理结果。
**至少统一展示**
- `resultStatus`
- `writeBackStatus`
- `approvalStatus`
- `resultObjectNo`
- `msg`
**验收重点**
- 不再只显示“成功 / 失败”
- 要能区分:
- 处理成功
- 待审批
- 回写失败
- 部分成功
---
### FE-05 历史迁移核查页 / 对账页
**目标**
给迁移验收和历史比对保留前端展示入口。
**建议查询维度**
- 客户号
- 手机号
- 表号
- 账期
- 营业所
- 渠道
- 业务单号
- `objectType`
- 状态
- 时间范围
**验收重点**
- 能看汇总
- 能点进明细
- 能定位单据级差异
---
## 5. 联调 / 共同实现清单
### JOINT-01 枚举与字段统一
前后端统一:
- `objectType`
- `adjustType`
- `resultStatus`
- `writeBackStatus`
- `approvalStatus`
### JOINT-02 当前事实与目标设计分层
必须明确:
- 哪些对象当前只是在统一入口下承接
- 哪些对象只是目标设计命名
- 哪些对象当前只支持查询,不支持在线提交
### JOINT-03 历史查询口径统一
统一以下查询口径:
- 业务对象查询
- 历史只读查询
- 投影查询
- 映射查询
### JOINT-04 迁移验收维度统一
统一核查维度:
- 对象类型
- 状态
- 审批状态
- 原单号 / 新单号
- 金额 / 水量
- 时间
- 关联账单
---
## 6. 建议拆给其他 Agent 的任务包
### Agent A后端统一入口改造
负责:
- `IF-REV-007` 输入输出扩展
- `objectType` 承接
- 统一 handler 分派机制
### Agent B后端账务查询与历史兼容
负责:
- 业务对象查询出口
- 历史只读 / 投影查询出口
- 映射层查询支撑
### Agent C后端审批 / 留痕承接
负责:
- `approvalRequired`
- `approvalStatus`
- 操作留痕增强
- 历史流程字段承接
### Agent D前端统一账务处理页面
负责:
- 统一入口页面改造
- 对象差异化表单展示
- 统一结果展示
### Agent E前端账务查询与迁移核查页面
负责:
- 业务对象查询页
- 历史只读查询页
- 差异定位展示
### Agent F联调与口径校验
负责:
- 枚举值统一
- 字段命名统一
- 页面与接口回包一致性校验
- 历史查询与迁移验收口径校验
---
## 7. 最小交付顺序建议
1. 后端统一入口改造
2. 后端查询出口改造
3. 前端统一入口页面改造
4. 前端查询页改造
5. 审批 / 留痕补强
6. 迁移验收 / 历史核查联调
---
## 8. 给其他 Agent 的任务提示模板
```text
任务背景
当前正在推进 REV-004 全量账务领域实现,对应正式文档已完成四层回写:详设、数据库、接口、概要。当前正式统一入口为 IF-REV-007目标是在不破坏当前统一入口事实的前提下承接全量账务对象。
实现约束
1. 不要把目标设计写成当前已完全实现事实
2. 当前统一入口仍是 IF-REV-007
3. 全量对象包括:
- PrepaidRefund
- RedinkRecord
- WrittenoffAdjust
- PriceDiffAdjust
- LateFeeReduce
- BadDebtRecord
- SplitAdjust
- 特殊开账 / 特账
- RefundBill
- CrossCycleWaterRecord
4. 历史对象需区分:
- 在线统一骨架
- 独立业务对象
- 查询投影
- 历史只读 / 映射层
请完成
- 你负责的子任务:[填写 Agent A/B/C/D/E/F 的任务]
- 输出:
1. 变更清单
2. 实现口径说明
3. 风险点
4. 与正式文档是否一致
文档依据
- docs/design/02_Detailed_Design/12_REV_Detailed.md
- docs/design/03_Technical_Design/01_Database_Design.md
- docs/design/03_Technical_Design/03_Interface_Design.md
- docs/design/01_Overview/03_Summary_Design.md
- docs/guides/REV004_FULL_ACCOUNTING_DOMAIN_DESIGN.md
```
---
## 9. 参考文档
- `docs/design/02_Detailed_Design/12_REV_Detailed.md`
- `docs/design/03_Technical_Design/01_Database_Design.md`
- `docs/design/03_Technical_Design/03_Interface_Design.md`
- `docs/design/01_Overview/03_Summary_Design.md`
- `docs/guides/REV004_FULL_ACCOUNTING_DOMAIN_DESIGN.md`

View File

@ -0,0 +1,137 @@
# REV004 预存退款/转账原系统证据与待确认规则对照表
## 文档目的
用于区分:
1. **原系统操作文档中已有直接证据**的规则/UI 形态
2. **当前仅来自法规口径、业务访谈或实现推断**,尚需产品/财务/制度确认的规则
避免把“原系统已证明”和“待确认业务规则”混写。
---
## 一、证据来源
主要依据:
- `docs/design/04_Appendix/Archive/02_Manuals/营收系统_用户操作手册.md`
- `docs/design/04_Appendix/Archive/04_Original_Attachments/营收系统_用户操作手册.md`
关键章节:
- `12.3 预存调整`
- `3.3.4 客户欠费缴费`
关键截图:
- `营收系统_用户操作手册_images/media/image486.png`
---
## 二、原系统可直接证明的内容
| 主题 | 结论 | 证据类型 | 说明 |
|---|---|---|---|
| 预存调整功能存在 | 是 | 菜单/章节 | 原系统存在【账务处理】-【预存调整】模块 |
| 预存调整支持“新增” | 是 | 操作文案 | 文档明确说明点击“新增”后可添加预存调整 |
| 新增时必须输入系统存在的户号 | 是 | 操作文案 | 文档明确写“户号必须是系统中存在的户号” |
| 新增时先选客户,再自动带出客户信息 | 是 | 截图 | 从新增弹窗可见客户编号、客户名称、客户地址、用水性质、预存金额、未到账金额为联动展示结构 |
| 预存调整分为“预存退款/预存转账”两类 | 是 | 截图 | 新增弹窗存在两个 tab`预存退款``预存转账` |
| 预存退款页需要录入申请信息 | 是 | 截图 | 可见字段:退款金额、剩余金额、申请人、联系电话、申请原因、备注、上传 |
| 客户详情会同时展示预存与欠费统计 | 是 | 文案 | 文档明确写客户详情界面会展示预存、欠费笔数、欠费金额、应缴金额 |
| 预存与欠费在操作视图中有关联 | 是 | 文案/UI | 从客户详情汇总展示与预存调整入口形态可推断前台操作上是联动感知的 |
---
## 三、当前只能视为“待确认”的规则
以下规则**未在原系统操作手册中找到直接证据**,当前不能写成“原系统已确认规则”:
| 规则 | 当前状态 | 说明 |
|---|---|---|
| 用户对预存余额享有所有权 | 待确认 | 属于法规/制度表达,非原系统操作文案 |
| 退款必须原路返还 | 待确认 | 原系统手册未见“原路返还”明确表述 |
| 退款不收手续费 | 待确认 | 原系统手册未见 |
| 退款前必须先抵扣欠费 | 待确认 | 原系统手册未见明确账务顺序 |
| 预存抵扣顺序:违约金 → 水费本金 → 其他费用 | 待确认 | 这是强财务规则,原手册未给出 |
| 同主体、同区域、同公司才能转账 | 待确认 | 原手册仅证明“有预存转账”,未证明限制条件 |
| 有预存则不产生坏账 | 待确认 | 属于业务推断,非原系统直接表述 |
| 销户退款必须先清欠费再退款 | 待确认 | 原手册未见直接条文 |
| 已核销坏账与预存退款完全独立核算 | 待确认 | 原系统操作手册未给出 |
---
## 四、对当前 REV004 实现/设计的建议口径
### 4.1 可直接作为“原系统一致”的内容
可以写成:
1. 原系统存在**预存调整**模块
2. 预存调整包含:
- 预存退款
- 预存转账
3. 新增预存调整时,先选择客户/户号,再自动带出:
- 客户名称
- 客户地址
- 用水性质
- 预存金额
- 未到账金额
4. 退款页需要申请信息:
- 申请人
- 联系电话
- 申请原因
- 备注
- 附件
### 4.2 只能作为“待制度确认”的内容
建议写成:
> 以下规则依据法规理解、业务访谈或拟实现口径提出,尚未从原系统操作文档中找到直接证据,需产品/财务/制度进一步确认:
- 原路返还
- 不收手续费
- 抵扣顺序
- 转账主体/区域限制
- 清欠前置规则
- 预存与坏账/核销的强约束关系
---
## 五、建议给产品/财务确认的问题清单
1. 预存退款是否必须**原路返还**
2. 预存退款是否允许**线下转账**或人工指定退款账户?
3. 预存退款是否存在**手续费**或其他财务限制?
4. 有欠费时,是否必须**先抵扣后退款**
5. 如需抵扣,抵扣顺序是否为:
- 违约金
- 水费本金
- 其他费用
6. 预存转账是否仅允许:
- 同主体
- 同区域
- 同公司
7. 销户退款是否必须先完成:
- 销户
- 清欠
- 无冻结/无争议
8. 坏账/核销状态下,预存是否仍可退/可转?
---
## 六、当前结论
### 已有原系统证据的部分
- 功能存在
- UI 形态存在
- 关键字段存在
- 客户选择后信息自动带出这一交互形态基本成立
### 尚未有原系统直接证据的部分
- 财务清算规则
- 原路退款规则
- 抵扣优先级
- 转账限制边界
- 与坏账/核销的强约束关系
因此,当前最稳妥的做法是:
- **UI 与字段设计**可参考原系统直接推进
- **资金与账务规则**必须单独走确认流程
---

View File

@ -0,0 +1,22 @@
# REV004 账务调整统一化改造 — 第一批开发入口2026-04-15
## 说明
基于已完成的 P0 标准层工件,本轮已进一步拆出第一批可执行开发任务单,作为后续真正进入代码实现的入口。
## 第一批试点对象
- `late-fee reduce`(违约金减免)
## 第一批任务单
- `.omx/plans/first-batch-dev-tasklist-rev004-accounting-adjust-unification.md`
## 核心内容
1. 最小标准层 helper 落地
2. late-fee reduce 字典统一
3. late-fee reduce 状态映射冻结
4. formal-table 申请态写入
5. date-mode 最小执行闭环
6. page/log-page 摘要字段补齐
7. 测试闭环建立
## 用途
该任务单用于把“标准层规划”正式推进到“试点对象实现”。

View File

@ -0,0 +1,23 @@
# REV004 账务调整统一化改造 — P0 标准层启动证据2026-04-15
## 已完成
本轮已将统一化改造的 P0 标准层拆分成独立工件,供后续直接执行:
- `.omx/plans/standard-layer/rev004-accounting-adjust-semantic-map.md`
- `.omx/plans/standard-layer/rev004-accounting-adjust-dict-and-status-strategy.md`
- `.omx/plans/standard-layer/rev004-accounting-adjust-readmodel-matrix.md`
- `.omx/plans/standard-layer/rev004-accounting-adjust-canonical-semantic-layer.md`
- `.omx/plans/standard-layer/rev004-accounting-adjust-test-matrix.md`
- `.omx/plans/p0-tasklist-rev004-accounting-adjust-unification-standard-layer.md`
## 收口原则
- REV004 当前接口模型为主真值
- legacy 仅作映射与迁移参考
- 不强制前端改字段名
- 先做标准层,再做多对象 runtime 与迁移
## 本轮用途
这些工件用于:
1. 统一字段语义
2. 统一字典与状态口径
3. 统一读模型输出
4. 为 runtime 正式落库和历史迁移提供统一语义层

View File

@ -0,0 +1,47 @@
# REV004 accountLog 操作链接线说明2026-04-13
## 结论
当前 `accountLog` 的 4 条操作链在后端契约层已具备接线条件,不需要继续补新的稳定字段;前端主要做提交映射即可。
## 1. 查看流程 `log-process`
- 前端来源:`handleProcess(row)`
- 后端接口:`GET /business/accounting-adjust/log-process`
- 后端入参:`adjustmentNo`
- 当前行数据可用字段:`row.adjustmentNo`
- 接线结论:可直接接
## 2. 查看附件 `log-attachments`
- 前端来源:`handleAttachment(row)`
- 后端接口:`GET /business/accounting-adjust/log-attachments`
- 后端入参:`adjustmentNo`
- 当前行数据可用字段:`row.adjustmentNo`
- 接线结论:可直接接
## 3. 转退款 `log-refund`
- 前端来源:`RefundForm`
- 后端接口:`POST /business/accounting-adjust/log-refund`
- 后端请求对象:`AccountingAdjustSecondaryActionReqVO`
- 推荐映射:
- `adjustmentNo = row.adjustmentNo`
- `reason = form.remark`
- `collectionMethod = form.collectionMethod`
- `refundUser = form.refundUser`
- `attachmentRefs` = 前端上传结果(如有)
- 说明:前端 `refundAmount` 当前仅用于展示,不是后端必填字段
- 接线结论:可接,主要是前端提交映射
## 4. 转预存 `log-prestorage`
- 前端来源:`TransferPrestoreForm`
- 后端接口:`POST /business/accounting-adjust/log-prestorage`
- 后端请求对象:`AccountingAdjustSecondaryActionReqVO`
- 推荐映射:
- `adjustmentNo = row.adjustmentNo`
- `targetCustCode = form.targetCustCode`
- `reason = form.remark`
- `attachmentRefs` = 前端上传结果(如有)
- 说明:前端 `transferAmount` 当前仅用于展示,不是后端必填字段
- 接线结论:可接,主要是前端提交映射
## 补充说明
- 本轮后端新增的 `accountLog.custId` 已可支撑前端客户编号点击后稳定跳客户详情。
- `sold` 当前列表/弹窗展示字段已基本满足,不建议继续为展示字段扩充后端响应。

View File

@ -0,0 +1,148 @@
# REV004 / accountProcess 字典 label 与 UI 文案一致性摘要2026-04-13
## 1. 目的
本摘要用于收敛 `accountProcess` 当前涉及的状态/标签类字段,确认:
- 代码侧字典 type 是否已统一
- 正式文档口径是否与代码一致
- 是否仍存在旧快照 / 旧产出中的历史口径残留
---
## 2. 当前已确认一致的口径
### A. 工单状态 `work_status`
代码侧:
- `DictTypeConstants.WORK_STATUS = "work_status"`
- 口径:`0-未处理 / 1-已审核 / 2-已完成 / 3-已撤销`
正式 SQL seed
- `../water-docs/sql/rev004_account_adjust_dict_seed.sql`
- 已定义:
- `0 -> 未处理`
- `1 -> 已审核`
- `2 -> 已完成`
- `3 -> 已撤销`
正式数据库设计文档:
- `../water-docs/docs/design/03_Technical_Design/01_Database_Design.md`
- 当前已同步为四态:
- `0-未处理(工单创建,待审核/待处理)`
- `1-已审核(审核通过/无需审批,待完成)`
- `2-已完成(处理成功且已回写完成)`
- `3-已撤销(工单已撤销)`
结论:
- **代码 / seed SQL / 正式设计文档:已一致**
### B. REV004 账务调整核心状态字典
已确认存在统一 type
- `account_adjust_object_type`
- `account_adjust_result_status`
- `account_adjust_approval_status`
- `account_adjust_writeback_status`
代码侧使用:
- `AccountingAdjustLogProcessServiceImpl`
- `AccountingAdjustSoldProcessServiceImpl`
中均已通过 `DictFrameworkUtils.parseDictDataLabel(...)` 或统一映射读取 label
正式 SQL seed
- `../water-docs/sql/rev004_account_adjust_dict_seed.sql`
- 已定义:
- objectType`PREPAID_REFUND / REDINK_RECORD / BAD_DEBT_RECORD / WRITTENOFF_ADJUST / PRICE_DIFF_ADJUST / LATE_FEE_REDUCE / SPLIT_ADJUST`
- resultStatus`SUCCESS / PENDING_APPROVAL / FAIL`
- approvalStatus`NOT_REQUIRED / PENDING_APPROVAL / APPROVED / REJECTED`
- writeBackStatus`UPDATED / PENDING / SKIPPED`
结论:
- **REV004 核心状态字典类型与值域已成套收口**
### C. 收费方式 `charge_method`
代码侧:
- `DictTypeConstants.CHARGE_METHOD = "charge_method"`
- 已在 `AccountingAdjustSoldProcessServiceImpl` 中作为列表展示 label 来源
正式文档:
- `../water-docs/sql/lhc_数据库设计.md`
- `../water-docs/docs/design/04_Appendix/Archive/03_Design_Docs/数据库设计.md`
中可见 `charge_method` 语义为收费途径/收费方式
结论:
- **当前 sold 查询页“收费方式”展示依赖路径明确,代码口径稳定**
---
## 3. 当前已确认的“动态原因字典”绑定口径
参考:
- `../water-docs/docs/guides/REV004_DICT_BINDING_MATRIX.md`
当前已明确:
- `PREPAID_REFUND -> deposit_reason`
- `REDINK_RECORD -> redink_reason`
- `BAD_DEBT_RECORD -> knotty_reason`
- `WRITTENOFF_ADJUST -> payment_reason`(过渡复用)
- `PRICE_DIFF_ADJUST -> price_reason`
- `LATE_FEE_REDUCE -> late_fee_reason`
- `SPLIT_ADJUST -> separate_reason`
结论:
- **前端原因下拉不应绑定一个统一 reason 字典,而应按 objectType 动态切换**
---
## 4. 当前仍存在的不一致 / 残留风险
### A. 历史快照文档仍残留旧三态 `work_status`
在旧归档快照中,仍能看到:
- `../water-docs/docs/design/04_Appendix/Archive/08_Formal_Doc_Snapshots/RWB-02/2026-04-03-RWB-02-01_Database_Design.md`
- 其中 `work_status` 仍写作:`0-待处理 / 1-处理中 / 2-已完成`
这与当前正式口径不一致。
结论:
- **历史快照存在旧口径残留,但不应再作为当前真值来源**
### B. output 产物也有旧口径残留
例如:
- `../water-docs/output/01_Database_Design_processed.md`
中仍可见旧三态 `work_status`
结论:
- **产出型文件存在滞后,需要明确“正式设计文档 + REV004 seed + 代码”为真值源**
### C. UI 文案一致性尚未逐页核验
虽然字典 type / 值域已统一,但还没有逐页确认:
- 页面上显示的是字典 label 还是代码 fallback 值
- 页面文案是否与字典 label 完全一致
- 历史页面是否仍在用旧文案(例如“处理中”)
结论:
- **字典结构已较稳定,但 UI 最终展示一致性仍需前端/UAT 联合核验**
---
## 5. 当前建议真值源优先级
建议按以下优先级使用:
1. **后端代码中的 DictTypeConstants + Service 映射逻辑**
2. **`rev004_account_adjust_dict_seed.sql`**
3. **正式技术设计文档 `01_Database_Design.md`**
4. 历史快照 / output 产物(仅供追溯,不作为当前真值)
---
## 6. 对前端 / UAT 的建议
1. `work_status` 统一按四态使用:
- 0 未处理
- 1 已审核
- 2 已完成
- 3 已撤销
2. `objectType / resultStatus / approvalStatus / writeBackStatus` 使用 REV004 新字典,不再依赖旧业务大类字段硬编码。
3. “原因”下拉必须按 `objectType` 动态切换,不要做单字典通配。
4. 若页面仍显示“处理中”等旧文案,应优先视为前端展示遗留,而不是后端当前真值。
---
## 7. 当前结论
- **核心字典 type 与值域已基本收口**
- **正式文档与 seed SQL 已与代码当前口径对齐**
- **残留风险主要在旧快照/旧输出文件与前端展示层,而不在当前后端主实现**

View File

@ -0,0 +1,257 @@
# REV004 / accountProcess 最终评审摘要2026-04-13
## 1. 评审目的
本摘要用于对外汇报 `REV004 / accountProcess` 当前后端交付状态,统一回答以下问题:
- 这一块后端接口是否已形成可联调的稳定口径?
- 关键页面/弹窗对应的接口是否已经落地?
- 真实数据库条件下,主链路是否已经被证明可运行?
- 当前仍有哪些剩余风险与建议后续动作?
---
## 2. 结论摘要
### 结论
`REV004 / accountProcess` 当前已经达到:
- **接口口径已基本收口**
- **页面/弹窗映射已梳理清楚**
- **主链路已完成真实数据库验证**
- **可进入联调 / 评审 / 阶段性验收**
### 当前结论边界
这里的“通过”是指:
- 后端接口已存在并按当前代码口径稳定返回
- 关键提交流程、撤销流程、日志二次动作、主要查询面均有真实库证据
- 不代表已经完成 UI 层面的最终视觉/交互验收
- 不代表所有页面文案、字典 label、边角筛选条件都已逐项核验
---
## 3. 当前已沉淀产物
### A. 集成测试累计证据
- `docs/evidence/rev004-accountprocess-integration-testing-bootstrap-2026-04-13.md`
### B. 接口真值矩阵
- `docs/evidence/rev004-accountprocess-interface-truth-matrix-2026-04-13.md`
### C. 页面元素勾稽矩阵
- `docs/evidence/rev004-accountprocess-ui-element-matrix-2026-04-13.md`
- `docs/evidence/rev004-accountprocess-dict-ui-consistency-summary-2026-04-13.md`
- `docs/evidence/rev004-accountprocess-page-label-audit-2026-04-13.md`
- `docs/evidence/rev004-accountprocess-ui-orchestration-checklist-2026-04-13.md`
### D. 真实库集成测试代码
- `sw-business-server/src/test/java/.../integration/rev004/accountprocess/Rev004AccountProcessCanaryQueryIntegrationTest.java`
---
## 4. 范围说明
本轮范围仅覆盖:
- `REV004/accountProcess`
具体包括四类页面/域:
- 预存调整prestorage
- 已销调整sold
- 未销调整unsold
- 账务日志accountLog / log
本轮明确不扩散到:
- accountProcess 之外的其它业务模块
- 全项目通用测试框架治理
- UI 自动化或前端渲染层验证
---
## 5. 环境口径
### 测试策略
- **数据库:真实 PostgreSQL**
- **数据库外依赖:替身化 / MockBean**
### 已明确替身边界
例如:
- `TransactionApi`
- `ApiErrorLogCommonApi`
- `DictDataCommonApi`
- `OperateLogCommonApi`
- `RedissonClient`
### 当前价值
这意味着:
- 账务状态变化、operat_log 留痕、查询回看等核心真值来自真实数据库
- 外部交易/缓存/公共 RPC 不会干扰主链路验证
---
## 6. 最新验证结果
### 最新 surefire 结果
- `Rev004AccountProcessCanaryQueryIntegrationTest`
- **21 tests / 0 fail / 0 skip**
- `Rev004AccountProcessIntegrationFixtureAssetsTest`
- **1 pass**
- `Rev004AccountProcessLiveDbReadinessTest`
- **1 pass**
### 最新整体验证结果
- `Rev004AccountProcess*`
- **总计 23 tests / 0 fail / 0 skip**
这代表当前仓库内,至少在真实数据库 + 外部依赖替身化前提下,主链路验证是通过的。
---
## 7. 主链路覆盖概览
### 7.1 prestorage
已验证:
- `prestorage-page`
- `prestorage-stat`
- `prestorage-detail`
- `prestorage-submit`
- `prestorage-revoke`
- `prestorage-process`
- `prestorage-attachments`
- 提交后 page/stat/detail/process/attachments 联动回看
已证明能力:
- 预存退款提交成功
- 余额真实回写
- 记录可回看
- 撤销后余额可恢复
### 7.2 sold
已验证:
- `sold-page`
- `sold-stat`
- `sold-submit`
- `sold-batch-revoke`
- 提交后 page/stat 能反映撤销能力
已证明能力:
- 已销调整申请可成功落日志
- 状态为 `PENDING_APPROVAL`
- 批量撤销以 `chargeIds[]` 可执行
### 7.3 unsold
已验证:
- `unsold-page`
- `unsold-stat`
- `unsold-adjust-submit`
- `unsold-split-submit`
- `unsold-late-fee-reduce-submit`
- `unsold-price-diff-submit`
- `unsold-bad-debt-submit`
- 调整后 page/stat 可回看金额变化
已证明能力:
- 五类未销动作 happy path 都可跑通
- 查询面能反映初始数据与提交后的更新结果
### 7.4 log / accountLog
已验证:
- `log-page`
- `log-stat`
- `log-detail`
- `log-process`
- `log-attachments`
- `log-refund`
- `log-prestorage`
- `log-revoke`
- 日志页 page/stat/detail/process/attachments 联动回看
已证明能力:
- 日志链路是当前最完整的一组闭环
- 支持二次动作(退款/转预存/撤销)
- 可查看流程摘要与附件引用
---
## 8. 页面 / 弹窗映射结论
### 已收口
目前页面与后端接口映射已经可明确回答:
- 哪个页面查哪个 `page/stat/detail`
- 哪个弹窗调哪个 `submit/revoke/process/attachments`
- 哪些动作依赖 `adjustmentNo`
- 哪些动作依赖 `chargeId/chargeIds`
### 特别提醒
- **已销批量撤销**:当前后端口径是 `chargeIds[]`,不是 `adjustmentNo[]`
- **日志页二次动作**:当前统一以 `adjustmentNo` 为主键
- **已销查询页** 本身不返回 `adjustmentNo`,若前端要保留申请单号,应使用提交返回值或转到日志视角查看
---
## 9. 接口字段口径结论
### 前端建议优先读取
对于动作提交类接口,前端建议主读:
- `adjustmentNo`
- `resultStatus`
- `approvalStatus`
- `writeBackStatus`
- `msg`
### 兼容字段
- `status`
- `message`
这些保留给旧调用方或兼容逻辑,不建议新页面作为主读来源。
### 预存工单状态口径
- `0 = 未处理`
- `1 = 已审核`
- `2 = 已完成`
- `3 = 已撤销`
### 页面状态与工单状态不要混用
- `prestorage-page.workOrderStatus` 是预存工单状态
- `log-page.statusCode/status` 是日志页状态投影
- 两者不是同一套语义
---
## 10. 当前最可信的证据类型
从强到弱排序:
1. **真实数据库 + MockMvc canary 测试**
2. **Controller / ReqVO / RespVO / Service 代码口径**
3. **矩阵文档归纳**
因此当前对外说法应以:
- 已跑通真实库的场景 = 强证据
- 仅代码里存在但未单独断言的字段 = 次强证据
---
## 11. 剩余风险 / 空白
### 仍未完全补齐
1. 字典 type / 值域已基本收口,剩余是页面文案逐页核对
2. 前端弹窗 A/B/C 切换路径尚未做 UI 编排级验证
3. 边角筛选条件并未全部逐项做真实库穷尽验证
### 风险等级判断
- **主功能联调风险:中低**
- **页面展示细节风险:中**
- **最终 UI 验收风险:中**
---
## 12. 对外建议说法
建议对前端 / 产品 / 评审会的说法:
> `REV004/accountProcess` 后端接口已基本收口四条主链prestorage / sold / unsold / log均已有真实数据库验证证据
> 当前可进入稳定联调和阶段性验收。
> 剩余工作主要集中在页面文案/字典展示的一致性核验,以及更贴近前端的 UI 编排级验证。
---
## 13. 建议下一步
按优先级建议:
1. 做页面文案 / 字典 label 的逐页验收核对
2. 已提供 UI 编排级验收清单,若需要再补自动化/UI录屏级验证
3. 评审会可直接使用本摘要 + 两份矩阵文档 + 集成测试证据 + 字典一致性摘要 + 页面文案核对表 + UI 编排验收清单
---
## 14. 关联文档
- `docs/evidence/rev004-accountprocess-integration-testing-bootstrap-2026-04-13.md`
- `docs/evidence/rev004-accountprocess-interface-truth-matrix-2026-04-13.md`
- `docs/evidence/rev004-accountprocess-ui-element-matrix-2026-04-13.md`
- `docs/evidence/rev004-accountprocess-dict-ui-consistency-summary-2026-04-13.md`
- `docs/evidence/rev004-accountprocess-page-label-audit-2026-04-13.md`
- `docs/evidence/rev004-accountprocess-ui-orchestration-checklist-2026-04-13.md`

View File

@ -0,0 +1,124 @@
# REV004 accountProcess gap remediation — 2026-04-13 验证记录
## 本轮改动
- accountProcess 查询 ReqVO 补前端原型 range 参数兼容:
- `sold`: `accountMonth` / `collectionTime`
- `log`: `accountMonth` / `createTime` / `handleTime` / `paymentDate`
- `unsold`: `accountMonth`
- `prestorage`: `acceptTime`
- 查询 Controller 增加 query-normalize兼容多值 query param 传入
- `accountLog` 状态筛选补兼容值:`1=正常``2=已撤销`
## 验证结果
### 1. 主代码编译
命令:
```bash
cd sw-business/sw-business-server && mvn -q -DskipTests compile
```
结果:通过。
### 2. 变更相关测试(手动定向执行)
由于模块内存在与本次改动无关的存量 testCompile 问题(`ChargeService` / `ChargeDO` 相关旧测试编译失败),本轮对变更相关测试采用定向手动编译 + JUnit Console 执行。
执行类:
- `AccountingAdjustActionControllerTest`
- `AccountingAdjustRouteSmokeTest`
- `AccountingAdjustLogProcessServiceImplTest`
- `AccountingAdjustPrestorageProcessServiceImplTest`
- `AccountingAdjustSoldQueryProcessServiceImplTest`
- `AccountingAdjustUnsoldProcessServiceImplTest`
结果:
- 34 tests found
- 34 tests started
- 34 tests successful
- 0 tests failed
关键覆盖点:
- accountProcess 路由 smoke 正常
- query range 参数兼容归一化正常
- `accountLog``status=2` 已撤销兼容筛选正常
- `prestorage adjustmentNo` 闭环仍保持通过
- `sold` 查询过滤与 `unsold` / `prestorage` 主流程回归通过
### 3. 说明
直接执行:
```bash
cd sw-business/sw-business-server && mvn -Dtest='...' test
```
仍会被仓库内历史遗留的 testCompile 问题阻断;该问题与本轮 accountProcess 修复无直接关系。
## 4. `sold.isHistory` 语义收口(追加)
### 语义定义
- `isHistory=false`:查询当前已收记录(`payState=PAID`),保留调整/批量撤销能力
- `isHistory=true`:查询历史已结记录(`payState=SETTLED`),按只读口径返回,不开放调整/批量撤销
### 追加验证
命令:
- `cd sw-business/sw-business-server && mvn -q -DskipTests compile`
- 手动定向执行:`AccountingAdjustRouteSmokeTest` + `AccountingAdjustSoldQueryProcessServiceImplTest`
结果:
- 6 tests found
- 6 tests started
- 6 tests successful
- 0 tests failed
## 5. 完整相关回归(追加)
`sold.isHistory` 收口后,再次对本轮相关 6 个 accountProcess 测试类做完整定向回归。
执行类:
- `AccountingAdjustActionControllerTest`
- `AccountingAdjustRouteSmokeTest`
- `AccountingAdjustLogProcessServiceImplTest`
- `AccountingAdjustPrestorageProcessServiceImplTest`
- `AccountingAdjustSoldQueryProcessServiceImplTest`
- `AccountingAdjustUnsoldProcessServiceImplTest`
结果:
- 35 tests found
- 35 tests started
- 35 tests successful
- 0 tests failed
## 6. sold / accountLog 展示字段复核(追加)
### 复核结论
- `sold`:当前前端列表/弹窗所需展示字段已基本满足,暂不需要额外补展示字段
- `accountLog`:补 `custId`,用于前端客户编号点击后稳定跳转客户详情
### 追加验证
- `mvn -q -DskipTests compile` 通过
- 手动定向执行:`AccountingAdjustRouteSmokeTest` + `AccountingAdjustLogProcessServiceImplTest`
- 结果5 tests found / 5 tests successful / 0 failed
## 7. accountLog `custId` 补齐后的回归(追加)
### 变更目的
- 补 `accountLog` 列表/详情响应中的 `custId`
- 支撑前端客户编号点击后稳定跳客户详情
### 追加验证
- 手动定向编译:`AccountingAdjustRouteSmokeTest``AccountingAdjustLogProcessServiceImplTest``AccountingAdjustSoldQueryProcessServiceImplTest`
- JUnit Console 执行结果9 tests found / 9 tests successful / 0 failed
## 8. work_status 字典与正式文档同步(追加)
### 本轮结果
- 已在 `../water-docs/sql/rev004_account_adjust_dict_seed.sql` 中补充 `work_status` 字典 type/data幂等
- 已将 `../water-docs/docs/design/03_Technical_Design/01_Database_Design.md` 中的 `work_status` 口径同步为代码四态:`0-未处理 / 1-已审核 / 2-已完成 / 3-已撤销`
- 本轮仍无业务表 schema 变更,仅涉及字典 seed 与正式文档同步
### 业务解释补充
- `0 未处理`:工单创建,待审核/待处理
- `1 已审核`:审核通过(或无需审批),待完成
- `2 已完成`:处理成功且已回写完成
- `3 已撤销`:工单已撤销
- 说明:`钱到账``带红冲操作` 更偏业务侧解释;当前代码稳定判定仍以 `approvalStatus / resultStatus / writeBackStatus / revokeOfAdjustmentNo` 为准。
### 剩余风险
- 当前 seed SQL 采用幂等补缺策略,不会纠正**环境中已存在但口径错误**的 `work_status` 数据;若某环境已落旧三态字典,仍需后续补一条存量纠偏 SQL 或人工核查。
- Archive/历史快照与 processed/output 产物可能仍保留旧三态,它们属于历史资料/派生产物,不作为当前正式口径。

View File

@ -0,0 +1,574 @@
# REV004 accountProcess 集成测试方案 bootstrap2026-04-13
## 本轮已落地 bootstrap
- 新增 `AbstractRev004AccountProcessIntegrationTest`:定义真实数据库 + 外部依赖默认替身化的测试基类
- 新增 `Rev004AccountProcessLiveDbReadinessTest`:在提供 `REV004_IT_DB_*` 环境变量时,检查真实数据库所需表是否可见
- 新增 `Rev004AccountProcessIntegrationFixtureAssetsTest`:校验 fixture 资源文件存在
- 新增 `src/test/resources/sql/rev004/accountprocess/*.sql`:为 prestorage/sold/unsold/accountLog 预留 fixture 分层入口
## 当前价值
- 把“真实数据库 + 外部依赖替身化”的执行边界固化到测试骨架
- 把场景矩阵的 fixture 入口落到仓库,便于后续继续实现 query/action/close-loop 场景
- 在未提供真实数据库环境变量时,不强行执行 live DB readiness 测试
## 下一步建议
1. 补 `prestorage` 第 1 条全闭环场景
2. 补 `sold``isHistory` + submit/撤销场景
3. 补 `accountLog` 的 refund/prestorage 二次动作场景
## Canary 补充
- 新增 `Rev004AccountProcessCanaryQueryIntegrationTest`:在提供 `REV004_IT_DB_*` 环境变量时,真正启动 Spring + MockMvc打通 `prestorage-page` 只读 query 作为最小 canary。
- 当前 fixture 仍是 bootstrap 占位,因此该 canary 的目标是“验活骨架”,不是验证完整业务数据闭环。
## Fresh verification
- 执行:`mvn -Dtest='Rev004AccountProcessIntegrationFixtureAssetsTest,Rev004AccountProcessLiveDbReadinessTest,Rev004AccountProcessCanaryQueryIntegrationTest' test`
- 结果BUILD SUCCESS
- 细项:
- Fixture assets: 1 pass
- Live DB readiness: 1 skipped未提供 `REV004_IT_DB_*` 环境变量时跳过)
- Canary query: 1 skipped未提供 `REV004_IT_DB_*` 环境变量时跳过)
## Real DB canary verification
- 执行环境:使用 `application-local.yaml` 中 PostgreSQL 连接参数,以 `REV004_IT_DB_*` 环境变量注入
- 执行:`mvn -Dtest='Rev004AccountProcessIntegrationFixtureAssetsTest,Rev004AccountProcessLiveDbReadinessTest,Rev004AccountProcessCanaryQueryIntegrationTest' test`
- 结果BUILD SUCCESS
- 明细:
- Fixture assets: 1 pass
- Live DB readiness: 1 pass
- Canary query (`/admin-api/business/accounting-adjust/prestorage-page`): 1 pass
- 说明:当前 canary 证明了真实数据库 + Spring context + MockMvc + route wiring 可启动并返回成功包装;但 fixture 仍是最小占位,尚未证明完整业务数据闭环。
## Real DB full bootstrap verification
- 执行环境:使用 `application-local.yaml` 中 PostgreSQL 连接参数,经 `REV004_IT_DB_*` 环境变量注入
- 执行:`mvn -Dtest='Rev004AccountProcessIntegrationFixtureAssetsTest,Rev004AccountProcessLiveDbReadinessTest,Rev004AccountProcessCanaryQueryIntegrationTest' test`
- 结果BUILD SUCCESS
- 明细:
- Fixture assets: 1 pass
- Live DB readiness: 1 pass
- Canary query (`/admin-api/business/accounting-adjust/prestorage-page`): 1 pass
- 说明:当前已经从“资产存在”推进到“真实数据库 + Spring context + MockMvc + canary query 验活成功”。
- 剩余缺口fixture 仍是最小占位,尚未验证完整业务数据闭环。
## First real close-loop scenario
- 场景:`prestorage-submit` 预存退款
- 测试文件:`Rev004AccountProcessCanaryQueryIntegrationTest`
- 使用真实数据库中的独立测试数据(`REV004_IT_SRC` / `biz_account.id=900001`
- 断言:
- `POST /admin-api/business/accounting-adjust/prestorage-submit` 返回成功包装
- 账户余额由 `100.00` 变为 `90.00`
- `biz_operat_log` 写入 1 条 `旧预存调整` 日志
- `biz_operat_log_detail` 中存在与返回 `adjustmentNo` 对应的明细记录
- 执行:`REV004_IT_DB_URL=... REV004_IT_DB_USERNAME=... REV004_IT_DB_PASSWORD=... mvn -Dtest='Rev004AccountProcessCanaryQueryIntegrationTest' test`
- 结果BUILD SUCCESS2 tests / 0 fail / 0 skip
## Sold close-loop scenario
- 场景:`sold-submit` 已销调整申请
- 测试文件:`Rev004AccountProcessCanaryQueryIntegrationTest`
- 使用真实数据库中的独立测试营业账数据(`biz_charge.id=900002``pay_state=1`
- 断言:
- `POST /admin-api/business/accounting-adjust/sold-submit` 返回成功包装
- 返回 `resultStatus = PENDING_APPROVAL`
- `biz_operat_log` 写入 1 条 `旧账务兼容动作` 日志
- `biz_operat_log_detail` 中存在与返回 `adjustmentNo` 对应的明细记录
- 结果:与 prestorage canary 一起执行,`Rev004AccountProcessCanaryQueryIntegrationTest` 当前为 3 tests / 0 fail / 0 skip
## AccountLog close-loop scenario
- 场景:`log-prestorage` 二次动作
- 测试文件:`Rev004AccountProcessCanaryQueryIntegrationTest`
- 使用真实数据库中的独立源记录(`adjustmentNo=REV004-LOG-900003``biz_charge.id=900003`)与目标账户(`REV004_IT_TGT` / `biz_account.id=900004`
- 断言:
- `POST /admin-api/business/accounting-adjust/log-prestorage` 返回成功包装
- 源营业账 `pay_state` 被清为 `0`(未收)
- 目标账户余额增加到 `20.00`
- `biz_operat_log` 写入 1 条 `旧账务兼容动作` 日志
- `biz_operat_log_detail` 中存在与返回 `adjustmentNo` 对应的明细记录
- 结果:与 prestorage/sold 场景一起执行时,`Rev004AccountProcessCanaryQueryIntegrationTest` 当前为 4 tests / 0 fail / 0 skip
## AccountLog process query scenario
- 场景:`log-process` 读取已有调整记录流程摘要
- 测试文件:`Rev004AccountProcessCanaryQueryIntegrationTest`
- 使用真实数据库中的独立源记录(`adjustmentNo=REV004-LOG-900003`
- 断言:
- `GET /admin-api/business/accounting-adjust/log-process` 返回成功包装
- `processState = UPDATED`
- `resultStatus = SUCCESS`
## Current canary suite status
- `Rev004AccountProcessCanaryQueryIntegrationTest` 当前为 5 tests / 0 fail / 0 skip
- 已覆盖query canary、prestorage-submit、sold-submit、log-prestorage、log-process
## 2026-04-13 最新推进accountLog refund 闭环补齐
- 新增 `TransactionApi``@MockBean` 注入到 `AbstractRev004AccountProcessIntegrationTest`,确保真实数据库之外的交易后续流水依赖保持替身化。
- 更新 `40_accountlog_seed.sql`:为 `REV004-LOG-900003` 补入 `originalTranSeq=T-900003``originalSysTranSeq=SYS-900003`,使 `log-refund` 路径具备真实可执行前置数据。
- 新增 canary 闭环用例:`logRefund_shouldClearChargePaymentAndWriteFollowupLog`
### 新增闭环断言
- `POST /admin-api/business/accounting-adjust/log-refund`
- 返回:`resultStatus=SUCCESS``objectType=PREPAID_REFUND``writeBackStatus=UPDATED`
- 数据库断言:
- `biz_charge.id=900003``pay_state` 从已收改为 `0`
- `biz_operat_log` 中新增 1 条 `账务调整` 日志,`operat_content` 含返回的 `adjustmentNo`
- `biz_operat_log_detail` 中存在 `bankTranSeq=RF-900003`
- 替身断言:`TransactionApi.createFollowupTransaction(...)` 被调用
### 本次真实库验证
- 执行 1`REV004_IT_DB_URL=... REV004_IT_DB_USERNAME=... REV004_IT_DB_PASSWORD=... mvn -Dtest='Rev004AccountProcessCanaryQueryIntegrationTest' test`
- 结果 1BUILD SUCCESS
- `Rev004AccountProcessCanaryQueryIntegrationTest`: 7 tests / 0 fail / 0 skip
- 执行 2`REV004_IT_DB_URL=... REV004_IT_DB_USERNAME=... REV004_IT_DB_PASSWORD=... mvn -Dtest='Rev004AccountProcess*' test`
- 结果 2BUILD SUCCESS
- `Rev004AccountProcessLiveDbReadinessTest`: 1 pass
- `Rev004AccountProcessIntegrationFixtureAssetsTest`: 1 pass
- `Rev004AccountProcessCanaryQueryIntegrationTest`: 7 pass
- 总计9 tests / 0 fail / 0 skip
### 当前 canary 套件真实覆盖
- `prestorage-page_shouldReturnSuccessEnvelope`
- `prestorageSubmit_shouldChangeBalanceAndWriteOperatLog`
- `soldSubmit_shouldWritePendingOperatLog`
- `logPrestorage_shouldClearChargePaymentAndIncreaseTargetDeposit`
- `logProcess_shouldReturnUpdatedProcessSummary`
- `logAttachments_shouldReturnUnresolvedAttachmentRef`
- `logRefund_shouldClearChargePaymentAndWriteFollowupLog`
### 仍待继续
- `unsold` 五类 happy path 真实库闭环
- `revoke/log-revoke/sold-batch-revoke` 的真实库撤销链验证
- 更高一层的“提交 -> 查询页回看 -> detail/process/attachments 全链联动”场景编排
## 2026-04-13 继续推进:撤销链真实库闭环补齐
- 新增真实库撤销链 canary
- `prestorageRevoke_shouldRestoreBalanceAndWriteRevokeLog`
- `soldBatchRevoke_shouldWriteRevokeLogForPendingSyntheticRecord`
- `logRevoke_shouldRestoreChargePaymentAndRollbackTransferredDeposit`
- 为支持旧兼容动作撤销,本轮同时修正 `AccountingAdjustProcessServiceImpl.findSyntheticSnapshot(...)`
- 改为按 `adjustmentNo` 直接回查 operat_log / operat_log_detail
- 避免旧预存调整 / 旧日志转预存 场景在撤销时误落入 unified snapshot 分支
### 新增闭环断言
1. `prestorage-revoke`
- 先发起 `prestorage-submit`
- 再调用 `POST /admin-api/business/accounting-adjust/prestorage-revoke`
- 断言:
- 返回 `actionType=REVOKE`
- `biz_account.id=900001.deposit``90.00` 恢复到 `100.00`
- 存在 `revokeOfAdjustmentNo=<原adjustmentNo>` 明细
2. `sold-batch-revoke`
- 先发起 `sold-submit`
- 再调用 `POST /admin-api/business/accounting-adjust/sold-batch-revoke`
- 断言:
- `successCount=1`
- 存在 `revokeOfAdjustmentNo=<原adjustmentNo>` 明细
3. `log-revoke`
- 先发起 `log-prestorage`
- 再调用 `POST /admin-api/business/accounting-adjust/log-revoke`
- 断言:
- 返回 `actionType=REVOKE`
- `biz_charge.id=900003.pay_state` 恢复为 `1`
- `charge_method` 恢复为 `2`
- `charge_way` 恢复为 `6`
- `biz_account.id=900004.deposit``20.00` 回退到 `0.00`
- 存在 `revokeOfAdjustmentNo=<原adjustmentNo>` 明细
### 本次真实库验证
- 执行 1`REV004_IT_DB_URL=... REV004_IT_DB_USERNAME=... REV004_IT_DB_PASSWORD=... mvn -Dtest='Rev004AccountProcessCanaryQueryIntegrationTest' test`
- 结果 1BUILD SUCCESS
- `Rev004AccountProcessCanaryQueryIntegrationTest`: 10 tests / 0 fail / 0 skip
- 执行 2`REV004_IT_DB_URL=... REV004_IT_DB_USERNAME=... REV004_IT_DB_PASSWORD=... mvn -Dtest='Rev004AccountProcess*' test`
- 结果 2BUILD SUCCESS
- `Rev004AccountProcessLiveDbReadinessTest`: 1 pass
- `Rev004AccountProcessIntegrationFixtureAssetsTest`: 1 pass
- `Rev004AccountProcessCanaryQueryIntegrationTest`: 10 pass
- 总计12 tests / 0 fail / 0 skip
### 当前 canary 套件真实覆盖10 条)
- `prestoragePage_shouldReturnSuccessEnvelope`
- `prestorageSubmit_shouldChangeBalanceAndWriteOperatLog`
- `prestorageRevoke_shouldRestoreBalanceAndWriteRevokeLog`
- `soldSubmit_shouldWritePendingOperatLog`
- `soldBatchRevoke_shouldWriteRevokeLogForPendingSyntheticRecord`
- `logPrestorage_shouldClearChargePaymentAndIncreaseTargetDeposit`
## 2026-06-05 sw-business-server 启动循环修复验证
### 问题现象
- 环境:`root@192.168.10.130`,容器 `sw-business-server`
- 现象容器反复重启Docker healthcheck 一度显示 healthy但应用日志持续出现 `Application run failed`
- 关键异常:
- `Error creating bean with name 'prestorageAdjustBpmApiImpl'`
- `Error creating bean with name 'prestorageBpmCallbackService'`
- `BeanCurrentlyInCreationException: Error creating bean with name 'custServiceImpl'`
- `custServiceImpl` 已以 raw version 注入 `chargeServiceImpl``waterUseSchemeServiceImpl``exceedWaterUseSchemeServiceImpl``custWaterSchemeRelServiceImpl`,随后又被 AOP 包装Spring 拒绝混用 raw bean 与 proxy bean
### 修复策略
- 保留既有 `spring.main.allow-circular-references=true` 配置;该配置只能允许普通循环依赖,不能解决 raw bean 被代理包装后的不一致问题。
- 对异常中明确点名的 4 个 `CustService` 注入点增加 `@Lazy`
- `ChargeServiceImpl.custService`
- `WaterUseSchemeServiceImpl.custService`
- `ExceedWaterUseSchemeServiceImpl.custService`
- `CustWaterSchemeRelServiceImpl.custService`
- 新增回归测试 `CustServiceCircularDependencyContractTest`,约束上述 4 个注入点必须使用 lazy proxy。
### 本次验证
- RED 验证:
- 命令:`mvn -pl sw-business/sw-business-server -am -Dtest=CustServiceCircularDependencyContractTest -Dsurefire.failIfNoSpecifiedTests=false test`
- 结果:测试按预期失败,失败点为 `ChargeServiceImpl.custService must be @Lazy`
- GREEN 验证:
- 命令:`mvn -pl sw-business/sw-business-server -am -Dtest=CustServiceCircularDependencyContractTest -Dsurefire.failIfNoSpecifiedTests=false test`
- 结果BUILD SUCCESS`1 tests / 0 failures / 0 errors / 0 skipped`
- 打包验证:
- 命令:`mvn -pl sw-business/sw-business-server -am -DskipTests package`
- 结果BUILD SUCCESS生成 `sw-business/sw-business-server/target/sw-business-server.jar`
- 远端部署验证:
- 上传 jar 到 `/projects/sw-cloud/sw-business-server/target/sw-business-server.jar`
- 远端执行:`cd /projects/sw-cloud && docker compose build sw-business-server && docker compose up -d sw-business-server`
- 结果:镜像 `sw-cloud-sw-business-server` 重建成功,容器 `sw-business-server` 重新创建并启动
- 远端运行验证:
- 日志出现:`Tomcat started on port 48090``nacos registry, DEFAULT_GROUP business-server 192.168.10.130:48090 register finished``Started BusinessApplication in 42.77 seconds`
- 最近启动日志未再出现 `Application run failed``BeanCurrentlyInCreationException``Error creating bean with name`
- HTTP 验证:`curl http://127.0.0.1:48090/actuator/health` 返回 `{"status":"UP"}`HTTP 200
### 遗留风险
- `docker-compose.yml``sw-business-server` healthcheck 当前检查 `http://localhost:48080`,而业务应用实际监听 `48090`;该 healthcheck 会把其他服务返回的 HTTP 200 / 404 JSON 误判为业务健康。建议后续改为 `http://localhost:48090/actuator/health`
- 当前修复是最小断环修复;长期更优方向是拆分 `CustServiceImpl` 的查询能力与写能力,减少跨业务 service 之间的双向依赖。
- `logRevoke_shouldRestoreChargePaymentAndRollbackTransferredDeposit`
- `logProcess_shouldReturnUpdatedProcessSummary`
- `logAttachments_shouldReturnUnresolvedAttachmentRef`
- `logRefund_shouldClearChargePaymentAndWriteFollowupLog`
## 2026-04-13 继续推进unsold 五类真实库 happy path 补齐
- 新增 `30_unsold_seed.sql` 真实库 fixture
- `biz_cust.id=900005 / code=REV004_IT_UNSOLD`
- `biz_charge.id=900005 / pay_state=0 / bill_amount=120.00 / extended_amount=120.00 / late_fee=30.00`
- 扩展 `00_reset.sql`,纳入 `900005 / REV004_IT_UNSOLD` 清理范围
- 新增 5 条真实库 canary
- `unsoldAdjustSubmit_shouldUpdateAmountsAndWriteOperatLog`
- `unsoldSplitSubmit_shouldCreatePendingApprovalRecord`
- `unsoldLateFeeReduceSubmit_shouldCreatePendingApprovalRecord`
- `unsoldPriceDiffSubmit_shouldCreatePendingApprovalRecord`
- `unsoldBadDebtSubmit_shouldCreatePendingApprovalRecord`
### 新增闭环断言
1. `unsold-adjust-submit`
- 返回 `SUCCESS`
- `biz_charge.id=900005.extended_amount` 更新为 `88.88`
- `bill_amount` 更新为 `80.00`
- `biz_operat_log.operat_content` 含返回 `adjustmentNo`
2. `unsold-split-submit`
- 返回 `PENDING_APPROVAL`
- `objectType=SPLIT_ADJUST`
- `biz_operat_log.operat_content` 含返回 `adjustmentNo`
3. `unsold-late-fee-reduce-submit`
- 返回 `PENDING_APPROVAL`
- `objectType=LATE_FEE_REDUCE`
- `biz_operat_log.operat_content` 含返回 `adjustmentNo`
4. `unsold-price-diff-submit`
- 返回 `PENDING_APPROVAL`
- `objectType=PRICE_DIFF_ADJUST`
- `biz_operat_log.operat_content` 含返回 `adjustmentNo`
5. `unsold-bad-debt-submit`
- 返回 `PENDING_APPROVAL`
- `objectType=BAD_DEBT_RECORD`
- `biz_operat_log.operat_content` 含返回 `adjustmentNo`
### 本次真实库验证
- 执行 1`REV004_IT_DB_URL=... REV004_IT_DB_USERNAME=... REV004_IT_DB_PASSWORD=... mvn -Dtest='Rev004AccountProcessCanaryQueryIntegrationTest' test`
- 结果 1BUILD SUCCESS
- `Rev004AccountProcessCanaryQueryIntegrationTest`: 15 tests / 0 fail / 0 skip
- 执行 2`REV004_IT_DB_URL=... REV004_IT_DB_USERNAME=... REV004_IT_DB_PASSWORD=... mvn -Dtest='Rev004AccountProcess*' test`
- 结果 2BUILD SUCCESS
- `Rev004AccountProcessLiveDbReadinessTest`: 1 pass
- `Rev004AccountProcessIntegrationFixtureAssetsTest`: 1 pass
- `Rev004AccountProcessCanaryQueryIntegrationTest`: 15 pass
- 总计17 tests / 0 fail / 0 skip
### 当前 canary 套件真实覆盖15 条)
- `prestoragePage_shouldReturnSuccessEnvelope`
- `prestorageSubmit_shouldChangeBalanceAndWriteOperatLog`
- `prestorageRevoke_shouldRestoreBalanceAndWriteRevokeLog`
- `soldSubmit_shouldWritePendingOperatLog`
- `soldBatchRevoke_shouldWriteRevokeLogForPendingSyntheticRecord`
- `unsoldAdjustSubmit_shouldUpdateAmountsAndWriteOperatLog`
- `unsoldSplitSubmit_shouldCreatePendingApprovalRecord`
- `unsoldLateFeeReduceSubmit_shouldCreatePendingApprovalRecord`
- `unsoldPriceDiffSubmit_shouldCreatePendingApprovalRecord`
- `unsoldBadDebtSubmit_shouldCreatePendingApprovalRecord`
- `logPrestorage_shouldClearChargePaymentAndIncreaseTargetDeposit`
- `logRevoke_shouldRestoreChargePaymentAndRollbackTransferredDeposit`
- `logProcess_shouldReturnUpdatedProcessSummary`
- `logAttachments_shouldReturnUnresolvedAttachmentRef`
- `logRefund_shouldClearChargePaymentAndWriteFollowupLog`
### 剩余待补
- 更高一层的“提交 -> page/stat/detail/process/attachments 联动回看”场景编排
- 若要进一步贴近前端验收,可再补一轮按弹窗功能分组的端到端查询/动作串联用例
## 2026-04-13 继续推进:提交后联动回看场景补齐
- 新增 3 条更贴近前端验收的联动回看 canary
- `prestorageSubmit_thenPageStatAndDetailShouldExposeNewRecord`
- `soldSubmit_thenSoldPageAndStatShouldExposePendingRevokeCapability`
- `logSeed_thenPageStatDetailProcessAndAttachmentsShouldStayConsistent`
### 新增联动断言
1. `prestorage` 提交后联动回看
- 提交 `prestorage-submit`
- 再查:
- `prestorage-page`
- `prestorage-stat`
- `prestorage-detail`
- 断言:
- page 能看到新 `adjustmentNo`
- stat 的 `totalCount=1 / refundCount=1`
- detail 返回 `resultStatus=SUCCESS / writeBackStatus=UPDATED`
2. `sold` 提交后联动回看
- 提交 `sold-submit`
- 再查:
- `sold-page`
- `sold-stat`
- 断言:
- page 仍能看到 `biz_charge.id=900002`
- `canBatchRevoke=true`
- stat 的 `totalCount=1 / totalActualAmount=80.00`
3. `log` 全链回看
- 基于 `40_accountlog_seed.sql` 中的 `REV004-LOG-900003`
- 依次验证:
- `log-page`
- `log-stat`
- `log-detail`
- `log-process`
- `log-attachments`
- 断言:
- page 可见 `adjustmentNo=REV004-LOG-900003`
- stat `completedCount=1`
- detail 包含 `originalTranSeq=T-900003``attachmentRefs[0]=mock-ref-1`
- process `processState=UPDATED`
- attachments 可返回 `mock-ref-1`
### 本次真实库验证
- 执行 1`REV004_IT_DB_URL=... REV004_IT_DB_USERNAME=... REV004_IT_DB_PASSWORD=... mvn -Dtest='Rev004AccountProcessCanaryQueryIntegrationTest' test`
- 结果 1BUILD SUCCESS
- `Rev004AccountProcessCanaryQueryIntegrationTest`: 18 tests / 0 fail / 0 skip
- 执行 2`REV004_IT_DB_URL=... REV004_IT_DB_USERNAME=... REV004_IT_DB_PASSWORD=... mvn -Dtest='Rev004AccountProcess*' test`
- 结果 2BUILD SUCCESS
- `Rev004AccountProcessLiveDbReadinessTest`: 1 pass
- `Rev004AccountProcessIntegrationFixtureAssetsTest`: 1 pass
- `Rev004AccountProcessCanaryQueryIntegrationTest`: 18 pass
- 总计20 tests / 0 fail / 0 skip
### 当前真实库 canary 覆盖18 条)
- `prestoragePage_shouldReturnSuccessEnvelope`
- `prestorageSubmit_shouldChangeBalanceAndWriteOperatLog`
- `prestorageRevoke_shouldRestoreBalanceAndWriteRevokeLog`
- `prestorageSubmit_thenPageStatAndDetailShouldExposeNewRecord`
- `soldSubmit_shouldWritePendingOperatLog`
- `soldBatchRevoke_shouldWriteRevokeLogForPendingSyntheticRecord`
- `soldSubmit_thenSoldPageAndStatShouldExposePendingRevokeCapability`
- `unsoldAdjustSubmit_shouldUpdateAmountsAndWriteOperatLog`
- `unsoldSplitSubmit_shouldCreatePendingApprovalRecord`
- `unsoldLateFeeReduceSubmit_shouldCreatePendingApprovalRecord`
- `unsoldPriceDiffSubmit_shouldCreatePendingApprovalRecord`
- `unsoldBadDebtSubmit_shouldCreatePendingApprovalRecord`
- `logPrestorage_shouldClearChargePaymentAndIncreaseTargetDeposit`
- `logRevoke_shouldRestoreChargePaymentAndRollbackTransferredDeposit`
- `logSeed_thenPageStatDetailProcessAndAttachmentsShouldStayConsistent`
- `logProcess_shouldReturnUpdatedProcessSummary`
- `logAttachments_shouldReturnUnresolvedAttachmentRef`
- `logRefund_shouldClearChargePaymentAndWriteFollowupLog`
### 当前剩余缺口
- 若要进一步逼近前端验收,可继续补“弹窗 A/B/C 对应多接口切换”的编排式测试摘要
- 以及基于接口返回字段做一版更面向页面的 contract/assertion matrix
## 2026-04-13 继续推进unsold 查询面真实库回看补齐
- 新增 2 条真实库查询联动 canary
- `unsoldSeed_thenPageAndStatShouldExposeChargeSnapshot`
- `unsoldAdjustSubmit_thenPageAndStatShouldReflectUpdatedAmounts`
### 新增断言
1. `unsold` 初始查询面
- `GET /admin-api/business/accounting-adjust/unsold-page`
- `GET /admin-api/business/accounting-adjust/unsold-stat`
- 断言:
- `biz_charge.id=900005` 可见
- `totalAmount=120.00`
- `billAmount=120.00`
- `penaltyAmount=30.00`
- `canAdjust/canSplit/canLateFeeReduce/canPriceDiffAdjust/canBadDebtAdjust=true`
- stat`sumWaterVolume=5.000 / sumTotalAmount=120.00 / sumBillAmount=120.00 / sumPenaltyAmount=30.00`
2. `unsold-adjust-submit` 后查询面回看
- 先提交 `unsold-adjust-submit`
- 再查 `unsold-page / unsold-stat`
- 断言:
- page 中 `totalAmount=88.88`
- page 中 `billAmount=80.00`
- stat 中 `sumTotalAmount=88.88`
- stat 中 `sumBillAmount=80.00`
### 本次真实库验证
- 执行 1`REV004_IT_DB_URL=... REV004_IT_DB_USERNAME=... REV004_IT_DB_PASSWORD=... mvn -Dtest='Rev004AccountProcessCanaryQueryIntegrationTest' test`
- 结果 1BUILD SUCCESS
- `Rev004AccountProcessCanaryQueryIntegrationTest`: 20 tests / 0 fail / 0 skip
- 执行 2`REV004_IT_DB_URL=... REV004_IT_DB_USERNAME=... REV004_IT_DB_PASSWORD=... mvn -Dtest='Rev004AccountProcess*' test`
- 结果 2BUILD SUCCESS
- `Rev004AccountProcessLiveDbReadinessTest`: 1 pass
- `Rev004AccountProcessIntegrationFixtureAssetsTest`: 1 pass
- `Rev004AccountProcessCanaryQueryIntegrationTest`: 20 pass
- 总计22 tests / 0 fail / 0 skip
### 影响
- `prestorage / sold / unsold / log` 四条主查询面现在都至少有一组真实库回看证据
- 剩余未补重点从“主链查询缺口”收敛为“预存页流程/附件链路 + 字典/UI文案展示一致性”
## 2026-04-13 继续推进prestorage process / attachments 真实库补齐
- 新增 canary`prestorageSubmit_thenProcessAndAttachmentsShouldExposeReturnedAdjustmentNo`
- 在 `prestorage-submit` 场景中附带 `attachmentRefs=[pre-proof-2, pre-proof-3]`
- 提交后继续验证:
- `GET /admin-api/business/accounting-adjust/prestorage-process`
- `GET /admin-api/business/accounting-adjust/prestorage-attachments`
### 新增断言
- process
- `adjustmentNo` 与提交返回一致
- `resultStatus=SUCCESS`
- `approvalStatus=NOT_REQUIRED`
- `writeBackStatus=UPDATED`
- `processState=UPDATED`
- `stages[0].message` 存在
- attachments
- 返回两条附件引用
- `ref=pre-proof-2 / pre-proof-3`
- `resolved=false`
### 本次真实库验证
- `Rev004AccountProcessCanaryQueryIntegrationTest`: **21 tests / 0 fail / 0 skip**
- `Rev004AccountProcess*`: **总计 23 tests / 0 fail / 0 skip**
### 影响
- `prestorage` 查询/提交流程/撤销/流程查看/附件查看 这一组链路现在都已有真实库证据
- 剩余缺口进一步收敛到:字典 label / UI 文案一致性、边角筛选条件穷尽、UI 编排级验证
## 2026-06-05 远端启动修复后真实 HTTP/BPM 闭环验证
### 环境
- 后端:`192.168.10.130``sw-business-server` 端口 `48090`gateway 端口 `48080`
- 前端代理:本地 Vite `http://127.0.0.1:18080/admin-api` -> gateway
- 数据库:`192.168.10.130:5436/sw_system`
- 测试数据:`00_reset.sql` + `01_dict_seed.sql` + `10_prestorage_seed.sql`
### 启动与健康检查
- `sw-business-server` 重新构建部署后日志出现 `Started BusinessApplication`
- `curl http://192.168.10.130:48090/actuator/health` 返回 `{"status":"UP"}`
- `curl http://192.168.10.130:48080/actuator/health` 返回 `{"status":"UP"}`
### 真实接口链路
1. 登录
- `POST /admin-api/system/auth/login`
- `tenant-id: 1`
- 返回 `code=0`,获得 `accessToken`
2. 预存退款:保存 -> BPM 审批 -> 自动执行 -> 查询回看
- `POST /admin-api/business/accounting-adjust/prestorage-save`
- 请求核心字段:
- `custCode=REV004_IT_SRC`
- `refundAmount=10.00`
- `applicant=接口闭环测试人`
- `attachmentRefs=[real-api-proof-1]`
- 保存返回:
- `adjustmentNo=REV004-PRF-900001-20260605101623`
- `resultStatus=PENDING_APPROVAL`
- `approvalStatus=PENDING_APPROVAL`
- `writeBackStatus=PENDING`
- `resultObjectNo=8d8af605-6084-11f1-8df9-5ad978d7f8ab`
- BPM 待办:
- `GET /admin-api/bpm/task/list-by-process-instance-id?processInstanceId=8d8af605-6084-11f1-8df9-5ad978d7f8ab`
- taskId=`8da3600e-6084-11f1-8df9-5ad978d7f8ab`
- 审批:
- `PUT /admin-api/bpm/task/approve`
- 返回 `code=0,data=true`
- 回看:
- `GET /admin-api/business/accounting-adjust/prestorage-process`
- `resultStatus=SUCCESS`
- `approvalStatus=APPROVED`
- `writeBackStatus=UPDATED`
- `processState=UPDATED`
- `latestMessage=BPM审批通过后自动执行预存退款`
- DB 断言:
- `biz_account(900001).deposit=90.00`
- `biz_prestorage_adjust.business_status=COMPLETED`
- `biz_prestorage_payment_relation` 写入 `payment_record_id=159`
3. 预存转账:保存 -> BPM 审批 -> 自动执行 -> 查询回看
- `POST /admin-api/business/accounting-adjust/prestorage-save`
- 请求核心字段:
- `custCode=REV004_IT_SRC`
- `targetCustCode=REV004_IT_TGT`
- `transferAmount=30.00`
- `applicant=接口闭环转账人`
- `attachmentRefs=[real-api-transfer-proof]`
- 保存返回:
- `adjustmentNo=REV004-PTR-900001-20260605102121`
- `resultStatus=PENDING_APPROVAL`
- `approvalStatus=PENDING_APPROVAL`
- `writeBackStatus=PENDING`
- `resultObjectNo=3e231762-6085-11f1-8df9-5ad978d7f8ab`
- BPM 审批:
- taskId=`3e2401cb-6085-11f1-8df9-5ad978d7f8ab`
- `PUT /admin-api/bpm/task/approve` 返回 `code=0,data=true`
- 回看:
- `GET /admin-api/business/accounting-adjust/prestorage-process`
- `resultStatus=SUCCESS`
- `approvalStatus=APPROVED`
- `writeBackStatus=UPDATED`
- `latestMessage=BPM审批通过后自动执行预存转账`
- `prestorage-page``workOrderStatus=2 / adjustmentType=2 / targetCustCode=REV004_IT_TGT / remainingAmount=60.00`
- DB 断言:
- `biz_account(900001).deposit=60.00`
- `biz_account(900004).deposit=30.00`
### 本次联调发现并处理的问题
- 首次 BPM 审批退款失败,`/admin-api/bpm/task/approve` 返回 `code=500`
- 服务端根因:
- `ERROR: relation "biz_prestorage_payment_relation_seq" does not exist`
- 表 DDL 使用 `BIGSERIAL`,实际 sequence 为 `biz_prestorage_payment_relation_id_seq`
- Java 实体 `PrestoragePaymentRelationDO` 原标注 `@KeySequence("biz_prestorage_payment_relation_seq")`
- 临时环境修复:
- 远端库补齐兼容 sequence`biz_prestorage_payment_relation_seq`
- 代码修复:
- `PrestoragePaymentRelationDO` 改为 `@KeySequence("biz_prestorage_payment_relation_id_seq")`
- 新增 `PrestoragePaymentRelationSequenceContractTest` 锁定注解与 DDL sequence 名一致
### 本次本地验证
- `mvn -pl sw-business/sw-business-server -am -DskipTests compile`
- 结果BUILD SUCCESS
- `javap` 反射核验:
- `PrestoragePaymentRelationDO` 当前产物中的 `@KeySequence``biz_prestorage_payment_relation_id_seq`
- 受既有测试编译问题影响,单测命令 `mvn -pl sw-business/sw-business-server -Dtest=PrestoragePaymentRelationSequenceContractTest test` 未能进入目标测试:
- 阻塞点:`MeterInOutServiceImplTest` 引用缺失的 `METER_IN_MODEL_CALIBER_MISMATCH``METER_IMPORT_MODEL_CALIBER_MISMATCH_MSG`
### 2026-06-05 本地 targeted contract test 复核
- 命令:
- `mvn -pl sw-business/sw-business-server -am -Dtest=CustServiceCircularDependencyContractTest,PrestoragePaymentRelationSequenceContractTest -Dsurefire.failIfNoSpecifiedTests=false test`
- 结果:
- `BUILD SUCCESS`
- `CustServiceCircularDependencyContractTest`: 1 tests / 0 failures / 0 errors / 0 skipped
- `PrestoragePaymentRelationSequenceContractTest`: 1 tests / 0 failures / 0 errors / 0 skipped
- 说明:
- `-Dsurefire.failIfNoSpecifiedTests=false` 用于 reactor 前置模块无匹配 `-Dtest` 用例时继续执行到 `sw-business-server`
- 本次复核覆盖四处 `CustService` 懒加载注入 contract 与 `biz_prestorage_payment_relation_id_seq` 注解 contract

View File

@ -0,0 +1,315 @@
# REV004 / accountProcess 接口真值矩阵2026-04-13
## 目的
给前端/联调/验收一个“当前代码真实口径”矩阵:
- 哪个页面/弹窗对应哪些接口
- 每个接口当前稳定返回哪些关键字段
- 哪些字段已经被真实库测试覆盖
- 哪些地方仍然是页面编排层面的剩余缺口
> 口径来源:`water-backend` 当前控制器 + Req/RespVO + 已落地真实库 canary。
---
## 一、页面 / 弹窗 -> 接口映射
### 1. 预存调整页
#### 查询区
- `GET /admin-api/business/accounting-adjust/prestorage-page`
- `GET /admin-api/business/accounting-adjust/prestorage-stat`
- `GET /admin-api/business/accounting-adjust/prestorage-detail?id={id}`
#### 动作区
- `POST /admin-api/business/accounting-adjust/prestorage-submit`
- `POST /admin-api/business/accounting-adjust/prestorage-revoke`
- `GET /admin-api/business/accounting-adjust/prestorage-process?adjustmentNo=...`
- `GET /admin-api/business/accounting-adjust/prestorage-attachments?adjustmentNo=...`
#### 维护区
- `POST /prestorage-customer-update`
- `POST /prestorage-meter-update`
- `POST /prestorage-billing-update`
- `POST /prestorage-discount-update`
- `POST /prestorage-cost-component-update`
### 2. 已销调整页
#### 查询区
- `GET /admin-api/business/accounting-adjust/sold-page`
- `GET /admin-api/business/accounting-adjust/sold-stat`
#### 动作区
- `POST /admin-api/business/accounting-adjust/sold-submit`
- `POST /admin-api/business/accounting-adjust/sold-batch-revoke`
### 3. 未销调整页
#### 查询区
- `GET /admin-api/business/accounting-adjust/unsold-page`
- `GET /admin-api/business/accounting-adjust/unsold-stat`
#### 动作区(按弹窗切换)
- 调整:`POST /unsold-adjust-submit`
- 分账:`POST /unsold-split-submit`
- 违约金减免:`POST /unsold-late-fee-reduce-submit`
- 价差调整:`POST /unsold-price-diff-submit`
- 呆坏账:`POST /unsold-bad-debt-submit`
#### 批量区
- `POST /unsold-adjust-batch-submit`
- `POST /unsold-split-batch-submit`
- `POST /unsold-late-fee-reduce-batch-submit`
- `POST /unsold-price-diff-batch-submit`
- `POST /unsold-bad-debt-batch-submit`
### 4. 账务日志页
#### 查询区
- `GET /admin-api/business/accounting-adjust/log-page`
- `GET /admin-api/business/accounting-adjust/log-stat`
- `GET /admin-api/business/accounting-adjust/log-detail?adjustmentNo=...`
#### 二次动作弹窗
- 转退款:`POST /admin-api/business/accounting-adjust/log-refund`
- 转预存:`POST /admin-api/business/accounting-adjust/log-prestorage`
- 撤销:`POST /admin-api/business/accounting-adjust/log-revoke`
#### 辅助弹窗
- `GET /admin-api/business/accounting-adjust/log-process?adjustmentNo=...`
- `GET /admin-api/business/accounting-adjust/log-attachments?adjustmentNo=...`
---
## 二、关键返回字段真值
### A. 通用动作返回 `AccountingAdjustRespVO`
适用:
- `prestorage-submit`
- `sold-submit`
- `unsold-*submit`
- `log-refund`
- `log-prestorage`
当前稳定字段:
- `adjustmentNo`:主联调键
- `objectType`:对象类型;未销调整 AMOUNT/USAGE 场景可能为空,其他场景一般有值
- `resultStatus`:推荐前端主读字段
- `approvalStatus`
- `writeBackStatus`
- `approvalRequired`
- `resultObjectNo`
- `msg`
- `status`:兼容旧字段
- `message`:兼容旧字段
前端推荐:
- **优先读** `resultStatus / approvalStatus / writeBackStatus / msg`
- `status / message` 仅做兼容保底
### B. 撤销 / 审批动作返回 `AccountingAdjustActionRespVO`
适用:
- `prestorage-revoke`
- `log-revoke`
当前稳定字段:
- `adjustmentNo`
- `objectType`
- `actionType`(当前撤销返回 `REVOKE`
- `approvalStatus`
- `resultStatus`
- `writeBackStatus`
- `message`
- `actionTime`
### C. 预存分页 `AccountingAdjustPrestoragePageRespVO`
关键字段:
- `id`
- `adjustmentNo`
- `custId`
- `workOrderStatus`
- `adjustmentType`
- `custCode / custName / custAddress`
- `targetCustCode`
- `adjustAmount / preBalance / postBalance`
- `adjustReason / remark`
- `createTime / creatorName`
- `canRevoke / canViewAttachment`
补充:
- `prestorage-detail` 在上面基础上增加:
- `objectType`
- `resultStatus`
- `approvalStatus`
- `writeBackStatus`
- `processInstanceId`
- `attachmentRefs`
### D. 已销分页 `AccountingAdjustSoldPageRespVO`
关键字段:
- `id`
- `custId`
- `accountMonth`
- `custCode / custName / custAddress`
- `waterNature`
- `billedWaterVolume`
- `actualAmount`
- `billedAmount`
- `penaltyAmount`
- `collectionMethod`
- `collector`
- `collectionDate`
- `canAdjust`
- `canBatchRevoke`
注意:
- 已销查询页**不是调整日志页**,所以这里没有 `adjustmentNo`
- `sold-submit` 提交后,前端若要看申请单号,应跳到 `log-*` 维度或使用提交返回值保存的 `adjustmentNo`
### E. 未销分页 `AccountingAdjustUnsoldPageRespVO`
关键字段:
- `id`
- `custId`
- `custCode / custName / custAddress`
- `waterType`
- `accountMonth`
- `waterVolume`
- `totalAmount / billAmount / penaltyAmount`
- `waterFee / sewageFee / garbageFee / resourcesFee / overPlanFee / seasonalSupplement`
- `canAdjust / canSplit / canLateFeeReduce / canPriceDiffAdjust / canBadDebtAdjust`
### F. 日志分页 `AccountingAdjustLogPageRespVO`
关键字段:
- `id`
- `adjustmentNo`
- `accountMonth`
- `custId / custCode / custName / custAddress`
- `accountTypeCode / accountType`
- `processMethodCode / processMethod`
- `amount`
- `description`
- `registrant / handler`
- `handleTime / createTime`
- `statusCode / status`
- `targetCustCode`
- `originalPrestore / newPrestore`
- `originalBill / newBill`
- `originalWaterVolume / newWaterVolume`
- `originalPenalty / newPenalty`
- `billChange / waterVolumeChange / penaltyChange`
- `canRevoke / canRefund / canPrestore`
### G. 日志详情 `AccountingAdjustLogDetailRespVO`
在日志分页基础上增加:
- `objectType`
- `resultStatus`
- `approvalStatus`
- `writeBackStatus`
- `originalTranSeq`
- `originalSysTranSeq`
- `attachmentRefs`
- `details[]`
### H. 流程 / 附件
#### `AccountingAdjustProcessRespVO`
关键字段:
- `adjustmentNo`
- `objectType`
- `adjustType`
- `chargeId`
- `actionAmount`
- `resultStatus`
- `approvalStatus`
- `writeBackStatus`
- `taskId`
- `processState`
- `latestMessage`
- `latestOperationTime`
- `stages[]`
#### `AccountingAdjustAttachmentRespVO`
关键字段:
- `adjustmentNo`
- `ref`
- `resolved`
- `id / name / link / size / extension`
- `message`
---
## 三、当前已经被真实库测试证明的字段/链路
### 已证明
#### 预存
- `prestorage-submit` 返回 `adjustmentNo/resultStatus/writeBackStatus`
- `prestorage-page` 可见新记录
- `prestorage-stat` 可统计到 `refundCount`
- `prestorage-detail` 可按 `id` 回看 `resultStatus/writeBackStatus`
- `prestorage-revoke` 可回滚余额并生成撤销日志
#### 已销
- `sold-submit` 返回 `adjustmentNo/resultStatus=PENDING_APPROVAL`
- `sold-page` 可见原账单,且 `canBatchRevoke=true`
- `sold-stat` 可统计 `totalCount / totalActualAmount`
- `sold-batch-revoke` 可按营业账 ID 执行撤销
#### 未销
- 五类 submit 均已跑通真实库 happy path
- adjust
- split
- late-fee-reduce
- price-diff
- bad-debt
- 动作返回值与 operat_log 写入已被验证
#### 日志
- `log-page / log-stat / log-detail / log-process / log-attachments` 已形成一组真实库联动回看
- `log-refund``log-prestorage``log-revoke` 已打通闭环
- `log-detail``originalTranSeq / attachmentRefs` 已被验证
---
## 四、前端使用建议(当前真值口径)
### 1. 提交后立即展示
提交类动作完成后,前端应优先使用提交响应里的:
- `adjustmentNo`
- `resultStatus`
- `approvalStatus`
- `writeBackStatus`
- `msg`
### 2. 页面列表回看
- 预存页:查 `prestorage-page`
- 已销页:查 `sold-page`
- 未销页:查 `unsold-page`
- 若要看“申请/动作留痕”,查 `log-page`
### 3. 二次动作弹窗
- 日志页弹窗(退款/转预存/撤销)都应以 `adjustmentNo` 作为主键
- 已销批量撤销当前以 `chargeIds[]` 为入参,不是 `adjustmentNo[]`
### 4. 状态口径
- 预存页 `workOrderStatus`0未处理 / 1已审核 / 2已完成 / 3已撤销
- 通用动作结果:
- `resultStatus`
- `approvalStatus`
- `writeBackStatus`
- 日志页状态:
- `statusCode/status` 为页面状态投影,不等于 `workOrderStatus`
---
## 五、剩余缺口
1. 还没有做“前端弹窗 A/B/C 切换路径”的 UI 级编排,只做到了接口级真实库链路。
2. 还没有把所有字段做成“页面元素 -> 字段 -> 接口 -> 证据”的逐项勾稽表。
3. 若后续要做最终验收,建议再补一版:
- 弹窗功能分组矩阵
- 字段级 contract/assertion matrix
---
## 六、证据索引
- 集成测试 bootstrap 与累计验证:
- `docs/evidence/rev004-accountprocess-integration-testing-bootstrap-2026-04-13.md`
- 代码位置:
- `sw-business-server/src/main/java/.../accountProcess/*Controller.java`
- `sw-business-server/src/main/java/.../accountProcess/vo/*ReqVO.java`
- `sw-business-server/src/main/java/.../accountProcess/vo/*RespVO.java`
- `sw-business-server/src/test/java/.../integration/rev004/accountprocess/Rev004AccountProcessCanaryQueryIntegrationTest.java`

View File

@ -0,0 +1,104 @@
# REV004 / accountProcess 页面文案与字典 label 核对表2026-04-13
## 1. 目的
本表站在前端/UAT 视角,回答:
- 页面上显示的文案/标签,当前应从哪个字段读取?
- 该字段是原始编码、字典 label还是代码 fallback 值?
- 当前是否已具备稳定真值来源?
> 说明:本表是“字段/label 来源核对”,不是视觉稿验收。
---
## 2. 预存调整页
| 页面展示项 | 建议展示来源 | 类型 | 当前真值来源 | 状态 |
|---|---|---|---|---|
| 工单状态 | `workOrderStatus` -> `work_status` label | 字典 label | `DictTypeConstants.WORK_STATUS` + 正式设计文档 + seed SQL | 已确认 |
| 调整类型 | `adjustmentType` | 业务枚举/旧口径 | 代码已有字段,但 label 映射未单列文档 | 待前端确认展示文案 |
| 客户编号/名称/地址 | `custCode/custName/custAddress` | 直接值 | DB 真值 | 已确认 |
| 调整余额/期初/期末余额 | `adjustAmount/preBalance/postBalance` | 直接值 | DB 真值 | 已确认 |
| 是否可撤销 | `canRevoke` | 布尔能力 | Service 推导 | 已确认 |
| 是否可查看附件 | `canViewAttachment` | 布尔能力 | Service 推导 | 已确认 |
| 提交成功提示 | `msg` | 文本 | Action 返回 | 已确认 |
| 详情结果状态 | `resultStatus` | 字典 label 候选 | 当前后端主要返回 code前端如需文案应绑 `account_adjust_result_status` | 已确认 |
| 详情审批状态 | `approvalStatus` | 字典 label 候选 | 绑 `account_adjust_approval_status` | 已确认 |
| 详情回写状态 | `writeBackStatus` | 字典 label 候选 | 绑 `account_adjust_writeback_status` | 已确认 |
### 预存页当前结论
- 工单状态四态已稳定:`0未处理 / 1已审核 / 2已完成 / 3已撤销`
- 详情三状态字段推荐前端统一走 REV004 新字典做 label 显示
- `prestorage-process / prestorage-attachments` 已有真实库证据,页面已具备展示基础
---
## 3. 已销调整页
| 页面展示项 | 建议展示来源 | 类型 | 当前真值来源 | 状态 |
|---|---|---|---|---|
| 用水性质 | `waterNature` | 当前为模板名/编码映射 | `PriceTemplate` 名称映射 | 已确认 |
| 收费方式 | `collectionMethod` | 字典 label | `charge_method` | 已确认 |
| 收费员 | `collector` | 直接值 | DB 字段 | 已确认 |
| 是否可调整 | `canAdjust` | 布尔能力 | Service 推导 | 已确认 |
| 是否可批量撤销 | `canBatchRevoke` | 布尔能力 | Service 推导 | 已确认 |
| 提交结果状态 | `resultStatus` | 字典 label 候选 | `account_adjust_result_status` | 已确认 |
| 提交审批状态 | `approvalStatus` | 字典 label 候选 | `account_adjust_approval_status` | 已确认 |
### 已销页当前结论
- 收费方式文案应以后端经 `charge_method` 解析后的 `collectionMethod` 为准
- 已销提交后的审批态展示建议直接使用返回 code + 新字典映射 label
- 批量撤销按钮能力应以后端 `canBatchRevoke``chargeIds[]` 入参约束为准
---
## 4. 未销调整页
| 页面展示项 | 建议展示来源 | 类型 | 当前真值来源 | 状态 |
|---|---|---|---|---|
| 用水性质 | `waterType` | 当前为模板编码 | 代码直接回 `priceTemplateCode` | 待前端决定是否需要 label 化 |
| 合计金额/账单金额/违约金 | `totalAmount/billAmount/penaltyAmount` | 直接值 | DB 真值 | 已确认 |
| 五类按钮显隐 | `canAdjust/canSplit/canLateFeeReduce/canPriceDiffAdjust/canBadDebtAdjust` | 布尔能力 | Service 推导 | 已确认 |
| 调整原因 | `applyReason -> 动态 reason 字典` | 字典 label | 见 REV004_DICT_BINDING_MATRIX | 已确认 |
| 分账原因 | `applyReason -> separate_reason` | 字典 label | 见 REV004_DICT_BINDING_MATRIX | 已确认 |
| 违约金减免原因 | `applyReason -> late_fee_reason` | 字典 label | 见 REV004_DICT_BINDING_MATRIX | 已确认 |
| 价差调整原因 | `applyReason -> price_reason` | 字典 label | 见 REV004_DICT_BINDING_MATRIX | 已确认 |
| 呆坏账原因 | `applyReason -> knotty_reason` | 字典 label | 见 REV004_DICT_BINDING_MATRIX | 已确认 |
### 未销页当前结论
- 五类弹窗的“原因”不能共用一个字典,应按 objectType/弹窗类型动态切换
- `waterType` 当前更像编码字段,如果前端要展示“中文用水性质”,需要确认是否再做模板名映射
---
## 5. 账务日志页
| 页面展示项 | 建议展示来源 | 类型 | 当前真值来源 | 状态 |
|---|---|---|---|---|
| 账务类型 | `accountType` | 字典 label | `account_adjust_object_type` | 已确认 |
| 处理方式 | `processMethod` | 代码映射 label | Service 内 legacy 映射 | 已确认 |
| 页面状态 | `status/statusCode` | 字典/兼容投影 | success/pending/rejected 由 Service 投影 | 已确认 |
| 目标户号 | `targetCustCode` | 直接值 | 日志明细/业务上下文 | 已确认 |
| 原/新预存、账单、水量、违约金 | 对应 original/new 字段 | 直接值 | DB + log detail 推导 | 已确认 |
| 是否可撤销/退款/预转存 | `canRevoke/canRefund/canPrestore` | 布尔能力 | Service 推导 | 已确认 |
| 结果状态 | `resultStatus` | 字典 label 候选 | `account_adjust_result_status` | 已确认 |
| 审批状态 | `approvalStatus` | 字典 label 候选 | `account_adjust_approval_status` | 已确认 |
| 回写状态 | `writeBackStatus` | 字典 label 候选 | `account_adjust_writeback_status` | 已确认 |
| 附件状态 | `resolved/message` | 直接值 | Attachment 解析逻辑 | 已确认 |
### 日志页当前结论
- `accountType``resultStatus/approvalStatus/writeBackStatus` 已有稳定字典来源
- `processMethod` 目前是代码映射,不完全等于字典 label需要前端按当前返回文案展示不建议自行二次翻译
- 日志页是当前最适合承接“状态展示 / 二次动作 / 留痕回看”的页面
---
## 6. 当前仍需前端/UAT逐页确认的点
1. 未销页 `waterType` 是否需要从编码升级为可读 label。
2. 预存页 `adjustmentType` 的页面中文文案是否已有既定口径。
3. 日志页 `processMethod/status` 是否与现有页面视觉稿/原型文案完全一致。
4. 历史页面或旧截图中若还出现“处理中”这类旧三态文案,应以后端新四态口径为准重新校对。
---
## 7. 结论
- 当前**状态字典与核心文案来源已经收口**,后端没有明显“真值不清”的问题。
- 剩余问题主要不在后端结构,而在:
- 页面展示是否直接使用后端返回文案
- 前端是否仍残留旧页面文案/旧字典绑定
- 某些编码字段是否还需要做前端可读化处理

View File

@ -0,0 +1,63 @@
# REV004 accountProcess Real Wiring Cleanup Evidence (2026-06-05)
## Scope
- Frontend repository: `/Volumes/Dpan/github/water-workspace/water-frontend`
- Pages/components:
- `src/views/accountProcess/accountLog/index.vue`
- `src/views/accountProcess/accountLog/components/RefundForm.vue`
- `src/views/accountProcess/accountLog/components/TransferPrestoreForm.vue`
- `src/views/accountProcess/soldAdjustment/index.vue`
- `src/views/accountProcess/unsoldAdjustment/components/UnsoldAdjustmentForm.vue`
- `src/views/accountProcess/unsoldAdjustment/components/PriceAdjustmentForm.vue`
- API:
- `src/api/accountProcess/accountLog/index.ts`
## Interface Basis
- `GET /business/accounting-adjust/log-page`
- `GET /business/accounting-adjust/log-stat`
- `GET /business/accounting-adjust/log-process`
- `GET /business/accounting-adjust/log-attachments`
- `GET /business/accounting-adjust/log-export`
- `POST /business/accounting-adjust/log-refund`
- `POST /business/accounting-adjust/log-prestorage`
- `POST /business/accounting-adjust/log-revoke`
Basis documents:
- `docs/evidence/rev004-accountlog-action-wiring-notes-2026-04-13.md`
- `docs/evidence/rev004-accountprocess-interface-truth-matrix-2026-04-13.md`
## Verification
| Command | Result |
| --- | --- |
| `node --test tests/rev004/accountProcessRealWiring.test.mjs` | PASS (7/7) |
| `node --test tests/rev006/unsoldAdjustmentSubmitPayload.test.mjs tests/rev006/soldAdjustmentSubmit.test.mjs tests/rev006/badDebtBatchSubmitContext.test.mjs` | PASS (29/29) |
| `pnpm build:dev` | PASS |
## Commits
```
5d00f526 test(accountprocess): cover remaining mock wiring gaps
e001f9c0 feat(accountprocess): add account log action APIs
b133fcc5 feat(accountprocess): wire account log refund action
bf763907 feat(accountprocess): wire account log prestore action
a48bb23c feat(accountprocess): wire account log page actions
012fba78 fix(accountprocess): use customer id in sold detail navigation
ebc7bfc3 fix(accountprocess): remove unsold adjustment demo defaults
7b10e414 fix(accountprocess): remove hardcoded price adjustment selectors
```
## Result
- Account log secondary actions no longer show success without a backend call.
- Account log export, process, attachments, and revoke actions call formal account log APIs.
- Account log and sold adjustment customer links use the selected row `custId`.
- Unsold adjustment no longer seeds `111` / `11.1` / `0.9` demo values.
- Price adjustment no longer exposes hardcoded `112` / `测试2` options.
## Remaining Follow-up
- `src/views/accountProcess/index.vue` remains a static REV-004 workspace/handoff dashboard backed by `rev004.data.ts`; this is not converted to a live dashboard in this cleanup.

View File

@ -0,0 +1,85 @@
# REV004 / 分账短版说明(给产品 / 前端2026-04-14
## 一句话结论
**分账** 不是退款,也不是减免。
它的业务含义是:
> 把一笔原账单按规则拆成多笔可独立处理的结果,便于分别收费、分别开票、分别承担。
当前 REV004 后端已经支持:
- **发起分账申请**
- **进入待审批**
- **被日志/查询/审批状态承接**
当前还不支持:
- **审批通过后真正执行拆账,生成多张目标账单**
---
## 当前可以怎么理解
### 业务上
老系统分账支持两种方式:
1. **按水量分账**
2. **按费用组成分账**
它适用于:
- 客户需要开多张发票
- 客户不能一次缴清
- 一张账单需要按规则拆给多个结果对象
### 后端当前落地到哪里
当前后端实现的是:
- `SPLIT_ADJUST` 分账申请
- 提交后状态:`PENDING_APPROVAL`
- 可以查询、留痕、审批承接
但还没有做到:
- 真的把一张原账单拆成多张目标账单
- 真的生成拆分明细
- 真的进入收费/开票承接执行态
---
## 为什么还没做到执行态
因为“真正分账执行”不是普通字段修改,而是一个独立正式业务对象级能力,至少涉及:
- 原账单与目标账单关系
- 分摊规则
- 分摊明细
- 审批通过后的执行流程
- 执行失败回滚
- 与收费/开票的后续承接
所以当前先落的是:
> **申请态 / 受理态**
而不是:
> **执行态 / 拆账落地态**
---
## 现在前端该怎么处理
### 当前前端可以做的
- 把“分账”当成一种**待审批业务申请**来用
- 提交后展示:
- `adjustmentNo`
- `resultStatus = PENDING_APPROVAL`
- `approvalStatus = PENDING_APPROVAL`
- `writeBackStatus = PENDING`
- 在日志/列表中查看这笔申请的留痕与状态
### 当前前端不要假设的
- 不要假设提交分账后会立刻生成多张新账单
- 不要假设已经存在完整的“分账结果明细页”可直接读取目标账单集合
---
## 如果后续要继续做
建议后续按这个顺序推进:
1. 明确业务真值:分账结果到底是“多张子账单”为主,还是“多笔待收费结果”为主
2. 明确数据模型:`SplitAdjust / SplitAdjustDetail`
3. 明确审批后执行:真正拆账、重算、留痕、回看
4. 再补前端执行态页面
---
## 当前最适合对外的话术
> 当前 REV004 已支持“分账申请”的统一受理、审批与查询回看,但尚未落到“审批通过后真正拆账生成多张子账单”的执行态。若后续要继续建设,需要按独立正式业务对象方式展开,而不是把它当成普通账单字段修改处理。

View File

@ -0,0 +1,195 @@
# REV004 / 分账为什么还没做到执行态2026-04-14
## 1. 目的
本摘要用于解释:
为什么当前 `REV004/accountProcess` 后端已经支持 `SPLIT_ADJUST` 分账申请,
但还没有做到“审批通过后真正把一张原账单拆成多张目标账单”的执行态能力。
---
## 2. 当前结论
### 结论一句话
当前后端实现的是:
- **分账申请态**
尚未实现的是:
- **分账执行态**
也就是:
- 现在可以提交分账申请、进入待审批、被日志与查询承接;
- 但还没有真正生成多张目标账单、分摊明细和后续收费/开票承接结果。
---
## 3. 代码证据
### 3.1 当前入口
当前分账在 `accountProcess` 中的入口是:
- `POST /admin-api/business/accounting-adjust/unsold-split-submit`
对应代码:
- `AccountingAdjustProcessServiceImpl.createUnsoldSplit(...)`
其行为是把分账提交统一转成:
- `objectType = SPLIT_ADJUST`
- `adjustType = SPLIT`
然后交给统一账务处理入口:
- `chargeService.adjustAccounting(...)`
### 3.2 当前真正处理逻辑
`ChargeServiceImpl.handleSplitAdjust(...)` 中,当前逻辑只有:
- `approvalRequired = true`
- `resultStatus = PENDING_APPROVAL`
- `approvalStatus = PENDING_APPROVAL`
- `writeBackStatus = PENDING`
- `message = 账单拆分申请已提交,待审批`
- `needUpdate = false`
这说明:
1. 只受理申请;
2. 不改原账单;
3. 不生成目标账单;
4. 不生成拆分明细;
5. 不进入真正执行态。
### 3.3 当前校验逻辑
当前只做了最小申请校验:
- 仅未收费账单允许发起分账申请
- `splitCount >= 2`
- `splitCount <= 12`
- 必须填写原因编码
- 必须填写调整原因
这也进一步说明:
- 当前实现重心是“能不能发起分账申请”
- 不是“如何执行真正的分账结果落地”
---
## 4. 业务复杂度为什么高
真正的分账执行态,不是普通字段修改,而是**结构性重分摊**。
### 4.1 文档中的两类策略
老系统与文档明确支持:
- **按水量分账**
- **按费用组成分账**
### 4.2 按水量分账需要解决的问题
- 原账单总水量拆成多笔
- 拆分水量之和必须等于原水量
- 拆分后重新按水价重算金额
- 可能影响违约金、阶梯、费用明细
### 4.3 按费用组成分账需要解决的问题
- 原账单费用项按规则拆分
- 拆分后金额总额必须与原账单一致
- 目标账单 / 目标客户 / 金额分摊关系需要明确
- 费用项粒度如何绑定目标结果也需要独立明细结构
### 4.4 执行态至少需要的能力
若真正落地执行态,后端至少需要:
- 原账单与目标账单关系模型
- 分摊规则模型
- 分摊明细模型
- 审批通过后的执行流程
- 执行失败回滚
- 与收费/开票/日志/查询的后续承接
因此,这不是“小补丁”能力,而是**独立正式业务对象级别**的建设。
---
## 5. 文档证据
### 5.1 需求与手册口径
文档明确说明:
- 分账是“由一笔账单分成两笔独立账单信息”
- 可按“按水量”“按费用组成”进行分账调整
- 适用于:
- 客户开多张发票
- 不能一次缴清等场景
这说明业务真值更接近:
- **账单拆分 + 分摊结果重建**
不是单纯加一条审批记录。
### 5.2 REV004 正式对象口径
`REV004_FULL_ACCOUNTING_DOMAIN_DESIGN.md` 中已将:
- `SplitAdjust`
- `SplitAdjustDetail`
视为独立正式业务对象,并建议存在:
- `split_adjust_no`
- `split_rule_type`
- `source_charge_id`
- `target_charge_id`
- `target_cust_id`
- `split_amount`
- `split_ratio / split_basis`
这表明:
- 正式执行态设计方向已经明确;
- 但当前代码还没有真正落成这套模型。
### 5.3 文档中的关键冻结判断
文档还明确给出:
- 当前先冻结设计方向
- 分账调整后续应补规则表或规则字段组
这意味着:
- “申请态先落、执行态后补”是有设计背景的,
- 不是单纯遗漏开发。
---
## 6. 当前这轮 REV004 的实现目标
从当前整体 accountProcess 收口策略看,这轮优先解决的是:
- 统一提交入口
- 统一状态表达
- 审批边界
- 日志留痕
- 查询回看
也就是先做到:
- 用户能发起
- 系统能记录
- 前端能查看
- 审批流能承接
因此分账也被纳入了这套统一账务调整框架,先实现:
- **申请态可用**
而没有直接推进到:
- **执行态拆账**
---
## 7. 最终判断
### 当前已经做到
- `SPLIT_ADJUST` 已进入统一账务调整对象体系
- 可提交申请
- 返回 `PENDING_APPROVAL`
- 可被日志/查询/审批状态承接
### 当前还没做到
- 审批通过后真正生成多张目标账单
- 原账单与目标账单的主从关系落库
- 按水量/按费用组成的拆分明细落库
- 执行态后的收费/开票联动承接
### 最准确的一句话
> 当前 REV004 后端已经实现“分账申请态”,但由于真正的分账执行态涉及分摊规则、目标账单生成、拆分明细、重算与后续收费/开票承接,属于独立正式对象级能力,因此尚未在 `accountProcess` 这一层落地。
---
## 8. 建议下一步
如果后续要继续推进“分账执行态”,建议按顺序展开:
1. 先明确业务真值:
- 结果对象到底是多张子账单、还是多笔待收费结果、还是两者同时存在
2. 再明确数据模型:
- `SplitAdjust` / `SplitAdjustDetail` 主表、明细表、规则字段组
3. 再明确执行流程:
- 审批通过 -> 拆账执行 -> 重算 -> 留痕 -> 查询回看
4. 最后补前端:
- 按水量 / 按费用组成 两种弹窗的真实执行态交互

View File

@ -0,0 +1,250 @@
# REV004 / accountProcess 页面元素勾稽矩阵2026-04-13
## 目的
`accountProcess` 当前后端真实口径进一步整理成:
**页面元素 / 弹窗能力 -> 字段 -> 接口 -> 当前证据状态**
便于:
- 前端逐项对表
- UAT 做验收勾稽
- 后端明确哪些已被真实库证实,哪些仍属于页面编排层缺口
> 本表基于当前 `water-backend` 代码、Req/RespVO、已落地真实库 canary 测试结果整理。
---
## 证据状态说明
- **已真实库验证**:已有真实 PostgreSQL + Spring + MockMvc canary 覆盖
- **已代码确认**:可从当前 Controller/RespVO/Service 明确确认,但尚未单独做真实库断言
- **待补证据**:当前还缺更细粒度验证或 UI 编排验证
---
## 一、预存调整页
### A. 列表区
| 页面元素 | 后端字段 | 接口 | 当前状态 | 说明 |
|---|---|---|---|---|
| 列表记录主键 | `id` | `GET /prestorage-page` | 已真实库验证 | 提交后可回查 detail |
| 调整单号 | `adjustmentNo` | `GET /prestorage-page` | 已真实库验证 | 提交后 page 可见 |
| 客户ID | `custId` | `GET /prestorage-page` | 已代码确认 | Response VO 已定义 |
| 工单状态 | `workOrderStatus` | `GET /prestorage-page` | 已真实库验证 | 已验证提交后为 `2=已完成` |
| 调整类型 | `adjustmentType` | `GET /prestorage-page` | 已代码确认 | page/detail 都有 |
| 客户编号 | `custCode` | `GET /prestorage-page` | 已真实库验证 | `REV004_IT_SRC` 已验证 |
| 目标户号 | `targetCustCode` | `GET /prestorage-page` | 已代码确认 | 转账/转预存场景使用 |
| 客户名称 | `custName` | `GET /prestorage-page` | 已代码确认 | |
| 客户地址 | `custAddress` | `GET /prestorage-page` | 已代码确认 | |
| 调整余额 | `adjustAmount` | `GET /prestorage-page` | 已代码确认 | |
| 期初余额 | `preBalance` | `GET /prestorage-page` | 已代码确认 | |
| 期末余额 | `postBalance` | `GET /prestorage-page` | 已代码确认 | |
| 调整原因 | `adjustReason` | `GET /prestorage-page` | 已代码确认 | |
| 备注 | `remark` | `GET /prestorage-page` | 已代码确认 | |
| 登记时间 | `createTime` | `GET /prestorage-page` | 已代码确认 | |
| 登记人员 | `creatorName` | `GET /prestorage-page` | 已代码确认 | |
| 是否可撤销 | `canRevoke` | `GET /prestorage-page` | 已代码确认 | 撤销能力由状态推导 |
| 是否可查看附件 | `canViewAttachment` | `GET /prestorage-page` | 已代码确认 | |
### B. 统计区
| 页面元素 | 后端字段 | 接口 | 当前状态 | 说明 |
|---|---|---|---|---|
| 总条数 | `totalCount` | `GET /prestorage-stat` | 已真实库验证 | |
| 调整余额合计 | `totalAdjustAmount` | `GET /prestorage-stat` | 已代码确认 | |
| 期初余额合计 | `totalPreBalance` | `GET /prestorage-stat` | 已代码确认 | |
| 期末余额合计 | `totalPostBalance` | `GET /prestorage-stat` | 已代码确认 | |
| 预存转账数量 | `transferCount` | `GET /prestorage-stat` | 已真实库验证 | 已验证退款场景下为 0 |
| 预存退款数量 | `refundCount` | `GET /prestorage-stat` | 已真实库验证 | 已验证退款场景下为 1 |
### C. 提交弹窗
| 页面元素 | 入参/出参字段 | 接口 | 当前状态 | 说明 |
|---|---|---|---|---|
| 源客户编号 | `custCode` | `POST /prestorage-submit` | 已真实库验证 | |
| 目标户号 | `targetCustCode` | `POST /prestorage-submit` | 已真实库验证 | 转账场景可用;退款场景未单独验证 |
| 退款金额 | `refundAmount` | `POST /prestorage-submit` | 已真实库验证 | |
| 转账金额 | `transferAmount` | `POST /prestorage-submit` | 已代码确认 | 未单独真实库验证 |
| 原因 | `applyReason` | `POST /prestorage-submit` | 已真实库验证 | |
| 备注 | `remark` | `POST /prestorage-submit` | 已真实库验证 | |
| 附件引用 | `attachmentRefs` | `POST /prestorage-submit` | 已代码确认 | 未单独真实库验证 |
| 调整单号回显 | `adjustmentNo` | `POST /prestorage-submit` 返回 | 已真实库验证 | |
| 结果状态 | `resultStatus` | `POST /prestorage-submit` 返回 | 已真实库验证 | `SUCCESS` |
| 回写状态 | `writeBackStatus` | `POST /prestorage-submit` 返回 | 已真实库验证 | `UPDATED` |
| 提示文案 | `msg/message` | `POST /prestorage-submit` 返回 | 已代码确认 | |
### D. 详情 / 附件 / 流程 / 撤销
| 页面元素 | 后端字段 | 接口 | 当前状态 | 说明 |
|---|---|---|---|---|
| 详情对象类型 | `objectType` | `GET /prestorage-detail` | 已代码确认 | |
| 详情结果状态 | `resultStatus` | `GET /prestorage-detail` | 已真实库验证 | |
| 详情审批状态 | `approvalStatus` | `GET /prestorage-detail` | 已代码确认 | |
| 详情回写状态 | `writeBackStatus` | `GET /prestorage-detail` | 已真实库验证 | |
| 流程任务ID | `processInstanceId` | `GET /prestorage-detail` | 已代码确认 | |
| 附件引用 | `attachmentRefs` | `GET /prestorage-detail` | 已代码确认 | |
| 查看流程 | `processState/stages[]` | `GET /prestorage-process` | 已代码确认 | 预存页流程接口存在,未单独真实库验证 |
| 查看附件 | `ref/resolved/...` | `GET /prestorage-attachments` | 已代码确认 | 预存页附件接口存在,未单独真实库验证 |
| 撤销动作 | `actionType/resultStatus/writeBackStatus` | `POST /prestorage-revoke` | 已真实库验证 | 余额恢复已验证 |
---
## 二、已销调整页
### A. 列表区
| 页面元素 | 后端字段 | 接口 | 当前状态 | 说明 |
|---|---|---|---|---|
| 账单ID | `id` | `GET /sold-page` | 已真实库验证 | |
| 客户ID | `custId` | `GET /sold-page` | 已代码确认 | |
| 账务年月 | `accountMonth` | `GET /sold-page` | 已代码确认 | |
| 客户编号 | `custCode` | `GET /sold-page` | 已真实库验证 | |
| 客户名称 | `custName` | `GET /sold-page` | 已代码确认 | |
| 客户地址 | `custAddress` | `GET /sold-page` | 已代码确认 | |
| 用水性质 | `waterNature` | `GET /sold-page` | 已代码确认 | |
| 开账水量 | `billedWaterVolume` | `GET /sold-page` | 已代码确认 | |
| 实收金额 | `actualAmount` | `GET /sold-page` | 已真实库验证 | stat 已验证 80.00 |
| 开账金额 | `billedAmount` | `GET /sold-page` | 已代码确认 | |
| 违约金 | `penaltyAmount` | `GET /sold-page` | 已代码确认 | |
| 收费方式 | `collectionMethod` | `GET /sold-page` | 已代码确认 | |
| 收费员 | `collector` | `GET /sold-page` | 已代码确认 | |
| 收费日期 | `collectionDate` | `GET /sold-page` | 已代码确认 | |
| 是否可调整 | `canAdjust` | `GET /sold-page` | 已代码确认 | |
| 是否可批量撤销 | `canBatchRevoke` | `GET /sold-page` | 已真实库验证 | 提交后仍可撤销 |
### B. 统计区
| 页面元素 | 后端字段 | 接口 | 当前状态 | 说明 |
|---|---|---|---|---|
| 总条数 | `totalCount` | `GET /sold-stat` | 已真实库验证 | |
| 开账水量合计 | `totalBilledWaterVolume` | `GET /sold-stat` | 已代码确认 | |
| 实收金额合计 | `totalActualAmount` | `GET /sold-stat` | 已真实库验证 | 80.00 |
| 开账金额合计 | `totalBilledAmount` | `GET /sold-stat` | 已代码确认 | |
| 违约金合计 | `totalPenaltyAmount` | `GET /sold-stat` | 已代码确认 | |
### C. 提交 / 批量撤销
| 页面元素 | 入参/出参字段 | 接口 | 当前状态 | 说明 |
|---|---|---|---|---|
| 营业账ID | `chargeId` | `POST /sold-submit` | 已真实库验证 | |
| 原因 | `applyReason` | `POST /sold-submit` | 已真实库验证 | |
| 是否红冲发票 | `redInvoice` | `POST /sold-submit` | 已代码确认 | |
| 备注 | `remark` | `POST /sold-submit` | 已真实库验证 | |
| 附件引用 | `attachmentRefs` | `POST /sold-submit` | 已代码确认 | |
| 调整单号 | `adjustmentNo` | `POST /sold-submit` 返回 | 已真实库验证 | |
| 结果状态 | `resultStatus` | `POST /sold-submit` 返回 | 已真实库验证 | `PENDING_APPROVAL` |
| 审批状态 | `approvalStatus` | `POST /sold-submit` 返回 | 已真实库验证 | `PENDING_APPROVAL` |
| 批量撤销入参 | `chargeIds[]` | `POST /sold-batch-revoke` | 已真实库验证 | 不是 `adjustmentNo[]` |
| 批量撤销结果 | `successCount/failCount/items[]` | `POST /sold-batch-revoke` | 已真实库验证 | |
---
## 三、未销调整页
### A. 列表区
| 页面元素 | 后端字段 | 接口 | 当前状态 | 说明 |
|---|---|---|---|---|
| 账单ID | `id` | `GET /unsold-page` | 已代码确认 | |
| 客户ID | `custId` | `GET /unsold-page` | 已代码确认 | |
| 客户编号 | `custCode` | `GET /unsold-page` | 已代码确认 | |
| 客户名称 | `custName` | `GET /unsold-page` | 已代码确认 | |
| 客户地址 | `custAddress` | `GET /unsold-page` | 已代码确认 | |
| 用水性质 | `waterType` | `GET /unsold-page` | 已代码确认 | |
| 账务年月 | `accountMonth` | `GET /unsold-page` | 已代码确认 | |
| 用水量 | `waterVolume` | `GET /unsold-page` | 已代码确认 | |
| 合计金额 | `totalAmount` | `GET /unsold-page` | 已代码确认 | |
| 账单金额 | `billAmount` | `GET /unsold-page` | 已代码确认 | |
| 违约金 | `penaltyAmount` | `GET /unsold-page` | 已代码确认 | |
| 用水费/污水/垃圾/资源税/超定额/季节性 | 对应费用字段 | `GET /unsold-page` | 已代码确认 | |
| 是否可调整 | `canAdjust` | `GET /unsold-page` | 已代码确认 | |
| 是否可分账 | `canSplit` | `GET /unsold-page` | 已代码确认 | |
| 是否可违约金减免 | `canLateFeeReduce` | `GET /unsold-page` | 已代码确认 | |
| 是否可价差调整 | `canPriceDiffAdjust` | `GET /unsold-page` | 已代码确认 | |
| 是否可呆坏账调整 | `canBadDebtAdjust` | `GET /unsold-page` | 已代码确认 | |
### B. 统计区
| 页面元素 | 后端字段 | 接口 | 当前状态 | 说明 |
|---|---|---|---|---|
| 用水量合计 | `sumWaterVolume` | `GET /unsold-stat` | 已代码确认 | |
| 合计金额 | `sumTotalAmount` | `GET /unsold-stat` | 已代码确认 | |
| 账单金额 | `sumBillAmount` | `GET /unsold-stat` | 已代码确认 | |
| 违约金 | `sumPenaltyAmount` | `GET /unsold-stat` | 已代码确认 | |
| 费用组成合计 | `sumWaterFee/...` | `GET /unsold-stat` | 已代码确认 | |
### C. 五类弹窗动作
| 弹窗 | 关键入参 | 返回口径 | 当前状态 | 说明 |
|---|---|---|---|---|
| 调整 | `chargeId/applyReason/remark/targetBillWater/targetExtendedAmount/targetBillAmount` | `adjustmentNo/resultStatus/writeBackStatus` | 已真实库验证 | AMOUNT happy path 已验证 |
| 分账 | `chargeId/applyReason/remark/splitCount` | `adjustmentNo/objectType/resultStatus=PENDING_APPROVAL` | 已真实库验证 | |
| 违约金减免 | `chargeId/applyReason/remark/lateFeeReduceAmount` | `adjustmentNo/objectType/resultStatus=PENDING_APPROVAL` | 已真实库验证 | |
| 价差调整 | `chargeId/applyReason/remark/priceDiffAmount` | `adjustmentNo/objectType/resultStatus=PENDING_APPROVAL` | 已真实库验证 | |
| 呆坏账 | `chargeId/applyReason/remark/badDebtAmount` | `adjustmentNo/objectType=result BAD_DEBT_RECORD / PENDING_APPROVAL` | 已真实库验证 | |
---
## 四、账务日志页
### A. 列表区
| 页面元素 | 后端字段 | 接口 | 当前状态 | 说明 |
|---|---|---|---|---|
| 日志ID | `id` | `GET /log-page` | 已代码确认 | |
| 调整单号 | `adjustmentNo` | `GET /log-page` | 已真实库验证 | |
| 账务年月 | `accountMonth` | `GET /log-page` | 已代码确认 | |
| 客户ID/编号/名称/地址 | `custId/custCode/custName/custAddress` | `GET /log-page` | 部分已真实库验证 | `custCode` 已验证 |
| 账务类型 | `accountTypeCode/accountType` | `GET /log-page` | 已代码确认 | |
| 处理方式 | `processMethodCode/processMethod` | `GET /log-page` | 已代码确认 | |
| 金额 | `amount` | `GET /log-page` | 已代码确认 | |
| 描述 | `description` | `GET /log-page` | 已代码确认 | |
| 登记人/处理人 | `registrant/handler` | `GET /log-page` | 已代码确认 | |
| 处理时间/创建时间 | `handleTime/createTime` | `GET /log-page` | 已代码确认 | |
| 页面状态 | `statusCode/status` | `GET /log-page` | 已真实库验证 | `SUCCESS` 场景已验证 |
| 目标户号 | `targetCustCode` | `GET /log-page` | 已代码确认 | |
| 原/新预存 | `originalPrestore/newPrestore` | `GET /log-page` | 已代码确认 | |
| 原/新账单 | `originalBill/newBill` | `GET /log-page` | 已代码确认 | |
| 原/新水量 | `originalWaterVolume/newWaterVolume` | `GET /log-page` | 已代码确认 | |
| 原/新违约金 | `originalPenalty/newPenalty` | `GET /log-page` | 已代码确认 | |
| 变化量 | `billChange/waterVolumeChange/penaltyChange` | `GET /log-page` | 已代码确认 | |
| 是否可撤销/退款/转预存 | `canRevoke/canRefund/canPrestore` | `GET /log-page` | 已代码确认 | |
### B. 统计区
| 页面元素 | 后端字段 | 接口 | 当前状态 | 说明 |
|---|---|---|---|---|
| 总条数 | `totalCount` | `GET /log-stat` | 已真实库验证 | |
| 调整金额 | `totalAmount` | `GET /log-stat` | 已代码确认 | |
| 调整水量 | `totalWaterVolume` | `GET /log-stat` | 已代码确认 | |
| 已完成数量 | `completedCount` | `GET /log-stat` | 已真实库验证 | |
| 未完成数量 | `pendingCount` | `GET /log-stat` | 已真实库验证 | sold 提交后场景已间接证明 pending |
| 已撤销数量 | `cancelledCount` | `GET /log-stat` | 已代码确认 | |
### C. 详情 / 流程 / 附件
| 页面元素 | 后端字段 | 接口 | 当前状态 | 说明 |
|---|---|---|---|---|
| 详情调整单号 | `adjustmentNo` | `GET /log-detail` | 已真实库验证 | |
| 对象类型 | `objectType` | `GET /log-detail` | 已代码确认 | |
| 结果状态 | `resultStatus` | `GET /log-detail` | 已真实库验证 | |
| 审批状态 | `approvalStatus` | `GET /log-detail` | 已代码确认 | |
| 回写状态 | `writeBackStatus` | `GET /log-detail` | 已代码确认 | |
| 原交易流水号 | `originalTranSeq` | `GET /log-detail` | 已真实库验证 | |
| 原系统流水号 | `originalSysTranSeq` | `GET /log-detail` | 已代码确认 | |
| 附件引用 | `attachmentRefs[]` | `GET /log-detail` | 已真实库验证 | |
| 详情明细 | `details[]` | `GET /log-detail` | 已代码确认 | |
| 流程状态 | `processState` | `GET /log-process` | 已真实库验证 | |
| 流程阶段 | `stages[]` | `GET /log-process` | 已代码确认 | |
| 附件原始引用 | `ref` | `GET /log-attachments` | 已真实库验证 | |
| 附件解析状态 | `resolved` | `GET /log-attachments` | 已真实库验证 | |
| 附件元数据 | `id/name/link/size/extension` | `GET /log-attachments` | 已代码确认 | 当前 mock-ref 未解析为真实附件 |
### D. 日志页二次动作弹窗
| 弹窗 | 关键入参 | 返回口径 | 当前状态 | 说明 |
|---|---|---|---|---|
| 转退款 | `adjustmentNo/reasonCode/reason` | `adjustmentNo/objectType/resultStatus/writeBackStatus` | 已真实库验证 | 且 bank 后续流水已验证 |
| 转预存 | `adjustmentNo/targetCustCode/reason` | `adjustmentNo/resultStatus` | 已真实库验证 | 目标余额变化已验证 |
| 撤销 | `adjustmentNo/comment` | `actionType/resultStatus/writeBackStatus` | 已真实库验证 | 回滚账单状态/余额已验证 |
---
## 五、当前最适合前端/UAT对表的使用方式
1. **动作成功后的即时展示**:直接用 `AccountingAdjustRespVO / AccountingAdjustActionRespVO`
2. **页面列表展示**:按页面查各自 `*-page / *-stat`
3. **动作留痕与二次操作**:统一以 `log-*` 视角看 `adjustmentNo`
4. **已销批量撤销特别注意**:当前后端口径是 `chargeIds[]`,不是 `adjustmentNo[]`
---
## 六、仍待补的更细证据
- `unsold-page / unsold-stat` 的真实库回看断言还未单独补
- 预存页 `attachments/process` 还未像 log 页一样做完整真实库链路断言
- 页面元素到字典标签展示(例如名称 label 是否完全满足 UI 文案)还缺单独验收

View File

@ -0,0 +1,222 @@
# REV004 / accountProcess UI 编排级验收清单2026-04-13
## 1. 目的
本清单面向前端 / UAT / 联调验收人员,回答:
- 某个页面有哪些核心弹窗/动作入口
- 提交后应该回看哪些区域/接口
- 当前这些链路是否已有后端真实库证据支撑
> 本清单不替代自动化测试,而是给人工联调 / 验收一份“按页面操作”的顺序脚本。
---
## 2. 使用说明
每条清单包含:
- **入口**:从哪个页面/按钮进入
- **动作**:调哪个后端接口
- **回看**:操作成功后应检查哪些页面区域
- **后端证据状态**:当前是否已有真实库证据
状态说明:
- **已真实库覆盖**:已有后端集成测试证明主链可运行
- **已代码确认**:后端接口与字段存在,但尚未专门做该页面编排级回看
---
## 3. 预存调整页
### 场景 P1预存退款提交 -> 列表/统计/详情回看
- 入口:预存调整页“预存退款”弹窗
- 动作接口:`POST /prestorage-submit`
- 提交后应回看:
- 列表:`GET /prestorage-page`
- 统计:`GET /prestorage-stat`
- 详情:`GET /prestorage-detail?id=...`
- 应确认:
- 新 `adjustmentNo` 出现在列表中
- 工单状态为“已完成”
- `refundCount` 增加
- detail 中 `resultStatus=SUCCESS`
- 后端证据状态:**已真实库覆盖**
### 场景 P2预存退款提交 -> 流程/附件回看
- 入口:预存调整页提交成功后,点“查看流程 / 查看附件”
- 动作接口:
- `GET /prestorage-process?adjustmentNo=...`
- `GET /prestorage-attachments?adjustmentNo=...`
- 应确认:
- process 可返回 `processState=UPDATED`
- attachments 返回提交时带入的附件引用
- 后端证据状态:**已真实库覆盖**
### 场景 P3预存记录撤销 -> 列表/余额回退
- 入口:预存调整页列表“撤销”按钮
- 动作接口:`POST /prestorage-revoke`
- 回看:
- 列表撤销状态
- 账户余额恢复
- 关联撤销日志
- 后端证据状态:**已真实库覆盖**
### 场景 P4预存转账提交
- 入口:预存调整页“预存转账”弹窗
- 动作接口:`POST /prestorage-submit`(传 `targetCustCode + transferAmount`
- 回看:
- 列表记录
- 目标户号
- 期初/期末余额变化
- 后端证据状态:**已代码确认 / 部分真实库覆盖**
- 说明:转账字段链路存在,但当前主证据仍以退款场景为主
---
## 4. 已销调整页
### 场景 S1已销调整提交 -> 已销列表/统计回看
- 入口:已销调整页“提交”弹窗
- 动作接口:`POST /sold-submit`
- 回看:
- `GET /sold-page`
- `GET /sold-stat`
- 应确认:
- 原账单仍可见
- `canBatchRevoke=true`
- 统计金额仍符合账单真值
- 后端证据状态:**已真实库覆盖**
### 场景 S2已销调整提交 -> 日志页留痕回看
- 入口:已销调整提交成功后,使用返回的 `adjustmentNo` 在日志页查询
- 动作接口:
- `GET /log-detail?adjustmentNo=...`
- `GET /log-process?adjustmentNo=...`
- 应确认:
- 日志详情存在
- 状态为待审批
- 后端证据状态:**已代码确认 / 部分真实库覆盖**
- 说明:已销提交本身和日志链主字段已证实,但“提交后页面跳日志”的 UI 编排尚未专门做一条端到端脚本
### 场景 S3已销批量撤销
- 入口:已销调整页列表批量勾选 -> “批量撤销”
- 动作接口:`POST /sold-batch-revoke`
- 入参特点:`chargeIds[]`
- 回看:
- 列表可撤销状态变化
- 撤销日志存在
- 后端证据状态:**已真实库覆盖**
---
## 5. 未销调整页
### 场景 U1未销调整金额/水量)提交 -> 列表/统计回看
- 入口:未销调整页“调整”弹窗
- 动作接口:`POST /unsold-adjust-submit`
- 回看:
- `GET /unsold-page`
- `GET /unsold-stat`
- 应确认:
- `totalAmount/billAmount` 更新
- 统计金额同步变化
- 后端证据状态:**已真实库覆盖**
### 场景 U2未销分账提交
- 入口:未销调整页“分账”弹窗
- 动作接口:`POST /unsold-split-submit`
- 回看:
- 提交结果为待审批
- 如页面有日志联动入口,可再看日志留痕
- 后端证据状态:**已真实库覆盖(动作)**
### 场景 U3未销违约金减免提交
- 入口:未销调整页“违约金减免”弹窗
- 动作接口:`POST /unsold-late-fee-reduce-submit`
- 回看:提交结果状态 / 待审批标识
- 后端证据状态:**已真实库覆盖(动作)**
### 场景 U4未销价差调整提交
- 入口:未销调整页“价差调整”弹窗
- 动作接口:`POST /unsold-price-diff-submit`
- 回看:提交结果状态 / 待审批标识
- 后端证据状态:**已真实库覆盖(动作)**
### 场景 U5未销呆坏账提交
- 入口:未销调整页“呆坏账调整”弹窗
- 动作接口:`POST /unsold-bad-debt-submit`
- 回看:提交结果状态 / 待审批标识
- 后端证据状态:**已真实库覆盖(动作)**
### 场景 U6未销查询页初始态
- 入口:未销调整页默认查询
- 动作接口:
- `GET /unsold-page`
- `GET /unsold-stat`
- 应确认:
- 初始未销账单金额与违约金展示正确
- 五类按钮能力显隐正确
- 后端证据状态:**已真实库覆盖**
---
## 6. 账务日志页
### 场景 L1日志页默认查询 -> page/stat/detail 联动
- 入口:账务日志页查询
- 动作接口:
- `GET /log-page`
- `GET /log-stat`
- `GET /log-detail?adjustmentNo=...`
- 应确认:
- 列表、统计、详情三处状态一致
- 后端证据状态:**已真实库覆盖**
### 场景 L2日志页查看流程 / 附件
- 入口:日志页“查看流程 / 查看附件”
- 动作接口:
- `GET /log-process?adjustmentNo=...`
- `GET /log-attachments?adjustmentNo=...`
- 应确认:
- process 返回阶段与状态摘要
- attachments 返回引用及解析状态
- 后端证据状态:**已真实库覆盖**
### 场景 L3日志页转退款
- 入口:日志页“转退款”弹窗
- 动作接口:`POST /log-refund`
- 回看:
- 日志状态变化
- 账单收费状态变化
- bank follow-up 留痕
- 后端证据状态:**已真实库覆盖**
### 场景 L4日志页转预存
- 入口:日志页“转预存”弹窗
- 动作接口:`POST /log-prestorage`
- 回看:
- 日志状态变化
- 目标账户余额变化
- 可继续查看 process / attachments
- 后端证据状态:**已真实库覆盖**
### 场景 L5日志页撤销
- 入口:日志页“撤销”按钮
- 动作接口:`POST /log-revoke`
- 回看:
- 原账单支付状态恢复
- 目标预存余额回退
- 日志中出现撤销链记录
- 后端证据状态:**已真实库覆盖**
---
## 7. 当前对前端/UAT最重要的提醒
1. 已销批量撤销使用 `chargeIds[]`,不要误用 `adjustmentNo[]`
2. 日志页所有二次动作统一以 `adjustmentNo` 作为主键。
3. 未销页五类弹窗“原因”应动态切字典,不要共用一个原因下拉。
4. 若页面仍显示旧三态文案(如“处理中”),应以后端当前四态真值为准重新对齐。
---
## 8. 当前仍未通过本清单覆盖的点
1. 纯 UI 级交互(禁用态、二次确认提示、按钮联动)尚未自动化验证。
2. 边角筛选条件还未做完整穷尽。
3. 某些编码字段是否需要前端额外 label 化(如 `waterType`)仍需页面确认。

View File

@ -0,0 +1,156 @@
# REV004 未销违约金减免批量契约补齐实施记录2026-04-14
## 目标
补齐 `unsold-late-fee-reduce-batch-submit` 的批量 outer 契约,使其能够承接前端页面公共表单字段,并把字段真实透传到后端执行主链,而不是只停留在 controller VO。
## 本次实现
### 1. 批量 outer 契约补齐
文件:
- `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/vo/AccountingAdjustUnsoldLateFeeReduceBatchSubmitReqVO.java`
- `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/vo/AccountingAdjustUnsoldLateFeeReduceBatchItemReqVO.java`
新增 outer 字段:
- `lateFeeType`
- `applicant`
- `contactMobile`
- `applyReason`
- `remark`
- `attachmentRefs`
明细 item 独立为专用 VO
- `chargeId`
- `adjustmentNo`
- `applyReason`(兼容旧逐项提交)
- `remark`(兼容旧逐项提交)
- `attachmentRefs`(兼容旧逐项提交)
- `lateFeeReduceAmount`
- `startDate`
- `endDate`
### 2. 共享 batch 主链承接 outer 字段
文件:
- `.../AccountingAdjustBatchSubmitReqVO.java`
- `.../AccountingAdjustActionController.java`
- `.../AccountingAdjustProcessServiceImpl.java`
处理方式:
- controller 先把 late-fee batch outer 字段规范化映射到 generic batch DTO
- process 主链在 `executeBatchSubmit` 中统一执行 outer -> item 合并
- 统一 batch 主链承接字段:
- `reasonCode`
- `reason`
- `applicant`
- `contactMobile`
- `attachmentRefs`
- `lateFeeType`
这避免了“专用 VO 上有字段,但 generic batch 主链吃不到”的伪完成。
### 3. amount / date 模式校验补齐
文件:
- `.../AccountingAdjustProcessServiceImpl.java`
- `.../AccountingAdjustReqVO.java`
- `.../ChargeServiceImpl.java`
规则:
- `lateFeeType=1`(按金额)
- 要求 `lateFeeReduceAmount > 0`
- 不允许同时传 `startDate/endDate`
- `lateFeeType=2`(按日期)
- 要求 `startDate/endDate` 同时存在,且 `endDate >= startDate`
- 不允许同时传 `lateFeeReduceAmount`
- 未显式传 `lateFeeType` 时:
- 若存在日期字段,推导为 `2`
- 否则默认 `1`
### 4. 执行日志落地字段补齐
文件:
- `.../AccountingAdjustReqVO.java`
- `.../ChargeServiceImpl.java`
新增写入 operat log detail 的字段:
- `lateFeeType`
- `applicant`
- `contactMobile`
- `startDate`
- `endDate`
## 验证结果
### 编译
命令:
```bash
mvn -pl sw-business/sw-business-server -DskipTests compile
```
结果:**PASS**
### 定向单测
命令:
```bash
mvn -pl sw-business/sw-business-server \
-Dtest=AccountingAdjustActionControllerTest#batchCreateUnsoldLateFeeReduce_shouldReturnWrappedSuccess,AccountingAdjustProcessServiceImplTest#createUnsoldLateFeeReduce_shouldPassDateModeFieldsToUnifiedChargeService+createUnsoldLateFeeReduce_shouldRejectMixedAmountAndDateMode+batchCreateUnsoldLateFeeReduce_shouldMergeBatchOuterFieldsIntoItems \
test
```
结果:**PASS4 tests, 0 fail, 0 error**
### 真实 DB 定向集成
命令:
```bash
REV004_IT_DB_URL=jdbc:postgresql://192.168.10.130:5436/sw_system \
REV004_IT_DB_USERNAME=sw_system \
REV004_IT_DB_PASSWORD='Em@123456' \
mvn -pl sw-business/sw-business-server \
-Dtest=Rev004AccountProcessCanaryQueryIntegrationTest#unsoldLateFeeReduceBatchSubmit_shouldAcceptOuterFieldsAndDateModeContract \
test
```
结果:**PASS1 test, 0 fail, 0 error**
集成验证确认:
- batch outer 字段可提交
- `lateFeeType=2` date-mode 契约可进入主链
- operat log detail 中可看到:
- `lateFeeType=2`
- `applicant`
- `contactMobile`
- `startDate`
- `endDate`
- `attachmentRefs`
## 当前边界与剩余风险
1. **本轮重点是“提交契约 + 主链承接 + 日志落地”**
2. `lateFeeType=2` 的“按日期执行态/审批回写态”尚未展开为完整金额计算逻辑;当前已确保申请态与提交链路可用。
3. 由于仓库当前还存在其他在制改动,本轮只做了定向验证,没有把整个相关测试类全部拉绿。
## 结论
本轮已完成 REV004 未销违约金减免批量提交接口的主契约补齐,并把前端页面公共字段正式落到共享 batch 主链与日志记录中;可以支撑前端继续联调“按金额/按日期”两种提交形态。
## 追加收口(读模型展示字段)
### 本轮补齐
- `AccountingAdjustDetailRespVO` 新增:
- `lateFeeType`
- `applicant`
- `contactMobile`
- `startDate`
- `endDate`
- `AccountingAdjustLogDetailRespVO` 新增同名字段
- `AccountingAdjustProcessRespVO` 新增:
- `reasonCode`
- `attachmentRefs`
- `lateFeeType`
- `applicant`
- `contactMobile`
- `startDate`
- `endDate`
### 读取链路
- `AccountingAdjustQueryServiceImpl`:从 operat log detail 读取 late-fee 扩展字段并映射到 detail response
- `AccountingAdjustLogProcessServiceImpl`:从 request/action log detail 读取 late-fee 扩展字段并映射到 log-detail response
- `AccountingAdjustProcessServiceImpl#getProcess`:从 unified snapshot 读取并投影到 process response
### 追加验证
#### 定向读模型单测
```bash
mvn -pl sw-business/sw-business-server \
-Dtest=AccountingAdjustQueryServiceImplTest,AccountingAdjustLogProcessServiceImplTest,AccountingAdjustProcessServiceImplTest#getProcess_shouldExposeLateFeeDisplayFieldsFromUnifiedSnapshot \
test
```
结果:**PASS7 tests, 0 fail, 0 error**

View File

@ -0,0 +1,84 @@
# REV004 违约金规则确认2026-04-15
## 结论
当前后续实现应以以下业务规则作为真值:
> **违约金 = 欠缴水费金额 × 0.1‰ × 逾期天数**
该规则意味着:
- **按日计收**,不是按月滚动
- 计算基数是**欠缴水费金额**
- **不含违约金本身**
- **不得利滚利**
## 业务前提
违约金产生需满足:
1. 逾期未缴
2. 经供水单位通知后仍未缴
## 对 REV004 的实现影响
### 1. 计算模型
后续 `late-fee reduce` 的设计与实现,应以“按日计收”模型展开,而不是“按月滚动”模型。
### 2. date-mode 减免理解
`lateFeeType=2`(按日期)应理解为:
- 对指定日期区间内对应的逾期天数区间做减免
- 先计算该区间理论应收违约金
- 再形成:
- `lateFeeBefore`
- `reduceAmount`
- `lateFeeAfter`
### 3. 需要补齐/确认的系统要素
除现有:
- `lateFeeBeginDate`
- `lateFee`
- `penaltyCoefficient`
还应确认或补齐:
- 欠缴本金
- 通知状态 / 通知时间
- 逾期起算日
- 逾期天数
- 上限规则(如部分地区 30%
- 60 日后续处置边界
## 与当前仓库现状的关系
### 当前已确认存在的模型字段
- `ChargeDO.lateFeeBeginDate`
- `ChargeDO.lateFee`
- `CostComponentDO.penaltyCoefficient`
### 当前未确认存在的现成实现
仓库中尚未确认存在:
- 明确的“按日计收违约金”统一计算器
- 明确的“按日期区间重算违约金”统一服务
因此,当前应将该规则视为:
- **业务规则真值输入**
- 后续需要据此补正式实现
## 使用建议
后续 PRD / 设计稿 / 代码实现,不建议再使用“按月滚动”表述;应统一表述为:
> **按日计收、按区间减免、按期累计结果**
## 追加确认2026-04-15
### 系数来源模型
已进一步确认:
- 逐项重算违约金时,系数来源为 `CostComponentDO.penaltyCoefficient`
- 取数链路为:`ChargeDetailDO.costComponentCode -> CostComponentDO.code -> penaltyCoefficient`
### 参与计算范围
- 所有未缴费用项都参与。
- 不收违约金的费用项,不做额外白名单过滤,而是通过 `penaltyCoefficient = 0` 自然贡献 0。
### 汇总口径
- 每个费用项先单独计算违约金;
- 每项先四舍五入到分;
- 最后再求和。
### 当前仓库状态
- 已确认存在上述模型链路;
- 但尚未发现现成“逐项按日期重算违约金”的统一实现。

View File

@ -0,0 +1,27 @@
# REV004 late-fee formal table 部署 SQL 说明2026-04-15
## 新增脚本
- `sql/rev004/REV004_latefee_formal_tables_deploy.sql`
## 作用
为 late-fee reduce 的新 formal table 提供单独部署脚本,覆盖:
- `biz_latefee_reduce`
- `biz_latefee_reduce_detail`
- 对应 sequence
- 主键 / 唯一索引 / 常用索引
- 明细表外键
## 适用场景
1. 测试库 / 联调库先补表
2. 为真实 DB canary 排除“缺表”阻塞
3. 为后续 formal-table 路线提供独立部署入口
## 注意事项
- 当前脚本按 PostgreSQL / 测试环境风格编写
- 若正式环境不是 PostgreSQL需先做方言适配
- 此脚本只覆盖 late-fee 两张表,不包含坏账 / 核销 / 价差等对象
## 建议使用顺序
1. 先在目标库执行 `REV004_latefee_formal_tables_deploy.sql`
2. 再重跑 late-fee date-mode canary
3. 若通过,再考虑推广 formal-table 到其他对象

View File

@ -0,0 +1,122 @@
# REV004 违约金减免现结构与老字典差异摘要2026-04-14
## 结论
当前 REV004 违约金减免结构已经对齐老字典/老表结构的核心骨架:
- `LateFeeType` 仍保持 `1=按金额 / 2=按日期`
- 已承接申请人、联系电话、备注
- 已承接 `StartDate/EndDate`
- 已承接账单维度 `chargeId(FeeId)`
但当前更偏**申请契约 + 日志/读模型承接**,并未完整复刻老系统执行态表模型。
## 老字典/老表结构证据
### 主表 `PM_LATEFEE_RECORDS`
来源:`营收数据字典.md:3117-3126`
- `Applicant`
- `Mobile`
- `ApplyType`
- `LateFeeType`
- `State`
- `Remark`
- `TaskId`
- `StepId`
- `FlowRemark`
- `BusinessType`
### 明细表 `PM_LATEFEE_RECORD_DETAILS`
来源:`营收数据字典.md:3143-3158`
- `CustId/CustCode/CustName/CustAddress`
- `BillMonth`
- `LateFee`
- `ReduceMoney`
- `NewLateFee`
- `StartDate`
- `EndDate`
- `ProcType/ProcPerson/ProcDate/ProcRemark`
- `State`
- `FeeId`
### 老字典枚举
来源:`营收数据字典.md:5683-5697`
- `LateFeeReason`: `1=用户协商`, `2=其它`
- `LateFeeType`: `1=按金额`, `2=按日期`
## 当前结构证据
### DDL 草案
来源:`sql/rev004/REV004_accounting_adjustments_ddl.sql:289-403`
- 主表 `biz_latefee_reduce`
- `applicant_name`
- `applicant_mobile`
- `late_fee_type`
- `apply_reason_code`
- `remark`
- `approval_status`
- `reduce_status`
- 明细表 `biz_latefee_reduce_detail`
- `charge_id`
- `cust_id/cust_code/cust_name/cust_address`
- `bill_month`
- `start_date/end_date`
- `late_fee_before`
- `reduce_amount`
- `late_fee_after`
- `proc_type/proc_person/proc_time/proc_remark`
### 当前批量提交契约
来源:
- `AccountingAdjustUnsoldLateFeeReduceBatchSubmitReqVO`
- `AccountingAdjustUnsoldLateFeeReduceBatchItemReqVO`
当前 outer
- `lateFeeType`
- `applicant`
- `contactMobile`
- `applyReason`
- `remark`
- `attachmentRefs`
- `items`
当前 item
- `chargeId`
- `adjustmentNo`
- `lateFeeReduceAmount`
- `startDate`
- `endDate`
- 兼容项:`applyReason/remark/attachmentRefs`
## 已对齐项
1. `LateFeeType` 语义已对齐老字典
2. `Applicant/Mobile` 已有对应字段
3. `Remark` 已有对应字段
4. `StartDate/EndDate` 已有对应字段
5. `FeeId -> chargeId` 已有明确对应
6. `ReduceMoney -> lateFeeReduceAmount/reduce_amount` 语义已对齐
7. detail / log-detail / process 已可读出 late-fee 扩展字段
## 未完全落地项
1. **ApplyType / LateFeeReason 字典口径未完全统一**
- 老字典:`1=用户协商, 2=其它`
- 当前前端历史口径曾使用不同原因值
2. **State 未按老系统单字段口径落地**
- 当前主要使用 `approvalStatus/resultStatus/writeBackStatus`
3. **按日期模式仍偏申请态**
- 还未形成完整审批执行/计算回写
4. **执行结果落表未完整 runtime 化**
- DDL 草案已具备 `late_fee_before/reduce_amount/late_fee_after`
- 但当前核心还是契约 + 日志承接
5. **处理过程字段未完整 runtime 落地**
- `ProcType/ProcPerson/ProcDate/ProcRemark`
6. **老流程字段未完整复刻**
- `StepId/FlowRemark/BusinessType`
## 优先级建议
### P0
- 统一 `applyReason``LateFeeReason` 口径
- 给出老 `State` 与当前审批/回写状态映射表
### P1
- 补齐 `lateFeeType=2` 按日期模式执行态
- 审批通过后把结果正式落到 `biz_latefee_reduce / biz_latefee_reduce_detail`
### P2
- 再决定是否需要复刻 `StepId/FlowRemark/BusinessType/applicant_id`

View File

@ -0,0 +1,119 @@
# REV004 prestorage BPM 字段已应用到 application-dev 测试库2026-04-24
## 目标库
依据:
- `sw-business/sw-business-server/src/main/resources/application-dev.yaml`
解析结果:
- Host`192.168.10.130`
- Port`5436`
- DB`sw_system`
- User`sw_system`
## 本次目的
在既有 `biz_prestorage_adjust` / `biz_prestorage_adjust_detail` formal-table 基础上,补齐“保存即提审 / BPM 回写”所需的主表字段与索引。
## 实际执行
执行增量 DDL
- `ALTER TABLE biz_prestorage_adjust ADD COLUMN IF NOT EXISTS ...`
- 新增索引:
- `idx_biz_prestorage_adjust_process_instance`
- `idx_biz_prestorage_adjust_business_status`
- `idx_biz_prestorage_adjust_business_key`
同步更新本地脚本:
- `sql/rev004/REV004_prestorage_formal_tables_deploy.sql`
## 已补字段
- `business_status`
- `approval_result`
- `approval_comment`
- `execution_status`
- `execution_message`
- `approved_at`
- `rejected_at`
- `cancelled_at`
- `rolled_back_at`
- `process_instance_id`
- `process_definition_key`
- `business_key`
- `current_task_id`
- `current_task_name`
## 回读校验
### 字段回读
已确认上述 14 个字段均已存在于:
- `public.biz_prestorage_adjust`
### 索引回读
已确认以下索引存在:
- `idx_biz_prestorage_adjust_process_instance`
- `idx_biz_prestorage_adjust_business_status`
- `idx_biz_prestorage_adjust_business_key`
## DB smoke
在测试库中开启事务,插入一条带 BPM 字段的 `biz_prestorage_adjust` 记录并回读,随后回滚。
验证结果:
- 插入成功
- `business_status=IN_APPROVAL`
- `approval_status=PROCESSING`
- `execution_status=PENDING`
- `process_instance_id=PI-SMOKE-001`
- `current_task_name=预存调整审批`
- 回滚后 `left_count=0`
结果:**PASS**
## 当前结论
`application-dev` 指向的测试库已经具备 REV004 prestorage BPM 接线所需的数据库字段基础,可以继续进行:
1. 保存即发起 BPM
2. 审批回写业务状态
3. 审批中改类型重启流程
4. 审批中撤销终止流程
## 当前仍未覆盖
1. 真实 BPM 流程定义 `prestorage_adjust` 是否已在环境中部署,尚未验证;
2. 真实业务服务 + 真实 BPM 流程实例联调尚未完成;
3. 本次仅完成数据库结构到位与最小 smoke。
## 补充复核2026-04-24 11:05 +08:00
### 1. 部署脚本幂等重放
再次执行:
```bash
PGPASSWORD='Em@123456' \
psql -h 192.168.10.130 -p 5436 -U sw_system -d sw_system \
-v ON_ERROR_STOP=1 \
-f sql/rev004/REV004_prestorage_formal_tables_deploy.sql
```
结果:**PASS**
关键现象:
- 已存在 sequence / table / index 均以 `already exists, skipping` 跳过;
- 已存在 BPM 字段均以 `column ... already exists, skipping` 跳过;
- 脚本可重复执行,不会因本次增量字段而破坏既有库结构。
### 2. 相关模块回归测试
执行:
```bash
mvn -pl sw-business/sw-business-server,sw-module-bpm/sw-module-bpm-server -am \
-Dtest=AccountingAdjustProcessServiceImplTest,PrestorageBpmBridgeServiceTest,PrestorageBpmCallbackServiceTest,BpmPrestorageAdjustStatusListenerTest \
-Dsurefire.failIfNoSpecifiedTests=false test
```
结果:**BUILD SUCCESS**
测试汇总:
- `AccountingAdjustProcessServiceImplTest`27 通过
- `PrestorageBpmBridgeServiceTest`2 通过
- `PrestorageBpmCallbackServiceTest`3 通过
- `BpmPrestorageAdjustStatusListenerTest`1 通过
- 合计:**32/32 通过**
## 最新结论
截至 2026-04-24 11:05 +08:00
1. 测试库 BPM 字段已补齐;
2. 部署脚本可幂等重放;
3. 预存 BPM 相关业务/BPM 单测已通过;
4. 仍待真实 BPM 流程定义与真实流程实例联调验证。

View File

@ -0,0 +1,70 @@
# REV004 prestorage BPM 部署后集成检查2026-04-24
## 环境
- 主机:`root@192.168.10.130`
- 数据库:`jdbc:postgresql://192.168.10.130:5436/sw_system`
- 参考配置:
- `sw-business/sw-business-server/src/main/resources/application-dev.yaml`
- `sw-gateway/src/main/resources/application-dev.yaml`
## 检查目标
验证部署后的 REV004 预存调整 BPM 接口是否可通过网关/业务服务完成真实 HTTP 集成测试。
## 运行态探测
### 容器状态
- `sw-gateway`: Up
- `sw-module-bpm`: Up
- `sw-business-server`: `Up (health: starting)`,且 `RestartCount=7`
### 端口
- 网关:`48080`
- 业务服务映射:`48081 -> container 48090`
## HTTP 实测
### 1. 网关健康
`GET http://127.0.0.1:48080/actuator/health`
- 返回:`{"status":"UP"}`
- 结果PASS
### 2. 网关预存接口
`GET /admin-api/business/accounting-adjust/prestorage-page`
- 未登录:`401 账号未登录`
- 使用 `Authorization: emsoft1` 仍返回 `401`
- 结论:网关侧不支持直接使用 business 服务的 mock token 调测
### 3. 业务服务预存接口
`GET http://127.0.0.1:48081/admin-api/business/accounting-adjust/prestorage-page?pageNo=1&pageSize=1`
- `Authorization: emsoft1, tenant-id:0`:返回 `500 系统异常`
- `Authorization: emsoft1, tenant-id:1`:返回 `404 请求地址不存在`
## 日志根因
`docker logs sw-business-server` 明确显示:
- Spring 启动失败点:`Error creating bean with name 'accountingAdjustActionController': Injection of resource dependencies failed`
- 最终失败原因:
- `A component required a bean of type 'cn.com.emsoft.sw.module.bpm.api.task.BpmProcessInstanceApi' that could not be found.`
## 结论
当前 **不是接口联调数据问题**,而是 **部署后的 business 服务启动失败 / controller 未成功装配**,因此无法继续完成真实接口集成测试。
## 影响判断
本次预存 BPM 变更不仅修改了 business 模块,也修改了 BPM 相关模块:
- `sw-module-bpm-api/.../BpmProcessInstanceApi.java`
- `sw-module-bpm-api/.../dto/BpmProcessInstanceCancelReqDTO.java`
- `sw-module-bpm-server/.../BpmProcessInstanceApiImpl.java`
- `sw-module-bpm-server/.../RpcConfiguration.java`
- `sw-module-bpm-server/.../BpmPrestorageAdjustStatusListener.java`
因此若仅更新 `sw-business-server`,或 `sw-module-bpm` 未按同版本一并部署,就会出现当前缺少 `BpmProcessInstanceApi` Bean 的启动失败。
## 建议下一步
1. 同版本重新部署:
- `sw-business-server`
- `sw-module-bpm`
2. 部署后先验证:
- `sw-business-server` 健康状态变为 healthy / restart count 停止增长
- 业务服务日志不再出现 `BpmProcessInstanceApi` 缺失
3. 然后再执行预存 BPM 真实 HTTP 集成测试:
- save 即提审
- page/detail/process 查询
- revoke
- BPM 审批回写链路

View File

@ -0,0 +1,94 @@
# REV004 prestorage process/attachments strict formal-first2026-04-17
## 目标
收口:
- `GET /admin-api/business/accounting-adjust/prestorage-process`
- `GET /admin-api/business/accounting-adjust/prestorage-attachments`
使其优先读取 prestorage formal-table而不是继续主要依赖 legacy/unified fallback。
## 代码变更
核心变更位于:
- `AccountingAdjustProcessServiceImpl`
- `AccountingAdjustProcessServiceImplTest`
策略:
1. `getProcess(adjustmentNo)` 先尝试 `PrestorageFormalizationService.getView(adjustmentNo)`
2. 命中 formal view 时,直接组装 `AccountingAdjustProcessRespVO`
3. `getAttachments(adjustmentNo)` 先尝试读取 formal `attachmentRefs`
4. 仅在 formal 不存在时才回退到 synthetic / unified 口径
## 验证
### compile
```bash
mvn -pl sw-business/sw-business-server -DskipTests compile
```
结果:**PASS**
### targeted tests
```bash
mvn -pl sw-business/sw-business-server -Dtest=AccountingAdjustProcessServiceImplTest,AccountingAdjustPrestorageProcessServiceImplTest test
```
结果:**PASS**
- Tests run: 23
- Failures: 0
- Errors: 0
## fresh jar smokeport 48098
### health
- `GET /actuator/health` -> `{"status":"UP"}`
### smoke 数据
手工 seed
- `adjustmentNo = REV004-PTR-993001-SEED`
- formal main`biz_prestorage_adjust`
- formal detail`biz_prestorage_adjust_detail`
- attachment refs`101,proof-raw`
### process
调用:
- `GET /admin-api/business/accounting-adjust/prestorage-process?adjustmentNo=REV004-PTR-993001-SEED`
结果:**PASS**
返回关键字段:
- `objectType = LEGACY_PRESTORAGE_TRANSFER`
- `adjustType = TRANSFER`
- `actionAmount = 20.0`
- `resultStatus = SUCCESS`
- `approvalStatus = NOT_REQUIRED`
- `writeBackStatus = UPDATED`
- `attachmentRefs = ["101", "proof-raw"]`
- `applicant = 王五`
- `contactMobile = 13700000000`
- `latestMessage = 预存转账成功,目标户号=C-993002`
说明process 已命中 formal main而不是再走 unified detail 兜底。
### attachments
调用:
- `GET /admin-api/business/accounting-adjust/prestorage-attachments?adjustmentNo=REV004-PTR-993001-SEED`
结果:**PASS**
返回:
- ref=`101` -> 数值 ID 解析,当前附件实体不存在,返回 `resolved=false, message=附件不存在`
- ref=`proof-raw` -> 非数值引用,返回 `resolved=false, message=附件引用不是数值ID保留原始引用`
说明attachments 已优先读取 formal `attachmentRefs`,并保留现有解析策略。
## 清理
已删除:
- `biz_prestorage_adjust / detail` seed 数据
回读结果:
- `prestorage_main_left=0`
- `prestorage_detail_left=0`
运行态:
- `48098` 已停止
## 当前结论
本轮 prestorage 剩余查询缺口已完成 strict formal-first 收口:
1. `prestorage-process` 优先命中 formal
2. `prestorage-attachments` 优先命中 formal attachment refs
3. formal 缺失时仍可继续 fallback
4. 主链路 schema / 写路径无变更

View File

@ -0,0 +1,119 @@
# REV004 预存调整 prestorage-submit 开发环境调用记录2026-04-23
- 验证日期2026-04-23
- 后端仓基线:`21714d64822268d8dcb7cd1296656b4c19ff95fa`
- 前端仓基线:`d2698e1f5d107422f64928086d89d4b803cd12ae`
- 目标环境:`root@192.168.10.130`
- 服务端口:`sw-gateway -> 127.0.0.1:48080``sw-business-server -> 127.0.0.1:48090`
- 配置依据:
- `water-backend/sw-gateway/src/main/resources/application-dev.yaml`
- `water-backend/sw-business/sw-business-server/src/main/resources/application-dev.yaml`
- 鉴权方式:开发环境 `sw.security.mock-enable=true`,使用 `Authorization: Bearer emsoft1` + `tenant-id: 1`
## 1. 实际调用 payload
```json
{
"applicant": "李四",
"applyReason": "充值错误",
"attachmentRefs": [],
"contactMobile": "19928382738",
"custCode": "26041011111",
"refundAmount": 2,
"remark": "ssa"
}
```
## 2. 实际调用命令
### 2.1 直连 business
```bash
curl -X POST 'http://127.0.0.1:48090/admin-api/business/accounting-adjust/prestorage-submit' \
-H 'Authorization: Bearer emsoft1' \
-H 'tenant-id: 1' \
-H 'Content-Type: application/json' \
--data '{"applicant":"李四","applyReason":"充值错误","attachmentRefs":[],"contactMobile":"19928382738","custCode":"26041011111","refundAmount":2,"remark":"ssa"}'
```
### 2.2 走 gateway
```bash
curl -X POST 'http://127.0.0.1:48080/admin-api/business/accounting-adjust/prestorage-submit' \
-H 'Authorization: Bearer emsoft1' \
-H 'tenant-id: 1' \
-H 'Content-Type: application/json' \
--data '{"applicant":"李四","applyReason":"充值错误","attachmentRefs":[],"contactMobile":"19928382738","custCode":"26041011111","refundAmount":2,"remark":"ssa"}'
```
## 3. 调用结果
直连 business 与走 gateway 返回一致:
```json
{"code":500,"data":null,"msg":"系统异常"}
```
## 4. 后端日志根因
`sw-business-server` 日志显示真实异常为:
```text
java.lang.IllegalArgumentException: 账户预存余额不足
at cn.com.emsoft.sw.business.service.accountingadjust.accountProcess.AccountingAdjustProcessServiceImpl.createPrestorageAction(AccountingAdjustProcessServiceImpl.java:213)
```
对应代码位置:
- `water-backend/sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/accountProcess/AccountingAdjustProcessServiceImpl.java`
关键逻辑:
- 读取客户与账户
- 将 `deposit == null` 视为 `0`
- 校验 `deposit < refundAmount` 时抛出 `IllegalArgumentException("账户预存余额不足")`
## 5. 数据库核验
开发库(`sw_system`)中当前客户与账户数据:
```sql
select id, code, name, population, address, status, tenant_id
from biz_cust
where deleted = 0 and code = '26041011111';
select id, cust_id, deposit, uncheck_money, overdraft, status, deleted, tenant_id
from biz_account
where cust_id = 67 and deleted = 0;
```
结果:
```text
biz_cust:
67 | 26041011111 | liao | 3 | 11 | 0 | 1
biz_account:
65 | 67 | null | null | 0.0000 | 0 | 0 | 1
```
结论:当前测试客户 `26041011111``biz_account.deposit` 为空,业务侧按 `0` 处理,因此退款金额 `2` 会命中“账户预存余额不足”。
## 6. 结论
本次 `prestorage-submit` 调用链路本身可达,鉴权可用,接口也已命中后端业务逻辑。
失败原因不是网关/路由/权限,而是当前测试数据不满足退款校验条件:
- 客户存在
- 账户存在
- 账户预存余额为空(按 0 处理)
- `refundAmount = 2`,因此提交失败
## 7. 后续建议
二选一:
1. 修正该测试客户的 `biz_account.deposit`,使其大于等于 `2`
2. 改用当前开发库中预存余额充足的客户进行 `prestorage-submit` 联调
附带观察:当前前端收到的是泛化后的 `系统异常`,而非后端真实业务提示 `账户预存余额不足`。如果需要提升联调效率,可后续评估是否将该类参数/业务校验异常以明确业务消息返回前端。

View File

@ -0,0 +1,135 @@
# REV004 price-diff formal-table 已应用到 application-dev 测试库2026-04-17
## 目标库
依据:
- `sw-business/sw-business-server/src/main/resources/application-dev.yaml`
解析结果:
- Host`192.168.10.130`
- Port`5436`
- DB`sw_system`
- User`sw_system`
## 已执行脚本
- `sql/rev004/REV004_price_diff_formal_tables_deploy.sql`
## DDL 执行结果
结果:**PASS**
已确认存在:
- `biz_price_diff_adjust`
- `biz_price_diff_adjust_detail`
- `biz_price_diff_adjust_seq`
- `biz_price_diff_adjust_detail_seq`
已确认幂等重放通过:
- second apply 输出 `already exists, skipping`
- 无重复约束/重复索引异常
## 代码验证
### compile
```bash
mvn -pl sw-business/sw-business-server -DskipTests compile
```
结果:**PASS**
说明:
- 先顺手修复 develop 基线 compile blocker
- `CustApiImpl.java` 去掉 `updateCustPriceTemplateCode` 上多余 `@Override`
- `CustServiceImpl.java` 去掉 `dept.getCode()` 调用
- 之后串行重跑 compile 成功
- 曾出现一次 `NoSuchFileException ... target/generated-sources/...`,确认为 compile 与 test 并行访问同一 `target/` 目录导致的竞态,不属于业务代码错误
### targeted tests
```bash
mvn -pl sw-business/sw-business-server -Dtest=AccountingAdjustActionServiceImplTest,AccountingAdjustProcessServiceImplTest,AccountingAdjustQueryServiceImplTest,PriceDiffFormalizationServiceTest test
```
结果:**PASS**
- Tests run: 43
- Failures: 0
- Errors: 0
## HTTP smokefresh jar, port 48095
### health
- `GET /actuator/health` -> `{"status":"UP"}`
### submit
- `unsold-price-diff-submit` on charge `992205` -> `REV004-992205-20260417153413`
- `unsold-price-diff-submit` on charge `992206` -> `REV004-992206-20260417153414`
- 返回统一为:
- `objectType = PRICE_DIFF_ADJUST`
- `approvalStatus = PENDING_APPROVAL`
- `resultStatus = PENDING_APPROVAL`
- `writeBackStatus = PENDING`
### get / page
- `GET /admin-api/business/accounting-adjust/get?adjustmentNo=REV004-992205-20260417153413`
- 可返回 formal detail
- `priceDiffAmount=15.50`
- `billAmountBefore=100.00`
- `billAmountAfter=115.50`
- `extendedAmountBefore=100.00`
- `extendedAmountAfter=115.50`
- `GET /admin-api/business/accounting-adjust/page?...objectType=PRICE_DIFF_ADJUST`
- 可返回 2 条 fresh smoke formal-first 记录
- `reasonCodeLabel` 已能解析为字典文本(如 `用户协商` / `定价错误`
### approve / reject
- approve`/admin-api/business/accounting-adjust/approve`
- 返回 `APPROVED / SUCCESS / UPDATED`
- reject`/admin-api/business/accounting-adjust/reject`
- 返回 `REJECTED / FAIL / SKIPPED`
## DB 回读
`biz_price_diff_adjust`
- `REV004-992205-20260417153413 -> APPROVED | UPDATED | SUCCESS`
- `REV004-992206-20260417153414 -> REJECTED | SKIPPED | FAIL`
`biz_price_diff_adjust_detail`
- approve 明细:`SUCCESS | APPROVE_EXECUTE | approve price diff smoke`
- reject 明细:`REJECTED | REJECT | reject price diff smoke`
- approve 样例金额前后值:
- `bill_amount_before=100.00 -> bill_amount_after=115.50`
- `extended_amount_before=100.00 -> extended_amount_after=115.50`
`biz_charge`
- `992205 -> bill_amount=115.5000, extended_amount=115.5000, original_money=115.5000`
- `992206 -> bill_amount=80.0000, extended_amount=80.0000, original_money=80.0000`
## 测试数据清理
smoke 完成后已删除:
- `biz_operat_log / detail`identify_value = `992205`, `992206`
- `biz_price_diff_adjust / detail`
- `biz_charge``992205`, `992206`
- `biz_cust``REV004_PD_SMOKE_A`, `REV004_PD_SMOKE_B`
回读结果:
- `pricediff_main_left=0`
- `pricediff_detail_left=0`
- `operat_log_left=0`
- `charge_left=0`
- `cust_left=0`
## 运行态清理
用于 fresh-jar HTTP smoke 的临时实例:
- port `48095`
已停止,当前无残留 LISTEN 进程。
## 当前结论
本轮 price-diff formal-table 已达到:
1. DDL 可落库且可幂等重放;
2. unsold price-diff submit 能落 pending formal main/detail
3. approve/reject 会同步 formal 状态;
4. detail/page 对 `PRICE_DIFF_ADJUST` 已能返回 formal-first 结果;
5. 账单回写与当前 `applyPriceDiffWriteBack(...)` 口径一致;
6. fresh smoke / cleanup 证据完整。
## 当前仍需收口
- 这批尚未提交 commit / PR
- 如果后续要更贴近原系统 `PM_PRICE_RECORDS / DETAILS` 语义,可继续补:
- `PriceListId`
- `PriceCode`
- `IsLadder`
- `NewFeeId`
- 更细颗粒的 old/new late fee 与账单差额字段来源收口。

View File

@ -0,0 +1,212 @@
# REV004 redink formal-table 已应用到 application-dev 测试库2026-04-17
## 目标库
依据:
- `sw-business/sw-business-server/src/main/resources/application-dev.yaml`
解析结果:
- Host`192.168.10.130`
- Port`5436`
- DB`sw_system`
- User`sw_system`
## 已执行脚本
- `sql/rev004/REV004_redink_formal_tables_deploy.sql`
## DDL 执行结果
结果:**PASS**
已确认存在:
- `biz_redink_record`
- `biz_redink_record_detail`
- `biz_redink_record_seq`
- `biz_redink_record_detail_seq`
已确认幂等重放通过:
- second apply 输出 existing/skip 结果
- 本轮顺手把 sequence 创建方式从 `CREATE SEQUENCE IF NOT EXISTS` 收口为显式 `pg_class` 检查,规避 replay 时 sequence 级重复键异常
## 代码验证
### compile
```bash
mvn -pl sw-business/sw-business-server -DskipTests compile
```
结果:**PASS**
### targeted tests
```bash
mvn -pl sw-business/sw-business-server -Dtest=ChargeServiceAccountingAdjustTest,AccountingAdjustQueryServiceImplTest,RedinkFormalizationServiceTest test
```
结果:**PASS**
- Tests run: 34
- Failures: 0
- Errors: 0
## fresh query smokefresh jar, port 48096
### health
- `GET /actuator/health` -> `{"status":"UP"}`
### smoke 方式说明
REDINK_RECORD 当前依赖原交易定位 / bank follow-up 逻辑;本轮先采用:
1. application-dev 手工 seed 一条 redink formal 主表/明细表记录;
2. fresh jar 验证 `get/page` 是否已 formal-first 命中;
3. 不在本轮直接伪造 live bank follow-up 交易。
### seed 记录
- `adjustmentNo = REV004-RI-992305-SEED`
- `chargeId = 992305`
- formal 主表状态:
- `approvalStatus = NOT_REQUIRED`
- `executeStatus = UPDATED`
- `resultStatus = SUCCESS`
- bank follow-up 样例:`BK-992305`
### get / page
- `GET /admin-api/business/accounting-adjust/get?adjustmentNo=REV004-RI-992305-SEED`
- 返回:
- `objectType = REDINK_RECORD`
- `resultStatus = SUCCESS`
- `approvalStatus = NOT_REQUIRED`
- `writeBackStatus = UPDATED`
- `originalTranSeq = T-992305`
- `originalSysTranSeq = SYS-992305`
- detail 中包含:
- `bankTranSeq`
- `redinkAmount`
- `payStateBefore`
- `payStateAfter`
- `GET /admin-api/business/accounting-adjust/page?...objectType=REDINK_RECORD`
- 返回 1 条 formal-first 记录
- `reasonCodeLabel` 已能解析为字典文本(如 `收费错误`
## 当前结论
本轮 redink formal-table 已达到:
1. DDL 可落库且可幂等重放;
2. 结果型 formal-table 主从表已建模;
3. query detail/page 对 `REDINK_RECORD` 已能返回 formal-first
4. compile / targeted tests 通过;
5. fresh jar query smoke 已验证 formal-first 查询链路可用。
## 当前仍未完全覆盖
- 尚未完成“真实 redink 执行成功后自动落 formal 结果”的 live HTTP smoke
- 原因是当前红冲依赖原交易与 bank follow-up 外部链路,需要在下一轮补真实可复现数据后再做完整 execute smoke。
## 测试数据清理
已删除:
- `biz_redink_record / detail`
- `biz_charge`
- `biz_cust`
回读结果:
- `redink_main_left=0`
- `redink_detail_left=0`
- `charge_left=0`
- `cust_left=0`
## 运行态清理
用于 fresh-jar query smoke 的临时实例:
- port `48096`
已停止,当前无残留 LISTEN 进程。
## live execute smoke 阻塞证据2026-04-17 16:50 +08:00
本轮额外尝试了真实 redink 执行链路:
- fresh jar`48097`
- 请求:`POST /admin-api/business/charge/accounting-adjust`
- `objectType = REDINK_RECORD`
- `chargeId = 992306`
- `originalTranSeq = T-REDINK-NOPE`
接口响应:
```json
{"code":500,"data":null,"msg":"系统异常"}
```
从应用日志定位到的精确异常:
- `FeignException$ServiceUnavailable: [503]`
- `Load balancer does not contain an instance for the service business-bank-server`
- 失败位置:`TransactionApi#getTransactionByTranSeq(...)`
结论:
- 当前 live execute smoke 未能完成的直接原因是 **外部依赖 `business-bank-server` 在当前环境无可用实例**
- 这不是 redink formal-table 本身的 DDL / 查询接线问题。
因此本批已完成:
- compile
- targeted tests
- DDL apply / replay
- fresh query smoke
但“真实 redink 执行成功后自动落 formal”的端到端 smoke 仍需在 `business-bank-server` 可用时补跑。
## live execute smoke 成功补证2026-04-17 17:51 +08:00
在本轮额外启动本地 `business-bank-server`dev profile, port `48092`)并向测试库预置原交易后,重新完成了 REDINK_RECORD 的真实端到端执行 smoke。
### 前置
- 本地启动 `business-bank-server`,并成功注册到 Nacos dev`business-bank-server 192.168.9.109:48092 register finished`
- 向 `bk_transaction` seed 原交易:
- `tran_seq = T-REDINK-NOPE`
- `sys_tran_seq = SYS-REDINK-NOPE`
- `tran_type = PAY`
- `status = SUCCESS`
- `contract_no = 992306`
- `amount = 12000`
- 准备已收费营业账:`chargeId = 992306`
### execute 请求
`POST /admin-api/business/charge/accounting-adjust`
```json
{
"chargeId": 992306,
"objectType": "REDINK_RECORD",
"reasonCode": "1",
"reason": "redink execute smoke",
"originalTranSeq": "T-REDINK-NOPE"
}
```
### execute 响应
结果:**PASS**
返回:
- `adjustmentNo = REV004-992306-20260417175109`
- `objectType = REDINK_RECORD`
- `resultStatus = SUCCESS`
- `approvalStatus = NOT_REQUIRED`
- `writeBackStatus = UPDATED`
- `message = 冲正处理成功bank流水号=RV9923062026041717511073C805`
### DB 回读
`biz_redink_record`
- `adjustment_no = REV004-992306-20260417175109`
- `approval_status = NOT_REQUIRED`
- `execute_status = UPDATED`
- `result_status = SUCCESS`
- `bank_tran_seq = RV9923062026041717511073C805`
- `original_tran_seq = T-REDINK-NOPE`
`biz_redink_record_detail`
- `detail_status = SUCCESS`
- `proc_type = EXECUTE`
- `pay_state_before = 1`
- `pay_state_after = 0`
- `redink_amount = 120.00`
`biz_charge`
- `pay_state = 0`
- 支付信息已清空为红冲后的未收费态
`bk_transaction`
- 原交易 `T-REDINK-NOPE` 状态变为 `REVERSED`
- 新增 follow-up
- `tran_seq = RV9923062026041717511073C805`
- `tran_type = PAY_INVALID`
- `status = SUCCESS`
- `original_tran_seq = T-REDINK-NOPE`
### 查询回读
- `GET /admin-api/business/accounting-adjust/get?adjustmentNo=REV004-992306-20260417175109` -> formal-first 返回成功
- `GET /admin-api/business/accounting-adjust/page?...objectType=REDINK_RECORD` -> formal-first 返回成功
说明至此REDINK_RECORD 已补齐“真实执行 -> 自动落 formal -> formal-first 查询”的完整闭环证据。

View File

@ -0,0 +1,224 @@
# REV004 / 分账现状对照摘要2026-04-14
## 1. 目的
本摘要用于把 **旧设计**、**老数据字典**、**当前代码状态** 三者放在一起对“分账SplitAdjust”给出一页式结论回答
- 旧系统/旧设计里的分账到底是什么
- 当前 REV004 设计里把它定位成什么
- 当前后端代码做到哪一步了
- 三者之间的差距到底在哪里
---
## 2. 一句话结论
> **旧系统里的分账是“真正的账单拆分/重分摊业务对象”,有汇总表、明细表和拆分后的结果对象;当前 REV004 设计也把它定义为独立正式对象,但当前后端代码只落到了“分账申请态”,还没有落到“审批通过后真正拆账生成子账单”的执行态。**
---
## 3. 旧设计口径12_REV_Detailed.md
来源:
- `../water-docs/docs/design/02_Detailed_Design/12_REV_Detailed.md`
### 旧设计里怎么定义分账
文档明确把:
- `SplitAdjust`
列为:
- **目标正式业务对象**
- 并且属于:
- **L2 独立业务层**
### 旧设计里的关键语义
文档明确写到:
- `SplitAdjust | 分账调整 | 当前作为费用组成重分摊场景表达 | 支持按水量 / 按费用组成等分摊策略`
并在迁移补充里写到:
- **分账调整汇总 / 明细**
- 承接方式:
- 在线主模型 + 费用重分摊场景
- 需要保留:
- 原分摊结果
- 调整后结果
- 策略类型
- 责任链
### 从旧设计可得的结论
旧设计的分账不是普通字段修改,也不是一条审批备注,而是:
- 有独立业务语义
- 有汇总/明细层
- 有原结果与新结果的前后对照
- 有策略与责任链
---
## 4. 老数据字典口径(营收数据字典.md
来源:
- `../water-docs/docs/design/04_Appendix/Archive/05_Data_Dictionary/营收数据字典.md`
### 老系统真实表结构
#### 4.1 汇总表
- `PM_SEPARATE_RECORDS`
关键字段:
- `Applicant`
- `Mobile`
- `ApplyType`
- `SeparateType`
- `State`
- `Remark`
- `TaskId`
- `StepId`
- `FlowRemark`
- `BusinessType`
#### 4.2 明细表
- `PM_SEPARATE_RECORD_DETAILS`
关键字段:
- `SeparateRecordId`
- `CustId / CustCode / CustName / CustAddress`
- `FeeId`
- `NewFeeId`
- `BillMonth`
- `BillWater / NewBillWater`
- `LateFee / NewLateFee`
- `BillAmount / NewBillAmount`
- `ExtendedAmount / NewExtendedAmount`
- `ItemStr`
- `ProcType / ProcPerson / ProcDate / ProcRemark`
- `State`
### 从老数据字典可得的结论
老系统里的分账已经明显是**执行态对象**,因为它不只是有申请信息,还已经有:
- 原费用对象:`FeeId`
- 新产生对象:`NewFeeId`
- 原水量 / 分账水量
- 原金额 / 新金额
- 原滞纳金 / 新滞纳金
- 费用组成
也就是说,老系统分账不是“申请单”,而是:
> **有申请、有明细、有拆后结果对象的完整业务表族。**
---
## 5. 当前代码状态
来源:
- `sw-business/sw-business-server/src/main/java/.../AccountingAdjustProcessServiceImpl.java`
- `sw-business/sw-business-server/src/main/java/.../ChargeServiceImpl.java`
### 当前入口
当前分账提交入口:
- `POST /admin-api/business/accounting-adjust/unsold-split-submit`
调用路径:
- `AccountingAdjustProcessServiceImpl.createUnsoldSplit(...)`
- 转成统一账务调整:
- `objectType = SPLIT_ADJUST`
- `adjustType = SPLIT`
- 进入 `chargeService.adjustAccounting(...)`
### 当前真正处理逻辑
`ChargeServiceImpl.handleSplitAdjust(...)` 中,只做了:
- `approvalRequired = true`
- `resultStatus = PENDING_APPROVAL`
- `approvalStatus = PENDING_APPROVAL`
- `writeBackStatus = PENDING`
- `message = 账单拆分申请已提交,待审批`
- `needUpdate = false`
### 当前校验逻辑
只校验:
- 原账单必须未收费
- `splitCount >= 2`
- `splitCount <= 12`
- 必须填写原因编码
- 必须填写调整原因
### 从当前代码可得的结论
当前代码实现的是:
- **分账申请态**
没有做到:
- 生成分账主表/明细表
- 生成目标账单
- 回填新账单 ID
- 原账单失效/已拆分标记
- 执行态回看
---
## 6. 三者对照
| 对照维度 | 旧设计 | 老数据字典 | 当前代码 |
|---|---|---|---|
| 是否独立对象 | 是,`SplitAdjust` | 是,汇总表+明细表 | 语义上是,代码上仍挂统一入口 |
| 是否支持按水量 / 按费用组成 | 是 | 是(`SeparateType` | 申请态支持对象类型,但未落执行规则 |
| 是否有汇总表 | 有方向 | 有真实表 | 当前没有真实表落地 |
| 是否有明细表 | 有方向 | 有真实表 | 当前没有真实表落地 |
| 是否有“原对象 -> 新对象”关系 | 有方向 | 有(`FeeId -> NewFeeId` | 没有 |
| 是否有拆前拆后水量/金额 | 有方向 | 有 | 没有执行态数据 |
| 是否只到审批申请 | 否 | 否 | 是 |
| 是否真正生成子账单 | 应该要 | 已有新费用ID表达 | 当前未实现 |
---
## 7. 当前最大的差距
最核心的差距不是“少几个字段”,而是:
### 旧系统 / 旧设计的分账
是:
- **完整业务对象**
- **有结果承接**
- **有拆后对象**
### 当前 REV004 代码的分账
只是:
- **申请态入口**
- **待审批状态表达**
- **统一日志/查询承接**
所以差距本质上是:
> **从“受理态”到“执行态”的整层能力还没有落。**
---
## 8. 为什么会停在这里
结合三者判断,原因可以归纳成:
1. 当前这轮 REV004 后端优先目标是统一入口、状态、日志、查询,不是重建所有独立执行对象。
2. 真正分账执行涉及:
- 分摊规则
- 主表/明细表
- 目标账单生成
- 守恒校验
- 收费/开票承接
3. 因此当前才先落成:
- `SPLIT_ADJUST` 申请态
而没有一步到位落成:
- `SplitAdjust` 执行态。
---
## 9. 最终判断
### 业务真值
分账本质上是:
- **账单拆分 / 重分摊**
- 不是退款
- 不是减免
- 也不只是审批单
### 当前状态
当前 REV004
- 已经把“分账”纳入正式对象语义
- 但后端实现仍停在申请态
### 最适合对外讲的一句话
> 旧系统与旧设计里的分账本来就是“有汇总、有明细、有拆后结果对象”的正式业务对象;当前 REV004 后端只先落了统一申请与审批承接,没有把真正的拆账执行态一起落出来,所以它现在更像“分账申请”,还不是“已执行的分账对象”。
---
## 10. 建议下一步
若要继续推进分账执行态,建议:
1. 以老表结构 + 旧设计为真值来源,先冻结最小执行态模型
2. 先做按水量分账 MVP
3. 再扩按费用组成分账
4. 最后补前端执行态页面与回看能力

View File

@ -0,0 +1,246 @@
# REV004 / 分账执行态后端设计草案2026-04-14
## 1. 目标
在当前“分账申请态”基础上,补齐“审批通过后真正执行拆账”的后端能力,使系统可以:
- 将一笔原始未收费账单拆成多笔目标账单
- 支持两类分账策略:
- 按水量分账
- 按费用组成分账
- 保证金额/水量守恒、状态可追踪、失败可回滚
- 被后续收费、开票、日志、查询完整承接
---
## 2. 当前现状
### 已有
- 统一对象类型:`SPLIT_ADJUST`
- 统一提交入口:`unsold-split-submit`
- 当前结果:`PENDING_APPROVAL`
- 已有原因、审批、日志、查询承接能力
### 缺失
- 审批通过后的执行器
- 分账主表/明细表落库
- 目标账单生成
- 原账单与目标账单关系链
- 按水量/按费用组成的规则明细
- 执行态查询回看
---
## 3. 设计原则
1. **先审批、后执行**:分账执行必须挂在审批通过之后,不在申请提交时直接拆账。
2. **守恒原则**
- 按水量分账:子账单水量之和 = 原账单水量
- 按费用组成分账:子账单金额之和 = 原账单金额
3. **原账单可追溯**:必须保留原账单与所有目标账单关系。
4. **执行幂等**:同一分账单不得重复执行。
5. **失败可回滚**:执行中任一步失败,必须整体回滚。
6. **查询可回看**:日志页/分账页必须能看到申请、审批、执行结果、目标账单明细。
---
## 4. 结果对象建议
### 最终业务真值
建议以:
- **多张目标子账单**
为正式业务真值;
- “多笔待收费结果”只是前台呈现视角。
### 原因
- 文档原始语义就是“由一笔账单分成两笔独立账单信息”
- 后续还要支持分别收费、分别开票、分别承担
- 若没有目标账单实体,后续业务承接会变得模糊
---
## 5. 数据模型草案
### 5.1 主表:`biz_split_adjust`
建议字段:
- `id`
- `split_adjust_no`:分账单号
- `source_charge_id`:原账单 ID
- `split_rule_type`:分账规则类型(`BY_WATER` / `BY_COMPONENT`
- `reason_code`
- `remark`
- `approval_status`
- `execute_status``PENDING / PROCESSING / SUCCESS / FAIL`
- `execute_message`
- `executed_at`
- `accounting_case_id`(如继续挂统一骨架)
- 通用审计字段
### 5.2 明细表:`biz_split_adjust_detail`
建议字段:
- `id`
- `split_adjust_id`
- `seq_no`
- `target_cust_id` / `target_cust_code`(若允许拆给不同客户)
- `target_charge_id`(执行后生成)
- `split_basis`:拆分依据描述
- `split_ratio`
- `split_water`
- `split_amount`
- `component_code`(按费用组成分账时必填)
- `component_amount`
- `status`
- 通用审计字段
### 5.3 与现有 `biz_charge` 的关系
- 原账单:`source_charge_id`
- 子账单:执行后在 `biz_charge` 新增多条记录
- 子账单增加来源追踪字段(建议二选一):
- `source_charge_id`
- 或 `split_adjust_id`
建议至少有一个能让后续查询直接知道:
> 这张账单是由哪次分账生成的。
---
## 6. 执行流程草案
### Phase 1申请提交
接口:`POST /unsold-split-submit`
- 校验未收费、`splitCount`、原因等
- 保存申请主单/明细草稿(如果前端已能提供拆分明细)
- 状态置为 `PENDING_APPROVAL`
> 若当前前端还没有真正提交拆分明细,只是申请头信息,则需在后续补充“审批前明细维护”或“审批时确认明细”能力。
### Phase 2审批通过
触发点:审批系统回调/人工审批通过
- 校验该分账单仍未执行
- 锁定原账单
- 进入执行事务
### Phase 3执行拆账
#### 3.1 按水量分账
- 读取原账单水量、单价/模板/费用构成
- 根据各子项 `split_water` 重新计算子账单金额
- 生成 N 条目标账单
- 校验:
- `sum(split_water) = source.bill_water`
- `sum(target.bill_amount)` 与可接受计算误差范围内守恒
#### 3.2 按费用组成分账
- 读取原账单明细 `biz_charge_detail`
- 按费用项将金额拆入不同子账单
- 汇总生成 N 条目标账单及其明细
- 校验:
- 各费用项总额守恒
- 原账单总金额守恒
### Phase 4收尾
- 原账单标记为:
- 已拆分 / 已失效 / 不可继续收费(建议新增状态或来源标识)
- 分账主单置为 `SUCCESS`
- 明细回填 `target_charge_id`
- 写 operat_log / 业务日志 / 查询投影
### Phase 5失败回滚
- 任一步失败:回滚事务
- 主单置 `FAIL`
- 保留失败原因
- 不产生部分子账单残留
---
## 7. 接口草案
### 7.1 申请接口(保留)
- `POST /unsold-split-submit`
### 7.2 明细查询接口(建议新增)
- `GET /split-adjust-detail?adjustmentNo=...`
- 返回:
- 原账单摘要
- 分账规则类型
- 子项明细
- 目标账单(若已执行)
### 7.3 明细维护接口(建议新增,若前端需要)
- `POST /split-adjust-draft-save`
- 在审批前保存:
- 按水量拆分行
- 按费用组成拆分行
### 7.4 审批通过执行接口(内部)
- `POST /internal/split-adjust/execute`
- 由审批流或业务服务调用,不暴露给前端
### 7.5 查询接口增强(建议)
- `log-detail` / `log-process`
- 增加目标账单摘要
- `unsold-page`
- 原账单若已提交分账且审批中/已执行,要有能力位变化提示
---
## 8. 状态机建议
### 申请主单状态
- `PENDING_APPROVAL`
- `APPROVED_PENDING_EXECUTE`
- `PROCESSING`
- `SUCCESS`
- `FAIL`
- `REVOKED`(如审批前允许撤销)
### 原账单状态建议
原账单不建议简单删除,建议新增可追溯状态:
- `UNPAID`
- `SPLIT_PENDING`
- `SPLIT_DONE`
- `SPLIT_INVALIDATED`
若不想改老字段,可用额外来源标记字段表达。
---
## 9. 与前端配合建议
### 前端最少补充信息
若要真正进入执行态,前端弹窗至少要能提交:
- 分账方式:按水量 / 按费用组成
- 子项列表
- 按水量:每笔 `split_water`
- 按费用组成:每笔目标费用项/金额归属
- 申请人 / 联系电话 / 原因 / 备注
### 前端结果回看
执行态落地后,前端需要新增:
- 原账单 -> 目标账单列表
- 子账单金额/水量/费用项明细
- 分账执行结果与失败原因
---
## 10. 分阶段落地建议
### 第一阶段(最小闭环)
目标:审批通过后真正生成子账单,但先只支持**按水量分账**
- 优先原因:
- 规则相对更直观
- 守恒约束明确
- 更容易先做闭环
### 第二阶段
扩展支持:**按费用组成分账**
- 需要补足 `biz_charge_detail` 级别重分摊能力
### 第三阶段
增强:
- 目标客户维度分账
- 更复杂的规则模板
- 开票/收费联动优化查询
---
## 11. 最终建议
若项目要正式推进“分账执行态”,建议:
1. 先把 **按水量分账** 做成最小执行闭环
2. 明确 `biz_split_adjust` / `biz_split_adjust_detail` 模型
3. 把审批通过后的执行器作为独立服务落地
4. 再补按费用组成的二阶段能力
一句话:
> 不建议继续在当前“统一 adjustAccounting 申请态”里硬塞执行逻辑,而应把 `SplitAdjust` 升级成独立正式业务对象来建设。

View File

@ -0,0 +1,108 @@
# REV004 / split-adjust formalization + dedicated interfaces evidence2026-04-14
## 1. 本轮实现范围
- 保持旧接口兼容:
- `POST /admin-api/business/accounting-adjust/unsold-split-submit`
- `GET /admin-api/business/accounting-adjust/get`
- `GET /admin-api/business/accounting-adjust/log-detail`
- 新增 split 专属接口族:
- `POST /admin-api/business/split-adjust/submit`
- `GET /admin-api/business/split-adjust/page`
- `GET /admin-api/business/split-adjust/detail`
- `GET /admin-api/business/split-adjust/result`
- split 执行真值升级为:
- `splitItems[{seqNo, splitWater}]`
- `splitCount` 作为兼容口径保留,并在旧入口适配阶段转译为 `splitItems`
## 2. 本轮代码落点
### 新增
- `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/splitadjust/SplitAdjustController.java`
- `.../controller/admin/splitadjust/vo/SplitAdjustSubmitReqVO.java`
- `.../controller/admin/splitadjust/vo/SplitAdjustSubmitRespVO.java`
- `.../controller/admin/splitadjust/vo/SplitAdjustDetailRespVO.java`
- `.../controller/admin/splitadjust/vo/SplitAdjustPageReqVO.java`
- `.../controller/admin/splitadjust/vo/SplitAdjustPageRespVO.java`
- `.../service/splitadjust/SplitAdjustService.java`
- `.../service/splitadjust/SplitAdjustServiceImpl.java`
### 继续增强
- `SplitAdjustFormalizationService`
- `AccountingAdjustActionServiceImpl`
- `AccountingAdjustProcessServiceImpl`
- `AccountingAdjustLogProcessServiceImpl`
- `AccountingAdjustDetailRespVO`
- `AccountingAdjustLogDetailRespVO`
- `ChargeDO`
- `sql/rev004/REV004_accounting_adjustments_ddl.sql`
- `Rev004AccountProcessCanaryQueryIntegrationTest`
- `AccountingAdjustActionServiceImplTest`
- `Rev004AccountProcessLiveDbReadinessTest`
- `sql/rev004/accountprocess/00_reset.sql`
## 3. 关键实现结论
1. 新 split submit 入口已经支持显式 `splitItems[{seqNo, splitWater}]`
2. `splitItems` 成为 formal 明细真值,审批通过执行会按明细水量比例分配子账单金额/水量
3. 旧 `unsold-split-submit` 已兼容接入新 split 主链,可直接传 `splitItems`
4. 新增 split 专属 detail/result/page 接口
5. 旧 `log-detail` 对新 split 专属单据也可 fallback 回看
6. `biz_charge` 已承接最小追溯字段:
- `source_charge_id`
- `split_adjust_id`
- `charge_origin_type`
- `split_status`
## 4. 验证命令与结果
### 4.1 编译
```bash
mvn -pl sw-business/sw-business-server -DskipTests compile
```
结果PASS
### 4.2 targeted tests阶段一
```bash
REV004_IT_DB_URL='jdbc:postgresql://192.168.10.130:5436/sw_system' \
REV004_IT_DB_USERNAME='sw_system' \
REV004_IT_DB_PASSWORD='Em@123456' \
mvn -pl sw-business/sw-business-server \
-Dtest=Rev004AccountProcessCanaryQueryIntegrationTest#splitAdjustSubmit_shouldAcceptSplitItemsAndExposeDedicatedDetailRoute,AccountingAdjustActionServiceImplTest \
test
```
结果PASS
- Tests run: 10
- Failures: 0
- Errors: 0
- Skipped: 0
### 4.3 targeted tests阶段二
```bash
REV004_IT_DB_URL='jdbc:postgresql://192.168.10.130:5436/sw_system' \
REV004_IT_DB_USERNAME='sw_system' \
REV004_IT_DB_PASSWORD='Em@123456' \
mvn -pl sw-business/sw-business-server \
-Dtest=Rev004AccountProcessCanaryQueryIntegrationTest#splitAdjustSubmit_shouldAcceptSplitItemsAndExposeDedicatedDetailRoute+legacyUnsoldSplitSubmit_shouldCompatToSplitItemsFlow+splitAdjustPage_shouldReturnDedicatedRows+splitAdjustResult_shouldReturnSuccessAfterApprove,AccountingAdjustActionServiceImplTest \
test
```
结果PASS
- Tests run: 13
- Failures: 0
- Errors: 0
- Skipped: 0
## 5. 场景覆盖
### 已覆盖
- 新 split submit -> dedicated detail
- 新 split submit -> old log-detail fallback
- 旧 split submit -> new split detail
- dedicated page query
- dedicated result query after approve
- split action service unit testsapprove/reject/write-back
### 仍待补强
- dedicated page + approval status filter 组合查询
- dedicated process 专属接口(如果最终保留)
- split 执行失败/回滚路径的 real-db targeted integration
## 6. 风险与后续
1. 旧接口兼容已接主链,但还需要继续做更完整的批量兼容覆盖
2. 当前仍未进入 BY_COMPONENT / `ItemStr` 真执行
3. 若要最终收口,还应补一轮 architect verification + deslop + regression rerun

View File

@ -0,0 +1,234 @@
# REV004 / 按水量分账最小闭环设计2026-04-14
## 1. 目标
本设计只覆盖 **第一阶段:按水量分账最小闭环**
目标是:
- 审批通过后,能够把一笔未收费原账单按多笔水量拆分,真正生成多张目标子账单;
- 先不做“按费用组成分账”;
- 先把最小可运行闭环打通,再作为后续扩展基础。
---
## 2. 为什么第一阶段先做按水量分账
原因:
1. **规则最清晰**:拆分水量之和必须等于原水量。
2. **业务校验最直接**:原账单 -> 多笔子账单,按水量重算金额,容易理解。
3. **比按费用组成更容易守恒**:先处理总水量、总金额守恒,再逐步扩到费用项粒度。
4. **更适合先做执行态样板**:审批通过、执行、回看、失败回滚等流程都能先沉淀下来。
---
## 3. 最小闭环边界
### 本阶段要做到
- 提交按水量分账申请
- 保存拆分明细(每笔分账水量)
- 审批通过后执行拆账
- 生成多张目标账单
- 原账单标记为已拆分/失效
- 日志与查询可回看
### 本阶段不做
- 按费用组成分账
- 目标客户跨户分账(如无强需求,可先默认同客户)
- 更复杂规则模板
- 开票联动优化
- 自动撤销分账执行结果
---
## 4. 输入输出定义
### 4.1 前端最少输入
按水量分账弹窗至少提交:
- `sourceChargeId`
- `splitItems[]`
- `seqNo`
- `splitWater`
- `reasonCode`
- `remark`
- `applicant`
- `contactMobile`
### 4.2 关键校验
- 原账单必须是未收费
- `splitItems.length >= 2`
- 每笔 `splitWater > 0`
- `sum(splitWater) = source.billWater`
- 必须填写原因
### 4.3 输出结果
申请提交成功后:
- `adjustmentNo`
- `resultStatus = PENDING_APPROVAL`
执行成功后应可回看:
- 原账单摘要
- 目标子账单列表
- 每张子账单的:
- 水量
- 账单金额
- 应收金额
- 违约金(若适用)
---
## 5. 数据模型MVP
### 5.1 主表:`biz_split_adjust`
建议 MVP 字段:
- `id`
- `split_adjust_no`
- `source_charge_id`
- `split_rule_type` = `BY_WATER`
- `reason_code`
- `remark`
- `approval_status`
- `execute_status`
- `execute_message`
- `executed_at`
- `creator / create_time / updater / update_time`
### 5.2 明细表:`biz_split_adjust_detail`
建议 MVP 字段:
- `id`
- `split_adjust_id`
- `seq_no`
- `split_water`
- `target_charge_id`(执行后回填)
- `split_amount`(执行后回填)
- `status`
- `creator / create_time / updater / update_time`
### 5.3 账单表最小扩展建议
`biz_charge` 里增加追溯字段(二选一或组合):
- `source_charge_id`
- `split_adjust_id`
- `charge_origin_type`NORMAL / SPLIT_CHILD
原账单增加状态表达(二选一):
- 新字段标记 `split_status`
- 或现有字段中增加来源/失效标识
MVP 阶段关键不是状态字段优雅,而是:
> 后续能稳定区分“原账单”和“拆分生成的子账单”。
---
## 6. 执行流程MVP
### Step 1申请提交
接口:`POST /unsold-split-submit`
- 校验基本参数
- 保存 `biz_split_adjust`
- 保存 `biz_split_adjust_detail`(只保存水量)
- 返回待审批
### Step 2审批通过
- 触发内部执行器
- 对 `biz_split_adjust` 加幂等校验:只允许执行一次
- 锁定原账单 `source_charge_id`
- 开事务
### Step 3生成目标账单
对每个 `split_adjust_detail`
1. 复制原账单基础信息
2. 将 `bill_water` 改成当前 `split_water`
3. 调用现有计价逻辑重新计算:
- `bill_amount`
- `extended_amount`
- 若适用则重算 `late_fee`
4. 插入新的 `biz_charge` 记录
5. 回填 `target_charge_id / split_amount`
### Step 4处理原账单
- 原账单置为“已拆分,不可继续收费”
- 不直接删除原账单
- 保证历史可追溯
### Step 5收尾
- `biz_split_adjust.execute_status = SUCCESS`
- 写 operat_log / log detail
- 查询面可见结果
### Step 6失败回滚
- 任一子账单生成失败:事务回滚
- 主单置 `FAIL`
- 记录失败原因
- 不允许出现半拆分状态
---
## 7. 重算策略建议
MVP 不建议自己重新写一套计价逻辑,建议:
- 复用已有账单计算能力
- 输入拆分后的水量
- 走现有价格模板/费用计算规则
原因:
- 这样能减少“分账执行”和“普通开账计费”之间的口径偏差
- 也更容易保证后续账单金额解释一致
关键原则:
> 分账只是“改变水量输入并生成多张账单”,不要重造一套新的计费引擎。
---
## 8. 查询回看MVP
建议至少补两个查询面:
### 8.1 分账详情查询
- 原账单摘要
- 分账规则类型BY_WATER
- 每笔拆分水量
- 每笔生成的目标账单 ID / 金额
- 执行状态 / 错误原因
### 8.2 日志页增强
在现有 `log-detail / log-process` 中,增加:
- 原账单 -> 子账单映射摘要
- 执行结果摘要
---
## 9. 测试建议MVP
### 9.1 单测
- 水量守恒校验
- 拆分笔数边界(<2, >12
- 非未收费账单禁止发起
- 执行幂等
### 9.2 集成测试
- 提交按水量分账申请 -> 待审批
- 审批通过 -> 生成 N 张子账单
- 子账单水量之和 = 原账单水量
- 原账单不可继续收费
- 日志页可回看执行结果
- 执行失败整体回滚
### 9.3 验收测试
- 一张账单拆成 2 笔
- 一张账单拆成多笔
- 子账单后续可分别收费/开票(若本期不做全链,可先验证可查询/可识别)
---
## 10. MVP 成功标准
MVP 完成时,应满足:
1. 用户可提交按水量分账申请
2. 审批通过后,系统真正生成多张目标账单
3. 子账单水量之和与原账单守恒
4. 原账单被正确标记,不再继续作为普通未收费账单使用
5. 查询与日志能回看这次分账执行结果
---
## 11. 建议实施顺序
1. 先补 `biz_split_adjust` / `biz_split_adjust_detail`
2. 再补审批通过后的执行器
3. 再补查询回看
4. 最后补前端执行态结果页
一句话:
> 先把“按水量分账”做成审批后真正能拆出子账单的最小闭环,再考虑按费用组成扩展。

View File

@ -0,0 +1,46 @@
# REV004 分账固定费用归属修复验证记录
## 背景
按水量分账采用连续阶梯重算后,`calculationMode=3` 的固定金额费用会在每个子账单重算一次,导致固定费用被重复收取。
本次明确业务口径:按水量分账时,固定金额费用只归属第 1 个子账单,后续子账单该固定费用为 0。
## 实现范围
- 后端仓库:`../water-backend/`
- 基线:`6156e47f8`
- 验证日期2026-06-19
- 涉及文件:
- `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/trial/UnsoldTrialPreviewServiceImpl.java`
- `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/AccountingAdjustActionServiceImpl.java`
- `sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/accountingadjust/trial/UnsoldTrialPreviewServiceImplTest.java`
- `sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/accountingadjust/AccountingAdjustActionServiceImplTest.java`
## 规则
- 按水量分账继续按拆分顺序做连续阶梯重算。
- 水价费用配置中 `calculationMode=3` 的固定金额费用:
- 第 1 个子账单取原账单对应费用金额。
- 第 2..N 个子账单置 0。
- 正式回写时同步调整子账单费用明细,固定费用明细第 1 个子账单保留原金额,后续子账单清零。
- 按费用组成分账不在本次范围内,保持原逻辑。
## 验证
执行命令:
```bash
mvn -pl sw-business/sw-business-server -am -Dtest=UnsoldTrialPreviewServiceImplTest,AccountingAdjustActionServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test
```
结果:
- `UnsoldTrialPreviewServiceImplTest`11 tests, 0 failures, 0 errors
- `AccountingAdjustActionServiceImplTest`19 tests, 0 failures, 0 errors
- Reactor buildSUCCESS
补充说明:
- 初次仅使用 `-pl sw-business/sw-business-server` 执行时,因未同时构建 `sw-business-api` 依赖,命中本地依赖中的 `CustPriceChangeUpdateDTO` 缺失编译错误;改用 `-am` 后源码依赖正常参与构建。
- `-am` 执行指定测试时,上游模块需要追加 `-Dsurefire.failIfNoSpecifiedTests=false`,避免上游模块没有匹配测试导致提前失败。

View File

@ -0,0 +1,26 @@
# REV-004 未销水量调整零水量与补抄追收验证记录
## 变更范围
- 按水量调整预算允许 `targetBillWater=0`,仍拒绝负数水量。
- 正式水量调整默认保留“调整后水量不能超过抄见水量”限制。
- 当原因编码或原因说明属于补抄、追收、漏抄、少抄、估抄偏低、补征等场景时,允许目标水量超过原抄见水量。
## 验证命令
```bash
mvn -pl sw-business/sw-business-server -am \
-Dtest=UnsoldTrialPreviewServiceImplTest,ChargeServiceAccountingAdjustTest \
-Dsurefire.failIfNoSpecifiedTests=false test
```
## 验证结果
- `UnsoldTrialPreviewServiceImplTest`: 12 tests, 0 failures, 0 errors.
- `ChargeServiceAccountingAdjustTest`: 24 tests, 0 failures, 0 errors.
- Maven reactor: `BUILD SUCCESS`.
## 风险说明
- 本次未放开已收费、已开票、已结账、缺少抄表依据、缺少附件等既有校验。
- 补抄追收白名单为后端兼容策略,后续可在正式原因字典稳定后收敛为字典值校验。

View File

@ -0,0 +1,37 @@
# REV-004 未销水量调整预算选项无库变更验证记录
## 变更范围
- 未销按水量调整正式提交新增 `updateAccumulated``updateBaseCode` 两个选项字段。
- `unsold-adjust-submit` 控制器映射、统一提交 VO、核心账务调整 VO 均已透传上述字段。
- `updateBaseCode=true` 时,正式水量调整要求传入 `currentReading`,并将其写入营业账本次抄码。
- `updateBaseCode=false` 或未传时,即使传入 `currentReading`,也不写入营业账本次抄码。
- `updateAccumulated=true` 时,按 `调整后水量 - 调整前水量` 调整营业账累计水量;`false` 或未传时不调整累计水量。
- 操作日志记录 `updateAccumulated``updateBaseCode`,字段类型为 YES_OR_NO。
## 未纳入本轮范围
- 本轮按用户要求仅实现不需要数据库结构变更的功能。
- 价差调整选项正式持久化、价差明细表新增字段、SQL 迁移仍保留为后续 DB 相关任务。
## 验证命令
```bash
mvn -pl sw-business/sw-business-server -am \
-Dtest=UnsoldTrialPreviewServiceImplTest,AccountingAdjustActionControllerTest#createUnsoldAdjust_shouldReturnWrappedSuccess,AccountingAdjustProcessServiceImplTest#createUnsoldAdjust_shouldPassWaterOptionFlagsToUnifiedChargeService,ChargeServiceAccountingAdjustTest \
-Dsurefire.failIfNoSpecifiedTests=false test
```
## 验证结果
- `AccountingAdjustActionControllerTest#createUnsoldAdjust_shouldReturnWrappedSuccess`: 1 test, 0 failures, 0 errors.
- `AccountingAdjustProcessServiceImplTest#createUnsoldAdjust_shouldPassWaterOptionFlagsToUnifiedChargeService`: 1 test, 0 failures, 0 errors.
- `ChargeServiceAccountingAdjustTest`: 27 tests, 0 failures, 0 errors.
- `UnsoldTrialPreviewServiceImplTest`: 12 tests, 0 failures, 0 errors.
- Maven reactor: 41 tests, 0 failures, 0 errors, `BUILD SUCCESS`.
## 风险与兼容说明
- `currentReading` 不再单独触发底码写入;调用方必须显式传 `updateBaseCode=true` 才会更新本次抄码。
- 未传 checkbox 时按默认 false 处理,避免预算页选项未确认时误写正式字段。
- 本轮未放宽已收费、已开票、已结账、缺少抄表依据、缺少附件等既有水量调整校验。

View File

@ -0,0 +1,129 @@
# REV-005 发票业务流 — 文档审计报告
**审计日期**: 2026-06-16
**审计范围**: `water-docs/specs/002-rev005-invoice-flow/` + `docs/design/` 主文档 + `water-backend` 代码worktree `backend-rev005`
**审计目标**: 评估发票相关文档的完整性、一致性和可追溯性
---
## 1. 审计总览
| 维度 | 结论 |
|------|------|
| 规格完整性 | ✅ 完备 |
| 正式设计一致性 | ⚠️ 轻微偏差(状态定义 6 vs 4 态) |
| 接口契约覆盖 | ✅ 完备 |
| 数据模型覆盖 | ✅ 完备(正式版在 DB 设计中specs 草稿已删除) |
| 代码实现对应 | ✅ 59/65 任务完成6 项为验证任务 |
| 前端设计覆盖 | ✅ 有独立前端设计文档866 行) |
| 验证证据 | ❌ 8 项验证任务未完成 |
---
## 2. 文档地图
```
specs/002-rev005-invoice-flow/
├── spec.md ✅ 有
├── plan.md ✅ 有
├── tasks.md ✅ 有65 项59 完成)
├── research.md ✅ 有
├── quickstart.md ✅ 有
├── verification.md ⚠️ 有,但 8 个验证任务未完成
└── frontend-finance-design.md ✅ 有2026-05-12 新增)
docs/design/ (正式主文档)
├── 02_Detailed_Design/12_REV_Detailed.md ✅ REV-005 章节完整
├── 03_Technical_Design/01_Database_Design.md ✅ biz_invoice 表覆盖
└── 03_Technical_Design/03_Interface_Design.md ✅ IF-REV-008/009 定义完整
docs/evidence/rev005-invoice/ 📭 空(尚无证据入库)
```
---
## 3. 发现
### 3.1 状态定义差异(中风险)
**问题**`12_REV_Detailed.md` 定义了 6 个发票状态(`SUBMITTED``PENDING``SUCCESS``FAIL``INVALID``RED_INK`),但 `InvoiceController` 代码中实现了 `invalidate``red-ink` 两个独立端点,而 `InvoiceDO` 中的 `invoiceStatus` 字段是否支持这 6 态尚未在代码层确认。
**影响**:作废/红冲是二期补齐的功能,需确认 `biz_invoice` 表的 `invoice_status` 枚举已包含 `INVALID``RED_INK`,否则运行时会抛异常。
**建议**:检查 `InvoiceStatusEnum`(如存在)或 `biz_invoice` 的 DDL 确认枚举覆盖。
### 3.2 验证证据缺失(低风险)
`specs/002-rev005-invoice-flow/verification.md` 中 8 个验证任务未完成:
| 编号 | 内容 | 阻塞点 |
|------|------|--------|
| T022 | 重复申请、部分开票拒绝的样本 | 需联调环境 |
| T033 | SYS-008 不可用、超时等异常样本 | 需联调环境 |
| T044 | 回写/查询/下载/推送成功率统计 | 需联调环境 |
| T055 | 作废/红冲运行态日志样本 | 需联调环境 |
| T060-T063 | SC-001~SC-004 性能指标采样 | 需测试环境 |
**影响**:不影响功能实现,但会影响验收签字。
**建议**:在提测前集中补齐,可安排在联调阶段同步采集。
### 3.3 接口路径不一致(低风险)
| 定义位置 | 路径 |
|----------|------|
| `specs/002/contracts/if-rev-008.md`(已删除) | `/api/invoice/apply`(假设) |
| `03_Interface_Design.md` | `/business/invoice/apply` |
| `InvoiceController.java` | `/business/invoice/apply``@RequestMapping("/business/invoice")` + `@PostMapping("/apply")` |
正式接口设计和代码实现路径一致(`/business/invoice/apply`。specs 里的旧契约已删除,不存在冲突。✅ 无问题。
### 3.4 前端设计文档状态(观察项)
`frontend-finance-design.md` 是 2026-05-12 新增的晚于主设计文档2026-03-19。它是独立于后端设计文档的前端实现补充目前状态为"作为前端实现输入"。
**影响**`water-frontend` 尚未同步确认是否已按照此文档实现。
**建议**:下一步在 `water-frontend` 的 AGENTS.md 或对应 spec 中确认前端实现进度。
### 3.5 数据模型一致性(已解决)
此前 `specs/002/` 存在独立的 `data-model.md``contracts/` 目录,与正式设计文档有内容重叠。本次审计已删除冗余草稿,数据模型以 `01_Database_Design.md` 为准,接口以 `03_Interface_Design.md` 为准。
---
## 4. FR 覆盖矩阵
| FR | 描述 | 设计文档 | 代码 | 状态 |
|----|------|---------|------|------|
| FR-001 | 后台发票申请接口 | ✅ | ✅ `POST /apply` | 已实现 |
| FR-002 | 校验账单状态 | ✅ | ✅ `InvoiceServiceImpl` | 已实现 |
| FR-003 | 校验客户开票信息 | ✅ | ✅ | 已实现 |
| FR-004 | 校验开票限额 | ⚠️ 设计中提及但未详细展开 | ❓ 待确认 | 部分 |
| FR-005 | 生成发票申请记录 | ✅ | ✅ `biz_invoice_record` | 已实现 |
| FR-006 | 调用 SYS-008 | ✅ | ✅ `InvoicePlatformClient` | 已实现 |
| FR-007 | 定时查询兜底 | ✅ | ✅ `InvoiceCompensateJob` | 已实现 |
| FR-008 | 更新发票状态 | ✅ | ✅ | 已实现 |
| FR-009 | 发票-账单关联 | ✅ | ✅ `biz_charge_invoice_rel` | 已实现 |
| FR-010 | 客户侧查询/下载/推送 | ✅ | ✅ 3 个端点 | 已实现 |
| FR-011 | 操作日志 | ✅ | ⚠️ 部分方法有日志 | 部分 |
| FR-012 | 作废入口 | ✅ | ✅ `POST /invalidate` | 已实现 |
| FR-013 | 红冲入口 | ✅ | ✅ `POST /red-ink` | 已实现 |
| FR-014 | 终态保护与结果回写 | ✅ | ✅ | 已实现 |
**关键发现**FR-004开票限额在设计文档中提及但未明确限额来源配置表固定值代码层待确认。FR-011操作日志在关键动作上有日志但完整度待验证。
---
## 5. 建议优先级
| 优先级 | 建议 |
|--------|------|
| **P0** | 确认 `biz_invoice``invoiceStatus` 枚举是否包含 `INVALID``RED_INK`,避免作废/红冲端点运行时报错 |
| **P1** | 明确开票限额校验逻辑FR-004从配置表读取还是硬编码限额是单笔还是累计 |
| **P1** | 联调阶段集中采集 T022/T033/T044/T055/T060-T063 的验证样本 |
| **P2** | 确认 `water-frontend``frontend-finance-design.md` 的实现进度 |
---
审计结论REV-005 发票业务流的文档体系基本完备,设计文档、接口契约、数据模型和代码实现高度对齐。主要风险点是作废/红冲状态枚举的代码层覆盖确认,以及 8 项验证样本的补充。

View File

@ -328,7 +328,7 @@ REV-004 先统一账务状态、留痕、原交易校验口径
- `specs/001-rev004-accounting/`
- `specs/002-rev005-invoice-flow/`
- `specs/003-rev006-reminder-event-design/`
- `specs/003-rev003-rev006-reminder-event-design/`
- `specs/004-rev007-revenue-statistics-design/`
- `docs/design/00_Management/01_Project_Progress.md`
- `docs/design/00_Management/03_Task_Checklist.md`

View File

@ -0,0 +1,602 @@
# Excel 导出 xlsx 统一化与前端 CSV 替换 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 统一后端 Excel 下载为标准 `.xlsx`,并将 5 个前端页面从本地 CSV 导出切换为后端真实 Excel 导出。
**Architecture:** 后端以 `ExcelUtils` 为唯一公共导出入口,统一 MIME、文件名后缀和可编码文件名策略前端不再拼接 `csvContent` 或创建 `text/csv` Blob而是为目标页面新增 API 下载封装,直接请求后端导出接口并复用响应头中的文件名。实现按 backend lane / frontend lane 拆开,但在同一 feature 下联调收口。
**Tech Stack:** Java 17, Spring Boot, EasyExcel, MockMvc, Vue 3, TypeScript, Axios wrapper (`@/config/axios`), pnpm
---
## File Structure
### Backend files
- Modify: `../water-backend/sw-framework/sw-spring-boot-starter-excel/src/main/java/cn/com/emsoft/sw/framework/excel/core/util/ExcelUtils.java`
- 统一 `write` / `writeWithTemplate` / `writeTemplate` 的 xlsx MIME 与 `Content-Disposition`
- Modify: `../water-backend/sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/AccountingAdjustRouteSmokeTest.java`
- 把现有导出 smoke 断言从 `application/vnd.ms-excel` 更新为 xlsx MIME并验证 `Content-Disposition` 仍存在。
- Inspect/reuse (no code unless缺口真实存在):
- `../water-backend/sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/AccountingAdjustActionController.java`
- `../water-backend/sw-business-bank/sw-business-bank-server/src/main/java/cn/com/emsoft/sw/bankbusiness/controller/admin/withholdingbatch/WithholdingBatchController.java`
- `../water-backend/sw-business-bank/sw-business-bank-server/src/main/java/cn/com/emsoft/sw/bankbusiness/controller/admin/withholdingitem/WithholdingItemController.java`
- `../water-backend/sw-business-bank/sw-business-bank-server/src/main/java/cn/com/emsoft/sw/bankbusiness/controller/admin/channelstatistics/ChannelStatisticsController.java`
### Frontend files
- Create: `../water-frontend/src/api/operatingCharges/redReversalRecord/index.ts`
- 红冲记录页导出 API。
- Create: `../water-frontend/src/api/collection/export/index.ts`
- 银行托收 / 托收明细 / 银行代扣 / 实时收费导出 API。
- Modify: `../water-frontend/src/utils/download.ts`
- 增加从后端响应中提取文件名并下载 Blob 的工具,支持中文文件名编码。
- Modify: `../water-frontend/src/views/operatingCharges/redReversalRecord/index.vue`
- 删除本地 CSV 导出,改调红冲记录导出 API。
- Modify: `../water-frontend/src/views/collection/bankCollection/index.vue`
- 删除本地 CSV 导出,改调托收批次导出 API。
- Modify: `../water-frontend/src/views/collection/bankCollection/detail.vue`
- 删除本地 CSV 导出,改调托收明细导出 API。
- Modify: `../water-frontend/src/views/collection/bankWithholding/index.vue`
- 删除本地 CSV 导出,改调用同一批次导出 API并带 `businessType=WITHHOLDING`
- Modify: `../water-frontend/src/views/collection/realTimeBilling/index.vue`
- 删除本地 CSV 导出,改调实时收费统计导出 API。
---
### Task 1: 统一后端 ExcelUtils 为标准 xlsx 下载
**Files:**
- Modify: `../water-backend/sw-framework/sw-spring-boot-starter-excel/src/main/java/cn/com/emsoft/sw/framework/excel/core/util/ExcelUtils.java`
- Test: `../water-backend/sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/AccountingAdjustRouteSmokeTest.java`
- [ ] **Step 1: 先写出会失败的导出头断言**
`AccountingAdjustRouteSmokeTest.java` 把 4 个导出断言先改成 xlsx MIME保持其它断言不动
```java
mockMvc.perform(get(BASE + "/log-export"))
.andExpect(status().isOk())
.andExpect(header().string("Content-Disposition", org.hamcrest.Matchers.startsWith("attachment;filename=")))
.andExpect(content().contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8"));
mockMvc.perform(get(BASE + "/log-export-excel"))
.andExpect(status().isOk())
.andExpect(header().string("Content-Disposition", org.hamcrest.Matchers.startsWith("attachment;filename=")))
.andExpect(content().contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8"));
mockMvc.perform(get(BASE + "/prestorage-export"))
.andExpect(status().isOk())
.andExpect(header().string("Content-Disposition", org.hamcrest.Matchers.startsWith("attachment;filename=")))
.andExpect(content().contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8"));
mockMvc.perform(get(BASE + "/prestorage-export-excel"))
.andExpect(status().isOk())
.andExpect(header().string("Content-Disposition", org.hamcrest.Matchers.startsWith("attachment;filename=")))
.andExpect(content().contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8"));
```
- [ ] **Step 2: 运行后端 smoke确认当前实现失败**
Run:
```bash
mvn -f "/Volumes/Dpan/github/water-workspace/water-backend/pom.xml" -pl sw-business/sw-business-server -am -Dtest=AccountingAdjustRouteSmokeTest test
```
Expected: FAIL错误类似 `Content type expected:<application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8> but was:<application/vnd.ms-excel;charset=UTF-8>`
- [ ] **Step 3: 在 ExcelUtils 中抽出统一下载头设置方法,并把 MIME 改成 xlsx**
`ExcelUtils.java` 的响应头逻辑改成下面这种结构,三个写方法都复用它:
```java
private static final String XLSX_CONTENT_TYPE =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8";
private static void prepareAttachmentResponse(HttpServletResponse response, String filename) {
response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(ensureXlsxFilename(filename)));
response.setContentType(XLSX_CONTENT_TYPE);
}
private static String ensureXlsxFilename(String filename) {
if (filename == null || filename.isBlank()) {
return "export.xlsx";
}
if (filename.toLowerCase().endsWith(".xlsx")) {
return filename;
}
if (filename.toLowerCase().endsWith(".xls")) {
return filename.substring(0, filename.length() - 4) + ".xlsx";
}
return filename + ".xlsx";
}
```
然后把三个入口统一改成:
```java
public static <T> void write(HttpServletResponse response, String filename, String sheetName,
Class<T> head, List<T> data) throws IOException {
prepareAttachmentResponse(response, filename);
EasyExcel.write(response.getOutputStream(), head)
.autoCloseStream(false)
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.registerWriteHandler(new SelectSheetWriteHandler(head))
.registerConverter(new LongStringConverter())
.sheet(sheetName)
.doWrite(data);
}
```
```java
public static <T> void writeWithTemplate(HttpServletResponse response, String filename, InputStream templateStream,
String sheetName, Class<T> head, List<T> data) throws IOException {
if (templateStream == null) {
throw new RuntimeException("Excel模板文件流不能为空");
}
prepareAttachmentResponse(response, filename);
try (templateStream) {
EasyExcel.write(response.getOutputStream(), head)
.withTemplate(templateStream)
.autoCloseStream(false)
.registerWriteHandler(new SelectSheetWriteHandler(head))
.registerConverter(new LongStringConverter())
.sheet(sheetName)
.needHead(false)
.doWrite(data);
}
}
```
```java
public static void writeTemplate(HttpServletResponse response, String filename, InputStream templateStream)
throws IOException {
prepareAttachmentResponse(response, filename);
templateStream.transferTo(response.getOutputStream());
templateStream.close();
}
```
- [ ] **Step 4: 重新跑后端 smoke确认通过**
Run:
```bash
mvn -f "/Volumes/Dpan/github/water-workspace/water-backend/pom.xml" -pl sw-business/sw-business-server -am -Dtest=AccountingAdjustRouteSmokeTest test
```
Expected: PASS四个导出断言均匹配 xlsx MIME。
- [ ] **Step 5: 提交后端公共导出修复**
```bash
git -C "/Volumes/Dpan/github/water-workspace/water-backend" add \
sw-framework/sw-spring-boot-starter-excel/src/main/java/cn/com/emsoft/sw/framework/excel/core/util/ExcelUtils.java \
sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/AccountingAdjustRouteSmokeTest.java
git -C "/Volumes/Dpan/github/water-workspace/water-backend" commit -m "fix(excel): standardize xlsx download headers"
```
### Task 2: 给前端下载工具增加后端文件流下载与文件名解码
**Files:**
- Modify: `../water-frontend/src/utils/download.ts`
- Test: `../water-frontend/src/views/operatingCharges/redReversalRecord/index.vue` (first consumer)
- [ ] **Step 1: 为下载工具写出目标接口**
`download.ts` 里新增两个内部函数:一个解析 `Content-Disposition`,一个下载 axios blob 响应。目标代码如下:
```ts
const decodeFilename = (contentDisposition?: string, fallbackName = 'export.xlsx') => {
if (!contentDisposition) return fallbackName
const utf8Match = contentDisposition.match(/filename\*=utf-8''([^;]+)/i)
if (utf8Match?.[1]) {
return decodeURIComponent(utf8Match[1])
}
const plainMatch = contentDisposition.match(/filename=([^;]+)/i)
if (plainMatch?.[1]) {
return decodeURIComponent(plainMatch[1].trim().replace(/^"|"$/g, ''))
}
return fallbackName
}
const downloadResponse = (response: any, fallbackName = 'export.xlsx') => {
const fileName = decodeFilename(response?.headers?.['content-disposition'], fallbackName)
download0(response.data, fileName, response.data?.type || 'application/octet-stream')
}
```
- [ ] **Step 2: 把新工具挂到默认导出对象**
`download` 对象中追加:
```ts
response: (response: any, fallbackName?: string) => {
downloadResponse(response, fallbackName)
}
```
保留原有 `excel/word/zip/html/markdown/json`,不要顺手改其它行为。
- [ ] **Step 3: 运行前端类型检查,确认工具签名可用**
Run:
```bash
pnpm --dir "/Volumes/Dpan/github/water-workspace/water-frontend" ts:check
```
Expected: PASS如果有 `response` 未使用或 `any` 风格告警,只修本次新增代码,不扩散重构。
- [ ] **Step 4: 提交前端下载工具改动**
```bash
git -C "/Volumes/Dpan/github/water-workspace/water-frontend" add src/utils/download.ts
git -C "/Volumes/Dpan/github/water-workspace/water-frontend" commit -m "feat(download): support backend blob responses"
```
### Task 3: 新增前端导出 API 封装并替换红冲记录页
**Files:**
- Create: `../water-frontend/src/api/operatingCharges/redReversalRecord/index.ts`
- Modify: `../water-frontend/src/views/operatingCharges/redReversalRecord/index.vue`
- [ ] **Step 1: 新建红冲记录导出 API 文件**
创建 `src/api/operatingCharges/redReversalRecord/index.ts`
```ts
import request from '@/config/axios'
export const exportRedReversalRecord = (params: any) => {
return request.download({
url: '/business/accounting-adjust/log-export',
params
})
}
```
- [ ] **Step 2: 先删掉红冲记录页本地 CSV 构造,替换为后端下载调用**
`redReversalRecord/index.vue` 的导出逻辑改成:
```ts
import download from '@/utils/download'
import { exportRedReversalRecord } from '@/api/operatingCharges/redReversalRecord'
```
```ts
const handleExport = async () => {
try {
await message.exportConfirm()
exportLoading.value = true
const response = await exportRedReversalRecord({ ...queryParams })
download.response(response, `红冲记录_${formatDate(Date.now() as any)}.xlsx`)
} finally {
exportLoading.value = false
}
}
```
同时删除这些旧代码:
```ts
const data = list.value
const headers = ['红冲时间', '红冲金额', '收费员', '操作员', '收费时间', '备注']
const csvContent = [
headers.join(','),
...data.map((row: any) => [
row.reversalTime,
row.reversalAmount,
row.cashier,
row.operator,
row.chargeTime,
row.remark
].join(','))
].join('\n')
const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' })
download.excel(blob, `红冲记录_${formatDate(Date.now() as any)}.csv`)
```
- [ ] **Step 3: 运行前端类型检查,确认红冲记录页接线通过**
Run:
```bash
pnpm --dir "/Volumes/Dpan/github/water-workspace/water-frontend" ts:check
```
Expected: PASS页面仍可编译新增 API 路径可被解析。
- [ ] **Step 4: 提交红冲记录页接线**
```bash
git -C "/Volumes/Dpan/github/water-workspace/water-frontend" add \
src/api/operatingCharges/redReversalRecord/index.ts \
src/views/operatingCharges/redReversalRecord/index.vue
git -C "/Volumes/Dpan/github/water-workspace/water-frontend" commit -m "feat(red-reversal): use backend excel export"
```
### Task 4: 新增 collection 导出 API 并替换托收批次/托收明细页面
**Files:**
- Create: `../water-frontend/src/api/collection/export/index.ts`
- Modify: `../water-frontend/src/views/collection/bankCollection/index.vue`
- Modify: `../water-frontend/src/views/collection/bankCollection/detail.vue`
- [ ] **Step 1: 新建 collection 导出 API 文件**
创建 `src/api/collection/export/index.ts`
```ts
import request from '@/config/axios'
export const exportCollectionBatch = (params: any) => {
return request.download({
url: '/bankbusiness/withholding-batch/export-excel',
params: {
...params,
businessType: 'COLLECTION'
}
})
}
export const exportCollectionDetail = (params: any) => {
return request.download({
url: '/bankbusiness/withholding-item/export-excel',
params
})
}
export const exportWithholdingBatch = (params: any) => {
return request.download({
url: '/bankbusiness/withholding-batch/export-excel',
params: {
...params,
businessType: 'WITHHOLDING'
}
})
}
export const exportRealtimeStatistics = (params: any) => {
return request.download({
url: '/bankbusiness/channel-statistics/export-excel',
params
})
}
```
- [ ] **Step 2: 替换银行托收页导出**
`bankCollection/index.vue` 中引入:
```ts
import download from '@/utils/download'
import { exportCollectionBatch } from '@/api/collection/export'
```
把导出函数改成:
```ts
const handleExport = async () => {
try {
await message.exportConfirm()
exportLoading.value = true
const response = await exportCollectionBatch({ ...queryParams })
download.response(response, `银行托收_${formatDate(Date.now() as any)}.xlsx`)
} finally {
exportLoading.value = false
}
}
```
删除 `headers.join(',')` / `csvContent` / `Blob(text/csv)` / `.csv` 文件名代码。
- [ ] **Step 3: 替换托收明细页导出**
`bankCollection/detail.vue` 中引入:
```ts
import download from '@/utils/download'
import { exportCollectionDetail } from '@/api/collection/export'
```
把导出函数改成:
```ts
const handleExport = async () => {
try {
await message.exportConfirm()
exportLoading.value = true
const response = await exportCollectionDetail({ ...queryParams })
download.response(response, `托收明细_${formatDate(Date.now() as any)}.xlsx`)
} finally {
exportLoading.value = false
}
}
```
保留 `batchNo: route.query.batchNo || ''` 作为请求参数的一部分,不要丢掉详情上下文。
- [ ] **Step 4: 运行前端类型检查,确认两个页面通过**
Run:
```bash
pnpm --dir "/Volumes/Dpan/github/water-workspace/water-frontend" ts:check
```
Expected: PASS`bankCollection``detail` 不再包含 CSV 导出逻辑。
- [ ] **Step 5: 提交托收页面改造**
```bash
git -C "/Volumes/Dpan/github/water-workspace/water-frontend" add \
src/api/collection/export/index.ts \
src/views/collection/bankCollection/index.vue \
src/views/collection/bankCollection/detail.vue
git -C "/Volumes/Dpan/github/water-workspace/water-frontend" commit -m "feat(collection): switch batch exports to backend excel"
```
### Task 5: 替换银行代扣与实时收费页面导出
**Files:**
- Modify: `../water-frontend/src/views/collection/bankWithholding/index.vue`
- Modify: `../water-frontend/src/views/collection/realTimeBilling/index.vue`
- Reuse: `../water-frontend/src/api/collection/export/index.ts`
- [ ] **Step 1: 替换银行代扣页导出**
`bankWithholding/index.vue` 中引入:
```ts
import download from '@/utils/download'
import { exportWithholdingBatch } from '@/api/collection/export'
```
把导出函数改成:
```ts
const handleExport = async () => {
try {
await message.exportConfirm()
exportLoading.value = true
const response = await exportWithholdingBatch({ ...queryParams })
download.response(response, `银行代扣_${formatDate(Date.now() as any)}.xlsx`)
} finally {
exportLoading.value = false
}
}
```
删除本地 CSV 生成代码。
- [ ] **Step 2: 替换实时收费页导出**
`realTimeBilling/index.vue` 中引入:
```ts
import download from '@/utils/download'
import { exportRealtimeStatistics } from '@/api/collection/export'
```
把导出函数改成:
```ts
const handleExport = async () => {
try {
await message.exportConfirm()
exportLoading.value = true
const response = await exportRealtimeStatistics({ ...queryParams })
download.response(response, `实时收费_${formatDate(Date.now() as any)}.xlsx`)
} finally {
exportLoading.value = false
}
}
```
删除原 `csvContent` / `Blob(text/csv)` 代码。
- [ ] **Step 3: 跑前端类型检查与最小构建**
Run:
```bash
pnpm --dir "/Volumes/Dpan/github/water-workspace/water-frontend" ts:check
pnpm --dir "/Volumes/Dpan/github/water-workspace/water-frontend" build:dev
```
Expected: 两条命令都 PASS`build:dev` 产物成功生成。
- [ ] **Step 4: 提交银行代扣与实时收费页改造**
```bash
git -C "/Volumes/Dpan/github/water-workspace/water-frontend" add \
src/views/collection/bankWithholding/index.vue \
src/views/collection/realTimeBilling/index.vue
git -C "/Volumes/Dpan/github/water-workspace/water-frontend" commit -m "feat(collection): route remaining exports through backend"
```
### Task 6: 联调验证与收口
**Files:**
- Verify: `../water-backend/...` changed files
- Verify: `../water-frontend/...` changed files
- Optional evidence note: `../water-docs/docs/evidence/` (only if user asks for persistent evidence)
- [ ] **Step 1: 手工检查代码中已消失的 CSV 痕迹**
Run:
```bash
grep -R "text/csv\|csvContent\|\.csv" "/Volumes/Dpan/github/water-workspace/water-frontend/src/views/operatingCharges/redReversalRecord" \
"/Volumes/Dpan/github/water-workspace/water-frontend/src/views/collection/bankCollection" \
"/Volumes/Dpan/github/water-workspace/water-frontend/src/views/collection/bankWithholding" \
"/Volumes/Dpan/github/water-workspace/water-frontend/src/views/collection/realTimeBilling"
```
Expected: 无匹配,或者仅剩注释/字符串字面量以外的历史内容为 0。
- [ ] **Step 2: 手工检查后端 xlsx MIME 已生效**
Run:
```bash
grep -R "application/vnd.ms-excel;charset=UTF-8" "/Volumes/Dpan/github/water-workspace/water-backend/sw-framework/sw-spring-boot-starter-excel/src/main/java/cn/com/emsoft/sw/framework/excel/core/util/ExcelUtils.java"
```
Expected: 无匹配。
- [ ] **Step 3: 浏览器 smoke**
在本地/测试环境逐页点击导出并记录结果:
- `/operatingCharges/redReversalRecord`
- `/collection/bankCollection`
- `/collection/bankCollection/detail?batchNo=<sample>`
- `/collection/bankWithholding`
- `/collection/realTimeBilling`
Expected:
- 发起真实网络请求,不是浏览器本地 Blob 生成。
- 返回文件为 `.xlsx`
- 中文文件名不乱码。
- [ ] **Step 4: 分别查看两仓状态并准备后续 PR**
Run:
```bash
git -C "/Volumes/Dpan/github/water-workspace/water-backend" status --short
git -C "/Volumes/Dpan/github/water-workspace/water-frontend" status --short
```
Expected: 两仓仅包含本计划涉及的文件改动,无额外脏改动。
- [ ] **Step 5: 如果需要合并前整理最终提交**
```bash
git -C "/Volumes/Dpan/github/water-workspace/water-backend" log --oneline -3
git -C "/Volumes/Dpan/github/water-workspace/water-frontend" log --oneline -5
```
Expected: 后端至少 1 个导出统一提交,前端至少 3 个渐进提交下载工具、红冲记录、collection 页面)。
---
## Self-Review
- **Spec coverage:**
- FR-001/FR-002/FR-011/SC-001/SC-006 → Task 1
- FR-003/FR-004/FR-005/FR-008/SC-002/SC-003 → Tasks 2-5
- FR-006/FR-007/SC-004 → Tasks 3-5 通过明确 API 路径落地
- FR-009/FR-010/FR-012/SC-005 → Task 6 + 提交拆分策略
- **Placeholder scan:** 已避免使用 TBD/TODO/“类似 Task N”每个改代码步骤都给了具体代码片段。
- **Type consistency:** 统一使用 `download.response(...)` 处理后端 blob 响应;前端导出 API 全部基于 `request.download(...)`

View File

@ -0,0 +1,626 @@
# 催缴登记 (Arrearage Reminder) 前端对接 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 将两个前端页面(欠费催缴池 + 催缴记录)从 mock 数据切换到真实后端 API包括 API 层补齐、催缴表单对接、汇总统计、导出功能。
**Architecture:** 后端 9 个接口均已就绪 (`POST /create`, `GET /page`, `GET /pending-page`, `GET /get`, `GET /detail-list`, `POST /batch-create`, `GET /pending-summary`, `GET /pending-export`, `GET /pending-detail-export`)。前端按 "API 层 → 催缴表单 → 欠费池页面 → 催缴记录页面" 的顺序逐层对接,每层完成后可独立验证。
**Tech Stack:** Vue 3, TypeScript, Element Plus, Axios (`@/config/axios`), pnpm
---
## File Structure
### API layer
- Modify: `../water-frontend/src/api/collectionManage/arrears/index.ts`
- 新增所有请求/响应 VO 类型 + 7 个缺失的 API 方法
### Components
- Modify: `../water-frontend/src/views/collectionManage/arrears/components/RemindForm.vue`
- 对接 `batchCreate` API接收父组件传入的选中行数据
### Views
- Modify: `../water-frontend/src/views/collectionManage/arrears/index.vue`
- 汇总统计条对接 `getPendingSummary`、导出对接 `getPendingExportList`、催缴按钮传递选中数据给 RemindForm
- Modify: `../water-frontend/src/views/collectionManage/collectionRecord/index.vue`
- 列表对接 `getPage`、详情展开对接 `getDetailList`、导出对接后端(如有对应接口)
- 查询表单字段对齐 `ArrearageReminderPageReqVO`
---
### Task 1: 补齐 API 层 — 类型定义与全部接口方法
**Files:**
- Modify: `../water-frontend/src/api/collectionManage/arrears/index.ts`
- [ ] **Step 1: 添加所有 VO 类型定义和 API 方法**
```typescript
import request from '@/config/axios'
import download from '@/utils/download'
// ========== 请求 VO ==========
export interface ArrearagePendingPageReqVO {
pageNo: number
pageSize: number
deptId?: number
code?: string
name?: string
address?: string
mobile?: string
custStates?: number[]
custType?: number
billMonthStart?: number
billMonthEnd?: number
meterReaderId?: number
bookCode?: string
arrearsCountMin?: number
arrearsCountMax?: number
arrearsAmountMin?: number
arrearsAmountMax?: number
remindFilter?: string
}
export interface ArrearageReminderPageReqVO {
pageNo: number
pageSize: number
custId?: number
reminderUser?: string
reminderType?: number
reminderReason?: number
reminderResult?: number
}
export interface ArrearageReminderCreateReqVO {
custId: number
chargeIds: number[]
reminderType: number
reminderReason: number
reminderResult: number
reminderUser: string
reminderTemplate?: number
remark?: string
}
export interface ArrearageReminderBatchCreateReqVO {
customers: {
custId: number
chargeIds: number[]
}[]
reminderType: number
reminderReason: number
reminderResult: number
reminderUser: string
reminderTemplate?: number
remark?: string
}
// ========== 响应 VO ==========
export interface ArrearagePendingPageRespVO {
custId: number
custCode: string
custName: string
custAddress: string
meterCaliber: string
waterNature: string
arrearsCount: number
waterVolume: number
totalAmount: number
penaltyAmount: number
receivableAmount: number
billAmount: number
accountMonthRange: string
custStatus: string
prestoreAmount: number
mobile: string
isRemindedThisMonth: boolean
agreementNo: string
contractNo: string
details: ArrearagePendingChargeDetailRespVO[]
}
export interface ArrearagePendingChargeDetailRespVO {
chargeId: number
billMonth: string
lastReading: number
currentReading: number
waterVolume: number
billAmount: number
penaltyAmount: number
readingDate: string
}
export interface ArrearagePendingSummaryRespVO {
custCount: number
arrearsCount: number
waterVolume: number
billAmount: number
penaltyAmount: number
totalAmount: number
receivableAmount: number
}
export interface ArrearageReminderPageRespVO {
id: number
custId: number
custCode: string
custName: string
reminderType: number
reminderReason: number
reminderUser: string
remark: string
reminderTemplate: number
completeTime: string
pushState: number
pushResults: string
reminderResult: number
batchStamp: string
totalBillWater: number
totalExtendedAmount: number
totalLateFee: number
deposit: number
mobile: string
createTime: string
updateTime: string
}
export interface ArrearageReminderRespVO extends ArrearageReminderPageRespVO {}
export interface ArrearageReminderDetailRespVO {
id: number
arrearageReminderId: number
chargeId: number
lateFee: number
createTime: string
updateTime: string
}
export interface ArrearageReminderCreateRespVO {
id: number
detailCount: number
}
export interface ArrearageReminderBatchCreateRespVO {
successCount: number
failCount: number
successItems: { custId: number; reminderId: number; detailCount: number }[]
failItems: { custId: number; reason: string }[]
}
// ========== API 方法 ==========
export const ArrearsApi = {
// 待催欠费池
getPendingPage: async (params: ArrearagePendingPageReqVO) => {
return await request.get<{ list: ArrearagePendingPageRespVO[]; total: number }>({
url: '/business/arrearage-reminder/pending-page',
params
})
},
getPendingSummary: async (params: ArrearagePendingPageReqVO) => {
return await request.get<ArrearagePendingSummaryRespVO>({
url: '/business/arrearage-reminder/pending-summary',
params
})
},
// 催缴登记 CRUD
create: async (data: ArrearageReminderCreateReqVO) => {
return await request.post<ArrearageReminderCreateRespVO>({
url: '/business/arrearage-reminder/create',
data
})
},
batchCreate: async (data: ArrearageReminderBatchCreateReqVO) => {
return await request.post<ArrearageReminderBatchCreateRespVO>({
url: '/business/arrearage-reminder/batch-create',
data
})
},
getPage: async (params: ArrearageReminderPageReqVO) => {
return await request.get<{ list: ArrearageReminderPageRespVO[]; total: number }>({
url: '/business/arrearage-reminder/page',
params
})
},
get: async (id: number) => {
return await request.get<ArrearageReminderRespVO>({
url: '/business/arrearage-reminder/get',
params: { id }
})
},
getDetailList: async (reminderId: number) => {
return await request.get<ArrearageReminderDetailRespVO[]>({
url: '/business/arrearage-reminder/detail-list',
params: { reminderId }
})
},
// 导出
exportPending: async (params: ArrearagePendingPageReqVO) => {
const res = await request.download({
url: '/business/arrearage-reminder/pending-export',
params
})
download.response(res, '待催欠费客户.xlsx')
},
exportPendingDetail: async (params: ArrearagePendingPageReqVO) => {
const res = await request.download({
url: '/business/arrearage-reminder/pending-detail-export',
params
})
download.response(res, '待催欠费明细.xlsx')
}
}
```
- [ ] **Step 2: 验证 API 层编译**
Run: `cd ../water-frontend && npx vue-tsc --noEmit src/api/collectionManage/arrears/index.ts 2>&1 | head -20`
Expected: No type errors
---
### Task 2: RemindForm 对接 batchCreate API
**Files:**
- Modify: `../water-frontend/src/views/collectionManage/arrears/components/RemindForm.vue`
- [ ] **Step 1: 重写 RemindForm 脚本,接收选中行 props 并调 batchCreate**
`<script setup lang="ts">` 段替换为:
```typescript
import { ArrearsApi } from '@/api/collectionManage/arrears'
const message = useMessage()
const dialogVisible = ref(false)
const formRef = ref()
const props = defineProps<{
selectedRows: any[]
}>()
// reminderType 映射: '催缴单'->1, '短信'->2, '电话'->3, '微信'->4, '其它'->5, '热线'->6
const methodMap: Record<string, number> = {
'催缴单': 1, '短信': 2, '电话': 3, '微信': 4, '其它': 5, '热线': 6
}
const formData = ref({
method: '催缴单',
operator: '',
reason: '',
remark: ''
})
const rules = {
method: [{ required: true, message: '请选择催缴方式', trigger: 'change' }],
operator: [{ required: true, message: '请选择催缴人员', trigger: 'change' }],
reason: [{ required: true, message: '请选择催缴原因', trigger: 'change' }]
}
const open = () => {
dialogVisible.value = true
formData.value = {
method: '催缴单',
operator: '',
reason: '',
remark: ''
}
}
const submitForm = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (!valid) return
try {
const customers = props.selectedRows.map((row: any) => ({
custId: row.custId,
chargeIds: row.details?.map((d: any) => d.chargeId) || []
}))
const res = await ArrearsApi.batchCreate({
customers,
reminderType: methodMap[formData.value.method] || 1,
reminderReason: Number(formData.value.reason),
reminderResult: 0,
reminderUser: formData.value.operator,
remark: formData.value.remark
})
if (res.failCount > 0) {
const failMsgs = res.failItems.map((f: any) => `客户${f.custId}: ${f.reason}`).join('; ')
message.warning(`成功 ${res.successCount} 条,失败 ${res.failCount} 条。${failMsgs}`)
} else {
message.success(`成功创建 ${res.successCount} 条催缴记录`)
}
dialogVisible.value = false
emit('success')
} catch (e: any) {
message.error(e?.message || '催缴失败')
}
})
}
const emit = defineEmits(['success'])
defineExpose({ open })
```
- [ ] **Step 2: 修改父组件传参 — 更新 arrears/index.vue 中 RemindForm 的使用**
`../water-frontend/src/views/collectionManage/arrears/index.vue` 底部模板中:
```
<!-- 修改前 -->
<RemindForm ref="remindFormRef" @success="handleSuccess" />
<!-- 修改后 -->
<RemindForm ref="remindFormRef" :selected-rows="selectedRows" @success="handleSuccess" />
```
- [ ] **Step 3: 验证编译**
Run: `cd ../water-frontend && npx vue-tsc --noEmit 2>&1 | tail -20`
Expected: No type errors (may have pre-existing errors in other files, check only arrearage-related)
---
### Task 3: 欠费催缴池页面 — 汇总统计 + 导出 + 全选催缴
**Files:**
- Modify: `../water-frontend/src/views/collectionManage/arrears/index.vue`
- [ ] **Step 1: 添加汇总统计数据获取**
`getList` 调用后追加 summary 请求。找到 `script setup` 中的 `getList` 函数(约 line 629在其后添加
```typescript
const summary = ref<{
custCount: number
arrearsCount: number
waterVolume: number
billAmount: number
penaltyAmount: number
totalAmount: number
receivableAmount: number
}>({
custCount: 0, arrearsCount: 0, waterVolume: 0,
billAmount: 0, penaltyAmount: 0, totalAmount: 0, receivableAmount: 0
})
const fetchSummary = async () => {
try {
summary.value = await ArrearsApi.getPendingSummary(buildPageParams())
} catch {
// summary stays at zero
}
}
```
修改 `handleQuery``onMounted` 中的 `getList()` 调用为并行:
```typescript
const handleQuery = () => {
queryParams.pageNo = 1
Promise.all([getList(), fetchSummary()])
}
```
并在 `onMounted` 中:
```typescript
onMounted(async () => {
await Promise.resolve(getSiteTree())
await Promise.all([getList(), fetchSummary()])
})
```
- [ ] **Step 2: 替换模板中的硬编码统计值为真实数据**
找到 `<el-descriptions>` 区域(约 line 211-226替换其中各 `el-descriptions-item` 的值为 `summary` 响应式数据:
```html
<el-descriptions :column="5" class="mt-15px mb-8px" border label-width="120">
<el-descriptions-item label="客户数">
<span class="text-orange-500 font-bold">{{ summary.custCount }}</span>
</el-descriptions-item>
<el-descriptions-item label="欠费笔数">
<span class="text-orange-500 font-bold">{{ summary.arrearsCount }}</span>
</el-descriptions-item>
<el-descriptions-item label="用水量">
<span class="text-orange-500 font-bold">{{ summary.waterVolume }}</span>
</el-descriptions-item>
<el-descriptions-item label="合计金额">
<span class="text-orange-500 font-bold">¥{{ summary.totalAmount }}</span>
</el-descriptions-item>
<el-descriptions-item label="账单金额">
<span class="text-orange-500 font-bold">¥{{ summary.billAmount }}</span>
</el-descriptions-item>
<el-descriptions-item label="违约金">
<span class="text-orange-500 font-bold">¥{{ summary.penaltyAmount }}</span>
</el-descriptions-item>
<el-descriptions-item label="应缴金额">
<span class="text-orange-500 font-bold">¥{{ summary.receivableAmount }}</span>
</el-descriptions-item>
</el-descriptions>
```
- [ ] **Step 3: 导出对接真实 API**
找到 `handleExport` 函数(约 line 663替换为
```typescript
const handleExport = async () => {
try {
message.loading('正在导出...')
await ArrearsApi.exportPending(buildPageParams())
message.success('导出成功')
} catch {
message.error('导出失败')
}
}
```
- [ ] **Step 4: 全部催缴传参**
找到 `handleRemindAll`(约 line 657当前直接 `remindFormRef.value.open()`,需要传入当前列表中所有行的数据。修改为:
```typescript
const handleRemindAll = () => {
selectedRows.value = list.value
remindFormRef.value.open()
}
```
- [ ] **Step 5: 从 API 导入汇总相关方法**
`arrears/index.vue` 的 import 中,`ArrearsApi` 的导入行保持不变(当前已导入),新增 `summary` ref 定义的位置放到 `selectedRows` 附近。
- [ ] **Step 6: 验证编译**
Run: `cd ../water-frontend && npx vue-tsc --noEmit 2>&1 | tail -20`
Expected: No new type errors from arrears files
---
### Task 4: 催缴记录页面 — 列表与详情对接
**Files:**
- Modify: `../water-frontend/src/views/collectionManage/collectionRecord/index.vue`
- [ ] **Step 1: 添加 API 导入**
在 script setup 顶部 import 中加入:
```typescript
import { ArrearsApi } from '@/api/collectionManage/arrears'
```
- [ ] **Step 2: 添加详情数据 ref 和获取方法**
`list` ref 附近(约 line 294追加
```typescript
const detailMap = ref<Record<number, any[]>>({})
const fetchDetailList = async (reminderId: number) => {
if (detailMap.value[reminderId]) return
try {
const details = await ArrearsApi.getDetailList(reminderId)
detailMap.value[reminderId] = details
} catch {
detailMap.value[reminderId] = []
}
}
```
- [ ] **Step 3: 替换 getList 为真实 API 调用**
找到现有的 getList 函数(当前为 mock 数据),替换为:
```typescript
const getList = async () => {
loading.value = true
try {
const data = await ArrearsApi.getPage({
pageNo: queryParams.pageNo,
pageSize: queryParams.pageSize,
custId: undefined, // 后续可扩展
reminderUser: queryParams.remindUser || undefined,
reminderType: undefined,
reminderReason: queryParams.remindReason ? Number(queryParams.remindReason) : undefined,
reminderResult: queryParams.remindResult ? Number(queryParams.remindResult) : undefined
})
list.value = (data?.list || []).map((item: any) => ({
...item,
id: item.id,
deptName: item.deptName || '-',
custCode: item.custCode,
custName: item.custName,
custAddress: item.custAddress || '-',
remindUser: item.reminderUser,
remindMethod: item.reminderType,
remindReason: item.reminderReason,
mobile: item.mobile,
arrearsCount: 0, // reminder records don't carry count — fetched via details
arrearsWaterVolume: item.totalBillWater,
arrearsAmount: item.totalExtendedAmount,
penaltyAmount: item.totalLateFee,
prestoreBalance: item.deposit,
completeDate: item.completeTime,
registerDate: item.createTime,
remark: item.remark,
expanded: false,
pushStatus: item.pushState,
details: []
}))
total.value = data?.total || 0
} finally {
loading.value = false
}
}
```
- [ ] **Step 4: 展开行对接详情**
找到模板中的 expand slot当前显示 mock 数据 `detailsMock1`),替换为绑定 `detailMap`
```html
<template #expand="scope">
<div class="p-20px bg-gray-50">
<el-table
:data="detailMap[scope.row.id] || []"
border
style="width: 100%"
v-loading="!detailMap[scope.row.id]"
>
<el-table-column prop="chargeId" label="账单ID" width="120" />
<el-table-column prop="lateFee" label="违约金" width="120" />
<el-table-column prop="createTime" label="创建时间" width="180" />
</el-table>
</div>
</template>
```
- [ ] **Step 5: 展开事件触发详情加载**
`handleExpandChange` 中调用 `fetchDetailList`
```typescript
const handleExpandChange = (row: any, expandedRows: any[]) => {
row.expanded = expandedRows.includes(row)
if (row.expanded) {
fetchDetailList(row.id)
}
}
```
- [ ] **Step 6: 删除 mock 数据**
删除文件中的 `detailsMock1` 等 mock 数据声明。
- [ ] **Step 7: 验证编译**
Run: `cd ../water-frontend && npx vue-tsc --noEmit 2>&1 | tail -20`
Expected: No new type errors
---
## Verification Checklist
完成所有 4 个 Task 后,执行端到端 smoke
- [ ] `npx vue-tsc --noEmit` 编译通过
- [ ] 欠费催缴池页面:汇总统计数字不再是硬编码
- [ ] 欠费催缴池页面:点击"导出"触发 `GET /pending-export` 下载 xlsx
- [ ] 欠费催缴池页面:勾选客户 → 点击"选中催缴" → 弹窗填写 → 提交 → 调 `POST /batch-create`
- [ ] 欠费催缴池页面:点击"全部催缴"将当前列表全部行传入 RemindForm
- [ ] 催缴记录页面:列表从 `GET /page` 加载,查询条件生效
- [ ] 催缴记录页面:展开行从 `GET /detail-list` 加载账单明细

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,673 @@
# Revenue Bugfix Clear Scope Acceptance Test Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 为营收明确缺陷第一批修复提供一份可交给测试人员执行的业务验收用例计划。
**Architecture:** 本计划按缺陷编号拆分验收场景,每个场景包含前置条件、操作步骤、期望结果和证据要求。验收不绑定具体测试工具,测试人员可通过浏览器页面、业务后台、数据库只读查询或接口日志完成证据采集。
**Tech Stack:** 福建水务营收系统、营收后台页面、柜台收费、柜台结账、红冲记录、水价模板、账务调整审批流程。
---
## 适用范围
本计划对应 `docs/superpowers/specs/2026-06-08-revenue-bugfix-clear-scope-design.md` 中纳入本轮的缺陷:
- `#78` 水价调整:执行报错后,用户不应只能关闭菜单从头开始。
- `#39` 柜台结账:柜台预存缴费记录应能进入待结清并完成结账。
- `#50` 柜台结账:收费员筛选不应被后端无条件覆盖为当前登录用户。
- `#53` 柜台收费:预存抵扣金额需要前后端契约保护和回归验证。
- `#58/#59` 红冲记录:柜台红冲后应能在红冲记录页按红冲时间查到。
- `#69/#76` 未销分账、呆坏账、价差调整、违约金减免:前端应准确表达“申请已提交,待审批/待回写”,不得提示为已生效。
以下问题不纳入本次验收:
- `#70` 未销调整提示成功但账单未变。
- `#9` 抄表状态修改无影响。
## 验收环境要求
- 使用已经部署本轮前后端修复的测试环境。
- 测试账号至少包含:
- 管理员账号:可查看全部收费员数据,可执行水价调整、柜台结账、红冲记录查询。
- 收费员 A可进行柜台收费、预存充值、柜台结账。
- 收费员 B可进行柜台收费或预存充值用于验证收费员筛选。
- 审批相关账号:可查看账务调整提交后的待审批记录。
- 所有测试数据应使用测试客户,不使用生产客户。
- 每个用例执行前记录测试时间、测试账号、客户编号、业务单号、页面截图或后台记录。
## 测试数据准备
- 客户 C1存在正常水表、正常用水性质、可生成账单账户预存余额为 0。
- 客户 C2存在正常水表和未结清账单账户预存余额大于 0余额小于或等于选中账单应收金额。
- 客户 C3存在正常水表和未结清账单账户预存余额大于选中账单应收金额。
- 客户 C4用于柜台预存充值不要求存在当月未结清账单。
- 客户 C5用于账务调整申请存在可分账、可呆坏账、可价差调整或可违约金减免的历史账单。
- 收费员 A 和收费员 B 分别产生至少一笔柜台收费或预存充值记录。
- 柜台结账测试前确认存在未结账记录,红冲测试前确认存在已结账记录。
## 验收通过标准
- 所有 P0 和 P1 用例通过。
- P2 用例不出现阻断主流程的问题。
- 每个缺陷编号至少有一条正向用例和一条异常或边界用例完成验证。
- 页面提示、列表数据、业务记录三类证据能够互相印证。
- 若出现失败,必须记录缺陷编号、操作步骤、输入数据、期望结果、实际结果和证据截图。
---
### Task 1: #78 水价调整失败恢复验收
**覆盖目标:** 水价调整提交失败后,用户能看到明确错误,并可继续在当前页面修正后重新提交,不需要关闭菜单或重新进入页面。
- [ ] **Step 1: 准备水价调整测试数据**
前置条件:
- 使用管理员账号登录。
- 进入“设置 / 价格 / 水价模板”页面。
- 选择一个允许调价的水价模板。
- 记录模板名称、模板编号、当前生效水价。
通过标准:
- 页面能进入水价模板详情。
- 页面存在“开始调价”或等价操作入口。
- [ ] **Step 2: 验证开始调价失败提示**
操作步骤:
1. 使用账号 A 进入水价模板并点击“开始调价”。
2. 保持账号 A 不提交调价。
3. 使用账号 B 进入同一租户同一水价模板并点击“开始调价”。
期望结果:
- 账号 B 不应静默失败。
- 页面应提示锁被占用、当前不可调价或等价业务错误。
- 账号 B 页面不应进入可编辑调价态。
- 账号 A 的调价状态不被账号 B 破坏。
证据要求:
- 账号 B 错误提示截图。
- 账号 A 仍处于调价态的截图。
- [ ] **Step 3: 验证提交失败后可继续修正**
操作步骤:
1. 使用账号 A 在调价态修改一个价格项。
2. 构造一条业务不允许提交的数据,例如价格为空、价格为负数、必填项缺失,或使用环境中已知会被后端拒绝的调价数据。
3. 点击提交。
4. 查看失败提示。
5. 在不关闭菜单、不刷新浏览器、不重新进入页面的情况下,把错误字段修正为合法值。
6. 再次点击提交。
期望结果:
- 第一次提交失败时,页面显示明确失败原因。
- 页面仍保留在可继续处理的状态,用户能继续修改表单。
- 第二次提交成功后,页面提示调价成功或进入后续审批状态。
- 调价结果在水价模板详情中可见,或能看到对应待审批记录。
证据要求:
- 第一次失败提示截图。
- 修正后再次提交成功截图。
- 成功后的水价模板详情或审批记录截图。
- [ ] **Step 4: 验证取消调价失败提示**
操作步骤:
1. 进入水价模板调价态。
2. 点击“取消调价”。
3. 如环境支持模拟失败,触发取消失败;如不支持模拟失败,则在网络异常或锁失效条件下验证。
期望结果:
- 取消失败时页面显示明确失败原因。
- 页面不应出现无提示、按钮无响应或调试信息外露。
证据要求:
- 取消失败提示截图,或说明当前环境无法制造取消失败但正常取消路径通过。
---
### Task 2: #39 柜台预存缴费进入待结清并完成结账验收
**覆盖目标:** 柜台预存充值记录能出现在柜台结账待结清列表,并可被结账确认。
- [ ] **Step 1: 创建柜台预存充值记录**
前置条件:
- 使用收费员 A 登录。
- 选择客户 C4。
- 客户 C4 当前无未结账预存充值记录,或记录本次充值前的未结账数量。
操作步骤:
1. 进入“柜台收费”或预存充值入口。
2. 为客户 C4 办理一笔预存充值,金额使用 `100.00` 元。
3. 完成支付。
4. 记录缴费单号、客户编号、充值金额、收费员、缴费时间。
期望结果:
- 预存充值成功。
- 系统生成柜台缴费记录。
- 该记录未被标记为已结账。
证据要求:
- 充值成功页面或缴费记录截图。
- 单号、金额、收费员、时间。
- [ ] **Step 2: 查询柜台结账待结清列表**
操作步骤:
1. 使用收费员 A 或管理员进入“柜台结账”页面。
2. 查询未结账记录。
3. 使用客户 C4、收费员 A 或缴费时间范围筛选。
期望结果:
- 客户 C4 的 `100.00` 元预存充值记录出现在待结清列表。
- 记录金额、客户、收费员、缴费时间正确。
- 若该记录没有账单月份、收费单号或营业账 ID页面显示 `--` 或空值占位,不应显示 `undefined``null`、报错或导致整行无法选择。
证据要求:
- 待结清列表截图。
- 可见客户、金额、收费员、缴费时间、空值占位。
- [ ] **Step 3: 完成预存充值记录结账**
操作步骤:
1. 在待结清列表选择客户 C4 的预存充值记录。
2. 点击结账或确认结账。
3. 核对结账汇总金额为 `100.00` 元。
4. 确认提交。
期望结果:
- 结账成功。
- 结账汇总金额包含该预存充值金额。
- 结账后该记录不再出现在待结清列表。
- 已结账记录中能查询到对应结账单。
证据要求:
- 结账确认弹窗或确认页截图。
- 结账成功截图。
- 结账后待结清列表无该记录截图。
- 已结账记录或结账单详情截图。
---
### Task 3: #50 柜台结账收费员筛选验收
**覆盖目标:** 柜台结账查询在指定收费员时按指定收费员查询,不被强制覆盖为当前登录用户。
- [ ] **Step 1: 准备两个收费员的未结账记录**
操作步骤:
1. 使用收费员 A 产生一笔柜台收费或预存充值未结账记录。
2. 使用收费员 B 产生一笔柜台收费或预存充值未结账记录。
3. 记录两笔记录的客户、金额、收费员、缴费时间。
期望结果:
- 收费员 A 和收费员 B 各有至少一笔未结账记录。
- 两笔记录可通过客户或时间区分。
证据要求:
- 两笔未结账记录的业务单据或列表截图。
- [ ] **Step 2: 管理员按收费员 A 查询**
操作步骤:
1. 使用管理员进入“柜台结账”页面。
2. 筛选收费员为收费员 A。
3. 点击查询。
期望结果:
- 列表显示收费员 A 的未结账记录。
- 列表不混入收费员 B 的记录。
- 页面筛选条件保持为收费员 A。
证据要求:
- 查询条件和结果列表同屏截图。
- [ ] **Step 3: 管理员按收费员 B 查询**
操作步骤:
1. 保持管理员账号。
2. 将收费员筛选改为收费员 B。
3. 点击查询。
期望结果:
- 列表显示收费员 B 的未结账记录。
- 列表不再显示收费员 A 的记录。
- 系统没有把筛选条件改回管理员或当前登录用户。
证据要求:
- 查询条件和结果列表同屏截图。
- [ ] **Step 4: 不选择收费员时验证默认规则**
操作步骤:
1. 使用收费员 A 登录。
2. 进入“柜台结账”页面。
3. 不选择收费员,直接查询未结账记录。
期望结果:
- 若系统规则为普通收费员默认查本人,则列表只显示收费员 A 的记录。
- 页面不应显示其他收费员数据,除非当前账号被配置为可查看全部收费员。
证据要求:
- 登录账号信息和查询结果截图。
---
### Task 4: #53 柜台收费预存抵扣验收
**覆盖目标:** 预存抵扣金额受预存余额和账单应收限制,合法抵扣能扣减余额并写入支付记录,非法抵扣有明确提示。
- [ ] **Step 1: 验证预存余额为 0 时开启抵扣**
前置条件:
- 客户 C1 存在未结清账单。
- 客户 C1 预存余额为 `0.00`
操作步骤:
1. 进入“柜台收费”页面。
2. 查询客户 C1。
3. 选择一笔或多笔未结清账单。
4. 开启预存抵扣。
5. 尝试提交收费。
期望结果:
- 系统提示当前可抵扣金额为 0或要求确认不使用预存抵扣继续收费。
- 未经确认不应直接提交收费。
- 若确认继续,应按普通支付金额完成收费,不应产生预存扣减。
证据要求:
- 预存余额为 0 的页面截图。
- 提示或确认弹窗截图。
- 最终收费记录截图。
- [ ] **Step 2: 验证抵扣金额小于账单应收**
前置条件:
- 客户 C2 存在未结清账单,应收金额大于预存余额。
- 客户 C2 预存余额记录为 `B` 元,选中账单应收合计记录为 `R` 元,满足 `0 < B < R`
操作步骤:
1. 查询客户 C2。
2. 选择账单应收合计为 `R` 的账单。
3. 开启预存抵扣。
4. 确认页面计算抵扣金额为 `B`,剩余应付金额为 `R - B`
5. 完成收费。
6. 查询客户 C2 账户余额和支付记录。
期望结果:
- 抵扣金额不超过预存余额。
- 抵扣金额不超过账单应收合计。
- 支付成功后客户预存余额扣减为 `0.00`
- 支付记录中能看到预存抵扣金额为 `B`,或业务明细中能证明本次抵扣 `B`
证据要求:
- 收费前余额和应收金额截图。
- 收费确认金额截图。
- 收费成功后余额截图。
- 支付记录或业务明细截图。
- [ ] **Step 3: 验证预存余额大于账单应收**
前置条件:
- 客户 C3 存在未结清账单。
- 客户 C3 预存余额记录为 `B` 元,选中账单应收合计记录为 `R` 元,满足 `B > R > 0`
操作步骤:
1. 查询客户 C3。
2. 选择应收合计为 `R` 的账单。
3. 开启预存抵扣。
4. 完成收费。
5. 查询客户 C3 账户余额和支付记录。
期望结果:
- 抵扣金额为 `R`,不得超过账单应收合计。
- 本次实际支付金额为 `0.00` 或无需额外现金支付,按页面规则展示。
- 收费成功后客户预存余额为 `B - R`
- 支付记录保留本次预存抵扣金额 `R`
证据要求:
- 收费确认页截图。
- 收费成功后余额截图。
- 支付记录或业务明细截图。
- [ ] **Step 4: 验证非法抵扣金额被拒绝**
操作步骤:
1. 在可操作环境中尝试提交超过预存余额的抵扣金额。
2. 在可操作环境中尝试提交超过账单应收合计的抵扣金额。
3. 在可操作环境中尝试提交负数抵扣金额。
期望结果:
- 三类非法金额均被拒绝。
- 页面或后端返回明确错误原因。
- 客户余额、账单状态、支付记录不发生错误变更。
证据要求:
- 每类非法输入的错误提示截图。
- 验证余额和账单状态未变的截图。
---
### Task 5: #58/#59 柜台红冲记录验收
**覆盖目标:** 柜台结账红冲后,红冲记录页能按红冲时间查询到记录,并展示柜台红冲业务字段。
- [ ] **Step 1: 准备已结账记录**
操作步骤:
1. 使用收费员 A 完成一笔柜台收费或预存充值。
2. 在“柜台结账”中完成结账。
3. 记录结账单号、客户、收费员、结账金额、结账时间。
期望结果:
- 存在一笔可红冲的已结账记录。
证据要求:
- 结账单详情截图。
- [ ] **Step 2: 执行柜台红冲**
操作步骤:
1. 进入已结账记录或结账单详情。
2. 对 Step 1 的结账记录执行红冲。
3. 填写红冲原因,例如“验收测试红冲”。
4. 提交红冲。
5. 记录红冲时间、红冲金额、红冲原因。
期望结果:
- 红冲成功。
- 原结账记录状态变为部分红冲或全部红冲。
- 系统生成红冲记录。
证据要求:
- 红冲提交页面截图。
- 红冲成功提示截图。
- 原结账记录状态截图。
- [ ] **Step 3: 按红冲时间查询红冲记录**
操作步骤:
1. 进入“红冲记录”页面。
2. 设置红冲时间范围,开始时间早于 Step 2 的红冲时间,结束时间晚于 Step 2 的红冲时间。
3. 点击查询。
期望结果:
- 列表能查询到 Step 2 产生的红冲记录。
- 查询条件标签应表达为“红冲时间”或等价语义。
- 返回记录展示结账单号、收费员、客户、红冲金额、红冲时间、红冲原因。
- 红冲金额与 Step 2 记录一致。
证据要求:
- 查询条件和结果列表同屏截图。
- [ ] **Step 4: 验证红冲时间范围边界**
操作步骤:
1. 将查询开始时间设置为红冲时间之后。
2. 点击查询。
3. 将查询结束时间设置为红冲时间之前。
4. 点击查询。
5. 将查询时间范围重新覆盖红冲时间。
6. 点击查询。
期望结果:
- 红冲时间不在范围内时,不显示该红冲记录。
- 红冲时间在范围内时,显示该红冲记录。
- 系统不应按创建时间、处理时间或其他非红冲时间字段错误命中。
证据要求:
- 不命中截图两张。
- 命中截图一张。
---
### Task 6: #69/#76 待审批状态文案验收
**覆盖目标:** 未销分账、呆坏账、价差调整、违约金减免提交后,页面按业务状态提示“申请已提交,待审批/待回写/处理完成”,不把待审批误说成已生效。
- [ ] **Step 1: 验证未销分账申请文案**
操作步骤:
1. 进入账务调整相关页面。
2. 选择客户 C5 的一笔可分账账单。
3. 发起未销分账申请。
4. 提交后观察页面提示和列表状态。
期望结果:
- 如该操作需要审批,页面提示“申请已提交,待审批”或等价文案。
- 页面不应提示“分账成功”“已生效”这类让用户误解为即时落账的文案。
- 审批列表或业务记录中存在待审批申请。
证据要求:
- 提交后提示截图。
- 业务列表状态或审批记录截图。
- [ ] **Step 2: 验证呆坏账申请文案**
操作步骤:
1. 选择客户 C5 的一笔可做呆坏账处理的账单。
2. 发起呆坏账申请。
3. 提交后观察页面提示和列表状态。
期望结果:
- 如该操作需要审批,页面提示“申请已提交,待审批”或等价文案。
- 页面不应提示“呆坏账处理完成”“已生效”这类即时落账文案。
- 审批列表或业务记录中存在待审批申请。
证据要求:
- 提交后提示截图。
- 业务列表状态或审批记录截图。
- [ ] **Step 3: 验证价差调整申请文案**
操作步骤:
1. 选择客户 C5 的一笔可价差调整账单。
2. 发起价差调整申请。
3. 提交后观察页面提示和列表状态。
期望结果:
- 如该操作需要审批,页面提示“申请已提交,待审批”或等价文案。
- 若后端返回待回写状态,页面提示“待回写”或“待执行”。
- 页面不应把待审批或待回写状态表达为已完成。
证据要求:
- 提交后提示截图。
- 业务列表状态或审批记录截图。
- [ ] **Step 4: 验证违约金减免申请文案**
操作步骤:
1. 选择客户 C5 的一笔存在违约金或可减免账单。
2. 发起违约金减免申请。
3. 提交后观察页面提示和列表状态。
期望结果:
- 如该操作需要审批,页面提示“申请已提交,待审批”或等价文案。
- 若后端返回处理完成且已回写,页面可提示“处理完成”。
- 页面文案必须与审批状态和回写状态一致。
证据要求:
- 提交后提示截图。
- 业务列表状态或审批记录截图。
---
### Task 7: 回归与一致性验收
**覆盖目标:** 本轮修复不破坏柜台收费、柜台结账、红冲记录、水价模板和账务调整的基础链路。
- [ ] **Step 1: 柜台收费基础回归**
操作步骤:
1. 选择一个无预存抵扣的客户。
2. 完成一笔普通柜台收费。
3. 查询支付记录和账单状态。
期望结果:
- 收费成功。
- 账单状态更新正确。
- 支付记录金额正确。
证据要求:
- 收费成功截图。
- 支付记录或账单状态截图。
- [ ] **Step 2: 柜台结账基础回归**
操作步骤:
1. 查询普通账单收费产生的未结账记录。
2. 选择记录并完成结账。
3. 查询已结账记录。
期望结果:
- 普通账单收费记录仍可结账。
- 结账金额正确。
- 结账后不再出现在待结清列表。
证据要求:
- 待结清、结账确认、已结账记录截图。
- [ ] **Step 3: 红冲记录页面基础回归**
操作步骤:
1. 不输入任何筛选条件,打开红冲记录页面。
2. 输入客户、收费员、红冲时间范围分别查询。
期望结果:
- 页面正常加载。
- 查询条件可正常输入和清空。
- 列表分页、刷新、空结果状态正常。
证据要求:
- 默认列表或空列表截图。
- 条件查询截图。
- [ ] **Step 4: 水价模板基础回归**
操作步骤:
1. 进入水价模板页面。
2. 展开组织或模板树。
3. 查看水价明细。
4. 不进入调价态时执行普通查看和刷新。
期望结果:
- 水价模板树正常加载。
- 明细正常展示。
- 未进入调价态时不会出现可编辑误状态。
证据要求:
- 模板树和明细截图。
## 验收记录模板
测试人员执行时,每条用例按以下格式记录:
```text
缺陷编号:
用例名称:
执行人:
执行时间:
测试环境:
登录账号:
客户编号:
业务单号:
前置数据:
操作步骤:
期望结果:
实际结果:
通过状态:通过 / 不通过 / 阻塞
证据文件:
备注:
```
## 自检结果
- 规格覆盖:本计划覆盖 `#78``#39``#50``#53``#58/#59``#69/#76`,并明确排除 `#70``#9`
- 占位扫描:未发现占位内容。
- 类型一致性:缺陷编号、业务页面、状态文案、金额边界、时间字段均与设计文档保持一致。

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,153 @@
# 预存余额错误处理修复 + 柜台收费开票/分账状态展示
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 修复 3 类异常处理缺陷 + 柜台收费主表格增加开票状态和分账状态列。
**Architecture:** 后端 3 处异常处理修正已完成(未提交),本次计划覆盖剩余 2 项:后端 VO 加字段 + 前端表格加列。`BeanUtils.toBean` 自动映射同名属性,后端改动最小。
**Tech Stack:** Spring Boot 3, MyBatis-Plus, Vue 3, Element Plus, TypeScript
---
## 已完成修改 (未提交)
| 文件 | 修改内容 |
|------|---------|
| `ChargeServiceImpl.java:2276-2281` | `catch(Exception)` 不再包装为 `IllegalArgumentException`,改为 `exception(INTERNAL_SERVER_ERROR)` |
| `ChargeServiceImpl.java:2446-2448` | 开票状态校验从 `!= NOT_INVOICED` 改为仅拦截 `PARTIALLY_INVOICED``INVOICE_COMPLETED` |
| `PrestorageAdjustInternalApiImpl.java:194-200` | catch 内 DB 更新加嵌套 try-catch防止状态更新失败吞异常 |
| `BadDebt/PriceDiff/LateFeeReduce/Writtenoff` AdjustInternalApiImpl | catch 内 `.message(e.getMessage())` 加 null-safe |
---
### Task 1: 后端 — ChargeDetailsPageRespVO 加分账字段
**Files:**
- Modify: `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/charge/vo/ChargeDetailsPageRespVO.java` (在 `invoiceStateName` 之后插入)
**Purpose:** 柜台收费主表格 API (`/business/charge/page-details`) 返回 `splitAdjustId``splitStatus` 字段,供前端展示分账状态。`ChargeDO` 已有这两个字段,只需在 VO 中声明即可自动映射。
- [ ] **Step 1: 在 ChargeDetailsPageRespVO 添加字段**
`ChargeDetailsPageRespVO.java``invoiceStateName` 字段后面添加:
```java
@Schema(description = "分账调整主表ID")
private Long splitAdjustId;
@Schema(description = "分账状态NONE/PARENT_SPLIT/SPLIT_CHILD")
@ExcelProperty("分账状态")
private String splitStatus;
```
位置:`invoiceStateName` 字段声明之后、`meterOthType` 字段之前。`BeanUtils.toBean` 自动从 `ChargeDO.splitAdjustId` / `ChargeDO.splitStatus` 映射。
- [ ] **Step 2: 编译验证**
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend/sw-business/sw-business-server && mvn compile -o
```
Expected: BUILD SUCCESS.
- [ ] **Step 3: Commit**
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend
git add sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/charge/vo/ChargeDetailsPageRespVO.java
git commit -m "feat(charge): ChargeDetailsPageRespVO 加分账字段 splitAdjustId + splitStatus"
```
---
### Task 2: 前端 — 柜台收费主表格加开票状态 + 分账状态列
**Files:**
- Modify: `water-frontend/src/views/operatingCharges/counterCharging/index.vue:206-208` (在"状态"列之后插入两列)
**Purpose:** 待缴账单表格中,在"状态"列后面加"开票状态"(已有后端字段 `invoiceStateName`)和"分账状态"(新后端字段 `splitStatus`),让操作员预判操作是否会被拦截。
- [ ] **Step 1: 在待缴账单表格加两列**
当前代码(约 206-208 行):
```html
<el-table-column label="状态" min-width="100">
<template #default>未收</template>
</el-table-column>
```
修改为:
```html
<el-table-column label="状态" min-width="100">
<template #default>未收</template>
</el-table-column>
<el-table-column prop="invoiceStateName" label="开票状态" min-width="100">
<template #default="{ row }">{{ row.invoiceStateName || '-' }}</template>
</el-table-column>
<el-table-column label="分账" min-width="90">
<template #default="{ row }">
<el-tag v-if="row.splitStatus === 'PARENT_SPLIT'" type="warning" size="small">已分账</el-tag>
<el-tag v-else-if="row.splitStatus === 'SPLIT_CHILD'" type="info" size="small">子账单</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
```
- [ ] **Step 2: 集收号模式下的子表格也同样加列**
找到集收号子表格(约 75-89 行,`getHubCustomerCharges` 对应的 el-table在"状态"列后同样插入上述两列。列定义相同。
- [ ] **Step 3: TypeScript 类型检查**
```bash
cd /Volumes/Dpan/github/water-workspace/water-frontend && npx vue-tsc --noEmit 2>&1 | grep "counterCharging" | head -10
```
Expected: 无新增错误(`CounterChargingChargeVO` 声明了 `[key: string]: any`,新字段不会报类型错误)。
- [ ] **Step 4: Commit**
```bash
cd /Volumes/Dpan/github/water-workspace/water-frontend
git add src/views/operatingCharges/counterCharging/index.vue
git commit -m "feat(counter): 柜台收费表格加开票状态 + 分账状态列"
```
---
### Task 3: 提交全部后端异常处理修复
**Files:** (已在工作区修改,待提交)
- `ChargeServiceImpl.java`
- `PrestorageAdjustInternalApiImpl.java`
- `BadDebtAdjustInternalApiImpl.java`
- `PriceDiffAdjustInternalApiImpl.java`
- `LateFeeReduceInternalApiImpl.java`
- `WrittenoffAdjustInternalApiImpl.java`
- [ ] **Step 1: 确认修改内容**
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend && git diff --stat
```
应显示 6 files changed.
- [ ] **Step 2: 编译确认**
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend/sw-business/sw-business-server && mvn compile -o
```
Expected: BUILD SUCCESS (仅 WARNING无 ERROR).
- [ ] **Step 3: Commit**
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend
git add -A
git commit -m "fix: 异常处理修正 — handleUsageAdjust 不伪装 400 + invoiceState 放宽 + InternalApi catch 加固"
```
---

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,255 @@
# 账务日志统计字段对齐 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 修复 `getLogStat` 返回字段与前端 `AccountingAdjustStatRespVO` 不匹配的问题,使账务日志统计面板正确显示金额汇总、各状态笔数。
**Architecture:** 后端已有 `AccountingAdjustLogController`(含 `log-page`/`log-stat`/`log-detail`)和 `AccountingAdjustActionController`(含 `log-refund`/`log-prestorage`/`log-revoke`/`log-process`/`log-attachments`/`log-export`6 个操作端点均已存在。唯一缺口是 `log-stat` 返回的 `AccountingAdjustLogStatRespVO` 使用 `totalAmount`/`completedCount` 等旧字段名,而前端期望 `totalActionAmount`/`successCount`/`failCount` 等 8 个新字段。
**Tech Stack:** Java 17, Spring Boot, MyBatis-Plus, JUnit 5 + Mockito, Lombok
---
## 前置说明:端点已存在
前端调用的 6 个操作端点均已在 `AccountingAdjustActionController` 中实现,路径对比如下:
| 前端 API | 后端 Controller | 状态 |
|---|---|---|
| `GET log-export` | `ActionController.exportLog()` | ✅ |
| `GET log-process` | `ActionController.getLogProcess()` | ✅ |
| `GET log-attachments` | `ActionController.getLogAttachments()` | ✅ |
| `POST log-refund` | `ActionController.createLogRefund()` | ✅ |
| `POST log-prestorage` | `ActionController.createLogPrestorage()` | ✅ |
| `POST log-revoke` | `ActionController.revokeLog()` | ✅ |
本次只修 **统计字段对齐** 一个问题。
---
### Task 1: 扩展 StatRespVO 字段
**Files:**
- Modify: `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/vo/AccountingAdjustLogStatRespVO.java`
- Reference: `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/accountProcess/AccountingAdjustLogProcessServiceImpl.java:95-107` (current `getLogStat`)
**当前字段6个与前端不匹配**
```java
private Long totalCount; // 总条数 → 保留
private BigDecimal totalAmount; // 调整金额 → 改为 totalActionAmount
private BigDecimal totalWaterVolume; // 调整水量 → 保留兼容
private Long completedCount; // 已完成数量 → 替换
private Long pendingCount; // 未完成数量 → 替换
private Long cancelledCount; // 已撤销数量 → 替换
```
**前端期望字段(需新增):**
```java
private BigDecimal totalActionAmount; // 金额汇总
private Long totalCount; // 总笔数 (已有)
private Long successCount; // 成功笔数
private Long failCount; // 失败笔数
private Long pendingApprovalCount; // 待审批笔数
private Long approvalRequiredCount; // 需要审批笔数
private Long approvedCount; // 审批通过笔数
private Long rejectedCount; // 审批驳回笔数
```
- [ ] **Step 1: 修改 VO 字段**
用以下完整内容替换 `AccountingAdjustLogStatRespVO.java`
```java
package cn.com.emsoft.sw.business.controller.admin.accountingadjust.accountProcess.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - 账务日志兼容统计 Response VO")
@Data
public class AccountingAdjustLogStatRespVO {
@Schema(description = "金额汇总(所有记录 actionAmount 求和)")
private BigDecimal totalActionAmount;
@Schema(description = "总笔数")
private Long totalCount;
@Schema(description = "成功笔数 (resultStatus == SUCCESS)")
private Long successCount;
@Schema(description = "失败笔数 (resultStatus == FAIL)")
private Long failCount;
@Schema(description = "待审批笔数 (approvalStatus == PENDING_APPROVAL)")
private Long pendingApprovalCount;
@Schema(description = "需要审批笔数 (approvalRequired == true)")
private Long approvalRequiredCount;
@Schema(description = "审批通过笔数 (approvalStatus == APPROVED)")
private Long approvedCount;
@Schema(description = "审批驳回笔数 (approvalStatus == REJECTED)")
private Long rejectedCount;
// 保留旧字段兼容已有调用方(如单元测试中的 getTotalAmount()
@Schema(description = "调整金额(兼容别名,等同于 totalActionAmount")
private BigDecimal totalAmount;
@Schema(description = "调整水量")
private BigDecimal totalWaterVolume;
@Schema(description = "已完成数量(兼容别名,等同于 successCount")
private Long completedCount;
@Schema(description = "未完成数量(兼容别名,等同于 pendingApprovalCount")
private Long pendingCount;
@Schema(description = "已撤销数量(兼容别名,等同于 failCount + rejectedCount")
private Long cancelledCount;
}
```
**设计要点:保留旧字段 `totalAmount`/`completedCount`/`pendingCount`/`cancelledCount` 作为兼容别名Service 中同时设置新旧两套字段,避免破坏已有单元测试。**
---
### Task 2: 更新 getLogStat 实现
**Files:**
- Modify: `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/accountProcess/AccountingAdjustLogProcessServiceImpl.java:95-107`
- Test: `sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/accountingadjust/accountProcess/AccountingAdjustLogProcessServiceImplTest.java`
- [ ] **Step 1: 替换 getLogStat 方法体**
`AccountingAdjustLogProcessServiceImpl.java``getLogStat` 方法(当前约 12 行)替换为:
```java
@Override
public AccountingAdjustLogStatRespVO getLogStat(AccountingAdjustLogPageReqVO reqVO) {
List<LegacyLogRecord> records = loadRecords(reqVO);
AccountingAdjustLogStatRespVO respVO = new AccountingAdjustLogStatRespVO();
// 新字段:前端直接使用
respVO.setTotalActionAmount(records.stream()
.map(record -> nvl(record.actionAmount))
.reduce(BigDecimal.ZERO, BigDecimal::add));
respVO.setTotalCount((long) records.size());
respVO.setSuccessCount(records.stream()
.filter(record -> "SUCCESS".equals(record.resultStatus)).count());
respVO.setFailCount(records.stream()
.filter(record -> "FAIL".equals(record.resultStatus)).count());
respVO.setPendingApprovalCount(records.stream()
.filter(record -> "PENDING_APPROVAL".equals(record.approvalStatus)).count());
respVO.setApprovalRequiredCount(records.stream()
.filter(record -> Boolean.TRUE.equals(record.approvalRequired)).count());
respVO.setApprovedCount(records.stream()
.filter(record -> "APPROVED".equals(record.approvalStatus)).count());
respVO.setRejectedCount(records.stream()
.filter(record -> "REJECTED".equals(record.approvalStatus)).count());
// 旧字段兼容(供已有单元测试和未迁移调用方使用)
respVO.setTotalAmount(respVO.getTotalActionAmount());
respVO.setTotalWaterVolume(records.stream()
.map(record -> nvl(record.waterVolumeChange))
.reduce(BigDecimal.ZERO, BigDecimal::add));
respVO.setCompletedCount(respVO.getSuccessCount());
respVO.setPendingCount(respVO.getPendingApprovalCount());
respVO.setCancelledCount(respVO.getFailCount() + respVO.getRejectedCount());
return respVO;
}
```
**注意:`LegacyLogRecord``actionAmount``resultStatus``approvalStatus``approvalRequired``waterVolumeChange` 字段已在 Service 内部类中定义,无需额外 import。**
- [ ] **Step 2: 编译验证**
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend
mvn compile -pl sw-business/sw-business-server -am -q 2>&1 | tail -10
```
预期:`BUILD SUCCESS`
---
### Task 3: 更新单元测试
**Files:**
- Modify: `sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/accountingadjust/accountProcess/AccountingAdjustLogProcessServiceImplTest.java`
- [ ] **Step 1: 在已有测试中添加新字段断言**
`getLogDetailAndStat_shouldReturnMergedDetailsAndCounts` 测试方法末尾(`assertEquals(new BigDecimal("5.00"), stat.getTotalAmount());` 之后),追加新字段断言:
```java
// 新统计字段断言
assertEquals(new BigDecimal("5.00"), stat.getTotalActionAmount());
assertEquals(1L, stat.getSuccessCount());
assertEquals(0L, stat.getFailCount());
assertEquals(1L, stat.getPendingApprovalCount());
assertEquals(1L, stat.getApprovalRequiredCount());
assertEquals(0L, stat.getApprovedCount());
assertEquals(0L, stat.getRejectedCount());
// 保持旧字段兼容断言
assertEquals(new BigDecimal("5.00"), stat.getTotalAmount());
assertEquals(1L, stat.getCompletedCount());
assertEquals(1L, stat.getPendingCount());
assertEquals(0L, stat.getCancelledCount());
```
- [ ] **Step 2: 运行测试验证**
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend
mvn test -pl sw-business/sw-business-server -Dtest=AccountingAdjustLogProcessServiceImplTest -q 2>&1 | tail -15
```
预期所有测试通过4 个 test case含新增断言
---
### Task 4: 全量编译 + 提交
- [ ] **Step 1: 全量编译**
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend
mvn compile -pl sw-business/sw-business-server -am -q 2>&1 | tail -10
```
- [ ] **Step 2: 提交**
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend
git add sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/vo/AccountingAdjustLogStatRespVO.java
git add sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/accountProcess/AccountingAdjustLogProcessServiceImpl.java
git add sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/accountingadjust/accountProcess/AccountingAdjustLogProcessServiceImplTest.java
git commit -m "fix: align log-stat response fields with frontend AccountingAdjustStatRespVO
- Add totalActionAmount, successCount, failCount, pendingApprovalCount,
approvalRequiredCount, approvedCount, rejectedCount to StatRespVO
- Update getLogStat() to compute new fields from loaded records
- Keep legacy fields (totalAmount, completedCount, pendingCount,
cancelledCount) as compatibility aliases
- Update unit test with new field assertions"
```
---
## Self-Review
**1. Spec coverage:** 统计字段对齐是唯一的后端缺口Task 1-3 完整覆盖。
**2. Placeholder scan:** 无 TBD/TODO/占位符,所有步骤都有具体代码和命令。
**3. Type consistency:**
- `LegacyLogRecord` 内部类字段 `actionAmount`BigDecimal`resultStatus`String`approvalStatus`String`approvalRequired`Boolean`waterVolumeChange`BigDecimal均在 ServiceImpl 中定义,与 `getLogStat` 新实现一致。
- `AccountingAdjustLogStatRespVO` 新旧字段并存,旧测试 `getTotalAmount()` 等调用不受影响。
- 前端 `AccountingAdjustStatRespVO` 的 8 个字段名与新增 VO 字段名完全一致(驼峰命名)。

View File

@ -0,0 +1,203 @@
# 账务日志功能按钮 + 呆坏账恢复 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 修复账务日志页功能按钮(撤销/退转款/预转存/呆坏账恢复)根据数据状态正确显示,不再硬编码 `false`;新增呆坏账恢复入口。
**Architecture:** 后端 `toPageResp()` 当前将 `canRevoke`/`canRefund`/`canPrestore` 硬编码为 `false``canRecoverBadDebt` 字段不存在。修复后根据 `LegacyLogRecord``objectType` + `resultStatus` + `approvalStatus` 动态判定;前端补 API 调用和按钮。
**Tech Stack:** Java 17 + Spring Boot (backend), Vue 3 + TypeScript (frontend)
---
## canXxx 判定规则
`toPageResp(LegacyLogRecord record)` 中按以下规则赋值:
| 字段 | 条件 | 说明 |
|---|---|---|
| `canRevoke` | `approvalStatus == "PENDING_APPROVAL"` | 待审批状态可撤销 |
| `canRefund` | `objectType == "PREPAID_REFUND" && resultStatus == "SUCCESS"` | 退转款成功后允许二次退款 |
| `canPrestore` | `objectType == "PREPAID_REFUND" && resultStatus == "SUCCESS"` | 退转款成功后允许转预存 |
| `canRecoverBadDebt` | `objectType == "BAD_DEBT_RECORD" && resultStatus == "SUCCESS"` | 坏账已执行可恢复为欠费 |
---
### Task 1: 后端 VO 加 canRecoverBadDebt
**Files:**
- Modify: `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/vo/AccountingAdjustLogPageRespVO.java`
- [ ] **Step 1: 在 VO 末尾 canPrestore 后添加字段**
```java
@Schema(description = "是否可恢复呆坏账")
private Boolean canRecoverBadDebt;
```
添加位置:紧跟 `private Boolean canPrestore;` 之后。
---
### Task 2: 后端 Service 修正 canXxx 赋值
**Files:**
- Modify: `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/accountProcess/AccountingAdjustLogProcessServiceImpl.java:520-522`
- [ ] **Step 1: 替换硬编码 false 为动态判定**
`toPageResp()` 中的三行:
```java
respVO.setCanRevoke(false);
respVO.setCanRefund(false);
respVO.setCanPrestore(false);
```
替换为:
```java
// 动态判定功能按钮可见性
respVO.setCanRevoke("PENDING_APPROVAL".equals(record.approvalStatus));
boolean refundEnabled = "PREPAID_REFUND".equals(record.objectType) && "SUCCESS".equals(record.resultStatus);
respVO.setCanRefund(refundEnabled);
respVO.setCanPrestore(refundEnabled);
respVO.setCanRecoverBadDebt("BAD_DEBT_RECORD".equals(record.objectType) && "SUCCESS".equals(record.resultStatus));
```
**同时需要更新 `AccountingAdjustLogPageReqVO``copyReq` 方法:** 无需改动canXxx 是响应字段。
---
### Task 3: 前端 API 加 recoverBadDebt
**Files:**
- Modify: `src/api/accountProcess/accountLog/index.ts`
- [ ] **Step 1: 添加接口类型和 API 方法**
`AccountingAdjustPageRespVO` 接口中添加 `canRecoverBadDebt` 字段,并在 `AccountLogApi` 对象中添加 `recoverBadDebt` 方法:
```typescript
// 在 AccountingAdjustPageRespVO 接口中添加
canRecoverBadDebt?: boolean
```
```typescript
// 在 AccountLogApi 对象中添加(放在 revokeAccountLog 之前)
// 账务日志呆坏账恢复
recoverBadDebt: async (data: { adjustmentNo: string }) => {
return await request.post<AccountingAdjustRespVO>({
url: '/business/accounting-adjust/log-recover-bad-debt',
data
})
},
```
---
### Task 4: 前端页面加呆坏账恢复按钮
**Files:**
- Modify: `src/views/accountProcess/accountLog/index.vue`
- [ ] **Step 1: 在 function 模板中添加按钮**
在现有三个按钮之后、`</div>` 之前添加:
```html
<el-button
v-if="scope.row.canRecoverBadDebt"
link
type="primary"
@click="handleRecoverBadDebt(scope.row)"
>
<el-tooltip effect="dark" content="恢复呆坏账" placement="top">
<Icon icon="ep:refresh-left" :size="18" />
</el-tooltip>
</el-button>
```
- [ ] **Step 2: 在 script 中添加处理函数**
`handlePrestore` 之后添加:
```typescript
const handleRecoverBadDebt = async (row: any) => {
const adjustmentNo = row?.adjustmentNo
if (!adjustmentNo) {
message.warning('缺少调整单号,无法恢复')
return
}
try {
await message.confirm(`确认要将呆坏账调整单「${adjustmentNo}」恢复为欠费状态吗?`)
await AccountLogApi.recoverBadDebt({ adjustmentNo })
message.success('呆坏账已恢复为欠费')
await getList()
} catch (e: any) {
if (e !== 'cancel' && e !== 'close') {
message.error('恢复失败,请稍后重试')
}
}
}
```
---
### Task 5: 编译验证 + 提交
- [ ] **Step 1: 后端编译 + 测试**
```bash
cd /Volumes/Dpan/github/water-workspace/water-backend
mvn test -pl sw-business/sw-business-server -Dtest=AccountingAdjustLogProcessServiceImplTest -DfailIfNoTests=false 2>&1 | grep -E "Tests run|BUILD"
```
预期Tests run: 4, Failures: 0, BUILD SUCCESS
- [ ] **Step 2: 前端 TypeScript 编译**
```bash
cd /Volumes/Dpan/github/water-workspace/water-frontend
npx vue-tsc --noEmit 2>&1 | grep -i "error\|accountLog" | head -10
```
预期:无类型错误。
- [ ] **Step 3: 提交**
```bash
# 后端
cd /Volumes/Dpan/github/water-workspace/water-backend
git add sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/vo/AccountingAdjustLogPageRespVO.java
git add sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/accountProcess/AccountingAdjustLogProcessServiceImpl.java
git commit -m "feat: add canRecoverBadDebt and fix canRevoke/canRefund/canPrestore logic
- Add canRecoverBadDebt field to AccountingAdjustLogPageRespVO
- Replace hardcoded false with dynamic logic in toPageResp():
canRevoke = approvalStatus is PENDING_APPROVAL
canRefund/canPrestore = PREPAID_REFUND + SUCCESS
canRecoverBadDebt = BAD_DEBT_RECORD + SUCCESS"
# 前端
cd /Volumes/Dpan/github/water-workspace/water-frontend
git add src/api/accountProcess/accountLog/index.ts src/views/accountProcess/accountLog/index.vue
git commit -m "feat: add bad debt recovery button and API to account log page
- Add recoverBadDebt API method calling POST log-recover-bad-debt
- Add canRecoverBadDebt field to AccountingAdjustPageRespVO
- Add recovery button with confirm dialog in function column"
```
---
## Self-Review
**1. Spec coverage:** 4 个功能按钮全部覆盖canRevoke/canRefund/canPrestore 修正 + canRecoverBadDebt 新增。
**2. Placeholder scan:** 无占位符,所有步骤含具体代码。
**3. Type consistency:**
- `record.approvalStatus``record.resultStatus``record.objectType` 均为 `LegacyLogRecord` 内部类字段,已存在于 ServiceImpl
- 前端 `canRecoverBadDebt` 字段同时加入 `AccountingAdjustPageRespVO` 接口和 API 调用
- 后端 `recoverBadDebt` API 接收 `{ adjustmentNo: string }`,与 `AdjustExecuteReqDTO` 匹配

View File

@ -0,0 +1,278 @@
# 账务日志前端参数映射修正 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 修正账务日志页 `buildQueryParams()` 发送给后端的参数名与 `AccountingAdjustLogPageReqVO` 不匹配的问题,使表单筛选真正生效;同步更新前端 `AccountLogPageReqVO` 类型定义。
**Architecture:** 前端 `buildQueryParams()` 当前将表单字段映射为 `approvalStatus`/`objectType`/`resultStatus` 等前端旧命名,后端 `AccountingAdjustLogPageReqVO` 期望 `status`/`accountType`/`processMethod` 等字段名,且缺少 `deptId`/`custCode`/`createTime` 等关键字段。修正后筛选条件可正常下发,统计面板因后端已修复可直接显示。
**Tech Stack:** Vue 3 + TypeScript, Element Plus, Axios
---
## 文件结构
| 文件 | 职责 |
|---|---|
| `src/api/accountProcess/accountLog/index.ts` | API 类型定义,修正 `AccountLogPageReqVO` |
| `src/views/accountProcess/accountLog/index.vue` | 页面组件,修正 `buildQueryParams()` 映射 |
---
## 参数映射对照表
表单 `queryParams` → 后端期望字段 → 当前错误映射:
| 表单字段 | queryParams key | 后端字段 | 当前前端发送 | 问题 |
|---|---|---|---|---|
| 营业站点 | `deptId` | `deptId` | ❌ 未发送 | 缺失 |
| 客户编号 | `custCode` | `custCode` | ❌ 未发送 | 缺失 |
| 状态 | `status` | `status` | `approvalStatus` | 名称错误 |
| 账务年月 | `accountMonth` | `accountMonth` (List) | `accountMonthStart`/`End` | 缺少 `accountMonth` |
| 账务类型 | `accountType` | `accountType` | `objectType` | 名称错误 |
| 处理方式 | `processMethod` | `processMethod` | `resultStatus` + `writeBackStatus` | 名称错误+重复 |
| 创建时间 | `createTime` | `createTime` | ❌ 未发送 | 缺失 |
| 处理人 | `handler` | `handler` | ❌ 未发送 | 缺失 |
| 处理时间 | `handleTime` | `handleTime` | `operationTime` | 名称错误 |
| 目标户号 | `targetCustCode` | `targetCustCode` | ❌ 未发送 | 缺失 |
| 缴费日期 | `paymentDate` | `paymentDate` | ❌ 未发送 | 缺失 |
| 册本编号 | `bookCode` | `bookCode` | ❌ 未发送 | 缺失 |
---
### Task 1: 修正 AccountLogPageReqVO 类型定义
**Files:**
- Modify: `src/api/accountProcess/accountLog/index.ts`
- [ ] **Step 1: 替换类型定义**
`AccountLogPageReqVO` 接口替换为与后端 `AccountingAdjustLogPageReqVO` 对齐的版本:
```typescript
export interface AccountLogPageReqVO {
/**
* 调整单号
*/
adjustmentNo?: string
/**
* 营业站点 ID
*/
deptId?: number
/**
* 客户编号
*/
custCode?: string
/**
* 页面状态兼容前端1-正常2-已撤销)
*/
status?: string
/**
* 账务年月范围(兼容前端 monthrange
*/
accountMonth?: string[]
/**
* 账务年月开始,格式 YYYYMM
*/
accountMonthStart?: number
/**
* 账务年月结束,格式 YYYYMM
*/
accountMonthEnd?: number
/**
* 账务类型
*/
accountType?: string
/**
* 处理方式
*/
processMethod?: string
/**
* 创建时间范围(兼容前端 daterange
*/
createTime?: string[]
/**
* 创建时间开始
*/
createTimeStart?: string
/**
* 创建时间结束
*/
createTimeEnd?: string
/**
* 处理人
*/
handler?: string
/**
* 处理时间范围(兼容前端 daterange
*/
handleTime?: string[]
/**
* 处理时间开始
*/
handleTimeStart?: string
/**
* 处理时间结束
*/
handleTimeEnd?: string
/**
* 目标户号
*/
targetCustCode?: string
/**
* 缴费日期范围(兼容前端 daterange
*/
paymentDate?: string[]
/**
* 缴费日期开始
*/
paymentDateStart?: string
/**
* 缴费日期结束
*/
paymentDateEnd?: string
/**
* 册本编号
*/
bookCode?: string
/**
* 页码,从 1 开始
*/
pageNo: number
/**
* 每页条数,最大值为 100
*/
pageSize: number
[property: string]: any
}
```
**设计要点:同时保留 `accountMonth`(数组)和 `accountMonthStart/accountMonthEnd`(数值),后端 `AccountingAdjustLogPageReqVO` 的 getter 方法会优先使用后者,但发送数组可保证 Spring 的日期格式兼容。**
---
### Task 2: 重写 buildQueryParams() 参数映射
**Files:**
- Modify: `src/views/accountProcess/accountLog/index.vue` (仅 `buildQueryParams` 函数)
- [ ] **Step 1: 替换 buildQueryParams 函数**
将当前的 `buildQueryParams`(约 15 行)替换为:
```typescript
const buildQueryParams = (): AccountLogPageReqVO => {
const [accountMonthStartRaw, accountMonthEndRaw] = queryParams.accountMonth || []
const [createTimeStartRaw, createTimeEndRaw] = queryParams.createTime || []
const [handleTimeStartRaw, handleTimeEndRaw] = queryParams.handleTime || []
const [paymentDateStartRaw, paymentDateEndRaw] = queryParams.paymentDate || []
return {
pageNo: queryParams.pageNo,
pageSize: queryParams.pageSize,
// 基础字段
deptId: queryParams.deptId ?? undefined,
custCode: queryParams.custCode || undefined,
// 状态:前端 select 值为 "1"/"2",直接透传给后端 status
status: queryParams.status || undefined,
// 账务年月:同时发数组(后端 getAccountMonth() 会解析)+ 数值(后端优先取用)
accountMonth: queryParams.accountMonth?.length ? queryParams.accountMonth : undefined,
accountMonthStart: toMonthNumber(accountMonthStartRaw),
accountMonthEnd: toMonthNumber(accountMonthEndRaw),
// 账务类型
accountType: queryParams.accountType || undefined,
// 处理方式
processMethod: queryParams.processMethod || undefined,
// 创建时间
createTime: queryParams.createTime?.length ? queryParams.createTime : undefined,
createTimeStart: createTimeStartRaw || undefined,
createTimeEnd: createTimeEndRaw || undefined,
// 处理人
handler: queryParams.handler || undefined,
// 处理时间
handleTime: queryParams.handleTime?.length ? queryParams.handleTime : undefined,
handleTimeStart: handleTimeStartRaw || undefined,
handleTimeEnd: handleTimeEndRaw || undefined,
// 目标户号
targetCustCode: queryParams.targetCustCode || undefined,
// 缴费日期
paymentDate: queryParams.paymentDate?.length ? queryParams.paymentDate : undefined,
paymentDateStart: paymentDateStartRaw || undefined,
paymentDateEnd: paymentDateEndRaw || undefined,
// 册本编号
bookCode: queryParams.bookCode || undefined
}
}
```
**关键修正点:**
- `approvalStatus``status`
- `objectType``accountType`
- `resultStatus` / `writeBackStatus`(重复) → `processMethod`
- `operationTime``handleTime`
- 新增:`deptId``custCode``createTime``handler``targetCustCode``paymentDate``bookCode`
- 时间字段同时发送数组形式(`createTime: ['2025-01-01','2026-01-29']`)和拆解形式(`createTimeStart`/`createTimeEnd`),后端 `normalizeQuery` 和 getter 均能处理
- [ ] **Step 2: 验证 TypeScript 编译**
```bash
cd /Volumes/Dpan/github/water-workspace/water-frontend
npx vue-tsc --noEmit src/views/accountProcess/accountLog/index.vue 2>&1 | head -20
```
预期:无类型错误。
---
### Task 3: 端到端验证
- [ ] **Step 1: 启动前端 dev server 验证页面**
```bash
cd /Volumes/Dpan/github/water-workspace/water-frontend
npx vite --port 5173 &
sleep 3
echo "Dev server running at http://localhost:5173"
```
- [ ] **Step 2: 验证统计面板**
打开账务日志页面,确认统计值不再是全 0
- 金额汇总显示格式化货币值(如 `¥12,345.00`
- 各笔数按颜色区分(成功=绿、失败=红、待审批=橙、通过=蓝)
- [ ] **Step 3: 验证筛选功能**
分别测试以下筛选条件确认数据变化:
- 切换"营业站点"下拉
- 输入"客户编号"查询
- 选择"状态"为"已撤销"
- [ ] **Step 4: 提交**
```bash
cd /Volumes/Dpan/github/water-workspace/water-frontend
git add src/api/accountProcess/accountLog/index.ts src/views/accountProcess/accountLog/index.vue
git commit -m "fix: align account log query params with backend AccountingAdjustLogPageReqVO
- Rewrite AccountLogPageReqVO interface to match backend field names
(status, accountType, processMethod, handleTime, etc.)
- Add missing fields: deptId, custCode, createTime, handler,
targetCustCode, paymentDate, bookCode
- Fix buildQueryParams() to send correct parameter names
- Send both array and decomposed date formats for backend compatibility"
```
---
## Self-Review
**1. Spec coverage:** 所有 12 个表单字段的映射错误均已覆盖TypeScript 类型定义同步更新。
**2. Placeholder scan:** 无 TBD/TODO所有步骤含具体代码和命令。
**3. Type consistency:**
- `buildQueryParams` 返回 `AccountLogPageReqVO`,所有字段名与 Task 1 中定义一致
- 时间字段同时发送数组形式(`createTime: string[]`)和拆解形式(`createTimeStart: string`),匹配后端 VO 的 getter 逻辑
- `deptId` 使用 `?? undefined` 处理 0 值(根站点),避免被 `||` 误判为 falsy
- 其他字符串字段使用 `|| undefined` 将空字符串转为 undefined

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,168 @@
# 营收明确缺陷第一批修复设计
日期2026-06-08
## 背景
本设计用于收敛当前营收缺陷中代码证据最明确、可闭环验收的一批问题。前期排查表明,部分缺陷并非单纯后端写库失败,而是前后端接口契约、页面状态文案、查询语义和锁恢复流程不一致导致用户认为“成功但未生效”或“查不到记录”。
本轮不改变账务调整审批规则,不把待审批动作改成即时落账;目标是让系统行为与用户提示一致,并补齐明确漏查和重试闭环。
## 修复范围
本轮纳入以下缺陷:
- `#78` 水价调整:执行报错后,用户不应只能关闭菜单从头开始。
- `#39` 柜台结账:柜台预存缴费记录应能进入待结清并完成结账。
- `#50` 柜台结账:收费员筛选不应被后端无条件覆盖为当前登录用户。
- `#53` 柜台收费:预存抵扣金额需要前后端契约保护和回归验证。
- `#58/#59` 红冲记录:柜台红冲后应能在红冲记录页按红冲时间查到。
- `#69/#76` 未销分账、呆坏账:前端应准确表达“申请已提交,待审批/待回写”,不得提示为已生效。
以下缺陷不进入本轮实现,仅保留后续复现或产品确认:
- `#70` 未销调整提示成功但账单未变:现有后端金额/水量调整路径会写回,需要先抓请求体确认字段和值。
- `#9` 抄表状态修改无影响:需要产品确认状态配置影响已生成任务还是仅影响后续任务。
## 设计原则
1. 保持最小闭环,不重构完整账务调整体系。
2. 保留现有审批流语义,修正文案和状态展示,不把待审批改为立即落账。
3. 查询接口按业务语义返回数据,页面筛选字段与后端字段一致。
4. 后端对关键金额和状态做兜底校验,前端负责交互提示和用户确认。
5. 所有修改需要有最小单测、前端合约测试或页面 smoke 覆盖。
## 后端设计
### 水价调整锁恢复
`PriceTemplateServiceImpl.updatePriceTemplate()` 继续保持执行完成后释放调价锁的总体策略,但失败路径需要让前端能够明确恢复:
- 后端异常响应保持业务错误信息可读。
- 前端失败后重新进入 `startAdjustment()` 流程获取新锁。
- `PriceTemplateAdjustmentLockRedisDAO.refreshLock()` 不再采用 `forceUnlock()` 后重新 `tryLock()` 的刷新模式,改为安全续期,避免刷新瞬间锁被其他用户抢占。
### 柜台预存缴费进入结账
`PaymentRecordMapper.selectCounterUnsettledRecords()` 从仅查询 `CHARGE_PAYMENT` 扩展为同时包含 `DEPOSIT_TOPUP`
- `CHARGE_PAYMENT` 表示账单收费记录。
- `DEPOSIT_TOPUP` 表示柜台预存充值记录。
`CounterSettleApplicationServiceImpl.confirm()` 需要支持预存充值记录无营业账 ID
- 结账总金额仍以 `PaymentRecord.paymentAmount` 汇总。
- 写入结账明细时,`chargeId``billMonth` 可为空。
- `chargeMapper.markCounterSettled()` 只处理真实账单收费记录对应的 `chargeIds`
### 收费员筛选
柜台结账查询的收费员解析规则改为:
- 请求 `cashierId` 为空时,默认使用当前登录用户。
- 请求 `cashierId` 非空时,使用请求值查询。
- 如后续接入完整数据权限,可在此基础上限制普通用户只能查自己、管理员可查指定收费员。本轮先不让后端无条件覆盖前端筛选值。
### 预存抵扣校验
柜台收费继续由前端传入 `prepayDeductAmount`
- 后端校验抵扣金额不得小于 0。
- 抵扣金额不得超过账单应收金额。
- 抵扣金额不得超过账户预存余额。
- 抵扣金额大于 0 时扣减账户余额,并在支付记录 `extJson` 中保留 `prepayDeductAmount`
本轮不启用后端自动计算抵扣金额,避免改变用户输入语义。
### 柜台红冲记录查询
红冲记录页应基于柜台结账红冲链路展示,而不是复用账务调整日志:
- 查询来源为 `settle_record``settle_record_detail``payment_record` 的柜台红冲结果。
- 默认包含部分红冲和全部红冲状态。
- 日期筛选使用红冲时间 `reversedTime`,而不是账务日志的创建时间或处理时间。
- 返回字段至少包含结账单号、收费员、客户、红冲金额、红冲时间、红冲原因。
## 前端设计
### 水价调整失败恢复
水价调整提交失败后,前端不再只把页面退出调整态。推荐交互:
1. 显示调价失败原因。
2. 自动调用 `startAdjustment()` 尝试重新获取锁。
3. 重新获取成功时保留当前页面数据,提示用户可继续修正后提交。
4. 重新获取失败时提示锁被占用或已过期,并引导用户重新开始调价。
### 待审批动作状态文案
未销分账、呆坏账、价差调整、违约金减免等提交后,根据响应状态显示文案:
- `approvalRequired=true``resultStatus=PENDING_APPROVAL`:显示“申请已提交,待审批”。
- `writeBackStatus=PENDING`:显示“待回写”或“待执行”,不刷新为已生效。
- `resultStatus=SUCCESS``writeBackStatus=UPDATED`:显示“处理完成”。
页面不得仅用“提交成功”表达所有结果。
### 柜台结账页面
未结账列表需要正常展示预存充值记录:
- 无 `chargeId``billMonth` 时显示 `--`
- 客户、收费员、金额、缴费时间正常展示。
- 结账确认汇总包含预存充值金额。
收费员筛选继续保留,前端传入的 `cashierId` 应与后端查询语义一致。
### 红冲记录页面
红冲记录页面改为柜台红冲记录视图:
- 查询接口改为柜台红冲记录接口。
- 日期筛选标签改为“红冲时间”。
- 参数使用 `beginReversedTime``endReversedTime`
- 列表展示柜台红冲相关字段,不再按账务调整日志字段组织。
### 预存抵扣交互
柜台收费页面保留当前按选中账单分摊 `prepayDeductAmount` 的逻辑,并补充交互保护:
- 抵扣金额不得超过预存余额。
- 抵扣金额不得超过选中账单应收合计。
- 用户开启预存抵扣但计算抵扣为 0 时,提交前给出明确提示或确认。
## 测试设计
### 后端测试
- 水价调整:覆盖失败后前端可重新开始调价;覆盖刷新锁不释放抢占。
- 柜台结账:覆盖 `CHARGE_PAYMENT``DEPOSIT_TOPUP` 都进入未结账;覆盖预存记录结账时不要求 `chargeId`
- 收费员筛选:覆盖请求 `cashierId` 非空时后端按请求值查询。
- 预存抵扣:覆盖正常抵扣、余额不足、非法金额。
- 红冲记录:覆盖红冲状态默认可查,按 `reversedTime` 范围筛选。
### 前端测试
- 水价调整提交失败后重新获取锁或展示重获锁失败提示。
- 分账和呆坏账返回待审批时提示“申请已提交,待审批”。
- 柜台结账未结账列表能展示预存充值记录。
- 红冲记录页调用柜台红冲记录接口,并按红冲时间传参。
- 预存抵扣开启但抵扣金额为 0 时出现确认或提示。
## 验收标准
1. 用户在水价调整报错后无需关闭菜单即可继续修正并重新提交。
2. 柜台预存缴费能在柜台结账待结清列表中查询并完成结账。
3. 柜台结账收费员筛选不再无条件固定为当前登录用户。
4. 柜台红冲成功后,红冲记录页可按红冲时间查询到记录。
5. 分账、呆坏账等待审批动作不再被前端表达为已生效。
6. 预存抵扣金额写入支付记录并扣减账户余额,非法金额有明确错误。
## 实施边界
本设计不包含以下内容:
- 不重构完整 REV004 账务调整状态机。
- 不新增完整 BPM 审批能力。
- 不修改呆坏账、分账从待审批到已执行的业务规则。
- 不处理未复现的 `#70` 和产品规则未确认的 `#9`

View File

@ -0,0 +1,140 @@
# 分账连续阶梯重算设计
> 2026-06-12 | 将按水量分账从"比例均摊"改为"连续阶梯重算"
---
## 一、目标
按水量分账splitRuleType=COUNT的计费方式从比例均摊改为连续阶梯重算实现
- 总水费 = 子账单水费之和,与直接算总水量结果一致
- 第一笔子账单从第一阶梯开始计费,后续子账单接着前一笔的累计水量继续计算
- 原账单的其他费用(污水、垃圾等)暂按比例均摊
---
## 二、改动范围
**后端改动3 个文件):**
| 文件 | 改动 |
|------|------|
| `AccountingAdjustActionServiceImpl` | 重写 `createSplitChildren()` |
| `PriceDiffPreviewService` | 新增 `recalculate()` 重载,接受自定义水量和累计起始量 |
| `PriceTemplateService` + impl | 新增 `getPriceTemplateByCode(snapCode, templateCode)` 重载 |
**不改动的:**
- Controller、VO、数据库
- `cloneChargeDetailForSplit`(费用明细克隆保持现有比例逻辑)
- `createSplitChildrenByFeeComp`(按费用组成分账不动)
---
## 三、数据流
```
applySplitWriteBack()
├── markParentSplit() [不变]
├── createSplitChildren() [改造]
│ ├── getPriceTemplateByCode(snap, code) → 原账单当时的水价模板
│ ├── charge.getIsLadder() → 阶梯开关
│ │
│ ├── 逐笔重算 (accumulated 从 0 开始):
│ │ ├── recalculate(template, water, accumulated, isLadder, true)
│ │ │ → Map<costCode, amount>
│ │ ├── setFees(child, feeMap) → 设各费用字段
│ │ └── accumulated += water
│ │
│ └── 返回 children
└── cloneChargeDetailForSplit() [不变,比例复制]
```
---
## 四、新增方法签名
### 4.1 PriceDiffPreviewService.recalculate 重载
```java
/**
* 用指定水价模板 + 指定水量 + 累计起始量重算各项费用
*
* @param template 水价模板
* @param billWater 本次水量
* @param accumulatedWater 累计起始水量(前面子账单已用的水量)
* @param isLadder 是否阶梯计费
* @param calculateGarbageFee 是否计算垃圾费
* @return costCode → amount
*/
public Map<String, BigDecimal> recalculate(
PriceTemplateDO template,
BigDecimal billWater,
BigDecimal accumulatedWater,
Boolean isLadder,
Boolean calculateGarbageFee
)
```
实现逻辑:从现有 `recalculate(ChargeDO, PriceTemplateDO, Boolean, Boolean)` 提取核心计算段,用水量参数 `accumulatedWater + billWater` 替代 `charge.getBillWater()`,阶梯判断基于累计量而非单次水量。
### 4.2 PriceTemplateService.getPriceTemplateByCode 重载
```java
/**
* 按快照编号和模板代码查询水价模板
* @param snapCode 调价快照编号
* @param templateCode 模板代码
*/
PriceTemplateDO getPriceTemplateByCode(String snapCode, String templateCode);
```
实现:`priceTemplateMapper.selectOne(adjustmentSnapCode, snapCode, code, templateCode)`
---
## 五、createSplitChildren 改造
### 改造前(比例均摊)
```java
List<BigDecimal> ratios = allocateByWaterRatio(billWaters, charge.getBillWater());
List<BigDecimal> waterFees = allocateByRatio(charge.getWaterFee(), ratios, 2);
// ... 所有费用项按同一比例分
```
### 改造后(连续阶梯重算)
```java
PriceTemplateDO template = priceTemplateService.getPriceTemplateByCode(
charge.getAdjustmentSnapCode(), charge.getPriceTemplateCode());
Boolean isLadder = charge.getIsLadder();
BigDecimal accumulated = BigDecimal.ZERO;
for (BigDecimal water : billWaters) {
Map<String, BigDecimal> feeMap = priceDiffPreviewService.recalculate(
template, water, accumulated, isLadder, true);
ChargeDO child = cloneBase(charge, splitAdjustId);
child.setBillWater(water);
setFeesFromMap(child, feeMap); // 水费/污水/垃圾等
children.add(child);
accumulated = accumulated.add(water);
}
```
`setFeesFromMap``PriceDiffWriteBackService.setFeesFromMap` 逻辑一致。
---
## 六、边界条件
| 场景 | 处理 |
|------|------|
| 模板不存在 | 抛出 `invalidParamException("原账单水价模板已失效")` |
| billWaters 之和 ≠ charge.billWater | 不强制校验,允许浮点误差 |
| isLadder 为 null | 默认 true阶梯 |
| adjustmentSnapCode 为 null | 降级到最新快照 |

View File

@ -0,0 +1,155 @@
# REV-004 未销调整预算参数正式化设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 项目名称 | 福建水务营收系统 |
| 模块 | REV-004 账务处理 / 未销调整 |
| 文档类型 | 实现前设计草案 |
| 编写日期 | 2026-06-19 |
| 状态 | 待实现 |
## 背景
未销调整页存在若干只在预算阶段生效的 checkbox 参数,例如水量调整的累计量、底码计算,以及价差调整的阶梯计费、垃圾费计算、累计量更新。当前后端现状是:部分参数参与预算,但没有完整进入正式提交、正式调整对象、审批回写和账单重算链路,导致“预算结果”和“正式入账结果”存在不一致风险。
本设计目标是将预算阶段的 checkbox 语义升级为正式调整参数,保证用户在预算页勾选的计算口径能够被正式提交、审计记录和最终回写一致承接。
## 设计目标
1. 预算参数在正式提交时不丢失。
2. 正式调整对象能够记录预算参数,形成审计轨迹。
3. 审批或自动执行回写时使用与预算一致的计算开关。
4. 未传 checkbox 时保持现有默认行为,避免破坏旧调用方。
5. 只补齐未销调整相关链路,不扩大到已销、预存和其他 REV 场景。
## 非目标
1. 不重构完整 REV-004 账务调整状态机。
2. 不引入新的预算快照 token 机制。
3. 不改变已收费、已开票、已结账等既有边界校验。
4. 不在本轮统一改造前端 UI 布局,仅约束前后端字段语义。
## 推荐方案
采用“最小补链路”方案:沿用现有未销调整接口和正式对象结构,补齐缺失字段和默认值语义。
相比新增统一 `adjustOptions` 或预算快照机制,该方案改动范围较小,能快速解决当前预算与正式回写不一致的问题;后续如果 REV-004 进入全量对象化改造,再考虑统一参数模型。
## 水量调整设计
### 参数
水量预算已有参数:
| 字段 | 含义 | 当前预算行为 | 正式化要求 |
| --- | --- | --- | --- |
| `targetBillWater` | 调整后水量 | 参与费用重算 | 已支持,继续作为正式重算水量 |
| `updateAccumulated` | 是否计算累计量 | 返回调整前后累计量 | 正式提交需承接,并按水量差额更新账单累计量 |
| `updateBaseCode` | 是否计算底码 | 返回调整前后底码 | 正式提交需承接,并配合 `currentReading` 更新本次抄码/底码 |
| `currentReading` | 本次抄码 | 预算阶段不作为 checkbox | 正式提交继续保留,作为底码正式值来源 |
### 正式行为
1. `targetBillWater` 始终参与费用重算。
2. `updateAccumulated=true` 时,正式回写按 `targetBillWater - 原billWater` 调整账单累计量字段。
3. `updateBaseCode=true` 时,正式回写允许更新本次抄码;若未传 `currentReading`,应拒绝提交并提示“更新底码必须传入本次抄码”。
4. 两个 checkbox 未传时默认 `false`,保持旧行为。
5. 费用重算仍使用当前水价模板、费用组成、优惠方案和用水方案,不额外改变计费规则。
### 影响文件
后端需要补齐:
| 层级 | 文件/对象 | 改动 |
| --- | --- | --- |
| API VO | `AccountingAdjustUnsoldAdjustSubmitReqVO` | 增加 `updateAccumulated``updateBaseCode` |
| 统一 DTO | `AccountingAdjustSubmitReqVO` | 增加同名字段 |
| Controller 映射 | `AccountingAdjustActionController` | 将字段映射到统一 DTO |
| 核心调整 VO | `AccountingAdjustReqVO` | 增加同名字段 |
| 过程服务 | `AccountingAdjustProcessServiceImpl` | 将字段传入核心调整 VO |
| 回写服务 | `ChargeServiceImpl` | 水量正式调整时执行累计量/底码更新 |
| 测试 | 相关 unit test | 覆盖字段映射、底码必填、累计量更新 |
## 价差调整设计
### 参数
价差预算已有参数:
| 字段 | 含义 | 当前预算行为 | 正式化要求 |
| --- | --- | --- | --- |
| `isLadder` | 是否阶梯计费 | `false` 时使用第一档单价平铺 | 正式回写必须使用相同值 |
| `calculateGarbageFee` | 是否计算垃圾费 | `false` 时排除费用项 `103` | 正式回写必须使用相同值 |
| `updateAccumulatedVolume` | 是否更新客户累计量 | 提交 VO 已有字段,但正式对象链路不完整 | 正式对象和回写需保存并使用 |
### 正式行为
1. `isLadder=false` 时,正式回写按非阶梯算法生成新账单,不能回退为阶梯算法。
2. `calculateGarbageFee=false` 时,正式回写不生成垃圾费金额。
3. `updateAccumulatedVolume=true` 时,生成新账单后同步累计量相关字段;为 `false` 时不更新累计量。
4. 默认值必须与预算一致:
- `isLadder`:未传时按 `true` 处理。
- `calculateGarbageFee`:未传时按 `true` 处理。
- `updateAccumulatedVolume`:未传时按现有前端默认值处理;后端建议默认 `true`,但需与前端确认。
5. 禁止在正式回写阶段将缺失字段解释为 `false`
### 影响文件
后端需要补齐:
| 层级 | 文件/对象 | 改动 |
| --- | --- | --- |
| 提交 VO | `AccountingAdjustUnsoldPriceDiffSubmitReqVO` | 已有字段,补测试确认不丢失 |
| 统一 DTO | `AccountingAdjustSubmitReqVO` | 已有字段,补映射测试 |
| 核心调整 VO | `AccountingAdjustReqVO` | 已有字段,校正默认值语义 |
| 正式创建 DTO | `PriceDiffAdjustCreateReqDTO` | 增加 `calculateGarbageFee``updateAccumulatedVolume` |
| Internal API | `PriceDiffAdjustInternalApiImpl` | 将字段写入正式化 VO |
| 正式明细 DO/表 | `PriceDiffAdjustDetailDO` / `biz_price_diff_adjust_detail` | 增加或复用可审计字段 |
| 正式化服务 | `PriceDiffFormalizationService` | 保存 `isLadder``calculateGarbageFee``updateAccumulatedVolume` |
| 回写服务 | `PriceDiffWriteBackService` | 使用一致默认值并执行回写 |
| 审批回写 | `AccountingAdjustActionServiceImpl` | 从正式明细或日志明细读取三项参数 |
| 测试 | 相关 unit test | 覆盖预算参数到正式回写的一致性 |
## 数据持久化策略
优先在价差正式明细表增加明确字段:
| 字段建议 | 类型 | 含义 |
| --- | --- | --- |
| `is_ladder` | smallint | 是否阶梯计费 |
| `calculate_garbage_fee` | smallint | 是否计算垃圾费 |
| `update_accumulated_volume` | smallint | 是否更新累计量 |
如短期不便改表,可先将字段写入操作日志明细,但这只能作为过渡方案。正式审计与回写更推荐落入正式对象表。
水量调整当前走 legacy-only 即时回写,没有独立正式对象表;本轮可先通过操作日志明细记录 checkbox 参数,并在 `ChargeServiceImpl` 即时回写中使用。
## 错误处理
1. 水量调整 `updateBaseCode=true``currentReading` 为空时,拒绝提交。
2. checkbox 字段为空时统一按场景默认值处理,不能因空值导致预算与正式回写默认相反。
3. 正式回写读取不到价差参数时,按默认值补齐,并记录兼容说明。
4. 已收费、已开票、已结账等既有拒绝规则不变。
## 测试策略
最小测试集:
1. 水量预算允许 `targetBillWater=0` 且金额为 0。
2. 水量正式提交携带 `updateAccumulated=true` 时,累计量按水量差额更新。
3. 水量正式提交 `updateBaseCode=true` 且缺少 `currentReading` 时拒绝。
4. 价差预算 `calculateGarbageFee=false`,正式回写后新账单垃圾费为 0。
5. 价差预算 `isLadder=false`,正式回写后按第一档单价计算。
6. 价差提交 `updateAccumulatedVolume=false` 时,正式回写不更新累计量。
7. checkbox 未传时,旧测试保持通过。
## 验收标准
1. 预算和正式回写使用同一组 checkbox 参数。
2. 正式调整记录或操作日志可追溯 checkbox 入值。
3. 价差回写不再因字段缺失将 `calculateGarbageFee` 默认成 `false`
4. 水量调整的累计量、底码 checkbox 不再只停留在预算响应。
5. 后端最小验证命令通过,并将验证结果记录到 `docs/evidence/rev004-accounting/`

View File

@ -0,0 +1,453 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.6.6 Chrome/144.0.7559.236 Electron/40.8.4 Safari/537.36" version="29.6.6" pages="3">
<diagram id="zones" name="网络分区图">
<mxGraphModel grid="1" page="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" pageScale="1" pageWidth="1800" pageHeight="1000" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="9ridG9Hz4lSlouddoKVb-23" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="核心区" vertex="1">
<mxGeometry height="600" width="380" x="1060" y="-64" as="geometry" />
</mxCell>
<mxCell id="9ridG9Hz4lSlouddoKVb-22" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#eff6ff;strokeColor=#2563eb;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="办公区" vertex="1">
<mxGeometry height="178" width="200" x="140" y="-68" as="geometry" />
</mxCell>
<mxCell id="z1" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff7ed;strokeColor=#ea580c;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="Untrust&#xa;外网区域" vertex="1">
<mxGeometry height="424" width="200" x="140" y="120" as="geometry" />
</mxCell>
<mxCell id="m1" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.people.standing_man_2;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="移动端用户" vertex="1">
<mxGeometry height="70" width="40" x="230" y="135" as="geometry" />
</mxCell>
<mxCell id="m2" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.computers_and_peripherals.pc;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="第三方系统" vertex="1">
<mxGeometry height="80" width="80" x="210" y="264" as="geometry" />
</mxCell>
<mxCell id="m3" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.buildings.government_building;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="银行系统&#xa;公网/专线" vertex="1">
<mxGeometry height="80" width="80" x="210" y="367" as="geometry" />
</mxCell>
<mxCell id="z2" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fffbeb;strokeColor=#d97706;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="DMZ&#xa;对外服务区" vertex="1">
<mxGeometry height="608" width="280" x="370" y="-68" as="geometry" />
</mxCell>
<mxCell id="nginx" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.servers.www_server;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="Nginx 入口" vertex="1">
<mxGeometry height="82" width="85" x="480" y="123" as="geometry" />
</mxCell>
<mxCell id="ftp" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.servers.fileserver;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="SFTP/FTP&#xa;文件交换服务器" vertex="1">
<mxGeometry height="88" width="70" x="565" y="367" as="geometry" />
</mxCell>
<mxCell id="z3" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#eff6ff;strokeColor=#2563eb;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="服务器区域" vertex="1">
<mxGeometry height="610" width="330" x="670" y="-70" as="geometry" />
</mxCell>
<mxCell id="pc" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.computers_and_peripherals.pc;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="PC 端用户&#xa;办公网段" vertex="1">
<mxGeometry height="92" width="95" x="190" y="-20" as="geometry" />
</mxCell>
<mxCell id="svc" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.misc.hp_mini;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="综合节点&lt;br&gt;中间件&lt;div&gt;数据库存储控制&lt;/div&gt;" vertex="1">
<mxGeometry height="73.38" width="80" x="780" y="345.31" as="geometry" />
</mxCell>
<mxCell id="db1" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.storage.relational_database;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="PostgreSQL 主库" vertex="1">
<mxGeometry height="84" width="118" x="1100" y="330" as="geometry" />
</mxCell>
<mxCell id="db2" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.storage.relational_database;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="PostgreSQL 热备" vertex="1">
<mxGeometry height="84" width="118" x="1100" y="180" as="geometry" />
</mxCell>
<mxCell id="bak" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.servers.fileserver;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="备份归档存储" vertex="1">
<mxGeometry height="84" width="60" x="1360" y="180" as="geometry" />
</mxCell>
<mxCell id="e1" edge="1" parent="1" source="m1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="nginx" value="HTTPS 443">
<mxGeometry relative="1" as="geometry">
<mxPoint x="380" y="145" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="e2" edge="1" parent="1" source="m2" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" target="nginx" value="HTTPS/API">
<mxGeometry relative="1" as="geometry">
<mxPoint x="415" y="171.5" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="e2b" edge="1" parent="1" source="m3" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="ftp" value="公网隧道&lt;span style=&quot;background-color: light-dark(#ffffff, var(--ge-dark-color, #121212)); color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));&quot;&gt;/专线&lt;/span&gt;21/22 银行文件交换">
<mxGeometry relative="1" x="0.1096" y="-3" as="geometry">
<mxPoint as="offset" />
<mxPoint x="380" y="407" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="e3" edge="1" parent="1" source="pc" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="nginx" value="办公网访问,内网 IP">
<mxGeometry relative="1" as="geometry">
<mxPoint x="510" y="104" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="e4" edge="1" parent="1" source="nginx" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;" target="9ridG9Hz4lSlouddoKVb-13" value="API转发">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="560" y="124" />
<mxPoint x="560" y="130" />
<mxPoint x="694" y="130" />
</Array>
<mxPoint x="565" y="165" as="sourcePoint" />
<mxPoint x="817.3199999999999" y="256.38000000000045" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="e8" edge="1" parent="1" source="9ridG9Hz4lSlouddoKVb-13" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="svc" value="业务访问">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e10" edge="1" parent="1" source="9ridG9Hz4lSlouddoKVb-13" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;exitX=0.035;exitY=0.76;exitDx=0;exitDy=0;exitPerimeter=0;" target="ftp" value="21/22 文件交换">
<mxGeometry relative="1" as="geometry">
<mxPoint as="offset" />
<Array as="points">
<mxPoint x="703" y="210" />
<mxPoint x="600" y="210" />
</Array>
<mxPoint x="1010" y="286" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="e11" edge="1" parent="1" source="svc" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="db1" value="5432 数据库访问">
<mxGeometry relative="1" x="-0.0013" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="e12" edge="1" parent="1" source="svc" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;dashed=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" target="db2" value="5432 状态探测">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="980" y="350" />
<mxPoint x="980" y="222" />
</Array>
<mxPoint x="1160" y="446" as="sourcePoint" />
<mxPoint x="1339" y="104.00000000000023" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="e13" edge="1" parent="1" source="db1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;" target="db2" value="主备同步">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e14" edge="1" parent="1" source="db1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="bak" value="备份/WAL">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e15" edge="1" parent="1" source="db2" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="bak" value="备份副本">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e16" edge="1" parent="1" source="svc" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;exitX=0.9;exitY=0.9;exitDx=0;exitDy=0;exitPerimeter=0;" target="bak" value="文件归档">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="852" y="440" />
<mxPoint x="1420" y="440" />
</Array>
<mxPoint x="1130" y="468.04761904761926" as="sourcePoint" />
<mxPoint x="1487.0476190476188" y="371.99999999999955" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="HN1-GFPAo3kp1Sdrea_3-7" edge="1" parent="1" source="z1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" target="z1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="app1" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.misc.hp_mini;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="业务应用节点 1&#xa;Spring Boot Gateway&#xa;业务服务" vertex="1">
<mxGeometry height="73" width="90" x="775" y="144" as="geometry" />
</mxCell>
<mxCell id="app2" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.misc.hp_mini;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="业务应用节点 2&#xa;Spring Boot Gateway&#xa;业务服务" vertex="1">
<mxGeometry height="72" width="90" x="777.5" y="-5" as="geometry" />
</mxCell>
<mxCell id="9ridG9Hz4lSlouddoKVb-13" parent="1" style="rounded=0;whiteSpace=wrap;html=1;dashed=1;sketch=0;pointerEvents=1;strokeColor=#99CCFF;strokeWidth=2;align=center;verticalAlign=top;fontFamily=Helvetica;fontSize=12;fontColor=default;fillColor=none;perimeterSpacing=6;" value="" vertex="1">
<mxGeometry height="305" width="240" x="701.5" y="-22" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="links" name="网络连接图">
<mxGraphModel dx="1129" dy="742" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1800" pageHeight="1000" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="lz1" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff7ed;strokeColor=#ea580c;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="Untrust&#xa;外网区域" vertex="1">
<mxGeometry height="220" width="260" x="30" y="90" as="geometry" />
</mxCell>
<mxCell id="lm1" parent="1" style="shape=mxgraph.cisco.people.man;html=1;fillColor=#ffffff;strokeColor=#ea580c;" value="移动端用户" vertex="1">
<mxGeometry height="80" width="80" x="60" y="145" as="geometry" />
</mxCell>
<mxCell id="lm2" parent="1" style="shape=mxgraph.cisco.misc.generic_building;html=1;fillColor=#ffffff;strokeColor=#ea580c;" value="第三方系统" vertex="1">
<mxGeometry height="80" width="80" x="160" y="145" as="geometry" />
</mxCell>
<mxCell id="lm3" parent="1" style="shape=mxgraph.cisco.misc.generic_building;html=1;fillColor=#ffffff;strokeColor=#ea580c;" value="银行系统&#xa;公网/专线" vertex="1">
<mxGeometry height="80" width="110" x="95" y="230" as="geometry" />
</mxCell>
<mxCell id="ldmz" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fffbeb;strokeColor=#d97706;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="DMZ&#xa;对外服务区" vertex="1">
<mxGeometry height="250" width="300" x="340" y="70" as="geometry" />
</mxCell>
<mxCell id="lnginx" parent="1" style="shape=mxgraph.cisco.servers.server;html=1;fillColor=#ffffff;strokeColor=#d97706;" value="内网 Nginx 入口节点" vertex="1">
<mxGeometry height="82" width="105" x="435" y="118" as="geometry" />
</mxCell>
<mxCell id="lftp" parent="1" style="shape=mxgraph.cisco.servers.server;html=1;fillColor=#fff1f2;strokeColor=#dc2626;strokeWidth=2;" value="SFTP/FTP&#xa;文件交换服务器" vertex="1">
<mxGeometry height="88" width="145" x="415" y="220" as="geometry" />
</mxCell>
<mxCell id="llan" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#eff6ff;strokeColor=#2563eb;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="LAN&#xa;办公与应用区" vertex="1">
<mxGeometry height="360" width="560" x="690" y="50" as="geometry" />
</mxCell>
<mxCell id="lpc" parent="1" style="shape=mxgraph.cisco.people.man;html=1;fillColor=#ffffff;strokeColor=#2563eb;" value="PC 端用户&#xa;办公网段" vertex="1">
<mxGeometry height="92" width="95" x="720" y="108" as="geometry" />
</mxCell>
<mxCell id="lsw" parent="1" style="shape=mxgraph.cisco.switches.workgroup_switch;html=1;fillColor=#ffffff;strokeColor=#2563eb;" value="应用区交换" vertex="1">
<mxGeometry height="74" width="104" x="870" y="130" as="geometry" />
</mxCell>
<mxCell id="lapp1" parent="1" style="shape=mxgraph.cisco.servers.server;html=1;fillColor=#ffffff;strokeColor=#2563eb;" value="业务应用节点 1&#xa;Spring Boot Gateway&#xa;业务服务" vertex="1">
<mxGeometry height="96" width="120" x="1010" y="102" as="geometry" />
</mxCell>
<mxCell id="lapp2" parent="1" style="shape=mxgraph.cisco.servers.server;html=1;fillColor=#ffffff;strokeColor=#2563eb;" value="业务应用节点 2&#xa;Spring Boot Gateway&#xa;业务服务" vertex="1">
<mxGeometry height="96" width="120" x="1010" y="228" as="geometry" />
</mxCell>
<mxCell id="lsvc" parent="1" style="shape=mxgraph.cisco.servers.server;html=1;fillColor=#f8f4ff;strokeColor=#7c3aed;strokeWidth=2;" value="综合节点&#xa;缓存: Redis&#xa;配置: Nacos&#xa;对象存储: MinIO&#xa;数据库控制: HAProxy / PgBouncer / Patroni" vertex="1">
<mxGeometry height="134" width="200" x="800" y="245" as="geometry" />
</mxCell>
<mxCell id="lcore" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ecfdf5;strokeColor=#059669;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="Core&#xa;核心数据区" vertex="1">
<mxGeometry height="280" width="470" x="1300" y="90" as="geometry" />
</mxCell>
<mxCell id="ldb1" parent="1" style="shape=mxgraph.cisco.servers.database;html=1;fillColor=#ffffff;strokeColor=#059669;" value="PostgreSQL 主库" vertex="1">
<mxGeometry height="84" width="118" x="1355" y="145" as="geometry" />
</mxCell>
<mxCell id="ldb2" parent="1" style="shape=mxgraph.cisco.servers.database;html=1;fillColor=#ffffff;strokeColor=#059669;" value="PostgreSQL 热备" vertex="1">
<mxGeometry height="84" width="118" x="1510" y="145" as="geometry" />
</mxCell>
<mxCell id="lbak" parent="1" style="shape=mxgraph.cisco.storage.storage_array;html=1;fillColor=#ffffff;strokeColor=#d97706;strokeWidth=2;" value="备份归档存储" vertex="1">
<mxGeometry height="84" width="170" x="1432" y="260" as="geometry" />
</mxCell>
<mxCell id="le1" edge="1" parent="1" source="lm1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lnginx" value="HTTPS 443">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le2" edge="1" parent="1" source="lm2" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lnginx" value="HTTPS/API">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le2b" edge="1" parent="1" source="lm3" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lftp" value="21/22 银行文件交换">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le3" edge="1" parent="1" source="lpc" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lnginx" value="办公网访问">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le4" edge="1" parent="1" source="lnginx" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lsw" value="API转发">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le5" edge="1" parent="1" source="lsw" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lapp1" value="节点1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le6" edge="1" parent="1" source="lsw" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lapp2" value="节点2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le7" edge="1" parent="1" source="lapp1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lsvc" value="业务访问">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le8" edge="1" parent="1" source="lapp2" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lsvc" value="业务访问">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le9" edge="1" parent="1" source="lapp1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lftp" value="21/22 文件交换">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le10" edge="1" parent="1" source="lapp2" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lftp" value="21/22 文件交换">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le11" edge="1" parent="1" source="lsvc" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="ldb1" value="5432 数据库访问">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le12" edge="1" parent="1" source="lsvc" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;dashed=1;" target="ldb2" value="5432 状态探测">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le13" edge="1" parent="1" source="ldb1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="ldb2" value="主备同步">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le14" edge="1" parent="1" source="ldb1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lbak" value="备份/WAL">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le15" edge="1" parent="1" source="ldb2" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lbak" value="备份副本">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le16" edge="1" parent="1" source="lsvc" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lbak" value="文件归档">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="1P_7QRTpX64gtdREb-dQ" name="讨论版本">
<mxGraphModel dx="1298" dy="853" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="cTkcAQCcS-jnWanXpuNl-1" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="内网区" vertex="1">
<mxGeometry height="610" width="470" x="920" y="480" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-2" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#eff6ff;strokeColor=#2563eb;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="办公区" vertex="1">
<mxGeometry height="178" width="200" x="140" y="482" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-3" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff7ed;strokeColor=#ea580c;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="公网区域" vertex="1">
<mxGeometry height="424" width="200" x="140" y="670" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-4" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.people.standing_man_2;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="移动端用户" vertex="1">
<mxGeometry height="70" width="40" x="230" y="699" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-5" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.computers_and_peripherals.pc;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="第三方系统" vertex="1">
<mxGeometry height="80" width="80" x="210" y="826" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-6" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" target="cTkcAQCcS-jnWanXpuNl-31">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-7" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.buildings.government_building;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="银行系统&#xa;公网/专线" vertex="1">
<mxGeometry height="80" width="80" x="210" y="956" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-8" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fffbeb;strokeColor=#d97706;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="互联网区/DMZ" vertex="1">
<mxGeometry height="608" width="520" x="370" y="482" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-43" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;fontFamily=Helvetica;fontSize=12;fontColor=default;">
<mxGeometry relative="1" as="geometry">
<mxPoint x="700" y="1020" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-9" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.servers.fileserver;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="SFTP/FTP&#xa;文件交换服务器" vertex="1">
<mxGeometry height="88" width="70" x="703.75" y="948" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-10" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.computers_and_peripherals.pc;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="PC 端用户&#xa;办公网段" vertex="1">
<mxGeometry height="92" width="95" x="190" y="530" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-11" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.misc.hp_mini;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="综合节点&lt;br&gt;&lt;br&gt;&lt;div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;/div&gt;" vertex="1">
<mxGeometry height="73.38" width="80" x="970" y="641.81" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-12" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.storage.relational_database;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="PostgreSQL 主库" vertex="1">
<mxGeometry height="84" width="118" x="1164" y="730" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-13" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.storage.relational_database;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="PostgreSQL 热备" vertex="1">
<mxGeometry height="84" width="118" x="1164" y="547" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-14" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.servers.fileserver;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="备份归档存储" vertex="1">
<mxGeometry height="84" width="60" x="1294" y="940" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-15" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-4" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-31" value="HTTPS 443">
<mxGeometry relative="1" as="geometry">
<mxPoint x="380" y="695" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-16" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-5" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;entryX=0.12;entryY=0.9;entryDx=0;entryDy=0;entryPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-31" value="HTTPS/API">
<mxGeometry relative="1" as="geometry">
<mxPoint x="415" y="721.5" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-17" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-7" style="html=1;endArrow=block;fillColor=#f8cecc;strokeColor=#b85450;strokeWidth=5;edgeStyle=orthogonalEdgeStyle;" target="cTkcAQCcS-jnWanXpuNl-9" value="&lt;span style=&quot;background-color: light-dark(#ffffff, var(--ge-dark-color, #121212)); color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));&quot;&gt;专线&lt;/span&gt;银行访问SFTP/FTP">
<mxGeometry relative="1" x="0.1107" y="-3" as="geometry">
<mxPoint as="offset" />
<Array as="points">
<mxPoint x="300" y="1010" />
<mxPoint x="300" y="1010" />
</Array>
<mxPoint x="266.95" y="1044" as="sourcePoint" />
<mxPoint x="680.7" y="1040" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-18" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-10" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-33" value="办公网访问,内网 IP">
<mxGeometry relative="1" as="geometry">
<mxPoint x="510" y="654" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-19" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-33" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;entryX=0.038;entryY=0.681;entryDx=0;entryDy=0;entryPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-29" value="API转发">
<mxGeometry relative="1" as="geometry">
<mxPoint x="694" y="656" as="sourcePoint" />
<mxPoint x="763" y="676.5" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-20" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-29" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="cTkcAQCcS-jnWanXpuNl-11" value="业务访问">
<mxGeometry relative="1" as="geometry">
<mxPoint x="750.75" y="903" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-21" edge="1" parent="1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="cTkcAQCcS-jnWanXpuNl-9" value="21/22 文件交换">
<mxGeometry relative="1" as="geometry">
<mxPoint as="offset" />
<Array as="points" />
<mxPoint x="740" y="806" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-22" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-39" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;exitX=1;exitY=0.75;exitDx=0;exitDy=0;" target="cTkcAQCcS-jnWanXpuNl-12" value="5432 数据库访问">
<mxGeometry relative="1" x="-0.0013" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-23" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-39" style="html=1;endArrow=block;dashed=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;exitX=1;exitY=0;exitDx=0;exitDy=0;edgeStyle=orthogonalEdgeStyle;" target="cTkcAQCcS-jnWanXpuNl-13" value="5432 状态探测">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="1110" y="730" />
<mxPoint x="1110" y="589" />
</Array>
<mxPoint x="1011" y="863" as="sourcePoint" />
<mxPoint x="1150" y="755" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-24" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-12" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-13" value="主备同步">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-25" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-12" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="cTkcAQCcS-jnWanXpuNl-14" value="备份/WAL">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-26" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-13" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="cTkcAQCcS-jnWanXpuNl-14" value="备份副本">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-27" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-38" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;exitX=0.37;exitY=0.994;exitDx=0;exitDy=0;exitPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-14" value="文件归档">
<mxGeometry relative="1" as="geometry">
<mxPoint x="1130" y="1018.0476190476193" as="sourcePoint" />
<mxPoint x="1487.0476190476188" y="921.9999999999995" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-28" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" target="cTkcAQCcS-jnWanXpuNl-3">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-29" parent="1" style="rounded=0;whiteSpace=wrap;html=1;dashed=1;sketch=0;pointerEvents=1;strokeColor=#99CCFF;strokeWidth=2;align=center;verticalAlign=top;fontFamily=Helvetica;fontSize=12;fontColor=default;fillColor=none;perimeterSpacing=6;" value="" vertex="1">
<mxGeometry height="305" width="171.5" x="640" y="526" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-30" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.misc.hp_mini;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="业务应用节点 2&#xa;Spring Boot Gateway&#xa;业务服务" vertex="1">
<mxGeometry height="72" width="90" x="675.75" y="553" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-31" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.routers.router;" value="边界路由 NAT 转发" vertex="1">
<mxGeometry height="53" width="78" x="400" y="709" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-32" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-31" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.13;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-33">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-33" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.servers.www_server;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="Nginx 入口" vertex="1">
<mxGeometry height="82" width="85" x="513" y="694" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-34" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.misc.hp_mini;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="业务应用节点 1&#xa;Spring Boot Gateway&#xa;业务服务" vertex="1">
<mxGeometry height="73" width="90" x="680.75" y="698.5" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-35" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-11" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-11">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-36" connectable="0" parent="1" style="group" value="" vertex="1">
<mxGeometry height="90" width="84" x="966" y="750" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-37" parent="cTkcAQCcS-jnWanXpuNl-36" style="text;html=1;whiteSpace=wrap;strokeColor=default;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="中间件" vertex="1">
<mxGeometry height="30" width="84" y="30" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-38" parent="cTkcAQCcS-jnWanXpuNl-36" style="text;html=1;whiteSpace=wrap;strokeColor=default;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="对象存储" vertex="1">
<mxGeometry height="30" width="84" y="60" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-39" parent="cTkcAQCcS-jnWanXpuNl-36" style="text;html=1;whiteSpace=wrap;strokeColor=default;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;span style=&quot;text-wrap-mode: nowrap;&quot;&gt;数据库管理控件&lt;/span&gt;" vertex="1">
<mxGeometry height="30" width="84" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-40" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;fillColor=#dae8fc;strokeColor=#6c8ebf;strokeWidth=4;" target="cTkcAQCcS-jnWanXpuNl-31" value="银行公网访问">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="290" y="960" />
<mxPoint x="439" y="960" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-41" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-31" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.13;entryY=0.08;entryDx=0;entryDy=0;entryPerimeter=0;fillColor=#dae8fc;strokeColor=#6c8ebf;strokeWidth=4;" target="cTkcAQCcS-jnWanXpuNl-9" value="银行公网访问 SFTP">
<mxGeometry relative="1" x="0.6353" as="geometry">
<mxPoint x="1" as="offset" />
<Array as="points">
<mxPoint x="490" y="760" />
<mxPoint x="490" y="955" />
</Array>
<mxPoint x="490" y="1144" as="sourcePoint" />
<mxPoint x="639" y="910" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-42" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-7" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;fillColor=#f8cecc;strokeColor=#b85450;strokeWidth=5;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-33" value="专线访问API">
<mxGeometry relative="1" x="0.1107" y="-3" as="geometry">
<mxPoint as="offset" />
<mxPoint x="266.75" y="1074" as="sourcePoint" />
<mxPoint x="680.75" y="1070" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-44" edge="1" parent="1" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.13;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;strokeWidth=3;fillColor=#dae8fc;strokeColor=#6c8ebf;">
<mxGeometry relative="1" as="geometry">
<mxPoint x="479" y="725" as="sourcePoint" />
<mxPoint x="525" y="724" as="targetPoint" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

Binary file not shown.

10619
output/01_Database_Design.html Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,752 @@
---
title: "01_Detailed_Design"
author: "系统设计团队"
date: "2024年12月19日"
documentclass: article
geometry: margin=1in
fontsize: 11pt
mainfont: "PingFang SC"
CJKmainfont: "PingFang SC"
---
---
doc_id: DT-01-DETAIL
doc_role: master_document
authority: primary
scope: 详细设计
source_of_truth: true
last_reviewed: 2026-03-11
retrieval_priority: P0
---
# 福建水务营收系统详细设计说明书
| 文件状态: | 文档密级: | 公开 |
| :--- | :--- | :--- |
| 【】草稿 | | |
| 【√】修改稿 | | |
| 【】正式发布 | | |
| | **当前版本:** | **V1.6** |
| | **作者:** | **唐伟杰** |
| | **完成日期:** | **2026-03-10** |
## 版本历史
| 日期 | 版本号 | 作者 | 备注 |
| :--- | :--- | :--- | :--- |
| 2025-07-01 | V1.0 | 唐伟杰 | 初版 |
| 2025-07-17 | V1.1 | 唐伟杰 | 补充详细设计内容 |
| 2025-08-01 | V1.2 | 唐伟杰 | 同步更新概要设计中的子系统和模块结构,补充数据库设计章节并统一主要模块逻辑。 |
| 2025-08-01 | V1.3 | 唐伟杰 | 数据库系统口径统一为达梦数据库 8.0+。 |
| 2025-08-01 | V1.4 | 唐伟杰 | 单点登录采用 OAuth2.0 协议,补充统一认证相关设计。 |
| 2026-03-10 | V1.6 | 唐伟杰 | 将分散的模块设计、数据库设计、接口设计、安全设计、部署设计与报装电子签章设计整合为统一主详设,统一系统名称、章节体系、模块/接口编号及数据库口径,清理重复内容与部署脏片段。 |
## 章节导航(精简)
- [前言](#sec-preface)
- [系统总体设计](#sec-overall-design)
- [详细模块设计](#sec-module-detail)
- [统一平台详细设计](#sec-platform-detail)
- [营收业务详细设计](#sec-revenue-detail)
- [客户服务模块详细设计](#sec-customer-detail)
- [表务详细设计](#sec-meter-detail)
- [报装与签章详细设计](#sec-installation-detail)
- [数据库详细设计](#sec-database-detail)
- [接口详细设计](#sec-interface-detail)
- [安全详细设计](#sec-security-detail)
- [部署与运维设计](#sec-deployment-detail)
- [附录](#sec-appendix)
- [模块正文文件索引](#sec-module-files)
<a id="sec-preface"></a>
# 前言
## 编写目的
本文档用于指导福建水务营收系统的详细设计、开发实现、联调测试、部署上线及后续运维,是本项目详细设计阶段的统一主说明书。本文档以 `docs/design/02_Detailed_Design/01_Detailed_Design.md` 作为唯一主详设文件,吸收模块设计、数据库设计、接口设计、安全设计、部署设计以及报装电子签章专项设计中的可复用内容,形成可直接交付和实施使用的统一版本。
## 建设背景
福建水务营收系统面向集团化、多组织、多渠道的营收业务场景,覆盖客户资料、抄表开账、营业收费、账务处理、表务管理、报装立户、电子签章、客户服务、移动作业、外部支付与政务对接等核心业务。系统建设目标是形成统一平台、统一数据、统一接口、统一安全管控的营收业务支撑体系,满足集团及下属单位的标准化与可扩展管理要求。
## 设计范围
本文档覆盖以下设计内容:
1. 系统总体架构与部署架构设计。
2. 统一平台、营收业务、表务、报装与签章、客户服务渠道等业务模块详细设计。
3. 达梦数据库 8.0+ 口径下的核心数据模型、核心表结构、索引与性能设计。
4. 内部接口、外部接口、安全控制、部署与运维方案。
5. 关键业务流程、模块关系、接口交互与审计留痕要求。
## 术语与缩略语
| 术语/缩略语 | 说明 |
|---|---|
| SSO | 单点登录 |
| OAuth2.0 | 统一认证授权协议 |
| RBAC | 基于角色的访问控制模型 |
| DM8 | 达梦数据库 8.0+ |
| API | 应用程序接口 |
| CA | 电子认证与电子签章能力 |
| MFA | 多因素认证 |
| ETL | 数据抽取、转换、加载 |
## 参考资料
1. 《福建水务营收系统概要设计相关文档》
2. `docs/design/02_Detailed_Design/02_Module_Traceability_Index.md`
3. `docs/design/02_Detailed_Design/03_CA_Esignature_Supplement.md`
4. `docs/design/02_Detailed_Design/11_UP_Detailed.md`
5. `docs/design/02_Detailed_Design/12_REV_Detailed.md`
6. `docs/design/02_Detailed_Design/13_CS_Detailed.md`
7. `docs/design/02_Detailed_Design/14_METER_Detailed.md`
8. `docs/design/02_Detailed_Design/15_INST_Detailed.md`
9. `docs/design/03_Technical_Design/01_Database_Design.md`
10. `docs/design/03_Technical_Design/02_Table_Specs.md`(历史命名映射补充)
11. `docs/design/03_Technical_Design/03_Interface_Design.md`
12. `docs/design/03_Technical_Design/04_Security_Design.md`
13. `docs/design/03_Technical_Design/05_Deployment_Design.md`
14. 福建水投相关业务操作手册及需求说明资料
15. `docs/design/01_Overview/05_Module_Inventory.md`
<a id="sec-module-files"></a>
## 模块正文文件索引
- 总体层模块清单与承接映射:`docs/design/01_Overview/05_Module_Inventory.md`
- 统一平台模块正文:`docs/design/02_Detailed_Design/11_UP_Detailed.md`
- 营收业务模块正文:`docs/design/02_Detailed_Design/12_REV_Detailed.md`
- 客户服务模块正文:`docs/design/02_Detailed_Design/13_CS_Detailed.md`
- 表务模块正文:`docs/design/02_Detailed_Design/14_METER_Detailed.md`
- 报装与签章模块正文:`docs/design/02_Detailed_Design/15_INST_Detailed.md`
<a id="sec-overall-design"></a>
# 系统总体设计
## 总体目标
福建水务营收系统总体设计遵循“统一平台、业务协同、数据集中、接口标准、安全可控”的原则,实现以下目标:
- 建立统一认证、统一组织、统一权限、统一参数、统一审计基础能力。
- 构建覆盖营收、表务、报装、客户服务的完整业务闭环。
- 形成达梦数据库 8.0+ 为核心的数据架构,支撑集团化数据管理与查询分析。
- 通过标准化接口接入银行、第三方支付、短信、税控、物联网、政务、CA 签章等外部系统。
- 满足生产部署、容灾备份、日志审计与安全监管要求。
## 逻辑架构设计
```mermaid
graph TB
U1[柜台/客服/管理人员] --> A1[PC管理端]
U2[抄表员/表务人员] --> A2[移动作业端]
U3[客户用户] --> A3[微信/支付宝/微服务窗]
U4[第三方系统] --> A4[外部系统接口]
subgraph G[接入与网关层]
G1[统一门户]
G2[API网关]
G3[统一认证中心]
end
subgraph B[业务服务层]
B1[统一平台服务]
B2[营收业务服务]
B3[表务管理服务]
B4[报装与签章服务]
B5[客户服务渠道服务]
B6[消息通知与任务调度]
end
subgraph D[数据与支撑层]
D1[(达梦数据库 8.0+)]
D2[(Redis缓存)]
D3[对象存储/文件服务]
D4[消息队列]
D5[日志审计与监控]
end
A1 --> G1
A2 --> G1
A3 --> G1
A4 --> G2
G1 --> G2 --> G3
G2 --> B1
G2 --> B2
G2 --> B3
G2 --> B4
G2 --> B5
B1 --> D1
B2 --> D1
B3 --> D1
B4 --> D1
B5 --> D1
B2 --> D2
B3 --> D2
B4 --> D3
B5 --> D4
B1 --> D5
B2 --> D5
B3 --> D5
B4 --> D5
B5 --> D5
```
### 设计说明
1. 统一平台服务提供认证、组织、权限、参数、审计等公共能力。
2. 营收业务服务承担客户、抄表、开账、收费、账务、发票、催缴、统计与业务工单等核心处理。
3. 表务管理服务承担设备档案、表务工单、仓储、物联网集抄对接等处理。
4. 报装与签章服务承担申请受理、现场踏勘、施工验收、合同签署与资料归档。
5. 客户服务渠道服务面向微信、支付宝、微服务窗等客户侧渠道提供查询、缴费、电子发票与业务办理。
## 部署分区设计
```mermaid
graph TB
INTERNET[互联网/政务外网/合作机构] --> DMZ[DMZ接入区]
DMZ --> APP[应用服务区]
APP --> DATA[数据服务区]
APP --> OPS[管理运维区]
subgraph DMZ[DMZ接入区]
LB[负载均衡/Nginx]
WAF[WAF与边界防护]
GW[API网关]
end
subgraph APP[应用服务区]
APP1[统一平台服务]
APP2[营收业务服务]
APP3[表务服务]
APP4[报装签章服务]
APP5[客户渠道服务]
end
subgraph DATA[数据服务区]
DBM[(达梦主库)]
DBS[(达梦备库/从库)]
REDIS[(Redis)]
FILE[文件存储]
end
subgraph OPS[管理运维区]
MON[监控平台]
LOG[日志平台]
BAK[备份服务]
JUMP[堡垒机/跳板机]
end
```
## 子系统与模块划分
| 子系统 | 说明 | 核心模块 |
|---|---|---|
| 统一平台 | 提供统一认证、组织、权限、参数、审计与监控基础能力 | UP-001 ~ UP-004 |
| 营收业务 | 覆盖客户、抄表、收费、账务、发票、催缴、统计、代收与业务工单 | REV-001 ~ REV-009 |
| 表务管理 | 覆盖水表基础参数、仓储库存、设备档案以及工单和物联网同步等支撑能力 | METER-001 ~ METER-003 |
| 报装与签章 | 覆盖报装流程管理、工程管理、档案管理及其下挂签章归档能力 | INST-001 ~ INST-003 |
| 客户服务模块 | 覆盖账户绑定、信息查询、在线缴费、电子发票、网点服务、业务办理与柜面扫码支付 | CS-001 ~ CS-007 |
### 与架构图模块清单的承接说明
1. 架构图中的模块枚举基线以 `../01_Overview/05_Module_Inventory.md` 为准。
2. 当前详细设计采用“业务域正文承接多个架构层模块”的组织方式,不强制为 `MOBILE-*``WECHAT-*``WORK-*` 单独建立平行正式主稿。
3. `WECHAT-*` 当前按客户服务渠道视角并入 `CS-*` 体系承接;`MOBILE-*` 当前作为抄表与表务协同端能力分散承接于营收与表务正文;`WORK-*` 当前作为工单协同与流程支撑能力在客户服务、表务、报装正文中交叉引用。
4. 若后续需要新增独立正文,必须先确认该模块已具备稳定边界、独立接口和核心数据域,并同步更新概要设计与追溯索引。
<a id="sec-module-detail"></a>
<a id="sec-platform-detail"></a>
# 统一平台详细设计
## 章节定位
为避免主详设与分模块文件重复维护,本章仅保留统一平台模块摘要与入口链接,详细正文统一维护在模块文件中。
## 模块摘要
| 模块编号 | 模块名称 | 设计摘要 | 正文链接 |
|---|---|---|---|
| `UP-001` | 统一认证与单点登录 | 统一身份认证、单点登录、令牌生命周期管理与高敏感二次校验 | [UP-001](./11_UP_Detailed.md#mod-up-001) |
| `UP-002` | 组织用户与权限管理 | 组织、岗位、角色、菜单与数据权限统一管控 | [UP-002](./11_UP_Detailed.md#mod-up-002) |
| `UP-003` | 参数字典与基础配置 | 统一维护字典、价格、地址、渠道和任务参数 | [UP-003](./11_UP_Detailed.md#mod-up-003) |
| `UP-004` | 审计监控与运维支撑 | 操作审计、接口监控、任务追踪与告警通知 | [UP-004](./11_UP_Detailed.md#mod-up-004) |
## 正文入口
- [统一平台模块正文](./11_UP_Detailed.md#sec-content)
<a id="sec-revenue-detail"></a>
# 营收业务详细设计
## 章节定位
本章保留营收业务模块的设计摘要,详细流程、数据域与规则说明统一维护在分模块正文文件中。
## 模块摘要
| 模块编号 | 模块名称 | 设计摘要 | 正文链接 |
|---|---|---|---|
| `REV-001` | 客户资料管理 | 客户主档、账户、联系人、绑定关系等主数据管理 | [REV-001](./12_REV_Detailed.md#mod-rev-001) |
| `REV-002` | 抄表开账 | 抄表计划、异常复核、计费与账单生成 | [REV-002](./12_REV_Detailed.md#mod-rev-002) |
| `REV-003` | 营业收费 | 多渠道缴费、账单核销与收费凭证管理 | [REV-003](./12_REV_Detailed.md#mod-rev-003) |
| `REV-004` | 账务处理 | 调整、退款、冲正、呆坏账等账务修正 | [REV-004](./12_REV_Detailed.md#mod-rev-004) |
| `REV-005` | 发票与税务处理 | 发票申请、回写、作废与红冲协同 | [REV-005](./12_REV_Detailed.md#mod-rev-005) |
| `REV-006` | 催缴与通知 | 欠费催缴策略、触达与结果回写 | [REV-006](./12_REV_Detailed.md#mod-rev-006) |
| `REV-007` | 统计分析 | 营收、收费、欠费、渠道等多维统计 | [REV-007](./12_REV_Detailed.md#mod-rev-007) |
| `REV-008` | 代收与银行业务 | 代收代扣、对账、结算与差异处理 | [REV-008](./12_REV_Detailed.md#mod-rev-008) |
| `REV-009` | 业务参数配置 | 价格模板、页面配置与规则参数管理 | [REV-009](./12_REV_Detailed.md#mod-rev-009) |
## 正文入口
- [营收业务模块正文](./12_REV_Detailed.md#sec-content)
<a id="sec-customer-detail"></a>
# 客户服务模块详细设计
## 章节定位
本章保留客户服务模块摘要与入口,详细设计内容由分模块正文文件统一承载。
## 模块摘要
| 模块编号 | 模块名称 | 设计摘要 | 正文链接 |
|---|---|---|---|
| `CS-001` | 账户绑定管理 | 渠道账号与客户账户绑定、解绑、默认设置 | [CS-001](./13_CS_Detailed.md#mod-cs-001) |
| `CS-002` | 信息查询服务 | 账单、缴费、欠费、用水分析与历史查询 | [CS-002](./13_CS_Detailed.md#mod-cs-002) |
| `CS-003` | 在线缴费服务 | 多渠道在线支付下单、回调确认与补单 | [CS-003](./13_CS_Detailed.md#mod-cs-003) |
| `CS-004` | 电子发票服务 | 发票申请、查询、下载与推送 | [CS-004](./13_CS_Detailed.md#mod-cs-004) |
| `CS-005` | 营业网点服务 | 网点信息、服务范围与办事引导 | [CS-005](./13_CS_Detailed.md#mod-cs-005) |
| `CS-006` | 业务办理服务 | 线上办理入口与办理状态流转协同 | [CS-006](./13_CS_Detailed.md#mod-cs-006) |
| `CS-007` | 柜面扫码支付 | 柜面二维码收款与收费状态联动 | [CS-007](./13_CS_Detailed.md#mod-cs-007) |
## 正文入口
- [客户服务模块正文](./13_CS_Detailed.md#sec-content)
<a id="sec-meter-detail"></a>
# 表务详细设计
## 章节定位
本章保留表务模块摘要,详细流程与对象说明统一在分模块正文文件维护。
## 模块摘要
| 模块编号 | 模块名称 | 设计摘要 | 正文链接 |
|---|---|---|---|
| `METER-001` | 表务基础管理 | 水表档案、状态与参数基础管理 | [METER-001](./14_METER_Detailed.md#mod-meter-001) |
| `METER-002` | 仓库与库存管理 | 入库、出库、退库、报废与库存预警 | [METER-002](./14_METER_Detailed.md#mod-meter-002) |
| `METER-003` | 设备档案管理 | 设备主档、状态流转、工单协同与远传同步 | [METER-003](./14_METER_Detailed.md#mod-meter-003) |
## 正文入口
- [表务模块正文](./14_METER_Detailed.md#sec-content)
<a id="sec-installation-detail"></a>
# 报装与签章详细设计
## 章节定位
本章保留报装与签章模块摘要详细流程、CA 集成与异常补偿策略统一维护在分模块正文及 CA 专项补充文档。
## 模块摘要
| 模块编号 | 模块名称 | 设计摘要 | 正文链接 |
|---|---|---|---|
| `INST-001` | 报装流程管理 | 申请受理、踏勘流转与方案编制 | [INST-001](./15_INST_Detailed.md#mod-inst-001) |
| `INST-002` | 工程管理 | 施工验收、立户通水与合同签章协同 | [INST-002](./15_INST_Detailed.md#mod-inst-003) |
| `INST-003` | 档案管理 | 材料归档、签章回执留存与过程可追溯 | [INST-003](./15_INST_Detailed.md#mod-inst-005) |
## 正文入口
- [报装与签章模块正文](./15_INST_Detailed.md#sec-content)
- [报装 CA 电子签章专项补充](./03_CA_Esignature_Supplement.md#sec-position)
<a id="sec-database-detail"></a>
# 数据库详细设计
## 数据库选型与原则
系统数据库统一采用达梦数据库 8.0+。数据库设计遵循以下原则:
1. 统一主数据模型,避免多口径重复建模。
2. 面向业务闭环设计客户、水表、账单、缴费、工单、报装、签章等核心对象。
3. 兼顾 OLTP 事务处理与统计查询性能。
4. 支持多单位、多区域的数据隔离与权限过滤。
5. 敏感数据字段满足加密、脱敏和审计要求。
## 数据库逻辑架构
```mermaid
graph TB
APP[业务应用] --> ORM[数据访问层]
APP --> CACHE[Redis缓存]
ORM --> MASTER[(达梦主库)]
MASTER --> SLAVE[(达梦从库/备库)]
MASTER --> FILE[文件与归档索引]
```
## 核心数据模型
```mermaid
erDiagram
BIZ_CUST ||--o{ BIZ_CUST_CONTACT : 包含
BIZ_CUST ||--o{ BIZ_CUST_METER : 绑定
BIZ_CUST ||--|| BIZ_ACCOUNT : 对应
BIZ_METER ||--o{ BIZ_READING_DATA : 产生
BIZ_READING_DATA ||--|| BIZ_CHARGE : 生成
BIZ_CHARGE ||--o{ BIZ_CHARGE_DETAIL : 包含
BIZ_CHARGE ||--o{ BK_TRANSACTION : 核销
BIZ_CUST ||--o{ BIZ_INVOICE : 开票
INSTALLATION_APPLY ||--o{ INSTALLATION_CONTRACT : 生成
INSTALLATION_CONTRACT ||--o{ INSTALLATION_SIGNATURE : 签署
INSTALLATION_CONTRACT ||--o{ INSTALLATION_EVIDENCE : 存证
```
## 核心数据表设计
### 客户与账户类
| 表名 | 说明 | 关键字段 |
|---|---|---|
| `biz_cust` | 客户主档表 | code、name、cust_type、id_no、mobile、address、status |
| `biz_account` | 客户账户表 | code、cust_id、balance、arrears_amount、status |
| `biz_cust_contact` | 客户联系人表 | cust_id、name、mobile、contact_type、is_default |
| `biz_cust_app_binds` | 渠道绑定关系表 | cust_id、app_type、app_user_id、status |
| `biz_cust_invoice` | 客户开票信息表 | cust_id、invoice_title、tax_no、email、mobile |
### 水表与抄表类
| 表名 | 说明 | 关键字段 |
|---|---|---|
| `biz_meter` | 水表信息表 | code、meter_no、model_code、caliber_code、status |
| `biz_cust_meter` | 客户与水表关系表 | cust_id、meter_id、bind_status、bind_time |
| `biz_meter_book` | 抄表册本表 | code、name、reader_id、cycle_type、status |
| `biz_reading_data` | 抄表数据表 | meter_id、cust_id、reading_time、current_reading、usage_amount |
| `biz_last_reading` | 上次抄表结果表 | meter_id、last_reading、last_reading_time |
| `biz_meter_read` | 抄表任务状态表 | book_id、meter_id、read_status、reader_id |
### 账单、收费与发票类
| 表名 | 说明 | 关键字段 |
|---|---|---|
| `biz_charge` | 营业账主表 | code、cust_id、record_id、total_amount、charge_status |
| `biz_charge_detail` | 营业账明细表 | charge_id、cost_component_code、usage_amount、detail_amount |
| `biz_collection` | 托收资料表 | cust_id、channel_code、collection_status、apply_time |
| `biz_withholding` | 代扣资料表 | cust_id、agreement_no、withholding_status、sign_time |
| `biz_invoice` | 发票主表 | code、cust_id、invoice_status、invoice_type、issue_time |
| `biz_invoice_taxrate` | 发票税率表 | tax_code、tax_name、tax_rate、status |
### 银行渠道与交易类
| 表名 | 说明 | 关键字段 |
|---|---|---|
| `bk_payment_channel` | 支付渠道表 | channel_code、channel_name、channel_type、status |
| `bk_channel_api_config` | 渠道接口配置表 | channel_code、api_url、sign_type、status |
| `bk_channel_route_rule` | 渠道路由规则表 | business_type、channel_code、priority、status |
| `bk_transaction` | 渠道交易流水表 | trade_no、biz_order_no、channel_code、trade_amount、trade_status |
| `bk_transaction_callback` | 支付回调表 | trade_no、callback_status、callback_time、payload |
| `bk_transaction_exception` | 渠道异常表 | trade_no、exception_type、exception_status、remark |
| `bk_withholding_agreement` | 代扣签约表 | agreement_no、cust_id、bank_code、status |
| `bk_withholding_batch` | 代扣批次表 | batch_no、batch_date、total_count、status |
| `bk_withholding_item` | 代扣明细表 | batch_no、cust_id、charge_id、item_status |
| `bk_reconcile_batch` | 对账批次表 | batch_no、channel_code、reconcile_date、status |
| `bk_reconcile_diff` | 对账差异表 | batch_no、trade_no、diff_type、diff_amount |
| `bk_settlement_batch` | 结算批次表 | batch_no、channel_code、settlement_date、status |
### 表务与工单类
| 表名 | 说明 | 关键字段 |
|---|---|---|
| `biz_meter_log` | 表务工单/过程留痕表 | biz_type、biz_id、operate_user、operate_time、remark |
| `biz_meter_in_out` | 水表出入库主表 | code、in_out_type、warehouse_id、operate_time、status |
| `biz_meter_in_out_rel` | 出入库关联明细表 | in_out_id、meter_id、quantity、status |
| `biz_process` | 业务工单主表 | code、biz_type_code、cust_id、process_status |
### 报装与签章类
| 表名 | 说明 | 关键字段 |
|---|---|---|
| `biz_process` | 报装申请主表 | code、biz_type_code、cust_id、process_status |
| `biz_process_transfer` | 现场踏勘与流转表 | process_id、from_user、to_user、transfer_time |
| `installation_contract` | 报装合同表 | contract_code、installation_id、contract_type、contract_status |
| `installation_signature` | 签章记录表 | signature_code、contract_id、signer_id、signature_time、signature_status |
| `installation_evidence` | 存证记录表 | evidence_code、contract_id、evidence_hash、evidence_status |
## 索引与性能设计
### 主要索引策略
1. 唯一索引:客户编号、水表编号、账单编号、缴费编号、合同编号等业务唯一键。
2. 复合索引:
- 客户查询:`(customer_type, status)`
- 账单查询:`(customer_id, bill_month, bill_status)`
- 抄表查询:`(meter_id, reading_date)`
- 缴费查询:`(customer_id, payment_time)`
3. 时间分区:账单、缴费、日志等大表按月或按年管理归档。
4. 热点缓存:参数字典、用户会话、移动端任务、发票状态等进入 Redis。
<a id="sec-interface-detail"></a>
# 接口详细设计
## 接口设计原则
1. 内部接口统一采用 RESTful 风格JSON 作为主要报文格式。
2. 外部接口根据对接方规范支持 HTTPS API、SFTP 文件交换等方式。
3. 接口编号统一采用 `IF-``EXT-` 前缀,与模块编号区分。
4. 关键交易接口必须支持幂等控制、签名校验、失败重试与调用日志。
## 统一平台接口
| 接口编号 | 接口名称 | 功能描述 | 调用方 | 协议 |
|---|---|---|---|---|
| IF-UP-001 | 用户登录接口 | 用户登录并获取访问令牌 | PC端/移动端/渠道端 | HTTPS REST |
| IF-UP-002 | 用户信息接口 | 获取当前登录用户上下文 | 各业务系统 | HTTPS REST |
| IF-UP-003 | 权限校验接口 | 校验菜单、按钮和数据权限 | 各业务模块 | HTTPS REST |
| IF-UP-004 | 参数字典接口 | 获取字典与业务参数 | 各业务模块 | HTTPS REST |
## 营收业务接口
| 接口编号 | 接口名称 | 功能描述 | 调用方 | 协议 |
|---|---|---|---|---|
| IF-REV-001 | 客户信息查询接口 | 查询客户档案、账户状态、联系人与水表绑定关系 | 柜台/客户渠道/工单 | HTTPS REST |
| IF-REV-004 | 抄表数据提交接口 | 提交人工或远传抄表数据并触发校验 | 抄表APP/集抄系统 | HTTPS REST |
| IF-REV-005 | 账单生成接口 | 根据抄表结果、水价模板和费用组成生成账单 | 开账任务 | HTTPS REST |
| IF-REV-006 | 缴费处理接口 | 创建收费记录并核销账单 | 柜台/线上渠道 | HTTPS REST |
| IF-REV-007 | 账务调整接口 | 发起金额调整、退款、冲正、坏账等业务处理 | 财务/营业人员 | HTTPS REST |
| IF-REV-008 | 发票申请接口 | 发起开票申请并接收票据状态回写 | 柜台/客户渠道 | HTTPS REST |
| IF-REV-009 | 催缴任务接口 | 生成催缴名单并提交消息触达请求 | 营收系统/消息服务 | HTTPS REST |
| IF-REV-010 | 统计查询接口 | 查询营收、收费、欠费、渠道、客户统计结果 | 管理后台/统计分析端 | HTTPS REST |
| IF-REV-011 | 银行代收协同接口 | 发起代扣、回盘、对账、结算协同 | 银行代收模块/SYS-009 | HTTPS REST / 文件交换 |
| IF-REV-012 | 业务参数配置接口 | 查询和维护价格模板、优惠方案、业务参数配置 | 管理后台/参数管理端 | HTTPS REST |
## 表务与物联网接口
| 接口编号 | 接口名称 | 功能描述 | 调用方 | 协议 |
|---|---|---|---|---|
| IF-METER-001 | 水表档案查询接口 | 查询水表与生命周期信息 | 表务/营收/报装 | HTTPS REST |
| IF-METER-002 | 表务工单处理接口 | 提交换表、移表等结果 | 移动作业端 | HTTPS REST |
| IF-METER-003 | 库存出入库接口 | 处理领用、退库、报废 | 仓储管理端 | HTTPS REST |
| IF-METER-004 | 集抄数据接收接口 | 接收远程抄表数据 | 物联网平台 | HTTPS REST |
## 报装与签章接口
| 接口编号 | 接口名称 | 功能描述 | 调用方 | 协议 |
|---|---|---|---|---|
| IF-INST-001 | 报装申请提交接口 | 提交报装申请与附件 | 柜台/微网厅/政务平台 | HTTPS REST |
| IF-INST-002 | 踏勘结果回填接口 | 回填现场踏勘结果 | 报装人员 | HTTPS REST |
| IF-INST-003 | 合同签署发起接口 | 创建签章任务 | 报装系统 | HTTPS REST |
| IF-INST-004 | 签章回执接口 | 回写签章结果和存证信息 | CA系统/报装系统 | HTTPS REST |
| IF-INST-005 | 报装归档接口 | 归档申请、合同和验收资料 | 报装系统 | HTTPS REST |
## 客户渠道接口
| 接口编号 | 接口名称 | 功能描述 | 调用方 | 协议 |
|---|---|---|---|---|
| IF-CS-001 | 账户绑定接口 | 绑定或解绑客户账户 | 微信/支付宝/微网厅 | HTTPS REST |
| IF-CS-002 | 历史账单查询接口 | 查询账单、欠费、用水趋势 | 客户端 | HTTPS REST |
| IF-CS-003 | 在线支付下单接口 | 创建微信/支付宝支付订单 | 客户端 | HTTPS REST |
| IF-CS-004 | 发票申请接口 | 提交电子发票申请 | 客户端 | HTTPS REST |
| IF-CS-005 | 网点与业务办理接口 | 查询网点、提交业务办理 | 客户端 | HTTPS REST |
| IF-CS-006 | 业务办理进度接口 | 查询业务办理和工单进度 | 客户端 | HTTPS REST |
| IF-CS-007 | 柜面扫码支付接口 | 创建柜面扫码支付订单并回写结果 | 柜台终端/营业前台 | HTTPS REST |
## 外部系统接口
### 金融支付接口
| 接口编号 | 接口名称 | 功能描述 | 协议 | 输入参数 | 输出结果 |
|---|---|---|---|---|---|
| EXT-001 | 银行代扣接口 | 批量代扣水费 | HTTPS/SFTP | 客户信息、账单金额、代扣日期 | 扣款结果 |
| EXT-101 | 微信支付统一下单 | 创建微信支付订单 | HTTPS | 订单信息、金额 | 预支付信息 |
| EXT-201 | 支付宝统一收单 | 创建支付宝支付订单 | HTTPS | 订单信息、金额 | 支付结果 |
### 税务与消息接口
| 接口编号 | 接口名称 | 功能描述 | 协议 | 输入参数 | 输出结果 |
|---|---|---|---|---|---|
| EXT-301 | 短信发送接口 | 发送催缴或通知短信 | HTTPS | 手机号、模板、参数 | 发送结果 |
| EXT-401 | 邮件发送接口 | 发送电子发票或通知邮件 | HTTPS | 邮箱、主题、内容 | 发送结果 |
| EXT-501 | 电子发票开具接口 | 税控平台开票 | HTTPS | 发票信息、税率 | 发票结果 |
### 物联网、政务与签章接口
| 接口编号 | 接口名称 | 功能描述 | 协议 | 输入参数 | 输出结果 |
|---|---|---|---|---|---|
| EXT-601 | 水表数据采集接口 | 获取远程抄表数据 | HTTPS | 水表编号、时间范围 | 抄表数据 |
| EXT-701 | 政务数据汇聚接口 | 向政务平台推送业务数据 | HTTPS | 业务数据、统计数据 | 推送结果 |
| EXT-801 | 环卫收费对接接口 | 同步污水费/环卫收费数据 | HTTPS | 收费数据 | 同步结果 |
| EXT-901 | 客服工单创建接口 | 与客服系统同步工单 | HTTPS | 工单信息 | 工单编号 |
| EXT-1001 | 消火栓控制接口 | 控制取水权限与设备状态 | HTTPS | 设备信息、控制指令 | 控制结果 |
| EXT-CA-001 | 身份认证接口 | 验证合同签署方身份 | HTTPS REST | 用户信息、认证方式 | 认证结果 |
| EXT-CA-002 | 电子签章接口 | 执行电子签章 | HTTPS REST | 文档内容、签章位置 | 签章结果 |
| EXT-CA-003 | 时间戳接口 | 申请签署时间戳 | HTTPS REST | 文档哈希 | 时间戳凭证 |
| EXT-CA-004 | 电子存证接口 | 存储签署后合同 | HTTPS REST | 签署文档、元数据 | 存证凭证 |
<a id="sec-security-detail"></a>
# 安全详细设计
## 安全目标与分层防护
系统安全设计遵循机密性、完整性、可用性、可审计性原则,采用边界安全、应用安全、数据安全、运维安全四层防护模式。
```mermaid
graph TB
T[外部威胁] --> N[边界安全]
N --> A[应用安全]
A --> D[数据安全]
D --> O[运维安全]
O --> C[核心业务资产]
```
## 身份认证与访问控制
1. 采用 OAuth2.0 + JWT 统一认证。
2. 高风险操作支持 MFA 二次认证。
3. 基于 RBAC 的菜单、按钮、数据权限控制。
4. 管理端、移动端、客户端、外部系统按不同安全域实施权限隔离。
## 数据安全与隐私保护
1. 核心数据库统一为达梦数据库 8.0+,关键数据按要求启用加密存储。
2. 身份证号、手机号、银行账户等敏感字段按角色脱敏展示。
3. 文件、合同、签章凭证、验收附件统一归档并控制访问权限。
4. 备份数据加密存储,支持异地容灾保管。
## 接口安全与审计追踪
- 所有外部接口采用 HTTPS 加密传输。
- 关键接口支持签名、时间戳、随机数防重放。
- 支付、退款、签章、账务调整等交易型接口必须具备幂等控制。
- 统一记录调用时间、调用方、请求摘要、响应结果、异常码与处理人。
## 安全运营与应急响应
1. 建立暴力破解、异常访问、接口失败、支付异常、签章异常等监控规则。
2. 按 P0~P3 级别定义安全事件处置流程。
3. 定期进行漏洞扫描、补丁更新、备份恢复演练和权限审计。
<a id="sec-deployment-detail"></a>
# 部署与运维设计
## 部署总体方案
系统采用集中部署模式,生产环境分为接入区、应用区、数据区、运维管理区,支持主备容灾与横向扩展。
```mermaid
graph TB
U[外部访问] --> LB[负载均衡/Nginx]
LB --> APP1[应用节点1]
LB --> APP2[应用节点2]
LB --> APP3[应用节点3]
APP1 --> DB[(达梦主库)]
APP2 --> DB
APP3 --> DB
DB --> DBS[(达梦备库/从库)]
APP1 --> REDIS[(Redis)]
APP1 --> FILE[对象存储]
APP1 --> MON[监控与日志平台]
```
## 环境规划
| 环境 | 用途 | 说明 |
|---|---|---|
| 开发环境 | 开发联调 | 功能开发、单元验证 |
| 测试环境 | 集成测试 | 接口联调、业务测试、性能测试 |
| 预生产环境 | 上线前验证 | 模拟生产配置,进行发布演练 |
| 生产环境 | 正式运行 | 双机或多节点高可用部署 |
## 网络与分区设计
1. DMZ 区部署负载均衡、WAF、网关等对外接入组件。
2. 应用区部署统一平台、营收、表务、报装、客户渠道等应用节点。
3. 数据区部署达梦数据库、Redis、对象存储与备份服务。
4. 管理区部署堡垒机、日志平台、监控平台和运维工具。
## 监控告警与日志
### 监控指标
| 类别 | 监控项 |
|---|---|
| 主机监控 | CPU、内存、磁盘、网络 |
| 应用监控 | QPS、响应时间、错误率、线程池 |
| 数据库监控 | 连接数、慢 SQL、锁等待、主备同步 |
| 业务监控 | 开账量、收费量、退款量、签章成功率 |
### 日志分类
- 操作日志
- 登录日志
- 接口调用日志
- 任务执行日志
- 安全审计日志
- 外部系统对接日志
## 备份恢复与发布管理
1. 数据库执行每日增量、每周全量备份。
2. 关键文件、合同、电子发票、签章凭证同步纳入备份。
3. 发布采用版本化管理,执行发布审批、健康检查、回滚预案。
4. 对支付、签章、银行代扣等关键链路执行灰度验证前置检查,但生产方案不保留脚本碎片或临时配置片段。
<a id="sec-appendix"></a>
# 附录
## 附录A 模块编号说明
| 前缀 | 模块域 |
|---|---|
| UP | 统一平台 |
| REV | 营收业务 |
| METER | 表务管理 |
| INST | 报装与签章 |
| CS | 客户服务与渠道 |
## 附录B 接口编号说明
| 前缀 | 说明 |
|---|---|
| IF-UP / IF-REV / IF-CS / IF-METER / IF-INST | 系统内部标准接口 |
| IF-EXT | 对外系统接口 |
| EXT-CA | 历史资料中的电子签章专项外部接口编号(存量引用) |
## 附录C 设计约束与统一口径
1. 系统名称统一为“福建水务营收系统”。
2. 数据库口径统一为“达梦数据库 8.0+”。
3. 模块编号统一采用 `UP/REV/METER/INST/CS-001` 风格。
4. 接口编号统一采用 `IF-UP/IF-REV/IF-CS/IF-METER/IF-INST/IF-EXT-001` 风格,历史 `EXT-*` 仅用于存量资料引用。
5. 本文档为唯一主详设文件,其他专项文档作为历史参考与内容来源,不再作为并行主文件使用。

Some files were not shown because too many files have changed in this diff Show More