liu
2026-09-04 c6eb0d852595f235b21520dab1d8600ed1b80776
Merge remote-tracking branch 'origin/dev_business' into dev_business
已添加2个文件
已修改34个文件
1220 ■■■■■ 文件已修改
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/MesQcIpqcController.java 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcPageReqVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcRespVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcSaveReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/iqc/MesQcIqcController.java 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/iqc/vo/MesQcIqcRespVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/iqc/vo/MesQcIqcSaveReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/MesQcOqcController.java 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/MesQcOqcPageReqVO.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/MesQcOqcRespVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/MesQcOqcSaveReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/rqc/MesQcRqcController.java 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/rqc/vo/MesQcRqcPageReqVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/rqc/vo/MesQcRqcRespVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/template/MesQcTemplateItemController.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/template/vo/item/MesQcTemplateItemOptionRespVO.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/vo/MesQcQualityExportVO.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/indicatorresult/MesQcIndicatorResultDetailMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/indicatorresult/MesQcIndicatorResultMapper.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/ipqc/MesQcIpqcLineMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/ipqc/MesQcIpqcMapper.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/iqc/MesQcIqcLineMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/oqc/MesQcOqcLineMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/oqc/MesQcOqcMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/rqc/MesQcRqcLineMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/rqc/MesQcRqcMapper.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/indicatorresult/MesQcIndicatorResultDetailService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/indicatorresult/MesQcIndicatorResultDetailServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/indicatorresult/MesQcIndicatorResultService.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/indicatorresult/MesQcIndicatorResultServiceImpl.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImpl.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcServiceImpl.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateItemService.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateItemServiceImpl.java 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/MesQcIpqcController.java
@@ -1,26 +1,38 @@
package cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.vo.MesQcQualityExportVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcLineMapper;
import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService;
import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
import cn.iocoder.yudao.module.mes.service.qc.ipqc.MesQcIpqcService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
@@ -35,9 +47,14 @@
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -62,6 +79,12 @@
    private MesMdItemService itemService;
    @Resource
    private MesMdUnitMeasureService unitMeasureService;
    @Resource
    private MesQcIndicatorResultService indicatorResultService;
    @Resource
    private MesQcIndicatorService indicatorService;
    @Resource
    private MesQcIpqcLineMapper ipqcLineMapper;
    @Resource
    private AdminUserApi adminUserApi;
@@ -124,8 +147,8 @@
                                HttpServletResponse response) throws IOException {
        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        List<MesQcIpqcDO> list = ipqcService.getIpqcPage(pageReqVO).getList();
        List<MesQcIpqcRespVO> voList = buildIpqcRespVOList(list);
        ExcelUtils.write(response, "过程检验单.xls", "数据", MesQcIpqcRespVO.class, voList);
        List<MesQcQualityExportVO> exportList = buildIpqcQualityExportList(list);
        ExcelUtils.write(response, "过程检验单.xls", "数据", MesQcQualityExportVO.class, exportList);
    }
    // ==================== æ‹¼æŽ¥ VO ====================
