liu
22 小时以前 6d1bf32bc0b51dc12048fe6bb70badfbd31d67ac
fix: 修复销售订单审批及开票回款搜索
已修改5个文件
65 ■■■■■ 文件已修改
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/invoice/CrmInvoiceMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/invoice/CrmInvoiceMapper.java
@@ -104,7 +104,7 @@
    default PageResult<CrmInvoiceDO> selectPageByCustomerId(CrmInvoicePageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<CrmInvoiceDO>()
                .eq(CrmInvoiceDO::getCustomerId, reqVO.getCustomerId()) // 必须传递
                .eqIfPresent(CrmInvoiceDO::getNo, reqVO.getNo())
                .likeIfPresent(CrmInvoiceDO::getNo, reqVO.getNo())
                .eqIfPresent(CrmInvoiceDO::getContractId, reqVO.getContractId())
                .orderByDesc(CrmInvoiceDO::getId));
    }
@@ -116,7 +116,8 @@
                CrmInvoiceDO::getId, userId, pageReqVO.getSceneType());
        // 拼接自身的查询条件
        query.selectAll(CrmInvoiceDO.class)
                .eqIfPresent(CrmInvoiceDO::getNo, pageReqVO.getNo())
                .likeIfPresent(CrmInvoiceDO::getNo, pageReqVO.getNo())
                .eqIfPresent(CrmInvoiceDO::getCustomerId, pageReqVO.getCustomerId())
                .eqIfPresent(CrmInvoiceDO::getContractId, pageReqVO.getContractId())
                .eqIfPresent(CrmInvoiceDO::getAuditStatus, pageReqVO.getAuditStatus())
                .orderByDesc(CrmInvoiceDO::getId);
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java
@@ -43,7 +43,7 @@
    default PageResult<CrmReceivableDO> selectPageByCustomerId(CrmReceivablePageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<CrmReceivableDO>()
                .eq(CrmReceivableDO::getCustomerId, reqVO.getCustomerId()) // 必须传递
                .eqIfPresent(CrmReceivableDO::getNo, reqVO.getNo())
                .likeIfPresent(CrmReceivableDO::getNo, reqVO.getNo())
                .eqIfPresent(CrmReceivableDO::getContractId, reqVO.getContractId())
                .eqIfPresent(CrmReceivableDO::getPlanId, reqVO.getPlanId())
                .orderByDesc(CrmReceivableDO::getId));
@@ -56,7 +56,8 @@
                CrmReceivableDO::getId, userId, pageReqVO.getSceneType());
        // 拼接自身的查询条件
        query.selectAll(CrmReceivableDO.class)
                .eqIfPresent(CrmReceivableDO::getNo, pageReqVO.getNo())
                .likeIfPresent(CrmReceivableDO::getNo, pageReqVO.getNo())
                .eqIfPresent(CrmReceivableDO::getCustomerId, pageReqVO.getCustomerId())
                .eqIfPresent(CrmReceivableDO::getPlanId, pageReqVO.getPlanId())
                .eqIfPresent(CrmReceivableDO::getContractId, pageReqVO.getContractId())
                .eqIfPresent(CrmReceivableDO::getAuditStatus, pageReqVO.getAuditStatus())
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
@@ -47,6 +47,7 @@
import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mdm.enums.MdmItemTypeEnum;
import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
@@ -584,16 +585,18 @@
        createReqDTO.setRemark("由合同【" + contract.getNo() + "】自动生成");
        // 5. 构建订单项
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(contractProducts, CrmContractProductDO::getItemId));
        List<ErpSaleOrderCreateReqDTO.Item> items = contractProducts.stream()
                .map(cp -> {
                    ErpSaleOrderCreateReqDTO.Item item = new ErpSaleOrderCreateReqDTO.Item();
                    item.setProductId(cp.getItemId());
                    item.setProductPrice(cp.getContractPrice());
                    item.setCount(cp.getCount());
                    // 合同产品默认需要生产:订单审批通过后才能自动创建 MPS,
                    // 否则「合同 → 订单 → 生产」链路在合同来源的订单上会断掉。
                    // 若个别产品为外购/转售,可在销售订单明细中把「需要生产」改为否。
                    item.setNeedProduction(1);
                    // 只有产品才进入生产计划;温控等外购物料可随合同进入销售订单,
                    // 但不能创建 MPS,否则会导致整笔销售订单审批回滚。
                    MdmItemRespDTO mdmItem = itemMap.get(cp.getItemId());
                    item.setNeedProduction(mdmItem != null && MdmItemTypeEnum.isProduct(mdmItem.getItemType()) ? 1 : 0);
                    return item;
                })
                .toList();
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
@@ -30,6 +30,7 @@
import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mdm.enums.MdmItemTypeEnum;
import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi;
import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
import cn.iocoder.yudao.module.mes.api.mps.MesProMpsApi;
@@ -221,8 +222,22 @@
        int createdCount = 0;
        int reusedCount = 0;
        int skippedCount = 0;
        for (ErpSaleOrderItemDO item : items) {
            if (item.getNeedProduction() == null || item.getNeedProduction() != 1) {
                continue;
            }
            MdmItemRespDTO mdmItem = mdmItemApi.getItem(item.getProductId()).getCheckedData();
            if (mdmItem == null || !MdmItemTypeEnum.isProduct(mdmItem.getItemType())) {
                // 兼容修复前由合同生成的历史订单:当时所有明细都会被标记为需要生产。
                // 非产品明细不应创建 MPS,同时纠正标记,避免后续发货状态一直等待生产完成。
                saleOrderItemMapper.updateById(new ErpSaleOrderItemDO()
                        .setId(item.getId())
                        .setNeedProduction(0));
                skippedCount++;
                log.info("[createMpsForSaleOrder] 销售订单明细({}) 为非产品物料({}),跳过 MPS 并取消需要生产标记",
                        item.getId(), item.getProductId());
                continue;
            }
@@ -241,7 +256,7 @@
            }
            // 与销售订单审核共用事务;任一明细创建失败都会回滚审核状态和本次已创建的 MPS
            Long mpsId = createMpsForSaleOrderItem(saleOrder, item);
            Long mpsId = createMpsForSaleOrderItem(saleOrder, item, mdmItem);
            saleOrderItemMapper.updateById(new ErpSaleOrderItemDO()
                    .setId(item.getId())
                    .setMpsId(mpsId));
