云
2026-09-22 de0202274ed147e89fdbba0a953936a3b9997ec7
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImplTest.java
@@ -2,13 +2,9 @@
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
@@ -16,15 +12,10 @@
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
import cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackService;
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService;
import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
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.ncr.MesQcNcrService;
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@@ -34,6 +25,7 @@
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import java.math.BigDecimal;
import java.util.List;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
@@ -60,11 +52,7 @@
    @MockitoBean
    private MesQcIpqcLineService ipqcLineService;
    @MockitoBean
    private MesProWorkOrderService workOrderService;
    @MockitoBean
    private MesMdWorkstationService workstationService;
    @MockitoBean
    private MesMdItemService itemService;
    private MdmItemApi mdmItemApi;
    @MockitoBean
    private MesQcDefectRecordService defectRecordService;
    @MockitoBean
@@ -72,15 +60,11 @@
    @MockitoBean
    private MesProFeedbackService feedbackService;
    @MockitoBean
    private MesProRouteProductService routeProductService;
    @MockitoBean
    private MesProRouteProcessService routeProcessService;
    @MockitoBean
    private MesProTaskService taskService;
    @MockitoBean
    private MesQcIndicatorResultService indicatorResultService;
    @MockitoBean
    private MesWmProductProduceLineService productProduceLineService;
    @MockitoBean
    private MesQcNcrService ncrService;
    @Test
    public void testFinishIpqc_writeBack_feedback() {
@@ -185,34 +169,46 @@
    @Test
    public void testFinishIpqc_writeBack_sourceDocTypeDirtyData() {
        // 准备数据:sourceDocType 非空但 sourceDocId 为空,应该抛异常
        // 准备数据:sourceDocType 非空但 sourceDocId 为空,跳过回写
        MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> {
            o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
            o.setCheckResult(1);
            o.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
            o.setSourceDocId(null); // 脏数据
            o.setSourceLineId(null);
            o.setUnqualifiedQuantity(BigDecimal.ZERO);
        });
        ipqcMapper.insert(ipqc);
        // 调用,并断言异常
        assertThrows(IllegalArgumentException.class, () -> ipqcService.finishIpqc(ipqc.getId()));
        // 调用:不抛异常,仅跳过回写
        ipqcService.finishIpqc(ipqc.getId());
        MesQcIpqcDO updatedIpqc = ipqcMapper.selectById(ipqc.getId());
        assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedIpqc.getStatus());
        verify(feedbackService, never()).updateProFeedbackWhenIpqcFinish(
                anyLong(), anyLong(), any(), any(), any(), any(), any());
    }
    @Test
    public void testFinishIpqc_writeBack_unknownSourceDocType() {
        // 准备数据:sourceDocType 无法识别
        // 准备数据:sourceDocType 无法识别,回写阶段不校验类型(仅按 sourceDocId 回写报工)
        MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> {
            o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
            o.setCheckResult(1);
            o.setSourceDocType(99999); // 无法识别的类型
            o.setSourceDocId(randomLongId());
            o.setSourceLineId(null);
            o.setSourceLineId(randomLongId());
            o.setUnqualifiedQuantity(BigDecimal.ZERO);
        });
        ipqcMapper.insert(ipqc);
        // 调用,并断言异常
        assertThrows(IllegalArgumentException.class, () -> ipqcService.finishIpqc(ipqc.getId()));
        // 调用:按 sourceDocId 回写报工
        ipqcService.finishIpqc(ipqc.getId());
        MesQcIpqcDO updatedIpqc = ipqcMapper.selectById(ipqc.getId());
        assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedIpqc.getStatus());
        verify(feedbackService).updateProFeedbackWhenIpqcFinish(
                anyLong(), anyLong(), any(), any(), any(), any(), any());
    }
    @Test
