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
30 KiB
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
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
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
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
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
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
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
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
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:
SplitAdjustExecuteRespVO executeApproval(String splitAdjustNo, String approvalStatus, String approvalComment);
Add the VO:
// 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:
@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:
SplitAdjustDO getBySplitAdjustNo(String splitAdjustNo);
void markRejected(String splitAdjustNo, String approvalComment);
Implement in SplitAdjustFormalizationService:
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
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
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
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
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
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
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)
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
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
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
// 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.
// 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
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.javacontroller/app/baddebtadjust/vo/BpmBadDebtAdjustRespVO.javacontroller/admin/baddebtadjust/BpmBadDebtAdjustController.javaservice/baddebtadjust/BadDebtAdjustBusinessBridge.javaservice/baddebtadjust/BadDebtAdjustProcessInit.javaservice/baddebtadjust/BadDebtAdjustProcessService.javaservice/baddebtadjust/BadDebtAdjustApprovalListener.java
Files to create (Business):
api/accountingadjust/dto/BadDebtAdjustCreateReqDTO.javaapi/accountingadjust/dto/BadDebtAdjustCreateRespDTO.javaapi/accountingadjust/BadDebtAdjustInternalApi.javaapi/accountingadjust/BadDebtAdjustInternalApiImpl.java
Files to modify:
RpcConfiguration.java— addBadDebtAdjustInternalApi.classwater-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.alls 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.