fujian_water_biz_doc/superpowers/plans/2026-06-10-unsold-adjust-bpm.md
tangweijie 3eccab2cf9 docs: 文档治理统一 — AGENTS.md 生命周期规则 + 模块归档 + DDL 修正
1. AGENTS.md 更新
   - water-docs: 新增 specs/ 与 docs/design/ 生命周期规则章节
   - water-backend: 更新协作引用(建设期/建成后、evidence 模块化)

2. specs/ 重复合并
   - 006-reminder-event-design 合并入 003-rev006-reminder-event-design
   - 001-rev004-accounting 删除冗余 data-model.md + contracts/
   - 002-rev005-invoice-flow 删除冗余 data-model.md + contracts/

3. evidence 按模块归档
   - 35 个 REV-004 文件归入 evidence/rev004-accounting/
   - 7 个通用 bugfix 文件归入 evidence/bugfix/ 和 bugfix/frontend/
   - 新建 rev005-invoice/、rev006-reminder/、rev007-statistics/ 目录

4. guides/ 清理
   - 14 个 REV004_*.md 移入 evidence/rev004-accounting/

5. 遗留文件处理
   - docs/research/ 归档到 Archive/06_Migration_Plans/
   - backend-check detached worktrees 清理

6. 交叉引用修复
   - 006-reminder-event-design → 003-rev006-reminder-event-design
   - docs/guides/REV004_ → docs/evidence/rev004-accounting/REV004_

7. DB 设计文档修正(01_Database_Design.md)
   - biz_invoice 明确为开票配置表,非发票记录表
   - 新增 biz_invoice_record 为发票申请/结果主表
   - 新增 biz_charge_invoice_rel 账单-发票关联说明
   - REV-005 承接口径表名全部修正

8. 发票审计证据
   - 新增 evidence/rev005-invoice/2026-06-16-invoice-document-audit.md
2026-06-16 11:47:16 +08:00

791 lines
30 KiB
Markdown

