Compare commits

...

8 Commits

8 changed files with 2407 additions and 0 deletions

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,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,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,101 @@
# 营收明确缺陷第一批修复验证记录
日期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 需产品确认抄表状态规则。

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/` 目录下。

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 | 降级到最新快照 |