云
2026-10-24 a4ed0f250009167453ca637ce042ef8e0b69f613
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
@@ -24,13 +24,18 @@
import cn.iocoder.yudao.module.crm.service.invoice.CrmInvoiceService;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.enums.storage.StorageRecordTypeEnum;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -40,6 +45,7 @@
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
@@ -50,7 +56,11 @@
 */
@Service
@Validated
@Slf4j
public class CrmReceivableServiceImpl implements CrmReceivableService {
    private static final String RECEIVABLE_PROCESS_KEY = "crm-receivable-audit";
    private static final String RECEIVABLE_APPROVE_BIZ_TYPE = "crm_receivable_approve";
    @Resource
    private CrmReceivableMapper receivableMapper;
@@ -72,6 +82,10 @@
    private AdminUserApi adminUserApi;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private BpmProcessInstanceApi bpmProcessInstanceApi;
    @Resource
    private ApprovalConfigApi approvalConfigApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -92,9 +106,9 @@
            throw exception(RECEIVABLE_NO_EXISTS);
        }
        // 2.1 插入回款(创建后即为未审核状态)
        // 2.1 插入草稿;提交时再创建 BPM 流程
        CrmReceivableDO receivable = BeanUtils.toBean(createReqVO, CrmReceivableDO.class)
                .setNo(no).setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus());
                .setNo(no).setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus());
        receivableMapper.insert(receivable);
        // 2.2
@@ -227,8 +241,9 @@
        // 1.5 校验可回款金额不超过该开票剩余可回款金额
        validateReceivablePriceExceedsInvoiceRemaining(updateReqVO);
        // 1.6 只有未审核,可以编辑;
        if (ObjectUtil.notEqual(oldReceivable.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) {
        // 1.6 只有草稿或驳回后可以编辑
        if (ObjectUtil.notEqual(oldReceivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())
                && ObjectUtil.notEqual(oldReceivable.getAuditStatus(), CrmAuditStatusEnum.REJECT.getStatus())) {
            throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED);
        }
@@ -253,20 +268,48 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateReceivableStatus(Long id, Integer status) {
        boolean approve = CrmAuditStatusEnum.APPROVE.getStatus().equals(status);
        // 1.1 校验存在
    @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#id", level = CrmPermissionLevelEnum.WRITE)
    public void submitReceivable(Long id) {
        CrmReceivableDO receivable = validateReceivableExists(id);
        // 1.2 校验状态
        if (receivable.getAuditStatus().equals(status)) {
            throw exception(approve ? RECEIVABLE_APPROVE_FAIL : RECEIVABLE_PROCESS_FAIL);
        if (ObjectUtil.notEqual(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())
                && ObjectUtil.notEqual(receivable.getAuditStatus(), CrmAuditStatusEnum.REJECT.getStatus())) {
            throw exception(RECEIVABLE_SUBMIT_FAIL_NOT_DRAFT);
        }
        // 草稿期可能已产生其他回款,提交时重新校验合同及开票额度。
        CrmReceivableSaveReqVO req = new CrmReceivableSaveReqVO().setId(id)
                .setContractId(receivable.getContractId()).setInvoiceId(receivable.getInvoiceId())
                .setCustomerId(receivable.getCustomerId()).setPrice(receivable.getPrice());
        validateInvoiceForReceivable(req);
        validateReceivablePriceExceedsLimit(req);
        validateReceivablePriceExceedsInvoiceRemaining(req);
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(RECEIVABLE_APPROVE_BIZ_TYPE);
        int updateCount = receivableMapper.updateByIdAndStatus(id, receivable.getAuditStatus(),
                new CrmReceivableDO().setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus()));
        if (updateCount == 0) {
            throw exception(RECEIVABLE_SUBMIT_FAIL_NOT_DRAFT);
        }
        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(getLoginUserId(),
                new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(RECEIVABLE_PROCESS_KEY)
                        .setBusinessKey(String.valueOf(id)));
        receivableMapper.updateById(new CrmReceivableDO().setId(id).setProcessInstanceId(processInstanceId));
        }
        // 2. 更新状态
        int updateCount = receivableMapper.updateByIdAndStatus(id, receivable.getAuditStatus(),
                new CrmReceivableDO().setAuditStatus(status));
        if (updateCount == 0) {
            throw exception(approve ? RECEIVABLE_APPROVE_FAIL : RECEIVABLE_PROCESS_FAIL);
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateReceivableAuditStatus(Long id, Integer bpmStatus) {
        Integer auditStatus;
        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmStatus)) {
            auditStatus = CrmAuditStatusEnum.APPROVE.getStatus();
        } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmStatus)) {
            auditStatus = CrmAuditStatusEnum.REJECT.getStatus();
        } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmStatus)) {
            auditStatus = CrmAuditStatusEnum.CANCEL.getStatus();
        } else {
            return;
        }
        if (receivableMapper.updateByIdAndStatus(id, CrmAuditStatusEnum.PROCESS.getStatus(),
                new CrmReceivableDO().setAuditStatus(auditStatus)) != 1) {
            log.warn("[updateReceivableAuditStatus] 回款({}) 已不处于审批中,忽略 BPM 状态({})", id, bpmStatus);
        }
    }
@@ -282,8 +325,9 @@
        if (receivable.getPlanId() != null && receivablePlanService.getReceivablePlan(receivable.getPlanId()) != null) {
            throw exception(RECEIVABLE_DELETE_FAIL);
        }
        // 1.3 审批通过时,不允许删除
        if (ObjUtil.equal(receivable.getAuditStatus(), CrmAuditStatusEnum.APPROVE.getStatus())) {
        // 审批中、审核通过不可删除
        if (ObjUtil.equal(receivable.getAuditStatus(), CrmAuditStatusEnum.APPROVE.getStatus())
                || ObjUtil.equal(receivable.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) {
            throw exception(RECEIVABLE_DELETE_FAIL_IS_APPROVE);
        }