# Unsold Adjustment BPM Workflow Integration
> **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:** Wire BPM approval flows into 5 unsold adjustment types (split, bad debt, writtenoff, price diff, late fee reduce) that currently only set PENDING_APPROVAL flags without creating actual BPM process instances.
**Architecture:** Follow the existing prestorage adjust BPM pattern — Internal API (Feign) in sw-business-api, implementation in sw-business-server, BPM ProcessInit/BusinessBridge/ApprovalListener/Controller/VOs in sw-module-bpm. Frontend forms switch from calling `/business/accounting-adjust/unsold-*-submit` to calling new `/bpm/*-adjust/create` endpoints.
**Tech Stack:** Java 17, Spring Boot, Feign, MyBatis-Plus, Vue 3 + Element Plus
**Pattern reference:** Prestorage adjust files in sw-module-bpm/service/prestorageadjust/ and sw-business/api/accountingadjust/PrestorageAdjustInternalApi.java
---
## Shared Infrastructure (once)
### Task 0: Create shared execute DTO
**Files:**
- Create: `sw-business/sw-business-api/src/main/java/cn/com/emsoft/sw/business/api/accountingadjust/dto/AdjustExecuteReqDTO.java`
- Create: `sw-business/sw-business-api/src/main/java/cn/com/emsoft/sw/business/api/accountingadjust/dto/AdjustExecuteRespDTO.java`
- [ ] **Step 1: Create AdjustExecuteReqDTO**
```java
package cn.com.emsoft.sw.business.api.accountingadjust.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AdjustExecuteReqDTO {
private String adjustmentNo;
private String approvalStatus; // APPROVE / REJECT
private String approvalComment;
private String operatorId;
}
```
- [ ] **Step 2: Create AdjustExecuteRespDTO**
```java
package cn.com.emsoft.sw.business.api.accountingadjust.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AdjustExecuteRespDTO {
private String resultStatus; // SUCCESS / FAILED / SKIPPED
private String adjustmentNo;
private String message;
}
```
- [ ] **Step 3: Commit**
```bash
git add sw-business/sw-business-api/src/main/java/cn/com/emsoft/sw/business/api/accountingadjust/dto/AdjustExecuteReqDTO.java \
sw-business/sw-business-api/src/main/java/cn/com/emsoft/sw/business/api/accountingadjust/dto/AdjustExecuteRespDTO.java
git commit -m "feat: add shared execute DTOs for BPM approval callbacks"
```
---
## Type 1: Split Adjust (分账调整)
### Task 1.1: Split Adjust Internal API + DTOs
**Files:**
- Create: `sw-business/sw-business-api/src/main/java/cn/com/emsoft/sw/business/api/accountingadjust/dto/SplitAdjustCreateReqDTO.java`
- Create: `sw-business/sw-business-api/src/main/java/cn/com/emsoft/sw/business/api/accountingadjust/dto/SplitAdjustCreateRespDTO.java`
- Create: `sw-business/sw-business-api/src/main/java/cn/com/emsoft/sw/business/api/accountingadjust/SplitAdjustInternalApi.java`
- [ ] **Step 1: Create SplitAdjustCreateReqDTO**
```java
package cn.com.emsoft.sw.business.api.accountingadjust.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SplitAdjustCreateReqDTO {
private Long sourceChargeId;
private Integer splitCount;
private List<SplitWaterItem> splitItems;
private String reasonCode;
private String remark;
private String applicant;
private String contactMobile;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class SplitWaterItem {
private Integer seqNo;
private BigDecimal splitWater;
}
}
```
- [ ] **Step 2: Create SplitAdjustCreateRespDTO**
```java
package cn.com.emsoft.sw.business.api.accountingadjust.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SplitAdjustCreateRespDTO {
private String splitAdjustNo;
}
```
- [ ] **Step 3: Create SplitAdjustInternalApi**
```java
package cn.com.emsoft.sw.business.api.accountingadjust;
import cn.com.emsoft.sw.business.api.accountingadjust.dto.AdjustExecuteReqDTO;
import cn.com.emsoft.sw.business.api.accountingadjust.dto.AdjustExecuteRespDTO;
import cn.com.emsoft.sw.business.api.accountingadjust.dto.SplitAdjustCreateReqDTO;
import cn.com.emsoft.sw.business.api.accountingadjust.dto.SplitAdjustCreateRespDTO;
import cn.com.emsoft.sw.framework.common.pojo.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(name = cn.com.emsoft.sw.business.enums.ApiConstants.NAME)
@RequestMapping("/internal/split-adjust")
public interface SplitAdjustInternalApi {
@PostMapping("/create")
CommonResult<SplitAdjustCreateRespDTO> create(@RequestBody SplitAdjustCreateReqDTO reqDTO);
@PostMapping("/execute")
CommonResult<AdjustExecuteRespDTO> execute(@RequestBody AdjustExecuteReqDTO reqDTO);
}
```
- [ ] **Step 4: Commit**
```bash
git add sw-business/sw-business-api/src/main/java/cn/com/emsoft/sw/business/api/accountingadjust/dto/SplitAdjustCreateReqDTO.java \
sw-business/sw-business-api/src/main/java/cn/com/emsoft/sw/business/api/accountingadjust/dto/SplitAdjustCreateRespDTO.java \
sw-business/sw-business-api/src/main/java/cn/com/emsoft/sw/business/api/accountingadjust/SplitAdjustInternalApi.java
git commit -m "feat: add SplitAdjustInternalApi for BPM workflow integration"
```
### Task 1.2: Split Adjust Internal API Implementation
**Files:**
- Create: `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/api/accountingadjust/SplitAdjustInternalApiImpl.java`
- Modify: `sw-business/sw-business-server/src/main/java/cn/com/emsoft/sw/business/service/splitadjust/SplitAdjustServiceImpl.java` (add execute method)
- [ ] **Step 1: Create SplitAdjustInternalApiImpl**
```java
package cn.com.emsoft.sw.business.api.accountingadjust;
import cn.com.emsoft.sw.business.api.accountingadjust.dto.AdjustExecuteReqDTO;
import cn.com.emsoft.sw.business.api.accountingadjust.dto.AdjustExecuteRespDTO;
import cn.com.emsoft.sw.business.api.accountingadjust.dto.SplitAdjustCreateReqDTO;
import cn.com.emsoft.sw.business.api.accountingadjust.dto.SplitAdjustCreateRespDTO;
import cn.com.emsoft.sw.business.controller.admin.splitadjust.vo.SplitAdjustSubmitReqVO;
import cn.com.emsoft.sw.business.service.splitadjust.SplitAdjustService;
import cn.com.emsoft.sw.framework.common.pojo.CommonResult;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RestController;
import static cn.com.emsoft.sw.framework.common.pojo.CommonResult.success;
@Slf4j
@RestController
public class SplitAdjustInternalApiImpl implements SplitAdjustInternalApi {
@Resource
private SplitAdjustService splitAdjustService;
@Override
public CommonResult<SplitAdjustCreateRespDTO> create(SplitAdjustCreateReqDTO reqDTO) {
SplitAdjustSubmitReqVO vo = new SplitAdjustSubmitReqVO();
vo.setSourceChargeId(reqDTO.getSourceChargeId());
vo.setSplitCount(reqDTO.getSplitCount());
vo.setReasonCode(reqDTO.getReasonCode());
vo.setRemark(reqDTO.getRemark());
vo.setApplicant(reqDTO.getApplicant());
vo.setContactMobile(reqDTO.getContactMobile());
if (reqDTO.getSplitItems() != null) {
vo.setSplitItems(reqDTO.getSplitItems().stream().map(item -> {
SplitAdjustSubmitReqVO.SplitWaterItem voItem = new SplitAdjustSubmitReqVO.SplitWaterItem();
voItem.setSeqNo(item.getSeqNo());
voItem.setSplitWater(item.getSplitWater());
return voItem;
}).toList());
}
var resp = splitAdjustService.submit(vo);
return success(SplitAdjustCreateRespDTO.builder()
.splitAdjustNo(resp.getSplitAdjustNo())
.build());
}
@Override
public CommonResult<AdjustExecuteRespDTO> execute(AdjustExecuteReqDTO reqDTO) {
log.info("分账调整审批结果处理: splitAdjustNo={}, approvalStatus={}",
reqDTO.getAdjustmentNo(), reqDTO.getApprovalStatus());
var resp = splitAdjustService.executeApproval(reqDTO.getAdjustmentNo(),
reqDTO.getApprovalStatus(), reqDTO.getApprovalComment());
return success(AdjustExecuteRespDTO.builder()
.resultStatus(resp.getResultStatus())
.adjustmentNo(reqDTO.getAdjustmentNo())
.message(resp.getMessage())
.build());
}
}
```
- [ ] **Step 2: Add executeApproval to SplitAdjustService interface**
In `SplitAdjustService.java`, add:
```java
SplitAdjustExecuteRespVO executeApproval(String splitAdjustNo, String approvalStatus, String approvalComment);
```
Add the VO:
```java
// SplitAdjustExecuteRespVO.java
package cn.com.emsoft.sw.business.controller.admin.splitadjust.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SplitAdjustExecuteRespVO {
private String resultStatus;
private String message;
}
```
- [ ] **Step 3: Implement executeApproval in SplitAdjustServiceImpl**
In `SplitAdjustServiceImpl.java`, add:
```java
@Override
public SplitAdjustExecuteRespVO executeApproval(String splitAdjustNo, String approvalStatus, String approvalComment) {
SplitAdjustDO record = splitAdjustFormalizationService.getBySplitAdjustNo(splitAdjustNo);
if (record == null) {
return SplitAdjustExecuteRespVO.builder()
.resultStatus("FAILED")
.message("分账单不存在: " + splitAdjustNo)
.build();
}
if ("REJECT".equals(approvalStatus)) {
splitAdjustFormalizationService.markRejected(splitAdjustNo, approvalComment);
return SplitAdjustExecuteRespVO.builder()
.resultStatus("SUCCESS").message("审批拒绝,已标记").build();
}
if (!"APPROVE".equals(approvalStatus)) {
return SplitAdjustExecuteRespVO.builder()
.resultStatus("SKIPPED").message("非终态审批").build();
}
try {
splitAdjustFormalizationService.executeSplit(splitAdjustNo);
return SplitAdjustExecuteRespVO.builder()
.resultStatus("SUCCESS").message("分账执行成功").build();
} catch (Exception e) {
return SplitAdjustExecuteRespVO.builder()
.resultStatus("FAILED").message(e.getMessage()).build();
}
}
```
- [ ] **Step 4: Add markRejected and getBySplitAdjustNo to SplitAdjustFormalizationService**
Add to `SplitAdjustFormalizationService.java`:
```java
SplitAdjustDO getBySplitAdjustNo(String splitAdjustNo);
void markRejected(String splitAdjustNo, String approvalComment);
```
Implement in `SplitAdjustFormalizationService`:
```java
public SplitAdjustDO getBySplitAdjustNo(String splitAdjustNo) {
return splitAdjustMapper.selectOne(
new LambdaQueryWrapperX<SplitAdjustDO>()
.eq(SplitAdjustDO::getSplitAdjustNo, splitAdjustNo));
}
public void markRejected(String splitAdjustNo, String approvalComment) {
SplitAdjustDO record = getBySplitAdjustNo(splitAdjustNo);
if (record == null) return;
record.setApprovalStatus("REJECTED");
record.setSplitStatus("REJECTED");
record.setExecuteMessage(approvalComment);
splitAdjustMapper.updateById(record);
}
```
- [ ] **Step 5: Commit**
```bash
git add sw-business/sw-business-server/
git commit -m "feat: add SplitAdjustInternalApiImpl with BPM approval callback"
```
### Task 1.3: Split Adjust BPM — ProcessInit, BusinessBridge, Controller, VOs, Listener
**Files — Create all in sw-module-bpm:**
- `sw-module-bpm-server/src/main/java/cn/com/emsoft/sw/module/bpm/controller/app/splitadjust/vo/BpmSplitAdjustSaveReqVO.java`
- `sw-module-bpm-server/src/main/java/cn/com/emsoft/sw/module/bpm/controller/app/splitadjust/vo/BpmSplitAdjustRespVO.java`
- `sw-module-bpm-server/src/main/java/cn/com/emsoft/sw/module/bpm/controller/admin/splitadjust/BpmSplitAdjustController.java`
- `sw-module-bpm-server/src/main/java/cn/com/emsoft/sw/module/bpm/service/splitadjust/SplitAdjustBusinessBridge.java`
- `sw-module-bpm-server/src/main/java/cn/com/emsoft/sw/module/bpm/service/splitadjust/SplitAdjustProcessInit.java`
- `sw-module-bpm-server/src/main/java/cn/com/emsoft/sw/module/bpm/service/splitadjust/SplitAdjustApprovalListener.java`
- [ ] **Step 1: BpmSplitAdjustSaveReqVO**
```java
package cn.com.emsoft.sw.module.bpm.controller.app.splitadjust.vo;
import cn.com.emsoft.sw.module.bpm.controller.app.process.vo.ProcessBaseSaveReqVO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
public class BpmSplitAdjustSaveReqVO extends ProcessBaseSaveReqVO {
private Long sourceChargeId;
private Integer splitCount;
private List<SplitWaterItem> splitItems;
private String reasonCode;
private String remark;
private String applicant;
private String contactMobile;
private String sourceCustCode; // for cust lookup in ProcessInit
@Data
public static class SplitWaterItem {
private Integer seqNo;
private BigDecimal splitWater;
}
}
```
- [ ] **Step 2: BpmSplitAdjustRespVO**
```java
package cn.com.emsoft.sw.module.bpm.controller.app.splitadjust.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BpmSplitAdjustRespVO {
private Long bizProcessId;
private String splitAdjustNo;
private String processInstanceId;
}
```
- [ ] **Step 3: BpmSplitAdjustController**
```java
package cn.com.emsoft.sw.module.bpm.controller.admin.splitadjust;
import cn.com.emsoft.sw.module.bpm.controller.app.splitadjust.vo.BpmSplitAdjustSaveReqVO;
import cn.com.emsoft.sw.module.bpm.controller.app.splitadjust.vo.BpmSplitAdjustRespVO;
import cn.com.emsoft.sw.module.bpm.service.splitadjust.SplitAdjustProcessService;
import cn.com.emsoft.sw.framework.common.pojo.CommonResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static cn.com.emsoft.sw.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 分账调整工单")
@RestController
@RequestMapping("/bpm/split-adjust")
@Validated
public class BpmSplitAdjustController {
@Resource
private SplitAdjustProcessService splitAdjustProcessService;
@PostMapping("/create")
@Operation(summary = "创建分账调整工单")
@PreAuthorize("@ss.hasPermission('bpm:split-adjust:create')")
public CommonResult<BpmSplitAdjustRespVO> create(@Valid @RequestBody BpmSplitAdjustSaveReqVO reqVO) {
return success(splitAdjustProcessService.create(reqVO));
}
}
```
- [ ] **Step 4: SplitAdjustBusinessBridge**
```java
package cn.com.emsoft.sw.module.bpm.service.splitadjust;
import cn.com.emsoft.sw.business.api.accountingadjust.SplitAdjustInternalApi;
import cn.com.emsoft.sw.business.api.accountingadjust.dto.SplitAdjustCreateReqDTO;
import cn.com.emsoft.sw.business.api.accountingadjust.dto.SplitAdjustCreateRespDTO;
import cn.com.emsoft.sw.module.bpm.controller.app.splitadjust.vo.BpmSplitAdjustSaveReqVO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class SplitAdjustBusinessBridge {
@Resource
private SplitAdjustInternalApi splitAdjustInternalApi;
public SplitAdjustCreateRespDTO createAdjustRecord(BpmSplitAdjustSaveReqVO biz) {
SplitAdjustCreateReqDTO reqDTO = new SplitAdjustCreateReqDTO();
reqDTO.setSourceChargeId(biz.getSourceChargeId());
reqDTO.setSplitCount(biz.getSplitCount());
reqDTO.setReasonCode(biz.getReasonCode());
reqDTO.setRemark(biz.getRemark());
reqDTO.setApplicant(biz.getApplicant());
reqDTO.setContactMobile(biz.getContactMobile());
if (biz.getSplitItems() != null) {
reqDTO.setSplitItems(biz.getSplitItems().stream().map(item -> {
SplitAdjustCreateReqDTO.SplitWaterItem dtoItem = new SplitAdjustCreateReqDTO.SplitWaterItem();
dtoItem.setSeqNo(item.getSeqNo());
dtoItem.setSplitWater(item.getSplitWater());
return dtoItem;
}).toList());
}
SplitAdjustCreateRespDTO resp = splitAdjustInternalApi.create(reqDTO).getCheckedData();
log.info("分账调整记录创建成功, splitAdjustNo={}", resp.getSplitAdjustNo());
return resp;
}
}
```
- [ ] **Step 5: SplitAdjustProcessInit**
```java
package cn.com.emsoft.sw.module.bpm.service.splitadjust;
import cn.com.emsoft.sw.business.api.accountingadjust.dto.SplitAdjustCreateRespDTO;
import cn.com.emsoft.sw.module.bpm.controller.app.splitadjust.vo.BpmSplitAdjustSaveReqVO;
import cn.com.emsoft.sw.module.bpm.dal.dataobject.oa.BpmProcessDO;
import cn.com.emsoft.sw.module.bpm.enums.process.BusinessType;
import cn.com.emsoft.sw.module.bpm.service.process.template.BpmProcessInit;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class SplitAdjustProcessInit extends BpmProcessInit<BpmSplitAdjustSaveReqVO> {
{ PROCESS_KEY = "split_adjust"; }
@Resource
private SplitAdjustBusinessBridge businessBridge;
@Override
public Long initBusinessProcess(BpmProcessDO process, BpmSplitAdjustSaveReqVO biz) {
process.setBusinessType(BusinessType.FEN_ZHANG_TIAO_ZHENG.getValue());
SplitAdjustCreateRespDTO resp = businessBridge.createAdjustRecord(biz);
process.setBusinessCode(resp.getSplitAdjustNo());
return null; // businessId handled via businessCode
}
}
```
- [ ] **Step 6: SplitAdjustProcessService (thin wrapper)**
```java
package cn.com.emsoft.sw.module.bpm.service.splitadjust;
import cn.com.emsoft.sw.business.api.cust.CustApi;
import cn.com.emsoft.sw.business.api.cust.dto.CustDTO;
import cn.com.emsoft.sw.module.bpm.controller.app.splitadjust.vo.BpmSplitAdjustSaveReqVO;
import cn.com.emsoft.sw.module.bpm.controller.app.splitadjust.vo.BpmSplitAdjustRespVO;
import cn.com.emsoft.sw.module.bpm.dal.dataobject.oa.BpmProcessDO;
import cn.com.emsoft.sw.module.bpm.dal.mysql.oa.BpmProcessMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import static cn.com.emsoft.sw.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
@Slf4j
@Service
public class SplitAdjustProcessService {
@Resource
private SplitAdjustProcessInit processInit;
@Resource
private BpmProcessMapper bpmProcessMapper;
@Resource
private CustApi custApi;
@Transactional(rollbackFor = Exception.class)
public BpmSplitAdjustRespVO create(BpmSplitAdjustSaveReqVO reqVO) {
CustDTO custDTO = custApi.getCustDTObyCode(reqVO.getSourceCustCode()).getCheckedData();
if (custDTO == null) {
throw invalidParamException("客户不存在: {}", reqVO.getSourceCustCode());
}
processInit.mainProcess(custDTO, reqVO);
BpmProcessDO bizProcess = bpmProcessMapper.selectOne(
new LambdaQueryWrapper<BpmProcessDO>()
.eq(BpmProcessDO::getBusinessCode, reqVO.getSourceCustCode()) // we need to fix this lookup
);
// Better: processInit returns the bizProcessId
return BpmSplitAdjustRespVO.builder()
.bizProcessId(bizProcess != null ? bizProcess.getId() : null)
.splitAdjustNo(bizProcess != null ? bizProcess.getBusinessCode() : null)
.processInstanceId(bizProcess != null ? bizProcess.getProcessInstanceId() : null)
.build();
}
}
```
- [ ] **Step 7: SplitAdjustApprovalListener**
```java
package cn.com.emsoft.sw.module.bpm.service.splitadjust;
import cn.com.emsoft.sw.business.api.accountingadjust.SplitAdjustInternalApi;
import cn.com.emsoft.sw.business.api.accountingadjust.dto.AdjustExecuteReqDTO;
import cn.com.emsoft.sw.business.api.accountingadjust.dto.AdjustExecuteRespDTO;
import cn.com.emsoft.sw.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.com.emsoft.sw.module.bpm.dal.dataobject.oa.BpmProcessDO;
import cn.com.emsoft.sw.module.bpm.dal.mysql.oa.BpmProcessMapper;
import cn.com.emsoft.sw.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class SplitAdjustApprovalListener {
private static final String PROCESS_KEY = "split_adjust";
@Resource
private BpmProcessMapper bpmProcessMapper;
@Resource
private SplitAdjustInternalApi splitAdjustInternalApi;
@EventListener
public void onProcessInstanceCompleted(BpmProcessInstanceStatusEvent event) {
if (!PROCESS_KEY.equals(event.getProcessDefinitionKey())) return;
if (event.getBusinessKey() == null) return;
Long bizProcessId;
try {
bizProcessId = Long.valueOf(event.getBusinessKey());
} catch (NumberFormatException e) {
log.warn("无法解析 businessKey: {}", event.getBusinessKey());
return;
}
BpmProcessDO bizProcess = bpmProcessMapper.selectById(bizProcessId);
if (bizProcess == null || bizProcess.getBusinessCode() == null) {
log.warn("BPM 工单不存在或无 businessCode: bizProcessId={}", bizProcessId);
return;
}
String approvalStatus = BpmProcessInstanceStatusEnum.APPROVE.getStatus().equals(event.getStatus())
? "APPROVE" : "REJECT";
AdjustExecuteReqDTO reqDTO = new AdjustExecuteReqDTO();
reqDTO.setAdjustmentNo(bizProcess.getBusinessCode());
reqDTO.setApprovalStatus(approvalStatus);
reqDTO.setOperatorId("BPM_AUTO");
AdjustExecuteRespDTO resp = splitAdjustInternalApi.execute(reqDTO).getCheckedData();
log.info("分账调整执行结果: splitAdjustNo={}, resultStatus={}, message={}",
bizProcess.getBusinessCode(), resp.getResultStatus(), resp.getMessage());
}
}
```
- [ ] **Step 8: Register Feign client in RpcConfiguration**
In `sw-module-bpm-server/src/main/java/cn/com/emsoft/sw/module/bpm/framework/rpc/config/RpcConfiguration.java`, add `SplitAdjustInternalApi.class` to the `@EnableFeignClients` annotation.
- [ ] **Step 9: Commit**
```bash
git add sw-module-bpm/
git commit -m "feat: add BPM workflow for split adjust (分账调整)"
```
### Task 1.4: Frontend — Switch SplitAdjustmentForm to BPM endpoint
**Files:**
- Modify: `water-frontend/src/views/accountProcess/unsoldAdjustment/components/SplitAdjustmentForm.vue`
- Create: `water-frontend/src/api/bpm/splitAdjust/index.ts`
- [ ] **Step 1: Create BPM split adjust API**
```typescript
// src/api/bpm/splitAdjust/index.ts
import request from '@/config/axios'
export interface BpmSplitAdjustSaveReqVO {
sourceCustCode: string
sourceChargeId: number
splitCount: number
splitItems: { seqNo: number; splitWater: number }[]
reasonCode?: string
remark?: string
applicant?: string
contactMobile?: string
}
export interface BpmSplitAdjustRespVO {
bizProcessId?: number
splitAdjustNo?: string
processInstanceId?: string
}
export const createSplitAdjust = (data: BpmSplitAdjustSaveReqVO) =>
request.post<BpmSplitAdjustRespVO>({ url: '/bpm/split-adjust/create', data })
```
- [ ] **Step 2: Modify SplitAdjustmentForm submitForm**
Replace the call from `UnsoldAdjustmentApi.submitUnsoldSplit(...)` to `createSplitAdjust(...)`. The response now includes `processInstanceId` — show "已提交审批" on success.
```typescript
// In submitForm():
import { createSplitAdjust, type BpmSplitAdjustSaveReqVO } from '@/api/bpm/splitAdjust'
const resp = await createSplitAdjust({
sourceCustCode: rowData.value.custCode,
sourceChargeId: rowData.value.id,
splitCount: splitItems.length,
splitItems: splitItems.map(item => ({ seqNo: item.seqNo || 1, splitWater: item.splitWater || 0 })),
reasonCode: formData.value.applyReason || '1',
remark: formData.value.remark || undefined,
applicant: formData.value.applicant || undefined,
contactMobile: formData.value.contactMobile || undefined,
})
message.success('分账申请已提交,待审批')
dialogVisible.value = false
emit('success')
```
- [ ] **Step 3: Commit**
```bash
git add water-frontend/src/api/bpm/splitAdjust/ water-frontend/src/views/accountProcess/unsoldAdjustment/components/SplitAdjustmentForm.vue
git commit -m "feat: wire split adjust form to BPM workflow endpoint"
```
---
## Types 2-5: Remaining Adjustments (pattern replication)
Each of the remaining 4 types follows the same pattern as Type 1. Key differences noted below.
### Task 2: Bad Debt Adjust (呆坏账调整)
- PROCESS_KEY: `"bad_debt_adjust"`
- BusinessType: `DAI_HUA_TONG` (203)
- Internal API path: `/internal/bad-debt-adjust`
- BPM controller path: `/bpm/bad-debt-adjust`
- Create DTO fields: `chargeId, badDebtAmount, processType, reasonCode, remark, applicant, contactMobile, sourceCustCode`
- Execute logic: mark bad debt record, no account balance change needed (just status update)
- Frontend form: `BadDebtAdjustmentForm.vue` → call `/bpm/bad-debt-adjust/create`
**Files to create (BPM):**
- `controller/app/baddebtadjust/vo/BpmBadDebtAdjustSaveReqVO.java`
- `controller/app/baddebtadjust/vo/BpmBadDebtAdjustRespVO.java`
- `controller/admin/baddebtadjust/BpmBadDebtAdjustController.java`
- `service/baddebtadjust/BadDebtAdjustBusinessBridge.java`
- `service/baddebtadjust/BadDebtAdjustProcessInit.java`
- `service/baddebtadjust/BadDebtAdjustProcessService.java`
- `service/baddebtadjust/BadDebtAdjustApprovalListener.java`
**Files to create (Business):**
- `api/accountingadjust/dto/BadDebtAdjustCreateReqDTO.java`
- `api/accountingadjust/dto/BadDebtAdjustCreateRespDTO.java`
- `api/accountingadjust/BadDebtAdjustInternalApi.java`
- `api/accountingadjust/BadDebtAdjustInternalApiImpl.java`
**Files to modify:**
- `RpcConfiguration.java` — add `BadDebtAdjustInternalApi.class`
- `water-frontend/src/api/bpm/badDebtAdjust/index.ts` (new)
- `water-frontend/src/views/.../BadDebtAdjustmentForm.vue` — switch to BPM endpoint
### Task 3: Writtenoff Adjust (核销调整)
- PROCESS_KEY: `"writtenoff_adjust"`
- BusinessType: need new entry or reuse existing. Use `TIAO_ZHENG_JIAN_MIAN` (202) or add a new one
- Internal API path: `/internal/writtenoff-adjust`
- BPM controller path: `/bpm/writtenoff-adjust`
- Create DTO fields: `chargeId, writtenoffAmount, reasonCode, remark, applicant, contactMobile, sourceCustCode`
Note: Writtenoff adjust currently has no frontend form — verify if one is needed before creating BPM controller.
### Task 4: Price Diff Adjust (价差调整)
- PROCESS_KEY: `"price_diff_adjust"`
- BusinessType: `JIA_CHA_TIAO_ZHENG` (205)
- Internal API path: `/internal/price-diff-adjust`
- BPM controller path: `/bpm/price-diff-adjust`
- Create DTO fields: `chargeIds[]` (batch), `reasonCode, remark, applicant, contactMobile, sourceCustCode`
- Frontend form: `PriceAdjustmentForm.vue` — this is a BATCH form, needs adjustment for submitting multiple charges
### Task 5: Late Fee Reduce (违约金减免)
- PROCESS_KEY: `"late_fee_reduce_adjust"`
- BusinessType: `WEI_YUE_JIN_JIAN_MIAN` (204)
- Internal API path: `/internal/late-fee-reduce`
- BPM controller path: `/bpm/late-fee-reduce`
- Create DTO fields: `chargeIds[], lateFeeType, lateFeeReduceAmount, startDate, endDate, reasonCode, remark, applicant, contactMobile, sourceCustCode`
- Frontend form: `PenaltyRemissionForm.vue` — also a BATCH form
### Task 6: Frontend batch forms adaptation
`PriceAdjustmentForm.vue` and `PenaltyRemissionForm.vue` submit multiple charges at once. Two approaches:
A) Submit one BPM process per charge — loop create API calls
B) Submit one BPM process with all charges — requires BPM process to handle arrays
Choose A (one process per charge) since it aligns with how `BpmProcessDO` is single-charge. The frontend loops and `Promise.all`s the calls.
---
## Self-Review
**Spec coverage:** All 5 adjustment types (split, bad debt, writtenoff, price diff, late fee reduce) have BPM flow tasks mapped.
**Placeholder scan:** Task 1 (split) has complete code. Tasks 2-5 describe the pattern with key fields/differences — the implementing agent should replicate Task 1's exact code structure with the noted variations.
**Type consistency:** `AdjustExecuteReqDTO` / `AdjustExecuteRespDTO` shared across all 5 types. `SplitWaterItem` DTO consistent between API, BPM VO, and service layers.
**Risk:** BPM process definitions (`split_adjust`, `bad_debt_adjust`, etc.) must be created in the BPM designer separately. Code changes alone won't make the workflow runnable without matching process definitions.