云
2026-10-27 ffca46b39d25439a06a27cafda2ec523a3fb67cf
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
@@ -5,13 +5,14 @@
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.mes.controller.admin.pro.mps.vo.MesProMpsPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.mps.vo.MesProMpsSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.mps.MesProMpsDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.mps.MesProMpsMergeItemDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.mps.MesProMpsMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.mps.MesProMpsMergeItemMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
@@ -69,7 +70,9 @@
    @Resource
    private MesProMpsMapper mpsMapper;
    @Resource
    private MesProMpsMergeItemMapper mergeItemMapper;
    private ErpSaleOrderApi saleOrderApi;
    @Resource
    private PermissionApi permissionApi;
    @Resource
    private MesProWorkOrderService workOrderService;
    @Resource
@@ -93,7 +96,7 @@
    @Transactional(rollbackFor = Exception.class)
    public Long createMps(Long saleOrderId, String saleOrderNo, Long saleOrderItemId,
                          Long mdmItemId, String productName, BigDecimal quantity, Long clientId,
                          String requestDate, String remark) {
                          String requestDate, String remark, String reelNo) {
        // 1. 根据 MDM 物料 ID 查找 MES 产品
        cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO mesItem = itemService.getItemByMdmItemId(mdmItemId);
        if (mesItem == null) {
@@ -121,6 +124,7 @@
        mps.setSaleOrderId(saleOrderId);
        mps.setSaleOrderNo(saleOrderNo);
        mps.setSaleOrderItemId(saleOrderItemId);
        mps.setReelNo(reelNo);
        mps.setProductId(mesItem.getId()); // 使用 MES 产品 ID
        mps.setQuantity(quantity);
        mps.setQuantityProduced(BigDecimal.ZERO);
@@ -194,7 +198,14 @@
    @Transactional(rollbackFor = Exception.class)
    public Long issueMps(Long id) {
        // 校验存在 + 草稿状态
        MesProMpsDO mps = validateMpsExistsAndDraft(id);
        MesProMpsDO mps = mpsMapper.selectByIdForUpdate(id);
        if (mps == null) {
            throw exception(PRO_MPS_NOT_EXISTS);
        }
        if (!MesProMpsStatusEnum.DRAFT.getStatus().equals(mps.getStatus())) {
            throw exception(PRO_MPS_NOT_DRAFT);
        }
        validateIssuePermission(mps);
        // 创建生产工单
        Long workOrderId = createWorkOrderFromMps(mps, mps.getQuantity());
@@ -210,66 +221,30 @@
        return workOrderId;
    }
    private void validateIssuePermission(MesProMpsDO mps) {
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        if (userId == null) {
            throw exception(PRO_MPS_ISSUE_FORBIDDEN);
        }
        if (permissionApi.hasAnyRoles(userId, "super_admin")) {
            return;
        }
        // 手工创建且没有销售订单的计划以计划创建人为准。
        String creator = mps.getCreator();
        if (mps.getSaleOrderId() != null) {
            var order = saleOrderApi.getSaleOrder(mps.getSaleOrderId()).getCheckedData();
            creator = order != null ? order.getCreator() : null;
        }
        if (!String.valueOf(userId).equals(creator)) {
            throw exception(PRO_MPS_ISSUE_FORBIDDEN);
        }
    }
    /** 旧合并下发入口已停用;历史合并记录仍由取消和查询逻辑处理。 */
    @Deprecated
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long mergeAndIssueMps(List<Long> mpsIds) {
        // 校验 MPS 列表
        if (CollUtil.isEmpty(mpsIds) || mpsIds.size() < 2) {
            throw exception(PRO_MPS_MERGE_AT_LEAST_TWO);
        }
        List<MesProMpsDO> mpsList = mpsMapper.selectBatchIds(mpsIds);
        if (mpsList.size() != mpsIds.size()) {
            throw exception(PRO_MPS_NOT_EXISTS);
        }
        // 校验:必须是相同产品、草稿状态
        Long productId = null;
        for (MesProMpsDO mps : mpsList) {
            if (!MesProMpsStatusEnum.DRAFT.getStatus().equals(mps.getStatus())) {
                throw exception(PRO_MPS_NOT_DRAFT);
            }
            if (productId == null) {
                productId = mps.getProductId();
            } else if (!productId.equals(mps.getProductId())) {
                throw exception(PRO_MPS_MERGE_PRODUCT_MISMATCH);
            }
        }
        // 计算合并数量
        BigDecimal totalQuantity = mpsList.stream()
                .map(MesProMpsDO::getQuantity)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        // 取第一条 MPS 作为主记录,创建生产工单
        MesProMpsDO firstMps = mpsList.get(0);
        Long workOrderId = createWorkOrderFromMps(firstMps, totalQuantity);
        // 生成合并批次号
        String mergeBatchNo = generateMergeBatchNo();
        // 更新所有 MPS 状态,记录合并关系
        for (MesProMpsDO mps : mpsList) {
            mpsMapper.updateById(new MesProMpsDO()
                    .setId(mps.getId())
                    .setStatus(MesProMpsStatusEnum.ISSUED.getStatus())
                    .setWorkOrderId(workOrderId)
                    .setMergeFlag(1)
                    .setMergeBatchNo(mergeBatchNo)
                    .setIssueDate(LocalDateTime.now()));
            // 记录合并明细
            MesProMpsMergeItemDO mergeItem = new MesProMpsMergeItemDO();
            mergeItem.setMergeBatchNo(mergeBatchNo);
            mergeItem.setMpsId(mps.getId());
            mergeItem.setWorkOrderId(workOrderId);
            mergeItem.setQuantity(mps.getQuantity());
            mergeItemMapper.insert(mergeItem);
        }
        log.info("[mergeAndIssueMps] MPS 合并下发成功,mpsIds={}, workOrderId={}, totalQuantity={}",
                mpsIds, workOrderId, totalQuantity);
        return workOrderId;
        throw exception(PRO_MPS_MERGE_ISSUE_DISABLED);
    }
    @Override
@@ -452,12 +427,13 @@
                    mps.getId(), quantityProduced, mps.getQuantity());
            return false;
        }
        // 2. 检查工单下所有报工是否完成(确保质检完成)
        // 2. 已审批待检的报工已计入生产进度,不以质检状态阻塞生产完成。
        if (mps.getWorkOrderId() != null) {
            Long unfinishedCount = feedbackMapper.selectCount(
                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesProFeedbackDO>()
                            .eq(MesProFeedbackDO::getWorkOrderId, mps.getWorkOrderId())
                            .ne(MesProFeedbackDO::getStatus, MesProFeedbackStatusEnum.FINISHED.getStatus()));
                            .notIn(MesProFeedbackDO::getStatus, MesProFeedbackStatusEnum.FINISHED.getStatus(),
                                    MesProFeedbackStatusEnum.UNCHECK.getStatus()));
            log.debug("[isMpsFinished] MPS {} unfinished feedback count: {}", mps.getId(), unfinishedCount);
            if (unfinishedCount > 0) {
                return false;
@@ -481,9 +457,8 @@
        }
    }
    /**
     * 生成合并批次号
     */
    /** 已停用:旧合并下发批次号生成逻辑,仅供识别历史实现,不再调用。 */
    @Deprecated
    private String generateMergeBatchNo() {
        return "MERGE" + System.currentTimeMillis();
    }
@@ -555,6 +530,7 @@
        workOrderReq.setOrderSourceType(MesProWorkOrderSourceTypeEnum.ORDER.getType());
        workOrderReq.setOrderSourceCode(mps.getSaleOrderNo());
        workOrderReq.setProductId(mps.getProductId());
        workOrderReq.setReelNo(mps.getReelNo());
        workOrderReq.setQuantity(quantity);
        workOrderReq.setClientId(mps.getClientId());
        workOrderReq.setRequestDate(mps.getRequestDate() != null ? mps.getRequestDate() : LocalDateTime.now());