@@ -152,6 +175,8 @@
        // æ‰¹é‡æŸ¥è¯¢æ£€æµ‹äººå‘˜
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                convertSet(list, MesQcIpqcDO::getInspectorUserId));
        // æ‰¹é‡æŸ¥è¯¢æ£€éªŒé¡¹æŒ‡æ ‡
        Map<Long, String> indicatorSummaryMap = buildIndicatorSummaryMap(list);
        // æ‹¼è£… VO
        return BeanUtils.toBean(list, MesQcIpqcRespVO.class, vo -> {
            findAndThen(workOrderMap, vo.getWorkOrderId(), workOrder -> vo
@@ -167,7 +192,167 @@
            });
            findAndThen(userMap, vo.getInspectorUserId(),
                    user -> vo.setInspectorNickname(user.getNickname()));
            vo.setIndicatorResultSummary(indicatorSummaryMap.getOrDefault(vo.getId(), ""));
        });
    }
    /**
     * æž„建每个检验单的检验项指标汇总字符串(qcType = IPQC)
     */
    private Map<Long, String> buildIndicatorSummaryMap(List<MesQcIpqcDO> list) {
        Map<Long, String> indicatorSummaryMap = new HashMap<>();
        Set<Long> qcIds = convertSet(list, MesQcIpqcDO::getId);
        if (CollUtil.isEmpty(qcIds)) {
            return indicatorSummaryMap;
        }
        // æŒ‰ QC ID æ‰¹é‡æŸ¥è¯¢æ£€éªŒç»“æžœ
        List<MesQcIndicatorResultDO> indicatorResults = indicatorResultService.getIndicatorResultListByQcIdsAndType(
                new ArrayList<>(qcIds), MesBizTypeConstants.QC_IPQC);
        if (CollUtil.isEmpty(indicatorResults)) {
            return indicatorSummaryMap;
        }
        // æŒ‰ QC ID åˆ†ç»„结果
        Map<Long, List<MesQcIndicatorResultDO>> resultMap = indicatorResults.stream()
                .collect(Collectors.groupingBy(MesQcIndicatorResultDO::getQcId));
        // æ‰¹é‡æŸ¥è¯¢æ£€éªŒç»“果明细
        Set<Long> resultIds = convertSet(indicatorResults, MesQcIndicatorResultDO::getId);
        List<MesQcIndicatorResultDetailDO> indicatorDetails = indicatorResultService.getIndicatorResultDetailListByResultIds(
                new ArrayList<>(resultIds));
        // æŒ‰ç»“æžœ ID åˆ†ç»„明细
        Map<Long, List<MesQcIndicatorResultDetailDO>> detailMapByResultId = indicatorDetails.stream()
                .collect(Collectors.groupingBy(MesQcIndicatorResultDetailDO::getResultId));
        // æ‰¹é‡æŸ¥è¯¢æ£€æµ‹æŒ‡æ ‡
        Set<Long> indicatorIds = convertSet(indicatorDetails, MesQcIndicatorResultDetailDO::getIndicatorId);
        Map<Long, MesQcIndicatorDO> indicatorMap = indicatorService.getIndicatorMap(indicatorIds);
        // æž„建每个 QC çš„æŒ‡æ ‡æ±‡æ€»å­—符串
        resultMap.forEach((qcId, results) -> {
            List<String> indicatorSummaries = new ArrayList<>();
            for (MesQcIndicatorResultDO result : results) {
                List<MesQcIndicatorResultDetailDO> details = detailMapByResultId.getOrDefault(result.getId(), Collections.emptyList());
                for (MesQcIndicatorResultDetailDO detail : details) {
                    // é€šè¿‡ indicatorId å…³è”指标名称,查不到则回退为 "指标"
                    String indicatorName = "指标";
                    MesQcIndicatorDO indicator = indicatorMap.get(detail.getIndicatorId());
                    if (indicator != null && StrUtil.isNotBlank(indicator.getName())) {
                        indicatorName = indicator.getName();
                    }
                    String value = detail.getValue();
                    String remark = detail.getRemark();
                    // æ ¼å¼åŒ–为 "指标名称:值" æˆ– "指标名称:值(备注)"
                    String indicatorStr = indicatorName + ":" + value;
                    if (StrUtil.isNotBlank(remark)) {
                        indicatorStr += "(" + remark + ")";
                    }
                    indicatorSummaries.add(indicatorStr);
                }
            }
            indicatorSummaryMap.put(qcId, String.join(", ", indicatorSummaries));
        });
        return indicatorSummaryMap;
    }
    /**
     * æž„建导出行:一单一行,检验指标 / åˆ¤å®šä¾æ®å–自行明细并拼接
     */
    private List<MesQcQualityExportVO> buildIpqcQualityExportList(List<MesQcIpqcDO> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        // æ‰¹é‡æŸ¥è¯¢æ£€æµ‹äººå‘˜
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                convertSet(list, MesQcIpqcDO::getInspectorUserId));
        // æ‰¹é‡æŸ¥è¯¢ç‰©æ–™
        Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(
                convertSet(list, MesQcIpqcDO::getItemId));
        // æ‰¹é‡æŸ¥è¯¢è¡Œæ˜Žç»†
        List<MesQcIpqcLineDO> lines = ipqcLineMapper.selectListByIpqcIds(convertSet(list, MesQcIpqcDO::getId));
        Map<Long, List<MesQcIpqcLineDO>> lineMap = lines.stream()
                .collect(Collectors.groupingBy(MesQcIpqcLineDO::getIpqcId));
        // æ‰¹é‡æŸ¥è¯¢æ£€æµ‹æŒ‡æ ‡
        Map<Long, MesQcIndicatorDO> indicatorMap = CollUtil.isEmpty(lines) ? Collections.emptyMap()
                : indicatorService.getIndicatorMap(convertSet(lines, MesQcIpqcLineDO::getIndicatorId));
        // æ‰¹é‡æŸ¥è¯¢å®žæµ‹å€¼ï¼šæ£€éªŒå• ID -> {指标 ID -> å®žæµ‹å€¼}
        Map<Long, Map<Long, String>> measureMap = buildMeasureValueMap(
                convertSet(list, MesQcIpqcDO::getId), MesBizTypeConstants.QC_IPQC);
        // ç»„装导出行
        List<MesQcQualityExportVO> exportList = new ArrayList<>(list.size());
        for (MesQcIpqcDO ipqc : list) {
            MesQcQualityExportVO vo = new MesQcQualityExportVO();
            vo.setDocName(ipqc.getName());
            MesMdItemDO item = itemMap.get(ipqc.getItemId());
            vo.setItemName(item != null ? item.getName() : "");
            // IPQC æ— æ‰¹æ¬¡å·å­—段,暂用来源单据编号(一般为生产工单号)作为批号
            vo.setBatchNo(ipqc.getSourceDocCode());
            vo.setInspectDate(MesQcQualityExportVO.formatDateTime(ipqc.getInspectDate()));
            List<String> indicatorNames = new ArrayList<>();
            List<String> judgementSegments = new ArrayList<>();
            for (MesQcIpqcLineDO line : lineMap.getOrDefault(ipqc.getId(), Collections.emptyList())) {
                MesQcIndicatorDO indicator = indicatorMap.get(line.getIndicatorId());
                String name = indicator != null && StrUtil.isNotBlank(indicator.getName())
                        ? indicator.getName() : "";
                String range = MesQcQualityExportVO.buildRangeText(
                        line.getStandardValue(), line.getMinThreshold(), line.getMaxThreshold());
                if (StrUtil.isNotBlank(name)) {
                    String measuredValue = measureMap
                            .getOrDefault(ipqc.getId(), Collections.emptyMap())
                            .get(line.getIndicatorId());
                    indicatorNames.add(StrUtil.isNotBlank(measuredValue) ? name + ":" + measuredValue : name);
                    if (StrUtil.isNotBlank(range)) {
                        judgementSegments.add(name + range);
                    }
                }
            }
            vo.setIndicatorNames(String.join("、", indicatorNames));
            vo.setJudgementBasis(String.join(";", judgementSegments));
            vo.setCheckResultText(getCheckResultLabel(ipqc.getCheckResult()));
            findAndThen(userMap, ipqc.getInspectorUserId(),
                    user -> vo.setInspectorName(user.getNickname()));
            // æš‚无审核人数据来源,导出空列
            vo.setAuditorName("");
            vo.setRemark(ipqc.getRemark());
            exportList.add(vo);
        }
        return exportList;
    }
    /**
     * æž„建 æ£€éªŒå• ID -> {指标 ID -> å®žæµ‹å€¼} æ˜ å°„,实测值取自检验结果明细表
     */
    private Map<Long, Map<Long, String>> buildMeasureValueMap(Set<Long> qcIds, int qcType) {
        Map<Long, Map<Long, String>> measureMap = new HashMap<>();
        if (CollUtil.isEmpty(qcIds)) {
            return measureMap;
        }
        List<MesQcIndicatorResultDO> results = indicatorResultService.getIndicatorResultListByQcIdsAndType(
                new ArrayList<>(qcIds), qcType);
        if (CollUtil.isEmpty(results)) {
            return measureMap;
        }
        Map<Long, Long> resultQcMap = results.stream()
                .collect(Collectors.toMap(MesQcIndicatorResultDO::getId, MesQcIndicatorResultDO::getQcId));
        List<MesQcIndicatorResultDetailDO> details = indicatorResultService.getIndicatorResultDetailListByResultIds(
                new ArrayList<>(resultQcMap.keySet()));
        if (CollUtil.isEmpty(details)) {
            return measureMap;
        }
        for (MesQcIndicatorResultDetailDO detail : details) {
            Long qcId = resultQcMap.get(detail.getResultId());
            if (qcId == null) {
                continue;
            }
            measureMap.computeIfAbsent(qcId, k -> new HashMap<>())
                    .put(detail.getIndicatorId(), detail.getValue());
        }
        return measureMap;
    }
    private String getCheckResultLabel(Integer checkResult) {
        if (checkResult == null) {
            return "";
        }
        return DictFrameworkUtils.parseDictDataLabel(
                DictTypeConstants.MES_QC_CHECK_RESULT, String.valueOf(checkResult));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcPageReqVO.java
@@ -38,4 +38,8 @@
    @Schema(description = "检测人员用户 ID", example = "1")
    private Long inspectorUserId;
    @Schema(description = "检测日期")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
    private LocalDateTime[] inspectDate;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcRespVO.java
@@ -153,6 +153,12 @@
    @DictFormat(DictTypeConstants.MES_QC_CHECK_RESULT)
    private Integer checkResult;
    // ========== æ£€éªŒé¡¹æŒ‡æ ‡ ==========
    @Schema(description = "检验项指标汇总", example = "外观:良好(OK), å°ºå¯¸:100.00mm(OK)")
    @ExcelProperty("检验项指标")
    private String indicatorResultSummary;
    @Schema(description = "检测日期")
    @ExcelProperty("检测日期")
    private LocalDateTime inspectDate;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcSaveReqVO.java
@@ -63,6 +63,9 @@
    @Schema(description = "产品物料 ID", example = "50")
    private Long itemId;
    @Schema(description = "质检方案 ID", example = "5")
    private Long templateId;
    // ========== æ•°é‡ ==========
    @Schema(description = "检测数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/iqc/MesQcIqcController.java
@@ -1,22 +1,34 @@
package cn.iocoder.yudao.module.mes.controller.admin.qc.iqc;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.mes.controller.admin.qc.iqc.vo.MesQcIqcPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.iqc.vo.MesQcIqcRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.iqc.vo.MesQcIqcSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.vo.MesQcQualityExportVO;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.iqc.MesQcIqcLineMapper;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService;
import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
@@ -31,9 +43,14 @@
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -52,9 +69,15 @@
    @Resource
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private MesQcIndicatorResultService indicatorResultService;
    @Resource
    private MesQcIndicatorService indicatorService;
    @Resource
    private MesMdItemService itemService;
    @Resource
    private MesMdUnitMeasureService unitMeasureService;
    @Resource
    private MesQcIqcLineMapper iqcLineMapper;
    @Resource
    private AdminUserApi adminUserApi;
@@ -117,8 +140,8 @@
                               HttpServletResponse response) throws IOException {
        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        List<MesQcIqcDO> list = iqcService.getIqcPage(pageReqVO).getList();
        List<MesQcIqcRespVO> voList = buildIqcRespVOList(list);
        ExcelUtils.write(response, "来料检验单.xls", "数据", MesQcIqcRespVO.class, voList);
        List<MesQcQualityExportVO> exportList = buildIqcQualityExportList(list);
        ExcelUtils.write(response, "来料检验单.xls", "数据", MesQcQualityExportVO.class, exportList);
    }
    // ==================== æ‹¼æŽ¥ VO ====================
@@ -140,6 +163,63 @@
        // æ‰¹é‡æŸ¥è¯¢æ£€æµ‹äººå‘˜
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                convertSet(list, MesQcIqcDO::getInspectorUserId));
        // æ‰¹é‡æŸ¥è¯¢æ£€éªŒé¡¹æŒ‡æ ‡
        Set<Long> qcIds = convertSet(list, MesQcIqcDO::getId);
        Map<Long, List<MesQcIndicatorResultDO>> indicatorResultMap = new HashMap<>();
        if (CollUtil.isNotEmpty(qcIds)) {
            // æŒ‰QC ID批量查询检验结果(IQC类型)
            List<MesQcIndicatorResultDO> indicatorResults = indicatorResultService.getIndicatorResultListByQcIdsAndType(
                    new ArrayList<>(qcIds), MesBizTypeConstants.QC_IQC);
            indicatorResults.forEach(result -> {
                indicatorResultMap.computeIfAbsent(result.getQcId(), k -> new ArrayList<>()).add(result);
            });
        }
        // æ‰¹é‡æŸ¥è¯¢æ£€éªŒç»“果明细并构建指标映射
        Map<Long, String> indicatorSummaryMap = new HashMap<>();
        if (CollUtil.isNotEmpty(indicatorResultMap)) {
            Set<Long> resultIds = new HashSet<>();
            indicatorResultMap.values().forEach(results -> {
                results.forEach(result -> resultIds.add(result.getId()));
            });
            if (CollUtil.isNotEmpty(resultIds)) {
                // æ‰¹é‡æŸ¥è¯¢æ£€éªŒç»“果明细
                List<MesQcIndicatorResultDetailDO> indicatorDetails = indicatorResultService.getIndicatorResultDetailListByResultIds(
                        new ArrayList<>(resultIds));
                // æŒ‰ç»“æžœID分组明细
                Map<Long, List<MesQcIndicatorResultDetailDO>> detailMapByResultId = indicatorDetails.stream()
                        .collect(Collectors.groupingBy(MesQcIndicatorResultDetailDO::getResultId));
                // æ‰¹é‡æŸ¥è¯¢æ£€æµ‹æŒ‡æ ‡ï¼ˆindicatorId -> indicator)
                Set<Long> indicatorIds = convertSet(indicatorDetails, MesQcIndicatorResultDetailDO::getIndicatorId);
                Map<Long, MesQcIndicatorDO> indicatorMap = indicatorService.getIndicatorMap(indicatorIds);
                // æž„建每个QC的指标汇总字符串
                indicatorResultMap.forEach((qcId, results) -> {
                    List<String> indicatorSummaries = new ArrayList<>();
                    for (MesQcIndicatorResultDO result : results) {
                        List<MesQcIndicatorResultDetailDO> details = detailMapByResultId.getOrDefault(result.getId(), Collections.emptyList());
                        for (MesQcIndicatorResultDetailDO detail : details) {
                            // é€šè¿‡ indicatorId å…³è”指标名称,查不到则回退为 "指标"
                            String indicatorName = "指标";
                            MesQcIndicatorDO indicator = indicatorMap.get(detail.getIndicatorId());
                            if (indicator != null && StrUtil.isNotBlank(indicator.getName())) {
                                indicatorName = indicator.getName();
                            }
                            String value = detail.getValue();
                            String remark = detail.getRemark();
                            // æ ¼å¼åŒ–为 "指标名称:值" æˆ– "指标名称:值(备注)"
                            String indicatorStr = indicatorName + ":" + value;
                            if (StrUtil.isNotBlank(remark)) {
                                indicatorStr += "(" + remark + ")";
                            }
                            indicatorSummaries.add(indicatorStr);
                        }
                    }
                    // ç”¨é€—号连接多个指标
                    indicatorSummaryMap.put(qcId, String.join(", ", indicatorSummaries));
                });
            }
        }
        // æ‹¼è£… VO
        return BeanUtils.toBean(list, MesQcIqcRespVO.class, vo -> {
            findAndThen(vendorMap, vo.getVendorId(),
@@ -153,7 +233,111 @@
            });
            findAndThen(userMap, vo.getInspectorUserId(),
                    user -> vo.setInspectorNickname(user.getNickname()));
            // è®¾ç½®æ£€éªŒé¡¹æŒ‡æ ‡æ±‡æ€»
            vo.setIndicatorResultSummary(indicatorSummaryMap.getOrDefault(vo.getId(), ""));
        });
    }
    /**
     * æž„建导出行:一单一行,检验指标 / åˆ¤å®šä¾æ®å–自行明细并拼接
     */
    private List<MesQcQualityExportVO> buildIqcQualityExportList(List<MesQcIqcDO> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        // æ‰¹é‡æŸ¥è¯¢æ£€æµ‹äººå‘˜
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                convertSet(list, MesQcIqcDO::getInspectorUserId));
        // æ‰¹é‡æŸ¥è¯¢ç‰©æ–™
        Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(
                convertSet(list, MesQcIqcDO::getItemId));
        // æ‰¹é‡æŸ¥è¯¢è¡Œæ˜Žç»†
        List<MesQcIqcLineDO> lines = iqcLineMapper.selectListByIqcIds(convertSet(list, MesQcIqcDO::getId));
        Map<Long, List<MesQcIqcLineDO>> lineMap = lines.stream()
                .collect(Collectors.groupingBy(MesQcIqcLineDO::getIqcId));
        // æ‰¹é‡æŸ¥è¯¢æ£€æµ‹æŒ‡æ ‡
        Map<Long, MesQcIndicatorDO> indicatorMap = CollUtil.isEmpty(lines) ? Collections.emptyMap()
                : indicatorService.getIndicatorMap(convertSet(lines, MesQcIqcLineDO::getIndicatorId));
        // æ‰¹é‡æŸ¥è¯¢å®žæµ‹å€¼ï¼šæ£€éªŒå• ID -> {指标 ID -> å®žæµ‹å€¼}
        Map<Long, Map<Long, String>> measureMap = buildMeasureValueMap(
                convertSet(list, MesQcIqcDO::getId), MesBizTypeConstants.QC_IQC);
        // ç»„装导出行
        List<MesQcQualityExportVO> exportList = new ArrayList<>(list.size());
        for (MesQcIqcDO iqc : list) {
            MesQcQualityExportVO vo = new MesQcQualityExportVO();
            vo.setDocName(iqc.getName());
            MesMdItemDO item = itemMap.get(iqc.getItemId());
            vo.setItemName(item != null ? item.getName() : "");
            vo.setBatchNo(iqc.getVendorBatch());
            vo.setInspectDate(MesQcQualityExportVO.formatDateTime(iqc.getInspectDate()));
            List<String> indicatorNames = new ArrayList<>();
            List<String> judgementSegments = new ArrayList<>();
            for (MesQcIqcLineDO line : lineMap.getOrDefault(iqc.getId(), Collections.emptyList())) {
                MesQcIndicatorDO indicator = indicatorMap.get(line.getIndicatorId());
                String name = indicator != null && StrUtil.isNotBlank(indicator.getName())
                        ? indicator.getName() : "";
                String range = MesQcQualityExportVO.buildRangeText(
                        line.getStandardValue(), line.getMinThreshold(), line.getMaxThreshold());
                if (StrUtil.isNotBlank(name)) {
                    String measuredValue = measureMap
                            .getOrDefault(iqc.getId(), Collections.emptyMap())
                            .get(line.getIndicatorId());
                    indicatorNames.add(StrUtil.isNotBlank(measuredValue) ? name + ":" + measuredValue : name);
                    if (StrUtil.isNotBlank(range)) {
                        judgementSegments.add(name + range);
                    }
                }
            }
            vo.setIndicatorNames(String.join("、", indicatorNames));
            vo.setJudgementBasis(String.join(";", judgementSegments));
            vo.setCheckResultText(getCheckResultLabel(iqc.getCheckResult()));
            findAndThen(userMap, iqc.getInspectorUserId(),
                    user -> vo.setInspectorName(user.getNickname()));
            // æš‚无审核人数据来源,导出空列
            vo.setAuditorName("");
            vo.setRemark(iqc.getRemark());
            exportList.add(vo);
        }
        return exportList;
    }
    /**
     * æž„建 æ£€éªŒå• ID -> {指标 ID -> å®žæµ‹å€¼} æ˜ å°„,实测值取自检验结果明细表
     */
    private Map<Long, Map<Long, String>> buildMeasureValueMap(Set<Long> qcIds, int qcType) {
        Map<Long, Map<Long, String>> measureMap = new HashMap<>();
        if (CollUtil.isEmpty(qcIds)) {
            return measureMap;
        }
        List<MesQcIndicatorResultDO> results = indicatorResultService.getIndicatorResultListByQcIdsAndType(
                new ArrayList<>(qcIds), qcType);
        if (CollUtil.isEmpty(results)) {
            return measureMap;
        }
        Map<Long, Long> resultQcMap = results.stream()
                .collect(Collectors.toMap(MesQcIndicatorResultDO::getId, MesQcIndicatorResultDO::getQcId));
        List<MesQcIndicatorResultDetailDO> details = indicatorResultService.getIndicatorResultDetailListByResultIds(
                new ArrayList<>(resultQcMap.keySet()));
        if (CollUtil.isEmpty(details)) {
            return measureMap;
        }
        for (MesQcIndicatorResultDetailDO detail : details) {
            Long qcId = resultQcMap.get(detail.getResultId());
            if (qcId == null) {
                continue;
            }
            measureMap.computeIfAbsent(qcId, k -> new HashMap<>())
                    .put(detail.getIndicatorId(), detail.getValue());
        }
        return measureMap;
    }
    private String getCheckResultLabel(Integer checkResult) {
        if (checkResult == null) {
            return "";
        }
        return DictFrameworkUtils.parseDictDataLabel(
                DictTypeConstants.MES_QC_CHECK_RESULT, String.valueOf(checkResult));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/iqc/vo/MesQcIqcRespVO.java
@@ -122,6 +122,12 @@
    @DictFormat(DictTypeConstants.MES_QC_CHECK_RESULT)
    private Integer checkResult;
    // ========== æ£€éªŒé¡¹æŒ‡æ ‡ ==========
    @Schema(description = "检验项指标汇总", example = "外观:良好(OK), å°ºå¯¸:100.00mm(OK)")
    @ExcelProperty("检验项指标")
    private String indicatorResultSummary;
    @Schema(description = "来料日期")
    @ExcelProperty("来料日期")
    private LocalDateTime receiveDate;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/iqc/vo/MesQcIqcSaveReqVO.java
@@ -52,6 +52,9 @@
    @NotNull(message = "产品物料不能为空")
    private Long itemId;
    @Schema(description = "质检方案 ID", example = "5")
    private Long templateId;
    // ========== æ•°é‡ ==========
    @Schema(description = "本次接收数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/MesQcOqcController.java
@@ -1,23 +1,35 @@
package cn.iocoder.yudao.module.mes.controller.admin.qc.oqc;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo.MesQcOqcPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo.MesQcOqcRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo.MesQcOqcSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.vo.MesQcQualityExportVO;
import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcLineMapper;
import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService;
import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
import cn.iocoder.yudao.module.mes.service.qc.oqc.MesQcOqcService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
@@ -32,9 +44,14 @@
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -55,6 +72,12 @@
    private MesMdItemService itemService;
    @Resource
    private MesMdUnitMeasureService unitMeasureService;
    @Resource
    private MesQcIndicatorResultService indicatorResultService;
    @Resource
    private MesQcIndicatorService indicatorService;
    @Resource
    private MesQcOqcLineMapper oqcLineMapper;
    @Resource
    private AdminUserApi adminUserApi;
@@ -117,8 +140,8 @@
                               HttpServletResponse response) throws IOException {
        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        List<MesQcOqcDO> list = oqcService.getOqcPage(pageReqVO).getList();
        List<MesQcOqcRespVO> voList = buildOqcRespVOList(list);
        ExcelUtils.write(response, "出货检验单.xls", "数据", MesQcOqcRespVO.class, voList);
        List<MesQcQualityExportVO> exportList = buildOqcQualityExportList(list);
        ExcelUtils.write(response, "出货检验单.xls", "数据", MesQcQualityExportVO.class, exportList);
    }
    // ==================== æ‹¼æŽ¥ VO ====================
@@ -139,6 +162,8 @@
        // æ‰¹é‡æŸ¥è¯¢æ£€æµ‹äººå‘˜
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                convertSet(list, MesQcOqcDO::getInspectorUserId));
        // æ‰¹é‡æŸ¥è¯¢æ£€éªŒé¡¹æŒ‡æ ‡
        Map<Long, String> indicatorSummaryMap = buildIndicatorSummaryMap(list);
        // æ‹¼è£… VO
        return BeanUtils.toBean(list, MesQcOqcRespVO.class, vo -> {
            findAndThen(clientMap, vo.getClientId(),
@@ -150,7 +175,166 @@
            });
            findAndThen(userMap, vo.getInspectorUserId(),
                    user -> vo.setInspectorNickname(user.getNickname()));
            vo.setIndicatorResultSummary(indicatorSummaryMap.getOrDefault(vo.getId(), ""));
        });
    }
    /**
     * æž„建导出行:一单一行,检验指标 / åˆ¤å®šä¾æ®å–自行明细并拼接
     */
    private List<MesQcQualityExportVO> buildOqcQualityExportList(List<MesQcOqcDO> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        // æ‰¹é‡æŸ¥è¯¢æ£€æµ‹äººå‘˜
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                convertSet(list, MesQcOqcDO::getInspectorUserId));
        // æ‰¹é‡æŸ¥è¯¢ç‰©æ–™
        Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(
                convertSet(list, MesQcOqcDO::getItemId));
        // æ‰¹é‡æŸ¥è¯¢è¡Œæ˜Žç»†
        List<MesQcOqcLineDO> lines = oqcLineMapper.selectListByOqcIds(convertSet(list, MesQcOqcDO::getId));
        Map<Long, List<MesQcOqcLineDO>> lineMap = lines.stream()
                .collect(Collectors.groupingBy(MesQcOqcLineDO::getOqcId));
        // æ‰¹é‡æŸ¥è¯¢æ£€æµ‹æŒ‡æ ‡
        Map<Long, MesQcIndicatorDO> indicatorMap = CollUtil.isEmpty(lines) ? Collections.emptyMap()
                : indicatorService.getIndicatorMap(convertSet(lines, MesQcOqcLineDO::getIndicatorId));
        // æ‰¹é‡æŸ¥è¯¢å®žæµ‹å€¼ï¼šæ£€éªŒå• ID -> {指标 ID -> å®žæµ‹å€¼}
        Map<Long, Map<Long, String>> measureMap = buildMeasureValueMap(
                convertSet(list, MesQcOqcDO::getId), MesBizTypeConstants.QC_OQC);
        // ç»„装导出行
        List<MesQcQualityExportVO> exportList = new ArrayList<>(list.size());
        for (MesQcOqcDO oqc : list) {
            MesQcQualityExportVO vo = new MesQcQualityExportVO();
            vo.setDocName(oqc.getName());
            MesMdItemDO item = itemMap.get(oqc.getItemId());
            vo.setItemName(item != null ? item.getName() : "");
            vo.setBatchNo(oqc.getBatchCode());
            vo.setInspectDate(MesQcQualityExportVO.formatDateTime(oqc.getInspectDate()));
            List<String> indicatorNames = new ArrayList<>();
            List<String> judgementSegments = new ArrayList<>();
            for (MesQcOqcLineDO line : lineMap.getOrDefault(oqc.getId(), Collections.emptyList())) {
                MesQcIndicatorDO indicator = indicatorMap.get(line.getIndicatorId());
                String name = indicator != null && StrUtil.isNotBlank(indicator.getName())
                        ? indicator.getName() : "";
                String range = MesQcQualityExportVO.buildRangeText(
                        line.getStandardValue(), line.getMinThreshold(), line.getMaxThreshold());
                if (StrUtil.isNotBlank(name)) {
                    String measuredValue = measureMap
                            .getOrDefault(oqc.getId(), Collections.emptyMap())
                            .get(line.getIndicatorId());
                    indicatorNames.add(StrUtil.isNotBlank(measuredValue) ? name + ":" + measuredValue : name);
                    if (StrUtil.isNotBlank(range)) {
                        judgementSegments.add(name + range);
                    }
                }
            }
            vo.setIndicatorNames(String.join("、", indicatorNames));
            vo.setJudgementBasis(String.join(";", judgementSegments));
            vo.setCheckResultText(getCheckResultLabel(oqc.getCheckResult()));
            findAndThen(userMap, oqc.getInspectorUserId(),
                    user -> vo.setInspectorName(user.getNickname()));
            // æš‚无审核人数据来源,导出空列
            vo.setAuditorName("");
            vo.setRemark(oqc.getRemark());
            exportList.add(vo);
        }
        return exportList;
    }
    /**
     * æž„建 æ£€éªŒå• ID -> {指标 ID -> å®žæµ‹å€¼} æ˜ å°„,实测值取自检验结果明细表
     */
    private Map<Long, Map<Long, String>> buildMeasureValueMap(Set<Long> qcIds, int qcType) {
        Map<Long, Map<Long, String>> measureMap = new HashMap<>();
        if (CollUtil.isEmpty(qcIds)) {
            return measureMap;
        }
        List<MesQcIndicatorResultDO> results = indicatorResultService.getIndicatorResultListByQcIdsAndType(
                new ArrayList<>(qcIds), qcType);
        if (CollUtil.isEmpty(results)) {
            return measureMap;
        }
        Map<Long, Long> resultQcMap = results.stream()
                .collect(Collectors.toMap(MesQcIndicatorResultDO::getId, MesQcIndicatorResultDO::getQcId));
        List<MesQcIndicatorResultDetailDO> details = indicatorResultService.getIndicatorResultDetailListByResultIds(
                new ArrayList<>(resultQcMap.keySet()));
        if (CollUtil.isEmpty(details)) {
            return measureMap;
        }
        for (MesQcIndicatorResultDetailDO detail : details) {
            Long qcId = resultQcMap.get(detail.getResultId());
            if (qcId == null) {
                continue;
            }
            measureMap.computeIfAbsent(qcId, k -> new HashMap<>())
                    .put(detail.getIndicatorId(), detail.getValue());
        }
        return measureMap;
    }
    private String getCheckResultLabel(Integer checkResult) {
        if (checkResult == null) {
            return "";
        }
        return DictFrameworkUtils.parseDictDataLabel(
                DictTypeConstants.MES_QC_CHECK_RESULT, String.valueOf(checkResult));
    }
    /**
     * æž„建每个检验单的检验项指标汇总字符串(qcType = OQC)
     */
    private Map<Long, String> buildIndicatorSummaryMap(List<MesQcOqcDO> list) {
        Map<Long, String> indicatorSummaryMap = new HashMap<>();
        Set<Long> qcIds = convertSet(list, MesQcOqcDO::getId);
        if (CollUtil.isEmpty(qcIds)) {
            return indicatorSummaryMap;
        }
        // æŒ‰ QC ID æ‰¹é‡æŸ¥è¯¢æ£€éªŒç»“æžœ
        List<MesQcIndicatorResultDO> indicatorResults = indicatorResultService.getIndicatorResultListByQcIdsAndType(
                new ArrayList<>(qcIds), MesBizTypeConstants.QC_OQC);
        if (CollUtil.isEmpty(indicatorResults)) {
            return indicatorSummaryMap;
        }
        // æŒ‰ QC ID åˆ†ç»„结果
        Map<Long, List<MesQcIndicatorResultDO>> resultMap = indicatorResults.stream()
                .collect(Collectors.groupingBy(MesQcIndicatorResultDO::getQcId));
        // æ‰¹é‡æŸ¥è¯¢æ£€éªŒç»“果明细
        Set<Long> resultIds = convertSet(indicatorResults, MesQcIndicatorResultDO::getId);
        List<MesQcIndicatorResultDetailDO> indicatorDetails = indicatorResultService.getIndicatorResultDetailListByResultIds(
                new ArrayList<>(resultIds));
        // æŒ‰ç»“æžœ ID åˆ†ç»„明细
        Map<Long, List<MesQcIndicatorResultDetailDO>> detailMapByResultId = indicatorDetails.stream()
                .collect(Collectors.groupingBy(MesQcIndicatorResultDetailDO::getResultId));
        // æ‰¹é‡æŸ¥è¯¢æ£€æµ‹æŒ‡æ ‡
        Set<Long> indicatorIds = convertSet(indicatorDetails, MesQcIndicatorResultDetailDO::getIndicatorId);
        Map<Long, MesQcIndicatorDO> indicatorMap = indicatorService.getIndicatorMap(indicatorIds);
        // æž„建每个 QC çš„æŒ‡æ ‡æ±‡æ€»å­—符串
        resultMap.forEach((qcId, results) -> {
            List<String> indicatorSummaries = new ArrayList<>();
            for (MesQcIndicatorResultDO result : results) {
                List<MesQcIndicatorResultDetailDO> details = detailMapByResultId.getOrDefault(result.getId(), Collections.emptyList());
                for (MesQcIndicatorResultDetailDO detail : details) {
                    // é€šè¿‡ indicatorId å…³è”指标名称,查不到则回退为 "指标"
                    String indicatorName = "指标";
                    MesQcIndicatorDO indicator = indicatorMap.get(detail.getIndicatorId());
                    if (indicator != null && StrUtil.isNotBlank(indicator.getName())) {
                        indicatorName = indicator.getName();
                    }
                    String value = detail.getValue();
                    String remark = detail.getRemark();
                    // æ ¼å¼åŒ–为 "指标名称:值" æˆ– "指标名称:值(备注)"
                    String indicatorStr = indicatorName + ":" + value;
                    if (StrUtil.isNotBlank(remark)) {
                        indicatorStr += "(" + remark + ")";
                    }
                    indicatorSummaries.add(indicatorStr);
                }
            }
            indicatorSummaryMap.put(qcId, String.join(", ", indicatorSummaries));
        });
        return indicatorSummaryMap;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/MesQcOqcPageReqVO.java
@@ -5,6 +5,11 @@
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - MES å‡ºè´§æ£€éªŒå•分页 Request VO")
@Data
@@ -27,4 +32,8 @@
    @Schema(description = "检测结果", example = "1")
    private Integer checkResult;
    @Schema(description = "检测日期")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
    private LocalDateTime[] inspectDate;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/MesQcOqcRespVO.java
@@ -130,6 +130,12 @@
    @DictFormat(DictTypeConstants.MES_QC_CHECK_RESULT)
    private Integer checkResult;
    // ========== æ£€éªŒé¡¹æŒ‡æ ‡ ==========
    @Schema(description = "检验项指标汇总", example = "外观:良好(OK), å°ºå¯¸:100.00mm(OK)")
    @ExcelProperty("检验项指标")
    private String indicatorResultSummary;
    @Schema(description = "出货日期")
    @ExcelProperty("出货日期")
    private LocalDateTime outDate;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/MesQcOqcSaveReqVO.java
@@ -50,6 +50,9 @@
    @NotNull(message = "产品物料不能为空")
    private Long itemId;
    @Schema(description = "质检方案 ID", example = "5")
    private Long templateId;
    // ========== æ•°é‡ ==========
    @Schema(description = "本次出货数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/rqc/MesQcRqcController.java
@@ -1,20 +1,32 @@
package cn.iocoder.yudao.module.mes.controller.admin.qc.rqc;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.mes.controller.admin.qc.rqc.vo.MesQcRqcPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.rqc.vo.MesQcRqcRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.rqc.vo.MesQcRqcSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.vo.MesQcQualityExportVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.rqc.MesQcRqcDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.rqc.MesQcRqcLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.rqc.MesQcRqcLineMapper;
import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService;
import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
import cn.iocoder.yudao.module.mes.service.qc.rqc.MesQcRqcService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
@@ -29,9 +41,14 @@
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -50,6 +67,12 @@
    private MesMdItemService itemService;
    @Resource
    private MesMdUnitMeasureService unitMeasureService;
    @Resource
    private MesQcIndicatorResultService indicatorResultService;
    @Resource
    private MesQcIndicatorService indicatorService;
    @Resource
    private MesQcRqcLineMapper rqcLineMapper;
    @Resource
    private AdminUserApi adminUserApi;
@@ -112,8 +135,8 @@
                               HttpServletResponse response) throws IOException {
        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        List<MesQcRqcDO> list = rqcService.getRqcPage(pageReqVO).getList();
        List<MesQcRqcRespVO> voList = buildRqcRespVOList(list);
        ExcelUtils.write(response, "退货检验单.xls", "数据", MesQcRqcRespVO.class, voList);
        List<MesQcQualityExportVO> exportList = buildRqcQualityExportList(list);
        ExcelUtils.write(response, "退货检验单.xls", "数据", MesQcQualityExportVO.class, exportList);
    }
    // ==================== æ‹¼æŽ¥ VO ====================
@@ -131,6 +154,8 @@
        // æ‰¹é‡æŸ¥è¯¢æ£€æµ‹äººå‘˜
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                convertSet(list, MesQcRqcDO::getInspectorUserId));
        // æ‰¹é‡æŸ¥è¯¢æ£€éªŒé¡¹æŒ‡æ ‡
        Map<Long, String> indicatorSummaryMap = buildIndicatorSummaryMap(list);
        // æ‹¼è£… VO
        return BeanUtils.toBean(list, MesQcRqcRespVO.class, vo -> {
            findAndThen(itemMap, vo.getItemId(), item -> {
@@ -140,7 +165,166 @@
            });
            findAndThen(userMap, vo.getInspectorUserId(),
                    user -> vo.setInspectorNickname(user.getNickname()));
            vo.setIndicatorResultSummary(indicatorSummaryMap.getOrDefault(vo.getId(), ""));
        });
    }
    /**
     * æž„建每个检验单的检验项指标汇总字符串(qcType = RQC)
     */
    private Map<Long, String> buildIndicatorSummaryMap(List<MesQcRqcDO> list) {
        Map<Long, String> indicatorSummaryMap = new HashMap<>();
        Set<Long> qcIds = convertSet(list, MesQcRqcDO::getId);
        if (CollUtil.isEmpty(qcIds)) {
            return indicatorSummaryMap;
        }
        // æŒ‰ QC ID æ‰¹é‡æŸ¥è¯¢æ£€éªŒç»“æžœ
        List<MesQcIndicatorResultDO> indicatorResults = indicatorResultService.getIndicatorResultListByQcIdsAndType(
                new ArrayList<>(qcIds), MesBizTypeConstants.QC_RQC);
        if (CollUtil.isEmpty(indicatorResults)) {
            return indicatorSummaryMap;
        }
        // æŒ‰ QC ID åˆ†ç»„结果
        Map<Long, List<MesQcIndicatorResultDO>> resultMap = indicatorResults.stream()
                .collect(Collectors.groupingBy(MesQcIndicatorResultDO::getQcId));
        // æ‰¹é‡æŸ¥è¯¢æ£€éªŒç»“果明细
        Set<Long> resultIds = convertSet(indicatorResults, MesQcIndicatorResultDO::getId);
        List<MesQcIndicatorResultDetailDO> indicatorDetails = indicatorResultService.getIndicatorResultDetailListByResultIds(
                new ArrayList<>(resultIds));
        // æŒ‰ç»“æžœ ID åˆ†ç»„明细
        Map<Long, List<MesQcIndicatorResultDetailDO>> detailMapByResultId = indicatorDetails.stream()
                .collect(Collectors.groupingBy(MesQcIndicatorResultDetailDO::getResultId));
        // æ‰¹é‡æŸ¥è¯¢æ£€æµ‹æŒ‡æ ‡
        Set<Long> indicatorIds = convertSet(indicatorDetails, MesQcIndicatorResultDetailDO::getIndicatorId);
        Map<Long, MesQcIndicatorDO> indicatorMap = indicatorService.getIndicatorMap(indicatorIds);
        // æž„建每个 QC çš„æŒ‡æ ‡æ±‡æ€»å­—符串
        resultMap.forEach((qcId, results) -> {
            List<String> indicatorSummaries = new ArrayList<>();
            for (MesQcIndicatorResultDO result : results) {
                List<MesQcIndicatorResultDetailDO> details = detailMapByResultId.getOrDefault(result.getId(), Collections.emptyList());
                for (MesQcIndicatorResultDetailDO detail : details) {
                    // é€šè¿‡ indicatorId å…³è”指标名称,查不到则回退为 "指标"
                    String indicatorName = "指标";
                    MesQcIndicatorDO indicator = indicatorMap.get(detail.getIndicatorId());
                    if (indicator != null && StrUtil.isNotBlank(indicator.getName())) {
                        indicatorName = indicator.getName();
                    }
                    String value = detail.getValue();
                    String remark = detail.getRemark();
                    // æ ¼å¼åŒ–为 "指标名称:值" æˆ– "指标名称:值(备注)"
                    String indicatorStr = indicatorName + ":" + value;
                    if (StrUtil.isNotBlank(remark)) {
                        indicatorStr += "(" + remark + ")";
                    }
                    indicatorSummaries.add(indicatorStr);
                }
            }
            indicatorSummaryMap.put(qcId, String.join(", ", indicatorSummaries));
        });
        return indicatorSummaryMap;
    }
    /**
     * æž„建导出行:一单一行,检验指标 / åˆ¤å®šä¾æ®å–自行明细并拼接
     */
    private List<MesQcQualityExportVO> buildRqcQualityExportList(List<MesQcRqcDO> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        // æ‰¹é‡æŸ¥è¯¢æ£€æµ‹äººå‘˜
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                convertSet(list, MesQcRqcDO::getInspectorUserId));
        // æ‰¹é‡æŸ¥è¯¢ç‰©æ–™
        Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(
                convertSet(list, MesQcRqcDO::getItemId));
        // æ‰¹é‡æŸ¥è¯¢è¡Œæ˜Žç»†
        List<MesQcRqcLineDO> lines = rqcLineMapper.selectListByRqcIds(convertSet(list, MesQcRqcDO::getId));
        Map<Long, List<MesQcRqcLineDO>> lineMap = lines.stream()
                .collect(Collectors.groupingBy(MesQcRqcLineDO::getRqcId));
        // æ‰¹é‡æŸ¥è¯¢æ£€æµ‹æŒ‡æ ‡
        Map<Long, MesQcIndicatorDO> indicatorMap = CollUtil.isEmpty(lines) ? Collections.emptyMap()
                : indicatorService.getIndicatorMap(convertSet(lines, MesQcRqcLineDO::getIndicatorId));
        // æ‰¹é‡æŸ¥è¯¢å®žæµ‹å€¼ï¼šæ£€éªŒå• ID -> {指标 ID -> å®žæµ‹å€¼}
        Map<Long, Map<Long, String>> measureMap = buildMeasureValueMap(
                convertSet(list, MesQcRqcDO::getId), MesBizTypeConstants.QC_RQC);
        // ç»„装导出行
        List<MesQcQualityExportVO> exportList = new ArrayList<>(list.size());
        for (MesQcRqcDO rqc : list) {
            MesQcQualityExportVO vo = new MesQcQualityExportVO();
            vo.setDocName(rqc.getName());
            MesMdItemDO item = itemMap.get(rqc.getItemId());
            vo.setItemName(item != null ? item.getName() : "");
            vo.setBatchNo(rqc.getBatchCode());
            vo.setInspectDate(MesQcQualityExportVO.formatDateTime(rqc.getInspectDate()));
            List<String> indicatorNames = new ArrayList<>();
            List<String> judgementSegments = new ArrayList<>();
            for (MesQcRqcLineDO line : lineMap.getOrDefault(rqc.getId(), Collections.emptyList())) {
                MesQcIndicatorDO indicator = indicatorMap.get(line.getIndicatorId());
                String name = indicator != null && StrUtil.isNotBlank(indicator.getName())
                        ? indicator.getName() : "";
                String range = MesQcQualityExportVO.buildRangeText(
                        line.getStandardValue(), line.getMinThreshold(), line.getMaxThreshold());
                if (StrUtil.isNotBlank(name)) {
                    String measuredValue = measureMap
                            .getOrDefault(rqc.getId(), Collections.emptyMap())
                            .get(line.getIndicatorId());
                    indicatorNames.add(StrUtil.isNotBlank(measuredValue) ? name + ":" + measuredValue : name);
                    if (StrUtil.isNotBlank(range)) {
                        judgementSegments.add(name + range);
                    }
                }
            }
            vo.setIndicatorNames(String.join("、", indicatorNames));
            vo.setJudgementBasis(String.join(";", judgementSegments));
            vo.setCheckResultText(getCheckResultLabel(rqc.getCheckResult()));
            findAndThen(userMap, rqc.getInspectorUserId(),
                    user -> vo.setInspectorName(user.getNickname()));
            // æš‚无审核人数据来源,导出空列
            vo.setAuditorName("");
            vo.setRemark(rqc.getRemark());
            exportList.add(vo);
        }
        return exportList;
    }
    /**
     * æž„建 æ£€éªŒå• ID -> {指标 ID -> å®žæµ‹å€¼} æ˜ å°„,实测值取自检验结果明细表
     */
    private Map<Long, Map<Long, String>> buildMeasureValueMap(Set<Long> qcIds, int qcType) {
        Map<Long, Map<Long, String>> measureMap = new HashMap<>();
        if (CollUtil.isEmpty(qcIds)) {
            return measureMap;
        }
        List<MesQcIndicatorResultDO> results = indicatorResultService.getIndicatorResultListByQcIdsAndType(
                new ArrayList<>(qcIds), qcType);
        if (CollUtil.isEmpty(results)) {
            return measureMap;
        }
        Map<Long, Long> resultQcMap = results.stream()
                .collect(Collectors.toMap(MesQcIndicatorResultDO::getId, MesQcIndicatorResultDO::getQcId));
        List<MesQcIndicatorResultDetailDO> details = indicatorResultService.getIndicatorResultDetailListByResultIds(
                new ArrayList<>(resultQcMap.keySet()));
        if (CollUtil.isEmpty(details)) {
            return measureMap;
        }
        for (MesQcIndicatorResultDetailDO detail : details) {
            Long qcId = resultQcMap.get(detail.getResultId());
            if (qcId == null) {
                continue;
            }
            measureMap.computeIfAbsent(qcId, k -> new HashMap<>())
                    .put(detail.getIndicatorId(), detail.getValue());
        }
        return measureMap;
    }
    private String getCheckResultLabel(Integer checkResult) {
        if (checkResult == null) {
            return "";
        }
        return DictFrameworkUtils.parseDictDataLabel(
                DictTypeConstants.MES_QC_CHECK_RESULT, String.valueOf(checkResult));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/rqc/vo/MesQcRqcPageReqVO.java
@@ -41,4 +41,8 @@
    @Schema(description = "检测人员用户 ID", example = "1")
    private Long inspectorUserId;
    @Schema(description = "检测日期")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
    private LocalDateTime[] inspectDate;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/rqc/vo/MesQcRqcRespVO.java
@@ -121,6 +121,12 @@
    @DictFormat(DictTypeConstants.MES_QC_CHECK_RESULT)
    private Integer checkResult;
    // ========== æ£€éªŒé¡¹æŒ‡æ ‡ ==========
    @Schema(description = "检验项指标汇总", example = "外观:良好(OK), å°ºå¯¸:100.00mm(OK)")
    @ExcelProperty("检验项指标")
    private String indicatorResultSummary;
    @Schema(description = "检测日期")
    @ExcelProperty("检测日期")
    private LocalDateTime inspectDate;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/template/MesQcTemplateItemController.java
@@ -1,17 +1,21 @@
package cn.iocoder.yudao.module.mes.controller.admin.qc.template;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
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.qc.template.vo.item.MesQcTemplateItemOptionRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.item.MesQcTemplateItemPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.item.MesQcTemplateItemRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.item.MesQcTemplateItemSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -37,6 +41,8 @@
    @Resource
    private MesQcTemplateItemService templateItemService;
    @Resource
    private MesQcTemplateService templateService;
    @Resource
    private MesMdmItemService mdmItemService;
    @Resource
@@ -84,6 +90,31 @@
        return success(new PageResult<>(buildItemRespVOList(pageResult.getList()), pageResult.getTotal()));
    }
    @GetMapping("/list-by-item-and-type")
    @Operation(summary = "根据物料或工单/任务和检测种类获取可用质检方案")
    @PreAuthorize("@ss.hasPermission('mes:qc-template:query')")
    public CommonResult<List<MesQcTemplateItemOptionRespVO>> getTemplateItemOptionList(
            @RequestParam(value = "qcType", required = false) Integer qcType,
            @RequestParam(value = "itemId", required = false) Long itemId,
            @RequestParam(value = "workOrderId", required = false) Long workOrderId,
            @RequestParam(value = "taskId", required = false) Long taskId) {
        List<MesQcTemplateItemDO> list = templateItemService.getTemplateItemList(
                itemId, qcType, workOrderId, taskId);
        if (CollUtil.isEmpty(list)) {
            return success(Collections.emptyList());
        }
        Map<Long, MesQcTemplateDO> templateMap = templateService.getTemplateMap(
                convertSet(list, MesQcTemplateItemDO::getTemplateId));
        List<MesQcTemplateItemOptionRespVO> result = BeanUtils.toBean(list,
                MesQcTemplateItemOptionRespVO.class, vo -> {
                    MesQcTemplateDO template = templateMap.get(vo.getTemplateId());
                    if (template != null) {
                        vo.setTemplateCode(template.getCode()).setTemplateName(template.getName());
                    }
                });
        return success(result);
    }
    // ==================== æ‹¼æŽ¥ VO ====================
    private List<MesQcTemplateItemRespVO> buildItemRespVOList(List<MesQcTemplateItemDO> list) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/template/vo/item/MesQcTemplateItemOptionRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.item;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - MES è´¨æ£€æ–¹æ¡ˆ-产品关联 é€‰é¡¹ Response VO")
