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
791 lines
30 KiB
Markdown
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.
|