From 91e7b43b1d32617684da1b4a1750957e0f01e5d2 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 27 Mar 2026 17:26:08 +0800 Subject: [PATCH] ✨ feat(mes): 添加检测工具字段,优化出货检验单数据结构 --- yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/MesQcOqcPageReqVO.java | 6 ------ yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/line/MesQcOqcLineRespVO.java | 3 +++ yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/oqc/MesQcOqcDO.java | 8 ++++++++ yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/oqc/MesQcOqcMapper.java | 2 -- yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java | 28 +++++++++++++++++++--------- yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java | 25 +++++++++++++++++++++++++ yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImplTest.java | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------- yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImplTest.java | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 8 files changed, 255 insertions(+), 34 deletions(-) diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/MesQcOqcPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/MesQcOqcPageReqVO.java index 63e14d1..47926c1 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/MesQcOqcPageReqVO.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/MesQcOqcPageReqVO.java @@ -27,10 +27,4 @@ public class MesQcOqcPageReqVO extends PageParam { @Schema(description = "检测结果", example = "1") private Integer checkResult; - @Schema(description = "状态", example = "0") - private Integer status; - - @Schema(description = "检测人员用户 ID", example = "1") - private Long inspectorUserId; - } diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/line/MesQcOqcLineRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/line/MesQcOqcLineRespVO.java index e47b6bb..ae69409 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/line/MesQcOqcLineRespVO.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/line/MesQcOqcLineRespVO.java @@ -30,6 +30,9 @@ public class MesQcOqcLineRespVO { @Schema(description = "检测指标类型", example = "QUANTITATIVE") private String indicatorType; + @Schema(description = "检测工具", example = "卡尺") + private String tool; + @Schema(description = "检测方法", example = "目测") private String checkMethod; diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/oqc/MesQcOqcDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/oqc/MesQcOqcDO.java index bdd8ca4..ce7a97e 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/oqc/MesQcOqcDO.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/oqc/MesQcOqcDO.java @@ -99,6 +99,14 @@ public class MesQcOqcDO extends BaseDO { // ========== 数量 ========== /** + * 最低检测数 + */ + private Integer minCheckQuantity; + /** + * 最大不合格数 + */ + private Integer maxUnqualifiedQuantity; + /** * 本次出货数量 */ private BigDecimal outQuantity; diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/oqc/MesQcOqcMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/oqc/MesQcOqcMapper.java index 6219e60..2625818 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/oqc/MesQcOqcMapper.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/oqc/MesQcOqcMapper.java @@ -22,8 +22,6 @@ public interface MesQcOqcMapper extends BaseMapperX { .likeIfPresent(MesQcOqcDO::getBatchCode, reqVO.getBatchCode()) .eqIfPresent(MesQcOqcDO::getItemId, reqVO.getItemId()) .eqIfPresent(MesQcOqcDO::getCheckResult, reqVO.getCheckResult()) - .eqIfPresent(MesQcOqcDO::getStatus, reqVO.getStatus()) - .eqIfPresent(MesQcOqcDO::getInspectorUserId, reqVO.getInspectorUserId()) .orderByDesc(MesQcOqcDO::getId)); } diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java index d49adfc..da5cfb2 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java @@ -75,17 +75,18 @@ public class MesQcOqcServiceImpl implements MesQcOqcService { public Long createOqc(MesQcOqcSaveReqVO createReqVO) { // 1.1 校验编号唯一 validateOqcCodeUnique(null, createReqVO.getCode()); - // 1.2 校验客户、物料、检测人员存在 - clientService.validateClientExists(createReqVO.getClientId()); + // 1.2 校验物料、检测人员存在 itemService.validateItemExists(createReqVO.getItemId()); adminUserApi.validateUser(createReqVO.getInspectorUserId()); - // 1.3 根据产品 + 检验类型自动匹配模板 + // 1.3 校验来源单据参数,并验证数据存在 + String sourceDocCode = validateAndGetSourceDocCode( + createReqVO.getSourceDocType(), createReqVO.getSourceLineId()); + // 1.4 校验客户存在 + clientService.validateClientExists(createReqVO.getClientId()); + // 1.5 根据产品 + 检验类型自动匹配模板 MesQcTemplateItemDO templateItem = templateDetailService.getRequiredTemplateByItemIdAndType( createReqVO.getItemId(), MesQcTypeEnum.OQC.getType()); Long templateId = templateItem.getTemplateId(); - // 1.4 获取来源单据编号 - String sourceDocCode = validateAndGetSourceDocCode( - createReqVO.getSourceDocType(), createReqVO.getSourceLineId()); // 2. 插入主表 MesQcOqcDO oqc = BeanUtils.toBean(createReqVO, MesQcOqcDO.class) @@ -137,7 +138,9 @@ public class MesQcOqcServiceImpl implements MesQcOqcService { /** * 回写来源单据(OQC 完成后) * - *

当来源为销售出库单时,回写行的 oqcId 和 qualityStatus + *

当来源为销售出库单时,调用 + * {@link MesWmProductSalesLineService#updateProductSalesLineWhenOqcFinish} + * 回写行的 oqcId、qualityStatus,并由该方法内部联动检查出库单整体状态 * * @param oqc 出货检验单 */ @@ -151,8 +154,8 @@ public class MesQcOqcServiceImpl implements MesQcOqcService { } if (Objects.equals(oqc.getSourceDocType(), MesBizTypeConstants.WM_PRODUCT_SALES)) { - // 回写销售出库单行的 oqcId + qualityStatus - productSalesLineService.updateProductSalesLineWhenOqcFinish(oqc.getSourceLineId(), oqc.getId(), oqc.getCheckResult()); + productSalesLineService.updateProductSalesLineWhenOqcFinish( + oqc.getSourceLineId(), oqc.getId(), oqc.getCheckResult()); } else { throw new IllegalArgumentException( "OQC 单[" + oqc.getId() + "] sourceDocType=" + oqc.getSourceDocType() + " 无法识别,无法回写来源单据"); @@ -203,6 +206,13 @@ public class MesQcOqcServiceImpl implements MesQcOqcService { } } + /** + * 校验来源单据参数,并验证数据存在 + * + * @param sourceDocType 来源单据类型 + * @param sourceLineId 来源单据行 ID + * @return 来源单据编号 + */ private String validateAndGetSourceDocCode(Integer sourceDocType, Long sourceLineId) { if (sourceDocType == null || sourceLineId == null) { return null; diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java index 53f8358..9395fd1 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.mes.service.wm.productsales; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.line.MesWmProductSalesLinePageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.line.MesWmProductSalesLineSaveReqVO; @@ -9,6 +10,8 @@ import cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum; import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; @@ -26,6 +29,7 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_PRODUCT_SA */ @Service @Validated +@Slf4j public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineService { @Resource @@ -34,6 +38,9 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe private MesWmProductSalesDetailService productSalesDetailService; @Resource private MesMdItemService itemService; + @Resource + @Lazy + private MesWmProductSalesService productSalesService; @Override public Long createProductSalesLine(MesWmProductSalesLineSaveReqVO createReqVO) { @@ -100,11 +107,29 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe } @Override + @Transactional(rollbackFor = Exception.class) public void updateProductSalesLineWhenOqcFinish(Long id, Long oqcId, Integer checkResult) { + // 1. 更新行的 oqcId + qualityStatus Integer qualityStatus = Objects.equals(checkResult, MesQcCheckResultEnum.PASS.getType()) ? MesWmQualityStatusEnum.PASS.getStatus() : MesWmQualityStatusEnum.FAIL.getStatus(); productSalesLineMapper.updateById(new MesWmProductSalesLineDO() .setId(id).setOqcId(oqcId).setQualityStatus(qualityStatus)); + + // 2. 检查同一出库单下所有需要 OQC 检验的行的质检状态,联动更新出库单整体状态 + MesWmProductSalesLineDO currentLine = productSalesLineMapper.selectById(id); + if (currentLine == null || currentLine.getSalesId() == null) { + return; + } + Long salesId = currentLine.getSalesId(); + List allLines = productSalesLineMapper.selectListBySalesId(salesId); + boolean hasNg = CollectionUtils.anyMatch(allLines, + line -> Boolean.TRUE.equals(line.getOqcCheck()) + && Objects.equals(line.getQualityStatus(), MesWmQualityStatusEnum.FAIL.getStatus())); + if (hasNg) { + log.info("[updateProductSalesLineWhenOqcFinish][销售出库单({}) 存在质检不合格行,取消出库单]", salesId); + productSalesService.cancelProductSales(salesId); + } } } + diff --git a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImplTest.java b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImplTest.java index 8c4993e..b2cfbb2 100644 --- a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImplTest.java +++ b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImplTest.java @@ -5,10 +5,12 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO; import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcMapper; import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; +import cn.iocoder.yudao.module.mes.service.md.client.MesMdClientService; import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService; import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateDetailService; import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesLineService; +import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; @@ -18,11 +20,10 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.QC_OQC_NOT_EXISTS; -import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.QC_OQC_NOT_PREPARE; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.*; /** * {@link MesQcOqcServiceImpl} 的单元测试类 @@ -41,8 +42,12 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { @MockitoBean private MesWmProductSalesLineService productSalesLineService; @MockitoBean + private MesWmProductSalesService productSalesService; + @MockitoBean private MesQcOqcLineService oqcLineService; @MockitoBean + private MesMdClientService clientService; + @MockitoBean private MesMdItemService itemService; @MockitoBean private MesQcTemplateDetailService templateDetailService; @@ -52,12 +57,10 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { private AdminUserApi adminUserApi; @Test - public void testFinishOqc_success() { - // 准备参数 + public void testFinishOqc_writeBack_productSales() { + // 准备数据:插入一条草稿状态的 OQC 单,来源为销售出库单 Long sourceLineId = randomLongId(); Integer checkResult = 1; - - // mock 数据 MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); o.setSourceDocType(MesBizTypeConstants.WM_PRODUCT_SALES); @@ -69,12 +72,64 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { // 调用 oqcService.finishOqc(oqc.getId()); - // 断言:验证状态更新 + // 断言:验证状态更新为已完成 MesQcOqcDO updatedOqc = oqcMapper.selectById(oqc.getId()); assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedOqc.getStatus()); + // 断言:验证回写销售出库单行 + verify(productSalesLineService).updateProductSalesLineWhenOqcFinish( + eq(sourceLineId), eq(oqc.getId()), eq(checkResult)); + } - // 断言:验证回写调用 - verify(productSalesLineService).updateProductSalesLineWhenOqcFinish(eq(sourceLineId), eq(oqc.getId()), eq(checkResult)); + @Test + public void testFinishOqc_writeBack_noSourceDoc() { + // 准备数据:sourceDocType 为空,不需要回写 + MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); + o.setCheckResult(1); + o.setSourceDocType(null); + o.setSourceLineId(null); + }); + oqcMapper.insert(oqc); + + // 调用 + oqcService.finishOqc(oqc.getId()); + + // 断言:验证状态更新为已完成 + MesQcOqcDO updatedOqc = oqcMapper.selectById(oqc.getId()); + assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedOqc.getStatus()); + // 断言:不应该调用任何回写 + verify(productSalesLineService, never()).updateProductSalesLineWhenOqcFinish( + anyLong(), anyLong(), anyInt()); + } + + @Test + public void testFinishOqc_writeBack_sourceDocTypeDirtyData() { + // 准备数据:sourceDocType 非空但 sourceLineId 为空,应该抛异常 + MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); + o.setCheckResult(1); + o.setSourceDocType(MesBizTypeConstants.WM_PRODUCT_SALES); + o.setSourceLineId(null); // 脏数据 + }); + oqcMapper.insert(oqc); + + // 调用,并断言异常 + assertThrows(IllegalArgumentException.class, () -> oqcService.finishOqc(oqc.getId())); + } + + @Test + public void testFinishOqc_writeBack_unknownSourceDocType() { + // 准备数据:sourceDocType 无法识别 + MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); + o.setCheckResult(1); + o.setSourceDocType(99999); // 无法识别的类型 + o.setSourceLineId(randomLongId()); + }); + oqcMapper.insert(oqc); + + // 调用,并断言异常 + assertThrows(IllegalArgumentException.class, () -> oqcService.finishOqc(oqc.getId())); } @Test @@ -88,8 +143,7 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { @Test public void testFinishOqc_statusNotDraft() { - // 准备参数 - // mock 数据:状态为已完成 + // 准备参数:状态为已完成 MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { o.setStatus(MesQcStatusEnum.FINISHED.getStatus()); }); @@ -99,4 +153,48 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { assertServiceException(() -> oqcService.finishOqc(oqc.getId()), QC_OQC_NOT_PREPARE); } + @Test + public void testFinishOqc_checkResultEmpty() { + // 准备数据:checkResult 为空,应该报错 + MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); + o.setCheckResult(null); // 检测结论为空 + o.setSourceDocType(null); + }); + oqcMapper.insert(oqc); + + // 调用,应该抛异常(检测结论必填) + assertServiceException(() -> oqcService.finishOqc(oqc.getId()), QC_OQC_CHECK_RESULT_EMPTY); + } + + @Test + public void testDeleteOqc_success() { + // 准备数据:草稿状态 + MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); + }); + oqcMapper.insert(oqc); + + // 调用 + oqcService.deleteOqc(oqc.getId()); + + // 断言:已删除 + assertNull(oqcMapper.selectById(oqc.getId())); + } + + @Test + public void testDeleteOqc_notExists() { + assertServiceException(() -> oqcService.deleteOqc(randomLongId()), QC_OQC_NOT_EXISTS); + } + + @Test + public void testDeleteOqc_statusNotDraft() { + MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { + o.setStatus(MesQcStatusEnum.FINISHED.getStatus()); + }); + oqcMapper.insert(oqc); + + assertServiceException(() -> oqcService.deleteOqc(oqc.getId()), QC_OQC_NOT_PREPARE); + } + } diff --git a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImplTest.java b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImplTest.java index a156927..a760a23 100644 --- a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImplTest.java +++ b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImplTest.java @@ -15,6 +15,7 @@ import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; /** * {@link MesWmProductSalesLineServiceImpl} 的单元测试类 @@ -46,13 +47,16 @@ public class MesWmProductSalesLineServiceImplTest extends BaseDbUnitTest { Integer checkResult = MesQcCheckResultEnum.PASS.getType(); // mock 数据 - MesWmProductSalesLineDO lineDO = randomPojo(MesWmProductSalesLineDO.class); + MesWmProductSalesLineDO lineDO = randomPojo(MesWmProductSalesLineDO.class, o -> { + o.setOqcCheck(true); // 需要 OQC 检验 + o.setQualityStatus(null); // 初始为空 + }); productSalesLineMapper.insert(lineDO); // 调用 productSalesLineService.updateProductSalesLineWhenOqcFinish(lineDO.getId(), oqcId, checkResult); - // 断言:验证 mapper 更新参数正确 + // 断言:验证行字段更新 MesWmProductSalesLineDO updateLine = productSalesLineMapper.selectById(lineDO.getId()); assertEquals(oqcId, updateLine.getOqcId()); assertEquals(MesWmQualityStatusEnum.PASS.getStatus(), updateLine.getQualityStatus()); @@ -65,16 +69,97 @@ public class MesWmProductSalesLineServiceImplTest extends BaseDbUnitTest { Integer checkResult = MesQcCheckResultEnum.FAIL.getType(); // mock 数据 - MesWmProductSalesLineDO lineDO = randomPojo(MesWmProductSalesLineDO.class); + MesWmProductSalesLineDO lineDO = randomPojo(MesWmProductSalesLineDO.class, o -> { + o.setOqcCheck(true); + o.setQualityStatus(null); + }); productSalesLineMapper.insert(lineDO); // 调用 productSalesLineService.updateProductSalesLineWhenOqcFinish(lineDO.getId(), oqcId, checkResult); - // 断言:验证 mapper 更新参数正确 + // 断言:验证行字段更新 MesWmProductSalesLineDO updateLine = productSalesLineMapper.selectById(lineDO.getId()); assertEquals(oqcId, updateLine.getOqcId()); assertEquals(MesWmQualityStatusEnum.FAIL.getStatus(), updateLine.getQualityStatus()); } + @Test + public void testUpdateProductSalesLineWhenOqcFinish_cancelWhenNg() { + // 准备数据:同一出库单下 2 行,第 1 行已合格,第 2 行即将变更为不合格 + Long salesId = randomLongId(); + MesWmProductSalesLineDO line1 = randomPojo(MesWmProductSalesLineDO.class, o -> { + o.setSalesId(salesId); + o.setOqcCheck(true); + o.setQualityStatus(MesWmQualityStatusEnum.PASS.getStatus()); + }); + productSalesLineMapper.insert(line1); + + MesWmProductSalesLineDO line2 = randomPojo(MesWmProductSalesLineDO.class, o -> { + o.setSalesId(salesId); + o.setOqcCheck(true); + o.setQualityStatus(null); // 待检 + }); + productSalesLineMapper.insert(line2); + + // 调用:第 2 行质检不合格 + productSalesLineService.updateProductSalesLineWhenOqcFinish( + line2.getId(), randomLongId(), MesQcCheckResultEnum.FAIL.getType()); + + // 断言:应该调用 cancelProductSales + verify(productSalesService).cancelProductSales(eq(salesId)); + } + + @Test + public void testUpdateProductSalesLineWhenOqcFinish_noCancelWhenAllPass() { + // 准备数据:同一出库单下 2 行,第 1 行已合格,第 2 行即将变更为合格 + Long salesId = randomLongId(); + MesWmProductSalesLineDO line1 = randomPojo(MesWmProductSalesLineDO.class, o -> { + o.setSalesId(salesId); + o.setOqcCheck(true); + o.setQualityStatus(MesWmQualityStatusEnum.PASS.getStatus()); + }); + productSalesLineMapper.insert(line1); + + MesWmProductSalesLineDO line2 = randomPojo(MesWmProductSalesLineDO.class, o -> { + o.setSalesId(salesId); + o.setOqcCheck(true); + o.setQualityStatus(null); + }); + productSalesLineMapper.insert(line2); + + // 调用:第 2 行质检合格 + productSalesLineService.updateProductSalesLineWhenOqcFinish( + line2.getId(), randomLongId(), MesQcCheckResultEnum.PASS.getType()); + + // 断言:不应该调用 cancelProductSales + verify(productSalesService, never()).cancelProductSales(anyLong()); + } + + @Test + public void testUpdateProductSalesLineWhenOqcFinish_skipNonOqcCheckLines() { + // 准备数据:同一出库单下 2 行,第 1 行不需要 OQC 检验(oqcCheck=false),第 2 行需要检验且不合格 + Long salesId = randomLongId(); + MesWmProductSalesLineDO line1 = randomPojo(MesWmProductSalesLineDO.class, o -> { + o.setSalesId(salesId); + o.setOqcCheck(false); // 不需要 OQC 检验 + o.setQualityStatus(null); + }); + productSalesLineMapper.insert(line1); + + MesWmProductSalesLineDO line2 = randomPojo(MesWmProductSalesLineDO.class, o -> { + o.setSalesId(salesId); + o.setOqcCheck(true); + o.setQualityStatus(null); + }); + productSalesLineMapper.insert(line2); + + // 调用:第 2 行质检不合格 + productSalesLineService.updateProductSalesLineWhenOqcFinish( + line2.getId(), randomLongId(), MesQcCheckResultEnum.FAIL.getType()); + + // 断言:第 2 行不合格 → 应该取消出库单 + verify(productSalesService).cancelProductSales(eq(salesId)); + } + } -- libgit2 0.21.4