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

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.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.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.