@Data
public class MesQcTemplateItemOptionRespVO {
    @Schema(description = "产品关联编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    private Long id;
    @Schema(description = "质检方案 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long templateId;
    @Schema(description = "质检方案编号", example = "QC_CAR_001")
    private String templateCode;
    @Schema(description = "质检方案名称", example = "汽车整车质检方案")
    private String templateName;
    @Schema(description = "最低检测数", example = "5")
    private Integer quantityCheck;
    @Schema(description = "最大不合格数", example = "0")
    private Integer quantityUnqualified;
    @Schema(description = "最大致命缺陷率(%)", example = "0")
    private BigDecimal criticalRate;
    @Schema(description = "最大严重缺陷率(%)", example = "0")
    private BigDecimal majorRate;
    @Schema(description = "最大轻微缺陷率(%)", example = "100")
    private BigDecimal minorRate;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/vo/MesQcQualityExportVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,92 @@
package cn.iocoder.yudao.module.mes.controller.admin.qc.vo;
import cn.idev.excel.annotation.ExcelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
 * MES è´¨æ£€æ£€éªŒå•导出 Excel VO(IQC/IPQC/OQC/RQC å…±ç”¨ï¼‰
 *
 * <p>导出列:检验单名称 / ç‰©æ–™åç§° / æ‰¹å· / æ£€æµ‹æ—¥æœŸ / æ£€éªŒæŒ‡æ ‡ / åˆ¤å®šä¾æ® / æ£€éªŒç»“æžœ / æ£€éªŒäºº / å®¡æ ¸äºº / å¤‡æ³¨ã€‚
 * æ£€éªŒæŒ‡æ ‡ã€åˆ¤å®šä¾æ®å–自检验单行明细;审核人暂无数据来源,导出为空列。
 *
 * @author è¶…级管理员
 */
@Data
public class MesQcQualityExportVO {
    @ExcelProperty("检验单名称")
    private String docName;
    @ExcelProperty("物料名称")
    private String itemName;
    @ExcelProperty("批号")
    private String batchNo;
    @ExcelProperty("检测日期")
    private String inspectDate;
    @ExcelProperty("检验指标")
    private String indicatorNames;
    @ExcelProperty("判定依据")
    private String judgementBasis;
    @ExcelProperty("检验结果")
    private String checkResultText;
    @ExcelProperty("检验人")
    private String inspectorName;
    @ExcelProperty("审核人")
    private String auditorName;
    @ExcelProperty("备注")
    private String remark;
    /**
     * æ ¼å¼åŒ–检测日期(仅日期),null è¿”回空串
     */
    public static String formatDateTime(LocalDateTime dateTime) {
        if (dateTime == null) {
            return "";
        }
        return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
    }
    /**
     * åŽ»æŽ‰æ•°å€¼å°¾éƒ¨æ— æ„ä¹‰çš„ 0,如 35.000000 -> 35、2.500000 -> 2.5
     */
    public static String formatDecimal(BigDecimal value) {
        if (value == null) {
            return "";
        }
        return value.stripTrailingZeros().toPlainString();
    }
    /**
     * ç»„装单个检测项的判定依据文本:
     * ä¸Šä¸‹é™å‡æœ‰ -> "下限~上限";仅有上限 -> "≤上限";仅有下限 -> "≥下限";
     * å‡æ— ä½†ç»™äº†æ ‡å‡†å€¼ -> "标准值 æ ‡å‡†å€¼";否则返回空串。
     */
    public static String buildRangeText(BigDecimal standardValue, BigDecimal minThreshold, BigDecimal maxThreshold) {
        if (maxThreshold != null && minThreshold != null) {
            return formatDecimal(minThreshold) + "~" + formatDecimal(maxThreshold);
        }
        if (maxThreshold != null) {
            return "≤" + formatDecimal(maxThreshold);
        }
        if (minThreshold != null) {
            return "≥" + formatDecimal(minThreshold);
        }
        if (standardValue != null) {
            return "标准值 " + formatDecimal(standardValue);
        }
        return "";
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/indicatorresult/MesQcIndicatorResultDetailMapper.java
@@ -19,6 +19,10 @@
        return selectList(MesQcIndicatorResultDetailDO::getResultId, resultId);
    }
    default List<MesQcIndicatorResultDetailDO> selectListByResultIds(List<Long> resultIds) {
        return selectList(MesQcIndicatorResultDetailDO::getResultId, resultIds);
    }
    default void deleteByResultId(Long resultId) {
        delete(new LambdaQueryWrapperX<MesQcIndicatorResultDetailDO>()
                .eq(MesQcIndicatorResultDetailDO::getResultId, resultId));
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/indicatorresult/MesQcIndicatorResultMapper.java
@@ -38,4 +38,10 @@
                .eq(MesQcIndicatorResultDO::getQcType, qcType));
    }
    default List<MesQcIndicatorResultDO> selectListByQcIdsAndType(List<Long> qcIds, Integer qcType) {
        return selectList(new LambdaQueryWrapperX<MesQcIndicatorResultDO>()
                .in(MesQcIndicatorResultDO::getQcId, qcIds)
                .eq(MesQcIndicatorResultDO::getQcType, qcType));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/ipqc/MesQcIpqcLineMapper.java
@@ -7,6 +7,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcLineDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
@@ -27,6 +28,12 @@
        return selectList(MesQcIpqcLineDO::getIpqcId, ipqcId);
    }
    default List<MesQcIpqcLineDO> selectListByIpqcIds(Collection<Long> ipqcIds) {
        return selectList(new LambdaQueryWrapperX<MesQcIpqcLineDO>()
                .in(MesQcIpqcLineDO::getIpqcId, ipqcIds)
                .orderByAsc(MesQcIpqcLineDO::getId));
    }
    default void deleteByIpqcId(Long ipqcId) {
        delete(new LambdaQueryWrapperX<MesQcIpqcLineDO>()
                .eq(MesQcIpqcLineDO::getIpqcId, ipqcId));
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/ipqc/MesQcIpqcMapper.java
@@ -26,6 +26,7 @@
                .eqIfPresent(MesQcIpqcDO::getCheckResult, reqVO.getCheckResult())
                .eqIfPresent(MesQcIpqcDO::getStatus, reqVO.getStatus())
                .eqIfPresent(MesQcIpqcDO::getInspectorUserId, reqVO.getInspectorUserId())
                .betweenIfPresent(MesQcIpqcDO::getInspectDate, reqVO.getInspectDate())
                .orderByDesc(MesQcIpqcDO::getId));
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/iqc/MesQcIqcLineMapper.java
@@ -7,6 +7,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcLineDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
@@ -27,6 +28,12 @@
        return selectList(MesQcIqcLineDO::getIqcId, iqcId);
    }
    default List<MesQcIqcLineDO> selectListByIqcIds(Collection<Long> iqcIds) {
        return selectList(new LambdaQueryWrapperX<MesQcIqcLineDO>()
                .in(MesQcIqcLineDO::getIqcId, iqcIds)
                .orderByAsc(MesQcIqcLineDO::getId));
    }
    default void deleteByIqcId(Long iqcId) {
        delete(new LambdaQueryWrapperX<MesQcIqcLineDO>()
                .eq(MesQcIqcLineDO::getIqcId, iqcId));
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/oqc/MesQcOqcLineMapper.java
@@ -7,6 +7,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcLineDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
@@ -27,6 +28,12 @@
        return selectList(MesQcOqcLineDO::getOqcId, oqcId);
    }
    default List<MesQcOqcLineDO> selectListByOqcIds(Collection<Long> oqcIds) {
        return selectList(new LambdaQueryWrapperX<MesQcOqcLineDO>()
                .in(MesQcOqcLineDO::getOqcId, oqcIds)
                .orderByAsc(MesQcOqcLineDO::getId));
    }
    default void deleteByOqcId(Long oqcId) {
        delete(new LambdaQueryWrapperX<MesQcOqcLineDO>()
                .eq(MesQcOqcLineDO::getOqcId, oqcId));
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/oqc/MesQcOqcMapper.java
@@ -25,6 +25,7 @@
                .likeIfPresent(MesQcOqcDO::getBatchCode, reqVO.getBatchCode())
                .eqIfPresent(MesQcOqcDO::getItemId, reqVO.getItemId())
                .eqIfPresent(MesQcOqcDO::getCheckResult, reqVO.getCheckResult())
                .betweenIfPresent(MesQcOqcDO::getInspectDate, reqVO.getInspectDate())
                .orderByDesc(MesQcOqcDO::getId));
    }
@@ -38,6 +39,7 @@
                .likeIfPresent(MesQcOqcDO::getBatchCode, reqVO.getBatchCode())
                .eqIfPresent(MesQcOqcDO::getItemId, reqVO.getItemId())
                .eqIfPresent(MesQcOqcDO::getCheckResult, reqVO.getCheckResult())
                .betweenIfPresent(MesQcOqcDO::getInspectDate, reqVO.getInspectDate())
                .orderByDesc(MesQcOqcDO::getId);
        if (permittedClientIds != null) {
            if (CollUtil.isEmpty(permittedClientIds)) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/rqc/MesQcRqcLineMapper.java
@@ -7,6 +7,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.rqc.MesQcRqcLineDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
@@ -27,6 +28,12 @@
        return selectList(MesQcRqcLineDO::getRqcId, rqcId);
    }
    default List<MesQcRqcLineDO> selectListByRqcIds(Collection<Long> rqcIds) {
        return selectList(new LambdaQueryWrapperX<MesQcRqcLineDO>()
                .in(MesQcRqcLineDO::getRqcId, rqcIds)
                .orderByAsc(MesQcRqcLineDO::getId));
    }
    default void deleteByRqcId(Long rqcId) {
        delete(new LambdaQueryWrapperX<MesQcRqcLineDO>()
                .eq(MesQcRqcLineDO::getRqcId, rqcId));
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/rqc/MesQcRqcMapper.java
@@ -25,6 +25,7 @@
                .eqIfPresent(MesQcRqcDO::getCheckResult, reqVO.getCheckResult())
                .eqIfPresent(MesQcRqcDO::getStatus, reqVO.getStatus())
                .eqIfPresent(MesQcRqcDO::getInspectorUserId, reqVO.getInspectorUserId())
                .betweenIfPresent(MesQcRqcDO::getInspectDate, reqVO.getInspectDate())
                .orderByDesc(MesQcRqcDO::getId));
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/indicatorresult/MesQcIndicatorResultDetailService.java
@@ -34,6 +34,14 @@
    List<MesQcIndicatorResultDetailDO> getDetailListByResultId(Long resultId);
    /**
     * æ ¹æ®æ£€éªŒç»“æžœ ID åˆ—表批量获取明细列表
     *
     * @param resultIds æ£€éªŒç»“æžœ ID åˆ—表
     * @return æ˜Žç»†åˆ—表
     */
    List<MesQcIndicatorResultDetailDO> getDetailListByResultIds(List<Long> resultIds);
    /**
     * æ ¹æ®æ£€éªŒç»“æžœ ID åˆ é™¤æ‰€æœ‰æ˜Žç»†
     *
     * @param resultId æ£€éªŒç»“æžœ ID
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/indicatorresult/MesQcIndicatorResultDetailServiceImpl.java
@@ -37,6 +37,11 @@
    }
    @Override
    public List<MesQcIndicatorResultDetailDO> getDetailListByResultIds(List<Long> resultIds) {
        return detailMapper.selectListByResultIds(resultIds);
    }
    @Override
    public void deleteDetailByResultId(Long resultId) {
        detailMapper.delete(new LambdaQueryWrapperX<MesQcIndicatorResultDetailDO>()
                .eq(MesQcIndicatorResultDetailDO::getResultId, resultId));
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/indicatorresult/MesQcIndicatorResultService.java
@@ -63,6 +63,15 @@
    List<MesQcIndicatorResultDetailDO> getIndicatorResultDetailListByResultId(Long resultId);
    /**
     * èŽ·å–æŒ‡å®šè´¨æ£€å•ä¸‹çš„æ£€éªŒç»“æžœåˆ—è¡¨
     *
     * @param qcId   è´¨æ£€å• ID
     * @param qcType è´¨æ£€ç±»åž‹
     * @return ç»“果列表
     */
    List<MesQcIndicatorResultDO> getIndicatorResultListByQcIdAndType(Long qcId, Integer qcType);
    /**
     * èŽ·å–æŒ‡å®šè´¨æ£€å•ä¸‹çš„æ£€éªŒç»“æžœæ•°é‡
     *
     * @param qcId   è´¨æ£€å• ID
@@ -72,6 +81,23 @@
    Long getIndicatorResultCountByQcIdAndType(Long qcId, Integer qcType);
    /**
     * æ ¹æ®è´¨æ£€å•ID列表和类型批量获取检验结果
     *
     * @param qcIds  è´¨æ£€å• ID åˆ—表
     * @param qcType è´¨æ£€ç±»åž‹
     * @return æ£€éªŒç»“果列表
     */
    List<MesQcIndicatorResultDO> getIndicatorResultListByQcIdsAndType(List<Long> qcIds, Integer qcType);
    /**
     * æ ¹æ®ç»“æžœID列表批量获取检验结果明细
     *
     * @param resultIds ç»“æžœID列表
     * @return æ£€éªŒç»“果明细列表
     */
    List<MesQcIndicatorResultDetailDO> getIndicatorResultDetailListByResultIds(List<Long> resultIds);
    /**
     * æ ¡éªŒæŒ‡å®šè´¨æ£€å•下至少存在一条检验结果
     *
     * @param qcId   è´¨æ£€å• ID
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/indicatorresult/MesQcIndicatorResultServiceImpl.java
@@ -137,6 +137,34 @@
        return resultMapper.selectCountByQcIdAndType(qcId, qcType);
    }
    /**
     * èŽ·å–æŒ‡å®šè´¨æ£€å•ä¸‹çš„æ£€éªŒç»“æžœåˆ—è¡¨
     *
     * @param qcId   è´¨æ£€å• ID
     * @param qcType è´¨æ£€ç±»åž‹
     * @return ç»“果列表
     */
    @Override
    public List<MesQcIndicatorResultDO> getIndicatorResultListByQcIdAndType(Long qcId, Integer qcType) {
        return resultMapper.selectListByQcIdAndType(qcId, qcType);
    }
    @Override
    public List<MesQcIndicatorResultDO> getIndicatorResultListByQcIdsAndType(List<Long> qcIds, Integer qcType) {
        if (CollUtil.isEmpty(qcIds)) {
            return Collections.emptyList();
        }
        return resultMapper.selectListByQcIdsAndType(qcIds, qcType);
    }
    @Override
    public List<MesQcIndicatorResultDetailDO> getIndicatorResultDetailListByResultIds(List<Long> resultIds) {
        if (CollUtil.isEmpty(resultIds)) {
            return Collections.emptyList();
        }
        return resultDetailService.getDetailListByResultIds(resultIds);
    }
    @Override
    public void validateIndicatorResultExistsByQcIdAndType(Long qcId, Integer qcType) {
        Long resultCount = getIndicatorResultCountByQcIdAndType(qcId, qcType);
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImpl.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.mes.service.qc.ipqc;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -123,9 +124,21 @@
        MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderExists(createReqVO.getWorkOrderId());
        // 1.3 ç¡®å®šæ£€éªŒç‰©æ–™ï¼šæœ‰ä»»åŠ¡æ—¶å–ä»»åŠ¡çš„ç‰©æ–™ï¼Œæ— ä»»åŠ¡æ—¶å–å·¥å•çš„äº§å“
        Long itemId = (ctx.task() != null) ? ctx.task().getItemId() : workOrder.getProductId();
        // 1.4 æ ¹æ®ç‰©æ–™ + æ£€éªŒç±»åž‹è‡ªåŠ¨åŒ¹é…æ¨¡æ¿
        MesQcTemplateItemDO templateItem = templateItemService.getRequiredTemplateByItemIdAndType(
                itemId, MesQcTypeEnum.IPQC.getType());
        // 1.4 æ ¹æ®ç‰©æ–™ + IPQC ç±»åž‹æŸ¥æ‰¾å¯ç”¨è´¨æ£€æ–¹æ¡ˆï¼ˆæ”¯æŒå‰ç«¯æ˜¾å¼æŒ‡å®šï¼‰
        List<MesQcTemplateItemDO> templateItems = templateItemService.getTemplateItemList(
                itemId, MesQcTypeEnum.IPQC.getType(), null, null);
        if (CollUtil.isEmpty(templateItems)) {
            throw exception(QC_NO_TEMPLATE);
        }
        MesQcTemplateItemDO templateItem = templateItems.get(0);
        if (createReqVO.getTemplateId() != null) {
            templateItem = templateItems.stream()
                    .filter(available -> Objects.equals(available.getTemplateId(), createReqVO.getTemplateId()))
                    .findFirst().orElse(null);
            if (templateItem == null) {
                throw exception(QC_NO_TEMPLATE); // æŒ‡å®šäº†æ–¹æ¡ˆä½†ä¸åœ¨åŒ¹é…åˆ—表中,拒绝
            }
        }
        Long templateId = templateItem.getTemplateId();
        // 1.5 èŽ·å–æ¥æºå•æ®ç¼–å·
        String sourceDocCode = validateAndGetSourceDocCode(
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java
@@ -91,9 +91,21 @@
        // 1.2 æ ¡éªŒæ¥æºå•据参数,并验证数据存在
        String sourceDocCode = validateAndGetSourceDocCode(
                createReqVO.getSourceDocType(), createReqVO.getSourceDocId(), createReqVO.getSourceLineId());
        // 1.3 é€šè¿‡ itemId + IQC ç±»åž‹è‡ªåŠ¨æŸ¥æ‰¾æ¨¡æ¿
        MesQcTemplateItemDO templateItem = templateItemService.getRequiredTemplateByItemIdAndType(
                createReqVO.getItemId(), MesQcTypeEnum.IQC.getType());
        // 1.3 æ ¹æ®äº§å“ + IQC ç±»åž‹æŸ¥æ‰¾å¯ç”¨è´¨æ£€æ–¹æ¡ˆï¼ˆæ”¯æŒå‰ç«¯æ˜¾å¼æŒ‡å®šï¼‰
        List<MesQcTemplateItemDO> templateItems = templateItemService.getTemplateItemList(
                createReqVO.getItemId(), MesQcTypeEnum.IQC.getType(), null, null);
        if (CollUtil.isEmpty(templateItems)) {
            throw exception(QC_NO_TEMPLATE);
        }
        MesQcTemplateItemDO templateItem = templateItems.get(0);
        if (createReqVO.getTemplateId() != null) {
            templateItem = templateItems.stream()
                    .filter(item -> Objects.equals(item.getTemplateId(), createReqVO.getTemplateId()))
                    .findFirst().orElse(null);
            if (templateItem == null) {
                throw exception(QC_NO_TEMPLATE); // æŒ‡å®šäº†æ–¹æ¡ˆä½†ä¸åœ¨åŒ¹é…åˆ—表中,拒绝
            }
        }
        Long templateId = templateItem.getTemplateId();
        // 2. æ’入主表
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.mes.service.qc.oqc;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -88,9 +89,21 @@
        // 1.2 æ ¡éªŒæ¥æºå•据参数,并验证数据存在
        String sourceDocCode = validateAndGetSourceDocCode(
                createReqVO.getSourceDocType(), createReqVO.getSourceLineId());
        // 1.3 æ ¹æ®äº§å“ + æ£€éªŒç±»åž‹è‡ªåŠ¨åŒ¹é…æ¨¡æ¿
        MesQcTemplateItemDO templateItem = templateItemService.getRequiredTemplateByItemIdAndType(
                createReqVO.getItemId(), MesQcTypeEnum.OQC.getType());
        // 1.3 æ ¹æ®äº§å“ + OQC ç±»åž‹æŸ¥æ‰¾å¯ç”¨è´¨æ£€æ–¹æ¡ˆï¼ˆæ”¯æŒå‰ç«¯æ˜¾å¼æŒ‡å®šï¼‰
        List<MesQcTemplateItemDO> templateItems = templateItemService.getTemplateItemList(
                createReqVO.getItemId(), MesQcTypeEnum.OQC.getType(), null, null);
        if (CollUtil.isEmpty(templateItems)) {
            throw exception(QC_NO_TEMPLATE);
        }
        MesQcTemplateItemDO templateItem = templateItems.get(0);
        if (createReqVO.getTemplateId() != null) {
            templateItem = templateItems.stream()
                    .filter(available -> Objects.equals(available.getTemplateId(), createReqVO.getTemplateId()))
                    .findFirst().orElse(null);
            if (templateItem == null) {
                throw exception(QC_NO_TEMPLATE); // æŒ‡å®šäº†æ–¹æ¡ˆä½†ä¸åœ¨åŒ¹é…åˆ—表中,拒绝
            }
        }
        Long templateId = templateItem.getTemplateId();
        // 2. æ’入主表
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcServiceImpl.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.mes.service.qc.rqc;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -89,9 +90,21 @@
    public Long createRqc(MesQcRqcSaveReqVO createReqVO) {
        // 1.1 æ ¡éªŒæ•°æ®
        validateRqcSaveData(createReqVO);
        // 1.2 æ ¹æ®ç‰©æ–™ + æ£€éªŒç±»åž‹è‡ªåŠ¨åŒ¹é…æ¨¡æ¿
        MesQcTemplateItemDO templateItem = templateItemService.getRequiredTemplateByItemIdAndType(
                createReqVO.getItemId(), createReqVO.getType());
        // 1.2 æ ¹æ®ç‰©æ–™ + RQC æ£€æµ‹ç§ç±»æŸ¥æ‰¾å¯ç”¨è´¨æ£€æ–¹æ¡ˆï¼ˆæ”¯æŒå‰ç«¯æ˜¾å¼æŒ‡å®šï¼‰
        List<MesQcTemplateItemDO> templateItems = templateItemService.getTemplateItemList(
                createReqVO.getItemId(), MesQcTypeEnum.RQC.getType(), null, null);
        if (CollUtil.isEmpty(templateItems)) {
            throw exception(QC_NO_TEMPLATE);
        }
        MesQcTemplateItemDO templateItem = templateItems.get(0);
        if (createReqVO.getTemplateId() != null) {
            templateItem = templateItems.stream()
                    .filter(available -> Objects.equals(available.getTemplateId(), createReqVO.getTemplateId()))
                    .findFirst().orElse(null);
            if (templateItem == null) {
                throw exception(QC_NO_TEMPLATE); // æŒ‡å®šäº†æ–¹æ¡ˆä½†ä¸åœ¨åŒ¹é…åˆ—表中,拒绝
            }
        }
        Long templateId = templateItem.getTemplateId();
        // 1.3 èŽ·å–æ¥æºå•æ®ç¼–å·
        String sourceDocCode = validateAndGetSourceDocCode(
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateItemService.java
@@ -7,6 +7,8 @@
import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
import jakarta.validation.Valid;
import java.util.List;
/**
 * MES è´¨æ£€æ–¹æ¡ˆ-产品关联 Service æŽ¥å£
 *
@@ -68,4 +70,15 @@
     */
    MesQcTemplateItemDO getRequiredTemplateByItemIdAndType(Long itemId, Integer qcType);
    /**
     * æ ¹æ®äº§å“ç‰©æ–™ï¼ˆæˆ–工单/任务)和 QC ç±»åž‹ï¼ŒèŽ·å–æ‰€æœ‰å¯ç”¨çš„è´¨æ£€æ–¹æ¡ˆäº§å“å…³è”åˆ—è¡¨
     *
     * @param itemId      äº§å“ç‰©æ–™ ID(mes_md_item.id);与 workOrderId/taskId è‡³å°‘其一
     * @param qcType      æ£€æµ‹ç§ç±»ï¼ˆæžšä¸¾ {@link MesQcTypeEnum})
     * @param workOrderId ç”Ÿäº§å·¥å• ID(可选,IPQC åœºæ™¯ç”¨äºŽè§£æžäº§å“ç‰©æ–™ï¼‰
     * @param taskId      ç”Ÿäº§ä»»åŠ¡ ID(可选,优先用于解析产品物料)
     * @return åŒ¹é…çš„产品关联列表(保持原顺序),无匹配返回空列表
     */
    List<MesQcTemplateItemDO> getTemplateItemList(Long itemId, Integer qcType, Long workOrderId, Long taskId);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateItemServiceImpl.java
@@ -12,13 +12,18 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.template.MesQcTemplateItemMapper;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@@ -42,6 +47,12 @@
    private MesQcTemplateService templateService;
    @Resource
    private MesMdItemService mesMdItemService;
    @Resource
    @Lazy
    private MesProTaskService proTaskService;
    @Resource
    @Lazy
    private MesProWorkOrderService proWorkOrderService;
    @Override
    public Long createTemplateItem(MesQcTemplateItemSaveReqVO createReqVO) {
@@ -116,23 +127,46 @@
    @Override
    public MesQcTemplateItemDO getRequiredTemplateByItemIdAndType(Long itemId, Integer qcType) {
        // 1. æŸ¥å‡º itemId å…³è”的所有 templateItem
        MesMdItemDO item1 = mesMdItemService.getItem(itemId);
        List<MesQcTemplateItemDO> templateItems = templateItemMapper.selectListByItemId(item1.getMdmItemId());
        List<MesQcTemplateItemDO> templateItems = getTemplateItemList(itemId, qcType, null, null);
        if (CollUtil.isEmpty(templateItems)) {
            throw exception(QC_NO_TEMPLATE);
        }
        // 2. ç­›é€‰ types åŒ…含 qcType ä¸”状态为开启的模板
        return templateItems.get(0);
    }
    @Override
    public List<MesQcTemplateItemDO> getTemplateItemList(Long itemId, Integer qcType, Long workOrderId, Long taskId) {
        // 1. è§£æžçœŸå®žäº§å“ç‰©æ–™ ID:任务优先,其次工单,最后直接用入参
        Long resolvedItemId = itemId;
        if (taskId != null) {
            resolvedItemId = proTaskService.validateTaskExists(taskId).getItemId();
        } else if (workOrderId != null) {
            resolvedItemId = proWorkOrderService.validateWorkOrderExists(workOrderId).getProductId();
        }
        if (resolvedItemId == null) {
            return Collections.emptyList();
        }
        // 2. mes_md_item.id è½¬ mdm_item.id(产品关联表存的是 mdm ç‰©æ–™ï¼‰
        MesMdItemDO item = mesMdItemService.getItem(resolvedItemId);
        if (item == null || item.getMdmItemId() == null) {
            return Collections.emptyList();
        }
        // 3. æŸ¥å‡ºè¯¥ç‰©æ–™å…³è”的所有产品关联
        List<MesQcTemplateItemDO> templateItems = templateItemMapper.selectListByItemId(item.getMdmItemId());
        if (CollUtil.isEmpty(templateItems)) {
            return Collections.emptyList();
        }
        // 4. è¿‡æ»¤ï¼šæ–¹æ¡ˆå¯ç”¨ä¸”检测种类包含 qcType,返回全部匹配项
        List<MesQcTemplateDO> templates = templateService.getTemplateList(
                convertSet(templateItems, MesQcTemplateItemDO::getTemplateId));
        MesQcTemplateDO matchedTemplate = findFirst(templates,
                t -> CommonStatusEnum.isEnable(t.getStatus()) && CollUtil.contains(t.getTypes(), qcType));
        if (matchedTemplate == null) {
            throw exception(QC_NO_TEMPLATE);
        }
        // 3. è¿”回对应的 templateItem
        return findFirst(templateItems,
                item -> Objects.equals(item.getTemplateId(), matchedTemplate.getId()));
        Set<Long> matchedTemplateIds = templates.stream()
                .filter(t -> CommonStatusEnum.isEnable(t.getStatus())
                        && CollUtil.contains(t.getTypes(), qcType))
                .map(MesQcTemplateDO::getId)
                .collect(Collectors.toSet());
        return templateItems.stream()
                .filter(t -> matchedTemplateIds.contains(t.getTemplateId()))
                .collect(Collectors.toList());
    }
}