@@ -242,159 +238,82 @@
        assertThrows(Exception.class, () -> ipqcService.finishIpqc(ipqc.getId()));
    }
    // ==================== processId 推导行为测试 ====================
    // ==================== 批号必填校验 ====================
    /**
     * 工位有工序 + 该工序在产品工艺路线中 → processId 应该被设置为工位工序
     * 批号为 null → 创建失败
     */
    @Test
    public void testCreateIpqc_processId_workstationHasProcess() {
        // 准备参数
        Long workstationId = randomLongId();
        Long workOrderId = randomLongId();
        Long productId = randomLongId();
        Long processId = randomLongId();
        Long routeId = randomLongId();
        Long templateId = randomLongId();
    public void testCreateIpqc_batchCode_null() {
        MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
        reqVO.setCode("IPQC-TEST-001");
        reqVO.setWorkstationId(workstationId);
        reqVO.setWorkOrderId(workOrderId);
        reqVO.setCode("IPQC-BC-001");
        reqVO.setBatchCode(null);
        reqVO.setItemId(randomLongId());
        reqVO.setInspectorUserId(randomLongId());
        // mock 工位返回有工序
        MesMdWorkstationDO workstation = new MesMdWorkstationDO();
        workstation.setId(workstationId);
        workstation.setProcessId(processId);
        when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
        // mock 工单
        MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
        workOrder.setId(workOrderId);
        workOrder.setProductId(productId);
        when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
        when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
        // mock 模板匹配
        MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
        templateItem.setTemplateId(templateId);
        when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
        // mock 工艺路线
        MesProRouteProductDO routeProduct = new MesProRouteProductDO();
        routeProduct.setRouteId(routeId);
        when(routeProductService.getRouteProductByItemId(productId)).thenReturn(routeProduct);
        // mock 工序在路线中
        when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
                .thenReturn(new MesProRouteProcessDO());
        // 调用
        Long ipqcId = ipqcService.createIpqc(reqVO);
        // 断言:processId 应该是工位工序
        MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId);
        assertEquals(processId, ipqc.getProcessId());
        ServiceException ex = assertThrows(ServiceException.class, () -> ipqcService.createIpqc(reqVO));
        assertEquals(QC_IPQC_BATCH_CODE_REQUIRED.getCode(), ex.getCode());
    }
    /**
     * 工位没有工序(processId=null)→ processId 应该留 null(不回退到任务工序)
     * 批号为空白串 → 创建失败(兜底内部/RPC 调用绕过 VO 校验的情况)
     */
    @Test
    public void testCreateIpqc_processId_workstationNoProcess_shouldBeNull() {
        // 准备参数
        Long workstationId = randomLongId();
        Long workOrderId = randomLongId();
        Long productId = randomLongId();
        Long taskId = randomLongId();
        Long templateId = randomLongId();
    public void testCreateIpqc_batchCode_blank() {
        MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
        reqVO.setCode("IPQC-TEST-002");
        reqVO.setWorkstationId(workstationId);
        reqVO.setWorkOrderId(workOrderId);
        reqVO.setTaskId(taskId); // 故意传入 taskId,验证不会回退
        reqVO.setCode("IPQC-BC-002");
        reqVO.setBatchCode("  ");
        reqVO.setItemId(randomLongId());
        reqVO.setInspectorUserId(randomLongId());
        // mock 工位没有工序
        MesMdWorkstationDO workstation = new MesMdWorkstationDO();
        workstation.setId(workstationId);
        workstation.setProcessId(null); // 无工序
        when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
        // mock 工单
        MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
        workOrder.setId(workOrderId);
        workOrder.setProductId(productId);
        when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
        when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
        // mock 任务
        MesProTaskDO task = new MesProTaskDO();
        task.setId(taskId);
        task.setWorkOrderId(workOrderId);
        task.setWorkstationId(workstationId);
        task.setItemId(productId);
        when(taskService.validateTaskNotFinished(taskId)).thenReturn(task);
        // mock 模板匹配
        MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
        templateItem.setTemplateId(templateId);
        when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
        ServiceException ex = assertThrows(ServiceException.class, () -> ipqcService.createIpqc(reqVO));
        assertEquals(QC_IPQC_BATCH_CODE_REQUIRED.getCode(), ex.getCode());
    }
    // ==================== 创建成功:批号 / 物料落库 ====================
    @Test
    public void testCreateIpqc_success() {
        // 准备参数
        Long itemId = randomLongId();
        Long templateId = randomLongId();
        MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
        reqVO.setCode("IPQC-OK-001");
        reqVO.setName("测试过程检验");
        reqVO.setBatchCode("BC-2025-001");
        reqVO.setItemId(itemId);
        reqVO.setInspectorUserId(randomLongId());
        mockTemplate(itemId, templateId);
        // 调用
        Long ipqcId = ipqcService.createIpqc(reqVO);
        // 断言:processId 应该是 null,即使传入了 taskId 也不应该回退
        // 断言:批号与物料落库,模板按物料+IPQC 类型匹配
        MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId);
        assertNull(ipqc.getProcessId(),
                "工位无工序时 processId 应为 null,不应回退到任务工序");
        // 断言:不应该读取任务的 processId
        verify(routeProductService, never()).getRouteProductByItemId(any());
        assertNotNull(ipqc);
        assertEquals("BC-2025-001", ipqc.getBatchCode());
        assertEquals(itemId, ipqc.getItemId());
        assertEquals(templateId, ipqc.getTemplateId());
        verify(mdmItemApi).validateItemExists(itemId);
    }
    /**
     * 工位有工序但该工序不在产品工艺路线中 → processId 应该留 null
     * 传入了不在匹配列表中的 templateId → 拒绝创建
     */
    @Test
    public void testCreateIpqc_processId_processNotInRoute() {
        // 准备参数
        Long workstationId = randomLongId();
        Long workOrderId = randomLongId();
        Long productId = randomLongId();
        Long processId = randomLongId();
        Long routeId = randomLongId();
        Long templateId = randomLongId();
    public void testCreateIpqc_templateId_notMatched() {
        Long itemId = randomLongId();
        MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
        reqVO.setCode("IPQC-TEST-003");
        reqVO.setWorkstationId(workstationId);
        reqVO.setWorkOrderId(workOrderId);
        reqVO.setCode("IPQC-TPL-001");
        reqVO.setName("指定方案不匹配");
        reqVO.setBatchCode("BC-2025-002");
        reqVO.setItemId(itemId);
        reqVO.setInspectorUserId(randomLongId());
        reqVO.setTemplateId(randomLongId()); // 与匹配结果不一致
        mockTemplate(itemId, randomLongId());
        // mock 工位有工序
        MesMdWorkstationDO workstation = new MesMdWorkstationDO();
        workstation.setId(workstationId);
        workstation.setProcessId(processId);
        when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
        // mock 工单
        MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
        workOrder.setId(workOrderId);
        workOrder.setProductId(productId);
        when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
        when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
        // mock 模板匹配
        MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
        templateItem.setTemplateId(templateId);
        when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
        // mock 工艺路线存在
        MesProRouteProductDO routeProduct = new MesProRouteProductDO();
        routeProduct.setRouteId(routeId);
        when(routeProductService.getRouteProductByItemId(productId)).thenReturn(routeProduct);
        // mock 工序不在路线中
        when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
                .thenReturn(null);
        // 调用
        Long ipqcId = ipqcService.createIpqc(reqVO);
        // 断言:processId 应该是 null
        MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId);
        assertNull(ipqc.getProcessId());
        ServiceException ex = assertThrows(ServiceException.class, () -> ipqcService.createIpqc(reqVO));
        assertEquals(QC_NO_TEMPLATE.getCode(), ex.getCode());
    }
    // ==================== sourceLineId 校验测试 ====================