@@ -254,31 +269,21 @@
                    .set(ErpSaleOrderDO::getProductionStatus, ErpSaleOrderProductionStatusEnum.NOT_FINISHED.getStatus())
                    .set(ErpSaleOrderDO::getProductionFinishTime, null));
        }
        log.info("[createMpsForSaleOrder] 销售订单({}) 审核完成,创建 MPS {} 条,复用 MPS {} 条",
                orderId, createdCount, reusedCount);
        log.info("[createMpsForSaleOrder] 销售订单({}) 审核完成,创建 MPS {} 条,复用 MPS {} 条,跳过非产品 {} 条",
                orderId, createdCount, reusedCount, skippedCount);
    }
    /**
     * 为销售订单明细创建 MPS
     */
    private Long createMpsForSaleOrderItem(ErpSaleOrderDO saleOrder, ErpSaleOrderItemDO item) {
        // 获取 MDM 物料信息(用于获取产品名称)
        String productName = null;
        try {
            MdmItemRespDTO mdmItem = mdmItemApi.getItem(item.getProductId()).getCheckedData();
            if (mdmItem != null) {
                productName = mdmItem.getName();
            }
        } catch (Exception e) {
            log.warn("[createMpsForSaleOrderItem] 获取 MDM 物料信息失败,productId={}", item.getProductId(), e);
        }
    private Long createMpsForSaleOrderItem(ErpSaleOrderDO saleOrder, ErpSaleOrderItemDO item,
                                           MdmItemRespDTO mdmItem) {
        MesProMpsCreateReqDTO createReqDTO = new MesProMpsCreateReqDTO();
        createReqDTO.setSaleOrderId(saleOrder.getId());
        createReqDTO.setSaleOrderNo(saleOrder.getNo());
        createReqDTO.setSaleOrderItemId(item.getId());
        createReqDTO.setMdmItemId(item.getProductId());
        createReqDTO.setProductName(productName);
        createReqDTO.setProductName(mdmItem.getName());
        createReqDTO.setQuantity(item.getCount());
        createReqDTO.setClientId(saleOrder.getCustomerId());
        createReqDTO.setRequestDate(formatRequestDate(saleOrder.getOrderTime()));
yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java
@@ -39,4 +39,11 @@
        return null;
    }
    /**
     * 判断给定类型是否为可生产的产品。
     */
    public static boolean isProduct(Integer type) {
        return PRODUCT.getType().equals(type);
    }
}