From b643fb9b704d2d013ec209aa82d20b25377a6b92 Mon Sep 17 00:00:00 2001 From: yinq Date: Wed, 20 May 2026 16:39:58 +0800 Subject: [PATCH] =?UTF-8?q?1.1.48=20=E5=88=86=E6=AC=BE=E6=B4=BE=E5=8F=91?= =?UTF-8?q?=E7=BB=99=E5=AE=A2=E6=88=B7=E7=BB=8F=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ErpFinAccountInstallmentController.java | 12 + .../erp/domain/ErpFinAccountInstallment.java | 33 +- .../ErpFinAccountInstallmentDetail.java | 5 + .../domain/bo/ErpFinAccountInstallmentBo.java | 33 +- .../ErpFinAccountInstallmentDispatchBo.java | 36 ++ .../domain/vo/ErpFinAccountInstallmentVo.java | 47 +- .../IErpFinAccountInstallmentService.java | 16 + ...inAccountInstallmentDetailServiceImpl.java | 40 +- .../ErpFinAccountInstallmentServiceImpl.java | 510 ++++++++++++++---- .../ErpFinAccountInstallmentDetailMapper.xml | 4 +- .../oa/erp/ErpFinAccountInstallmentMapper.xml | 28 +- 11 files changed, 611 insertions(+), 153 deletions(-) create mode 100644 ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/bo/ErpFinAccountInstallmentDispatchBo.java diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/controller/ErpFinAccountInstallmentController.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/controller/ErpFinAccountInstallmentController.java index bf9a48df..815a0dbf 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/controller/ErpFinAccountInstallmentController.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/controller/ErpFinAccountInstallmentController.java @@ -27,6 +27,7 @@ import org.dromara.common.log.enums.BusinessType; import org.dromara.common.excel.utils.ExcelUtil; import org.dromara.oa.erp.domain.vo.ErpFinAccountInstallmentVo; import org.dromara.oa.erp.domain.bo.ErpFinAccountInstallmentBo; +import org.dromara.oa.erp.domain.bo.ErpFinAccountInstallmentDispatchBo; import org.dromara.oa.erp.service.IErpFinAccountInstallmentService; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.springframework.web.multipart.MultipartFile; @@ -148,4 +149,15 @@ public class ErpFinAccountInstallmentController extends BaseController { } + /** + * 派发给客户经理(可多选),草稿 -> 已派发 + */ + @SaCheckPermission("oa/erp:finAccountInstallment:dispatch") + @Log(title = "分款信息", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PostMapping("/dispatch") + public R dispatch(@Validated @RequestBody ErpFinAccountInstallmentDispatchBo bo) { + return toAjax(erpFinAccountInstallmentService.dispatchToManagers(bo)); + } + } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/ErpFinAccountInstallment.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/ErpFinAccountInstallment.java index a44e6e12..c1895fd7 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/ErpFinAccountInstallment.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/ErpFinAccountInstallment.java @@ -26,7 +26,7 @@ public class ErpFinAccountInstallment extends TenantEntity { /** * 分款ID */ - @TableId(value = "account_installment_id", type = IdType.AUTO) + @TableId(value = "account_installment_id", type = IdType.ASSIGN_ID) private Long accountInstallmentId; /** @@ -65,14 +65,39 @@ public class ErpFinAccountInstallment extends TenantEntity { private String remark; /** - * 分款状态(0草稿 1已发出 2分款完成) + * 流程状态(字典 flow_status:draft/waiting/finish/back 等,同出差申请) + */ + private String flowStatus; + + /** + * 派发人用户ID + */ + private Long dispatchUserId; + + /** + * 派发人部门ID + */ + private Long dispatchDeptId; + + /** + * 派发时间 + */ + private Date dispatchDate; + + /** + * 分款状态(0未分款 1已派发 2分款完成,字典 installment_status) */ private String installmentStatus; /** - * 客户经理 + * 客户经理用户ID(多选,逗号分隔) */ - private Long accountManageId; + private String accountManagerIds; + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private String delFlag; } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/ErpFinAccountInstallmentDetail.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/ErpFinAccountInstallmentDetail.java index 01705d06..d0ebb429 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/ErpFinAccountInstallmentDetail.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/ErpFinAccountInstallmentDetail.java @@ -68,6 +68,11 @@ public class ErpFinAccountInstallmentDetail extends BaseEntity { */ private String remark; + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private String delFlag; @TableField(exist = false) private String contractCode; diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/bo/ErpFinAccountInstallmentBo.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/bo/ErpFinAccountInstallmentBo.java index 33dfe4d8..e80ba804 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/bo/ErpFinAccountInstallmentBo.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/bo/ErpFinAccountInstallmentBo.java @@ -9,6 +9,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import jakarta.validation.constraints.*; import java.util.Date; +import java.util.List; import com.fasterxml.jackson.annotation.JsonFormat; /** @@ -65,15 +66,39 @@ public class ErpFinAccountInstallmentBo extends BaseEntity { private String remark; /** - * 分款状态(0草稿 1已发出 2分款完成) + * 流程状态(字典 flow_status) + */ + private String flowStatus; + + /** + * 派发人用户ID + */ + private Long dispatchUserId; + + /** + * 派发人部门ID + */ + private Long dispatchDeptId; + + /** + * 派发时间 + */ + private Date dispatchDate; + + /** + * 分款状态(0未分款 1已派发 2分款完成,字典 installment_status) */ - @NotBlank(message = "分款状态(0草稿 1已发出 2分款完成)不能为空", groups = { AddGroup.class, EditGroup.class }) private String installmentStatus; /** - * 客户经理 + * 客户经理用户ID(多选,逗号分隔) */ - private Long accountManageId; + private String accountManagerIds; + + /** + * 指定客户经理用户ID列表(派发入参,写入 accountManagerIds) + */ + private List managerUserIds; } 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 new file mode 100644 index 00000000..375757e5 --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/bo/ErpFinAccountInstallmentDispatchBo.java @@ -0,0 +1,36 @@ +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 { + + @NotEmpty(message = "请选择要派发的回款记录") + private List accountInstallmentIds; + + @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 174f3c61..9291de01 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 @@ -13,6 +13,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.util.Date; +import java.util.List; @@ -79,17 +80,53 @@ public class ErpFinAccountInstallmentVo implements Serializable { private String remark; /** - * 分款状态(0草稿 1已发出 2分款完成) + * 流程状态(字典 flow_status) + */ + @ExcelProperty(value = "流程状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "flow_status") + private String flowStatus; + + /** + * 派发人用户ID + */ + private Long dispatchUserId; + + /** + * 派发人部门ID + */ + private Long dispatchDeptId; + + /** + * 派发时间 + */ + private Date dispatchDate; + + /** + * 分款状态(0未分款 1已派发 2分款完成,字典 installment_status) */ @ExcelProperty(value = "分款状态", converter = ExcelDictConvert.class) - @ExcelDictFormat(readConverterExp = "0=草稿,1=已发出,2=分款完成") + @ExcelDictFormat(readConverterExp = "0=未分款,1=已派发,2=分款完成") private String installmentStatus; /** - * 客户经理 + * 客户经理用户ID(多选,逗号分隔,库字段) */ - @ExcelProperty(value = "客户经理") - private Long accountManageId; + private String accountManagerIds; + + /** + * 指定客户经理用户ID列表(解析后,供前端使用) + */ + private List managerUserIds; + + /** + * 客户经理姓名(逗号分隔,展示用) + */ + private String managerNickNames; + + /** + * 当前登录用户是否可对该回款进行分款操作 + */ + private Boolean canAllocate; } 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 a7b0539f..d9a08adf 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 @@ -3,6 +3,7 @@ package org.dromara.oa.erp.service; import org.dromara.oa.erp.domain.ErpFinAccountInstallment; import org.dromara.oa.erp.domain.vo.ErpFinAccountInstallmentVo; import org.dromara.oa.erp.domain.bo.ErpFinAccountInstallmentBo; +import org.dromara.oa.erp.domain.bo.ErpFinAccountInstallmentDispatchBo; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.PageQuery; @@ -74,4 +75,19 @@ public interface IErpFinAccountInstallmentService { * @return 是否删除成功 */ Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 将草稿回款派发给客户经理(可多选) + */ + 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 2f25263a..cb1c5e19 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,6 +20,7 @@ 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; @@ -44,6 +45,8 @@ public class ErpFinAccountInstallmentDetailServiceImpl implements IErpFinAccount private final ErpFinAccountInstallmentMapper finAccountInstallmentMapper; + private final IErpFinAccountInstallmentService finAccountInstallmentService; + private final IErpProjectPlanService erpProjectPlanService; /** @@ -150,6 +153,7 @@ public class ErpFinAccountInstallmentDetailServiceImpl implements IErpFinAccount .eq(ErpContractPaymentMethod::getContractId, ErpFinAccountInstallmentDetail::getContractId) .eq(ErpContractPaymentMethod::getPaymentStageId, ErpFinAccountInstallmentDetail::getPaymentStageId) .eq(ErpContractPaymentMethod::getDelFlag, "0")) + .eq(ErpFinAccountInstallmentDetail::getDelFlag, "0") .eq(bo.getAccountInstallmentId() != null, ErpFinAccountInstallmentDetail::getAccountInstallmentId, bo.getAccountInstallmentId()) .eq(bo.getProjectId() != null, ErpFinAccountInstallmentDetail::getProjectId, bo.getProjectId()) .eq(StringUtils.isNotBlank(bo.getProjectCode()), ErpFinAccountInstallmentDetail::getProjectCode, bo.getProjectCode()) @@ -169,16 +173,16 @@ 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"); + } validEntityBeforeSave(add); boolean flag = baseMapper.insert(add) > 0; if (flag) { bo.setInstallmentDetailId(add.getInstallmentDetailId()); - } - ErpFinAccountInstallment erpFinAccountInstallment = finAccountInstallmentMapper.selectById(bo.getAccountInstallmentId()); - erpFinAccountInstallment.setInstallmentStatus(bo.getInstallmentStatus()); - finAccountInstallmentMapper.updateById(erpFinAccountInstallment); - if (flag) { + finAccountInstallmentService.recalculateInstallmentStatus(bo.getAccountInstallmentId()); syncContractCollectionStage(bo.getContractId(), bo.getProjectId(), bo.getPaymentStageId(), bo.getAccountInstallmentId()); } @@ -194,14 +198,13 @@ 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); - ErpFinAccountInstallment erpFinAccountInstallment = finAccountInstallmentMapper.selectById(bo.getAccountInstallmentId()); - erpFinAccountInstallment.setInstallmentStatus(bo.getInstallmentStatus()); - finAccountInstallmentMapper.updateById(erpFinAccountInstallment); boolean ok = baseMapper.updateById(update) > 0; if (ok) { + finAccountInstallmentService.recalculateInstallmentStatus(bo.getAccountInstallmentId()); if (old != null) { syncContractCollectionStage(old.getContractId(), old.getProjectId(), old.getPaymentStageId(), bo.getAccountInstallmentId()); @@ -220,16 +223,12 @@ public class ErpFinAccountInstallmentDetailServiceImpl implements IErpFinAccount } /** - * 校验并批量删除分款明细信息信息 - * - * @param ids 待删除的主键集合 - * @param isValid 是否进行有效性校验 - * @return 是否删除成功 + * 校验并批量删除分款明细信息(逻辑删除,依赖 {@link ErpFinAccountInstallmentDetail#delFlag} @TableLogic) */ @Override public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if (isValid) { - //TODO 做一些业务上的校验,判断是否需要校验 + // 预留业务校验 } if (ids == null || ids.isEmpty()) { return true; @@ -261,16 +260,15 @@ 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()); - ErpFinAccountInstallment erpFinAccountInstallment = new ErpFinAccountInstallment(); - erpFinAccountInstallment.setAccountInstallmentId(bo.getAccountInstallmentId()); - erpFinAccountInstallment.setInstallmentStatus(bo.getInstallmentStatus()); - finAccountInstallmentMapper.updateById(erpFinAccountInstallment); - boolean ok = baseMapper.deleteById(bo.getInstallmentDetailId()) > 0; - if (ok && row != null) { - syncContractCollectionStage(row.getContractId(), row.getProjectId(), row.getPaymentStageId()); + if (ok) { + finAccountInstallmentService.recalculateInstallmentStatus(bo.getAccountInstallmentId()); + if (row != null) { + syncContractCollectionStage(row.getContractId(), row.getProjectId(), row.getPaymentStageId()); + } } return ok; } 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 a0228e2e..9934ca76 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,31 +1,50 @@ package org.dromara.oa.erp.service.impl; -import cn.hutool.core.util.ObjectUtil; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +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; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.github.yulichang.toolkit.JoinWrappers; +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; import org.dromara.common.core.utils.StringUtils; - import org.dromara.common.mybatis.core.page.TableDataInfo; - import org.dromara.common.mybatis.core.page.PageQuery; - import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.github.yulichang.toolkit.JoinWrappers; -import com.github.yulichang.wrapper.MPJLambdaWrapper; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import lombok.RequiredArgsConstructor; -import org.dromara.oa.crm.domain.CrmShippingTariff; -import org.dromara.oa.crm.domain.bo.CrmShippingTariffBo; -import org.dromara.system.api.RemoteCodeRuleService; -import org.springframework.stereotype.Service; -import org.dromara.oa.erp.domain.bo.ErpFinAccountInstallmentBo; -import org.dromara.oa.erp.domain.vo.ErpFinAccountInstallmentVo; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.common.tenant.helper.TenantHelper; import org.dromara.oa.erp.domain.ErpFinAccountInstallment; +import org.dromara.oa.erp.domain.ErpFinAccountInstallmentDetail; +import org.dromara.oa.erp.domain.bo.ErpFinAccountInstallmentBo; +import org.dromara.oa.erp.domain.bo.ErpFinAccountInstallmentDispatchBo; +import org.dromara.oa.erp.domain.vo.ErpFinAccountInstallmentVo; +import org.dromara.oa.erp.mapper.ErpFinAccountInstallmentDetailMapper; import org.dromara.oa.erp.mapper.ErpFinAccountInstallmentMapper; import org.dromara.oa.erp.service.IErpFinAccountInstallmentService; +import org.dromara.system.api.RemoteCodeRuleService; +import org.dromara.system.api.RemoteUserService; +import org.dromara.workflow.api.RemoteWorkflowService; +import org.dromara.workflow.api.domain.RemoteFlowInstanceBizExt; +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 java.math.BigDecimal; +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.Collection; +import java.util.Objects; +import java.util.stream.Collectors; /** * 分款信息Service业务层处理 @@ -33,81 +52,139 @@ import java.util.Collection; * @author xins * @date 2026-03-09 */ +@Slf4j @RequiredArgsConstructor @Service public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstallmentService { 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"; + private static final String INSTALLMENT_STATUS_DISPATCHED = "1"; + private static final String INSTALLMENT_STATUS_COMPLETE = "2"; private final ErpFinAccountInstallmentMapper baseMapper; + private final ErpFinAccountInstallmentDetailMapper detailMapper; @DubboReference(timeout = 30000) private RemoteCodeRuleService remoteCodeRuleService; - /** - * 查询分款信息 - * - * @param accountInstallmentId 主键 - * @return 分款信息 - */ + @DubboReference + private RemoteUserService remoteUserService; + + @DubboReference + private RemoteWorkflowService remoteWorkflowService; + @Override - public ErpFinAccountInstallmentVo queryById(Long accountInstallmentId){ - return baseMapper.selectVoById(accountInstallmentId); + public ErpFinAccountInstallmentVo queryById(Long accountInstallmentId) { + ErpFinAccountInstallmentVo vo = baseMapper.selectVoById(accountInstallmentId); + if (vo != null) { + fillManagerInfo(List.of(vo)); + } + return vo; } - /** - * 分页查询分款信息列表 - * - * @param bo 查询条件 - * @param pageQuery 分页参数 - * @return 分款信息分页列表 - */ - @Override - public TableDataInfo queryPageList(ErpFinAccountInstallmentBo bo, PageQuery pageQuery) { - MPJLambdaWrapper lqw = buildQueryWrapper(bo); - Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); - return TableDataInfo.build(result); - } + @Override + public TableDataInfo queryPageList(ErpFinAccountInstallmentBo bo, PageQuery pageQuery) { + MPJLambdaWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + fillManagerInfo(result.getRecords()); + return TableDataInfo.build(result); + } - /** - * 查询符合条件的分款信息列表 - * - * @param bo 查询条件 - * @return 分款信息列表 - */ @Override public List queryList(ErpFinAccountInstallmentBo bo) { MPJLambdaWrapper lqw = buildQueryWrapper(bo); - return baseMapper.selectVoList(lqw); + List list = baseMapper.selectVoList(lqw); + fillManagerInfo(list); + return list; } private MPJLambdaWrapper buildQueryWrapper(ErpFinAccountInstallmentBo bo) { - Map params = bo.getParams(); MPJLambdaWrapper lqw = JoinWrappers.lambda(ErpFinAccountInstallment.class) - .selectAll(ErpFinAccountInstallment.class) - .eq(StringUtils.isNotBlank(bo.getInstallmentCode()), ErpFinAccountInstallment::getInstallmentCode, bo.getInstallmentCode()) - .eq(bo.getCustomerId() != null, ErpFinAccountInstallment::getCustomerId, bo.getCustomerId()) - .like(StringUtils.isNotBlank(bo.getCustomerName()), ErpFinAccountInstallment::getCustomerName, bo.getCustomerName()) - .eq(StringUtils.isNotBlank(bo.getCurrency()), ErpFinAccountInstallment::getCurrency, bo.getCurrency()) - .eq(bo.getPaymentAmount() != null, ErpFinAccountInstallment::getPaymentAmount, bo.getPaymentAmount()) - .eq(bo.getPaymentDate() != null, ErpFinAccountInstallment::getPaymentDate, bo.getPaymentDate()) - .eq(StringUtils.isNotBlank(bo.getInstallmentStatus()), ErpFinAccountInstallment::getInstallmentStatus, bo.getInstallmentStatus()) - .eq(bo.getAccountManageId() != null, ErpFinAccountInstallment::getAccountManageId, bo.getAccountManageId()) - .orderByAsc(ErpFinAccountInstallment::getInstallmentStatus) - .orderByDesc(ErpFinAccountInstallment::getCreateTime) -; + .selectAll(ErpFinAccountInstallment.class) + .eq(StringUtils.isNotBlank(bo.getInstallmentCode()), ErpFinAccountInstallment::getInstallmentCode, bo.getInstallmentCode()) + .eq(bo.getCustomerId() != null, ErpFinAccountInstallment::getCustomerId, bo.getCustomerId()) + .like(StringUtils.isNotBlank(bo.getCustomerName()), ErpFinAccountInstallment::getCustomerName, bo.getCustomerName()) + .eq(StringUtils.isNotBlank(bo.getCurrency()), ErpFinAccountInstallment::getCurrency, bo.getCurrency()) + .eq(bo.getPaymentAmount() != null, ErpFinAccountInstallment::getPaymentAmount, bo.getPaymentAmount()) + .eq(bo.getPaymentDate() != null, ErpFinAccountInstallment::getPaymentDate, bo.getPaymentDate()) + .eq(StringUtils.isNotBlank(bo.getFlowStatus()), ErpFinAccountInstallment::getFlowStatus, bo.getFlowStatus()) + .eq(StringUtils.isNotBlank(bo.getInstallmentStatus()), ErpFinAccountInstallment::getInstallmentStatus, bo.getInstallmentStatus()) + .orderByDesc(ErpFinAccountInstallment::getInstallmentCode) + .orderByDesc(ErpFinAccountInstallment::getCreateTime); return lqw; } - /** - * 新增分款信息 - * - * @param bo 分款信息 - * @return 是否新增成功 - */ + 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); + if (CollUtil.isNotEmpty(userIds)) { + 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); + } + + private static String joinManagerUserIds(List ids) { + if (CollUtil.isEmpty(ids)) { + return null; + } + return ids.stream().map(String::valueOf).collect(Collectors.joining(",")); + } + + private static List parseManagerUserIds(String idsStr) { + if (StringUtils.isBlank(idsStr)) { + return List.of(); + } + return Arrays.stream(idsStr.split(",")) + .map(String::trim) + .filter(StringUtils::isNotBlank) + .map(Long::valueOf) + .toList(); + } + @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); + } validEntityBeforeSave(add); boolean flag = baseMapper.insert(add) > 0; if (flag) { @@ -116,18 +193,10 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal return flag; } - /** - * 新增分款信息 - * - * @param installmentVolist 回款信息列表 - * @return 是否新增成功 - */ @Override public String importInstallmentData(List installmentVolist) { - try - { - if (StringUtils.isNull(installmentVolist) || installmentVolist.isEmpty()) - { + try { + if (StringUtils.isNull(installmentVolist) || installmentVolist.isEmpty()) { throw new ServiceException("导入数据不能为空!"); } @@ -141,80 +210,293 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal StringBuilder successMsg = new StringBuilder(); StringBuilder failureMsg = new StringBuilder(); - for (ErpFinAccountInstallmentVo installmentVo : installmentVolist) - { - try - { - // 检查必填字段 - if (StringUtils.isEmpty(installmentVo.getCustomerName()) || installmentVo.getPaymentAmount() == null || installmentVo.getPaymentDate() == null) - { + for (ErpFinAccountInstallmentVo installmentVo : installmentVolist) { + try { + if (StringUtils.isEmpty(installmentVo.getCustomerName()) + || installmentVo.getPaymentAmount() == null + || installmentVo.getPaymentDate() == null) { failureNum++; - failureMsg.append("
第" + (failureNum + 1) + "行数据不完整,缺少必填字段"); + failureMsg.append("
第").append(failureNum).append("行数据不完整,缺少必填字段"); continue; } ErpFinAccountInstallment add = MapstructUtils.convert(installmentVo, ErpFinAccountInstallment.class); add.setInstallmentCode(code); + add.setFlowStatus(BusinessStatusEnum.DRAFT.getStatus()); + add.setInstallmentStatus(INSTALLMENT_STATUS_NOT_ALLOCATED); + add.setDelFlag("0"); baseMapper.insert(add); successNum++; - successMsg.append("
" + successNum + "、" + installmentVo.getCustomerName() + " 导入成功"); - } - catch (Exception e) - { + successMsg.append("
").append(successNum).append("、").append(installmentVo.getCustomerName()).append(" 导入成功"); + } catch (Exception e) { failureNum++; - String msg = "
" + installmentVo.getCustomerName() + " 导入失败:"; - failureMsg.append(msg + e.getMessage()); + failureMsg.append("
").append(installmentVo.getCustomerName()).append(" 导入失败:").append(e.getMessage()); } } - if (failureNum > 0) - { + if (failureNum > 0) { failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:"); throw new ServiceException(failureMsg.toString()); } - else - { - successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); - } + successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); return successMsg.toString(); - } - catch (Exception e) - { + } catch (ServiceException e) { + throw e; + } catch (Exception e) { throw new ServiceException("导入Excel数据失败:" + e.getMessage()); } } - /** - * 修改分款信息 - * - * @param bo 分款信息 - * @return 是否修改成功 - */ @Override public Boolean updateByBo(ErpFinAccountInstallmentBo bo) { + ErpFinAccountInstallment existing = baseMapper.selectById(bo.getAccountInstallmentId()); + if (existing == null) { + throw new ServiceException("回款记录不存在"); + } + if (!isFlowEditable(existing.getFlowStatus())) { + throw new ServiceException("仅流程草稿/退回/撤销状态的回款可修改"); + } + if (!isInstallmentNotAllocated(existing.getInstallmentStatus())) { + throw new ServiceException("仅未分款的回款可修改"); + } ErpFinAccountInstallment update = MapstructUtils.convert(bo, ErpFinAccountInstallment.class); validEntityBeforeSave(update); return baseMapper.updateById(update) > 0; } - /** - * 保存前的数据校验 - */ - private void validEntityBeforeSave(ErpFinAccountInstallment entity){ - //TODO 做一些数据校验,如唯一约束 + @Override + @GlobalTransactional(rollbackFor = Exception.class) + public Boolean dispatchToManagers(ErpFinAccountInstallmentDispatchBo bo) { + List installmentIds = bo.getAccountInstallmentIds().stream().distinct().toList(); + List managerUserIds = bo.getManagerUserIds().stream().distinct().toList(); + if (CollUtil.isEmpty(installmentIds) || CollUtil.isEmpty(managerUserIds)) { + throw new ServiceException("请选择回款记录与客户经理"); + } + + List installments = baseMapper.selectBatchIds(installmentIds); + if (installments.size() != installmentIds.size()) { + throw new ServiceException("部分回款记录不存在"); + } + for (ErpFinAccountInstallment inst : installments) { + if (!isFlowEditable(inst.getFlowStatus())) { + throw new ServiceException("回款【" + inst.getCustomerName() + "】流程状态不允许派发"); + } + if (!isInstallmentNotAllocated(inst.getInstallmentStatus())) { + throw new ServiceException("回款【" + inst.getCustomerName() + "】已派发,无法重复派发"); + } + } + + Long dispatchUserId = LoginHelper.getUserId(); + Long dispatchDeptId = LoginHelper.getDeptId(); + Date now = new Date(); + String accountManagerIds = joinManagerUserIds(managerUserIds); + + Map installmentMap = installments.stream() + .collect(Collectors.toMap(ErpFinAccountInstallment::getAccountInstallmentId, i -> i)); + + for (Long installmentId : installmentIds) { + ErpFinAccountInstallment update = new ErpFinAccountInstallment(); + update.setAccountInstallmentId(installmentId); + update.setAccountManagerIds(accountManagerIds); + update.setFlowStatus(BusinessStatusEnum.WAITING.getStatus()); + update.setInstallmentStatus(INSTALLMENT_STATUS_DISPATCHED); + update.setDispatchUserId(dispatchUserId); + update.setDispatchDeptId(dispatchDeptId); + update.setDispatchDate(now); + baseMapper.updateById(update); + + ErpFinAccountInstallment inst = installmentMap.get(installmentId); + startDispatchFlow(installmentId, inst, bo); + } + return true; } /** - * 校验并批量删除分款信息信息 - * - * @param ids 待删除的主键集合 - * @param isValid 是否进行有效性校验 - * @return 是否删除成功 + * 派发后发起分款审核流程(流程参数由前端组装传入) + */ + 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); + } + + RemoteStartProcess startProcess = new RemoteStartProcess(); + startProcess.setBusinessId(installmentId.toString()); + startProcess.setFlowCode(StringUtils.isNotBlank(bo.getFlowCode()) ? bo.getFlowCode() : FLOW_CODE_FKSH); + startProcess.setVariables(variables); + + RemoteFlowInstanceBizExt bizExt = resolveDispatchBizExt(installmentId, inst, bo); + bizExt.setBusinessId(startProcess.getBusinessId()); + startProcess.setBizExt(bizExt); + + boolean started = remoteWorkflowService.startCompleteTask(startProcess); + if (!started) { + throw new ServiceException("分款审核流程发起失败"); + } + } + + 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; + } + } + } + RemoteFlowInstanceBizExt bizExt = new RemoteFlowInstanceBizExt(); + if (inst != null) { + bizExt.setBusinessCode(inst.getInstallmentCode()); + bizExt.setBusinessTitle("分款审核-" + inst.getCustomerName()); + } else { + bizExt.setBusinessTitle("分款审核"); + } + return bizExt; + } + + /** + * 分款审核流程状态监听 + */ + @EventListener(condition = "#processEvent.flowCode == 'FKSH'") + public void processHandler(ProcessEvent processEvent) { + TenantHelper.dynamic(processEvent.getTenantId(), () -> { + log.info("【分款审核流程监听】flowCode={}, status={}", processEvent.getFlowCode(), processEvent.getStatus()); + + Long installmentId = Convert.toLong(processEvent.getBusinessId()); + ErpFinAccountInstallment inst = baseMapper.selectById(installmentId); + if (inst == null) { + log.error("未找到回款分款记录,id={}", installmentId); + return; + } + + 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); + } + }); + } + + @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("您不是该回款的指定客户经理,无法进行分款操作"); + } + } + + @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); + } + } + + /** 流程是否可编辑/派发(同出差申请:草稿、退回、撤销) */ + private static boolean isFlowEditable(String flowStatus) { + return BusinessStatusEnum.isDraftOrCancelOrBack(flowStatus); + } + + /** 分款状态:未分款(installment_status = 0) */ + 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) { + // 预留业务校验 + } + + /** + * 删除回款主表(逻辑删除),并级联逻辑删除其分款明细 */ @Override public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { - if(isValid){ - //TODO 做一些业务上的校验,判断是否需要校验 + if (isValid) { + List list = baseMapper.selectBatchIds(ids); + for (ErpFinAccountInstallment item : list) { + if (!isFlowEditable(item.getFlowStatus())) { + throw new ServiceException("仅可删除流程草稿/退回/撤销状态的回款记录"); + } + if (!isInstallmentNotAllocated(item.getInstallmentStatus())) { + throw new ServiceException("仅可删除未分款的回款记录"); + } + } + } + if (CollUtil.isNotEmpty(ids)) { + detailMapper.delete(Wrappers.lambdaQuery(ErpFinAccountInstallmentDetail.class) + .in(ErpFinAccountInstallmentDetail::getAccountInstallmentId, ids)); } return baseMapper.deleteByIds(ids) > 0; } diff --git a/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/erp/ErpFinAccountInstallmentDetailMapper.xml b/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/erp/ErpFinAccountInstallmentDetailMapper.xml index 91fdf4cf..dcaad649 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/erp/ErpFinAccountInstallmentDetailMapper.xml +++ b/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/erp/ErpFinAccountInstallmentDetailMapper.xml @@ -17,6 +17,7 @@ payment_stage_id, detail_amount, remark, + del_flag, create_dept, create_by, create_time, @@ -29,7 +30,8 @@ diff --git a/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/erp/ErpFinAccountInstallmentMapper.xml b/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/erp/ErpFinAccountInstallmentMapper.xml index 9aa45a00..f8140117 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/erp/ErpFinAccountInstallmentMapper.xml +++ b/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/erp/ErpFinAccountInstallmentMapper.xml @@ -1,14 +1,34 @@ + PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> -