Compare commits

...

5 Commits

6 changed files with 1425 additions and 0 deletions

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,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 处理,避免预算页选项未确认时误写正式字段。
- 本轮未放宽已收费、已开票、已结账、缺少抄表依据、缺少附件等既有水量调整校验。

File diff suppressed because it is too large Load Diff

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/`