fix(mes): 修复工艺路线【本工序产出】保存不生效 - 后端补齐 outputs 字段与 JSON 存储/解析

1. SaveReqVO/RespVO 增加 outputs(List<MesProProcessOutputItemVO>),支持{ itemId, productId }产出归属配置

2. ServiceImpl 将 outputs 序列化为 JSON 存入 output_item_id(兼容旧逗号分隔)

3. Controller 解析 output_item_id 回填 outputs(含物料/产品名称),并修复 String→Long 转换异常

4. 新增 MesOutputItemUtils,统一解析工单/MPS/排产/任务等 6 处 output_item_id 消费方
已添加2个文件
已修改9个文件
358 ■■■■■ 文件已修改
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/MesProRouteProcessController.java 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProProcessOutputItemVO.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessRespVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessSaveReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderProcessController.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProcessServiceImpl.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/schedule/MesProBatchScheduleService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/util/MesOutputItemUtils.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/MesProRouteProcessController.java
@@ -6,6 +6,7 @@
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.process.MesProProcessBomItemVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.process.MesProProcessOutputItemVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.process.MesProRouteProcessRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.process.MesProRouteProcessSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
@@ -146,48 +147,52 @@
        list.forEach(item -> { if (item.getNextProcessId() != null) processIds.add(item.getNextProcessId()); });
        Map<Long, MesProProcessDO> processMap = convertMap(
                processService.getProcessList(new ArrayList<>(processIds)), MesProProcessDO::getId);
        // 2. æ‰¹é‡èŽ·å–äº§å‡ºäº§å“ç‰©æ–™ä¿¡æ¯ï¼ˆè§£æžé€—å·åˆ†éš”çš„å­—ç¬¦ä¸²ï¼‰
        Set<Long> allOutputItemIds = new HashSet<>();
        Map<Long, List<Long>> voOutputItemIdsMap = new HashMap<>();
        for (int i = 0; i < list.size(); i++) {
            String outputItemIdStr = list.get(i).getOutputItemId();
            if (outputItemIdStr != null && !outputItemIdStr.isBlank()) {
                List<Long> ids = Arrays.stream(outputItemIdStr.split(","))
                        .map(String::trim).filter(s -> !s.isEmpty())
                        .map(Long::valueOf).collect(Collectors.toList());
                voOutputItemIdsMap.put((long) i, ids);
                allOutputItemIds.addAll(ids);
        // 2. è§£æžäº§å‡ºé…ç½®ï¼ˆJSON æ•°ç»„ æˆ– æ—§çš„逗号分隔),收集全部物料/产品编号
        List<List<MesProProcessOutputItemVO>> outputsPerRow = new ArrayList<>();
        Set<Long> allItemIds = new HashSet<>();
        for (MesProRouteProcessDO row : list) {
            List<MesProProcessOutputItemVO> outs = parseOutputsRaw(row.getOutputItemId());
            outputsPerRow.add(outs);
            for (MesProProcessOutputItemVO out : outs) {
                if (out.getItemId() != null) {
                    allItemIds.add(out.getItemId());
                }
                if (out.getProductId() != null) {
                    allItemIds.add(out.getProductId());
                }
            }
        }
        Map<Long, MesMdItemDO> outputItemMap = allOutputItemIds.isEmpty()
                ? Collections.emptyMap() : mdItemService.getItemMap(allOutputItemIds);
        // 3. æ‹¼æŽ¥ VO
        List<MesProRouteProcessRespVO> result = BeanUtils.toBean(list, MesProRouteProcessRespVO.class);
        Map<Long, MesMdItemDO> outputItemMap = allItemIds.isEmpty()
                ? Collections.emptyMap() : mdItemService.getItemMap(allItemIds);
        // 3. æ‹¼æŽ¥ VO(先把 output_item_id ç½®ç©ºï¼Œé¿å… String è½¬ Long æŠ› NumberFormatException;产出单独填充)
        List<MesProRouteProcessDO> safeList = BeanUtils.toBean(list, MesProRouteProcessDO.class);
        safeList.forEach(row -> row.setOutputItemId(null));
        List<MesProRouteProcessRespVO> result = BeanUtils.toBean(safeList, MesProRouteProcessRespVO.class);
        for (int i = 0; i < result.size(); i++) {
            MesProRouteProcessRespVO vo = result.get(i);
            MapUtils.findAndThen(processMap, vo.getProcessId(),
                    process -> vo.setProcessCode(process.getCode()).setProcessName(process.getName()));
            MapUtils.findAndThen(processMap, vo.getNextProcessId(),
                    nextProcess -> vo.setNextProcessName(nextProcess.getName()));
            // äº§å‡ºäº§å“ï¼ˆæ”¯æŒå¤šé€‰ï¼‰
            List<Long> ids = voOutputItemIdsMap.getOrDefault((long) i, Collections.emptyList());
            if (!ids.isEmpty()) {
                vo.setOutputItemIds(ids);
                List<String> names = ids.stream()
                        .map(id -> {
                            MesMdItemDO item = outputItemMap.get(id);
                            return item != null ? item.getName() : String.valueOf(id);
                        }).collect(Collectors.toList());
                vo.setOutputItemNames(String.join(", ", names));
                // å‘后兼容:第一个产出产品
                Long firstId = ids.get(0);
                vo.setOutputItemId(firstId);
                MesMdItemDO firstItem = outputItemMap.get(firstId);
                if (firstItem != null) {
                    vo.setOutputItemCode(firstItem.getCode());
                    vo.setOutputItemName(firstItem.getName());
                }
            // äº§å‡ºç‰©æ–™ï¼ˆæ”¯æŒå¤šé€‰ï¼Œå«å½’属产品)
            List<MesProProcessOutputItemVO> outs = outputsPerRow.get(i);
            for (MesProProcessOutputItemVO out : outs) {
                fillOutputItemInfo(out, outputItemMap);
            }
            if (!outs.isEmpty()) {
                List<Long> ids = outs.stream().map(MesProProcessOutputItemVO::getItemId)
                        .filter(Objects::nonNull).collect(Collectors.toList());
                vo.setOutputItemIds(ids);
                vo.setOutputItemNames(outs.stream().map(MesProProcessOutputItemVO::getItemName)
                        .filter(Objects::nonNull).collect(Collectors.joining(", ")));
                // å‘后兼容:第一个产出物料
                MesProProcessOutputItemVO first = outs.get(0);
                vo.setOutputItemId(first.getItemId());
                vo.setOutputItemCode(first.getItemCode());
                vo.setOutputItemName(first.getItemName());
            }
            // å‰ç«¯ä¸»ç”¨ï¼šoutputs(含归属产品)
            vo.setOutputs(outs);
        }
        return result;
    }
@@ -199,4 +204,73 @@
        return buildRouteProcessRespVOList(ListUtil.of(routeProcess)).get(0);
    }
    /**
     * è§£æž output_item_id ä¸ºäº§å‡ºé…ç½®åˆ—表(不含名称)
     * å…¼å®¹ JSON æ•°ç»„ [{"itemId":231,"productId":231}] ä¸Žé€—号分隔 "231,232" ä¸¤ç§æ ¼å¼
     */
    private List<MesProProcessOutputItemVO> parseOutputsRaw(String raw) {
        List<MesProProcessOutputItemVO> result = new ArrayList<>();
        if (raw == null || raw.isBlank()) {
            return result;
        }
        String text = raw.trim();
        if (text.startsWith("[")) {
            try {
                cn.hutool.json.JSONArray array = cn.hutool.json.JSONUtil.parseArray(text);
                for (Object element : array) {
                    if (!(element instanceof cn.hutool.json.JSONObject)) {
                        continue;
                    }
                    cn.hutool.json.JSONObject m = (cn.hutool.json.JSONObject) element;
                    Long itemId = m.getLong("itemId");
                    if (itemId == null) {
                        continue;
                    }
                    MesProProcessOutputItemVO vo = new MesProProcessOutputItemVO();
                    vo.setItemId(itemId);
                    vo.setProductId(m.getLong("productId"));
                    result.add(vo);
                }
                return result;
            } catch (Exception ignored) {
                // è§£æžå¤±è´¥æŒ‰é€—号兜底
                result.clear();
            }
        }
        for (String s : text.split(",")) {
            String t = s.trim();
            if (t.isEmpty()) {
                continue;
            }
            try {
                MesProProcessOutputItemVO vo = new MesProProcessOutputItemVO();
                vo.setItemId(Long.valueOf(t));
                result.add(vo);
            } catch (NumberFormatException ignored) {
                // è·³è¿‡éžæ³•值
            }
        }
        return result;
    }
    /**
     * å¡«å……产出物料的编码、名称与归属产品名称
     */
    private void fillOutputItemInfo(MesProProcessOutputItemVO vo, Map<Long, MesMdItemDO> itemMap) {
        if (vo.getItemId() == null) {
            return;
        }
        MesMdItemDO item = itemMap.get(vo.getItemId());
        if (item != null) {
            vo.setItemCode(item.getCode());
            vo.setItemName(item.getName());
        }
        if (vo.getProductId() != null) {
            MesMdItemDO product = itemMap.get(vo.getProductId());
            if (product != null) {
                vo.setProductName(product.getName());
            }
        }
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProProcessOutputItemVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.process;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
 * å·¥åºäº§å‡ºç‰©æ–™å½’属 VO
 *
 * ä¸€ä¸ªå·¥åºå¯ä»¥äº§å‡ºå¤šä¸ªç‰©æ–™ï¼Œæ¯ä¸ªç‰©æ–™å¯å½’属到某个产品(或「通用」)。
 * ä¸Žå‰ç«¯ RouteOutputAttribution ç»„ä»¶çš„ { itemId, productId } ç»“构对应。
 */
@Schema(description = "工序产出物料归属 VO")
@Data
public class MesProProcessOutputItemVO {
    @Schema(description = "产出物料编号", example = "231")
    private Long itemId;
    @Schema(description = "产出物料编码", example = "ITEM_20261102008")
    private String itemCode;
    @Schema(description = "产出物料名称", example = "钢绞线")
    private String itemName;
    @Schema(description = "归属产品编号;为空表示多产品通用", example = "231")
    private Long productId;
    @Schema(description = "归属产品名称")
    private String productName;
    @Schema(description = "排序", example = "1")
    private Integer sort;
    @Schema(description = "备注")
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessRespVO.java
@@ -83,6 +83,9 @@
    @Schema(description = "工序投料BOM列表")
    private List<MesProProcessBomItemVO> bomItems;
    @Schema(description = "产出物料归属配置(含归属产品)")
    private List<MesProProcessOutputItemVO> outputs;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessSaveReqVO.java
@@ -54,6 +54,9 @@
    @Schema(description = "产出产品物料编号列表(多选,为空时默认用工单成品)", example = "[100, 101]")
    private List<Long> outputItemIds;
    @Schema(description = "产出物料归属配置(含归属产品,前端主用)")
    private List<MesProProcessOutputItemVO> outputs;
    @Schema(description = "备注")
    private String remark;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java
@@ -30,6 +30,7 @@
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderBomService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.util.MesOutputItemUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -251,7 +252,11 @@
                    items.add(bom);
                }
            }
            processBomMap.put(Long.valueOf(process.getOutputItemId().split(",")[0].trim()), items);
            Long outputItemId = MesOutputItemUtils.firstItemId(process.getOutputItemId());
            if (outputItemId == null) {
                continue;
            }
            processBomMap.put(outputItemId, items);
        }
        // 2. å–最后一道工序(sort æœ€å¤§ï¼‰ä½œä¸ºå±•开起点,逐层向前递推
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderProcessController.java
@@ -18,6 +18,7 @@
import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderBomService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
import cn.iocoder.yudao.module.mes.util.MesOutputItemUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -135,15 +136,12 @@
        processIds.addAll(nextProcessIds);
        Map<Long, MesProProcessDO> processMap = CollUtil.isNotEmpty(processIds)
                ? processService.getProcessMap(processIds) : Collections.emptyMap();
        // èŽ·å–äº§å‡ºç‰©æ–™ï¼ˆè§£æžé€—å·åˆ†éš”çš„å­—ç¬¦ä¸²ï¼‰
        // èŽ·å–äº§å‡ºç‰©æ–™ï¼ˆå…¼å®¹ JSON å½’属配置与旧逗号分隔格式)
        Set<Long> allOutputItemIds = new HashSet<>();
        Map<Long, List<Long>> voOutputItemIdsMap = new HashMap<>();
        for (int i = 0; i < list.size(); i++) {
            String outputItemIdStr = list.get(i).getOutputItemId();
            if (outputItemIdStr != null && !outputItemIdStr.isBlank()) {
                List<Long> ids = Arrays.stream(outputItemIdStr.split(","))
                        .map(String::trim).filter(s -> !s.isEmpty())
                        .map(Long::valueOf).collect(Collectors.toList());
            List<Long> ids = MesOutputItemUtils.parseItemIds(list.get(i).getOutputItemId());
            if (!ids.isEmpty()) {
                voOutputItemIdsMap.put((long) i, ids);
                allOutputItemIds.addAll(ids);
            }
@@ -175,8 +173,10 @@
            });
        }));
        // 3. æ‹¼æŽ¥ VO
        List<MesProWorkOrderProcessRespVO> result = BeanUtils.toBean(list, MesProWorkOrderProcessRespVO.class);
        // 3. æ‹¼æŽ¥ VO(先置空 output_item_id,避免 String è½¬ Long å¼‚常;产出下面单独填充)
        List<MesProWorkOrderProcessDO> safeList = BeanUtils.toBean(list, MesProWorkOrderProcessDO.class);
        safeList.forEach(row -> row.setOutputItemId(null));
        List<MesProWorkOrderProcessRespVO> result = BeanUtils.toBean(safeList, MesProWorkOrderProcessRespVO.class);
        for (int i = 0; i < result.size(); i++) {
            MesProWorkOrderProcessRespVO vo = result.get(i);
            MapUtils.findAndThen(processMap, vo.getProcessId(), process -> {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
@@ -30,6 +30,7 @@
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.process.MesProWorkOrderProcessSaveReqVO;
import cn.iocoder.yudao.module.mes.util.MesOutputItemUtils;
import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
@@ -576,9 +577,7 @@
            process.setCheckFlag(rp.getCheckFlag());
            process.setBackflushFlag(rp.getBackflushFlag());
            if (rp.getOutputItemId() != null && !rp.getOutputItemId().isBlank()) {
                process.setOutputItemIds(Arrays.stream(rp.getOutputItemId().split(","))
                        .map(String::trim).filter(s -> !s.isEmpty())
                        .map(Long::valueOf).collect(Collectors.toList()));
                process.setOutputItemIds(MesOutputItemUtils.parseItemIds(rp.getOutputItemId()));
            }
            process.setColorCode(rp.getColorCode());
            // å¸¦å…¥å·¥åºæŠ•æ–™BOM
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProcessServiceImpl.java
@@ -2,6 +2,7 @@
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.process.MesProProcessOutputItemVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.process.MesProRouteProcessSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductBomDO;
@@ -14,9 +15,11 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -63,12 +66,8 @@
        // 2. æ’å…¥
        MesProRouteProcessDO routeProcess = BeanUtils.toBean(createReqVO, MesProRouteProcessDO.class);
        if (CollUtil.isNotEmpty(createReqVO.getOutputItemIds())) {
            routeProcess.setOutputItemId(
                createReqVO.getOutputItemIds().stream()
                    .map(String::valueOf)
                    .collect(Collectors.joining(",")));
        }
        // äº§å‡ºé…ç½®ï¼ˆå«å½’属产品)序列化为 JSON å­˜å…¥ output_item_id;兼容旧的 outputItemIds å¹³é“ºæ ¼å¼
        routeProcess.setOutputItemId(serializeOutputs(createReqVO.getOutputs(), createReqVO.getOutputItemIds()));
        routeProcessMapper.insert(routeProcess);
        // 3. æ›´æ–°å·¥åºé“¾
@@ -98,11 +97,10 @@
        // 2. æ›´æ–°
        MesProRouteProcessDO updateObj = BeanUtils.toBean(updateReqVO, MesProRouteProcessDO.class);
        if (CollUtil.isNotEmpty(updateReqVO.getOutputItemIds())) {
            updateObj.setOutputItemId(
                updateReqVO.getOutputItemIds().stream()
                    .map(String::valueOf)
                    .collect(Collectors.joining(",")));
        // äº§å‡ºé…ç½®ï¼šåªè¦å‰ç«¯æ˜¾å¼ä¼ äº† outputs / outputItemIds å°±è¦†ç›–(传空数组表示清空)
        if (updateReqVO.getOutputs() != null || updateReqVO.getOutputItemIds() != null) {
            String serialized = serializeOutputs(updateReqVO.getOutputs(), updateReqVO.getOutputItemIds());
            updateObj.setOutputItemId(serialized == null ? "" : serialized);
        }
        routeProcessMapper.updateById(updateObj);
@@ -245,4 +243,38 @@
        routeProcessMapper.deleteByRouteId(routeId);
    }
    /**
     * äº§å‡ºé…ç½®åºåˆ—化到 output_item_id å­—段(JSON æ•°ç»„)
     * æ ¼å¼ï¼š[{"itemId":231,"productId":231}]
     */
    private String serializeOutputs(List<MesProProcessOutputItemVO> outputs, List<Long> outputItemIds) {
        List<Map<String, Object>> list = new ArrayList<>();
        if (CollUtil.isNotEmpty(outputs)) {
            for (MesProProcessOutputItemVO vo : outputs) {
                if (vo == null || vo.getItemId() == null) {
                    continue;
                }
                Map<String, Object> m = new LinkedHashMap<>();
                m.put("itemId", vo.getItemId());
                if (vo.getProductId() != null) {
                    m.put("productId", vo.getProductId());
                }
                list.add(m);
            }
        } else if (CollUtil.isNotEmpty(outputItemIds)) {
            for (Long id : outputItemIds) {
                if (id == null) {
                    continue;
                }
                Map<String, Object> m = new LinkedHashMap<>();
                m.put("itemId", id);
                list.add(m);
            }
        }
        if (list.isEmpty()) {
            return null;
        }
        return cn.hutool.json.JSONUtil.toJsonStr(list);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
@@ -36,6 +36,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderParamRecordService;
import cn.iocoder.yudao.module.mes.util.MesOutputItemUtils;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -151,8 +152,9 @@
            MesProRouteProcessDO routeProcess = routeProcessService
                    .getRouteProcessByRouteIdAndProcessId(createReqVO.getRouteId(), createReqVO.getProcessId());
            if (routeProcess != null && routeProcess.getOutputItemId() != null) {
                itemId = Long.valueOf(routeProcess.getOutputItemId().split(",")[0].trim());
            } else {
                itemId = MesOutputItemUtils.firstItemId(routeProcess.getOutputItemId());
            }
            if (itemId == null) {
                itemId = workOrder.getProductId();
            }
        }
@@ -563,7 +565,8 @@
        List<Long> taskIds = new java.util.ArrayList<>();
        for (MesProRouteProcessDO rp : routeProcesses) {
            cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO proc = procMap.get(rp.getProcessId());
            Long itemId = rp.getOutputItemId() != null ? Long.valueOf(rp.getOutputItemId().split(",")[0].trim()) : workOrder.getProductId();
            Long outputItemId = rp.getOutputItemId() != null ? MesOutputItemUtils.firstItemId(rp.getOutputItemId()) : null;
        Long itemId = outputItemId != null ? outputItemId : workOrder.getProductId();
            cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO item = itemService.validateItemExists(itemId);
            MesProTaskDO task = new MesProTaskDO()
                    .setWorkOrderId(workOrder.getId())
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/schedule/MesProBatchScheduleService.java
@@ -22,6 +22,7 @@
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
import cn.iocoder.yudao.module.mes.service.cal.holiday.MesCalHolidayService;
import cn.iocoder.yudao.module.mes.util.MesOutputItemUtils;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -252,9 +253,9 @@
                    }
                    Long itemId = order.getProductId();
                    if (process.getOutputItemId() != null && !process.getOutputItemId().isBlank()) {
                        String[] output = process.getOutputItemId().split(",");
                        if (output.length != 1) throw new IllegalArgumentException("多产出工序请使用手工排产确认各产出数量");
                        itemId = Long.valueOf(output[0].trim());
                        List<Long> outputIds = MesOutputItemUtils.parseItemIds(process.getOutputItemId());
                        if (outputIds.size() != 1) throw new IllegalArgumentException("多产出工序请使用手工排产确认各产出数量");
                        itemId = outputIds.get(0);
                    }
                    var item = itemService.validateItemExists(itemId);
                    if (!Objects.equals(item.getStatus(), 0)) throw new IllegalArgumentException("工序产出产品已停用");
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/util/MesOutputItemUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,77 @@
package cn.iocoder.yudao.module.mes.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import java.util.ArrayList;
import java.util.List;
/**
 * MES å·¥åºäº§å‡ºç‰©æ–™é…ç½®è§£æžå·¥å…·
 *
 * <p>mes_pro_route_process.output_item_id / mes_pro_work_order_process.output_item_id å­—段
 * åŒæ—¶å…¼å®¹ä¸¤ç§å­˜å‚¨æ ¼å¼ï¼š
 * <ul>
 *     <li>新格式(含归属产品):[{"itemId":231,"productId":231}]</li>
 *     <li>旧格式(纯物料编号,逗号分隔):231,232</li>
 * </ul>
 *
 * @author è¶…级管理员
 */
public class MesOutputItemUtils {
    private MesOutputItemUtils() {
    }
    /**
     * è§£æžå­—段中的全部产出物料编号(不含归属产品)
     */
    public static List<Long> parseItemIds(String raw) {
        List<Long> ids = new ArrayList<>();
        if (raw == null || raw.isBlank()) {
            return ids;
        }
        String text = raw.trim();
        if (text.startsWith("[")) {
            try {
                JSONArray array = JSONUtil.parseArray(text);
                for (Object element : array) {
                    if (!(element instanceof JSONObject)) {
                        continue;
                    }
                    Long itemId = ((JSONObject) element).getLong("itemId");
                    if (itemId != null) {
                        ids.add(itemId);
                    }
                }
                return ids;
            } catch (Exception ignored) {
                // è§£æžå¤±è´¥æŒ‰é€—号分隔兜底
                ids.clear();
            }
        }
        for (String s : text.split(",")) {
            String t = s.trim();
            if (t.isEmpty()) {
                continue;
            }
            try {
                ids.add(Long.valueOf(t));
            } catch (NumberFormatException ignored) {
                // è·³è¿‡éžæ³•值
            }
        }
        return ids;
    }
    /**
     * å–第一个产出物料编号,解析不到返回 null
     */
    public static Long firstItemId(String raw) {
        List<Long> ids = parseItemIds(raw);
        return CollUtil.isEmpty(ids) ? null : ids.get(0);
    }
}