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,10 +27,4 @@ public class MesQcOqcPageReqVO extends PageParam { | ||
| 27 | @Schema(description = "检测结果", example = "1") | 27 | @Schema(description = "检测结果", example = "1") |
| 28 | private Integer checkResult; | 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,6 +30,9 @@ public class MesQcOqcLineRespVO { | ||
| 30 | @Schema(description = "检测指标类型", example = "QUANTITATIVE") | 30 | @Schema(description = "检测指标类型", example = "QUANTITATIVE") |
| 31 | private String indicatorType; | 31 | private String indicatorType; |
| 32 | 32 | ||
| 33 | + @Schema(description = "检测工具", example = "卡尺") | ||
| 34 | + private String tool; | ||
| 35 | + | ||
| 33 | @Schema(description = "检测方法", example = "目测") | 36 | @Schema(description = "检测方法", example = "目测") |
| 34 | private String checkMethod; | 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,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 | private BigDecimal outQuantity; | 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,8 +22,6 @@ public interface MesQcOqcMapper extends BaseMapperX<MesQcOqcDO> { | ||
| 22 | .likeIfPresent(MesQcOqcDO::getBatchCode, reqVO.getBatchCode()) | 22 | .likeIfPresent(MesQcOqcDO::getBatchCode, reqVO.getBatchCode()) |
| 23 | .eqIfPresent(MesQcOqcDO::getItemId, reqVO.getItemId()) | 23 | .eqIfPresent(MesQcOqcDO::getItemId, reqVO.getItemId()) |
| 24 | .eqIfPresent(MesQcOqcDO::getCheckResult, reqVO.getCheckResult()) | 24 | .eqIfPresent(MesQcOqcDO::getCheckResult, reqVO.getCheckResult()) |
| 25 | - .eqIfPresent(MesQcOqcDO::getStatus, reqVO.getStatus()) | ||
| 26 | - .eqIfPresent(MesQcOqcDO::getInspectorUserId, reqVO.getInspectorUserId()) | ||
| 27 | .orderByDesc(MesQcOqcDO::getId)); | 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,17 +75,18 @@ public class MesQcOqcServiceImpl implements MesQcOqcService { | ||
| 75 | public Long createOqc(MesQcOqcSaveReqVO createReqVO) { | 75 | public Long createOqc(MesQcOqcSaveReqVO createReqVO) { |
| 76 | // 1.1 校验编号唯一 | 76 | // 1.1 校验编号唯一 |
| 77 | validateOqcCodeUnique(null, createReqVO.getCode()); | 77 | validateOqcCodeUnique(null, createReqVO.getCode()); |
| 78 | - // 1.2 校验客户、物料、检测人员存在 | ||
| 79 | - clientService.validateClientExists(createReqVO.getClientId()); | 78 | + // 1.2 校验物料、检测人员存在 |
| 80 | itemService.validateItemExists(createReqVO.getItemId()); | 79 | itemService.validateItemExists(createReqVO.getItemId()); |
| 81 | adminUserApi.validateUser(createReqVO.getInspectorUserId()); | 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 | MesQcTemplateItemDO templateItem = templateDetailService.getRequiredTemplateByItemIdAndType( | 87 | MesQcTemplateItemDO templateItem = templateDetailService.getRequiredTemplateByItemIdAndType( |
| 84 | createReqVO.getItemId(), MesQcTypeEnum.OQC.getType()); | 88 | createReqVO.getItemId(), MesQcTypeEnum.OQC.getType()); |
| 85 | Long templateId = templateItem.getTemplateId(); | 89 | Long templateId = templateItem.getTemplateId(); |
| 86 | - // 1.4 获取来源单据编号 | ||
| 87 | - String sourceDocCode = validateAndGetSourceDocCode( | ||
| 88 | - createReqVO.getSourceDocType(), createReqVO.getSourceLineId()); | ||
| 89 | 90 | ||
| 90 | // 2. 插入主表 | 91 | // 2. 插入主表 |
| 91 | MesQcOqcDO oqc = BeanUtils.toBean(createReqVO, MesQcOqcDO.class) | 92 | MesQcOqcDO oqc = BeanUtils.toBean(createReqVO, MesQcOqcDO.class) |
| @@ -137,7 +138,9 @@ public class MesQcOqcServiceImpl implements MesQcOqcService { | @@ -137,7 +138,9 @@ public class MesQcOqcServiceImpl implements MesQcOqcService { | ||
| 137 | /** | 138 | /** |
| 138 | * 回写来源单据(OQC 完成后) | 139 | * 回写来源单据(OQC 完成后) |
| 139 | * | 140 | * |
| 140 | - * <p>当来源为销售出库单时,回写行的 oqcId 和 qualityStatus | 141 | + * <p>当来源为销售出库单时,调用 |
| 142 | + * {@link MesWmProductSalesLineService#updateProductSalesLineWhenOqcFinish} | ||
| 143 | + * 回写行的 oqcId、qualityStatus,并由该方法内部联动检查出库单整体状态 | ||
| 141 | * | 144 | * |
| 142 | * @param oqc 出货检验单 | 145 | * @param oqc 出货检验单 |
| 143 | */ | 146 | */ |
| @@ -151,8 +154,8 @@ public class MesQcOqcServiceImpl implements MesQcOqcService { | @@ -151,8 +154,8 @@ public class MesQcOqcServiceImpl implements MesQcOqcService { | ||
| 151 | } | 154 | } |
| 152 | 155 | ||
| 153 | if (Objects.equals(oqc.getSourceDocType(), MesBizTypeConstants.WM_PRODUCT_SALES)) { | 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 | } else { | 159 | } else { |
| 157 | throw new IllegalArgumentException( | 160 | throw new IllegalArgumentException( |
| 158 | "OQC 单[" + oqc.getId() + "] sourceDocType=" + oqc.getSourceDocType() + " 无法识别,无法回写来源单据"); | 161 | "OQC 单[" + oqc.getId() + "] sourceDocType=" + oqc.getSourceDocType() + " 无法识别,无法回写来源单据"); |
| @@ -203,6 +206,13 @@ public class MesQcOqcServiceImpl implements MesQcOqcService { | @@ -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 | private String validateAndGetSourceDocCode(Integer sourceDocType, Long sourceLineId) { | 216 | private String validateAndGetSourceDocCode(Integer sourceDocType, Long sourceLineId) { |
| 207 | if (sourceDocType == null || sourceLineId == null) { | 217 | if (sourceDocType == null || sourceLineId == null) { |
| 208 | return null; | 218 | return null; |
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java
| 1 | package cn.iocoder.yudao.module.mes.service.wm.productsales; | 1 | package cn.iocoder.yudao.module.mes.service.wm.productsales; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | ||
| 3 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 4 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 4 | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.line.MesWmProductSalesLinePageReqVO; | 5 | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.line.MesWmProductSalesLinePageReqVO; |
| 5 | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.line.MesWmProductSalesLineSaveReqVO; | 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,6 +10,8 @@ import cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum; | ||
| 9 | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; | 10 | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; |
| 10 | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; | 11 | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| 11 | import jakarta.annotation.Resource; | 12 | import jakarta.annotation.Resource; |
| 13 | +import lombok.extern.slf4j.Slf4j; | ||
| 14 | +import org.springframework.context.annotation.Lazy; | ||
| 12 | import org.springframework.stereotype.Service; | 15 | import org.springframework.stereotype.Service; |
| 13 | import org.springframework.transaction.annotation.Transactional; | 16 | import org.springframework.transaction.annotation.Transactional; |
| 14 | import org.springframework.validation.annotation.Validated; | 17 | import org.springframework.validation.annotation.Validated; |
| @@ -26,6 +29,7 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_PRODUCT_SA | @@ -26,6 +29,7 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_PRODUCT_SA | ||
| 26 | */ | 29 | */ |
| 27 | @Service | 30 | @Service |
| 28 | @Validated | 31 | @Validated |
| 32 | +@Slf4j | ||
| 29 | public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineService { | 33 | public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineService { |
| 30 | 34 | ||
| 31 | @Resource | 35 | @Resource |
| @@ -34,6 +38,9 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe | @@ -34,6 +38,9 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe | ||
| 34 | private MesWmProductSalesDetailService productSalesDetailService; | 38 | private MesWmProductSalesDetailService productSalesDetailService; |
| 35 | @Resource | 39 | @Resource |
| 36 | private MesMdItemService itemService; | 40 | private MesMdItemService itemService; |
| 41 | + @Resource | ||
| 42 | + @Lazy | ||
| 43 | + private MesWmProductSalesService productSalesService; | ||
| 37 | 44 | ||
| 38 | @Override | 45 | @Override |
| 39 | public Long createProductSalesLine(MesWmProductSalesLineSaveReqVO createReqVO) { | 46 | public Long createProductSalesLine(MesWmProductSalesLineSaveReqVO createReqVO) { |
| @@ -100,11 +107,29 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe | @@ -100,11 +107,29 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe | ||
| 100 | } | 107 | } |
| 101 | 108 | ||
| 102 | @Override | 109 | @Override |
| 110 | + @Transactional(rollbackFor = Exception.class) | ||
| 103 | public void updateProductSalesLineWhenOqcFinish(Long id, Long oqcId, Integer checkResult) { | 111 | public void updateProductSalesLineWhenOqcFinish(Long id, Long oqcId, Integer checkResult) { |
| 112 | + // 1. 更新行的 oqcId + qualityStatus | ||
| 104 | Integer qualityStatus = Objects.equals(checkResult, MesQcCheckResultEnum.PASS.getType()) | 113 | Integer qualityStatus = Objects.equals(checkResult, MesQcCheckResultEnum.PASS.getType()) |
| 105 | ? MesWmQualityStatusEnum.PASS.getStatus() : MesWmQualityStatusEnum.FAIL.getStatus(); | 114 | ? MesWmQualityStatusEnum.PASS.getStatus() : MesWmQualityStatusEnum.FAIL.getStatus(); |
| 106 | productSalesLineMapper.updateById(new MesWmProductSalesLineDO() | 115 | productSalesLineMapper.updateById(new MesWmProductSalesLineDO() |
| 107 | .setId(id).setOqcId(oqcId).setQualityStatus(qualityStatus)); | 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,10 +5,12 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO; | ||
| 5 | import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcMapper; | 5 | import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcMapper; |
| 6 | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; | 6 | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| 7 | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; | 7 | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| 8 | +import cn.iocoder.yudao.module.mes.service.md.client.MesMdClientService; | ||
| 8 | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; | 9 | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| 9 | import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService; | 10 | import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService; |
| 10 | import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateDetailService; | 11 | import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateDetailService; |
| 11 | import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesLineService; | 12 | import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesLineService; |
| 13 | +import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesService; | ||
| 12 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | 14 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 13 | import jakarta.annotation.Resource; | 15 | import jakarta.annotation.Resource; |
| 14 | import org.junit.jupiter.api.Test; | 16 | import org.junit.jupiter.api.Test; |
| @@ -18,11 +20,10 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; | @@ -18,11 +20,10 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; | ||
| 18 | import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; | 20 | import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; |
| 19 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; | 21 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; |
| 20 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | 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 | import static org.mockito.ArgumentMatchers.eq; | 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 | * {@link MesQcOqcServiceImpl} 的单元测试类 | 29 | * {@link MesQcOqcServiceImpl} 的单元测试类 |
| @@ -41,8 +42,12 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { | @@ -41,8 +42,12 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { | ||
| 41 | @MockitoBean | 42 | @MockitoBean |
| 42 | private MesWmProductSalesLineService productSalesLineService; | 43 | private MesWmProductSalesLineService productSalesLineService; |
| 43 | @MockitoBean | 44 | @MockitoBean |
| 45 | + private MesWmProductSalesService productSalesService; | ||
| 46 | + @MockitoBean | ||
| 44 | private MesQcOqcLineService oqcLineService; | 47 | private MesQcOqcLineService oqcLineService; |
| 45 | @MockitoBean | 48 | @MockitoBean |
| 49 | + private MesMdClientService clientService; | ||
| 50 | + @MockitoBean | ||
| 46 | private MesMdItemService itemService; | 51 | private MesMdItemService itemService; |
| 47 | @MockitoBean | 52 | @MockitoBean |
| 48 | private MesQcTemplateDetailService templateDetailService; | 53 | private MesQcTemplateDetailService templateDetailService; |
| @@ -52,12 +57,10 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { | @@ -52,12 +57,10 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { | ||
| 52 | private AdminUserApi adminUserApi; | 57 | private AdminUserApi adminUserApi; |
| 53 | 58 | ||
| 54 | @Test | 59 | @Test |
| 55 | - public void testFinishOqc_success() { | ||
| 56 | - // 准备参数 | 60 | + public void testFinishOqc_writeBack_productSales() { |
| 61 | + // 准备数据:插入一条草稿状态的 OQC 单,来源为销售出库单 | ||
| 57 | Long sourceLineId = randomLongId(); | 62 | Long sourceLineId = randomLongId(); |
| 58 | Integer checkResult = 1; | 63 | Integer checkResult = 1; |
| 59 | - | ||
| 60 | - // mock 数据 | ||
| 61 | MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { | 64 | MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { |
| 62 | o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); | 65 | o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); |
| 63 | o.setSourceDocType(MesBizTypeConstants.WM_PRODUCT_SALES); | 66 | o.setSourceDocType(MesBizTypeConstants.WM_PRODUCT_SALES); |
| @@ -69,12 +72,64 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { | @@ -69,12 +72,64 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { | ||
| 69 | // 调用 | 72 | // 调用 |
| 70 | oqcService.finishOqc(oqc.getId()); | 73 | oqcService.finishOqc(oqc.getId()); |
| 71 | 74 | ||
| 72 | - // 断言:验证状态更新 | 75 | + // 断言:验证状态更新为已完成 |
| 73 | MesQcOqcDO updatedOqc = oqcMapper.selectById(oqc.getId()); | 76 | MesQcOqcDO updatedOqc = oqcMapper.selectById(oqc.getId()); |
| 74 | assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedOqc.getStatus()); | 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 | @Test | 135 | @Test |
| @@ -88,8 +143,7 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { | @@ -88,8 +143,7 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { | ||
| 88 | 143 | ||
| 89 | @Test | 144 | @Test |
| 90 | public void testFinishOqc_statusNotDraft() { | 145 | public void testFinishOqc_statusNotDraft() { |
| 91 | - // 准备参数 | ||
| 92 | - // mock 数据:状态为已完成 | 146 | + // 准备参数:状态为已完成 |
| 93 | MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { | 147 | MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { |
| 94 | o.setStatus(MesQcStatusEnum.FINISHED.getStatus()); | 148 | o.setStatus(MesQcStatusEnum.FINISHED.getStatus()); |
| 95 | }); | 149 | }); |
| @@ -99,4 +153,48 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { | @@ -99,4 +153,48 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest { | ||
| 99 | assertServiceException(() -> oqcService.finishOqc(oqc.getId()), QC_OQC_NOT_PREPARE); | 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,6 +15,7 @@ import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; | ||
| 15 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; | 15 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; |
| 16 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | 16 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; |
| 17 | import static org.junit.jupiter.api.Assertions.assertEquals; | 17 | import static org.junit.jupiter.api.Assertions.assertEquals; |
| 18 | +import static org.mockito.Mockito.*; | ||
| 18 | 19 | ||
| 19 | /** | 20 | /** |
| 20 | * {@link MesWmProductSalesLineServiceImpl} 的单元测试类 | 21 | * {@link MesWmProductSalesLineServiceImpl} 的单元测试类 |
| @@ -46,13 +47,16 @@ public class MesWmProductSalesLineServiceImplTest extends BaseDbUnitTest { | @@ -46,13 +47,16 @@ public class MesWmProductSalesLineServiceImplTest extends BaseDbUnitTest { | ||
| 46 | Integer checkResult = MesQcCheckResultEnum.PASS.getType(); | 47 | Integer checkResult = MesQcCheckResultEnum.PASS.getType(); |
| 47 | 48 | ||
| 48 | // mock 数据 | 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 | productSalesLineMapper.insert(lineDO); | 54 | productSalesLineMapper.insert(lineDO); |
| 51 | 55 | ||
| 52 | // 调用 | 56 | // 调用 |
| 53 | productSalesLineService.updateProductSalesLineWhenOqcFinish(lineDO.getId(), oqcId, checkResult); | 57 | productSalesLineService.updateProductSalesLineWhenOqcFinish(lineDO.getId(), oqcId, checkResult); |
| 54 | 58 | ||
| 55 | - // 断言:验证 mapper 更新参数正确 | 59 | + // 断言:验证行字段更新 |
| 56 | MesWmProductSalesLineDO updateLine = productSalesLineMapper.selectById(lineDO.getId()); | 60 | MesWmProductSalesLineDO updateLine = productSalesLineMapper.selectById(lineDO.getId()); |
| 57 | assertEquals(oqcId, updateLine.getOqcId()); | 61 | assertEquals(oqcId, updateLine.getOqcId()); |
| 58 | assertEquals(MesWmQualityStatusEnum.PASS.getStatus(), updateLine.getQualityStatus()); | 62 | assertEquals(MesWmQualityStatusEnum.PASS.getStatus(), updateLine.getQualityStatus()); |
| @@ -65,16 +69,97 @@ public class MesWmProductSalesLineServiceImplTest extends BaseDbUnitTest { | @@ -65,16 +69,97 @@ public class MesWmProductSalesLineServiceImplTest extends BaseDbUnitTest { | ||
| 65 | Integer checkResult = MesQcCheckResultEnum.FAIL.getType(); | 69 | Integer checkResult = MesQcCheckResultEnum.FAIL.getType(); |
| 66 | 70 | ||
| 67 | // mock 数据 | 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 | productSalesLineMapper.insert(lineDO); | 76 | productSalesLineMapper.insert(lineDO); |
| 70 | 77 | ||
| 71 | // 调用 | 78 | // 调用 |
| 72 | productSalesLineService.updateProductSalesLineWhenOqcFinish(lineDO.getId(), oqcId, checkResult); | 79 | productSalesLineService.updateProductSalesLineWhenOqcFinish(lineDO.getId(), oqcId, checkResult); |
| 73 | 80 | ||
| 74 | - // 断言:验证 mapper 更新参数正确 | 81 | + // 断言:验证行字段更新 |
| 75 | MesWmProductSalesLineDO updateLine = productSalesLineMapper.selectById(lineDO.getId()); | 82 | MesWmProductSalesLineDO updateLine = productSalesLineMapper.selectById(lineDO.getId()); |
| 76 | assertEquals(oqcId, updateLine.getOqcId()); | 83 | assertEquals(oqcId, updateLine.getOqcId()); |
| 77 | assertEquals(MesWmQualityStatusEnum.FAIL.getStatus(), updateLine.getQualityStatus()); | 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 | } |