41 KiB
REV-004 Unsold Preview Options 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: Make unsold-adjust preview checkbox options flow into formal submission, audit records, and final write-back calculation consistently.
Architecture: Keep the existing REV-004 unsold-adjust endpoints and services. Extend the current VO/DTO/formalization/write-back chain so preview options are explicit formal adjustment parameters instead of transient preview-only flags.
Tech Stack: Java 17, Spring Boot, MyBatis Plus, JUnit 5, Mockito, Maven, Markdown evidence in ../water-docs.
File Structure
Backend files:
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/vo/AccountingAdjustUnsoldAdjustSubmitReqVO.java- Add formal water-adjust options
updateAccumulatedandupdateBaseCode.
- Add formal water-adjust options
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/vo/AccountingAdjustSubmitReqVO.java- Carry water-adjust options through accountProcess services.
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/AccountingAdjustActionController.java- Map water and price-diff option fields from endpoint-specific VO to unified submit VO.
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/charge/vo/AccountingAdjustReqVO.java- Carry water-adjust options into
ChargeServiceImpl.
- Carry water-adjust options into
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/accountProcess/AccountingAdjustProcessServiceImpl.java- Pass water-adjust options into legacy/core adjustment request.
- Pass price-diff options into formal create DTO.
sw-business/sw-business-api/src/main/java/cn/com/emsoft/sw/business/api/accountingadjust/dto/PriceDiffAdjustCreateReqDTO.java- Add
calculateGarbageFeeandupdateAccumulatedVolume.
- Add
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/api/accountingadjust/PriceDiffAdjustInternalApiImpl.java- Move price-diff options from API DTO to formalization VO.
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/dal/dataobject/pricediffadjustdetail/PriceDiffAdjustDetailDO.java- Add
calculateGarbageFeeandupdateAccumulatedVolume.
- Add
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/pricediff/PriceDiffFormalizationService.java- Persist all price-diff option values in formal detail rows.
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/AccountingAdjustActionServiceImpl.java- Read price-diff options from formal/log detail and send them into write-back.
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/pricediff/PriceDiffWriteBackService.java- Align default option semantics with preview and apply
updateAccumulatedVolume.
- Align default option semantics with preview and apply
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/charge/ChargeServiceImpl.java- Apply formal water-adjust options and record option values in operation log details.
Backend test files:
sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/AccountingAdjustActionControllerTest.javasw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/accountingadjust/accountProcess/AccountingAdjustProcessServiceImplTest.javasw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/charge/ChargeServiceAccountingAdjustTest.javasw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/accountingadjust/pricediff/PriceDiffFormalizationServiceTest.javasw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/accountingadjust/pricediff/PriceDiffWriteBackServiceTest.java
Docs files:
../water-docs/docs/evidence/rev004-accounting/rev004-unsold-preview-options-formalization-2026-06-19.md
Task 0: Preserve Current Usage-Adjustment Fix
Files:
-
Modify:
../water-docs/docs/evidence/rev004-accounting/rev004-unsold-usage-adjust-zero-and-recovery-2026-06-19.md -
Existing backend changes:
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/trial/UnsoldTrialPreviewServiceImpl.javasw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/charge/ChargeServiceImpl.javasw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/accountingadjust/trial/UnsoldTrialPreviewServiceImplTest.javasw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/charge/ChargeServiceAccountingAdjustTest.java
-
Step 1: Confirm the current backend diff only contains the prior usage-adjustment fix
Run:
git status --short
git diff -- 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/charge/ChargeServiceImpl.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/charge/ChargeServiceAccountingAdjustTest.java
Expected:
-
UnsoldTrialPreviewServiceImplaccepts zero target water and rejects negative target water. -
ChargeServiceImplallows target water above read/check water only for supplementary/recovery reasons. -
Tests cover zero-water preview and supplementary reading recovery.
-
Step 2: Re-run the already-passing validation
Run:
mvn -pl sw-business/sw-business-server -am \
-Dtest=UnsoldTrialPreviewServiceImplTest,ChargeServiceAccountingAdjustTest \
-Dsurefire.failIfNoSpecifiedTests=false test
Expected:
Tests run: 36, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS
- Step 3: Add evidence for the prior usage-adjustment fix
Create ../water-docs/docs/evidence/rev004-accounting/rev004-unsold-usage-adjust-zero-and-recovery-2026-06-19.md:
# 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.
风险说明
- 本次未放开已收费、已开票、已结账、缺少抄表依据、缺少附件等既有校验。
- 补抄追收白名单为后端兼容策略,后续可在正式原因字典稳定后收敛为字典值校验。
- [ ] **Step 4: Commit the prior usage-adjustment fix**
Run:
```bash
git add \
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/charge/ChargeServiceImpl.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/charge/ChargeServiceAccountingAdjustTest.java
git commit -m "fix(accounting): relax unsold usage adjust water validation"
Then in docs repo:
git -C ../water-docs add docs/evidence/rev004-accounting/rev004-unsold-usage-adjust-zero-and-recovery-2026-06-19.md
git -C ../water-docs commit -m "docs(accounting): record unsold usage adjust validation evidence"
Expected:
- Backend and docs commits are separate.
git status --shortin backend is clean before Task 1 starts.
Task 1: Add Water-Adjust Option Fields to Request Chain
Files:
-
Modify:
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/vo/AccountingAdjustUnsoldAdjustSubmitReqVO.java -
Modify:
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/vo/AccountingAdjustSubmitReqVO.java -
Modify:
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/charge/vo/AccountingAdjustReqVO.java -
Modify:
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/AccountingAdjustActionController.java -
Modify:
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/accountProcess/AccountingAdjustProcessServiceImpl.java -
Test:
sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/accountingadjust/accountProcess/AccountingAdjustProcessServiceImplTest.java -
Step 1: Write failing service mapping test
Add this test to AccountingAdjustProcessServiceImplTest near the existing createUnsoldAdjust_shouldDelegateUsageAdjustToUnifiedChargeService test:
@Test
void createUnsoldAdjust_shouldPassWaterOptionFlagsToUnifiedChargeService() {
AccountingAdjustSubmitReqVO reqVO = new AccountingAdjustSubmitReqVO();
reqVO.setChargeId(15801L);
reqVO.setTargetBillWater(new BigDecimal("0.000"));
reqVO.setReasonCode("REV004_USAGE_FIX");
reqVO.setReason("零水量更正");
reqVO.setAttachmentRefs(List.of("proof-001"));
reqVO.setUpdateAccumulated(true);
reqVO.setUpdateBaseCode(true);
reqVO.setCurrentReading(new BigDecimal("1234.000"));
AccountingAdjustRespVO expected = AccountingAdjustRespVO.of(
"REV004-15801-20260619110000",
null,
"SUCCESS",
false,
"NOT_REQUIRED",
"UPDATED",
"REV004-15801-20260619110000",
"水量调整成功"
);
when(chargeService.adjustAccounting(any(AccountingAdjustReqVO.class))).thenReturn(expected);
AccountingAdjustRespVO actual = service.createUnsoldAdjust(reqVO);
assertEquals("SUCCESS", actual.getResultStatus());
ArgumentCaptor<AccountingAdjustReqVO> captor = ArgumentCaptor.forClass(AccountingAdjustReqVO.class);
verify(chargeService).adjustAccounting(captor.capture());
AccountingAdjustReqVO coreReq = captor.getValue();
assertEquals(new BigDecimal("0.000"), coreReq.getTargetBillWater());
assertEquals(Boolean.TRUE, coreReq.getUpdateAccumulated());
assertEquals(Boolean.TRUE, coreReq.getUpdateBaseCode());
assertEquals(new BigDecimal("1234.000"), coreReq.getCurrentReading());
}
Ensure imports exist:
import org.mockito.ArgumentCaptor;
- Step 2: Run test to verify it fails
Run:
mvn -pl sw-business/sw-business-server -am \
-Dtest=AccountingAdjustProcessServiceImplTest#createUnsoldAdjust_shouldPassWaterOptionFlagsToUnifiedChargeService \
-Dsurefire.failIfNoSpecifiedTests=false test
Expected: compile failure or assertion failure because getUpdateAccumulated and getUpdateBaseCode are not present or not mapped.
- Step 3: Add fields to request VOs
In AccountingAdjustUnsoldAdjustSubmitReqVO, add after currentReading:
@Schema(description = "是否更新累计量;未传默认false", example = "true")
private Boolean updateAccumulated;
@Schema(description = "是否更新底码;未传默认false", example = "true")
private Boolean updateBaseCode;
In AccountingAdjustSubmitReqVO, add near currentReading:
private Boolean updateAccumulated;
private Boolean updateBaseCode;
In AccountingAdjustReqVO, add after currentReading:
@Schema(description = "是否更新累计量,仅 legacy-only 水量调整使用;未传默认 false", example = "true")
private Boolean updateAccumulated;
@Schema(description = "是否更新底码,仅 legacy-only 水量调整使用;未传默认 false", example = "true")
private Boolean updateBaseCode;
- Step 4: Map fields through Controller and process service
In AccountingAdjustActionController.toUnsoldSubmitReq(AccountingAdjustUnsoldAdjustSubmitReqVO reqVO), add:
target.setUpdateAccumulated(reqVO.getUpdateAccumulated());
target.setUpdateBaseCode(reqVO.getUpdateBaseCode());
In AccountingAdjustProcessServiceImpl.buildLegacyAdjustReq(...), add:
coreReq.setUpdateAccumulated(reqVO.getUpdateAccumulated());
coreReq.setUpdateBaseCode(reqVO.getUpdateBaseCode());
- Step 5: Run test to verify it passes
Run:
mvn -pl sw-business/sw-business-server -am \
-Dtest=AccountingAdjustProcessServiceImplTest#createUnsoldAdjust_shouldPassWaterOptionFlagsToUnifiedChargeService \
-Dsurefire.failIfNoSpecifiedTests=false test
Expected:
BUILD SUCCESS
- Step 6: Commit Task 1
Run:
git add \
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/vo/AccountingAdjustUnsoldAdjustSubmitReqVO.java \
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/vo/AccountingAdjustSubmitReqVO.java \
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/charge/vo/AccountingAdjustReqVO.java \
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/controller/admin/accountingadjust/accountProcess/AccountingAdjustActionController.java \
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/accountProcess/AccountingAdjustProcessServiceImpl.java \
sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/accountingadjust/accountProcess/AccountingAdjustProcessServiceImplTest.java
git commit -m "feat(accounting): carry water preview options into unsold submit"
Task 2: Apply Water-Adjust Options in ChargeService
Files:
-
Modify:
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/charge/ChargeServiceImpl.java -
Test:
sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/charge/ChargeServiceAccountingAdjustTest.java -
Step 1: Write failing test for updateBaseCode requiring currentReading
Add to ChargeServiceAccountingAdjustTest:
@Test
void testAdjustAccounting_usageUpdateBaseCodeRequiresCurrentReading() {
ChargeDO charge = buildCharge(123L, PayStateEnum.UNPAID.getValue(), new BigDecimal("18.50"));
charge.setRecordId(923L);
charge.setReadWater(new BigDecimal("30.000"));
when(chargeMapper.selectById(123L)).thenReturn(charge);
ReadingDataDO readingData = new ReadingDataDO();
readingData.setId(923L);
readingData.setReadWater(new BigDecimal("30.000"));
readingData.setCheckWater(new BigDecimal("30.000"));
when(readingDataService.getReadingDataByIds(Collections.singletonList(923L)))
.thenReturn(Collections.singletonList(readingData));
when(chargeDetailService.getChargeDetailsByFeeIds(Collections.singletonList(123L)))
.thenReturn(Collections.emptyList());
AccountingAdjustReqVO reqVO = new AccountingAdjustReqVO();
reqVO.setChargeId(123L);
reqVO.setAdjustType("USAGE");
reqVO.setTargetBillWater(new BigDecimal("20.000"));
reqVO.setReasonCode("REV004_USAGE_FIX");
reqVO.setReason("底码更正");
reqVO.setAttachmentRefs(List.of("proof-001"));
reqVO.setUpdateBaseCode(true);
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
() -> chargeService.adjustAccounting(reqVO));
assertEquals("更新底码必须传入本次抄码", ex.getMessage());
}
- Step 2: Write failing test for updateBaseCode and updateAccumulated effects
Add to ChargeServiceAccountingAdjustTest:
@Test
void testAdjustAccounting_usageOptionsUpdateReadingAndAccumulatedWater() {
ChargeDO charge = buildCharge(124L, PayStateEnum.UNPAID.getValue(), new BigDecimal("18.50"));
charge.setRecordId(924L);
charge.setReadWater(new BigDecimal("30.000"));
charge.setReading(new BigDecimal("1000.000"));
charge.setTotalWater(new BigDecimal("500.000"));
when(chargeMapper.selectById(124L)).thenReturn(charge);
ReadingDataDO readingData = new ReadingDataDO();
readingData.setId(924L);
readingData.setReadWater(new BigDecimal("30.000"));
readingData.setCheckWater(new BigDecimal("30.000"));
when(readingDataService.getReadingDataByIds(Collections.singletonList(924L)))
.thenReturn(Collections.singletonList(readingData));
when(chargeDetailService.getChargeDetailsByFeeIds(Collections.singletonList(124L)))
.thenReturn(Collections.emptyList());
CustDO cust = new CustDO();
cust.setId(1L);
cust.setPriceTemplateCode("PRICE-001");
when(custService.getCust(1L)).thenReturn(cust);
when(custWaterUseSchemeService.getCustWaterUseSchemeByCustId(1L)).thenReturn(null);
PriceTemplateDO priceTemplate = new PriceTemplateDO();
priceTemplate.setId(11L);
priceTemplate.setCode("PRICE-001");
priceTemplate.setAdjustmentSnapCode("SNAP-001");
when(priceTemplateService.getPriceTemplatesByCodes(Collections.singletonList("PRICE-001")))
.thenReturn(Collections.singletonList(priceTemplate));
PriceCostAdjustmentDO costAdjustment = new PriceCostAdjustmentDO();
costAdjustment.setId(21L);
costAdjustment.setCostComponentCode(ChargeCalculateHelper.COST_CODE_WATER);
when(priceCostAdjustmentService.getPriceCostAdjustmentsByTemplateIds(Collections.singletonList(11L)))
.thenReturn(Collections.singletonList(costAdjustment));
PriceTierAdjustmentDO tierAdjustment = new PriceTierAdjustmentDO();
tierAdjustment.setId(31L);
tierAdjustment.setCostAdjustmentId(21L);
tierAdjustment.setTierLevel(1);
tierAdjustment.setStartVolume(BigDecimal.ZERO);
tierAdjustment.setEndVolume(new BigDecimal("999999"));
tierAdjustment.setPrice(new BigDecimal("1.0000"));
when(priceTierAdjustmentService.getPriceTierAdjustmentsByCostAdjustmentIds(Collections.singletonList(21L)))
.thenReturn(Collections.singletonList(tierAdjustment));
AccountingAdjustReqVO reqVO = new AccountingAdjustReqVO();
reqVO.setChargeId(124L);
reqVO.setAdjustType("USAGE");
reqVO.setTargetBillWater(new BigDecimal("20.000"));
reqVO.setReasonCode("REV004_USAGE_FIX");
reqVO.setReason("同步累计量和底码");
reqVO.setAttachmentRefs(List.of("proof-001"));
reqVO.setUpdateBaseCode(true);
reqVO.setUpdateAccumulated(true);
reqVO.setCurrentReading(new BigDecimal("1010.000"));
AccountingAdjustRespVO respVO = chargeService.adjustAccounting(reqVO);
assertEquals("SUCCESS", respVO.getResultStatus());
assertEquals(new BigDecimal("1010.000"), charge.getReading());
assertEquals(new BigDecimal("510.000"), charge.getTotalWater());
verify(chargeMapper).updateById(charge);
}
- Step 3: Run tests to verify they fail
Run:
mvn -pl sw-business/sw-business-server -am \
-Dtest=ChargeServiceAccountingAdjustTest#testAdjustAccounting_usageUpdateBaseCodeRequiresCurrentReading+testAdjustAccounting_usageOptionsUpdateReadingAndAccumulatedWater \
-Dsurefire.failIfNoSpecifiedTests=false test
Expected: at least one failure because updateBaseCode and updateAccumulated are not implemented.
- Step 4: Implement validation and write-back
In ChargeServiceImpl.validateUsageAdjust(...), after reason/attachment checks are available and before recalculation, add:
if (Boolean.TRUE.equals(reqVO.getUpdateBaseCode()) && reqVO.getCurrentReading() == null) {
throw new IllegalArgumentException("更新底码必须传入本次抄码");
}
In ChargeServiceImpl.handleUsageAdjust(...), preserve original water before recalculation:
BigDecimal beforeBillWater = state.getChargeDO().getBillWater();
After copyRecalculatedFees(recalculated, state.getChargeDO());, add:
applyUsageAdjustOptions(state.getChargeDO(), state.getReqVO(), beforeBillWater);
Add helper method near copyRecalculatedFees:
private void applyUsageAdjustOptions(ChargeDO chargeDO, AccountingAdjustReqVO reqVO, BigDecimal beforeBillWater) {
if (Boolean.TRUE.equals(reqVO.getUpdateBaseCode())) {
chargeDO.setReading(reqVO.getCurrentReading());
}
if (Boolean.TRUE.equals(reqVO.getUpdateAccumulated())) {
BigDecimal beforeTotalWater = chargeDO.getTotalWater();
if (beforeTotalWater != null) {
BigDecimal oldWater = beforeBillWater == null ? BigDecimal.ZERO : beforeBillWater;
BigDecimal newWater = reqVO.getTargetBillWater() == null ? BigDecimal.ZERO : reqVO.getTargetBillWater();
chargeDO.setTotalWater(beforeTotalWater.add(newWater.subtract(oldWater)));
}
}
}
- Step 5: Include option values in operation log details
In the operation-log detail builder for accounting adjustments in ChargeServiceImpl, add details for:
addOperatLogDetail(dto, "updateAccumulated", "是否更新累计量", null, reqVO.getUpdateAccumulated(), EnumColumnType.NORMAL_TYPE);
addOperatLogDetail(dto, "updateBaseCode", "是否更新底码", null, reqVO.getUpdateBaseCode(), EnumColumnType.NORMAL_TYPE);
- Step 6: Run Task 2 tests
Run:
mvn -pl sw-business/sw-business-server -am \
-Dtest=ChargeServiceAccountingAdjustTest \
-Dsurefire.failIfNoSpecifiedTests=false test
Expected:
BUILD SUCCESS
- Step 7: Commit Task 2
Run:
git add \
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/charge/ChargeServiceImpl.java \
sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/charge/ChargeServiceAccountingAdjustTest.java
git commit -m "feat(accounting): apply water adjust preview options"
Task 3: Add Price-Diff Options to Formal Object Chain
Files:
-
Modify:
sw-business/sw-business-api/src/main/java/cn/com/emsoft/sw/business/api/accountingadjust/dto/PriceDiffAdjustCreateReqDTO.java -
Modify:
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/api/accountingadjust/PriceDiffAdjustInternalApiImpl.java -
Modify:
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/accountProcess/AccountingAdjustProcessServiceImpl.java -
Modify:
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/dal/dataobject/pricediffadjustdetail/PriceDiffAdjustDetailDO.java -
Modify:
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/pricediff/PriceDiffFormalizationService.java -
Test:
sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/accountingadjust/pricediff/PriceDiffFormalizationServiceTest.java -
Step 1: Write failing formalization test
In PriceDiffFormalizationServiceTest, add:
@Test
void ensurePendingRecord_shouldPersistPriceDiffOptionFlags() {
ChargeDO sourceCharge = new ChargeDO();
sourceCharge.setId(15801L);
sourceCharge.setCustCode("C001");
sourceCharge.setBillMonth(202606);
sourceCharge.setBillAmount(new BigDecimal("100.00"));
sourceCharge.setExtendedAmount(new BigDecimal("100.00"));
sourceCharge.setPriceTemplateCode("OLD");
sourceCharge.setAdjustmentSnapCode("OLD-SNAP");
sourceCharge.setPayState(PayStateEnum.UNPAID.getValue());
AccountingAdjustSubmitReqVO reqVO = new AccountingAdjustSubmitReqVO();
reqVO.setChargeId(15801L);
reqVO.setPriceDiffAmount(new BigDecimal("15.50"));
reqVO.setReasonCode("REV004_PRICE_DIFF");
reqVO.setReason("价差调整");
reqVO.setNewPriceTemplateCode("NEW");
reqVO.setAdjustmentSnapCode("NEW-SNAP");
reqVO.setIsLadder(false);
reqVO.setCalculateGarbageFee(false);
reqVO.setUpdateAccumulatedVolume(false);
service.ensurePendingRecord("REV004-PD-C001-15801-20260619110000", sourceCharge, reqVO);
ArgumentCaptor<PriceDiffAdjustDetailDO> detailCaptor = ArgumentCaptor.forClass(PriceDiffAdjustDetailDO.class);
verify(priceDiffAdjustDetailMapper).insert(detailCaptor.capture());
PriceDiffAdjustDetailDO detail = detailCaptor.getValue();
assertEquals(Short.valueOf((short) 0), detail.getIsLadder());
assertEquals(Short.valueOf((short) 0), detail.getCalculateGarbageFee());
assertEquals(Short.valueOf((short) 0), detail.getUpdateAccumulatedVolume());
}
Ensure imports exist:
import org.mockito.ArgumentCaptor;
- Step 2: Run test to verify it fails
Run:
mvn -pl sw-business/sw-business-server -am \
-Dtest=PriceDiffFormalizationServiceTest#ensurePendingRecord_shouldPersistPriceDiffOptionFlags \
-Dsurefire.failIfNoSpecifiedTests=false test
Expected: compile failure because getCalculateGarbageFee and getUpdateAccumulatedVolume are not defined on PriceDiffAdjustDetailDO.
- Step 3: Extend DTO and DO
In PriceDiffAdjustCreateReqDTO, add:
private Boolean calculateGarbageFee;
private Boolean updateAccumulatedVolume;
In PriceDiffAdjustDetailDO, add:
private Short calculateGarbageFee;
private Short updateAccumulatedVolume;
- Step 4: Pass options into formalization VO
In AccountingAdjustProcessServiceImpl.createUnsoldPriceDiff(...), after dto.setIsLadder(reqVO.getIsLadder());, add:
dto.setCalculateGarbageFee(reqVO.getCalculateGarbageFee());
dto.setUpdateAccumulatedVolume(reqVO.getUpdateAccumulatedVolume());
In PriceDiffAdjustInternalApiImpl.create(...), after vo.setIsLadder(reqDTO.getIsLadder());, add:
vo.setCalculateGarbageFee(reqDTO.getCalculateGarbageFee());
vo.setUpdateAccumulatedVolume(reqDTO.getUpdateAccumulatedVolume());
- Step 5: Persist options in formal detail
In PriceDiffFormalizationService.ensurePendingRecord(...), set the new fields in PriceDiffAdjustDetailDO.builder():
.isLadder(toShortDefaultTrue(reqVO.getIsLadder()))
.calculateGarbageFee(toShortDefaultTrue(reqVO.getCalculateGarbageFee()))
.updateAccumulatedVolume(toShortDefaultTrue(reqVO.getUpdateAccumulatedVolume()))
Add helper method:
private Short toShortDefaultTrue(Boolean value) {
return Boolean.FALSE.equals(value) ? (short) 0 : (short) 1;
}
Use this helper for isLadder as well, replacing the current Boolean.TRUE.equals(...) ? 1 : 0 default so missing values default to true.
- Step 6: Run formalization test
Run:
mvn -pl sw-business/sw-business-server -am \
-Dtest=PriceDiffFormalizationServiceTest#ensurePendingRecord_shouldPersistPriceDiffOptionFlags \
-Dsurefire.failIfNoSpecifiedTests=false test
Expected:
BUILD SUCCESS
- Step 7: Commit Task 3
Run:
git add \
sw-business/sw-business-api/src/main/java/cn/com/emsoft/sw/business/api/accountingadjust/dto/PriceDiffAdjustCreateReqDTO.java \
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/api/accountingadjust/PriceDiffAdjustInternalApiImpl.java \
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/accountProcess/AccountingAdjustProcessServiceImpl.java \
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/dal/dataobject/pricediffadjustdetail/PriceDiffAdjustDetailDO.java \
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/pricediff/PriceDiffFormalizationService.java \
sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/accountingadjust/pricediff/PriceDiffFormalizationServiceTest.java
git commit -m "feat(accounting): persist price diff preview options"
Task 4: Apply Price-Diff Options During Write-Back
Files:
-
Modify:
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/AccountingAdjustActionServiceImpl.java -
Modify:
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/pricediff/PriceDiffWriteBackService.java -
Modify:
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/charge/ChargeServiceImpl.java -
Test:
sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/accountingadjust/pricediff/PriceDiffWriteBackServiceTest.java -
Step 1: Write failing test for calculateGarbageFee default true
In PriceDiffWriteBackServiceTest, add or adapt a test:
@Test
void cancelAndCreate_shouldDefaultCalculateGarbageFeeToTrueWhenOptionMissing() {
ChargeDO original = new ChargeDO();
original.setId(15801L);
original.setPayState(PayStateEnum.UNPAID.getValue());
original.setBillWater(new BigDecimal("10.000"));
original.setTotalWater(new BigDecimal("100.000"));
when(chargeMapper.selectById(15801L)).thenReturn(original);
PriceTemplateDO template = new PriceTemplateDO();
template.setCode("NEW");
template.setAdjustmentSnapCode("SNAP-NEW");
when(priceTemplateService.getPriceTemplateByCode("NEW")).thenReturn(template);
when(priceDiffPreviewService.recalculate(eq(original), eq(template), eq(true), eq(true)))
.thenReturn(Map.of(
"101", new BigDecimal("20.00"),
"103", new BigDecimal("3.00")
));
Long newChargeId = service.cancelAndCreate(15801L, "NEW", "SNAP-NEW", null, null, null, "价差调整");
assertNull(newChargeId);
ArgumentCaptor<ChargeDO> insertCaptor = ArgumentCaptor.forClass(ChargeDO.class);
verify(chargeMapper).insert(insertCaptor.capture());
assertEquals(new BigDecimal("3.00"), insertCaptor.getValue().getGarbageFee());
verify(priceDiffPreviewService).recalculate(original, template, true, true);
}
The unit test mock does not assign an ID during chargeMapper.insert(newCharge), so newChargeId remains null in this test.
- Step 2: Write failing test for calculateGarbageFee false
Add:
@Test
void cancelAndCreate_shouldExcludeGarbageFeeWhenOptionFalse() {
ChargeDO original = new ChargeDO();
original.setId(15802L);
original.setPayState(PayStateEnum.UNPAID.getValue());
original.setBillWater(new BigDecimal("10.000"));
when(chargeMapper.selectById(15802L)).thenReturn(original);
PriceTemplateDO template = new PriceTemplateDO();
template.setCode("NEW");
template.setAdjustmentSnapCode("SNAP-NEW");
when(priceTemplateService.getPriceTemplateByCode("NEW")).thenReturn(template);
when(priceDiffPreviewService.recalculate(eq(original), eq(template), eq(false), eq(false)))
.thenReturn(Map.of("101", new BigDecimal("20.00")));
service.cancelAndCreate(15802L, "NEW", "SNAP-NEW", false, false, false, "价差调整");
ArgumentCaptor<ChargeDO> insertCaptor = ArgumentCaptor.forClass(ChargeDO.class);
verify(chargeMapper).insert(insertCaptor.capture());
assertEquals(BigDecimal.ZERO, insertCaptor.getValue().getGarbageFee());
verify(priceDiffPreviewService).recalculate(original, template, false, false);
}
- Step 3: Run tests to verify failure
Run:
mvn -pl sw-business/sw-business-server -am \
-Dtest=PriceDiffWriteBackServiceTest \
-Dsurefire.failIfNoSpecifiedTests=false test
Expected: failure because calculateGarbageFee currently defaults to false in write-back when null.
- Step 4: Fix write-back default semantics
In PriceDiffWriteBackService.cancelAndCreate(...), replace:
Map<String, BigDecimal> newFeeMap = priceDiffPreviewService.recalculate(original, newTemplate, isLadder, calculateGarbageFee != null && calculateGarbageFee);
with:
Boolean effectiveIsLadder = Boolean.FALSE.equals(isLadder) ? false : true;
Boolean effectiveCalculateGarbageFee = Boolean.FALSE.equals(calculateGarbageFee) ? false : true;
Map<String, BigDecimal> newFeeMap = priceDiffPreviewService.recalculate(
original, newTemplate, effectiveIsLadder, effectiveCalculateGarbageFee);
- Step 5: Apply updateAccumulatedVolume behavior
In PriceDiffWriteBackService.cancelAndCreate(...), after newCharge.setTotalWater(source.getTotalWater()) is copied by cloneChargeMetadata, set:
if (Boolean.FALSE.equals(updateAccumulatedVolume)) {
newCharge.setTotalWater(original.getTotalWater());
}
Do not alter customer-level accumulated volume in this task because this service currently only clones and inserts ChargeDO.
- Step 6: Write price-diff option values to operation log details
In ChargeServiceImpl.recordAccountingAdjustLog(...), add these log details near priceDiffAmount:
addOperatLogDetail(dto, "newPriceTemplateCode", "新水价模板代码", null, reqVO.getNewPriceTemplateCode(), EnumColumnType.NORMAL_TYPE);
addOperatLogDetail(dto, "adjustmentSnapCode", "调价号", null, reqVO.getAdjustmentSnapCode(), EnumColumnType.NORMAL_TYPE);
addOperatLogDetail(dto, "isLadder", "是否阶梯计费", null, reqVO.getIsLadder(), EnumColumnType.NORMAL_TYPE);
addOperatLogDetail(dto, "calculateGarbageFee", "是否计算垃圾费", null, reqVO.getCalculateGarbageFee(), EnumColumnType.NORMAL_TYPE);
addOperatLogDetail(dto, "updateAccumulatedVolume", "是否更新累计量", null, reqVO.getUpdateAccumulatedVolume(), EnumColumnType.NORMAL_TYPE);
AccountingAdjustActionServiceImpl.applyPriceDiffWriteBack(...) already reads these exact column names:
Boolean isLadder = parseBoolean(detailColumnValue(detail, "isLadder"));
Boolean calculateGarbageFee = parseBoolean(detailColumnValue(detail, "calculateGarbageFee"));
Boolean updateAccumulatedVolume = parseBoolean(detailColumnValue(detail, "updateAccumulatedVolume"));
- Step 7: Run write-back tests
Run:
mvn -pl sw-business/sw-business-server -am \
-Dtest=PriceDiffWriteBackServiceTest \
-Dsurefire.failIfNoSpecifiedTests=false test
Expected:
BUILD SUCCESS
- Step 8: Commit Task 4
Run:
git add \
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/AccountingAdjustActionServiceImpl.java \
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/accountingadjust/pricediff/PriceDiffWriteBackService.java \
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/charge/ChargeServiceImpl.java \
sw-business/sw-business-server/src/test/java/cn/com/emsoft/sw/business/service/accountingadjust/pricediff/PriceDiffWriteBackServiceTest.java
git commit -m "fix(accounting): align price diff option writeback"
Task 5: Add Database Migration for Price-Diff Detail Options
Files:
-
Create:
sql/rev004/REV004_price_diff_option_fields_deploy.sql -
Modify:
../water-docs/docs/evidence/rev004-accounting/rev004-unsold-preview-options-formalization-2026-06-19.md -
Step 1: Add deploy SQL
Create sql/rev004/REV004_price_diff_option_fields_deploy.sql:
ALTER TABLE biz_price_diff_adjust_detail
ADD calculate_garbage_fee SMALLINT DEFAULT 1 NULL;
COMMENT ON COLUMN biz_price_diff_adjust_detail.calculate_garbage_fee IS '是否计算垃圾费:0-否,1-是';
ALTER TABLE biz_price_diff_adjust_detail
ADD update_accumulated_volume SMALLINT DEFAULT 1 NULL;
COMMENT ON COLUMN biz_price_diff_adjust_detail.update_accumulated_volume IS '是否更新累计量:0-否,1-是';
- Step 2: Run compile to validate entity changes
Run:
mvn -pl sw-business/sw-business-server -am -DskipTests compile
Expected:
BUILD SUCCESS
- Step 3: Commit Task 5
Run:
git add \
sql/rev004/REV004_price_diff_option_fields_deploy.sql \
sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/dal/dataobject/pricediffadjustdetail/PriceDiffAdjustDetailDO.java
git commit -m "chore(accounting): add price diff option fields ddl"
Task 6: End-to-End Regression and Evidence
Files:
-
Modify:
../water-docs/docs/evidence/rev004-accounting/rev004-unsold-preview-options-formalization-2026-06-19.md -
Step 1: Run focused unit tests
Run:
mvn -pl sw-business/sw-business-server -am \
-Dtest=AccountingAdjustProcessServiceImplTest,ChargeServiceAccountingAdjustTest,PriceDiffFormalizationServiceTest,PriceDiffWriteBackServiceTest \
-Dsurefire.failIfNoSpecifiedTests=false test
Expected:
BUILD SUCCESS
- Step 2: Run existing trial preview tests
Run:
mvn -pl sw-business/sw-business-server -am \
-Dtest=UnsoldTrialPreviewServiceImplTest \
-Dsurefire.failIfNoSpecifiedTests=false test
Expected:
BUILD SUCCESS
- Step 3: Add evidence document
Create or update ../water-docs/docs/evidence/rev004-accounting/rev004-unsold-preview-options-formalization-2026-06-19.md:
# REV-004 未销调整预算参数正式化验证记录
## 变更范围
- 水量调整正式提交承接 `updateAccumulated`、`updateBaseCode`。
- 价差调整正式对象承接 `isLadder`、`calculateGarbageFee`、`updateAccumulatedVolume`。
- 价差正式回写与预算使用一致默认值:未传 `isLadder` 和 `calculateGarbageFee` 时按 `true` 处理。
## 验证命令
```bash
mvn -pl sw-business/sw-business-server -am \
-Dtest=AccountingAdjustProcessServiceImplTest,ChargeServiceAccountingAdjustTest,PriceDiffFormalizationServiceTest,PriceDiffWriteBackServiceTest \
-Dsurefire.failIfNoSpecifiedTests=false test
mvn -pl sw-business/sw-business-server -am \
-Dtest=UnsoldTrialPreviewServiceImplTest \
-Dsurefire.failIfNoSpecifiedTests=false test
验证结果
- 聚焦账务调整测试:通过。
- 未销试算测试:通过。
数据库变更
- 新增
sql/rev004/REV004_price_diff_option_fields_deploy.sql。 biz_price_diff_adjust_detail新增calculate_garbage_fee与update_accumulated_volume。
风险说明
- 水量调整仍使用 legacy-only 即时回写,checkbox 审计通过操作日志明细承接。
- 价差正式对象已承接 checkbox;部署前需在目标库执行新增字段 SQL。
- [ ] **Step 4: Validate evidence document**
Run:
```bash
make validate-file FILE=docs/evidence/rev004-accounting/rev004-unsold-preview-options-formalization-2026-06-19.md
from ../water-docs.
Expected:
文档验证通过
- Step 5: Commit evidence
Run:
git -C ../water-docs add docs/evidence/rev004-accounting/rev004-unsold-preview-options-formalization-2026-06-19.md
git -C ../water-docs commit -m "docs(accounting): record unsold preview option evidence"
Task 7: Final Status Check
Files:
-
No file edits.
-
Step 1: Check backend status
Run:
git status --short
git log --oneline -5
Expected:
-
No unstaged backend files.
-
Recent commits include usage validation, water option chain, price-diff option persistence, price-diff write-back, and DDL.
-
Step 2: Check docs status
Run:
git -C ../water-docs status --short
git -C ../water-docs log --oneline -5
Expected:
-
No unstaged docs files.
-
Recent commits include design, plan, and evidence.
-
Step 3: Summarize implementation
Report:
Implemented REV-004 unsold preview option formalization.
Backend commits:
- Include the short SHA and subject for each backend commit from `git log --oneline -6`.
Docs commits:
- Include the short SHA and subject for each docs commit from `git -C ../water-docs log --oneline -3`.
Validation:
- Focused Maven command: BUILD SUCCESS
- Preview Maven command: BUILD SUCCESS
Self-Review
Spec coverage:
- Water preview options entering formal submit: covered by Tasks 1 and 2.
- Price-diff preview options entering formal object and write-back: covered by Tasks 3 and 4.
- Data persistence for price-diff formal detail: covered by Task 5.
- Evidence under
docs/evidence/rev004-accounting/: covered by Tasks 0 and 6. - Validation and final status: covered by Tasks 6 and 7.
Plan specificity scan:
- The plan contains concrete file paths, commands, expected outputs, and code snippets for each implementation task.
- No implementation step depends on unnamed additional work.
Type consistency:
- Water option field names are
updateAccumulatedandupdateBaseCodeacross endpoint VO, unified submit VO, core charge VO, and service logic. - Price-diff option field names are
isLadder,calculateGarbageFee, andupdateAccumulatedVolumeacross submit VO, DTO, formal detail, and write-back.