@@ -404,35 +323,10 @@
     */
    @Test
    public void testCreateIpqc_sourceLineId_required() {
        // 准备参数
        Long workstationId = randomLongId();
        Long workOrderId = randomLongId();
        Long productId = randomLongId();
        Long itemId = randomLongId();
        Long feedbackId = randomLongId();
        Long templateId = randomLongId();
        MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
        reqVO.setCode("IPQC-SL-001");
        reqVO.setWorkstationId(workstationId);
        reqVO.setWorkOrderId(workOrderId);
        reqVO.setInspectorUserId(randomLongId());
        reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
        reqVO.setSourceDocId(feedbackId);
        reqVO.setSourceLineId(null); // 不传 sourceLineId
        // mock
        MesMdWorkstationDO workstation = new MesMdWorkstationDO();
        workstation.setId(workstationId);
        workstation.setProcessId(null);
        when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
        MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
        workOrder.setId(workOrderId);
        workOrder.setProductId(productId);
        when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
        when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
        MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
        templateItem.setTemplateId(templateId);
        when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
        MesQcIpqcSaveReqVO reqVO = buildFeedbackReqVO("IPQC-SL-001", itemId, feedbackId, null);
        mockTemplate(itemId, randomLongId());
        when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(new MesProFeedbackDO());
        // 调用,断言异常
@@ -445,36 +339,11 @@
     */
    @Test
    public void testCreateIpqc_sourceLineId_notBelong() {
        // 准备参数
        Long workstationId = randomLongId();
        Long workOrderId = randomLongId();
        Long productId = randomLongId();
        Long itemId = randomLongId();
        Long feedbackId = randomLongId();
        Long sourceLineId = randomLongId();
        Long templateId = randomLongId();
        MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
        reqVO.setCode("IPQC-SL-002");
        reqVO.setWorkstationId(workstationId);
        reqVO.setWorkOrderId(workOrderId);
        reqVO.setInspectorUserId(randomLongId());
        reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
        reqVO.setSourceDocId(feedbackId);
        reqVO.setSourceLineId(sourceLineId);
        // mock
        MesMdWorkstationDO workstation = new MesMdWorkstationDO();
        workstation.setId(workstationId);
        workstation.setProcessId(null);
        when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
        MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
        workOrder.setId(workOrderId);
        workOrder.setProductId(productId);
        when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
        when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
        MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
        templateItem.setTemplateId(templateId);
        when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
        MesQcIpqcSaveReqVO reqVO = buildFeedbackReqVO("IPQC-SL-002", itemId, feedbackId, sourceLineId);
        mockTemplate(itemId, randomLongId());
        when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(new MesProFeedbackDO());
        // mock:产出行存在但 feedbackId 不匹配
        MesWmProductProduceLineDO line = new MesWmProductProduceLineDO();
@@ -493,36 +362,11 @@
     */
    @Test
    public void testCreateIpqc_sourceLineId_notPending() {
        // 准备参数
        Long workstationId = randomLongId();
        Long workOrderId = randomLongId();
        Long productId = randomLongId();
        Long itemId = randomLongId();
        Long feedbackId = randomLongId();
        Long sourceLineId = randomLongId();
        Long templateId = randomLongId();
        MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
        reqVO.setCode("IPQC-SL-003");
        reqVO.setWorkstationId(workstationId);
        reqVO.setWorkOrderId(workOrderId);
        reqVO.setInspectorUserId(randomLongId());
        reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
        reqVO.setSourceDocId(feedbackId);
        reqVO.setSourceLineId(sourceLineId);
        // mock
        MesMdWorkstationDO workstation = new MesMdWorkstationDO();
        workstation.setId(workstationId);
        workstation.setProcessId(null);
        when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
        MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
        workOrder.setId(workOrderId);
        workOrder.setProductId(productId);
        when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
        when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
        MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
        templateItem.setTemplateId(templateId);
        when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
        MesQcIpqcSaveReqVO reqVO = buildFeedbackReqVO("IPQC-SL-003", itemId, feedbackId, sourceLineId);
        mockTemplate(itemId, randomLongId());
        when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(new MesProFeedbackDO());
        // mock:产出行存在且归属正确,但不是 PENDING 状态
        MesWmProductProduceLineDO line = new MesWmProductProduceLineDO();
@@ -541,36 +385,11 @@
     */
    @Test
    public void testCreateIpqc_sourceLineId_success() {
        // 准备参数
        Long workstationId = randomLongId();
        Long workOrderId = randomLongId();
        Long productId = randomLongId();
        Long itemId = randomLongId();
        Long feedbackId = randomLongId();
        Long sourceLineId = randomLongId();
        Long templateId = randomLongId();
        MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
        reqVO.setCode("IPQC-SL-004");
        reqVO.setWorkstationId(workstationId);
        reqVO.setWorkOrderId(workOrderId);
        reqVO.setInspectorUserId(randomLongId());
        reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
        reqVO.setSourceDocId(feedbackId);
        reqVO.setSourceLineId(sourceLineId);
        // mock
        MesMdWorkstationDO workstation = new MesMdWorkstationDO();
        workstation.setId(workstationId);
        workstation.setProcessId(null);
        when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
        MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
        workOrder.setId(workOrderId);
        workOrder.setProductId(productId);
        when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
        when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
        MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
        templateItem.setTemplateId(templateId);
        when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
        MesQcIpqcSaveReqVO reqVO = buildFeedbackReqVO("IPQC-SL-004", itemId, feedbackId, sourceLineId);
        mockTemplate(itemId, randomLongId());
        MesProFeedbackDO feedback = new MesProFeedbackDO();
        feedback.setId(feedbackId);
        feedback.setCode("FB-001");
@@ -593,4 +412,26 @@
        assertEquals("FB-001", ipqc.getSourceDocCode());
    }
    // ==================== 测试辅助 ====================
    private MesQcIpqcSaveReqVO buildFeedbackReqVO(String code, Long itemId, Long feedbackId, Long sourceLineId) {
        MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
        reqVO.setCode(code);
        reqVO.setName("来源校验过程检验");
        reqVO.setBatchCode("BC-SL-001");
        reqVO.setItemId(itemId);
        reqVO.setInspectorUserId(randomLongId());
        reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
        reqVO.setSourceDocId(feedbackId);
        reqVO.setSourceLineId(sourceLineId);
        return reqVO;
    }
    private void mockTemplate(Long itemId, Long templateId) {
        MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
        templateItem.setTemplateId(templateId);
        when(templateItemService.getTemplateItemList(eq(itemId), anyInt()))
                .thenReturn(List.of(templateItem));
    }
}