云
2026-10-25 d54fbc9d4f8981fa0f7f613b33a28a8cd4f764ea
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderServiceImpl.java
@@ -2,11 +2,16 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderImportExcelVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderImportRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemBatchConfigDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper;
import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum;
@@ -24,7 +29,11 @@
import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
import cn.iocoder.yudao.module.mes.service.wm.barcode.MesWmBarcodeService;
import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi;
import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -33,6 +42,7 @@
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -69,11 +79,17 @@
    @Resource
    private CrmCustomerApi customerApi;
    @Resource
    @Lazy // 避免 erpSaleOrderApiImpl -> ... -> mesProWorkOrderServiceImpl 的循环依赖
    private ErpSaleOrderApi saleOrderApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createWorkOrder(MesProWorkOrderSaveReqVO createReqVO) {
        // 1. 校验数据
        validateWorkOrderSaveData(null, createReqVO);
        // 1.1 关联营销订单信息回填
        fillSaleOrderInfo(createReqVO);
        // 2.1 设置默认值
        if (createReqVO.getParentId() == null) {
@@ -84,8 +100,10 @@
        workOrder.setStatus(MesProWorkOrderStatusEnum.PREPARE.getStatus());
        workOrderMapper.insert(workOrder);
        // 3. 自动生成 BOM:根据产品 BOM 生成工单 BOM
        workOrderBomService.generateWorkOrderBom(workOrder.getId(), createReqVO, false);
        // 3. 自动生成 BOM:根据产品 BOM 生成工单 BOM(无产品的运维/发电/检修类工单跳过)
        if (createReqVO.getProductId() != null) {
            workOrderBomService.generateWorkOrderBom(workOrder.getId(), createReqVO, false);
        }
        // 4. 工序由前端选择产品后带入,不再自动生成
@@ -105,11 +123,13 @@
        }
        // 1.2 校验数据
        validateWorkOrderSaveData(updateReqVO.getId(), updateReqVO);
        // 1.3 关联营销订单信息回填
        fillSaleOrderInfo(updateReqVO);
        // 2. 判断产品或数量是否变更,如果变更则重新生成 BOM(updated=true 会先清理旧数据)
        // 2. 判断产品或数量是否变更,如果变更则重新生成 BOM(updated=true 会先清理旧数据;无产品的运维类工单跳过)
        boolean productChanged = ObjUtil.notEqual(oldWorkOrder.getProductId(), updateReqVO.getProductId());
        boolean quantityChanged = oldWorkOrder.getQuantity().compareTo(updateReqVO.getQuantity()) != 0;
        if (productChanged || quantityChanged) {
        if ((productChanged || quantityChanged) && updateReqVO.getProductId() != null) {
            workOrderBomService.generateWorkOrderBom(updateReqVO.getId(), updateReqVO, true);
        }
        // 2.1 工序由前端管理,产品变更时前端会重新获取工艺路线工序
@@ -157,6 +177,75 @@
    @Override
    public MesProWorkOrderDO getWorkOrder(String code) {
        return workOrderMapper.selectByCode(code);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public MesProWorkOrderImportRespVO importWorkOrderList(List<MesProWorkOrderImportExcelVO> importList,
                                                           boolean updateSupport) {
        // 1. 校验非空
        if (CollUtil.isEmpty(importList)) {
            throw exception(MES_PRO_WORK_ORDER_IMPORT_LIST_IS_EMPTY);
        }
        // 2. 逐行处理
        MesProWorkOrderImportRespVO respVO = MesProWorkOrderImportRespVO.builder()
                .createCodes(new ArrayList<>()).updateCodes(new ArrayList<>())
                .failureCodes(new LinkedHashMap<>()).build();
        AtomicInteger index = new AtomicInteger(1);
        importList.forEach(importItem -> {
            int currentIndex = index.getAndIncrement();
            String key = StrUtil.blankToDefault(importItem.getCode(), "第 " + currentIndex + " 行");
            if (StrUtil.isBlank(importItem.getCode())) {
                respVO.getFailureCodes().put(key, "工单编码不能为空");
                return;
            }
            if (StrUtil.isBlank(importItem.getName())) {
                respVO.getFailureCodes().put(key, "工单名称不能为空");
                return;
            }
            // 2.1 解析产品
            Long productId = null;
            if (StrUtil.isNotBlank(importItem.getProductCode())) {
                MesMdItemDO item = itemService.getItemByCode(importItem.getProductCode().trim());
                if (item == null) {
                    respVO.getFailureCodes().put(key, "产品编码[" + importItem.getProductCode() + "]不存在");
                    return;
                }
                productId = item.getId();
            }
            // 2.2 解析客户
            Long clientId = null;
            if (StrUtil.isNotBlank(importItem.getClientName())) {
                CrmCustomerRespDTO customer = customerApi.getCustomerByName(importItem.getClientName().trim());
                if (customer == null) {
                    respVO.getFailureCodes().put(key, "客户名称[" + importItem.getClientName() + "]不存在");
                    return;
                }
                clientId = customer.getId();
            }
            // 2.3 构建请求并创建/更新(复用现有 create/update 服务,保留 BOM 与条码生成逻辑)
            MesProWorkOrderSaveReqVO reqVO = BeanUtils.toBean(importItem, MesProWorkOrderSaveReqVO.class);
            reqVO.setProductId(productId);
            reqVO.setClientId(clientId);
            MesProWorkOrderDO existWorkOrder = workOrderMapper.selectByCode(importItem.getCode());
            try {
                if (existWorkOrder == null) {
                    reqVO.setId(null);
                    createWorkOrder(reqVO);
                    respVO.getCreateCodes().add(importItem.getCode());
                } else if (updateSupport) {
                    reqVO.setId(existWorkOrder.getId());
                    updateWorkOrder(reqVO);
                    respVO.getUpdateCodes().add(importItem.getCode());
                } else {
                    respVO.getFailureCodes().put(key, "工单编码已存在");
                }
            } catch (ServiceException e) {
                respVO.getFailureCodes().put(key, e.getMessage());
            }
        });
        return respVO;
    }
    @Override
@@ -212,6 +301,20 @@
        if (ObjUtil.notEqual(workOrder.getStatus(), MesProWorkOrderStatusEnum.CONFIRMED.getStatus())) {
            throw exception(PRO_WORK_ORDER_NOT_CONFIRMED);
        }
        if (workOrder.getQuantityProduced() == null
                || workOrder.getQuantity() == null
                || workOrder.getQuantityProduced().compareTo(workOrder.getQuantity()) < 0) {
            throw exception(PRO_WORK_ORDER_PRODUCTION_NOT_COMPLETE);
        }
        // 校验每个任务的实际产出是否已达成计划数量(任务状态只有在工单完成时由级联方法置为已完成,不能在此校验状态)
        List<MesProTaskDO> tasks = taskService.getTaskListByWorkOrderIds(Collections.singletonList(id));
        boolean hasUnfinishedTask = CollUtil.isEmpty(tasks) || tasks.stream().anyMatch(task ->
                task.getProducedQuantity() == null
                        || task.getQuantity() == null
                        || task.getProducedQuantity().compareTo(task.getQuantity()) < 0);
        if (hasUnfinishedTask) {
            throw exception(PRO_WORK_ORDER_PRODUCTION_NOT_COMPLETE);
        }
        // 2. 级联完成所有关联任务
        taskService.finishTaskByOrderId(id);
@@ -245,12 +348,14 @@
    private void validateWorkOrderSaveData(Long id, MesProWorkOrderSaveReqVO reqVO) {
        // 1. 校验编码唯一
        validateWorkOrderCodeUnique(id, reqVO.getCode());
        // 2. 校验产品存在
        itemService.validateItemExists(reqVO.getProductId());
        // 3. 校验批次配置(如果产品有 clientFlag=true,则 clientId 必填)
        MesMdItemBatchConfigDO batchConfig = itemBatchConfigService.getItemBatchConfigByItemId(reqVO.getProductId());
        if (batchConfig != null && Boolean.TRUE.equals(batchConfig.getClientFlag()) && reqVO.getClientId() == null) {
            throw exception(MD_CLIENT_NOT_EXISTS);
        // 2. 产品非空时校验产品存在(运维/发电/检修类工单可无产品)
        if (reqVO.getProductId() != null) {
            itemService.validateItemExists(reqVO.getProductId());
            // 3. 校验批次配置(如果产品有 clientFlag=true,则 clientId 必填)
            MesMdItemBatchConfigDO batchConfig = itemBatchConfigService.getItemBatchConfigByItemId(reqVO.getProductId());
            if (batchConfig != null && Boolean.TRUE.equals(batchConfig.getClientFlag()) && reqVO.getClientId() == null) {
                throw exception(MD_CLIENT_NOT_EXISTS);
            }
        }
    }
@@ -267,6 +372,23 @@
        }
    }
    /**
     * 关联营销订单(ERP 销售订单):校验存在性并回填单号、客户
     */
    private void fillSaleOrderInfo(MesProWorkOrderSaveReqVO reqVO) {
        if (reqVO.getSaleOrderId() == null) {
            return;
        }
        ErpSaleOrderRespDTO saleOrder = saleOrderApi.getSaleOrder(reqVO.getSaleOrderId()).getCheckedData();
        if (saleOrder == null) {
            throw exception(PRO_WORK_ORDER_SALE_ORDER_NOT_EXISTS);
        }
        reqVO.setSaleOrderNo(saleOrder.getNo());
        if (reqVO.getClientId() == null && saleOrder.getCustomerId() != null) {
            reqVO.setClientId(saleOrder.getCustomerId());
        }
    }
    @Override
    public void updateProducedQuantity(Long id, BigDecimal incrQuantityProduced) {
        // 校验工单存在