yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java
@@ -3,6 +3,7 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -15,6 +16,7 @@
import cn.iocoder.yudao.module.mes.service.qc.imports.MesQcImportHelper;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO;
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.dal.mysql.qc.oqc.MesQcOqcMapper;
import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum;
@@ -24,6 +26,7 @@
import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateService;
import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesLineService;
import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrSaveReqVO;
import cn.iocoder.yudao.module.mes.service.qc.ncr.MesQcNcrService;
@@ -33,6 +36,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO;
import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
@@ -66,6 +70,8 @@
    @Resource
    private MesQcTemplateItemService templateItemService;
    @Resource
    private MesQcTemplateService templateService;
    @Resource
    private MesQcOqcLineService oqcLineService;
    @Resource
@@ -106,19 +112,28 @@
        // 1.3 根据产品 + OQC 类型查找可用质检方案(支持前端显式指定)
        List<MesQcTemplateItemDO> templateItems = templateItemService.getTemplateItemList(
                createReqVO.getItemId(), MesQcTypeEnum.OQC.getType(), null, null);
        Long templateId;
        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); // 指定了方案但不在匹配列表中,拒绝
            // 产品物料未绑定出货检验方案:显式指定的方案改为校验方案本身(存在、已启用、检测种类含出货检验)
            if (createReqVO.getTemplateId() == null) {
                throw new ServiceException(QC_NO_TEMPLATE.getCode(), buildNoTemplateMessage(createReqVO.getItemId()));
            }
            templateId = validateTemplateUsable(createReqVO.getTemplateId());
        } else {
            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 new ServiceException(QC_NO_TEMPLATE.getCode(), StrUtil.format(
                            "所选质检方案不在产品物料[{}]已绑定的出货检验方案范围内:请重新选择质检方案,"
                                    + "或先在【质量管理-质检方案】中为该物料绑定所选方案后重试。",
                            describeItem(createReqVO.getItemId())));
                }
            }
            templateId = templateItem.getTemplateId();
        }
        Long templateId = templateItem.getTemplateId();
        // 2. 插入主表
        MesQcOqcDO oqc = BeanUtils.toBean(createReqVO, MesQcOqcDO.class)
@@ -145,6 +160,43 @@
        updateObj.setTemplateId(null); // 不允许修改模板
        updateObj.setItemId(null); // 不允许修改物料
        oqcMapper.updateById(updateObj);
    }
    /**
     * 校验质检方案本身可用:存在、已启用、检测种类包含出货检验,返回方案编号
     */
    private Long validateTemplateUsable(Long templateId) {
        MesQcTemplateDO template = templateService.getTemplate(templateId);
        if (template == null) {
            throw new ServiceException(QC_TEMPLATE_NOT_EXISTS.getCode(), StrUtil.format(
                    "所选质检方案不存在(ID={}),可能已被删除,请刷新后重新选择质检方案", templateId));
        }
        if (!CommonStatusEnum.isEnable(template.getStatus())) {
            throw exception(QC_OQC_TEMPLATE_IS_DISABLE, template.getName());
        }
        if (!CollUtil.contains(template.getTypes(), MesQcTypeEnum.OQC.getType())) {
            throw exception(QC_OQC_TEMPLATE_TYPE_NOT_MATCH, template.getName());
        }
        return template.getId();
    }
    /**
     * 构建「无可用质检方案」的提示:给出物料名称与下一步动作
     */
    private String buildNoTemplateMessage(Long itemId) {
        return StrUtil.format("产品物料[{}]未绑定「出货检验」质检方案,且未选择质检方案:请先选择质检方案后重试,"
                + "或在【质量管理-质检方案】中为该物料绑定检测种类包含出货检验的方案。", describeItem(itemId));
    }
    /**
     * 描述物料,用于异常提示:优先给出「编码 名称」,查不到时回退为物料编号
     */
    private String describeItem(Long itemId) {
        if (itemId == null) {
            return "未选择";
        }
        MdmItemRespDTO item = mdmItemApi.getItem(itemId).getCheckedData();
        return item != null ? item.getCode() + " " + item.getName() : "物料编号 ID=" + itemId;
    }
    private void validateOqcSaveData(MesQcOqcSaveReqVO reqVO) {
@@ -476,7 +528,7 @@
        reqVO.setName(row.getName().trim());
        reqVO.setItemId(qcImportHelper.resolveItemId(row.getItemCode()));
        Long clientId = qcImportHelper.resolveClientId(row.getClientName());
        qcImportHelper.validateClientPermission(clientId, row.getClientName().trim());
        qcImportHelper.validateClientPermission(clientId, StrUtil.trimToNull(row.getClientName()));
        reqVO.setClientId(clientId);
        reqVO.setBatchCode(StrUtil.trimToNull(row.getBatchCode()));
        reqVO.setTemplateId(qcImportHelper.resolveTemplateId(row.getTemplateName()));