xiaoyi
3 天以前 a57dceb3e6bc02e28a86f4de66f593706452c2b4
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/cancellation/AfterSaleCancellationService.java
@@ -12,6 +12,7 @@
import cn.iocoder.yudao.module.aftersales.dal.redis.no.AfterSaleNoRedisDAO;
import cn.iocoder.yudao.module.aftersales.service.returnobj.AfterSaleReturnService;
import cn.iocoder.yudao.module.crm.api.contract.CrmContractApi;
import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
@@ -22,6 +23,7 @@
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
import static cn.iocoder.yudao.module.aftersales.enums.ErrorCodeConstants.*;
/**
@@ -53,6 +55,7 @@
    @Resource private AfterSaleNoRedisDAO noRedisDAO;
    @Resource private AfterSaleReturnService returnService;
    @Resource private CrmContractApi crmContractApi;
    @Resource private ErpSaleOrderApi erpSaleOrderApi;
    public PageResult<AfterSaleCancellationDO> page(PageParam page, String code, String type,
                                                    Integer status, Long customerId) {
@@ -133,7 +136,10 @@
    }
    /**
     * 审批:通过后执行下游动作;驳回必须填意见。
     * 审批:驳回必须填意见;通过时先执行下游动作,全部成功才落审批结论。
     *
     * <p>下游失败(如销售订单仍有关联生产计划)会整体回滚,受理单保持「待审批」,
     * 由调用方看到具体原因,避免出现「审批通过了但业务没执行」的不一致状态。
     *
     * @return 通过时生成的退货单编号(非退货类型为 null)
     */
@@ -146,27 +152,47 @@
        AfterSaleCancellationDO task = get(req.getId());
        if (!Objects.equals(task.getStatus(), ST_PENDING)) throw exception(CANCELLATION_APPROVE_FAIL_STATUS);
        int target = Boolean.TRUE.equals(req.getApproved()) ? ST_APPROVED : ST_REJECTED;
        // 驳回:只改状态,不执行下游
        if (!Boolean.TRUE.equals(req.getApproved())) {
            if (mapper.update(null, new LambdaUpdateWrapper<AfterSaleCancellationDO>()
                    .eq(AfterSaleCancellationDO::getId, task.getId())
                    .eq(AfterSaleCancellationDO::getStatus, ST_PENDING)
                    .set(AfterSaleCancellationDO::getStatus, ST_REJECTED)
                    .set(AfterSaleCancellationDO::getApproverId, userId)
                    .set(AfterSaleCancellationDO::getApprovalTime, LocalDateTime.now())
                    .set(AfterSaleCancellationDO::getApprovalRemark, req.getRemark())) != 1) {
                throw exception(CANCELLATION_APPROVE_FAIL_STATUS);
            }
            return null;
        }
        // 通过:先执行下游动作(异常直接抛出,事务回滚,单据仍为待审批)
        Long returnId = executeDownstream(task, userId);
        // 下游成功后再落审批结论
        if (mapper.update(null, new LambdaUpdateWrapper<AfterSaleCancellationDO>()
                .eq(AfterSaleCancellationDO::getId, task.getId())
                .eq(AfterSaleCancellationDO::getStatus, ST_PENDING)
                .set(AfterSaleCancellationDO::getStatus, target)
                .set(AfterSaleCancellationDO::getStatus, ST_APPROVED)
                .set(AfterSaleCancellationDO::getApproverId, userId)
                .set(AfterSaleCancellationDO::getApprovalTime, LocalDateTime.now())
                .set(AfterSaleCancellationDO::getApprovalRemark, req.getRemark())) != 1) {
                .set(AfterSaleCancellationDO::getApprovalRemark, req.getRemark())
                .set(AfterSaleCancellationDO::getExecuted, 1)) != 1) {
            throw exception(CANCELLATION_APPROVE_FAIL_STATUS);
        }
        if (!Boolean.TRUE.equals(req.getApproved())) return null;
        return executeDownstream(task, userId);
        return returnId;
    }
    /** 审批通过后的下游动作;失败时记录原因但不回滚审批结论,便于人工补做。 */
    /**
     * 审批通过后的下游动作。
     *
     * <p>不吞异常:任一步失败直接抛出,由 {@link #approve} 的事务整体回滚,
     * 保证「审批通过」与「下游已执行」永远一致。
     */
    private Long executeDownstream(AfterSaleCancellationDO task, Long userId) {
        Long returnId = null;
        String remark;
        boolean ok = true;
        try {
        {
            switch (task.getType()) {
                case "RETURN" -> {
                    var vo = new AfterSaleReturnSaveReqVO();
@@ -175,23 +201,31 @@
                    vo.setReturnType(task.getReturnType() == null ? 1 : task.getReturnType());
                    vo.setReturnReason(task.getReason());
                    returnId = returnService.createReturn(vo, userId);
                    remark = "已生成退货单 " + returnId;
                    if (task.getSaleOrderId() != null) {
                        erpSaleOrderApi.cancelSaleOrder(task.getSaleOrderId()).getCheckedData();
                        remark = "已生成退货单 " + returnId
                                + ",并作废销售订单 " + task.getSaleOrderId();
                    } else {
                        remark = "已生成退货单 " + returnId;
                    }
                }
                case "CONTRACT_TERMINATE" -> {
                    crmContractApi.updateContractStatus(task.getContractId(), CRM_CONTRACT_INVALID).getCheckedData();
                    remark = "已作废 CRM 合同 " + task.getContractId();
                    // 若关联 ERP 销售订单,一并作废(会被未取消的 MPS 计划拦截)
                    if (task.getSaleOrderId() != null) {
                        erpSaleOrderApi.cancelSaleOrder(task.getSaleOrderId()).getCheckedData();
                        remark = "已作废 CRM 合同 " + task.getContractId()
                                + ",并作废销售订单 " + task.getSaleOrderId();
                    } else {
                        remark = "已作废 CRM 合同 " + task.getContractId();
                    }
                }
                case "CONTRACT_CHANGE" -> remark = "变更受理已通过;CRM 合同内容请按附件同步维护";
                default -> { ok = false; remark = "未知受理类型:" + task.getType(); }
                default -> throw invalidParamException("未知受理类型:" + task.getType());
            }
        } catch (Exception e) {
            ok = false;
            remark = "下游执行失败:" + e.getMessage();
            log.warn("退单受理[{}]下游动作失败:{}", task.getCode(), e.getMessage());
        }
        var update = new AfterSaleCancellationDO();
        update.setId(task.getId());
        update.setExecuted(ok ? 1 : 0);
        update.setExecuteRemark(remark);
        if (returnId != null) update.setReturnId(returnId);
        mapper.updateById(update);