# 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 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 create(@RequestBody SplitAdjustCreateReqDTO reqDTO); @PostMapping("/execute") CommonResult 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 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 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() .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 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 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 { { 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() .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({ 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.