Commit dbccfbab8e7448b2fd8ff68f520d20e849e766d9
1 parent
2e113305
✅ test(iqc): 添加 IQC 服务单元测试,验证状态更新逻辑
新增 IQC 服务的单元测试,确保在完成 IQC 时正确更新状态并回写相关单据。测试覆盖了不同来源的 IQC 单,包括到货通知单和外协入库单,以及处理无来源单据和脏数据的情况。
Showing
5 changed files
with
603 additions
and
0 deletions
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImplTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mes.service.qc.iqc; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; | ||
| 4 | +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO; | ||
| 5 | +import cn.iocoder.yudao.module.mes.dal.mysql.qc.iqc.MesQcIqcMapper; | ||
| 6 | +import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; | ||
| 7 | +import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; | ||
| 8 | +import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; | ||
| 9 | +import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService; | ||
| 10 | +import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService; | ||
| 11 | +import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateDetailService; | ||
| 12 | +import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService; | ||
| 13 | +import cn.iocoder.yudao.module.mes.service.wm.outsourcereceipt.MesWmOutsourceReceiptService; | ||
| 14 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | ||
| 15 | +import org.junit.jupiter.api.Test; | ||
| 16 | +import org.springframework.context.annotation.Import; | ||
| 17 | +import org.springframework.test.context.bean.override.mockito.MockitoBean; | ||
| 18 | + | ||
| 19 | +import jakarta.annotation.Resource; | ||
| 20 | + | ||
| 21 | +import java.math.BigDecimal; | ||
| 22 | + | ||
| 23 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; | ||
| 24 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | ||
| 25 | +import static org.junit.jupiter.api.Assertions.*; | ||
| 26 | +import static org.mockito.Mockito.*; | ||
| 27 | + | ||
| 28 | +/** | ||
| 29 | + * {@link MesQcIqcServiceImpl} 的单元测试 | ||
| 30 | + * | ||
| 31 | + * @author 芋道源码 | ||
| 32 | + */ | ||
| 33 | +@Import(MesQcIqcServiceImpl.class) | ||
| 34 | +public class MesQcIqcServiceImplTest extends BaseDbUnitTest { | ||
| 35 | + | ||
| 36 | + @Resource | ||
| 37 | + private MesQcIqcServiceImpl iqcService; | ||
| 38 | + | ||
| 39 | + @Resource | ||
| 40 | + private MesQcIqcMapper iqcMapper; | ||
| 41 | + | ||
| 42 | + @MockitoBean | ||
| 43 | + private MesWmArrivalNoticeService arrivalNoticeService; | ||
| 44 | + @MockitoBean | ||
| 45 | + private MesWmOutsourceReceiptService outsourceReceiptService; | ||
| 46 | + @MockitoBean | ||
| 47 | + private MesQcIqcLineService iqcLineService; | ||
| 48 | + @MockitoBean | ||
| 49 | + private MesQcDefectRecordService defectRecordService; | ||
| 50 | + @MockitoBean | ||
| 51 | + private MesMdVendorService vendorService; | ||
| 52 | + @MockitoBean | ||
| 53 | + private MesMdItemService itemService; | ||
| 54 | + @MockitoBean | ||
| 55 | + private MesQcTemplateDetailService templateDetailService; | ||
| 56 | + @MockitoBean | ||
| 57 | + private AdminUserApi adminUserApi; | ||
| 58 | + | ||
| 59 | + @Test | ||
| 60 | + public void testFinishIqc_writeBack_arrivalNotice() { | ||
| 61 | + // mock 数据:插入一条草稿状态的 IQC 单,来源为到货通知单 | ||
| 62 | + Long sourceDocId = randomLongId(); | ||
| 63 | + Long sourceLineId = randomLongId(); | ||
| 64 | + MesQcIqcDO iqc = randomPojo(MesQcIqcDO.class, o -> { | ||
| 65 | + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); | ||
| 66 | + o.setCheckResult(1); // 非空,满足 finishIqc 的校验 | ||
| 67 | + o.setSourceDocType(MesBizTypeConstants.WM_ARRIVAL_NOTICE); | ||
| 68 | + o.setSourceDocId(sourceDocId); | ||
| 69 | + o.setSourceLineId(sourceLineId); | ||
| 70 | + o.setQualifiedQuantity(BigDecimal.valueOf(100)); | ||
| 71 | + o.setUnqualifiedQuantity(BigDecimal.ZERO); | ||
| 72 | + }); | ||
| 73 | + iqcMapper.insert(iqc); | ||
| 74 | + | ||
| 75 | + // 调用 | ||
| 76 | + iqcService.finishIqc(iqc.getId()); | ||
| 77 | + | ||
| 78 | + // 断言:验证状态更新为已完成 | ||
| 79 | + MesQcIqcDO updatedIqc = iqcMapper.selectById(iqc.getId()); | ||
| 80 | + assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedIqc.getStatus()); | ||
| 81 | + // 断言:验证回写到货通知单(参数使用 any 避免 BigDecimal 精度问题) | ||
| 82 | + verify(arrivalNoticeService).approveArrivalNoticeWhenIqcComplete( | ||
| 83 | + eq(sourceDocId), eq(sourceLineId), eq(iqc.getId()), any(BigDecimal.class)); | ||
| 84 | + // 断言:不应该调用外协入库单的回写 | ||
| 85 | + verify(outsourceReceiptService, never()).approveOutsourceReceiptWhenIqcComplete( | ||
| 86 | + anyLong(), anyLong(), anyLong(), any(), any()); | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + @Test | ||
| 90 | + public void testFinishIqc_writeBack_outsourceReceipt() { | ||
| 91 | + // mock 数据:插入一条草稿状态的 IQC 单,来源为外协入库单 | ||
| 92 | + Long sourceDocId = randomLongId(); | ||
| 93 | + Long sourceLineId = randomLongId(); | ||
| 94 | + MesQcIqcDO iqc = randomPojo(MesQcIqcDO.class, o -> { | ||
| 95 | + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); | ||
| 96 | + o.setCheckResult(1); | ||
| 97 | + o.setSourceDocType(MesBizTypeConstants.WM_OUTSOURCE_RECPT); | ||
| 98 | + o.setSourceDocId(sourceDocId); | ||
| 99 | + o.setSourceLineId(sourceLineId); | ||
| 100 | + o.setQualifiedQuantity(BigDecimal.valueOf(80)); | ||
| 101 | + o.setUnqualifiedQuantity(BigDecimal.valueOf(20)); | ||
| 102 | + }); | ||
| 103 | + iqcMapper.insert(iqc); | ||
| 104 | + | ||
| 105 | + // 调用 | ||
| 106 | + iqcService.finishIqc(iqc.getId()); | ||
| 107 | + | ||
| 108 | + // 断言:验证状态更新为已完成 | ||
| 109 | + MesQcIqcDO updatedIqc = iqcMapper.selectById(iqc.getId()); | ||
| 110 | + assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedIqc.getStatus()); | ||
| 111 | + // 断言:验证回写外协入库单(参数使用 any 避免 BigDecimal 精度问题) | ||
| 112 | + verify(outsourceReceiptService).approveOutsourceReceiptWhenIqcComplete( | ||
| 113 | + eq(sourceDocId), eq(sourceLineId), eq(iqc.getId()), any(BigDecimal.class), any(BigDecimal.class)); | ||
| 114 | + // 断言:不应该调用到货通知单的回写 | ||
| 115 | + verify(arrivalNoticeService, never()).approveArrivalNoticeWhenIqcComplete( | ||
| 116 | + anyLong(), anyLong(), anyLong(), any()); | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + @Test | ||
| 120 | + public void testFinishIqc_writeBack_noSourceDoc() { | ||
| 121 | + // mock 数据:sourceDocType 为空,不需要回写 | ||
| 122 | + MesQcIqcDO iqc = randomPojo(MesQcIqcDO.class, o -> { | ||
| 123 | + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); | ||
| 124 | + o.setCheckResult(1); | ||
| 125 | + o.setSourceDocType(null); | ||
| 126 | + o.setSourceDocId(null); | ||
| 127 | + o.setSourceLineId(null); | ||
| 128 | + }); | ||
| 129 | + iqcMapper.insert(iqc); | ||
| 130 | + | ||
| 131 | + // 调用 | ||
| 132 | + iqcService.finishIqc(iqc.getId()); | ||
| 133 | + | ||
| 134 | + // 断言:验证状态更新为已完成 | ||
| 135 | + MesQcIqcDO updatedIqc = iqcMapper.selectById(iqc.getId()); | ||
| 136 | + assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedIqc.getStatus()); | ||
| 137 | + // 断言:不应该调用任何回写 | ||
| 138 | + verify(arrivalNoticeService, never()).approveArrivalNoticeWhenIqcComplete( | ||
| 139 | + anyLong(), anyLong(), anyLong(), any()); | ||
| 140 | + verify(outsourceReceiptService, never()).approveOutsourceReceiptWhenIqcComplete( | ||
| 141 | + anyLong(), anyLong(), anyLong(), any(), any()); | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + @Test | ||
| 145 | + public void testFinishIqc_writeBack_sourceDocTypeDirtyData() { | ||
| 146 | + // mock 数据:sourceDocType 非空但 sourceLineId 为空,应该抛异常 | ||
| 147 | + MesQcIqcDO iqc = randomPojo(MesQcIqcDO.class, o -> { | ||
| 148 | + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); | ||
| 149 | + o.setCheckResult(1); | ||
| 150 | + o.setSourceDocType(MesBizTypeConstants.WM_ARRIVAL_NOTICE); | ||
| 151 | + o.setSourceDocId(randomLongId()); | ||
| 152 | + o.setSourceLineId(null); // 脏数据 | ||
| 153 | + }); | ||
| 154 | + iqcMapper.insert(iqc); | ||
| 155 | + | ||
| 156 | + // 调用,并断言异常 | ||
| 157 | + assertThrows(IllegalArgumentException.class, () -> iqcService.finishIqc(iqc.getId())); | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + @Test | ||
| 161 | + public void testFinishIqc_writeBack_unknownSourceDocType() { | ||
| 162 | + // mock 数据:sourceDocType 无法识别 | ||
| 163 | + MesQcIqcDO iqc = randomPojo(MesQcIqcDO.class, o -> { | ||
| 164 | + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); | ||
| 165 | + o.setCheckResult(1); | ||
| 166 | + o.setSourceDocType(99999); // 无法识别的类型 | ||
| 167 | + o.setSourceDocId(randomLongId()); | ||
| 168 | + o.setSourceLineId(randomLongId()); | ||
| 169 | + }); | ||
| 170 | + iqcMapper.insert(iqc); | ||
| 171 | + | ||
| 172 | + // 调用,并断言异常 | ||
| 173 | + assertThrows(IllegalArgumentException.class, () -> iqcService.finishIqc(iqc.getId())); | ||
| 174 | + } | ||
| 175 | + | ||
| 176 | +} |
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImplTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mes.service.wm.arrivalnotice; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; | ||
| 4 | +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO; | ||
| 5 | +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO; | ||
| 6 | +import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeMapper; | ||
| 7 | +import cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum; | ||
| 8 | +import org.junit.jupiter.api.Test; | ||
| 9 | +import org.springframework.context.annotation.Import; | ||
| 10 | +import org.springframework.test.context.bean.override.mockito.MockitoBean; | ||
| 11 | + | ||
| 12 | +import jakarta.annotation.Resource; | ||
| 13 | + | ||
| 14 | +import java.math.BigDecimal; | ||
| 15 | +import java.util.Arrays; | ||
| 16 | + | ||
| 17 | +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; | ||
| 18 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; | ||
| 19 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | ||
| 20 | +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; | ||
| 21 | +import static org.junit.jupiter.api.Assertions.*; | ||
| 22 | +import static org.mockito.ArgumentMatchers.eq; | ||
| 23 | +import static org.mockito.Mockito.*; | ||
| 24 | + | ||
| 25 | +/** | ||
| 26 | + * {@link MesWmArrivalNoticeServiceImpl#approveArrivalNoticeWhenIqcComplete} 的单元测试 | ||
| 27 | + * | ||
| 28 | + * @author 芋道源码 | ||
| 29 | + */ | ||
| 30 | +@Import(MesWmArrivalNoticeServiceImpl.class) | ||
| 31 | +public class MesWmArrivalNoticeServiceImplTest extends BaseDbUnitTest { | ||
| 32 | + | ||
| 33 | + @Resource | ||
| 34 | + private MesWmArrivalNoticeServiceImpl arrivalNoticeService; | ||
| 35 | + | ||
| 36 | + @Resource | ||
| 37 | + private MesWmArrivalNoticeMapper arrivalNoticeMapper; | ||
| 38 | + | ||
| 39 | + @MockitoBean | ||
| 40 | + private MesWmArrivalNoticeLineService arrivalNoticeLineService; | ||
| 41 | + | ||
| 42 | + @Test | ||
| 43 | + public void testApproveArrivalNoticeWhenIqcComplete_success_allLinesChecked() { | ||
| 44 | + // mock 数据:插入一条待质检的到货通知单 | ||
| 45 | + MesWmArrivalNoticeDO notice = randomPojo(MesWmArrivalNoticeDO.class, o -> { | ||
| 46 | + o.setStatus(MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus()); | ||
| 47 | + }); | ||
| 48 | + arrivalNoticeMapper.insert(notice); | ||
| 49 | + // 准备参数 | ||
| 50 | + Long noticeId = notice.getId(); | ||
| 51 | + Long lineId = randomLongId(); | ||
| 52 | + Long iqcId = randomLongId(); | ||
| 53 | + BigDecimal qualifiedQuantity = BigDecimal.valueOf(100); | ||
| 54 | + | ||
| 55 | + // mock 行服务方法:所有需检行都已完成(iqcId 不为空) | ||
| 56 | + MesWmArrivalNoticeLineDO line1 = randomPojo(MesWmArrivalNoticeLineDO.class, o -> { | ||
| 57 | + o.setNoticeId(noticeId); | ||
| 58 | + o.setIqcCheckFlag(true); | ||
| 59 | + o.setIqcId(iqcId); // 已检完 | ||
| 60 | + }); | ||
| 61 | + MesWmArrivalNoticeLineDO line2 = randomPojo(MesWmArrivalNoticeLineDO.class, o -> { | ||
| 62 | + o.setNoticeId(noticeId); | ||
| 63 | + o.setIqcCheckFlag(false); // 不需要检验 | ||
| 64 | + o.setIqcId(null); | ||
| 65 | + }); | ||
| 66 | + when(arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(eq(noticeId))) | ||
| 67 | + .thenReturn(Arrays.asList(line1, line2)); | ||
| 68 | + | ||
| 69 | + // 调用 | ||
| 70 | + arrivalNoticeService.approveArrivalNoticeWhenIqcComplete(noticeId, lineId, iqcId, qualifiedQuantity); | ||
| 71 | + | ||
| 72 | + // 断言:行回写被调用 | ||
| 73 | + verify(arrivalNoticeLineService).updateByIqcComplete(eq(lineId), eq(iqcId), eq(qualifiedQuantity)); | ||
| 74 | + // 断言:主表状态更新为待入库 | ||
| 75 | + MesWmArrivalNoticeDO updatedNotice = arrivalNoticeMapper.selectById(noticeId); | ||
| 76 | + assertEquals(MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT.getStatus(), updatedNotice.getStatus()); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + @Test | ||
| 80 | + public void testApproveArrivalNoticeWhenIqcComplete_success_hasUncheckedLines() { | ||
| 81 | + // mock 数据:插入一条待质检的到货通知单 | ||
| 82 | + MesWmArrivalNoticeDO notice = randomPojo(MesWmArrivalNoticeDO.class, o -> { | ||
| 83 | + o.setStatus(MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus()); | ||
| 84 | + }); | ||
| 85 | + arrivalNoticeMapper.insert(notice); | ||
| 86 | + // 准备参数 | ||
| 87 | + Long noticeId = notice.getId(); | ||
| 88 | + Long lineId = randomLongId(); | ||
| 89 | + Long iqcId = randomLongId(); | ||
| 90 | + BigDecimal qualifiedQuantity = BigDecimal.valueOf(50); | ||
| 91 | + | ||
| 92 | + // mock 行服务方法:还有未完成的需检行 | ||
| 93 | + MesWmArrivalNoticeLineDO line1 = randomPojo(MesWmArrivalNoticeLineDO.class, o -> { | ||
| 94 | + o.setNoticeId(noticeId); | ||
| 95 | + o.setIqcCheckFlag(true); | ||
| 96 | + o.setIqcId(iqcId); // 已检完 | ||
| 97 | + }); | ||
| 98 | + MesWmArrivalNoticeLineDO line2 = randomPojo(MesWmArrivalNoticeLineDO.class, o -> { | ||
| 99 | + o.setNoticeId(noticeId); | ||
| 100 | + o.setIqcCheckFlag(true); | ||
| 101 | + o.setIqcId(null); // 还未检验 | ||
| 102 | + }); | ||
| 103 | + when(arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(eq(noticeId))) | ||
| 104 | + .thenReturn(Arrays.asList(line1, line2)); | ||
| 105 | + | ||
| 106 | + // 调用 | ||
| 107 | + arrivalNoticeService.approveArrivalNoticeWhenIqcComplete(noticeId, lineId, iqcId, qualifiedQuantity); | ||
| 108 | + | ||
| 109 | + // 断言:行回写被调用 | ||
| 110 | + verify(arrivalNoticeLineService).updateByIqcComplete(eq(lineId), eq(iqcId), eq(qualifiedQuantity)); | ||
| 111 | + // 断言:主表状态不变,仍然是待质检 | ||
| 112 | + MesWmArrivalNoticeDO updatedNotice = arrivalNoticeMapper.selectById(noticeId); | ||
| 113 | + assertEquals(MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus(), updatedNotice.getStatus()); | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + @Test | ||
| 117 | + public void testApproveArrivalNoticeWhenIqcComplete_noticeNotExists() { | ||
| 118 | + // 准备参数 | ||
| 119 | + Long noticeId = randomLongId(); | ||
| 120 | + Long lineId = randomLongId(); | ||
| 121 | + Long iqcId = randomLongId(); | ||
| 122 | + BigDecimal qualifiedQuantity = BigDecimal.valueOf(100); | ||
| 123 | + | ||
| 124 | + // 调用,并断言异常 | ||
| 125 | + assertServiceException( | ||
| 126 | + () -> arrivalNoticeService.approveArrivalNoticeWhenIqcComplete(noticeId, lineId, iqcId, qualifiedQuantity), | ||
| 127 | + WM_ARRIVAL_NOTICE_NOT_EXISTS); | ||
| 128 | + } | ||
| 129 | + | ||
| 130 | + @Test | ||
| 131 | + public void testApproveArrivalNoticeWhenIqcComplete_statusNotPendingQc() { | ||
| 132 | + // mock 数据:插入一条草稿状态的到货通知单(非待质检) | ||
| 133 | + MesWmArrivalNoticeDO notice = randomPojo(MesWmArrivalNoticeDO.class, o -> { | ||
| 134 | + o.setStatus(MesWmArrivalNoticeStatusEnum.PREPARE.getStatus()); | ||
| 135 | + }); | ||
| 136 | + arrivalNoticeMapper.insert(notice); | ||
| 137 | + // 准备参数 | ||
| 138 | + Long noticeId = notice.getId(); | ||
| 139 | + Long lineId = randomLongId(); | ||
| 140 | + Long iqcId = randomLongId(); | ||
| 141 | + BigDecimal qualifiedQuantity = BigDecimal.valueOf(100); | ||
| 142 | + | ||
| 143 | + // 调用,并断言异常 | ||
| 144 | + assertServiceException( | ||
| 145 | + () -> arrivalNoticeService.approveArrivalNoticeWhenIqcComplete(noticeId, lineId, iqcId, qualifiedQuantity), | ||
| 146 | + WM_ARRIVAL_NOTICE_STATUS_NOT_PENDING_QC); | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | +} |
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptServiceImplTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mes.service.wm.outsourcereceipt; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; | ||
| 4 | +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.outsourcereceipt.MesWmOutsourceReceiptDO; | ||
| 5 | +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.outsourcereceipt.MesWmOutsourceReceiptLineDO; | ||
| 6 | +import cn.iocoder.yudao.module.mes.dal.mysql.wm.outsourcereceipt.MesWmOutsourceReceiptMapper; | ||
| 7 | +import cn.iocoder.yudao.module.mes.enums.wm.MesWmOutsourceReceiptStatusEnum; | ||
| 8 | +import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; | ||
| 9 | +import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; | ||
| 10 | +import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService; | ||
| 11 | +import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; | ||
| 12 | +import org.junit.jupiter.api.Test; | ||
| 13 | +import org.springframework.context.annotation.Import; | ||
| 14 | +import org.springframework.test.context.bean.override.mockito.MockitoBean; | ||
| 15 | + | ||
| 16 | +import jakarta.annotation.Resource; | ||
| 17 | + | ||
| 18 | +import java.math.BigDecimal; | ||
| 19 | + | ||
| 20 | +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; | ||
| 21 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; | ||
| 22 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | ||
| 23 | +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; | ||
| 24 | +import static org.junit.jupiter.api.Assertions.*; | ||
| 25 | +import static org.mockito.ArgumentMatchers.*; | ||
| 26 | +import static org.mockito.Mockito.*; | ||
| 27 | + | ||
| 28 | +/** | ||
| 29 | + * {@link MesWmOutsourceReceiptServiceImpl#approveOutsourceReceiptWhenIqcComplete} 的单元测试 | ||
| 30 | + * | ||
| 31 | + * @author 芋道源码 | ||
| 32 | + */ | ||
| 33 | +@Import(MesWmOutsourceReceiptServiceImpl.class) | ||
| 34 | +public class MesWmOutsourceReceiptServiceImplTest extends BaseDbUnitTest { | ||
| 35 | + | ||
| 36 | + @Resource | ||
| 37 | + private MesWmOutsourceReceiptServiceImpl outsourceReceiptService; | ||
| 38 | + | ||
| 39 | + @Resource | ||
| 40 | + private MesWmOutsourceReceiptMapper outsourceReceiptMapper; | ||
| 41 | + | ||
| 42 | + @MockitoBean | ||
| 43 | + private MesWmOutsourceReceiptLineService outsourceReceiptLineService; | ||
| 44 | + @MockitoBean | ||
| 45 | + private MesWmOutsourceReceiptDetailService outsourceReceiptDetailService; | ||
| 46 | + @MockitoBean | ||
| 47 | + private MesWmTransactionService wmTransactionService; | ||
| 48 | + @MockitoBean | ||
| 49 | + private MesMdVendorService vendorService; | ||
| 50 | + @MockitoBean | ||
| 51 | + private MesMdItemService itemService; | ||
| 52 | + | ||
| 53 | + @Test | ||
| 54 | + public void testApproveOutsourceReceiptWhenIqcComplete_success_bothQualifiedAndUnqualified() { | ||
| 55 | + // mock 数据:插入一条待检验状态的外协入库单 | ||
| 56 | + MesWmOutsourceReceiptDO receipt = randomPojo(MesWmOutsourceReceiptDO.class, o -> { | ||
| 57 | + o.setStatus(MesWmOutsourceReceiptStatusEnum.CONFIRMED.getStatus()); | ||
| 58 | + }); | ||
| 59 | + outsourceReceiptMapper.insert(receipt); | ||
| 60 | + // 准备参数 | ||
| 61 | + Long receiptId = receipt.getId(); | ||
| 62 | + Long lineId = randomLongId(); | ||
| 63 | + Long iqcId = randomLongId(); | ||
| 64 | + BigDecimal qualifiedQuantity = BigDecimal.valueOf(80); | ||
| 65 | + BigDecimal unqualifiedQuantity = BigDecimal.valueOf(20); | ||
| 66 | + | ||
| 67 | + // mock 行查询 | ||
| 68 | + MesWmOutsourceReceiptLineDO line = randomPojo(MesWmOutsourceReceiptLineDO.class, o -> { | ||
| 69 | + o.setId(lineId); | ||
| 70 | + o.setReceiptId(receiptId); | ||
| 71 | + o.setQuantity(BigDecimal.valueOf(100)); | ||
| 72 | + }); | ||
| 73 | + when(outsourceReceiptLineService.getOutsourceReceiptLine(eq(lineId))).thenReturn(line); | ||
| 74 | + | ||
| 75 | + // 调用 | ||
| 76 | + outsourceReceiptService.approveOutsourceReceiptWhenIqcComplete( | ||
| 77 | + receiptId, lineId, iqcId, qualifiedQuantity, unqualifiedQuantity); | ||
| 78 | + | ||
| 79 | + // 断言:合格品行更新 | ||
| 80 | + verify(outsourceReceiptLineService).updateOutsourceReceiptLineDO(argThat(updateLine -> | ||
| 81 | + lineId.equals(updateLine.getId()) | ||
| 82 | + && MesWmQualityStatusEnum.PASS.getStatus().equals(updateLine.getQualityStatus()) | ||
| 83 | + && iqcId.equals(updateLine.getIqcId()) | ||
| 84 | + && qualifiedQuantity.equals(updateLine.getQuantity()))); | ||
| 85 | + // 断言:不合格品新增行 | ||
| 86 | + verify(outsourceReceiptLineService).createOutsourceReceiptLineDO(argThat(newLine -> | ||
| 87 | + newLine.getId() == null | ||
| 88 | + && MesWmQualityStatusEnum.FAIL.getStatus().equals(newLine.getQualityStatus()) | ||
| 89 | + && iqcId.equals(newLine.getIqcId()) | ||
| 90 | + && unqualifiedQuantity.equals(newLine.getQuantity()))); | ||
| 91 | + // 断言:主表状态更新为待上架 | ||
| 92 | + MesWmOutsourceReceiptDO updatedReceipt = outsourceReceiptMapper.selectById(receiptId); | ||
| 93 | + assertEquals(MesWmOutsourceReceiptStatusEnum.APPROVING.getStatus(), updatedReceipt.getStatus()); | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + @Test | ||
| 97 | + public void testApproveOutsourceReceiptWhenIqcComplete_success_onlyQualified() { | ||
| 98 | + // mock 数据:插入一条待检验状态的外协入库单 | ||
| 99 | + MesWmOutsourceReceiptDO receipt = randomPojo(MesWmOutsourceReceiptDO.class, o -> { | ||
| 100 | + o.setStatus(MesWmOutsourceReceiptStatusEnum.CONFIRMED.getStatus()); | ||
| 101 | + }); | ||
| 102 | + outsourceReceiptMapper.insert(receipt); | ||
| 103 | + // 准备参数 | ||
| 104 | + Long receiptId = receipt.getId(); | ||
| 105 | + Long lineId = randomLongId(); | ||
| 106 | + Long iqcId = randomLongId(); | ||
| 107 | + BigDecimal qualifiedQuantity = BigDecimal.valueOf(100); | ||
| 108 | + BigDecimal unqualifiedQuantity = BigDecimal.ZERO; // 没有不合格品 | ||
| 109 | + | ||
| 110 | + // mock 行查询 | ||
| 111 | + MesWmOutsourceReceiptLineDO line = randomPojo(MesWmOutsourceReceiptLineDO.class, o -> { | ||
| 112 | + o.setId(lineId); | ||
| 113 | + o.setReceiptId(receiptId); | ||
| 114 | + }); | ||
| 115 | + when(outsourceReceiptLineService.getOutsourceReceiptLine(eq(lineId))).thenReturn(line); | ||
| 116 | + | ||
| 117 | + // 调用 | ||
| 118 | + outsourceReceiptService.approveOutsourceReceiptWhenIqcComplete( | ||
| 119 | + receiptId, lineId, iqcId, qualifiedQuantity, unqualifiedQuantity); | ||
| 120 | + | ||
| 121 | + // 断言:合格品行更新 | ||
| 122 | + verify(outsourceReceiptLineService).updateOutsourceReceiptLineDO(argThat(updateLine -> | ||
| 123 | + lineId.equals(updateLine.getId()))); | ||
| 124 | + // 断言:不合格品不应该新增行(数量为 0) | ||
| 125 | + verify(outsourceReceiptLineService, never()).createOutsourceReceiptLineDO(any()); | ||
| 126 | + // 断言:主表状态更新为待上架 | ||
| 127 | + MesWmOutsourceReceiptDO updatedReceipt = outsourceReceiptMapper.selectById(receiptId); | ||
| 128 | + assertEquals(MesWmOutsourceReceiptStatusEnum.APPROVING.getStatus(), updatedReceipt.getStatus()); | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + @Test | ||
| 132 | + public void testApproveOutsourceReceiptWhenIqcComplete_receiptNotExists() { | ||
| 133 | + // 准备参数 | ||
| 134 | + Long receiptId = randomLongId(); | ||
| 135 | + Long lineId = randomLongId(); | ||
| 136 | + Long iqcId = randomLongId(); | ||
| 137 | + BigDecimal qualifiedQuantity = BigDecimal.valueOf(100); | ||
| 138 | + BigDecimal unqualifiedQuantity = BigDecimal.ZERO; | ||
| 139 | + | ||
| 140 | + // 调用,并断言异常 | ||
| 141 | + assertServiceException( | ||
| 142 | + () -> outsourceReceiptService.approveOutsourceReceiptWhenIqcComplete( | ||
| 143 | + receiptId, lineId, iqcId, qualifiedQuantity, unqualifiedQuantity), | ||
| 144 | + WM_OUTSOURCE_RECEIPT_NOT_EXISTS); | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + @Test | ||
| 148 | + public void testApproveOutsourceReceiptWhenIqcComplete_statusNotConfirmed() { | ||
| 149 | + // mock 数据:插入一条草稿状态的外协入库单(非待检验状态) | ||
| 150 | + MesWmOutsourceReceiptDO receipt = randomPojo(MesWmOutsourceReceiptDO.class, o -> { | ||
| 151 | + o.setStatus(MesWmOutsourceReceiptStatusEnum.PREPARE.getStatus()); | ||
| 152 | + }); | ||
| 153 | + outsourceReceiptMapper.insert(receipt); | ||
| 154 | + // 准备参数 | ||
| 155 | + Long receiptId = receipt.getId(); | ||
| 156 | + Long lineId = randomLongId(); | ||
| 157 | + Long iqcId = randomLongId(); | ||
| 158 | + BigDecimal qualifiedQuantity = BigDecimal.valueOf(100); | ||
| 159 | + BigDecimal unqualifiedQuantity = BigDecimal.ZERO; | ||
| 160 | + | ||
| 161 | + // 调用,并断言异常 | ||
| 162 | + assertServiceException( | ||
| 163 | + () -> outsourceReceiptService.approveOutsourceReceiptWhenIqcComplete( | ||
| 164 | + receiptId, lineId, iqcId, qualifiedQuantity, unqualifiedQuantity), | ||
| 165 | + WM_OUTSOURCE_RECEIPT_STATUS_ERROR); | ||
| 166 | + } | ||
| 167 | + | ||
| 168 | + @Test | ||
| 169 | + public void testApproveOutsourceReceiptWhenIqcComplete_lineNotExists() { | ||
| 170 | + // mock 数据:插入一条待检验状态的外协入库单 | ||
| 171 | + MesWmOutsourceReceiptDO receipt = randomPojo(MesWmOutsourceReceiptDO.class, o -> { | ||
| 172 | + o.setStatus(MesWmOutsourceReceiptStatusEnum.CONFIRMED.getStatus()); | ||
| 173 | + }); | ||
| 174 | + outsourceReceiptMapper.insert(receipt); | ||
| 175 | + // 准备参数 | ||
| 176 | + Long receiptId = receipt.getId(); | ||
| 177 | + Long lineId = randomLongId(); | ||
| 178 | + Long iqcId = randomLongId(); | ||
| 179 | + BigDecimal qualifiedQuantity = BigDecimal.valueOf(100); | ||
| 180 | + BigDecimal unqualifiedQuantity = BigDecimal.ZERO; | ||
| 181 | + | ||
| 182 | + // mock 行查询返回 null | ||
| 183 | + when(outsourceReceiptLineService.getOutsourceReceiptLine(eq(lineId))).thenReturn(null); | ||
| 184 | + | ||
| 185 | + // 调用,并断言异常 | ||
| 186 | + assertServiceException( | ||
| 187 | + () -> outsourceReceiptService.approveOutsourceReceiptWhenIqcComplete( | ||
| 188 | + receiptId, lineId, iqcId, qualifiedQuantity, unqualifiedQuantity), | ||
| 189 | + WM_OUTSOURCE_RECEIPT_LINE_NOT_EXISTS); | ||
| 190 | + } | ||
| 191 | + | ||
| 192 | +} |
yudao-module-mes/src/test/resources/sql/clean.sql
| 1 | DELETE FROM "mes_md_auto_code_rule"; | 1 | DELETE FROM "mes_md_auto_code_rule"; |
| 2 | DELETE FROM "mes_md_auto_code_part"; | 2 | DELETE FROM "mes_md_auto_code_part"; |
| 3 | DELETE FROM "mes_md_auto_code_record"; | 3 | DELETE FROM "mes_md_auto_code_record"; |
| 4 | +DELETE FROM "mes_wm_arrival_notice"; | ||
| 5 | +DELETE FROM "mes_wm_outsource_receipt"; | ||
| 6 | +DELETE FROM "mes_qc_iqc"; |
yudao-module-mes/src/test/resources/sql/create_tables.sql
| @@ -63,3 +63,86 @@ CREATE TABLE IF NOT EXISTS "mes_md_auto_code_record" ( | @@ -63,3 +63,86 @@ CREATE TABLE IF NOT EXISTS "mes_md_auto_code_record" ( | ||
| 63 | "tenant_id" bigint NOT NULL DEFAULT 0, | 63 | "tenant_id" bigint NOT NULL DEFAULT 0, |
| 64 | PRIMARY KEY ("id") | 64 | PRIMARY KEY ("id") |
| 65 | ); | 65 | ); |
| 66 | + | ||
| 67 | +-- ---------------------------- | ||
| 68 | +-- MES 到货通知单 | ||
| 69 | +-- ---------------------------- | ||
| 70 | +CREATE TABLE IF NOT EXISTS "mes_wm_arrival_notice" ( | ||
| 71 | + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, | ||
| 72 | + "code" varchar(64) DEFAULT NULL, | ||
| 73 | + "name" varchar(255) DEFAULT NULL, | ||
| 74 | + "purchase_order_code" varchar(64) DEFAULT NULL, | ||
| 75 | + "vendor_id" bigint DEFAULT NULL, | ||
| 76 | + "arrival_date" timestamp DEFAULT NULL, | ||
| 77 | + "contact_name" varchar(64) DEFAULT NULL, | ||
| 78 | + "contact_telephone" varchar(64) DEFAULT NULL, | ||
| 79 | + "status" tinyint DEFAULT NULL, | ||
| 80 | + "remark" varchar(500) DEFAULT NULL, | ||
| 81 | + "creator" varchar(64) DEFAULT '', | ||
| 82 | + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| 83 | + "updater" varchar(64) DEFAULT '', | ||
| 84 | + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| 85 | + "deleted" bit NOT NULL DEFAULT FALSE, | ||
| 86 | + "tenant_id" bigint NOT NULL DEFAULT 0, | ||
| 87 | + PRIMARY KEY ("id") | ||
| 88 | +); | ||
| 89 | + | ||
| 90 | +-- ---------------------------- | ||
| 91 | +-- MES 外协入库单 | ||
| 92 | +-- ---------------------------- | ||
| 93 | +CREATE TABLE IF NOT EXISTS "mes_wm_outsource_receipt" ( | ||
| 94 | + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, | ||
| 95 | + "code" varchar(64) DEFAULT NULL, | ||
| 96 | + "name" varchar(255) DEFAULT NULL, | ||
| 97 | + "work_order_id" bigint DEFAULT NULL, | ||
| 98 | + "vendor_id" bigint DEFAULT NULL, | ||
| 99 | + "receipt_date" timestamp DEFAULT NULL, | ||
| 100 | + "status" tinyint DEFAULT NULL, | ||
| 101 | + "remark" varchar(500) DEFAULT NULL, | ||
| 102 | + "creator" varchar(64) DEFAULT '', | ||
| 103 | + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| 104 | + "updater" varchar(64) DEFAULT '', | ||
| 105 | + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| 106 | + "deleted" bit NOT NULL DEFAULT FALSE, | ||
| 107 | + "tenant_id" bigint NOT NULL DEFAULT 0, | ||
| 108 | + PRIMARY KEY ("id") | ||
| 109 | +); | ||
| 110 | + | ||
| 111 | +-- ---------------------------- | ||
| 112 | +-- MES 来料检验单(IQC) | ||
| 113 | +-- ---------------------------- | ||
| 114 | +CREATE TABLE IF NOT EXISTS "mes_qc_iqc" ( | ||
| 115 | + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, | ||
| 116 | + "code" varchar(64) DEFAULT NULL, | ||
| 117 | + "name" varchar(255) DEFAULT NULL, | ||
| 118 | + "template_id" bigint DEFAULT NULL, | ||
| 119 | + "source_doc_type" int DEFAULT NULL, | ||
| 120 | + "source_doc_id" bigint DEFAULT NULL, | ||
| 121 | + "source_line_id" bigint DEFAULT NULL, | ||
| 122 | + "vendor_id" bigint DEFAULT NULL, | ||
| 123 | + "vendor_batch" varchar(64) DEFAULT NULL, | ||
| 124 | + "item_id" bigint DEFAULT NULL, | ||
| 125 | + "received_quantity" decimal(24,6) DEFAULT NULL, | ||
| 126 | + "check_quantity" decimal(24,6) DEFAULT NULL, | ||
| 127 | + "qualified_quantity" decimal(24,6) DEFAULT NULL, | ||
| 128 | + "unqualified_quantity" decimal(24,6) DEFAULT NULL, | ||
| 129 | + "critical_rate" decimal(10,2) DEFAULT NULL, | ||
| 130 | + "major_rate" decimal(10,2) DEFAULT NULL, | ||
| 131 | + "minor_rate" decimal(10,2) DEFAULT NULL, | ||
| 132 | + "critical_quantity" int DEFAULT NULL, | ||
| 133 | + "major_quantity" int DEFAULT NULL, | ||
| 134 | + "minor_quantity" int DEFAULT NULL, | ||
| 135 | + "check_result" tinyint DEFAULT NULL, | ||
| 136 | + "receive_date" timestamp DEFAULT NULL, | ||
| 137 | + "inspect_date" timestamp DEFAULT NULL, | ||
| 138 | + "inspector_user_id" bigint DEFAULT NULL, | ||
| 139 | + "status" tinyint DEFAULT NULL, | ||
| 140 | + "remark" varchar(500) DEFAULT NULL, | ||
| 141 | + "creator" varchar(64) DEFAULT '', | ||
| 142 | + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| 143 | + "updater" varchar(64) DEFAULT '', | ||
| 144 | + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| 145 | + "deleted" bit NOT NULL DEFAULT FALSE, | ||
| 146 | + "tenant_id" bigint NOT NULL DEFAULT 0, | ||
| 147 | + PRIMARY KEY ("id") | ||
| 148 | +); |