diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/bo/ErpFinAccountInstallmentDispatchBo.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/bo/ErpFinAccountInstallmentDispatchBo.java index 375757e5..83cd5139 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/bo/ErpFinAccountInstallmentDispatchBo.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/bo/ErpFinAccountInstallmentDispatchBo.java @@ -2,13 +2,11 @@ package org.dromara.oa.erp.domain.bo; import jakarta.validation.constraints.NotEmpty; import lombok.Data; -import org.dromara.workflow.api.domain.RemoteFlowInstanceBizExt; import java.util.List; -import java.util.Map; /** - * 回款分款派发给客户经理(含流程启动参数,由前端组装) + * 回款分款派发给客户经理 */ @Data public class ErpFinAccountInstallmentDispatchBo { @@ -18,19 +16,4 @@ public class ErpFinAccountInstallmentDispatchBo { @NotEmpty(message = "请至少选择一名客户经理") private List managerUserIds; - - /** - * 流程编码(如 FKSH) - */ - private String flowCode; - - /** - * 流程变量(如 accountManagerId、ignore 等) - */ - private Map variables; - - /** - * 各回款流程业务扩展(businessId=回款主键,businessCode、businessTitle 由前端传入) - */ - private List flowBizList; } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/vo/ErpFinAccountInstallmentVo.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/vo/ErpFinAccountInstallmentVo.java index 9291de01..df121d13 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/vo/ErpFinAccountInstallmentVo.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/vo/ErpFinAccountInstallmentVo.java @@ -123,10 +123,4 @@ public class ErpFinAccountInstallmentVo implements Serializable { */ private String managerNickNames; - /** - * 当前登录用户是否可对该回款进行分款操作 - */ - private Boolean canAllocate; - - } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/mapper/ErpProjectInfoMapper.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/mapper/ErpProjectInfoMapper.java index 940bdc1e..b7774181 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/mapper/ErpProjectInfoMapper.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/mapper/ErpProjectInfoMapper.java @@ -61,10 +61,10 @@ public interface ErpProjectInfoMapper extends BaseMapperPlus selectErpProjectInfoVoJoinList(@Param("page") Page page, @Param(Constants.WRAPPER) MPJLambdaWrapper queryWrapper); /** diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpFinAccountInstallmentService.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpFinAccountInstallmentService.java index d9a08adf..52aba945 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpFinAccountInstallmentService.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpFinAccountInstallmentService.java @@ -80,14 +80,4 @@ public interface IErpFinAccountInstallmentService { * 将草稿回款派发给客户经理(可多选) */ Boolean dispatchToManagers(ErpFinAccountInstallmentDispatchBo bo); - - /** - * 校验当前用户是否可对指定回款进行分款明细维护 - */ - void validateAllocatePermission(Long accountInstallmentId); - - /** - * 根据分款明细汇总重算主表分款状态 - */ - void recalculateInstallmentStatus(Long accountInstallmentId); } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpFinAccountInstallmentDetailServiceImpl.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpFinAccountInstallmentDetailServiceImpl.java index cb1c5e19..8d1d86fd 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpFinAccountInstallmentDetailServiceImpl.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpFinAccountInstallmentDetailServiceImpl.java @@ -20,7 +20,6 @@ import org.dromara.oa.erp.domain.vo.ErpFinAccountInstallmentDetailVo; import org.dromara.oa.erp.domain.ErpFinAccountInstallmentDetail; import org.dromara.oa.erp.mapper.ErpFinAccountInstallmentDetailMapper; import org.dromara.oa.erp.service.IErpFinAccountInstallmentDetailService; -import org.dromara.oa.erp.service.IErpFinAccountInstallmentService; import org.dromara.oa.erp.service.IErpProjectPlanService; import org.springframework.transaction.annotation.Transactional; @@ -45,8 +44,6 @@ public class ErpFinAccountInstallmentDetailServiceImpl implements IErpFinAccount private final ErpFinAccountInstallmentMapper finAccountInstallmentMapper; - private final IErpFinAccountInstallmentService finAccountInstallmentService; - private final IErpProjectPlanService erpProjectPlanService; /** @@ -173,7 +170,6 @@ public class ErpFinAccountInstallmentDetailServiceImpl implements IErpFinAccount @Override @Transactional(rollbackFor = Exception.class) public Boolean insertByBo(ErpFinAccountInstallmentDetailBo bo) { - finAccountInstallmentService.validateAllocatePermission(bo.getAccountInstallmentId()); ErpFinAccountInstallmentDetail add = MapstructUtils.convert(bo, ErpFinAccountInstallmentDetail.class); if (StringUtils.isBlank(add.getDelFlag())) { add.setDelFlag("0"); @@ -182,7 +178,6 @@ public class ErpFinAccountInstallmentDetailServiceImpl implements IErpFinAccount boolean flag = baseMapper.insert(add) > 0; if (flag) { bo.setInstallmentDetailId(add.getInstallmentDetailId()); - finAccountInstallmentService.recalculateInstallmentStatus(bo.getAccountInstallmentId()); syncContractCollectionStage(bo.getContractId(), bo.getProjectId(), bo.getPaymentStageId(), bo.getAccountInstallmentId()); } @@ -198,13 +193,11 @@ public class ErpFinAccountInstallmentDetailServiceImpl implements IErpFinAccount @Override @Transactional(rollbackFor = Exception.class) public Boolean updateByBo(ErpFinAccountInstallmentDetailBo bo) { - finAccountInstallmentService.validateAllocatePermission(bo.getAccountInstallmentId()); ErpFinAccountInstallmentDetail old = baseMapper.selectById(bo.getInstallmentDetailId()); ErpFinAccountInstallmentDetail update = MapstructUtils.convert(bo, ErpFinAccountInstallmentDetail.class); validEntityBeforeSave(update); boolean ok = baseMapper.updateById(update) > 0; if (ok) { - finAccountInstallmentService.recalculateInstallmentStatus(bo.getAccountInstallmentId()); if (old != null) { syncContractCollectionStage(old.getContractId(), old.getProjectId(), old.getPaymentStageId(), bo.getAccountInstallmentId()); @@ -260,12 +253,10 @@ public class ErpFinAccountInstallmentDetailServiceImpl implements IErpFinAccount */ @Override public Boolean deleteAccountInstallmentDetail(ErpFinAccountInstallmentDetailBo bo) { - finAccountInstallmentService.validateAllocatePermission(bo.getAccountInstallmentId()); ErpFinAccountInstallmentDetail row = bo.getInstallmentDetailId() == null ? null : baseMapper.selectById(bo.getInstallmentDetailId()); boolean ok = baseMapper.deleteById(bo.getInstallmentDetailId()) > 0; if (ok) { - finAccountInstallmentService.recalculateInstallmentStatus(bo.getAccountInstallmentId()); if (row != null) { syncContractCollectionStage(row.getContractId(), row.getProjectId(), row.getPaymentStageId()); } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpFinAccountInstallmentServiceImpl.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpFinAccountInstallmentServiceImpl.java index 9934ca76..aee9703f 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpFinAccountInstallmentServiceImpl.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpFinAccountInstallmentServiceImpl.java @@ -1,6 +1,5 @@ package org.dromara.oa.erp.service.impl; -import cn.dev33.satoken.stp.StpUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import com.baomidou.mybatisplus.core.toolkit.Wrappers; @@ -10,7 +9,6 @@ import com.github.yulichang.wrapper.MPJLambdaWrapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboReference; -import org.apache.seata.spring.annotation.GlobalTransactional; import org.dromara.common.core.enums.BusinessStatusEnum; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.MapstructUtils; @@ -35,15 +33,17 @@ import org.dromara.workflow.api.domain.RemoteStartProcess; import org.dromara.workflow.api.event.ProcessEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; -import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.stream.Collectors; /** @@ -59,7 +59,6 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal private static final String ACCOUNT_INSTALLMENT_CODE_RULE = "1031"; private static final String FLOW_CODE_FKSH = "FKSH"; - private static final String PERM_DISPATCH = "oa/erp:finAccountInstallment:dispatch"; /** 分款状态字典 installment_status:0未分款 1已派发 2分款完成 */ private static final String INSTALLMENT_STATUS_NOT_ALLOCATED = "0"; @@ -78,6 +77,12 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal @DubboReference private RemoteWorkflowService remoteWorkflowService; + /** + * 查询分款信息详情,并填充客户经理昵称 + * + * @param accountInstallmentId 回款主键 + * @return 分款信息 + */ @Override public ErpFinAccountInstallmentVo queryById(Long accountInstallmentId) { ErpFinAccountInstallmentVo vo = baseMapper.selectVoById(accountInstallmentId); @@ -87,6 +92,13 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal return vo; } + /** + * 分页查询分款信息列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 分款信息分页列表 + */ @Override public TableDataInfo queryPageList(ErpFinAccountInstallmentBo bo, PageQuery pageQuery) { MPJLambdaWrapper lqw = buildQueryWrapper(bo); @@ -95,6 +107,12 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal return TableDataInfo.build(result); } + /** + * 查询符合条件的分款信息列表 + * + * @param bo 查询条件 + * @return 分款信息列表 + */ @Override public List queryList(ErpFinAccountInstallmentBo bo) { MPJLambdaWrapper lqw = buildQueryWrapper(bo); @@ -103,6 +121,12 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal return list; } + /** + * 构建分款信息查询条件 + * + * @param bo 查询条件 + * @return 查询包装器 + */ private MPJLambdaWrapper buildQueryWrapper(ErpFinAccountInstallmentBo bo) { MPJLambdaWrapper lqw = JoinWrappers.lambda(ErpFinAccountInstallment.class) .selectAll(ErpFinAccountInstallment.class) @@ -119,13 +143,15 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal return lqw; } + /** + * 批量填充客户经理用户ID、昵称 + * + * @param rows 分款信息列表 + */ private void fillManagerInfo(List rows) { if (CollUtil.isEmpty(rows)) { return; } - Long currentUserId = LoginHelper.getUserId(); - boolean canDispatchAll = LoginHelper.isSuperAdmin() || StpUtil.hasPermission(PERM_DISPATCH); - for (ErpFinAccountInstallmentVo vo : rows) { List userIds = parseManagerUserIds(vo.getAccountManagerIds()); vo.setManagerUserIds(userIds); @@ -133,24 +159,15 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal String idsStr = userIds.stream().map(String::valueOf).collect(Collectors.joining(",")); vo.setManagerNickNames(remoteUserService.selectNicknameByIds(idsStr)); } - vo.setCanAllocate(resolveCanAllocate(vo, userIds, currentUserId, canDispatchAll)); } } - private boolean resolveCanAllocate(ErpFinAccountInstallmentVo vo, List managerUserIds, - Long currentUserId, boolean canDispatchAll) { - if (!BusinessStatusEnum.WAITING.getStatus().equals(vo.getFlowStatus())) { - return false; - } - if (!canAllocateByInstallmentStatus(vo.getInstallmentStatus())) { - return false; - } - if (canDispatchAll) { - return true; - } - return managerUserIds.contains(currentUserId); - } - + /** + * 将客户经理用户ID列表拼接为逗号分隔字符串 + * + * @param ids 用户ID列表 + * @return 逗号分隔的ID字符串,空列表返回 null + */ private static String joinManagerUserIds(List ids) { if (CollUtil.isEmpty(ids)) { return null; @@ -158,6 +175,12 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal return ids.stream().map(String::valueOf).collect(Collectors.joining(",")); } + /** + * 解析逗号分隔的客户经理用户ID字符串 + * + * @param idsStr 逗号分隔的用户ID + * @return 用户ID列表,空字符串返回空列表 + */ private static List parseManagerUserIds(String idsStr) { if (StringUtils.isBlank(idsStr)) { return List.of(); @@ -169,21 +192,20 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal .toList(); } + /** + * 新增回款分款信息:默认草稿流程、未分款状态,未填编号时按编码规则生成唯一回款编号 + * + * @param bo 分款信息 + * @return 是否新增成功 + */ @Override public Boolean insertByBo(ErpFinAccountInstallmentBo bo) { ErpFinAccountInstallment add = MapstructUtils.convert(bo, ErpFinAccountInstallment.class); - if (StringUtils.isBlank(add.getFlowStatus())) { - add.setFlowStatus(BusinessStatusEnum.DRAFT.getStatus()); - } if (StringUtils.isBlank(add.getInstallmentStatus())) { add.setInstallmentStatus(INSTALLMENT_STATUS_NOT_ALLOCATED); } if (StringUtils.isBlank(add.getInstallmentCode())) { - String code = remoteCodeRuleService.selectCodeRuleCode(ACCOUNT_INSTALLMENT_CODE_RULE); - if (StringUtils.isBlank(code)) { - throw new ServiceException("生成回款编号失败"); - } - add.setInstallmentCode(code); + add.setInstallmentCode(generateInstallmentCode()); } validEntityBeforeSave(add); boolean flag = baseMapper.insert(add) > 0; @@ -193,6 +215,12 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal return flag; } + /** + * 批量导入回款数据,每条记录单独生成唯一回款编号并校验必填项 + * + * @param installmentVolist 导入的回款信息列表 + * @return 导入结果提示信息 + */ @Override public String importInstallmentData(List installmentVolist) { try { @@ -200,11 +228,6 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal throw new ServiceException("导入数据不能为空!"); } - String code = remoteCodeRuleService.selectCodeRuleCode(ACCOUNT_INSTALLMENT_CODE_RULE); - if (StringUtils.isBlank(code)) { - throw new ServiceException("生成回款编号失败"); - } - int successNum = 0; int failureNum = 0; StringBuilder successMsg = new StringBuilder(); @@ -221,7 +244,10 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal } ErpFinAccountInstallment add = MapstructUtils.convert(installmentVo, ErpFinAccountInstallment.class); - add.setInstallmentCode(code); + if (StringUtils.isBlank(add.getInstallmentCode())) { + add.setInstallmentCode(generateInstallmentCode()); + } + validEntityBeforeSave(add); add.setFlowStatus(BusinessStatusEnum.DRAFT.getStatus()); add.setInstallmentStatus(INSTALLMENT_STATUS_NOT_ALLOCATED); add.setDelFlag("0"); @@ -247,6 +273,12 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal } } + /** + * 修改回款分款信息,仅允许流程草稿/退回/撤销且未分款状态的记录 + * + * @param bo 分款信息 + * @return 是否修改成功 + */ @Override public Boolean updateByBo(ErpFinAccountInstallmentBo bo) { ErpFinAccountInstallment existing = baseMapper.selectById(bo.getAccountInstallmentId()); @@ -264,8 +296,14 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal return baseMapper.updateById(update) > 0; } + /** + * 将草稿回款派发给客户经理,更新派发信息并发起分款审核流程(FKSH) + * + * @param bo 派发参数(回款ID、客户经理) + * @return 是否派发成功 + */ @Override - @GlobalTransactional(rollbackFor = Exception.class) + @Transactional(rollbackFor = Exception.class) public Boolean dispatchToManagers(ErpFinAccountInstallmentDispatchBo bo) { List installmentIds = bo.getAccountInstallmentIds().stream().distinct().toList(); List managerUserIds = bo.getManagerUserIds().stream().distinct().toList(); @@ -291,12 +329,10 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal Date now = new Date(); String accountManagerIds = joinManagerUserIds(managerUserIds); - Map installmentMap = installments.stream() - .collect(Collectors.toMap(ErpFinAccountInstallment::getAccountInstallmentId, i -> i)); - - for (Long installmentId : installmentIds) { + List flowTargets = new ArrayList<>(installments.size()); + for (ErpFinAccountInstallment inst : installments) { ErpFinAccountInstallment update = new ErpFinAccountInstallment(); - update.setAccountInstallmentId(installmentId); + update.setAccountInstallmentId(inst.getAccountInstallmentId()); update.setAccountManagerIds(accountManagerIds); update.setFlowStatus(BusinessStatusEnum.WAITING.getStatus()); update.setInstallmentStatus(INSTALLMENT_STATUS_DISPATCHED); @@ -304,31 +340,55 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal update.setDispatchDeptId(dispatchDeptId); update.setDispatchDate(now); baseMapper.updateById(update); - - ErpFinAccountInstallment inst = installmentMap.get(installmentId); - startDispatchFlow(installmentId, inst, bo); + flowTargets.add(inst); } + + // 先提交本地事务释放行锁,再发起流程,避免 Seata 全局锁与流程回调争用同一行 + registerDispatchFlowAfterCommit(flowTargets, accountManagerIds); return true; } /** - * 派发后发起分款审核流程(流程参数由前端组装传入) + * 事务提交后批量发起分款审核流程,避免 update 与流程监听在同一全局事务内锁等待 */ - private void startDispatchFlow(Long installmentId, ErpFinAccountInstallment inst, - ErpFinAccountInstallmentDispatchBo bo) { - Map variables = bo.getVariables() != null - ? new HashMap<>(bo.getVariables()) - : new HashMap<>(); - if (!variables.containsKey("ignore")) { - variables.put("ignore", true); + private void registerDispatchFlowAfterCommit(List installments, String accountManagerIds) { + if (CollUtil.isEmpty(installments)) { + return; } + Runnable startFlows = () -> { + for (ErpFinAccountInstallment inst : installments) { + startDispatchFlow(inst, accountManagerIds); + } + }; + if (TransactionSynchronizationManager.isSynchronizationActive()) { + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + startFlows.run(); + } + }); + } else { + startFlows.run(); + } + } + + /** + * 派发后发起分款审核流程(FKSH) + * + * @param inst 回款实体 + * @param accountManagerIds 客户经理用户ID(逗号分隔,写入流程变量 accountManagerId) + */ + private void startDispatchFlow(ErpFinAccountInstallment inst, String accountManagerIds) { + Map variables = new HashMap<>(); + variables.put("ignore", true); + variables.put("accountManagerId", accountManagerIds); RemoteStartProcess startProcess = new RemoteStartProcess(); - startProcess.setBusinessId(installmentId.toString()); - startProcess.setFlowCode(StringUtils.isNotBlank(bo.getFlowCode()) ? bo.getFlowCode() : FLOW_CODE_FKSH); + startProcess.setBusinessId(inst.getAccountInstallmentId().toString()); + startProcess.setFlowCode(FLOW_CODE_FKSH); startProcess.setVariables(variables); - RemoteFlowInstanceBizExt bizExt = resolveDispatchBizExt(installmentId, inst, bo); + RemoteFlowInstanceBizExt bizExt = buildDispatchBizExt(inst); bizExt.setBusinessId(startProcess.getBusinessId()); startProcess.setBizExt(bizExt); @@ -338,31 +398,23 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal } } - private RemoteFlowInstanceBizExt resolveDispatchBizExt(Long installmentId, ErpFinAccountInstallment inst, - ErpFinAccountInstallmentDispatchBo bo) { - String installmentIdStr = installmentId.toString(); - if (CollUtil.isNotEmpty(bo.getFlowBizList())) { - for (RemoteFlowInstanceBizExt item : bo.getFlowBizList()) { - if (item != null && installmentIdStr.equals(item.getBusinessId())) { - RemoteFlowInstanceBizExt bizExt = new RemoteFlowInstanceBizExt(); - bizExt.setBusinessCode(item.getBusinessCode()); - bizExt.setBusinessTitle(item.getBusinessTitle()); - return bizExt; - } - } - } + /** + * 构建派发流程业务扩展信息 + * + * @param inst 回款实体 + * @return 流程业务扩展信息 + */ + private RemoteFlowInstanceBizExt buildDispatchBizExt(ErpFinAccountInstallment inst) { RemoteFlowInstanceBizExt bizExt = new RemoteFlowInstanceBizExt(); - if (inst != null) { - bizExt.setBusinessCode(inst.getInstallmentCode()); - bizExt.setBusinessTitle("分款审核-" + inst.getCustomerName()); - } else { - bizExt.setBusinessTitle("分款审核"); - } + bizExt.setBusinessCode(inst.getInstallmentCode()); + bizExt.setBusinessTitle(inst.getCustomerName() + "分款审核"); return bizExt; } /** - * 分款审核流程状态监听 + * 分款审核流程(FKSH)状态监听:同步流程状态,并按流程状态更新分款进度 + * + * @param processEvent 流程事件 */ @EventListener(condition = "#processEvent.flowCode == 'FKSH'") public void processHandler(ProcessEvent processEvent) { @@ -378,108 +430,94 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal ErpFinAccountInstallment update = new ErpFinAccountInstallment(); update.setAccountInstallmentId(installmentId); - update.setFlowStatus(processEvent.getStatus()); - String status = processEvent.getStatus(); - - if (BusinessStatusEnum.BACK.getStatus().equals(status) - || BusinessStatusEnum.CANCEL.getStatus().equals(status)) { - update.setInstallmentStatus(INSTALLMENT_STATUS_NOT_ALLOCATED); - update.setAccountManagerIds(null); - baseMapper.updateById(update); - } else if (BusinessStatusEnum.INVALID.getStatus().equals(status) - || BusinessStatusEnum.TERMINATION.getStatus().equals(status)) { - update.setInstallmentStatus(INSTALLMENT_STATUS_NOT_ALLOCATED); - baseMapper.updateById(update); - } else { - baseMapper.updateById(update); - } + applyInstallmentStatusByFlowStatus(update, processEvent.getStatus()); + baseMapper.updateById(update); }); } - @Override - public void validateAllocatePermission(Long accountInstallmentId) { - ErpFinAccountInstallment inst = baseMapper.selectById(accountInstallmentId); - if (inst == null) { - throw new ServiceException("回款记录不存在"); - } - if (!BusinessStatusEnum.WAITING.getStatus().equals(inst.getFlowStatus())) { - throw new ServiceException("当前回款流程未处于待处理状态,无法进行分款操作"); - } - if (!canAllocateByInstallmentStatus(inst.getInstallmentStatus())) { - throw new ServiceException("当前回款尚未派发或分款已完成,无法进行分款操作"); - } - if (LoginHelper.isSuperAdmin() || StpUtil.hasPermission(PERM_DISPATCH)) { - return; - } - Long userId = LoginHelper.getUserId(); - if (!parseManagerUserIds(inst.getAccountManagerIds()).contains(userId)) { - throw new ServiceException("您不是该回款的指定客户经理,无法进行分款操作"); + /** + * 按流程状态同步主表 flow_status、installment_status + * + * @param update 待更新实体(需已设置 accountInstallmentId) + * @param flowStatus 流程状态 + */ + private void applyInstallmentStatusByFlowStatus(ErpFinAccountInstallment update, String flowStatus) { + update.setFlowStatus(flowStatus); + if (BusinessStatusEnum.FINISH.getStatus().equals(flowStatus)) { + update.setInstallmentStatus(INSTALLMENT_STATUS_COMPLETE); + } else if (BusinessStatusEnum.WAITING.getStatus().equals(flowStatus)) { + update.setInstallmentStatus(INSTALLMENT_STATUS_DISPATCHED); + } else if (BusinessStatusEnum.DRAFT.getStatus().equals(flowStatus) + || BusinessStatusEnum.BACK.getStatus().equals(flowStatus) + || BusinessStatusEnum.CANCEL.getStatus().equals(flowStatus) + || BusinessStatusEnum.INVALID.getStatus().equals(flowStatus) + || BusinessStatusEnum.TERMINATION.getStatus().equals(flowStatus)) { + update.setInstallmentStatus(INSTALLMENT_STATUS_NOT_ALLOCATED); + if (BusinessStatusEnum.BACK.getStatus().equals(flowStatus) + || BusinessStatusEnum.CANCEL.getStatus().equals(flowStatus)) { + update.setAccountManagerIds(null); + } } } - @Override - public void recalculateInstallmentStatus(Long accountInstallmentId) { - ErpFinAccountInstallment inst = baseMapper.selectById(accountInstallmentId); - if (inst == null) { - return; - } - String status = inst.getInstallmentStatus(); - if (isInstallmentNotAllocated(status) - || !BusinessStatusEnum.WAITING.getStatus().equals(inst.getFlowStatus())) { - return; - } - - List details = detailMapper.selectList( - Wrappers.lambdaQuery(ErpFinAccountInstallmentDetail.class) - .eq(ErpFinAccountInstallmentDetail::getAccountInstallmentId, accountInstallmentId)); - - BigDecimal allocated = details.stream() - .map(ErpFinAccountInstallmentDetail::getDetailAmount) - .filter(Objects::nonNull) - .reduce(BigDecimal.ZERO, BigDecimal::add); - - BigDecimal payment = inst.getPaymentAmount() == null - ? BigDecimal.ZERO - : BigDecimal.valueOf(inst.getPaymentAmount()); - - String newStatus; - if (allocated.compareTo(BigDecimal.ZERO) <= 0) { - newStatus = INSTALLMENT_STATUS_DISPATCHED; - } else if (allocated.compareTo(payment) >= 0) { - newStatus = INSTALLMENT_STATUS_COMPLETE; - } else { - newStatus = INSTALLMENT_STATUS_DISPATCHED; - } - - ErpFinAccountInstallment update = new ErpFinAccountInstallment(); - update.setAccountInstallmentId(accountInstallmentId); - if (!Objects.equals(status, newStatus)) { - update.setInstallmentStatus(newStatus); - baseMapper.updateById(update); - } - } - - /** 流程是否可编辑/派发(同出差申请:草稿、退回、撤销) */ + /** + * 流程是否可编辑/派发(草稿、退回、撤销) + * + * @param flowStatus 流程状态 + * @return 是否可编辑或派发 + */ private static boolean isFlowEditable(String flowStatus) { return BusinessStatusEnum.isDraftOrCancelOrBack(flowStatus); } - /** 分款状态:未分款(installment_status = 0) */ + /** + * 分款状态是否为未分款(installment_status = 0) + * + * @param installmentStatus 分款状态 + * @return 是否未分款 + */ private static boolean isInstallmentNotAllocated(String installmentStatus) { return INSTALLMENT_STATUS_NOT_ALLOCATED.equals(installmentStatus); } - /** 分款状态:已派发,可进行明细维护(installment_status = 1) */ - private static boolean canAllocateByInstallmentStatus(String installmentStatus) { - return INSTALLMENT_STATUS_DISPATCHED.equals(installmentStatus); - } - - private void validEntityBeforeSave(ErpFinAccountInstallment entity) { - // 预留业务校验 + /** + * 按编码规则(1031)生成唯一回款编号 + * + * @return 回款编号 + */ + private String generateInstallmentCode() { + String code = remoteCodeRuleService.selectCodeRuleCode(ACCOUNT_INSTALLMENT_CODE_RULE); + if (StringUtils.isBlank(code)) { + throw new ServiceException("生成回款编号失败"); + } + return code; } /** - * 删除回款主表(逻辑删除),并级联逻辑删除其分款明细 + * 保存前数据校验:回款编号全局唯一 + * + * @param entity 回款实体 + */ + private void validEntityBeforeSave(ErpFinAccountInstallment entity) { + if (StringUtils.isBlank(entity.getInstallmentCode())) { + return; + } + boolean codeExists = baseMapper.exists( + Wrappers.lambdaQuery(ErpFinAccountInstallment.class) + .eq(ErpFinAccountInstallment::getInstallmentCode, entity.getInstallmentCode()) + .ne(entity.getAccountInstallmentId() != null, ErpFinAccountInstallment::getAccountInstallmentId, entity.getAccountInstallmentId()) + ); + if (codeExists) { + throw new ServiceException("回款编号已存在:" + entity.getInstallmentCode()); + } + } + + /** + * 校验并批量删除回款主表(逻辑删除),并级联逻辑删除其分款明细 + * + * @param ids 待删除的主键集合 + * @param isValid 是否校验流程状态与分款状态 + * @return 是否删除成功 */ @Override public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) {