Commit 91e7b43b1d32617684da1b4a1750957e0f01e5d2
1 parent
828c878f
✨ feat(mes): 添加检测工具字段,优化出货检验单数据结构
新增检测工具字段以支持更全面的检验信息,同时优化相关数据结构,提升系统的可用性和灵活性。
Showing
8 changed files
with
255 additions
and
34 deletions
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 { |
| 27 | 27 | @Schema(description = "检测结果", example = "1") |
| 28 | 28 | private Integer checkResult; |
| 29 | 29 | |
| 30 | - @Schema(description = "状态", example = "0") | |
| 31 | - private Integer status; | |
| 32 | - | |
| 33 | - @Schema(description = "检测人员用户 ID", example = "1") | |
| 34 | - private Long inspectorUserId; | |
| 35 | - | |
| 36 | 30 | } | ... | ... |
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 { |
| 30 | 30 | @Schema(description = "检测指标类型", example = "QUANTITATIVE") |
| 31 | 31 | private String indicatorType; |
| 32 | 32 | |
| 33 | + @Schema(description = "检测工具", example = "卡尺") | |
| 34 | + private String tool; | |
| 35 | + | |
| 33 | 36 | @Schema(description = "检测方法", example = "目测") |
| 34 | 37 | private String checkMethod; |
| 35 | 38 | ... | ... |
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 { |
| 99 | 99 | // ========== 数量 ========== |
| 100 | 100 | |
| 101 | 101 | /** |
| 102 | + * 最低检测数 | |
| 103 | + */ | |
| 104 | + private Integer minCheckQuantity; | |
| 105 | + /** | |
| 106 | + * 最大不合格数 | |
| 107 | + */ | |
| 108 | + private Integer maxUnqualifiedQuantity; | |
| 109 | + /** | |
| 102 | 110 | * 本次出货数量 |
| 103 | 111 | */ |
| 104 | 112 | private BigDecimal outQuantity; | ... | ... |
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<MesQcOqcDO> { |
| 22 | 22 | .likeIfPresent(MesQcOqcDO::getBatchCode, reqVO.getBatchCode()) |
| 23 | 23 | .eqIfPresent(MesQcOqcDO::getItemId, reqVO.getItemId()) |
| 24 | 24 | .eqIfPresent(MesQcOqcDO::getCheckResult, reqVO.getCheckResult()) |
| 25 | - .eqIfPresent(MesQcOqcDO::getStatus, reqVO.getStatus()) | |
| 26 | - .eqIfPresent(MesQcOqcDO::getInspectorUserId, reqVO.getInspectorUserId()) | |
| 27 | 25 | .orderByDesc(MesQcOqcDO::getId)); |
| 28 | 26 | } |
| 29 | 27 | ... | ... |
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 { |
| 75 | 75 | public Long createOqc(MesQcOqcSaveReqVO createReqVO) { |
| 76 | 76 | // 1.1 校验编号唯一 |
| 77 | 77 | validateOqcCodeUnique(null, createReqVO.getCode()); |
| 78 | - // 1.2 校验客户、物料、检测人员存在 | |
| 79 | - clientService.validateClientExists(createReqVO.getClientId()); | |
| 78 | + // 1.2 校验物料、检测人员存在 | |
| 80 | 79 | itemService.validateItemExists(createReqVO.getItemId()); |
| 81 | 80 | adminUserApi.validateUser(createReqVO.getInspectorUserId()); |
| 82 | - // 1.3 根据产品 + 检验类型自动匹配模板 | |
| 81 | + // 1.3 校验来源单据参数,并验证数据存在 | |
| 82 | + String sourceDocCode = validateAndGetSourceDocCode( | |
| 83 | + createReqVO.getSourceDocType(), createReqVO.getSourceLineId()); | |
| 84 | + // 1.4 校验客户存在 | |
| 85 | + clientService.validateClientExists(createReqVO.getClientId()); | |
| 86 | + // 1.5 根据产品 + 检验类型自动匹配模板 | |
| 83 | 87 | MesQcTemplateItemDO templateItem = templateDetailService.getRequiredTemplateByItemIdAndType( |
| 84 | 88 | createReqVO.getItemId(), MesQcTypeEnum.OQC.getType()); |
| 85 | 89 | Long templateId = templateItem.getTemplateId(); |
| 86 | - // 1.4 获取来源单据编号 | |
| 87 | - String sourceDocCode = validateAndGetSourceDocCode( | |
| 88 | - createReqVO.getSourceDocType(), createReqVO.getSourceLineId()); | |
| 89 | 90 | |
| 90 | 91 | // 2. 插入主表 |
| 91 | 92 | MesQcOqcDO oqc = BeanUtils.toBean(createReqVO, MesQcOqcDO.class) |
| ... | ... | @@ -137,7 +138,9 @@ public class MesQcOqcServiceImpl implements MesQcOqcService { |
| 137 | 138 | /** |
| 138 | 139 | * 回写来源单据(OQC 完成后) |
| 139 | 140 | * |
| 140 | - * <p>当来源为销售出库单时,回写行的 oqcId 和 qualityStatus | |
| 141 | + * <p>当来源为销售出库单时,调用 | |
| 142 | + * {@link MesWmProductSalesLineService#updateProductSalesLineWhenOqcFinish} | |
| 143 | + * 回写行的 oqcId、qualityStatus,并由该方法内部联动检查出库单整体状态 | |
| 141 | 144 | * |
| 142 | 145 | * @param oqc 出货检验单 |
| 143 | 146 | */ |
| ... | ... | @@ -151,8 +154,8 @@ public class MesQcOqcServiceImpl implements MesQcOqcService { |
| 151 | 154 | } |
| 152 | 155 | |
| 153 | 156 | if (Objects.equals(oqc.getSourceDocType(), MesBizTypeConstants.WM_PRODUCT_SALES)) { |
| 154 | - // 回写销售出库单行的 oqcId + qualityStatus | |
| 155 | - productSalesLineService.updateProductSalesLineWhenOqcFinish(oqc.getSourceLineId(), oqc.getId(), oqc.getCheckResult()); | |
| 157 | + productSalesLineService.updateProductSalesLineWhenOqcFinish( | |
| 158 | + oqc.getSourceLineId(), oqc.getId(), oqc.getCheckResult()); | |
| 156 | 159 | } else { |
| 157 | 160 | throw new IllegalArgumentException( |
| 158 | 161 | "OQC 单[" + oqc.getId() + "] sourceDocType=" + oqc.getSourceDocType() + " 无法识别,无法回写来源单据"); |
| ... | ... | @@ -203,6 +206,13 @@ public class MesQcOqcServiceImpl implements MesQcOqcService { |
| 203 | 206 | } |
| 204 | 207 | } |
| 205 | 208 | |
| 209 | + /** | |
| 210 | + * 校验来源单据参数,并验证数据存在 | |
| 211 | + * | |
| 212 | + * @param sourceDocType 来源单据类型 | |
| 213 | + * @param sourceLineId 来源单据行 ID | |
| 214 | + * @return 来源单据编号 | |
| 215 | + */ | |
| 206 | 216 | private String validateAndGetSourceDocCode(Integer sourceDocType, Long sourceLineId) { |
| 207 | 217 | if (sourceDocType == null || sourceLineId == null) { |
| 208 | 218 | return null; | ... | ... |
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.mes.service.wm.productsales; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 4 | 5 | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.line.MesWmProductSalesLinePageReqVO; |
| 5 | 6 | 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; |
| 9 | 10 | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; |
| 10 | 11 | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| 11 | 12 | import jakarta.annotation.Resource; |
| 13 | +import lombok.extern.slf4j.Slf4j; | |
| 14 | +import org.springframework.context.annotation.Lazy; | |
| 12 | 15 | import org.springframework.stereotype.Service; |
| 13 | 16 | import org.springframework.transaction.annotation.Transactional; |
| 14 | 17 | import org.springframework.validation.annotation.Validated; |
| ... | ... | @@ -26,6 +29,7 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_PRODUCT_SA |
| 26 | 29 | */ |
| 27 | 30 | @Service |
| 28 | 31 | @Validated |
| 32 | +@Slf4j | |
| 29 | 33 | public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineService { |
| 30 | 34 | |
| 31 | 35 | @Resource |
| ... | ... | @@ -34,6 +38,9 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe |
| 34 | 38 | private MesWmProductSalesDetailService productSalesDetailService; |
| 35 | 39 | @Resource |
| 36 | 40 | private MesMdItemService itemService; |
| 41 | + @Resource | |
| 42 | + @Lazy | |
| 43 | + private MesWmProductSalesService productSalesService; | |
| 37 | 44 | |
| 38 | 45 | @Override |
| 39 | 46 | public Long createProductSalesLine(MesWmProductSalesLineSaveReqVO createReqVO) { |
| ... | ... | @@ -100,11 +107,29 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe |
| 100 | 107 | } |
| 101 | 108 | |
| 102 | 109 | @Override |
| 110 | + @Transactional(rollbackFor = Exception.class) | |
| 103 | 111 | public void updateProductSalesLineWhenOqcFinish(Long id, Long oqcId, Integer checkResult) { |
| 112 | + // 1. 更新行的 oqcId + qualityStatus | |
| 104 | 113 | Integer qualityStatus = Objects.equals(checkResult, MesQcCheckResultEnum.PASS.getType()) |
| 105 | 114 | ? MesWmQualityStatusEnum.PASS.getStatus() : MesWmQualityStatusEnum.FAIL.getStatus(); |
| 106 | 115 | productSalesLineMapper.updateById(new MesWmProductSalesLineDO() |
| 107 | 116 | .setId(id).setOqcId(oqcId).setQualityStatus(qualityStatus)); |
| 117 | + | |
| 118 | + // 2. 检查同一出库单下所有需要 OQC 检验的行的质检状态,联动更新出库单整体状态 | |
| 119 | + MesWmProductSalesLineDO currentLine = productSalesLineMapper.selectById(id); | |
| 120 | + if (currentLine == null || currentLine.getSalesId() == null) { | |
| 121 | + return; | |
| 122 | + } | |
| 123 | + Long salesId = currentLine.getSalesId(); | |
| 124 | + List<MesWmProductSalesLineDO> allLines = productSalesLineMapper.selectListBySalesId(salesId); | |
| 125 | + boolean hasNg = CollectionUtils.anyMatch(allLines, | |
| 126 | + line -> Boolean.TRUE.equals(line.getOqcCheck()) | |
| 127 | + && Objects.equals(line.getQualityStatus(), MesWmQualityStatusEnum.FAIL.getStatus())); | |
| 128 | + if (hasNg) { | |
| 129 | + log.info("[updateProductSalesLineWhenOqcFinish][销售出库单({}) 存在质检不合格行,取消出库单]", salesId); | |
| 130 | + productSalesService.cancelProductSales(salesId); | |
| 131 | + } | |
| 108 | 132 | } |
| 109 | 133 | |
| 110 | 134 | } |
| 135 | + | ... | ... |
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; |
| 5 | 5 | import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcMapper; |
| 6 | 6 | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| 7 | 7 | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| 8 | +import cn.iocoder.yudao.module.mes.service.md.client.MesMdClientService; | |
| 8 | 9 | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| 9 | 10 | import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService; |
| 10 | 11 | import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateDetailService; |
| 11 | 12 | import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesLineService; |
| 13 | +import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesService; | |
| 12 | 14 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 13 | 15 | import jakarta.annotation.Resource; |
| 14 | 16 | import org.junit.jupiter.api.Test; |
| ... | ... | @@ -18,11 +20,10 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; |
| 18 | 20 | import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; |
| 19 | 21 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; |
| 20 | 22 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; |
| 21 | -import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.QC_OQC_NOT_EXISTS; | |
| 22 | -import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.QC_OQC_NOT_PREPARE; | |
| 23 | -import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 23 | +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; | |
| 24 | +import static org.junit.jupiter.api.Assertions.*; | |
| 24 | 25 | import static org.mockito.ArgumentMatchers.eq; |
| 25 | -import static org.mockito.Mockito.verify; | |
| 26 | +import static org.mockito.Mockito.*; | |
| 26 | 27 | |
| 27 | 28 | /** |
| 28 | 29 | * {@link MesQcOqcServiceImpl} 的单元测试类 |
| ... | ... | @@ -41,8 +42,12 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { |
| 41 | 42 | @MockitoBean |
| 42 | 43 | private MesWmProductSalesLineService productSalesLineService; |
| 43 | 44 | @MockitoBean |
| 45 | + private MesWmProductSalesService productSalesService; | |
| 46 | + @MockitoBean | |
| 44 | 47 | private MesQcOqcLineService oqcLineService; |
| 45 | 48 | @MockitoBean |
| 49 | + private MesMdClientService clientService; | |
| 50 | + @MockitoBean | |
| 46 | 51 | private MesMdItemService itemService; |
| 47 | 52 | @MockitoBean |
| 48 | 53 | private MesQcTemplateDetailService templateDetailService; |
| ... | ... | @@ -52,12 +57,10 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { |
| 52 | 57 | private AdminUserApi adminUserApi; |
| 53 | 58 | |
| 54 | 59 | @Test |
| 55 | - public void testFinishOqc_success() { | |
| 56 | - // 准备参数 | |
| 60 | + public void testFinishOqc_writeBack_productSales() { | |
| 61 | + // 准备数据:插入一条草稿状态的 OQC 单,来源为销售出库单 | |
| 57 | 62 | Long sourceLineId = randomLongId(); |
| 58 | 63 | Integer checkResult = 1; |
| 59 | - | |
| 60 | - // mock 数据 | |
| 61 | 64 | MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { |
| 62 | 65 | o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); |
| 63 | 66 | o.setSourceDocType(MesBizTypeConstants.WM_PRODUCT_SALES); |
| ... | ... | @@ -69,12 +72,64 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { |
| 69 | 72 | // 调用 |
| 70 | 73 | oqcService.finishOqc(oqc.getId()); |
| 71 | 74 | |
| 72 | - // 断言:验证状态更新 | |
| 75 | + // 断言:验证状态更新为已完成 | |
| 73 | 76 | MesQcOqcDO updatedOqc = oqcMapper.selectById(oqc.getId()); |
| 74 | 77 | assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedOqc.getStatus()); |
| 78 | + // 断言:验证回写销售出库单行 | |
| 79 | + verify(productSalesLineService).updateProductSalesLineWhenOqcFinish( | |
| 80 | + eq(sourceLineId), eq(oqc.getId()), eq(checkResult)); | |
| 81 | + } | |
| 75 | 82 | |
| 76 | - // 断言:验证回写调用 | |
| 77 | - verify(productSalesLineService).updateProductSalesLineWhenOqcFinish(eq(sourceLineId), eq(oqc.getId()), eq(checkResult)); | |
| 83 | + @Test | |
| 84 | + public void testFinishOqc_writeBack_noSourceDoc() { | |
| 85 | + // 准备数据:sourceDocType 为空,不需要回写 | |
| 86 | + MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { | |
| 87 | + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); | |
| 88 | + o.setCheckResult(1); | |
| 89 | + o.setSourceDocType(null); | |
| 90 | + o.setSourceLineId(null); | |
| 91 | + }); | |
| 92 | + oqcMapper.insert(oqc); | |
| 93 | + | |
| 94 | + // 调用 | |
| 95 | + oqcService.finishOqc(oqc.getId()); | |
| 96 | + | |
| 97 | + // 断言:验证状态更新为已完成 | |
| 98 | + MesQcOqcDO updatedOqc = oqcMapper.selectById(oqc.getId()); | |
| 99 | + assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedOqc.getStatus()); | |
| 100 | + // 断言:不应该调用任何回写 | |
| 101 | + verify(productSalesLineService, never()).updateProductSalesLineWhenOqcFinish( | |
| 102 | + anyLong(), anyLong(), anyInt()); | |
| 103 | + } | |
| 104 | + | |
| 105 | + @Test | |
| 106 | + public void testFinishOqc_writeBack_sourceDocTypeDirtyData() { | |
| 107 | + // 准备数据:sourceDocType 非空但 sourceLineId 为空,应该抛异常 | |
| 108 | + MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { | |
| 109 | + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); | |
| 110 | + o.setCheckResult(1); | |
| 111 | + o.setSourceDocType(MesBizTypeConstants.WM_PRODUCT_SALES); | |
| 112 | + o.setSourceLineId(null); // 脏数据 | |
| 113 | + }); | |
| 114 | + oqcMapper.insert(oqc); | |
| 115 | + | |
| 116 | + // 调用,并断言异常 | |
| 117 | + assertThrows(IllegalArgumentException.class, () -> oqcService.finishOqc(oqc.getId())); | |
| 118 | + } | |
| 119 | + | |
| 120 | + @Test | |
| 121 | + public void testFinishOqc_writeBack_unknownSourceDocType() { | |
| 122 | + // 准备数据:sourceDocType 无法识别 | |
| 123 | + MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { | |
| 124 | + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); | |
| 125 | + o.setCheckResult(1); | |
| 126 | + o.setSourceDocType(99999); // 无法识别的类型 | |
| 127 | + o.setSourceLineId(randomLongId()); | |
| 128 | + }); | |
| 129 | + oqcMapper.insert(oqc); | |
| 130 | + | |
| 131 | + // 调用,并断言异常 | |
| 132 | + assertThrows(IllegalArgumentException.class, () -> oqcService.finishOqc(oqc.getId())); | |
| 78 | 133 | } |
| 79 | 134 | |
| 80 | 135 | @Test |
| ... | ... | @@ -88,8 +143,7 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { |
| 88 | 143 | |
| 89 | 144 | @Test |
| 90 | 145 | public void testFinishOqc_statusNotDraft() { |
| 91 | - // 准备参数 | |
| 92 | - // mock 数据:状态为已完成 | |
| 146 | + // 准备参数:状态为已完成 | |
| 93 | 147 | MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { |
| 94 | 148 | o.setStatus(MesQcStatusEnum.FINISHED.getStatus()); |
| 95 | 149 | }); |
| ... | ... | @@ -99,4 +153,48 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { |
| 99 | 153 | assertServiceException(() -> oqcService.finishOqc(oqc.getId()), QC_OQC_NOT_PREPARE); |
| 100 | 154 | } |
| 101 | 155 | |
| 156 | + @Test | |
| 157 | + public void testFinishOqc_checkResultEmpty() { | |
| 158 | + // 准备数据:checkResult 为空,应该报错 | |
| 159 | + MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { | |
| 160 | + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); | |
| 161 | + o.setCheckResult(null); // 检测结论为空 | |
| 162 | + o.setSourceDocType(null); | |
| 163 | + }); | |
| 164 | + oqcMapper.insert(oqc); | |
| 165 | + | |
| 166 | + // 调用,应该抛异常(检测结论必填) | |
| 167 | + assertServiceException(() -> oqcService.finishOqc(oqc.getId()), QC_OQC_CHECK_RESULT_EMPTY); | |
| 168 | + } | |
| 169 | + | |
| 170 | + @Test | |
| 171 | + public void testDeleteOqc_success() { | |
| 172 | + // 准备数据:草稿状态 | |
| 173 | + MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { | |
| 174 | + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); | |
| 175 | + }); | |
| 176 | + oqcMapper.insert(oqc); | |
| 177 | + | |
| 178 | + // 调用 | |
| 179 | + oqcService.deleteOqc(oqc.getId()); | |
| 180 | + | |
| 181 | + // 断言:已删除 | |
| 182 | + assertNull(oqcMapper.selectById(oqc.getId())); | |
| 183 | + } | |
| 184 | + | |
| 185 | + @Test | |
| 186 | + public void testDeleteOqc_notExists() { | |
| 187 | + assertServiceException(() -> oqcService.deleteOqc(randomLongId()), QC_OQC_NOT_EXISTS); | |
| 188 | + } | |
| 189 | + | |
| 190 | + @Test | |
| 191 | + public void testDeleteOqc_statusNotDraft() { | |
| 192 | + MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { | |
| 193 | + o.setStatus(MesQcStatusEnum.FINISHED.getStatus()); | |
| 194 | + }); | |
| 195 | + oqcMapper.insert(oqc); | |
| 196 | + | |
| 197 | + assertServiceException(() -> oqcService.deleteOqc(oqc.getId()), QC_OQC_NOT_PREPARE); | |
| 198 | + } | |
| 199 | + | |
| 102 | 200 | } | ... | ... |
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; |
| 15 | 15 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; |
| 16 | 16 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; |
| 17 | 17 | import static org.junit.jupiter.api.Assertions.assertEquals; |
| 18 | +import static org.mockito.Mockito.*; | |
| 18 | 19 | |
| 19 | 20 | /** |
| 20 | 21 | * {@link MesWmProductSalesLineServiceImpl} 的单元测试类 |
| ... | ... | @@ -46,13 +47,16 @@ public class MesWmProductSalesLineServiceImplTest extends BaseDbUnitTest { |
| 46 | 47 | Integer checkResult = MesQcCheckResultEnum.PASS.getType(); |
| 47 | 48 | |
| 48 | 49 | // mock 数据 |
| 49 | - MesWmProductSalesLineDO lineDO = randomPojo(MesWmProductSalesLineDO.class); | |
| 50 | + MesWmProductSalesLineDO lineDO = randomPojo(MesWmProductSalesLineDO.class, o -> { | |
| 51 | + o.setOqcCheck(true); // 需要 OQC 检验 | |
| 52 | + o.setQualityStatus(null); // 初始为空 | |
| 53 | + }); | |
| 50 | 54 | productSalesLineMapper.insert(lineDO); |
| 51 | 55 | |
| 52 | 56 | // 调用 |
| 53 | 57 | productSalesLineService.updateProductSalesLineWhenOqcFinish(lineDO.getId(), oqcId, checkResult); |
| 54 | 58 | |
| 55 | - // 断言:验证 mapper 更新参数正确 | |
| 59 | + // 断言:验证行字段更新 | |
| 56 | 60 | MesWmProductSalesLineDO updateLine = productSalesLineMapper.selectById(lineDO.getId()); |
| 57 | 61 | assertEquals(oqcId, updateLine.getOqcId()); |
| 58 | 62 | assertEquals(MesWmQualityStatusEnum.PASS.getStatus(), updateLine.getQualityStatus()); |
| ... | ... | @@ -65,16 +69,97 @@ public class MesWmProductSalesLineServiceImplTest extends BaseDbUnitTest { |
| 65 | 69 | Integer checkResult = MesQcCheckResultEnum.FAIL.getType(); |
| 66 | 70 | |
| 67 | 71 | // mock 数据 |
| 68 | - MesWmProductSalesLineDO lineDO = randomPojo(MesWmProductSalesLineDO.class); | |
| 72 | + MesWmProductSalesLineDO lineDO = randomPojo(MesWmProductSalesLineDO.class, o -> { | |
| 73 | + o.setOqcCheck(true); | |
| 74 | + o.setQualityStatus(null); | |
| 75 | + }); | |
| 69 | 76 | productSalesLineMapper.insert(lineDO); |
| 70 | 77 | |
| 71 | 78 | // 调用 |
| 72 | 79 | productSalesLineService.updateProductSalesLineWhenOqcFinish(lineDO.getId(), oqcId, checkResult); |
| 73 | 80 | |
| 74 | - // 断言:验证 mapper 更新参数正确 | |
| 81 | + // 断言:验证行字段更新 | |
| 75 | 82 | MesWmProductSalesLineDO updateLine = productSalesLineMapper.selectById(lineDO.getId()); |
| 76 | 83 | assertEquals(oqcId, updateLine.getOqcId()); |
| 77 | 84 | assertEquals(MesWmQualityStatusEnum.FAIL.getStatus(), updateLine.getQualityStatus()); |
| 78 | 85 | } |
| 79 | 86 | |
| 87 | + @Test | |
| 88 | + public void testUpdateProductSalesLineWhenOqcFinish_cancelWhenNg() { | |
| 89 | + // 准备数据:同一出库单下 2 行,第 1 行已合格,第 2 行即将变更为不合格 | |
| 90 | + Long salesId = randomLongId(); | |
| 91 | + MesWmProductSalesLineDO line1 = randomPojo(MesWmProductSalesLineDO.class, o -> { | |
| 92 | + o.setSalesId(salesId); | |
| 93 | + o.setOqcCheck(true); | |
| 94 | + o.setQualityStatus(MesWmQualityStatusEnum.PASS.getStatus()); | |
| 95 | + }); | |
| 96 | + productSalesLineMapper.insert(line1); | |
| 97 | + | |
| 98 | + MesWmProductSalesLineDO line2 = randomPojo(MesWmProductSalesLineDO.class, o -> { | |
| 99 | + o.setSalesId(salesId); | |
| 100 | + o.setOqcCheck(true); | |
| 101 | + o.setQualityStatus(null); // 待检 | |
| 102 | + }); | |
| 103 | + productSalesLineMapper.insert(line2); | |
| 104 | + | |
| 105 | + // 调用:第 2 行质检不合格 | |
| 106 | + productSalesLineService.updateProductSalesLineWhenOqcFinish( | |
| 107 | + line2.getId(), randomLongId(), MesQcCheckResultEnum.FAIL.getType()); | |
| 108 | + | |
| 109 | + // 断言:应该调用 cancelProductSales | |
| 110 | + verify(productSalesService).cancelProductSales(eq(salesId)); | |
| 111 | + } | |
| 112 | + | |
| 113 | + @Test | |
| 114 | + public void testUpdateProductSalesLineWhenOqcFinish_noCancelWhenAllPass() { | |
| 115 | + // 准备数据:同一出库单下 2 行,第 1 行已合格,第 2 行即将变更为合格 | |
| 116 | + Long salesId = randomLongId(); | |
| 117 | + MesWmProductSalesLineDO line1 = randomPojo(MesWmProductSalesLineDO.class, o -> { | |
| 118 | + o.setSalesId(salesId); | |
| 119 | + o.setOqcCheck(true); | |
| 120 | + o.setQualityStatus(MesWmQualityStatusEnum.PASS.getStatus()); | |
| 121 | + }); | |
| 122 | + productSalesLineMapper.insert(line1); | |
| 123 | + | |
| 124 | + MesWmProductSalesLineDO line2 = randomPojo(MesWmProductSalesLineDO.class, o -> { | |
| 125 | + o.setSalesId(salesId); | |
| 126 | + o.setOqcCheck(true); | |
| 127 | + o.setQualityStatus(null); | |
| 128 | + }); | |
| 129 | + productSalesLineMapper.insert(line2); | |
| 130 | + | |
| 131 | + // 调用:第 2 行质检合格 | |
| 132 | + productSalesLineService.updateProductSalesLineWhenOqcFinish( | |
| 133 | + line2.getId(), randomLongId(), MesQcCheckResultEnum.PASS.getType()); | |
| 134 | + | |
| 135 | + // 断言:不应该调用 cancelProductSales | |
| 136 | + verify(productSalesService, never()).cancelProductSales(anyLong()); | |
| 137 | + } | |
| 138 | + | |
| 139 | + @Test | |
| 140 | + public void testUpdateProductSalesLineWhenOqcFinish_skipNonOqcCheckLines() { | |
| 141 | + // 准备数据:同一出库单下 2 行,第 1 行不需要 OQC 检验(oqcCheck=false),第 2 行需要检验且不合格 | |
| 142 | + Long salesId = randomLongId(); | |
| 143 | + MesWmProductSalesLineDO line1 = randomPojo(MesWmProductSalesLineDO.class, o -> { | |
| 144 | + o.setSalesId(salesId); | |
| 145 | + o.setOqcCheck(false); // 不需要 OQC 检验 | |
| 146 | + o.setQualityStatus(null); | |
| 147 | + }); | |
| 148 | + productSalesLineMapper.insert(line1); | |
| 149 | + | |
| 150 | + MesWmProductSalesLineDO line2 = randomPojo(MesWmProductSalesLineDO.class, o -> { | |
| 151 | + o.setSalesId(salesId); | |
| 152 | + o.setOqcCheck(true); | |
| 153 | + o.setQualityStatus(null); | |
| 154 | + }); | |
| 155 | + productSalesLineMapper.insert(line2); | |
| 156 | + | |
| 157 | + // 调用:第 2 行质检不合格 | |
| 158 | + productSalesLineService.updateProductSalesLineWhenOqcFinish( | |
| 159 | + line2.getId(), randomLongId(), MesQcCheckResultEnum.FAIL.getType()); | |
| 160 | + | |
| 161 | + // 断言:第 2 行不合格 → 应该取消出库单 | |
| 162 | + verify(productSalesService).cancelProductSales(eq(salesId)); | |
| 163 | + } | |
| 164 | + | |
| 80 | 165 | } | ... | ... |