🎉 Initial commit
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
package cn.iocoder.yudao.module.mes.dal.mysql.pro.task;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPageReqVO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.pro.route.MesProRouteProcessMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* {@link MesProTaskMapper#selectPage(MesProTaskPageReqVO)} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class MesProTaskMapperTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesProTaskMapper taskMapper;
|
||||
|
||||
@Resource
|
||||
private MesProRouteProcessMapper routeProcessMapper;
|
||||
|
||||
/**
|
||||
* 创建 Task 时统一设置 BigDecimal 字段为 2 位小数,避免 H2 decimal(14,2) 精度不匹配
|
||||
*/
|
||||
private MesProTaskDO createTaskPojo(Consumer<MesProTaskDO> consumer) {
|
||||
return randomPojo(MesProTaskDO.class, o -> {
|
||||
o.setQuantity(new BigDecimal("10.00"));
|
||||
o.setProducedQuantity(new BigDecimal("5.00"));
|
||||
o.setQualifyQuantity(new BigDecimal("4.00"));
|
||||
o.setUnqualifyQuantity(new BigDecimal("1.00"));
|
||||
o.setChangedQuantity(new BigDecimal("0.00"));
|
||||
consumer.accept(o);
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== selectPage 基础过滤 ====================
|
||||
|
||||
@Test
|
||||
public void testSelectPage_byCode() {
|
||||
MesProTaskDO matchTask = createTaskPojo(o -> {
|
||||
o.setCode("PT202501010001");
|
||||
o.setStatus(0);
|
||||
});
|
||||
taskMapper.insert(matchTask);
|
||||
taskMapper.insert(cloneIgnoreId(matchTask, o -> o.setCode("OTHER_CODE")));
|
||||
|
||||
MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
|
||||
reqVO.setCode("PT2025");
|
||||
PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
|
||||
|
||||
assertEquals(1, result.getTotal());
|
||||
assertPojoEquals(matchTask, result.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectPage_byName() {
|
||||
MesProTaskDO matchTask = createTaskPojo(o -> {
|
||||
o.setName("注塑任务-A");
|
||||
o.setStatus(0);
|
||||
});
|
||||
taskMapper.insert(matchTask);
|
||||
taskMapper.insert(cloneIgnoreId(matchTask, o -> o.setName("OTHER_NAME")));
|
||||
|
||||
MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
|
||||
reqVO.setName("注塑");
|
||||
PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
|
||||
|
||||
assertEquals(1, result.getTotal());
|
||||
assertPojoEquals(matchTask, result.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectPage_byWorkOrderId() {
|
||||
MesProTaskDO matchTask = createTaskPojo(o -> {
|
||||
o.setWorkOrderId(100L);
|
||||
o.setStatus(0);
|
||||
});
|
||||
taskMapper.insert(matchTask);
|
||||
taskMapper.insert(cloneIgnoreId(matchTask, o -> o.setWorkOrderId(999L)));
|
||||
|
||||
MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
|
||||
reqVO.setWorkOrderId(100L);
|
||||
PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
|
||||
|
||||
assertEquals(1, result.getTotal());
|
||||
assertPojoEquals(matchTask, result.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectPage_byStatus() {
|
||||
MesProTaskDO matchTask = createTaskPojo(o -> o.setStatus(1));
|
||||
taskMapper.insert(matchTask);
|
||||
taskMapper.insert(cloneIgnoreId(matchTask, o -> o.setStatus(2)));
|
||||
|
||||
MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
|
||||
reqVO.setStatus(1);
|
||||
PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
|
||||
|
||||
assertEquals(1, result.getTotal());
|
||||
assertPojoEquals(matchTask, result.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectPage_byStatuses() {
|
||||
MesProTaskDO task1 = createTaskPojo(o -> o.setStatus(1));
|
||||
MesProTaskDO task2 = createTaskPojo(o -> o.setStatus(2));
|
||||
MesProTaskDO task3 = createTaskPojo(o -> o.setStatus(3));
|
||||
taskMapper.insert(task1);
|
||||
taskMapper.insert(task2);
|
||||
taskMapper.insert(task3);
|
||||
|
||||
MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
|
||||
reqVO.setStatuses(ListUtil.of(1, 2));
|
||||
PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
|
||||
|
||||
assertEquals(2, result.getTotal());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectPage_noFilter() {
|
||||
MesProTaskDO task1 = createTaskPojo(o -> o.setStatus(0));
|
||||
MesProTaskDO task2 = createTaskPojo(o -> o.setStatus(1));
|
||||
taskMapper.insert(task1);
|
||||
taskMapper.insert(task2);
|
||||
|
||||
MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
|
||||
PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
|
||||
|
||||
assertEquals(2, result.getTotal());
|
||||
}
|
||||
|
||||
// ==================== selectPage + checkFlag (MPJ LEFT JOIN) ====================
|
||||
|
||||
@Test
|
||||
public void testSelectPage_checkFlagTrue() {
|
||||
Long routeId = 10L;
|
||||
Long processId1 = 100L;
|
||||
Long processId2 = 200L;
|
||||
|
||||
MesProTaskDO task1 = createTaskPojo(o -> {
|
||||
o.setRouteId(routeId);
|
||||
o.setProcessId(processId1);
|
||||
o.setStatus(0);
|
||||
});
|
||||
MesProTaskDO task2 = createTaskPojo(o -> {
|
||||
o.setRouteId(routeId);
|
||||
o.setProcessId(processId2);
|
||||
o.setStatus(0);
|
||||
});
|
||||
taskMapper.insert(task1);
|
||||
taskMapper.insert(task2);
|
||||
|
||||
routeProcessMapper.insert(randomPojo(MesProRouteProcessDO.class, o -> {
|
||||
o.setRouteId(routeId);
|
||||
o.setProcessId(processId1);
|
||||
o.setCheckFlag(true);
|
||||
}));
|
||||
routeProcessMapper.insert(randomPojo(MesProRouteProcessDO.class, o -> {
|
||||
o.setRouteId(routeId);
|
||||
o.setProcessId(processId2);
|
||||
o.setCheckFlag(false);
|
||||
}));
|
||||
|
||||
// 查询:checkFlag = true,只返回 task1
|
||||
MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
|
||||
reqVO.setCheckFlag(true);
|
||||
PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
|
||||
|
||||
assertEquals(1, result.getTotal());
|
||||
assertPojoEquals(task1, result.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectPage_checkFlagFalse() {
|
||||
Long routeId = 20L;
|
||||
Long processId1 = 300L;
|
||||
Long processId2 = 400L;
|
||||
|
||||
MesProTaskDO task1 = createTaskPojo(o -> {
|
||||
o.setRouteId(routeId);
|
||||
o.setProcessId(processId1);
|
||||
o.setStatus(0);
|
||||
});
|
||||
MesProTaskDO task2 = createTaskPojo(o -> {
|
||||
o.setRouteId(routeId);
|
||||
o.setProcessId(processId2);
|
||||
o.setStatus(0);
|
||||
});
|
||||
taskMapper.insert(task1);
|
||||
taskMapper.insert(task2);
|
||||
|
||||
routeProcessMapper.insert(randomPojo(MesProRouteProcessDO.class, o -> {
|
||||
o.setRouteId(routeId);
|
||||
o.setProcessId(processId1);
|
||||
o.setCheckFlag(true);
|
||||
}));
|
||||
routeProcessMapper.insert(randomPojo(MesProRouteProcessDO.class, o -> {
|
||||
o.setRouteId(routeId);
|
||||
o.setProcessId(processId2);
|
||||
o.setCheckFlag(false);
|
||||
}));
|
||||
|
||||
// 查询:checkFlag = false,只返回 task2
|
||||
MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
|
||||
reqVO.setCheckFlag(false);
|
||||
PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
|
||||
|
||||
assertEquals(1, result.getTotal());
|
||||
assertPojoEquals(task2, result.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectPage_checkFlagWithOtherFilter() {
|
||||
Long routeId = 30L;
|
||||
Long processId = 500L;
|
||||
|
||||
MesProTaskDO taskMatch = createTaskPojo(o -> {
|
||||
o.setRouteId(routeId);
|
||||
o.setProcessId(processId);
|
||||
o.setCode("PT-MATCH-001");
|
||||
o.setStatus(0);
|
||||
});
|
||||
MesProTaskDO taskNoMatch = createTaskPojo(o -> {
|
||||
o.setRouteId(routeId);
|
||||
o.setProcessId(processId);
|
||||
o.setCode("OTHER-CODE");
|
||||
o.setStatus(0);
|
||||
});
|
||||
taskMapper.insert(taskMatch);
|
||||
taskMapper.insert(taskNoMatch);
|
||||
|
||||
routeProcessMapper.insert(randomPojo(MesProRouteProcessDO.class, o -> {
|
||||
o.setRouteId(routeId);
|
||||
o.setProcessId(processId);
|
||||
o.setCheckFlag(true);
|
||||
}));
|
||||
|
||||
// 查询:checkFlag=true 且 code like 'PT-MATCH'
|
||||
MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
|
||||
reqVO.setCheckFlag(true);
|
||||
reqVO.setCode("PT-MATCH");
|
||||
PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
|
||||
|
||||
assertEquals(1, result.getTotal());
|
||||
assertPojoEquals(taskMatch, result.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectPage_checkFlagNull_noJoin() {
|
||||
Long routeId = 40L;
|
||||
Long processId = 600L;
|
||||
|
||||
MesProTaskDO task = createTaskPojo(o -> {
|
||||
o.setRouteId(routeId);
|
||||
o.setProcessId(processId);
|
||||
o.setStatus(0);
|
||||
});
|
||||
taskMapper.insert(task);
|
||||
|
||||
MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
|
||||
PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
|
||||
|
||||
assertEquals(1, result.getTotal());
|
||||
assertPojoEquals(task, result.getList().get(0));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDetailDO;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* {@link MesWmProductSalesDetailMapper} 的单元测试
|
||||
* <p>
|
||||
* 覆盖:selectListByLineId、selectListBySalesId、deleteByLineId、deleteBySalesId
|
||||
*/
|
||||
public class MesWmProductSalesDetailMapperTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesWmProductSalesDetailMapper detailMapper;
|
||||
|
||||
// ==================== 辅助方法 ====================
|
||||
|
||||
/**
|
||||
* 创建明细 DO,固定 BigDecimal 精度为 2 位,避免 H2 decimal(14,2) 精度不匹配
|
||||
*/
|
||||
private MesWmProductSalesDetailDO createDetailPojo(Consumer<MesWmProductSalesDetailDO> consumer) {
|
||||
return randomPojo(MesWmProductSalesDetailDO.class, o -> {
|
||||
o.setQuantity(new BigDecimal("10.00"));
|
||||
consumer.accept(o);
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== selectListByLineId ====================
|
||||
|
||||
@Test
|
||||
public void testSelectListByLineId_match() {
|
||||
Long lineId = 100L;
|
||||
MesWmProductSalesDetailDO match = createDetailPojo(o -> {
|
||||
o.setLineId(lineId);
|
||||
o.setSalesId(1L);
|
||||
});
|
||||
detailMapper.insert(match);
|
||||
// 插入另一条不同 lineId 的记录,不应被返回
|
||||
detailMapper.insert(cloneIgnoreId(match, o -> o.setLineId(999L)));
|
||||
|
||||
List<MesWmProductSalesDetailDO> result = detailMapper.selectListByLineId(lineId);
|
||||
|
||||
assertEquals(1, result.size());
|
||||
assertPojoEquals(match, result.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectListByLineId_multipleRows() {
|
||||
Long lineId = 200L;
|
||||
MesWmProductSalesDetailDO detail1 = createDetailPojo(o -> {
|
||||
o.setLineId(lineId);
|
||||
o.setSalesId(2L);
|
||||
});
|
||||
MesWmProductSalesDetailDO detail2 = createDetailPojo(o -> {
|
||||
o.setLineId(lineId);
|
||||
o.setSalesId(2L);
|
||||
});
|
||||
detailMapper.insert(detail1);
|
||||
detailMapper.insert(detail2);
|
||||
|
||||
List<MesWmProductSalesDetailDO> result = detailMapper.selectListByLineId(lineId);
|
||||
|
||||
assertEquals(2, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectListByLineId_noMatch() {
|
||||
MesWmProductSalesDetailDO detail = createDetailPojo(o -> {
|
||||
o.setLineId(300L);
|
||||
o.setSalesId(3L);
|
||||
});
|
||||
detailMapper.insert(detail);
|
||||
|
||||
List<MesWmProductSalesDetailDO> result = detailMapper.selectListByLineId(999L);
|
||||
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
// ==================== selectListBySalesId ====================
|
||||
|
||||
@Test
|
||||
public void testSelectListBySalesId_match() {
|
||||
Long salesId = 10L;
|
||||
MesWmProductSalesDetailDO match = createDetailPojo(o -> {
|
||||
o.setSalesId(salesId);
|
||||
o.setLineId(1L);
|
||||
});
|
||||
detailMapper.insert(match);
|
||||
// 插入另一条不同 salesId 的记录
|
||||
detailMapper.insert(cloneIgnoreId(match, o -> o.setSalesId(888L)));
|
||||
|
||||
List<MesWmProductSalesDetailDO> result = detailMapper.selectListBySalesId(salesId);
|
||||
|
||||
assertEquals(1, result.size());
|
||||
assertPojoEquals(match, result.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectListBySalesId_multipleRows() {
|
||||
Long salesId = 20L;
|
||||
MesWmProductSalesDetailDO detail1 = createDetailPojo(o -> {
|
||||
o.setSalesId(salesId);
|
||||
o.setLineId(11L);
|
||||
});
|
||||
MesWmProductSalesDetailDO detail2 = createDetailPojo(o -> {
|
||||
o.setSalesId(salesId);
|
||||
o.setLineId(12L);
|
||||
});
|
||||
detailMapper.insert(detail1);
|
||||
detailMapper.insert(detail2);
|
||||
|
||||
List<MesWmProductSalesDetailDO> result = detailMapper.selectListBySalesId(salesId);
|
||||
|
||||
assertEquals(2, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectListBySalesId_noMatch() {
|
||||
MesWmProductSalesDetailDO detail = createDetailPojo(o -> {
|
||||
o.setSalesId(30L);
|
||||
o.setLineId(2L);
|
||||
});
|
||||
detailMapper.insert(detail);
|
||||
|
||||
List<MesWmProductSalesDetailDO> result = detailMapper.selectListBySalesId(9999L);
|
||||
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
// ==================== deleteByLineId ====================
|
||||
|
||||
@Test
|
||||
public void testDeleteByLineId_deletesOnlyTargetLine() {
|
||||
Long lineId = 400L;
|
||||
MesWmProductSalesDetailDO toDelete = createDetailPojo(o -> {
|
||||
o.setLineId(lineId);
|
||||
o.setSalesId(4L);
|
||||
});
|
||||
MesWmProductSalesDetailDO toKeep = createDetailPojo(o -> {
|
||||
o.setLineId(500L);
|
||||
o.setSalesId(4L);
|
||||
});
|
||||
detailMapper.insert(toDelete);
|
||||
detailMapper.insert(toKeep);
|
||||
|
||||
detailMapper.deleteByLineId(lineId);
|
||||
|
||||
// lineId=400 的记录已被逻辑删除
|
||||
List<MesWmProductSalesDetailDO> deletedResult = detailMapper.selectListByLineId(lineId);
|
||||
assertTrue(deletedResult.isEmpty(), "lineId=400 的记录应被删除");
|
||||
|
||||
// lineId=500 的记录保留
|
||||
List<MesWmProductSalesDetailDO> keptResult = detailMapper.selectListByLineId(500L);
|
||||
assertEquals(1, keptResult.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteByLineId_multipleRows() {
|
||||
Long lineId = 600L;
|
||||
detailMapper.insert(createDetailPojo(o -> { o.setLineId(lineId); o.setSalesId(5L); }));
|
||||
detailMapper.insert(createDetailPojo(o -> { o.setLineId(lineId); o.setSalesId(5L); }));
|
||||
|
||||
detailMapper.deleteByLineId(lineId);
|
||||
|
||||
List<MesWmProductSalesDetailDO> result = detailMapper.selectListByLineId(lineId);
|
||||
assertTrue(result.isEmpty(), "同 lineId 下的所有记录均应被删除");
|
||||
}
|
||||
|
||||
// ==================== deleteBySalesId ====================
|
||||
|
||||
@Test
|
||||
public void testDeleteBySalesId_deletesOnlyTargetSales() {
|
||||
Long salesId = 700L;
|
||||
MesWmProductSalesDetailDO toDelete = createDetailPojo(o -> {
|
||||
o.setSalesId(salesId);
|
||||
o.setLineId(6L);
|
||||
});
|
||||
MesWmProductSalesDetailDO toKeep = createDetailPojo(o -> {
|
||||
o.setSalesId(800L);
|
||||
o.setLineId(7L);
|
||||
});
|
||||
detailMapper.insert(toDelete);
|
||||
detailMapper.insert(toKeep);
|
||||
|
||||
detailMapper.deleteBySalesId(salesId);
|
||||
|
||||
// salesId=700 的记录已被逻辑删除
|
||||
List<MesWmProductSalesDetailDO> deletedResult = detailMapper.selectListBySalesId(salesId);
|
||||
assertTrue(deletedResult.isEmpty(), "salesId=700 的记录应被删除");
|
||||
|
||||
// salesId=800 的记录保留
|
||||
List<MesWmProductSalesDetailDO> keptResult = detailMapper.selectListBySalesId(800L);
|
||||
assertEquals(1, keptResult.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteBySalesId_multipleRows() {
|
||||
Long salesId = 900L;
|
||||
detailMapper.insert(createDetailPojo(o -> { o.setSalesId(salesId); o.setLineId(8L); }));
|
||||
detailMapper.insert(createDetailPojo(o -> { o.setSalesId(salesId); o.setLineId(9L); }));
|
||||
detailMapper.insert(createDetailPojo(o -> { o.setSalesId(salesId); o.setLineId(10L); }));
|
||||
|
||||
detailMapper.deleteBySalesId(salesId);
|
||||
|
||||
List<MesWmProductSalesDetailDO> result = detailMapper.selectListBySalesId(salesId);
|
||||
assertTrue(result.isEmpty(), "同 salesId 下的所有记录均应被删除");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package cn.iocoder.yudao.module.mes.service.cal.plan;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.controller.admin.cal.plan.vo.MesCalPlanSaveReqVO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.cal.plan.MesCalPlanMapper;
|
||||
import cn.iocoder.yudao.module.mes.enums.cal.MesCalPlanStatusEnum;
|
||||
import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftMethodEnum;
|
||||
import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftTypeEnum;
|
||||
import cn.iocoder.yudao.module.mes.service.cal.team.MesCalTeamShiftService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link MesCalPlanServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesCalPlanServiceImpl.class)
|
||||
public class MesCalPlanServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesCalPlanServiceImpl planService;
|
||||
|
||||
@Resource
|
||||
private MesCalPlanMapper planMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesCalPlanShiftService planShiftService;
|
||||
@MockitoBean
|
||||
private MesCalPlanTeamService planTeamService;
|
||||
@MockitoBean
|
||||
private MesCalTeamShiftService teamShiftService;
|
||||
|
||||
@Test
|
||||
public void testCreatePlan_singleShift_addDefaultShift() {
|
||||
// 准备参数
|
||||
MesCalPlanSaveReqVO reqVO = new MesCalPlanSaveReqVO();
|
||||
reqVO.setCode("PLAN-SINGLE-001");
|
||||
reqVO.setName("单白班默认班次");
|
||||
reqVO.setCalendarType(1);
|
||||
reqVO.setStartDate(LocalDateTime.of(2026, 4, 1, 0, 0));
|
||||
reqVO.setEndDate(LocalDateTime.of(2026, 4, 30, 0, 0));
|
||||
reqVO.setShiftType(MesCalShiftTypeEnum.SINGLE.getType());
|
||||
reqVO.setShiftMethod(MesCalShiftMethodEnum.DAY.getMethod());
|
||||
reqVO.setShiftCount(1);
|
||||
|
||||
// 调用
|
||||
Long planId = planService.createPlan(reqVO);
|
||||
|
||||
// 断言 1:计划状态为草稿
|
||||
MesCalPlanDO plan = planMapper.selectById(planId);
|
||||
assertEquals(MesCalPlanStatusEnum.PREPARE.getStatus(), plan.getStatus());
|
||||
// 断言 2:自动生成默认班次
|
||||
verify(planShiftService).addDefaultPlanShift(planId, MesCalShiftTypeEnum.SINGLE.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfirmPlan_syncGenerateTeamShiftRecords() {
|
||||
// mock 数据:插入一条草稿状态的排班计划
|
||||
MesCalPlanDO plan = randomPojo(MesCalPlanDO.class, o -> {
|
||||
o.setCalendarType(1);
|
||||
o.setStartDate(LocalDateTime.of(2026, 4, 1, 0, 0));
|
||||
o.setEndDate(LocalDateTime.of(2026, 4, 3, 0, 0));
|
||||
o.setStatus(MesCalPlanStatusEnum.PREPARE.getStatus());
|
||||
o.setShiftType(MesCalShiftTypeEnum.SINGLE.getType());
|
||||
o.setShiftMethod(MesCalShiftMethodEnum.DAY.getMethod());
|
||||
o.setShiftCount(1);
|
||||
});
|
||||
planMapper.insert(plan);
|
||||
// mock 方法:班组数量满足要求
|
||||
when(planTeamService.getPlanTeamCountByPlanId(plan.getId())).thenReturn(1L);
|
||||
|
||||
// 调用
|
||||
planService.confirmPlan(plan.getId());
|
||||
|
||||
// 断言 1:计划状态更新为已确认
|
||||
MesCalPlanDO updatePlan = planMapper.selectById(plan.getId());
|
||||
assertEquals(MesCalPlanStatusEnum.CONFIRMED.getStatus(), updatePlan.getStatus());
|
||||
// 断言 2:触发生成班组排班记录
|
||||
verify(teamShiftService).generateTeamShiftRecords(plan.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeletePlan_notCascadeDeleteTeamShift() {
|
||||
// mock 数据:插入一条草稿状态的排班计划
|
||||
MesCalPlanDO plan = randomPojo(MesCalPlanDO.class, o -> {
|
||||
o.setCalendarType(1);
|
||||
o.setStartDate(LocalDateTime.of(2026, 4, 1, 0, 0));
|
||||
o.setEndDate(LocalDateTime.of(2026, 4, 3, 0, 0));
|
||||
o.setStatus(MesCalPlanStatusEnum.PREPARE.getStatus());
|
||||
o.setShiftType(MesCalShiftTypeEnum.SINGLE.getType());
|
||||
o.setShiftMethod(MesCalShiftMethodEnum.DAY.getMethod());
|
||||
o.setShiftCount(1);
|
||||
});
|
||||
planMapper.insert(plan);
|
||||
|
||||
// 调用
|
||||
planService.deletePlan(plan.getId());
|
||||
|
||||
// 断言 1:计划被删除
|
||||
assertNull(planMapper.selectById(plan.getId()));
|
||||
// 断言 2:级联删除班次和班组关联
|
||||
verify(planShiftService).deletePlanShiftByPlanId(plan.getId());
|
||||
verify(planTeamService).deleteByPlanId(plan.getId());
|
||||
// 断言 3:不级联删除班组排班记录
|
||||
verify(teamShiftService, never()).deleteByPlanId(plan.getId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package cn.iocoder.yudao.module.mes.service.cal.plan;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanShiftDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.cal.plan.MesCalPlanShiftMapper;
|
||||
import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftTypeEnum;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* {@link MesCalPlanShiftServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesCalPlanShiftServiceImpl.class)
|
||||
public class MesCalPlanShiftServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesCalPlanShiftServiceImpl planShiftService;
|
||||
|
||||
@Resource
|
||||
private MesCalPlanShiftMapper planShiftMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesCalPlanService planService;
|
||||
|
||||
@Test
|
||||
public void testAddDefaultPlanShift_singleShift_use18Clock() {
|
||||
// 准备参数
|
||||
Long planId = 1001L;
|
||||
|
||||
// 调用
|
||||
planShiftService.addDefaultPlanShift(planId, MesCalShiftTypeEnum.SINGLE.getType());
|
||||
|
||||
// 断言:生成 1 条默认班次(白班 08:00~18:00)
|
||||
List<MesCalPlanShiftDO> shifts = planShiftMapper.selectListByPlanId(planId);
|
||||
assertEquals(1, shifts.size());
|
||||
MesCalPlanShiftDO shift = shifts.get(0);
|
||||
assertEquals(planId, shift.getPlanId());
|
||||
assertEquals(1, shift.getSort());
|
||||
assertEquals("白班", shift.getName());
|
||||
assertEquals("08:00", shift.getStartTime());
|
||||
assertEquals("18:00", shift.getEndTime());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package cn.iocoder.yudao.module.mes.service.cal.team;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanShiftDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanTeamDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamShiftDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.cal.team.MesCalTeamShiftMapper;
|
||||
import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftMethodEnum;
|
||||
import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftTypeEnum;
|
||||
import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanService;
|
||||
import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanShiftService;
|
||||
import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanTeamService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link MesCalTeamShiftServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesCalTeamShiftServiceImpl.class)
|
||||
public class MesCalTeamShiftServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesCalTeamShiftServiceImpl teamShiftService;
|
||||
|
||||
@Resource
|
||||
private MesCalTeamShiftMapper teamShiftMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesCalPlanService planService;
|
||||
@MockitoBean
|
||||
private MesCalPlanShiftService planShiftService;
|
||||
@MockitoBean
|
||||
private MesCalPlanTeamService planTeamService;
|
||||
|
||||
@Test
|
||||
public void testGenerateTeamShiftRecords_threeShift_rotateThreeStates() {
|
||||
// 准备参数
|
||||
Long planId = 3001L;
|
||||
// mock 方法:排班计划(三班倒、按天轮班、3 天)
|
||||
MesCalPlanDO plan = MesCalPlanDO.builder()
|
||||
.id(planId)
|
||||
.code("PLAN-THREE-001")
|
||||
.name("三班倒轮转")
|
||||
.calendarType(1)
|
||||
.startDate(LocalDateTime.of(2026, 4, 1, 0, 0))
|
||||
.endDate(LocalDateTime.of(2026, 4, 3, 0, 0))
|
||||
.shiftType(MesCalShiftTypeEnum.THREE.getType())
|
||||
.shiftMethod(MesCalShiftMethodEnum.DAY.getMethod())
|
||||
.shiftCount(1)
|
||||
.build();
|
||||
List<MesCalPlanShiftDO> shifts = ListUtil.of(
|
||||
MesCalPlanShiftDO.builder().id(11L).planId(planId).sort(1).name("白班").startTime("08:00").endTime("16:00").build(),
|
||||
MesCalPlanShiftDO.builder().id(12L).planId(planId).sort(2).name("中班").startTime("16:00").endTime("00:00").build(),
|
||||
MesCalPlanShiftDO.builder().id(13L).planId(planId).sort(3).name("夜班").startTime("00:00").endTime("08:00").build()
|
||||
);
|
||||
List<MesCalPlanTeamDO> teams = ListUtil.of(
|
||||
MesCalPlanTeamDO.builder().id(21L).planId(planId).teamId(101L).build(),
|
||||
MesCalPlanTeamDO.builder().id(22L).planId(planId).teamId(102L).build(),
|
||||
MesCalPlanTeamDO.builder().id(23L).planId(planId).teamId(103L).build()
|
||||
);
|
||||
when(planService.getPlan(planId)).thenReturn(plan);
|
||||
when(planShiftService.getPlanShiftListByPlanId(planId)).thenReturn(shifts);
|
||||
when(planTeamService.getPlanTeamListByPlanId(planId)).thenReturn(teams);
|
||||
|
||||
// 调用
|
||||
teamShiftService.generateTeamShiftRecords(planId);
|
||||
|
||||
// 断言:3 天 × 3 班次 = 9 条排班记录,班组按天轮转
|
||||
List<MesCalTeamShiftDO> records = teamShiftMapper.selectListByPlanId(planId);
|
||||
records.sort(Comparator.comparing(MesCalTeamShiftDO::getDay).thenComparing(MesCalTeamShiftDO::getSort));
|
||||
assertEquals(9, records.size());
|
||||
// 断言:第 1 天(shiftIndex=0):A 白班、B 中班、C 夜班
|
||||
assertRecord(records.get(0), LocalDateTime.of(2026, 4, 1, 0, 0), 1, 101L, 11L);
|
||||
assertRecord(records.get(1), LocalDateTime.of(2026, 4, 1, 0, 0), 2, 102L, 12L);
|
||||
assertRecord(records.get(2), LocalDateTime.of(2026, 4, 1, 0, 0), 3, 103L, 13L);
|
||||
// 断言:第 2 天(shiftIndex=1):C 白班、A 中班、B 夜班
|
||||
assertRecord(records.get(3), LocalDateTime.of(2026, 4, 2, 0, 0), 1, 103L, 11L);
|
||||
assertRecord(records.get(4), LocalDateTime.of(2026, 4, 2, 0, 0), 2, 101L, 12L);
|
||||
assertRecord(records.get(5), LocalDateTime.of(2026, 4, 2, 0, 0), 3, 102L, 13L);
|
||||
// 断言:第 3 天(shiftIndex=2):B 白班、C 中班、A 夜班
|
||||
assertRecord(records.get(6), LocalDateTime.of(2026, 4, 3, 0, 0), 1, 102L, 11L);
|
||||
assertRecord(records.get(7), LocalDateTime.of(2026, 4, 3, 0, 0), 2, 103L, 12L);
|
||||
assertRecord(records.get(8), LocalDateTime.of(2026, 4, 3, 0, 0), 3, 101L, 13L);
|
||||
}
|
||||
|
||||
private static void assertRecord(MesCalTeamShiftDO actual, LocalDateTime day, Integer sort,
|
||||
Long teamId, Long shiftId) {
|
||||
assertEquals(day, actual.getDay());
|
||||
assertEquals(sort, actual.getSort());
|
||||
assertEquals(teamId, actual.getTeamId());
|
||||
assertEquals(shiftId, actual.getShiftId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package cn.iocoder.yudao.module.mes.service.md.autocode;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodePartDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodeRecordDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodeRuleDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.md.autocode.MesMdAutoCodeRecordMapper;
|
||||
import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodePartTypeEnum;
|
||||
import cn.iocoder.yudao.module.mes.service.md.autocode.strategy.MesMdAutoCodeContext;
|
||||
import cn.iocoder.yudao.module.mes.service.md.autocode.strategy.MesMdAutoCodePartStrategy;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link MesMdAutoCodeRecordServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class MesMdAutoCodeRecordServiceImplTest {
|
||||
|
||||
@InjectMocks
|
||||
private MesMdAutoCodeRecordServiceImpl recordService;
|
||||
|
||||
@Mock
|
||||
private MesMdAutoCodeRecordMapper recordMapper;
|
||||
|
||||
@Mock
|
||||
private MesMdAutoCodeRuleService ruleService;
|
||||
@Mock
|
||||
private MesMdAutoCodePartService partService;
|
||||
|
||||
@Mock
|
||||
private MesMdAutoCodePartStrategy fixedCharStrategy;
|
||||
@Mock
|
||||
private MesMdAutoCodePartStrategy serialNumberStrategy;
|
||||
@Mock
|
||||
private MesMdAutoCodePartStrategy inputCharStrategy;
|
||||
@Mock
|
||||
private MesMdAutoCodePartStrategy dateStrategy;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
// 使用反射注入 strategyList
|
||||
List<MesMdAutoCodePartStrategy> strategyList = Arrays.asList(
|
||||
fixedCharStrategy, serialNumberStrategy, inputCharStrategy, dateStrategy
|
||||
);
|
||||
when(fixedCharStrategy.getType()).thenReturn(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType());
|
||||
when(serialNumberStrategy.getType()).thenReturn(MesMdAutoCodePartTypeEnum.SERIAL_NUMBER.getType());
|
||||
when(inputCharStrategy.getType()).thenReturn(MesMdAutoCodePartTypeEnum.INPUT_CHAR.getType());
|
||||
when(dateStrategy.getType()).thenReturn(MesMdAutoCodePartTypeEnum.DATE.getType());
|
||||
ReflectUtil.setFieldValue(recordService, "strategyList", strategyList);
|
||||
// 手动调用 init 方法初始化策略 map
|
||||
recordService.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateAutoCode_fixedCharAndSerialNumber() {
|
||||
// 准备参数
|
||||
String ruleCode = "ITEM_CODE";
|
||||
MesMdAutoCodeRuleDO rule = new MesMdAutoCodeRuleDO().setId(1L).setCode(ruleCode).setPadded(false);
|
||||
MesMdAutoCodePartDO part1 = new MesMdAutoCodePartDO().setType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType())
|
||||
.setFixCharacter("ITEM_").setLength(5).setSort(1);
|
||||
MesMdAutoCodePartDO part2 = new MesMdAutoCodePartDO().setType(MesMdAutoCodePartTypeEnum.SERIAL_NUMBER.getType())
|
||||
.setSerialStartNo(1).setSerialStep(1).setLength(4).setSort(2).setCycleFlag(false);
|
||||
// mock
|
||||
when(ruleService.getAutoCodeRuleByCode(ruleCode)).thenReturn(rule);
|
||||
when(partService.getAutoCodePartListByRuleId(1L)).thenReturn(Arrays.asList(part1, part2));
|
||||
when(fixedCharStrategy.generate(eq(part1), any(MesMdAutoCodeContext.class))).thenReturn("ITEM_");
|
||||
when(serialNumberStrategy.generate(eq(part2), any(MesMdAutoCodeContext.class))).thenAnswer(invocation -> {
|
||||
MesMdAutoCodeContext context = invocation.getArgument(1);
|
||||
context.setSerialNo(1L);
|
||||
return "0001";
|
||||
});
|
||||
when(recordMapper.selectByResult("ITEM_0001")).thenReturn(null);
|
||||
|
||||
// 调用
|
||||
String result = recordService.generateAutoCode(ruleCode, null);
|
||||
// 断言
|
||||
assertEquals("ITEM_0001", result);
|
||||
ArgumentCaptor<MesMdAutoCodeRecordDO> captor = ArgumentCaptor.forClass(MesMdAutoCodeRecordDO.class);
|
||||
verify(recordMapper).insert(captor.capture());
|
||||
MesMdAutoCodeRecordDO record = captor.getValue();
|
||||
assertEquals(1L, record.getRuleId());
|
||||
assertEquals("ITEM_0001", record.getResult());
|
||||
assertEquals(1, record.getSerialNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateAutoCode_retryWhenResultExists() {
|
||||
// 准备参数
|
||||
String ruleCode = "DV_MACHINERY_CODE";
|
||||
MesMdAutoCodeRuleDO rule = new MesMdAutoCodeRuleDO().setId(5L).setCode(ruleCode).setPadded(false);
|
||||
MesMdAutoCodePartDO part1 = new MesMdAutoCodePartDO().setType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType())
|
||||
.setFixCharacter("M").setLength(1).setSort(1);
|
||||
MesMdAutoCodePartDO part2 = new MesMdAutoCodePartDO().setType(MesMdAutoCodePartTypeEnum.SERIAL_NUMBER.getType())
|
||||
.setSerialStartNo(1).setSerialStep(1).setLength(5).setSort(2).setCycleFlag(false);
|
||||
// mock
|
||||
when(ruleService.getAutoCodeRuleByCode(ruleCode)).thenReturn(rule);
|
||||
when(partService.getAutoCodePartListByRuleId(5L)).thenReturn(Arrays.asList(part1, part2));
|
||||
when(fixedCharStrategy.generate(eq(part1), any(MesMdAutoCodeContext.class))).thenReturn("M");
|
||||
long[] serialNoRef = {0L};
|
||||
when(serialNumberStrategy.generate(eq(part2), any(MesMdAutoCodeContext.class))).thenAnswer(invocation -> {
|
||||
MesMdAutoCodeContext context = invocation.getArgument(1);
|
||||
long serialNo = ++serialNoRef[0];
|
||||
context.setSerialNo(serialNo);
|
||||
return String.format("%05d", serialNo);
|
||||
});
|
||||
when(recordMapper.selectByResult("M00001")).thenReturn(new MesMdAutoCodeRecordDO());
|
||||
when(recordMapper.selectByResult("M00002")).thenReturn(new MesMdAutoCodeRecordDO());
|
||||
when(recordMapper.selectByResult("M00003")).thenReturn(null);
|
||||
|
||||
// 调用
|
||||
String result = recordService.generateAutoCode(ruleCode, null);
|
||||
// 断言
|
||||
assertEquals("M00003", result);
|
||||
ArgumentCaptor<MesMdAutoCodeRecordDO> captor = ArgumentCaptor.forClass(MesMdAutoCodeRecordDO.class);
|
||||
verify(recordMapper).insert(captor.capture());
|
||||
MesMdAutoCodeRecordDO record = captor.getValue();
|
||||
assertEquals(5L, record.getRuleId());
|
||||
assertEquals("M00003", record.getResult());
|
||||
assertEquals(3L, record.getSerialNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateAutoCode_withInputChar() {
|
||||
// 准备参数
|
||||
String ruleCode = "CLIENT_CODE";
|
||||
String inputChar = "A";
|
||||
MesMdAutoCodeRuleDO rule = new MesMdAutoCodeRuleDO().setId(2L).setCode(ruleCode).setPadded(false);
|
||||
MesMdAutoCodePartDO part1 = new MesMdAutoCodePartDO().setType(MesMdAutoCodePartTypeEnum.INPUT_CHAR.getType())
|
||||
.setLength(1).setSort(1);
|
||||
MesMdAutoCodePartDO part2 = new MesMdAutoCodePartDO().setType(MesMdAutoCodePartTypeEnum.SERIAL_NUMBER.getType())
|
||||
.setSerialStartNo(1).setSerialStep(1).setLength(4).setSort(2).setCycleFlag(false);
|
||||
// mock
|
||||
when(ruleService.getAutoCodeRuleByCode(ruleCode)).thenReturn(rule);
|
||||
when(partService.getAutoCodePartListByRuleId(2L)).thenReturn(Arrays.asList(part1, part2));
|
||||
when(inputCharStrategy.generate(eq(part1), any(MesMdAutoCodeContext.class))).thenReturn("A");
|
||||
when(serialNumberStrategy.generate(eq(part2), any(MesMdAutoCodeContext.class))).thenReturn("0001");
|
||||
when(recordMapper.selectByResult("A0001")).thenReturn(null);
|
||||
|
||||
// 调用
|
||||
String result = recordService.generateAutoCode(ruleCode, inputChar);
|
||||
// 断言
|
||||
assertEquals("A0001", result);
|
||||
ArgumentCaptor<MesMdAutoCodeRecordDO> captor = ArgumentCaptor.forClass(MesMdAutoCodeRecordDO.class);
|
||||
verify(recordMapper).insert(captor.capture());
|
||||
MesMdAutoCodeRecordDO record = captor.getValue();
|
||||
assertEquals(inputChar, record.getInputChar());
|
||||
assertEquals("A0001", record.getResult());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateAutoCode_withPadding() {
|
||||
// 准备参数
|
||||
String ruleCode = "PADDED_CODE";
|
||||
MesMdAutoCodeRuleDO rule = new MesMdAutoCodeRuleDO().setId(3L).setCode(ruleCode)
|
||||
.setPadded(true).setMaxLength(10).setPaddedChar("0").setPaddedMethod(1);
|
||||
MesMdAutoCodePartDO part1 = new MesMdAutoCodePartDO().setType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType())
|
||||
.setFixCharacter("IT").setLength(2).setSort(1);
|
||||
// mock
|
||||
when(ruleService.getAutoCodeRuleByCode(ruleCode)).thenReturn(rule);
|
||||
when(partService.getAutoCodePartListByRuleId(3L)).thenReturn(Arrays.asList(part1));
|
||||
when(fixedCharStrategy.generate(eq(part1), any(MesMdAutoCodeContext.class))).thenReturn("IT");
|
||||
when(recordMapper.selectByResult("00000000IT")).thenReturn(null);
|
||||
|
||||
// 调用
|
||||
String result = recordService.generateAutoCode(ruleCode, null);
|
||||
// 断言:IT 补齐到 10 位,左补 0
|
||||
assertEquals("00000000IT", result);
|
||||
verify(recordMapper).insert(any(MesMdAutoCodeRecordDO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateAutoCode_withDatePart() {
|
||||
// 准备参数
|
||||
String ruleCode = "DATE_CODE";
|
||||
MesMdAutoCodeRuleDO rule = new MesMdAutoCodeRuleDO().setId(4L).setCode(ruleCode).setPadded(false);
|
||||
MesMdAutoCodePartDO part1 = new MesMdAutoCodePartDO().setType(MesMdAutoCodePartTypeEnum.DATE.getType())
|
||||
.setDateFormat("yyyyMMdd").setLength(8).setSort(1);
|
||||
MesMdAutoCodePartDO part2 = new MesMdAutoCodePartDO().setType(MesMdAutoCodePartTypeEnum.SERIAL_NUMBER.getType())
|
||||
.setSerialStartNo(1).setSerialStep(1).setLength(4).setSort(2).setCycleFlag(false);
|
||||
|
||||
// mock
|
||||
when(ruleService.getAutoCodeRuleByCode(ruleCode)).thenReturn(rule);
|
||||
when(partService.getAutoCodePartListByRuleId(4L)).thenReturn(Arrays.asList(part1, part2));
|
||||
when(dateStrategy.generate(eq(part1), any(MesMdAutoCodeContext.class))).thenReturn("20260304");
|
||||
when(serialNumberStrategy.generate(eq(part2), any(MesMdAutoCodeContext.class))).thenReturn("0001");
|
||||
when(recordMapper.selectByResult("202603040001")).thenReturn(null);
|
||||
|
||||
// 调用
|
||||
String result = recordService.generateAutoCode(ruleCode, null);
|
||||
// 断言:日期 8 位 + 流水号 4 位 = 12 位
|
||||
assertEquals(12, result.length());
|
||||
assertEquals("202603040001", result);
|
||||
verify(recordMapper).insert(any(MesMdAutoCodeRecordDO.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package cn.iocoder.yudao.module.mes.service.md.autocode.strategy.impl;
|
||||
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodePartDO;
|
||||
import cn.iocoder.yudao.module.mes.service.md.autocode.strategy.MesMdAutoCodeContext;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* {@link MesMdAutoCodeDatePartStrategy} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class MesMdAutoCodeDatePartStrategyTest {
|
||||
|
||||
private final MesMdAutoCodeDatePartStrategy strategy = new MesMdAutoCodeDatePartStrategy();
|
||||
|
||||
@Test
|
||||
public void testGenerate_withDefaultFormat() {
|
||||
// 准备参数
|
||||
MesMdAutoCodePartDO part = new MesMdAutoCodePartDO().setDateFormat(null);
|
||||
MesMdAutoCodeContext context = new MesMdAutoCodeContext();
|
||||
|
||||
// 调用
|
||||
String result = strategy.generate(part, context);
|
||||
|
||||
// 断言:默认格式为 yyyyMMdd,长度为 8
|
||||
assertEquals(8, result.length());
|
||||
assertTrue(result.matches("\\d{8}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerate_withCustomFormat() {
|
||||
// 准备参数
|
||||
MesMdAutoCodePartDO part = new MesMdAutoCodePartDO().setDateFormat("yyyyMM");
|
||||
MesMdAutoCodeContext context = new MesMdAutoCodeContext();
|
||||
|
||||
// 调用
|
||||
String result = strategy.generate(part, context);
|
||||
// 断言:格式为 yyyyMM,长度为 6
|
||||
assertEquals(6, result.length());
|
||||
assertTrue(result.matches("\\d{6}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerate_withTimeFormat() {
|
||||
// 准备参数
|
||||
MesMdAutoCodePartDO part = new MesMdAutoCodePartDO().setDateFormat("HHmmss");
|
||||
MesMdAutoCodeContext context = new MesMdAutoCodeContext();
|
||||
|
||||
// 调用
|
||||
String result = strategy.generate(part, context);
|
||||
// 断言:格式为 HHmmss,长度为 6
|
||||
assertEquals(6, result.length());
|
||||
assertTrue(result.matches("\\d{6}"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package cn.iocoder.yudao.module.mes.service.md.autocode.strategy.impl;
|
||||
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodePartDO;
|
||||
import cn.iocoder.yudao.module.mes.service.md.autocode.strategy.MesMdAutoCodeContext;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* {@link MesMdAutoCodeFixedCharPartStrategy} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class MesMdAutoCodeFixedCharPartStrategyTest {
|
||||
|
||||
private final MesMdAutoCodeFixedCharPartStrategy strategy = new MesMdAutoCodeFixedCharPartStrategy();
|
||||
|
||||
@Test
|
||||
public void testGenerate_withFixedChar() {
|
||||
// 准备参数
|
||||
MesMdAutoCodePartDO part = new MesMdAutoCodePartDO().setFixCharacter("ITEM_");
|
||||
MesMdAutoCodeContext context = new MesMdAutoCodeContext();
|
||||
|
||||
// 调用
|
||||
String result = strategy.generate(part, context);
|
||||
// 断言
|
||||
assertEquals("ITEM_", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerate_withEmptyFixedChar() {
|
||||
// 准备参数
|
||||
MesMdAutoCodePartDO part = new MesMdAutoCodePartDO().setFixCharacter("");
|
||||
MesMdAutoCodeContext context = new MesMdAutoCodeContext();
|
||||
|
||||
// 调用
|
||||
String result = strategy.generate(part, context);
|
||||
// 断言
|
||||
assertEquals("", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerate_withNullFixedChar() {
|
||||
// 准备参数
|
||||
MesMdAutoCodePartDO part = new MesMdAutoCodePartDO().setFixCharacter(null);
|
||||
MesMdAutoCodeContext context = new MesMdAutoCodeContext();
|
||||
|
||||
// 调用
|
||||
String result = strategy.generate(part, context);
|
||||
// 断言
|
||||
assertEquals("", result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package cn.iocoder.yudao.module.mes.service.md.autocode.strategy.impl;
|
||||
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodePartDO;
|
||||
import cn.iocoder.yudao.module.mes.service.md.autocode.strategy.MesMdAutoCodeContext;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* {@link MesMdAutoCodeInputCharPartStrategy} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class MesMdAutoCodeInputCharPartStrategyTest {
|
||||
|
||||
private final MesMdAutoCodeInputCharPartStrategy strategy = new MesMdAutoCodeInputCharPartStrategy();
|
||||
|
||||
@Test
|
||||
public void testGenerate_withInputChar() {
|
||||
// 准备参数
|
||||
MesMdAutoCodePartDO part = new MesMdAutoCodePartDO();
|
||||
MesMdAutoCodeContext context = new MesMdAutoCodeContext().setInputChar("ABC");
|
||||
|
||||
// 调用
|
||||
String result = strategy.generate(part, context);
|
||||
// 断言
|
||||
assertEquals("ABC", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerate_withEmptyInputChar() {
|
||||
// 准备参数
|
||||
MesMdAutoCodePartDO part = new MesMdAutoCodePartDO();
|
||||
MesMdAutoCodeContext context = new MesMdAutoCodeContext().setInputChar("");
|
||||
|
||||
// 调用
|
||||
String result = strategy.generate(part, context);
|
||||
// 断言
|
||||
assertEquals("", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerate_withNullInputChar() {
|
||||
// 准备参数
|
||||
MesMdAutoCodePartDO part = new MesMdAutoCodePartDO();
|
||||
MesMdAutoCodeContext context = new MesMdAutoCodeContext().setInputChar(null);
|
||||
|
||||
// 调用
|
||||
String result = strategy.generate(part, context);
|
||||
// 断言
|
||||
assertEquals("", result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package cn.iocoder.yudao.module.mes.service.md.autocode.strategy.impl;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseRedisUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodePartDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodeRuleDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.redis.md.autocode.MesMdAutoCodeRedisDAO;
|
||||
import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeCycleMethodEnum;
|
||||
import cn.iocoder.yudao.module.mes.service.md.autocode.strategy.MesMdAutoCodeContext;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* {@link MesMdAutoCodeSerialNumberPartStrategy} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import({MesMdAutoCodeSerialNumberPartStrategy.class, MesMdAutoCodeRedisDAO.class})
|
||||
public class MesMdAutoCodeSerialNumberPartStrategyTest extends BaseRedisUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesMdAutoCodeSerialNumberPartStrategy strategy;
|
||||
|
||||
@Test
|
||||
public void testGenerate_noCycle() {
|
||||
// 准备参数
|
||||
MesMdAutoCodePartDO part = new MesMdAutoCodePartDO().setLength(4).setSerialStartNo(1).setSerialStep(1).setCycleFlag(false);
|
||||
MesMdAutoCodeRuleDO rule = new MesMdAutoCodeRuleDO().setId(1L);
|
||||
MesMdAutoCodeContext context = new MesMdAutoCodeContext().setRule(rule);
|
||||
|
||||
// 调用
|
||||
String result = strategy.generate(part, context);
|
||||
// 断言
|
||||
assertEquals("0001", result);
|
||||
assertEquals(1, context.getSerialNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerate_withCycleByDay() {
|
||||
// 准备参数
|
||||
MesMdAutoCodePartDO part = new MesMdAutoCodePartDO().setLength(4).setSerialStartNo(1).setSerialStep(1)
|
||||
.setCycleFlag(true).setCycleMethod(MesMdAutoCodeCycleMethodEnum.DAY.getMethod());
|
||||
MesMdAutoCodeRuleDO rule = new MesMdAutoCodeRuleDO().setId(2L);
|
||||
MesMdAutoCodeContext context = new MesMdAutoCodeContext().setRule(rule);
|
||||
|
||||
// 调用
|
||||
String result = strategy.generate(part, context);
|
||||
// 断言
|
||||
assertEquals("0001", result);
|
||||
assertEquals(1, context.getSerialNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerate_withCycleByInputChar() {
|
||||
// 准备参数
|
||||
MesMdAutoCodePartDO part = new MesMdAutoCodePartDO().setLength(4).setSerialStartNo(1).setSerialStep(1)
|
||||
.setCycleFlag(true).setCycleMethod(MesMdAutoCodeCycleMethodEnum.INPUT_CHAR.getMethod());
|
||||
MesMdAutoCodeRuleDO rule = new MesMdAutoCodeRuleDO().setId(3L);
|
||||
MesMdAutoCodeContext context = new MesMdAutoCodeContext().setRule(rule).setInputChar("A");
|
||||
|
||||
// 调用
|
||||
String result = strategy.generate(part, context);
|
||||
// 断言
|
||||
assertEquals("0001", result);
|
||||
assertEquals(1, context.getSerialNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerate_withStep() {
|
||||
// 准备参数
|
||||
MesMdAutoCodePartDO part = new MesMdAutoCodePartDO().setLength(4).setSerialStartNo(1).setSerialStep(5).setCycleFlag(false);
|
||||
MesMdAutoCodeRuleDO rule = new MesMdAutoCodeRuleDO().setId(4L);
|
||||
MesMdAutoCodeContext context1 = new MesMdAutoCodeContext().setRule(rule);
|
||||
MesMdAutoCodeContext context2 = new MesMdAutoCodeContext().setRule(rule);
|
||||
|
||||
// 调用
|
||||
String result1 = strategy.generate(part, context1);
|
||||
String result2 = strategy.generate(part, context2);
|
||||
// 断言
|
||||
assertEquals("0001", result1);
|
||||
assertEquals(1, context1.getSerialNo());
|
||||
assertEquals("0006", result2);
|
||||
assertEquals(6, context2.getSerialNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerate_multipleCallsIncrement() {
|
||||
// 准备参数
|
||||
MesMdAutoCodePartDO part = new MesMdAutoCodePartDO().setLength(4).setSerialStartNo(1).setSerialStep(1).setCycleFlag(false);
|
||||
MesMdAutoCodeRuleDO rule = new MesMdAutoCodeRuleDO().setId(5L);
|
||||
MesMdAutoCodeContext context1 = new MesMdAutoCodeContext().setRule(rule);
|
||||
MesMdAutoCodeContext context2 = new MesMdAutoCodeContext().setRule(rule);
|
||||
|
||||
// 调用
|
||||
String result1 = strategy.generate(part, context1);
|
||||
String result2 = strategy.generate(part, context2);
|
||||
// 断言
|
||||
assertEquals("0001", result1);
|
||||
assertEquals("0002", result2);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
package cn.iocoder.yudao.module.mes.service.pro.feedback;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
|
||||
import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum;
|
||||
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
|
||||
import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
|
||||
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
|
||||
import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
|
||||
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.itemconsume.MesWmItemConsumeService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
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.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link MesProFeedbackServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesProFeedbackServiceImpl.class)
|
||||
public class MesProFeedbackServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesProFeedbackServiceImpl feedbackService;
|
||||
|
||||
@Resource
|
||||
private MesProFeedbackMapper feedbackMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesProWorkOrderService workOrderService;
|
||||
@MockitoBean
|
||||
private MesProRouteProcessService routeProcessService;
|
||||
@MockitoBean
|
||||
private MesMdWorkstationService workstationService;
|
||||
@MockitoBean
|
||||
private MesProTaskService taskService;
|
||||
@MockitoBean
|
||||
private MesWmItemConsumeService itemConsumeService;
|
||||
@MockitoBean
|
||||
private MesWmProductProduceService productProduceService;
|
||||
@MockitoBean
|
||||
private MesWmProductProduceLineService produceLineService;
|
||||
|
||||
@Test
|
||||
public void testUpdateProFeedbackWhenIpqcFinish_success_withUnqualified() {
|
||||
// 准备数据:插入一条待检验状态的报工单
|
||||
Long taskId = randomLongId();
|
||||
Long workOrderId = randomLongId();
|
||||
Long sourceLineId = randomLongId();
|
||||
MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> {
|
||||
o.setStatus(MesProFeedbackStatusEnum.UNCHECK.getStatus());
|
||||
o.setTaskId(taskId);
|
||||
o.setWorkOrderId(workOrderId);
|
||||
o.setFeedbackQuantity(BigDecimal.valueOf(100));
|
||||
});
|
||||
feedbackMapper.insert(feedback);
|
||||
|
||||
// mock: 产出行(用于 updateTaskAndWorkOrderByFeedback 内的数量聚合)
|
||||
MesWmProductProduceLineDO qualifiedLine = MesWmProductProduceLineDO.builder()
|
||||
.quantity(BigDecimal.valueOf(80))
|
||||
.qualityStatus(MesWmQualityStatusEnum.PASS.getStatus())
|
||||
.build();
|
||||
MesWmProductProduceLineDO unqualifiedLine = MesWmProductProduceLineDO.builder()
|
||||
.quantity(BigDecimal.valueOf(20))
|
||||
.qualityStatus(MesWmQualityStatusEnum.FAIL.getStatus())
|
||||
.build();
|
||||
when(produceLineService.getProductProduceLineListByFeedbackId(feedback.getId()))
|
||||
.thenReturn(ListUtil.of(qualifiedLine, unqualifiedLine));
|
||||
|
||||
// 调用
|
||||
BigDecimal qualifiedQty = BigDecimal.valueOf(80);
|
||||
BigDecimal unqualifiedQty = BigDecimal.valueOf(20);
|
||||
BigDecimal laborScrapQty = BigDecimal.valueOf(5);
|
||||
BigDecimal materialScrapQty = BigDecimal.valueOf(10);
|
||||
BigDecimal otherScrapQty = BigDecimal.valueOf(5);
|
||||
feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(), sourceLineId,
|
||||
qualifiedQty, unqualifiedQty, laborScrapQty, materialScrapQty, otherScrapQty);
|
||||
|
||||
// 断言 1:调用了 splitPendingAndFinishProduce
|
||||
verify(productProduceService).splitPendingAndFinishProduce(
|
||||
eq(feedback.getId()), eq(sourceLineId), eq(qualifiedQty), eq(unqualifiedQty));
|
||||
|
||||
// 断言 2:报工单状态更新为已完成
|
||||
MesProFeedbackDO updatedFeedback = feedbackMapper.selectById(feedback.getId());
|
||||
assertEquals(MesProFeedbackStatusEnum.FINISHED.getStatus(), updatedFeedback.getStatus());
|
||||
|
||||
// 断言 3:数量回写正确
|
||||
assertEquals(0, qualifiedQty.compareTo(updatedFeedback.getQualifiedQuantity()));
|
||||
assertEquals(0, unqualifiedQty.compareTo(updatedFeedback.getUnqualifiedQuantity()));
|
||||
assertEquals(0, BigDecimal.ZERO.compareTo(updatedFeedback.getUncheckQuantity()));
|
||||
assertEquals(0, laborScrapQty.compareTo(updatedFeedback.getLaborScrapQuantity()));
|
||||
assertEquals(0, materialScrapQty.compareTo(updatedFeedback.getMaterialScrapQuantity()));
|
||||
assertEquals(0, otherScrapQty.compareTo(updatedFeedback.getOtherScrapQuantity()));
|
||||
|
||||
// 断言 4:更新了任务和工单的已生产数量
|
||||
// 注意:数量经过 DB 存取后 scale 可能变化,用 any() 匹配
|
||||
verify(taskService).updateProducedQuantity(eq(taskId),
|
||||
any(BigDecimal.class), any(BigDecimal.class), any(BigDecimal.class));
|
||||
verify(workOrderService).updateProducedQuantity(eq(workOrderId),
|
||||
any(BigDecimal.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateProFeedbackWhenIpqcFinish_success_allQualified() {
|
||||
// 准备数据:全部合格
|
||||
Long taskId = randomLongId();
|
||||
Long workOrderId = randomLongId();
|
||||
MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> {
|
||||
o.setStatus(MesProFeedbackStatusEnum.UNCHECK.getStatus());
|
||||
o.setTaskId(taskId);
|
||||
o.setWorkOrderId(workOrderId);
|
||||
o.setFeedbackQuantity(BigDecimal.valueOf(50));
|
||||
});
|
||||
feedbackMapper.insert(feedback);
|
||||
|
||||
// mock: 产出行(全部合格)
|
||||
MesWmProductProduceLineDO qualifiedLine = MesWmProductProduceLineDO.builder()
|
||||
.quantity(BigDecimal.valueOf(50))
|
||||
.qualityStatus(MesWmQualityStatusEnum.PASS.getStatus())
|
||||
.build();
|
||||
when(produceLineService.getProductProduceLineListByFeedbackId(feedback.getId()))
|
||||
.thenReturn(ListUtil.of(qualifiedLine));
|
||||
|
||||
// 调用
|
||||
feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(), randomLongId(),
|
||||
BigDecimal.valueOf(50), BigDecimal.ZERO,
|
||||
BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO);
|
||||
|
||||
// 断言 1:报工单状态为已完成
|
||||
MesProFeedbackDO updatedFeedback = feedbackMapper.selectById(feedback.getId());
|
||||
assertEquals(MesProFeedbackStatusEnum.FINISHED.getStatus(), updatedFeedback.getStatus());
|
||||
|
||||
// 断言 2:合格品数量正确,不合格品和废品为 0
|
||||
assertEquals(0, BigDecimal.valueOf(50).compareTo(updatedFeedback.getQualifiedQuantity()));
|
||||
assertEquals(0, BigDecimal.ZERO.compareTo(updatedFeedback.getUnqualifiedQuantity()));
|
||||
assertEquals(0, BigDecimal.ZERO.compareTo(updatedFeedback.getLaborScrapQuantity()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateProFeedbackWhenIpqcFinish_feedbackNotExists() {
|
||||
// 调用不存在的 feedbackId,应该抛异常
|
||||
Long feedbackId = randomLongId();
|
||||
assertThrows(Exception.class, () ->
|
||||
feedbackService.updateProFeedbackWhenIpqcFinish(feedbackId, randomLongId(),
|
||||
BigDecimal.TEN, BigDecimal.ZERO,
|
||||
BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateProFeedbackWhenIpqcFinish_feedbackNotUncheck() {
|
||||
// 准备数据:草稿状态(不是 UNCHECK),应该报错
|
||||
MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> {
|
||||
o.setStatus(MesProFeedbackStatusEnum.PREPARE.getStatus());
|
||||
});
|
||||
feedbackMapper.insert(feedback);
|
||||
|
||||
// 调用,应该抛异常
|
||||
assertThrows(Exception.class, () ->
|
||||
feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(), randomLongId(),
|
||||
BigDecimal.TEN, BigDecimal.ZERO,
|
||||
BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO));
|
||||
|
||||
// 断言:不应该调用任何产出单方法
|
||||
verify(productProduceService, never()).splitPendingAndFinishProduce(anyLong(), anyLong(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateProFeedbackWhenIpqcFinish_feedbackAlreadyFinished() {
|
||||
// 准备数据:已完成状态,不能再次完成
|
||||
MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> {
|
||||
o.setStatus(MesProFeedbackStatusEnum.FINISHED.getStatus());
|
||||
});
|
||||
feedbackMapper.insert(feedback);
|
||||
|
||||
// 调用,应该抛异常
|
||||
assertThrows(Exception.class, () ->
|
||||
feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(), randomLongId(),
|
||||
BigDecimal.TEN, BigDecimal.ZERO,
|
||||
BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
// ==================== approveFeedback 测试 ====================
|
||||
|
||||
@Test
|
||||
public void testApproveFeedback_keyNonCheck_success() {
|
||||
// 准备数据:关键非质检工序,常规审批
|
||||
Long routeId = randomLongId();
|
||||
Long processId = randomLongId();
|
||||
MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> {
|
||||
o.setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus());
|
||||
o.setFeedbackQuantity(BigDecimal.valueOf(100));
|
||||
o.setQualifiedQuantity(BigDecimal.valueOf(80));
|
||||
o.setUnqualifiedQuantity(BigDecimal.valueOf(20));
|
||||
o.setUncheckQuantity(BigDecimal.ZERO);
|
||||
o.setRouteId(routeId);
|
||||
o.setProcessId(processId);
|
||||
});
|
||||
feedbackMapper.insert(feedback);
|
||||
|
||||
// mock: 工序配置 key=true, check=false
|
||||
MesProRouteProcessDO routeProcess = MesProRouteProcessDO.builder()
|
||||
.routeId(routeId).processId(processId)
|
||||
.keyFlag(true).checkFlag(false).build();
|
||||
when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
|
||||
.thenReturn(routeProcess);
|
||||
|
||||
// mock: 产品产出单
|
||||
MesWmProductProduceDO produce = randomPojo(MesWmProductProduceDO.class);
|
||||
when(productProduceService.generateProductProduce(any(), eq(false))).thenReturn(produce);
|
||||
|
||||
// mock: 产出行(用于 updateTaskAndWorkOrderByFeedback)
|
||||
MesWmProductProduceLineDO qualifiedLine = MesWmProductProduceLineDO.builder()
|
||||
.quantity(BigDecimal.valueOf(80))
|
||||
.qualityStatus(MesWmQualityStatusEnum.PASS.getStatus()).build();
|
||||
when(produceLineService.getProductProduceLineListByFeedbackId(feedback.getId()))
|
||||
.thenReturn(ListUtil.of(qualifiedLine));
|
||||
|
||||
// 调用
|
||||
boolean result = feedbackService.approveFeedback(feedback.getId());
|
||||
|
||||
// 断言 1:返回 true(已完成)
|
||||
assertTrue(result);
|
||||
|
||||
// 断言 2:状态为已完成,uncheckQuantity 清零
|
||||
MesProFeedbackDO updated = feedbackMapper.selectById(feedback.getId());
|
||||
assertEquals(MesProFeedbackStatusEnum.FINISHED.getStatus(), updated.getStatus());
|
||||
assertEquals(0, BigDecimal.ZERO.compareTo(updated.getUncheckQuantity()));
|
||||
|
||||
// 断言 3:调用了产出单生成 + 入库 + 任务/工单更新
|
||||
verify(productProduceService).generateProductProduce(any(), eq(false));
|
||||
verify(productProduceService).finishProductProduce(produce.getId());
|
||||
verify(taskService).updateProducedQuantity(eq(feedback.getTaskId()),
|
||||
any(BigDecimal.class), any(BigDecimal.class), any(BigDecimal.class));
|
||||
verify(workOrderService).updateProducedQuantity(eq(feedback.getWorkOrderId()),
|
||||
any(BigDecimal.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApproveFeedback_keyCheck_enterUncheck() {
|
||||
// 准备数据:关键质检工序,应进入待检验
|
||||
Long routeId = randomLongId();
|
||||
Long processId = randomLongId();
|
||||
MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> {
|
||||
o.setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus());
|
||||
o.setFeedbackQuantity(BigDecimal.valueOf(50));
|
||||
o.setUncheckQuantity(BigDecimal.valueOf(50)); // 质检工序 uncheckQuantity > 0
|
||||
o.setRouteId(routeId);
|
||||
o.setProcessId(processId);
|
||||
});
|
||||
feedbackMapper.insert(feedback);
|
||||
|
||||
// mock: key=true, check=true
|
||||
MesProRouteProcessDO routeProcess = MesProRouteProcessDO.builder()
|
||||
.routeId(routeId).processId(processId)
|
||||
.keyFlag(true).checkFlag(true).build();
|
||||
when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
|
||||
.thenReturn(routeProcess);
|
||||
|
||||
// 调用
|
||||
boolean result = feedbackService.approveFeedback(feedback.getId());
|
||||
|
||||
// 断言 1:返回 false(待检验)
|
||||
assertFalse(result);
|
||||
|
||||
// 断言 2:状态为待检验
|
||||
MesProFeedbackDO updated = feedbackMapper.selectById(feedback.getId());
|
||||
assertEquals(MesProFeedbackStatusEnum.UNCHECK.getStatus(), updated.getStatus());
|
||||
|
||||
// 断言 3:生成了待检产出单,但没有 finishProductProduce
|
||||
verify(productProduceService).generateProductProduce(any(), eq(true));
|
||||
verify(productProduceService, never()).finishProductProduce(anyLong());
|
||||
|
||||
// 断言 4:没有更新任务/工单数量(等 IPQC 回调)
|
||||
verify(taskService, never()).updateProducedQuantity(anyLong(), any(), any(), any());
|
||||
verify(workOrderService, never()).updateProducedQuantity(anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApproveFeedback_nonKey_directFinish() {
|
||||
// 准备数据:非关键工序,直接完结
|
||||
Long routeId = randomLongId();
|
||||
Long processId = randomLongId();
|
||||
MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> {
|
||||
o.setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus());
|
||||
o.setFeedbackQuantity(BigDecimal.valueOf(30));
|
||||
o.setUncheckQuantity(BigDecimal.ZERO);
|
||||
o.setRouteId(routeId);
|
||||
o.setProcessId(processId);
|
||||
});
|
||||
feedbackMapper.insert(feedback);
|
||||
|
||||
// mock: key=false, check=false
|
||||
MesProRouteProcessDO routeProcess = MesProRouteProcessDO.builder()
|
||||
.routeId(routeId).processId(processId)
|
||||
.keyFlag(false).checkFlag(false).build();
|
||||
when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
|
||||
.thenReturn(routeProcess);
|
||||
|
||||
// 调用
|
||||
boolean result = feedbackService.approveFeedback(feedback.getId());
|
||||
|
||||
// 断言 1:返回 true(已完成)
|
||||
assertTrue(result);
|
||||
|
||||
// 断言 2:状态为已完成
|
||||
MesProFeedbackDO updated = feedbackMapper.selectById(feedback.getId());
|
||||
assertEquals(MesProFeedbackStatusEnum.FINISHED.getStatus(), updated.getStatus());
|
||||
|
||||
// 断言 3:不生成产出单,不更新任务/工单
|
||||
verify(productProduceService, never()).generateProductProduce(any(), anyBoolean());
|
||||
verify(taskService, never()).updateProducedQuantity(anyLong(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApproveFeedback_nonCheck_uncheckQuantityReject() {
|
||||
// 准备数据:非质检工序,但 uncheckQuantity > 0(异常数据),应被拦截
|
||||
Long routeId = randomLongId();
|
||||
Long processId = randomLongId();
|
||||
MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> {
|
||||
o.setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus());
|
||||
o.setFeedbackQuantity(BigDecimal.valueOf(10));
|
||||
o.setUncheckQuantity(BigDecimal.valueOf(10)); // 非质检工序不应有待检数量
|
||||
o.setRouteId(routeId);
|
||||
o.setProcessId(processId);
|
||||
});
|
||||
feedbackMapper.insert(feedback);
|
||||
|
||||
// mock: key=true, check=false(非质检)
|
||||
MesProRouteProcessDO routeProcess = MesProRouteProcessDO.builder()
|
||||
.routeId(routeId).processId(processId)
|
||||
.keyFlag(true).checkFlag(false).build();
|
||||
when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
|
||||
.thenReturn(routeProcess);
|
||||
|
||||
// 调用,应该抛异常
|
||||
assertThrows(Exception.class, () ->
|
||||
feedbackService.approveFeedback(feedback.getId()));
|
||||
|
||||
// 断言:不应该执行任何后续操作
|
||||
verify(productProduceService, never()).generateProductProduce(any(), anyBoolean());
|
||||
verify(itemConsumeService, never()).generateItemConsume(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApproveFeedback_nonKeyCheck_directFinishAndCleanUncheck() {
|
||||
// 准备数据:非关键 + 质检工序(!key+check),uncheckQuantity > 0
|
||||
// 应放行(checkFlag=true 不拦截 uncheckQuantity),直接完结并清零 uncheckQuantity
|
||||
Long routeId = randomLongId();
|
||||
Long processId = randomLongId();
|
||||
MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> {
|
||||
o.setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus());
|
||||
o.setFeedbackQuantity(BigDecimal.valueOf(20));
|
||||
o.setUncheckQuantity(BigDecimal.valueOf(20));
|
||||
o.setRouteId(routeId);
|
||||
o.setProcessId(processId);
|
||||
});
|
||||
feedbackMapper.insert(feedback);
|
||||
|
||||
// mock: key=false, check=true
|
||||
MesProRouteProcessDO routeProcess = MesProRouteProcessDO.builder()
|
||||
.routeId(routeId).processId(processId)
|
||||
.keyFlag(false).checkFlag(true).build();
|
||||
when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
|
||||
.thenReturn(routeProcess);
|
||||
|
||||
// 调用
|
||||
boolean result = feedbackService.approveFeedback(feedback.getId());
|
||||
|
||||
// 断言 1:返回 true(直接完成,不走 UNCHECK)
|
||||
assertTrue(result);
|
||||
|
||||
// 断言 2:状态为已完成,uncheckQuantity 被清零
|
||||
MesProFeedbackDO updated = feedbackMapper.selectById(feedback.getId());
|
||||
assertEquals(MesProFeedbackStatusEnum.FINISHED.getStatus(), updated.getStatus());
|
||||
assertEquals(0, BigDecimal.ZERO.compareTo(updated.getUncheckQuantity()));
|
||||
|
||||
// 断言 3:非关键工序不生成产出单
|
||||
verify(productProduceService, never()).generateProductProduce(any(), anyBoolean());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package cn.iocoder.yudao.module.mes.service.qc.indicatorresult;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.controller.admin.qc.indicatorresult.vo.MesQcIndicatorResultPageReqVO;
|
||||
import cn.iocoder.yudao.module.mes.controller.admin.qc.indicatorresult.vo.MesQcIndicatorResultSaveReqVO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.qc.indicatorresult.MesQcIndicatorResultMapper;
|
||||
import cn.iocoder.yudao.module.mes.enums.qc.MesQcResultValueTypeEnum;
|
||||
import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
|
||||
import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService;
|
||||
import cn.iocoder.yudao.module.mes.service.qc.ipqc.MesQcIpqcService;
|
||||
import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService;
|
||||
import cn.iocoder.yudao.module.mes.service.qc.oqc.MesQcOqcService;
|
||||
import cn.iocoder.yudao.module.mes.service.qc.rqc.MesQcRqcService;
|
||||
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
|
||||
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.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.anySet;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link MesQcIndicatorResultServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesQcIndicatorResultServiceImpl.class)
|
||||
public class MesQcIndicatorResultServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesQcIndicatorResultServiceImpl indicatorResultService;
|
||||
|
||||
@Resource
|
||||
private MesQcIndicatorResultMapper resultMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesQcIndicatorResultDetailService resultDetailService;
|
||||
@MockitoBean
|
||||
private MesQcIndicatorService indicatorService;
|
||||
@MockitoBean
|
||||
private MesQcIqcService iqcService;
|
||||
@MockitoBean
|
||||
private MesQcIpqcService ipqcService;
|
||||
@MockitoBean
|
||||
private MesQcOqcService oqcService;
|
||||
@MockitoBean
|
||||
private MesQcRqcService rqcService;
|
||||
@MockitoBean
|
||||
private DictDataApi dictDataApi;
|
||||
|
||||
@Test
|
||||
public void testCreateIndicatorResult_success() {
|
||||
// 准备参数
|
||||
MesQcIndicatorResultSaveReqVO reqVO = buildReqVO("plain text value", MesQcResultValueTypeEnum.TEXT.getType());
|
||||
// mock 方法
|
||||
mockIqcAndIndicator(reqVO, MesQcResultValueTypeEnum.TEXT.getType());
|
||||
|
||||
// 调用
|
||||
Long resultId = indicatorResultService.createIndicatorResult(reqVO);
|
||||
|
||||
// 断言
|
||||
assertNotNull(resultId);
|
||||
MesQcIndicatorResultDO dbResult = resultMapper.selectById(resultId);
|
||||
assertNotNull(dbResult);
|
||||
assertEquals(reqVO.getCode(), dbResult.getCode());
|
||||
assertEquals(reqVO.getQcId(), dbResult.getQcId());
|
||||
assertEquals(reqVO.getQcType(), dbResult.getQcType());
|
||||
assertEquals(100L, dbResult.getItemId()); // 从 mock IQC 获取
|
||||
verify(resultDetailService).createDetailList(anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateIndicatorResult_fileValueInvalid() {
|
||||
// 准备参数
|
||||
MesQcIndicatorResultSaveReqVO reqVO = buildReqVO("not-a-url", MesQcResultValueTypeEnum.FILE.getType());
|
||||
// mock 方法
|
||||
mockIqcAndIndicator(reqVO, MesQcResultValueTypeEnum.FILE.getType());
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> indicatorResultService.createIndicatorResult(reqVO),
|
||||
QC_RESULT_VALUE_FORMAT_INVALID, "检测项[检测项A]要求文件 URL,实际值=not-a-url");
|
||||
|
||||
// 断言:未入库
|
||||
assertEquals(0, resultMapper.selectCount());
|
||||
verify(resultDetailService, never()).createDetailList(anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateIndicatorResult_success() {
|
||||
// mock 数据
|
||||
MesQcIndicatorResultDO dbResult = randomPojo(MesQcIndicatorResultDO.class, o -> {
|
||||
o.setQcType(MesQcTypeEnum.IQC.getType());
|
||||
});
|
||||
resultMapper.insert(dbResult);
|
||||
// 准备参数
|
||||
MesQcIndicatorResultSaveReqVO reqVO = buildReqVO("updated text", MesQcResultValueTypeEnum.TEXT.getType());
|
||||
reqVO.setId(dbResult.getId());
|
||||
// mock 方法
|
||||
MesQcIndicatorDO indicator = new MesQcIndicatorDO();
|
||||
indicator.setId(reqVO.getItems().get(0).getIndicatorId());
|
||||
indicator.setName("检测项A");
|
||||
indicator.setResultType(MesQcResultValueTypeEnum.TEXT.getType());
|
||||
when(indicatorService.validateIndicatorListExists(anySet()))
|
||||
.thenReturn(MapUtil.of(indicator.getId(), indicator));
|
||||
|
||||
// 调用
|
||||
indicatorResultService.updateIndicatorResult(reqVO);
|
||||
|
||||
// 断言
|
||||
MesQcIndicatorResultDO updatedResult = resultMapper.selectById(dbResult.getId());
|
||||
assertEquals(reqVO.getCode(), updatedResult.getCode());
|
||||
// qcId/qcType/itemId 不允许改挂,应保持原值
|
||||
assertEquals(dbResult.getQcId(), updatedResult.getQcId());
|
||||
assertEquals(dbResult.getQcType(), updatedResult.getQcType());
|
||||
assertEquals(dbResult.getItemId(), updatedResult.getItemId());
|
||||
verify(resultDetailService).createOrUpdateDetailList(anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateIndicatorResult_notExists() {
|
||||
// 准备参数
|
||||
MesQcIndicatorResultSaveReqVO reqVO = buildReqVO("text", MesQcResultValueTypeEnum.TEXT.getType());
|
||||
reqVO.setId(randomLongId());
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> indicatorResultService.updateIndicatorResult(reqVO),
|
||||
QC_RESULT_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteIndicatorResult_success() {
|
||||
// mock 数据
|
||||
MesQcIndicatorResultDO dbResult = randomPojo(MesQcIndicatorResultDO.class);
|
||||
resultMapper.insert(dbResult);
|
||||
|
||||
// 调用
|
||||
indicatorResultService.deleteIndicatorResult(dbResult.getId());
|
||||
|
||||
// 断言
|
||||
assertNull(resultMapper.selectById(dbResult.getId()));
|
||||
verify(resultDetailService).deleteDetailByResultId(dbResult.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteIndicatorResult_notExists() {
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> indicatorResultService.deleteIndicatorResult(randomLongId()),
|
||||
QC_RESULT_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIndicatorResult() {
|
||||
// mock 数据
|
||||
MesQcIndicatorResultDO dbResult = randomPojo(MesQcIndicatorResultDO.class);
|
||||
resultMapper.insert(dbResult);
|
||||
|
||||
// 调用
|
||||
MesQcIndicatorResultDO result = indicatorResultService.getIndicatorResult(dbResult.getId());
|
||||
|
||||
// 断言
|
||||
assertPojoEquals(dbResult, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIndicatorResultPage() {
|
||||
// mock 数据
|
||||
MesQcIndicatorResultDO dbResult = randomPojo(MesQcIndicatorResultDO.class, o -> {
|
||||
o.setQcId(1L);
|
||||
o.setQcType(MesQcTypeEnum.IQC.getType());
|
||||
o.setCode("SPL-001");
|
||||
o.setItemId(100L);
|
||||
});
|
||||
resultMapper.insert(dbResult);
|
||||
// 测试 qcId 不匹配
|
||||
resultMapper.insert(cloneIgnoreId(dbResult, o -> o.setQcId(2L)));
|
||||
// 测试 qcType 不匹配
|
||||
resultMapper.insert(cloneIgnoreId(dbResult, o -> o.setQcType(MesQcTypeEnum.IPQC.getType())));
|
||||
// 测试 code 不匹配
|
||||
resultMapper.insert(cloneIgnoreId(dbResult, o -> o.setCode("SPL-999")));
|
||||
// 测试 itemId 不匹配
|
||||
resultMapper.insert(cloneIgnoreId(dbResult, o -> o.setItemId(999L)));
|
||||
// 准备参数
|
||||
MesQcIndicatorResultPageReqVO pageReqVO = new MesQcIndicatorResultPageReqVO();
|
||||
pageReqVO.setQcId(1L);
|
||||
pageReqVO.setQcType(MesQcTypeEnum.IQC.getType());
|
||||
pageReqVO.setCode("SPL-001");
|
||||
pageReqVO.setItemId(100L);
|
||||
|
||||
// 调用
|
||||
PageResult<MesQcIndicatorResultDO> pageResult = indicatorResultService.getIndicatorResultPage(pageReqVO);
|
||||
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbResult, pageResult.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIndicatorResultCountByQcIdAndType() {
|
||||
// mock 数据
|
||||
MesQcIndicatorResultDO result1 = randomPojo(MesQcIndicatorResultDO.class, o -> {
|
||||
o.setQcId(1L);
|
||||
o.setQcType(MesQcTypeEnum.IQC.getType());
|
||||
});
|
||||
resultMapper.insert(result1);
|
||||
MesQcIndicatorResultDO result2 = randomPojo(MesQcIndicatorResultDO.class, o -> {
|
||||
o.setQcId(1L);
|
||||
o.setQcType(MesQcTypeEnum.IQC.getType());
|
||||
});
|
||||
resultMapper.insert(result2);
|
||||
// 不匹配
|
||||
resultMapper.insert(randomPojo(MesQcIndicatorResultDO.class, o -> {
|
||||
o.setQcId(2L);
|
||||
o.setQcType(MesQcTypeEnum.IPQC.getType());
|
||||
}));
|
||||
|
||||
// 调用
|
||||
Long count = indicatorResultService.getIndicatorResultCountByQcIdAndType(1L, MesQcTypeEnum.IQC.getType());
|
||||
|
||||
// 断言
|
||||
assertEquals(2L, count);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateIndicatorResultExistsByQcIdAndType_success() {
|
||||
// mock 数据
|
||||
resultMapper.insert(randomPojo(MesQcIndicatorResultDO.class, o -> {
|
||||
o.setQcId(1L);
|
||||
o.setQcType(MesQcTypeEnum.IQC.getType());
|
||||
}));
|
||||
|
||||
// 调用(不抛异常即通过)
|
||||
indicatorResultService.validateIndicatorResultExistsByQcIdAndType(1L, MesQcTypeEnum.IQC.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateIndicatorResultExistsByQcIdAndType_notExists() {
|
||||
// 调用,并断言异常
|
||||
assertServiceException(
|
||||
() -> indicatorResultService.validateIndicatorResultExistsByQcIdAndType(randomLongId(), MesQcTypeEnum.IQC.getType()),
|
||||
QC_FINISH_INDICATOR_RESULT_REQUIRED);
|
||||
}
|
||||
|
||||
// ==================== 辅助方法 ====================
|
||||
|
||||
private void mockIqcAndIndicator(MesQcIndicatorResultSaveReqVO reqVO, Integer resultType) {
|
||||
MesQcIqcDO iqc = new MesQcIqcDO();
|
||||
iqc.setId(reqVO.getQcId());
|
||||
iqc.setItemId(100L);
|
||||
when(iqcService.validateIqcExists(reqVO.getQcId())).thenReturn(iqc);
|
||||
|
||||
MesQcIndicatorDO indicator = new MesQcIndicatorDO();
|
||||
indicator.setId(reqVO.getItems().get(0).getIndicatorId());
|
||||
indicator.setName("检测项A");
|
||||
indicator.setResultType(resultType);
|
||||
when(indicatorService.validateIndicatorListExists(anySet()))
|
||||
.thenReturn(MapUtil.of(indicator.getId(), indicator));
|
||||
}
|
||||
|
||||
private MesQcIndicatorResultSaveReqVO buildReqVO(String value, Integer resultType) {
|
||||
MesQcIndicatorResultSaveReqVO.Item item = new MesQcIndicatorResultSaveReqVO.Item();
|
||||
item.setIndicatorId(10L);
|
||||
item.setValue(value);
|
||||
|
||||
MesQcIndicatorResultSaveReqVO reqVO = new MesQcIndicatorResultSaveReqVO();
|
||||
reqVO.setCode("SPL-001");
|
||||
reqVO.setQcId(1L);
|
||||
reqVO.setQcType(MesQcTypeEnum.IQC.getType());
|
||||
reqVO.setItems(ListUtil.of(item));
|
||||
return reqVO;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,596 @@
|
||||
package cn.iocoder.yudao.module.mes.service.qc.ipqc;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcSaveReqVO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcMapper;
|
||||
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
|
||||
import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum;
|
||||
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
|
||||
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
|
||||
import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
|
||||
import cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackService;
|
||||
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
|
||||
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService;
|
||||
import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
|
||||
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
|
||||
import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
|
||||
import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
|
||||
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
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.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link MesQcIpqcServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesQcIpqcServiceImpl.class)
|
||||
public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesQcIpqcServiceImpl ipqcService;
|
||||
|
||||
@Resource
|
||||
private MesQcIpqcMapper ipqcMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesQcTemplateItemService templateItemService;
|
||||
@MockitoBean
|
||||
private MesQcIpqcLineService ipqcLineService;
|
||||
@MockitoBean
|
||||
private MesProWorkOrderService workOrderService;
|
||||
@MockitoBean
|
||||
private MesMdWorkstationService workstationService;
|
||||
@MockitoBean
|
||||
private MesMdItemService itemService;
|
||||
@MockitoBean
|
||||
private MesQcDefectRecordService defectRecordService;
|
||||
@MockitoBean
|
||||
private AdminUserApi adminUserApi;
|
||||
@MockitoBean
|
||||
private MesProFeedbackService feedbackService;
|
||||
@MockitoBean
|
||||
private MesProRouteProductService routeProductService;
|
||||
@MockitoBean
|
||||
private MesProRouteProcessService routeProcessService;
|
||||
@MockitoBean
|
||||
private MesProTaskService taskService;
|
||||
@MockitoBean
|
||||
private MesQcIndicatorResultService indicatorResultService;
|
||||
@MockitoBean
|
||||
private MesWmProductProduceLineService productProduceLineService;
|
||||
|
||||
@Test
|
||||
public void testFinishIpqc_writeBack_feedback() {
|
||||
// 准备数据:插入一条草稿状态的 IPQC 单,来源为报工单(PRO_FEEDBACK=304)
|
||||
Long sourceDocId = randomLongId();
|
||||
Long sourceLineId = randomLongId();
|
||||
BigDecimal qualifiedQty = BigDecimal.valueOf(80);
|
||||
BigDecimal unqualifiedQty = BigDecimal.valueOf(20);
|
||||
BigDecimal laborScrapQty = BigDecimal.valueOf(5);
|
||||
BigDecimal materialScrapQty = BigDecimal.valueOf(10);
|
||||
BigDecimal otherScrapQty = BigDecimal.valueOf(5);
|
||||
|
||||
MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
|
||||
o.setCheckResult(1); // 非空,满足 finishIpqc 的校验
|
||||
o.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
|
||||
o.setSourceDocId(sourceDocId);
|
||||
o.setSourceLineId(sourceLineId);
|
||||
o.setQualifiedQuantity(qualifiedQty);
|
||||
o.setUnqualifiedQuantity(unqualifiedQty);
|
||||
o.setLaborScrapQuantity(laborScrapQty);
|
||||
o.setMaterialScrapQuantity(materialScrapQty);
|
||||
o.setOtherScrapQuantity(otherScrapQty);
|
||||
});
|
||||
ipqcMapper.insert(ipqc);
|
||||
|
||||
// 调用
|
||||
ipqcService.finishIpqc(ipqc.getId());
|
||||
|
||||
// 断言 1:状态更新为已完成
|
||||
MesQcIpqcDO updatedIpqc = ipqcMapper.selectById(ipqc.getId());
|
||||
assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedIpqc.getStatus());
|
||||
|
||||
// 断言 2:调用了 feedbackService,传递 sourceLineId
|
||||
// 注意:数量经过 DB 存取后 scale 可能变化(例如 80 → 80.00),所以用 any() 匹配
|
||||
verify(feedbackService).updateProFeedbackWhenIpqcFinish(
|
||||
eq(sourceDocId),
|
||||
eq(sourceLineId),
|
||||
any(BigDecimal.class),
|
||||
any(BigDecimal.class),
|
||||
any(BigDecimal.class),
|
||||
any(BigDecimal.class),
|
||||
any(BigDecimal.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishIpqc_writeBack_feedback_withNullQuantities() {
|
||||
// 准备数据:合格品/不合格品/废品数量为 null 的场景,应该 defaultIfNull 为 0
|
||||
Long sourceDocId = randomLongId();
|
||||
Long sourceLineId = randomLongId();
|
||||
MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
|
||||
o.setCheckResult(1);
|
||||
o.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
|
||||
o.setSourceDocId(sourceDocId);
|
||||
o.setSourceLineId(sourceLineId);
|
||||
o.setQualifiedQuantity(null);
|
||||
o.setUnqualifiedQuantity(null);
|
||||
o.setLaborScrapQuantity(null);
|
||||
o.setMaterialScrapQuantity(null);
|
||||
o.setOtherScrapQuantity(null);
|
||||
});
|
||||
ipqcMapper.insert(ipqc);
|
||||
|
||||
// 调用
|
||||
ipqcService.finishIpqc(ipqc.getId());
|
||||
|
||||
// 断言:所有数量参数都是 BigDecimal.ZERO(defaultIfNull 处理)
|
||||
verify(feedbackService).updateProFeedbackWhenIpqcFinish(
|
||||
eq(sourceDocId),
|
||||
eq(sourceLineId),
|
||||
eq(BigDecimal.ZERO),
|
||||
eq(BigDecimal.ZERO),
|
||||
eq(BigDecimal.ZERO),
|
||||
eq(BigDecimal.ZERO),
|
||||
eq(BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishIpqc_writeBack_noSourceDoc() {
|
||||
// 准备数据:sourceDocType 为空,不需要回写
|
||||
MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
|
||||
o.setCheckResult(1);
|
||||
o.setSourceDocType(null);
|
||||
o.setSourceDocId(null);
|
||||
o.setSourceLineId(null);
|
||||
});
|
||||
ipqcMapper.insert(ipqc);
|
||||
|
||||
// 调用
|
||||
ipqcService.finishIpqc(ipqc.getId());
|
||||
|
||||
// 断言 1:状态更新成功
|
||||
MesQcIpqcDO updatedIpqc = ipqcMapper.selectById(ipqc.getId());
|
||||
assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedIpqc.getStatus());
|
||||
|
||||
// 断言 2:不应该调用 feedbackService
|
||||
verify(feedbackService, never()).updateProFeedbackWhenIpqcFinish(
|
||||
anyLong(), anyLong(), any(), any(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishIpqc_writeBack_sourceDocTypeDirtyData() {
|
||||
// 准备数据:sourceDocType 非空但 sourceDocId 为空,应该抛异常
|
||||
MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
|
||||
o.setCheckResult(1);
|
||||
o.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
|
||||
o.setSourceDocId(null); // 脏数据
|
||||
o.setSourceLineId(null);
|
||||
});
|
||||
ipqcMapper.insert(ipqc);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertThrows(IllegalArgumentException.class, () -> ipqcService.finishIpqc(ipqc.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishIpqc_writeBack_unknownSourceDocType() {
|
||||
// 准备数据:sourceDocType 无法识别
|
||||
MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
|
||||
o.setCheckResult(1);
|
||||
o.setSourceDocType(99999); // 无法识别的类型
|
||||
o.setSourceDocId(randomLongId());
|
||||
o.setSourceLineId(null);
|
||||
});
|
||||
ipqcMapper.insert(ipqc);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertThrows(IllegalArgumentException.class, () -> ipqcService.finishIpqc(ipqc.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishIpqc_checkResultEmpty() {
|
||||
// 准备数据:checkResult 为空,应该报错
|
||||
MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
|
||||
o.setCheckResult(null); // 检测结论为空
|
||||
o.setSourceDocType(null);
|
||||
});
|
||||
ipqcMapper.insert(ipqc);
|
||||
|
||||
// 调用,应该抛异常(检测结论必填)
|
||||
assertThrows(Exception.class, () -> ipqcService.finishIpqc(ipqc.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishIpqc_notDraftStatus() {
|
||||
// 准备数据:已完成状态,不能再次完成
|
||||
MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.FINISHED.getStatus());
|
||||
o.setCheckResult(1);
|
||||
});
|
||||
ipqcMapper.insert(ipqc);
|
||||
|
||||
// 调用,应该抛异常(不是草稿状态)
|
||||
assertThrows(Exception.class, () -> ipqcService.finishIpqc(ipqc.getId()));
|
||||
}
|
||||
|
||||
// ==================== processId 推导行为测试 ====================
|
||||
|
||||
/**
|
||||
* 工位有工序 + 该工序在产品工艺路线中 → processId 应该被设置为工位工序
|
||||
*/
|
||||
@Test
|
||||
public void testCreateIpqc_processId_workstationHasProcess() {
|
||||
// 准备参数
|
||||
Long workstationId = randomLongId();
|
||||
Long workOrderId = randomLongId();
|
||||
Long productId = randomLongId();
|
||||
Long processId = randomLongId();
|
||||
Long routeId = randomLongId();
|
||||
Long templateId = randomLongId();
|
||||
|
||||
MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
|
||||
reqVO.setCode("IPQC-TEST-001");
|
||||
reqVO.setWorkstationId(workstationId);
|
||||
reqVO.setWorkOrderId(workOrderId);
|
||||
reqVO.setInspectorUserId(randomLongId());
|
||||
|
||||
// mock 工位返回有工序
|
||||
MesMdWorkstationDO workstation = new MesMdWorkstationDO();
|
||||
workstation.setId(workstationId);
|
||||
workstation.setProcessId(processId);
|
||||
when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
|
||||
// mock 工单
|
||||
MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
|
||||
workOrder.setId(workOrderId);
|
||||
workOrder.setProductId(productId);
|
||||
when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
|
||||
when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
|
||||
// mock 模板匹配
|
||||
MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
|
||||
templateItem.setTemplateId(templateId);
|
||||
when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
|
||||
// mock 工艺路线
|
||||
MesProRouteProductDO routeProduct = new MesProRouteProductDO();
|
||||
routeProduct.setRouteId(routeId);
|
||||
when(routeProductService.getRouteProductByItemId(productId)).thenReturn(routeProduct);
|
||||
// mock 工序在路线中
|
||||
when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
|
||||
.thenReturn(new MesProRouteProcessDO());
|
||||
|
||||
// 调用
|
||||
Long ipqcId = ipqcService.createIpqc(reqVO);
|
||||
|
||||
// 断言:processId 应该是工位工序
|
||||
MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId);
|
||||
assertEquals(processId, ipqc.getProcessId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 工位没有工序(processId=null)→ processId 应该留 null(不回退到任务工序)
|
||||
*/
|
||||
@Test
|
||||
public void testCreateIpqc_processId_workstationNoProcess_shouldBeNull() {
|
||||
// 准备参数
|
||||
Long workstationId = randomLongId();
|
||||
Long workOrderId = randomLongId();
|
||||
Long productId = randomLongId();
|
||||
Long taskId = randomLongId();
|
||||
Long templateId = randomLongId();
|
||||
|
||||
MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
|
||||
reqVO.setCode("IPQC-TEST-002");
|
||||
reqVO.setWorkstationId(workstationId);
|
||||
reqVO.setWorkOrderId(workOrderId);
|
||||
reqVO.setTaskId(taskId); // 故意传入 taskId,验证不会回退
|
||||
reqVO.setInspectorUserId(randomLongId());
|
||||
|
||||
// mock 工位没有工序
|
||||
MesMdWorkstationDO workstation = new MesMdWorkstationDO();
|
||||
workstation.setId(workstationId);
|
||||
workstation.setProcessId(null); // 无工序
|
||||
when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
|
||||
// mock 工单
|
||||
MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
|
||||
workOrder.setId(workOrderId);
|
||||
workOrder.setProductId(productId);
|
||||
when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
|
||||
when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
|
||||
// mock 任务
|
||||
MesProTaskDO task = new MesProTaskDO();
|
||||
task.setId(taskId);
|
||||
task.setWorkOrderId(workOrderId);
|
||||
task.setWorkstationId(workstationId);
|
||||
task.setItemId(productId);
|
||||
when(taskService.validateTaskNotFinished(taskId)).thenReturn(task);
|
||||
// mock 模板匹配
|
||||
MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
|
||||
templateItem.setTemplateId(templateId);
|
||||
when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
|
||||
|
||||
// 调用
|
||||
Long ipqcId = ipqcService.createIpqc(reqVO);
|
||||
|
||||
// 断言:processId 应该是 null,即使传入了 taskId 也不应该回退
|
||||
MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId);
|
||||
assertNull(ipqc.getProcessId(),
|
||||
"工位无工序时 processId 应为 null,不应回退到任务工序");
|
||||
// 断言:不应该读取任务的 processId
|
||||
verify(routeProductService, never()).getRouteProductByItemId(any());
|
||||
}
|
||||
|
||||
/**
|
||||
* 工位有工序但该工序不在产品工艺路线中 → processId 应该留 null
|
||||
*/
|
||||
@Test
|
||||
public void testCreateIpqc_processId_processNotInRoute() {
|
||||
// 准备参数
|
||||
Long workstationId = randomLongId();
|
||||
Long workOrderId = randomLongId();
|
||||
Long productId = randomLongId();
|
||||
Long processId = randomLongId();
|
||||
Long routeId = randomLongId();
|
||||
Long templateId = randomLongId();
|
||||
|
||||
MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
|
||||
reqVO.setCode("IPQC-TEST-003");
|
||||
reqVO.setWorkstationId(workstationId);
|
||||
reqVO.setWorkOrderId(workOrderId);
|
||||
reqVO.setInspectorUserId(randomLongId());
|
||||
|
||||
// mock 工位有工序
|
||||
MesMdWorkstationDO workstation = new MesMdWorkstationDO();
|
||||
workstation.setId(workstationId);
|
||||
workstation.setProcessId(processId);
|
||||
when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
|
||||
// mock 工单
|
||||
MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
|
||||
workOrder.setId(workOrderId);
|
||||
workOrder.setProductId(productId);
|
||||
when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
|
||||
when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
|
||||
// mock 模板匹配
|
||||
MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
|
||||
templateItem.setTemplateId(templateId);
|
||||
when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
|
||||
// mock 工艺路线存在
|
||||
MesProRouteProductDO routeProduct = new MesProRouteProductDO();
|
||||
routeProduct.setRouteId(routeId);
|
||||
when(routeProductService.getRouteProductByItemId(productId)).thenReturn(routeProduct);
|
||||
// mock 工序不在路线中
|
||||
when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
|
||||
.thenReturn(null);
|
||||
|
||||
// 调用
|
||||
Long ipqcId = ipqcService.createIpqc(reqVO);
|
||||
|
||||
// 断言:processId 应该是 null
|
||||
MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId);
|
||||
assertNull(ipqc.getProcessId());
|
||||
}
|
||||
|
||||
// ==================== sourceLineId 校验测试 ====================
|
||||
|
||||
/**
|
||||
* sourceDocType=PRO_FEEDBACK 时 sourceLineId 为空 → 创建失败
|
||||
*/
|
||||
@Test
|
||||
public void testCreateIpqc_sourceLineId_required() {
|
||||
// 准备参数
|
||||
Long workstationId = randomLongId();
|
||||
Long workOrderId = randomLongId();
|
||||
Long productId = randomLongId();
|
||||
Long feedbackId = randomLongId();
|
||||
Long templateId = randomLongId();
|
||||
|
||||
MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
|
||||
reqVO.setCode("IPQC-SL-001");
|
||||
reqVO.setWorkstationId(workstationId);
|
||||
reqVO.setWorkOrderId(workOrderId);
|
||||
reqVO.setInspectorUserId(randomLongId());
|
||||
reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
|
||||
reqVO.setSourceDocId(feedbackId);
|
||||
reqVO.setSourceLineId(null); // 不传 sourceLineId
|
||||
|
||||
// mock
|
||||
MesMdWorkstationDO workstation = new MesMdWorkstationDO();
|
||||
workstation.setId(workstationId);
|
||||
workstation.setProcessId(null);
|
||||
when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
|
||||
MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
|
||||
workOrder.setId(workOrderId);
|
||||
workOrder.setProductId(productId);
|
||||
when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
|
||||
when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
|
||||
MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
|
||||
templateItem.setTemplateId(templateId);
|
||||
when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
|
||||
when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(new MesProFeedbackDO());
|
||||
|
||||
// 调用,断言异常
|
||||
ServiceException ex = assertThrows(ServiceException.class, () -> ipqcService.createIpqc(reqVO));
|
||||
assertEquals(QC_IPQC_SOURCE_LINE_REQUIRED.getCode(), ex.getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* sourceLineId 不属于该报工的产出行 → 创建失败
|
||||
*/
|
||||
@Test
|
||||
public void testCreateIpqc_sourceLineId_notBelong() {
|
||||
// 准备参数
|
||||
Long workstationId = randomLongId();
|
||||
Long workOrderId = randomLongId();
|
||||
Long productId = randomLongId();
|
||||
Long feedbackId = randomLongId();
|
||||
Long sourceLineId = randomLongId();
|
||||
Long templateId = randomLongId();
|
||||
|
||||
MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
|
||||
reqVO.setCode("IPQC-SL-002");
|
||||
reqVO.setWorkstationId(workstationId);
|
||||
reqVO.setWorkOrderId(workOrderId);
|
||||
reqVO.setInspectorUserId(randomLongId());
|
||||
reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
|
||||
reqVO.setSourceDocId(feedbackId);
|
||||
reqVO.setSourceLineId(sourceLineId);
|
||||
|
||||
// mock
|
||||
MesMdWorkstationDO workstation = new MesMdWorkstationDO();
|
||||
workstation.setId(workstationId);
|
||||
workstation.setProcessId(null);
|
||||
when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
|
||||
MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
|
||||
workOrder.setId(workOrderId);
|
||||
workOrder.setProductId(productId);
|
||||
when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
|
||||
when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
|
||||
MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
|
||||
templateItem.setTemplateId(templateId);
|
||||
when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
|
||||
when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(new MesProFeedbackDO());
|
||||
// mock:产出行存在但 feedbackId 不匹配
|
||||
MesWmProductProduceLineDO line = new MesWmProductProduceLineDO();
|
||||
line.setId(sourceLineId);
|
||||
line.setFeedbackId(randomLongId()); // 不同的 feedbackId
|
||||
line.setQualityStatus(MesWmQualityStatusEnum.PENDING.getStatus());
|
||||
when(productProduceLineService.validateProductProduceLineExists(sourceLineId)).thenReturn(line);
|
||||
|
||||
// 调用,断言异常
|
||||
ServiceException ex = assertThrows(ServiceException.class, () -> ipqcService.createIpqc(reqVO));
|
||||
assertEquals(QC_IPQC_SOURCE_LINE_NOT_BELONG.getCode(), ex.getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* sourceLineId 对应的产出行不是待检验状态 → 创建失败
|
||||
*/
|
||||
@Test
|
||||
public void testCreateIpqc_sourceLineId_notPending() {
|
||||
// 准备参数
|
||||
Long workstationId = randomLongId();
|
||||
Long workOrderId = randomLongId();
|
||||
Long productId = randomLongId();
|
||||
Long feedbackId = randomLongId();
|
||||
Long sourceLineId = randomLongId();
|
||||
Long templateId = randomLongId();
|
||||
|
||||
MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
|
||||
reqVO.setCode("IPQC-SL-003");
|
||||
reqVO.setWorkstationId(workstationId);
|
||||
reqVO.setWorkOrderId(workOrderId);
|
||||
reqVO.setInspectorUserId(randomLongId());
|
||||
reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
|
||||
reqVO.setSourceDocId(feedbackId);
|
||||
reqVO.setSourceLineId(sourceLineId);
|
||||
|
||||
// mock
|
||||
MesMdWorkstationDO workstation = new MesMdWorkstationDO();
|
||||
workstation.setId(workstationId);
|
||||
workstation.setProcessId(null);
|
||||
when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
|
||||
MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
|
||||
workOrder.setId(workOrderId);
|
||||
workOrder.setProductId(productId);
|
||||
when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
|
||||
when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
|
||||
MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
|
||||
templateItem.setTemplateId(templateId);
|
||||
when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
|
||||
when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(new MesProFeedbackDO());
|
||||
// mock:产出行存在且归属正确,但不是 PENDING 状态
|
||||
MesWmProductProduceLineDO line = new MesWmProductProduceLineDO();
|
||||
line.setId(sourceLineId);
|
||||
line.setFeedbackId(feedbackId);
|
||||
line.setQualityStatus(MesWmQualityStatusEnum.PASS.getStatus()); // 已合格,非待检
|
||||
when(productProduceLineService.validateProductProduceLineExists(sourceLineId)).thenReturn(line);
|
||||
|
||||
// 调用,断言异常
|
||||
ServiceException ex = assertThrows(ServiceException.class, () -> ipqcService.createIpqc(reqVO));
|
||||
assertEquals(QC_IPQC_SOURCE_LINE_NOT_PENDING.getCode(), ex.getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* sourceLineId 校验全部通过 → 创建成功
|
||||
*/
|
||||
@Test
|
||||
public void testCreateIpqc_sourceLineId_success() {
|
||||
// 准备参数
|
||||
Long workstationId = randomLongId();
|
||||
Long workOrderId = randomLongId();
|
||||
Long productId = randomLongId();
|
||||
Long feedbackId = randomLongId();
|
||||
Long sourceLineId = randomLongId();
|
||||
Long templateId = randomLongId();
|
||||
|
||||
MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
|
||||
reqVO.setCode("IPQC-SL-004");
|
||||
reqVO.setWorkstationId(workstationId);
|
||||
reqVO.setWorkOrderId(workOrderId);
|
||||
reqVO.setInspectorUserId(randomLongId());
|
||||
reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
|
||||
reqVO.setSourceDocId(feedbackId);
|
||||
reqVO.setSourceLineId(sourceLineId);
|
||||
|
||||
// mock
|
||||
MesMdWorkstationDO workstation = new MesMdWorkstationDO();
|
||||
workstation.setId(workstationId);
|
||||
workstation.setProcessId(null);
|
||||
when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
|
||||
MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
|
||||
workOrder.setId(workOrderId);
|
||||
workOrder.setProductId(productId);
|
||||
when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
|
||||
when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
|
||||
MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
|
||||
templateItem.setTemplateId(templateId);
|
||||
when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
|
||||
MesProFeedbackDO feedback = new MesProFeedbackDO();
|
||||
feedback.setId(feedbackId);
|
||||
feedback.setCode("FB-001");
|
||||
when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(feedback);
|
||||
// mock:产出行存在、归属正确、PENDING 状态
|
||||
MesWmProductProduceLineDO line = new MesWmProductProduceLineDO();
|
||||
line.setId(sourceLineId);
|
||||
line.setFeedbackId(feedbackId);
|
||||
line.setQualityStatus(MesWmQualityStatusEnum.PENDING.getStatus());
|
||||
when(productProduceLineService.validateProductProduceLineExists(sourceLineId)).thenReturn(line);
|
||||
|
||||
// 调用
|
||||
Long ipqcId = ipqcService.createIpqc(reqVO);
|
||||
|
||||
// 断言:创建成功,sourceDocCode 回写
|
||||
MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId);
|
||||
assertNotNull(ipqc);
|
||||
assertEquals(feedbackId, ipqc.getSourceDocId());
|
||||
assertEquals(sourceLineId, ipqc.getSourceLineId());
|
||||
assertEquals("FB-001", ipqc.getSourceDocCode());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package cn.iocoder.yudao.module.mes.service.qc.iqc;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.qc.iqc.MesQcIqcMapper;
|
||||
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.item.MesMdItemService;
|
||||
import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService;
|
||||
import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
|
||||
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.outsourcereceipt.MesWmOutsourceReceiptService;
|
||||
import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
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.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link MesQcIqcServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesQcIqcServiceImpl.class)
|
||||
public class MesQcIqcServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesQcIqcServiceImpl iqcService;
|
||||
|
||||
@Resource
|
||||
private MesQcIqcMapper iqcMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesWmArrivalNoticeService arrivalNoticeService;
|
||||
@MockitoBean
|
||||
private MesWmOutsourceReceiptService outsourceReceiptService;
|
||||
@MockitoBean
|
||||
private MesQcIqcLineService iqcLineService;
|
||||
@MockitoBean
|
||||
private MesQcDefectRecordService defectRecordService;
|
||||
@MockitoBean
|
||||
private MesMdVendorService vendorService;
|
||||
@MockitoBean
|
||||
private MesMdItemService itemService;
|
||||
@MockitoBean
|
||||
private MesQcTemplateItemService templateItemService;
|
||||
@MockitoBean
|
||||
private AdminUserApi adminUserApi;
|
||||
@MockitoBean
|
||||
private MesQcIndicatorResultService indicatorResultService;
|
||||
|
||||
@Test
|
||||
public void testFinishIqc_writeBack_arrivalNotice() {
|
||||
// 准备数据:插入一条草稿状态的 IQC 单,来源为到货通知单
|
||||
Long sourceDocId = randomLongId();
|
||||
Long sourceLineId = randomLongId();
|
||||
MesQcIqcDO iqc = randomPojo(MesQcIqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
|
||||
o.setCheckResult(1); // 非空,满足 finishIqc 的校验
|
||||
o.setSourceDocType(MesBizTypeConstants.WM_ARRIVAL_NOTICE);
|
||||
o.setSourceDocId(sourceDocId);
|
||||
o.setSourceLineId(sourceLineId);
|
||||
o.setQualifiedQuantity(BigDecimal.valueOf(100));
|
||||
o.setUnqualifiedQuantity(BigDecimal.ZERO);
|
||||
});
|
||||
iqcMapper.insert(iqc);
|
||||
|
||||
// 调用
|
||||
iqcService.finishIqc(iqc.getId());
|
||||
|
||||
// 断言:验证状态更新为已完成
|
||||
MesQcIqcDO updatedIqc = iqcMapper.selectById(iqc.getId());
|
||||
assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedIqc.getStatus());
|
||||
// 断言:验证回写到货通知单(参数使用 any 避免 BigDecimal 精度问题)
|
||||
verify(arrivalNoticeService).updateArrivalNoticeWhenIqcFinish(
|
||||
eq(sourceDocId), eq(sourceLineId), eq(iqc.getId()), any(BigDecimal.class));
|
||||
// 断言:不应该调用外协入库单的回写
|
||||
verify(outsourceReceiptService, never()).updateOutsourceReceiptWhenIqcFinish(
|
||||
anyLong(), anyLong(), anyLong(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishIqc_writeBack_outsourceReceipt() {
|
||||
// 准备数据:插入一条草稿状态的 IQC 单,来源为外协入库单
|
||||
Long sourceDocId = randomLongId();
|
||||
Long sourceLineId = randomLongId();
|
||||
MesQcIqcDO iqc = randomPojo(MesQcIqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
|
||||
o.setCheckResult(1);
|
||||
o.setSourceDocType(MesBizTypeConstants.WM_OUTSOURCE_RECPT);
|
||||
o.setSourceDocId(sourceDocId);
|
||||
o.setSourceLineId(sourceLineId);
|
||||
o.setQualifiedQuantity(BigDecimal.valueOf(80));
|
||||
o.setUnqualifiedQuantity(BigDecimal.valueOf(20));
|
||||
});
|
||||
iqcMapper.insert(iqc);
|
||||
|
||||
// 调用
|
||||
iqcService.finishIqc(iqc.getId());
|
||||
|
||||
// 断言:验证状态更新为已完成
|
||||
MesQcIqcDO updatedIqc = iqcMapper.selectById(iqc.getId());
|
||||
assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedIqc.getStatus());
|
||||
// 断言:验证回写外协入库单(参数使用 any 避免 BigDecimal 精度问题)
|
||||
verify(outsourceReceiptService).updateOutsourceReceiptWhenIqcFinish(
|
||||
eq(sourceDocId), eq(sourceLineId), eq(iqc.getId()), any(BigDecimal.class), any(BigDecimal.class));
|
||||
// 断言:不应该调用到货通知单的回写
|
||||
verify(arrivalNoticeService, never()).updateArrivalNoticeWhenIqcFinish(
|
||||
anyLong(), anyLong(), anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishIqc_writeBack_noSourceDoc() {
|
||||
// 准备数据:sourceDocType 为空,不需要回写
|
||||
MesQcIqcDO iqc = randomPojo(MesQcIqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
|
||||
o.setCheckResult(1);
|
||||
o.setSourceDocType(null);
|
||||
o.setSourceDocId(null);
|
||||
o.setSourceLineId(null);
|
||||
});
|
||||
iqcMapper.insert(iqc);
|
||||
|
||||
// 调用
|
||||
iqcService.finishIqc(iqc.getId());
|
||||
|
||||
// 断言:验证状态更新为已完成
|
||||
MesQcIqcDO updatedIqc = iqcMapper.selectById(iqc.getId());
|
||||
assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedIqc.getStatus());
|
||||
// 断言:不应该调用任何回写
|
||||
verify(arrivalNoticeService, never()).updateArrivalNoticeWhenIqcFinish(
|
||||
anyLong(), anyLong(), anyLong(), any());
|
||||
verify(outsourceReceiptService, never()).updateOutsourceReceiptWhenIqcFinish(
|
||||
anyLong(), anyLong(), anyLong(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishIqc_writeBack_sourceDocTypeDirtyData() {
|
||||
// 准备数据:sourceDocType 非空但 sourceLineId 为空,应该抛异常
|
||||
MesQcIqcDO iqc = randomPojo(MesQcIqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
|
||||
o.setCheckResult(1);
|
||||
o.setSourceDocType(MesBizTypeConstants.WM_ARRIVAL_NOTICE);
|
||||
o.setSourceDocId(randomLongId());
|
||||
o.setSourceLineId(null); // 脏数据
|
||||
});
|
||||
iqcMapper.insert(iqc);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertThrows(IllegalArgumentException.class, () -> iqcService.finishIqc(iqc.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishIqc_writeBack_unknownSourceDocType() {
|
||||
// 准备数据:sourceDocType 无法识别
|
||||
MesQcIqcDO iqc = randomPojo(MesQcIqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
|
||||
o.setCheckResult(1);
|
||||
o.setSourceDocType(99999); // 无法识别的类型
|
||||
o.setSourceDocId(randomLongId());
|
||||
o.setSourceLineId(randomLongId());
|
||||
});
|
||||
iqcMapper.insert(iqc);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertThrows(IllegalArgumentException.class, () -> iqcService.finishIqc(iqc.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishIqc_checkResultEmpty() {
|
||||
// 准备数据:checkResult 为空,应该报错
|
||||
MesQcIqcDO iqc = randomPojo(MesQcIqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
|
||||
o.setCheckResult(null); // 检测结论为空
|
||||
o.setSourceDocType(null);
|
||||
});
|
||||
iqcMapper.insert(iqc);
|
||||
|
||||
// 调用,应该抛异常(检测结论必填)
|
||||
assertThrows(Exception.class, () -> iqcService.finishIqc(iqc.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishIqc_notDraftStatus() {
|
||||
// 准备数据:已完成状态,不能再次完成
|
||||
MesQcIqcDO iqc = randomPojo(MesQcIqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.FINISHED.getStatus());
|
||||
o.setCheckResult(1);
|
||||
});
|
||||
iqcMapper.insert(iqc);
|
||||
|
||||
// 调用,应该抛异常(不是草稿状态)
|
||||
assertThrows(Exception.class, () -> iqcService.finishIqc(iqc.getId()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package cn.iocoder.yudao.module.mes.service.qc.oqc;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
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.MesQcTemplateItemService;
|
||||
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.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
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.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link MesQcOqcServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesQcOqcServiceImpl.class)
|
||||
public class MesQcOqcServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesQcOqcServiceImpl oqcService;
|
||||
|
||||
@Resource
|
||||
private MesQcOqcMapper oqcMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesWmProductSalesLineService productSalesLineService;
|
||||
@MockitoBean
|
||||
private MesWmProductSalesService productSalesService;
|
||||
@MockitoBean
|
||||
private MesQcOqcLineService oqcLineService;
|
||||
@MockitoBean
|
||||
private MesMdClientService clientService;
|
||||
@MockitoBean
|
||||
private MesMdItemService itemService;
|
||||
@MockitoBean
|
||||
private MesQcTemplateItemService templateItemService;
|
||||
@MockitoBean
|
||||
private MesQcDefectRecordService defectRecordService;
|
||||
@MockitoBean
|
||||
private AdminUserApi adminUserApi;
|
||||
@MockitoBean
|
||||
private MesQcIndicatorResultService indicatorResultService;
|
||||
|
||||
@Test
|
||||
public void testFinishOqc_writeBack_productSales() {
|
||||
// 准备数据:插入一条草稿状态的 OQC 单,来源为销售出库单
|
||||
Long sourceLineId = randomLongId();
|
||||
Integer checkResult = 1;
|
||||
MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
|
||||
o.setSourceDocType(MesBizTypeConstants.WM_PRODUCT_SALES);
|
||||
o.setSourceLineId(sourceLineId);
|
||||
o.setCheckResult(checkResult);
|
||||
});
|
||||
oqcMapper.insert(oqc);
|
||||
|
||||
// 调用
|
||||
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));
|
||||
}
|
||||
|
||||
@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
|
||||
public void testFinishOqc_notExists() {
|
||||
// 准备参数
|
||||
Long oqcId = randomLongId();
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> oqcService.finishOqc(oqcId), QC_OQC_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishOqc_statusNotDraft() {
|
||||
// 准备参数:状态为已完成
|
||||
MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.FINISHED.getStatus());
|
||||
});
|
||||
oqcMapper.insert(oqc);
|
||||
|
||||
// 调用,并断言异常
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package cn.iocoder.yudao.module.mes.service.qc.rqc;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.rqc.MesQcRqcDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.qc.rqc.MesQcRqcMapper;
|
||||
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.item.MesMdItemService;
|
||||
import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
|
||||
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.returnissue.MesWmReturnIssueLineService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.returnsales.MesWmReturnSalesLineService;
|
||||
import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
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_RQC_NOT_EXISTS;
|
||||
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.QC_RQC_NOT_PREPARE;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* {@link MesQcRqcServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesQcRqcServiceImpl.class)
|
||||
public class MesQcRqcServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesQcRqcServiceImpl rqcService;
|
||||
|
||||
@Resource
|
||||
private MesQcRqcMapper rqcMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesWmReturnIssueLineService returnIssueLineService;
|
||||
@MockitoBean
|
||||
private MesWmReturnSalesLineService returnSalesLineService;
|
||||
@MockitoBean
|
||||
private MesQcRqcLineService rqcLineService;
|
||||
@MockitoBean
|
||||
private MesMdItemService itemService;
|
||||
@MockitoBean
|
||||
private MesQcTemplateItemService templateItemService;
|
||||
@MockitoBean
|
||||
private MesQcDefectRecordService defectRecordService;
|
||||
@MockitoBean
|
||||
private AdminUserApi adminUserApi;
|
||||
@MockitoBean
|
||||
private MesQcIndicatorResultService indicatorResultService;
|
||||
|
||||
@Test
|
||||
public void testFinishRqc_successWithReturnIssue() {
|
||||
// 准备参数
|
||||
Long sourceLineId = randomLongId();
|
||||
Long sourceDocId = randomLongId();
|
||||
Integer checkResult = 1;
|
||||
BigDecimal qualifiedQty = new BigDecimal("80");
|
||||
BigDecimal unqualifiedQty = new BigDecimal("20");
|
||||
|
||||
// mock 数据
|
||||
MesQcRqcDO rqc = randomPojo(MesQcRqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
|
||||
o.setSourceDocType(MesBizTypeConstants.WM_RETURN_ISSUE);
|
||||
o.setSourceDocId(sourceDocId);
|
||||
o.setSourceLineId(sourceLineId);
|
||||
o.setCheckResult(checkResult);
|
||||
// 设置数量通过 finishRqc 的数量校验:合格 + 不合格 = 检测数量
|
||||
o.setCheckQuantity(new BigDecimal("100"));
|
||||
o.setQualifiedQuantity(qualifiedQty);
|
||||
o.setUnqualifiedQuantity(unqualifiedQty);
|
||||
});
|
||||
rqcMapper.insert(rqc);
|
||||
|
||||
// 调用
|
||||
rqcService.finishRqc(rqc.getId());
|
||||
|
||||
// 断言:验证状态更新
|
||||
MesQcRqcDO updatedRqc = rqcMapper.selectById(rqc.getId());
|
||||
assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedRqc.getStatus());
|
||||
|
||||
// 断言:验证回写调用生产退料(含拆分参数)
|
||||
verify(returnIssueLineService).updateReturnIssueLineWhenRqcFinish(
|
||||
eq(sourceLineId), eq(sourceDocId), eq(checkResult),
|
||||
argThat(v -> v.compareTo(qualifiedQty) == 0),
|
||||
argThat(v -> v.compareTo(unqualifiedQty) == 0));
|
||||
// 断言:未调用销售退货
|
||||
verify(returnSalesLineService, never()).updateReturnSalesLineWhenRqcFinish(
|
||||
anyLong(), anyLong(), anyInt(), any(BigDecimal.class), any(BigDecimal.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishRqc_successWithReturnSales() {
|
||||
// 准备参数
|
||||
Long sourceLineId = randomLongId();
|
||||
Long sourceDocId = randomLongId();
|
||||
Integer checkResult = 1;
|
||||
BigDecimal qualifiedQty = new BigDecimal("90");
|
||||
BigDecimal unqualifiedQty = new BigDecimal("10");
|
||||
|
||||
// mock 数据
|
||||
MesQcRqcDO rqc = randomPojo(MesQcRqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
|
||||
o.setSourceDocType(MesBizTypeConstants.WM_RETURN_SALES);
|
||||
o.setSourceDocId(sourceDocId);
|
||||
o.setSourceLineId(sourceLineId);
|
||||
o.setCheckResult(checkResult);
|
||||
// 设置数量通过 finishRqc 的数量校验:合格 + 不合格 = 检测数量
|
||||
o.setCheckQuantity(new BigDecimal("100"));
|
||||
o.setQualifiedQuantity(qualifiedQty);
|
||||
o.setUnqualifiedQuantity(unqualifiedQty);
|
||||
});
|
||||
rqcMapper.insert(rqc);
|
||||
|
||||
// 调用
|
||||
rqcService.finishRqc(rqc.getId());
|
||||
|
||||
// 断言:验证状态更新
|
||||
MesQcRqcDO updatedRqc = rqcMapper.selectById(rqc.getId());
|
||||
assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedRqc.getStatus());
|
||||
|
||||
// 断言:验证回写调用销售退货(含拆分参数)
|
||||
verify(returnSalesLineService).updateReturnSalesLineWhenRqcFinish(
|
||||
eq(sourceLineId), eq(sourceDocId), eq(checkResult),
|
||||
argThat(v -> v.compareTo(qualifiedQty) == 0),
|
||||
argThat(v -> v.compareTo(unqualifiedQty) == 0));
|
||||
// 断言:未调用生产退料
|
||||
verify(returnIssueLineService, never()).updateReturnIssueLineWhenRqcFinish(
|
||||
anyLong(), anyLong(), anyInt(), any(BigDecimal.class), any(BigDecimal.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishRqc_notExists() {
|
||||
// 准备参数
|
||||
Long rqcId = randomLongId();
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> rqcService.finishRqc(rqcId), QC_RQC_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishRqc_statusNotDraft() {
|
||||
// 准备参数
|
||||
// mock 数据:状态为已完成
|
||||
MesQcRqcDO rqc = randomPojo(MesQcRqcDO.class, o -> {
|
||||
o.setStatus(MesQcStatusEnum.FINISHED.getStatus());
|
||||
});
|
||||
rqcMapper.insert(rqc);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> rqcService.finishRqc(rqc.getId()), QC_RQC_NOT_PREPARE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package cn.iocoder.yudao.module.mes.service.wm.arrivalnotice;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeMapper;
|
||||
import cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum;
|
||||
import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
|
||||
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.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link MesWmArrivalNoticeServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesWmArrivalNoticeServiceImpl.class)
|
||||
public class MesWmArrivalNoticeServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesWmArrivalNoticeServiceImpl arrivalNoticeService;
|
||||
|
||||
@Resource
|
||||
private MesWmArrivalNoticeMapper arrivalNoticeMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesWmArrivalNoticeLineService arrivalNoticeLineService;
|
||||
@MockitoBean
|
||||
private MesMdVendorService vendorService;
|
||||
|
||||
@Test
|
||||
public void testUpdateArrivalNoticeWhenIqcFinish_success_allLinesChecked() {
|
||||
// mock 数据:插入一条待质检的到货通知单
|
||||
MesWmArrivalNoticeDO notice = randomPojo(MesWmArrivalNoticeDO.class, o -> {
|
||||
o.setStatus(MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus());
|
||||
});
|
||||
arrivalNoticeMapper.insert(notice);
|
||||
// 准备参数
|
||||
Long noticeId = notice.getId();
|
||||
Long lineId = randomLongId();
|
||||
Long iqcId = randomLongId();
|
||||
BigDecimal qualifiedQuantity = BigDecimal.valueOf(100);
|
||||
|
||||
// mock 行服务方法:所有需检行都已完成(iqcId 不为空)
|
||||
MesWmArrivalNoticeLineDO line1 = randomPojo(MesWmArrivalNoticeLineDO.class, o -> {
|
||||
o.setNoticeId(noticeId);
|
||||
o.setIqcCheckFlag(true);
|
||||
o.setIqcId(iqcId); // 已检完
|
||||
});
|
||||
MesWmArrivalNoticeLineDO line2 = randomPojo(MesWmArrivalNoticeLineDO.class, o -> {
|
||||
o.setNoticeId(noticeId);
|
||||
o.setIqcCheckFlag(false); // 不需要检验
|
||||
o.setIqcId(null);
|
||||
});
|
||||
when(arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(eq(noticeId)))
|
||||
.thenReturn(Arrays.asList(line1, line2));
|
||||
|
||||
// 调用
|
||||
arrivalNoticeService.updateArrivalNoticeWhenIqcFinish(noticeId, lineId, iqcId, qualifiedQuantity);
|
||||
|
||||
// 断言:行回写被调用
|
||||
verify(arrivalNoticeLineService).updateArrivalNoticeLineWhenIqcFinish(eq(lineId), eq(iqcId), eq(qualifiedQuantity));
|
||||
// 断言:主表状态更新为待入库
|
||||
MesWmArrivalNoticeDO updatedNotice = arrivalNoticeMapper.selectById(noticeId);
|
||||
assertEquals(MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT.getStatus(), updatedNotice.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateArrivalNoticeWhenIqcFinish_success_hasUncheckedLines() {
|
||||
// mock 数据:插入一条待质检的到货通知单
|
||||
MesWmArrivalNoticeDO notice = randomPojo(MesWmArrivalNoticeDO.class, o -> {
|
||||
o.setStatus(MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus());
|
||||
});
|
||||
arrivalNoticeMapper.insert(notice);
|
||||
// 准备参数
|
||||
Long noticeId = notice.getId();
|
||||
Long lineId = randomLongId();
|
||||
Long iqcId = randomLongId();
|
||||
BigDecimal qualifiedQuantity = BigDecimal.valueOf(50);
|
||||
|
||||
// mock 行服务方法:还有未完成的需检行
|
||||
MesWmArrivalNoticeLineDO line1 = randomPojo(MesWmArrivalNoticeLineDO.class, o -> {
|
||||
o.setNoticeId(noticeId);
|
||||
o.setIqcCheckFlag(true);
|
||||
o.setIqcId(iqcId); // 已检完
|
||||
});
|
||||
MesWmArrivalNoticeLineDO line2 = randomPojo(MesWmArrivalNoticeLineDO.class, o -> {
|
||||
o.setNoticeId(noticeId);
|
||||
o.setIqcCheckFlag(true);
|
||||
o.setIqcId(null); // 还未检验
|
||||
});
|
||||
when(arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(eq(noticeId)))
|
||||
.thenReturn(Arrays.asList(line1, line2));
|
||||
|
||||
// 调用
|
||||
arrivalNoticeService.updateArrivalNoticeWhenIqcFinish(noticeId, lineId, iqcId, qualifiedQuantity);
|
||||
|
||||
// 断言:行回写被调用
|
||||
verify(arrivalNoticeLineService).updateArrivalNoticeLineWhenIqcFinish(eq(lineId), eq(iqcId), eq(qualifiedQuantity));
|
||||
// 断言:主表状态不变,仍然是待质检
|
||||
MesWmArrivalNoticeDO updatedNotice = arrivalNoticeMapper.selectById(noticeId);
|
||||
assertEquals(MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus(), updatedNotice.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateArrivalNoticeWhenIqcFinish_noticeNotExists() {
|
||||
// 准备参数
|
||||
Long noticeId = randomLongId();
|
||||
Long lineId = randomLongId();
|
||||
Long iqcId = randomLongId();
|
||||
BigDecimal qualifiedQuantity = BigDecimal.valueOf(100);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(
|
||||
() -> arrivalNoticeService.updateArrivalNoticeWhenIqcFinish(noticeId, lineId, iqcId, qualifiedQuantity),
|
||||
WM_ARRIVAL_NOTICE_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateArrivalNoticeWhenIqcFinish_statusNotPendingQc() {
|
||||
// mock 数据:插入一条草稿状态的到货通知单(非待质检)
|
||||
MesWmArrivalNoticeDO notice = randomPojo(MesWmArrivalNoticeDO.class, o -> {
|
||||
o.setStatus(MesWmArrivalNoticeStatusEnum.PREPARE.getStatus());
|
||||
});
|
||||
arrivalNoticeMapper.insert(notice);
|
||||
// 准备参数
|
||||
Long noticeId = notice.getId();
|
||||
Long lineId = randomLongId();
|
||||
Long iqcId = randomLongId();
|
||||
BigDecimal qualifiedQuantity = BigDecimal.valueOf(100);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(
|
||||
() -> arrivalNoticeService.updateArrivalNoticeWhenIqcFinish(noticeId, lineId, iqcId, qualifiedQuantity),
|
||||
WM_ARRIVAL_NOTICE_STATUS_NOT_PENDING_QC);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
package cn.iocoder.yudao.module.mes.service.wm.batch;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeDetailDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDetailDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.wm.batch.MesWmBatchMapper;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.wm.itemconsume.MesWmItemConsumeDetailMapper;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.wm.itemconsume.MesWmItemConsumeMapper;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceDetailMapper;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceLineMapper;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceMapper;
|
||||
import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
|
||||
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemBatchConfigService;
|
||||
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.barcode.MesWmBarcodeService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* {@link MesWmBatchServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesWmBatchServiceImpl.class)
|
||||
public class MesWmBatchServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesWmBatchServiceImpl batchService;
|
||||
|
||||
@Resource
|
||||
private MesWmBatchMapper batchMapper;
|
||||
|
||||
@Resource
|
||||
private MesWmItemConsumeMapper consumeMapper;
|
||||
@Resource
|
||||
private MesWmItemConsumeDetailMapper consumeDetailMapper;
|
||||
@Resource
|
||||
private MesProFeedbackMapper feedbackMapper;
|
||||
@Resource
|
||||
private MesWmProductProduceMapper produceMapper;
|
||||
@Resource
|
||||
private MesWmProductProduceLineMapper produceLineMapper;
|
||||
@Resource
|
||||
private MesWmProductProduceDetailMapper produceDetailMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesMdItemService itemService;
|
||||
@MockitoBean
|
||||
private MesMdItemBatchConfigService itemBatchConfigService;
|
||||
@MockitoBean
|
||||
private MesMdAutoCodeRecordService autoCodeRecordService;
|
||||
@MockitoBean
|
||||
private MesWmBarcodeService barcodeService;
|
||||
|
||||
// ==================== 向前追溯 ====================
|
||||
|
||||
@Test
|
||||
public void testGetForwardBatchList_nullCode() {
|
||||
// 传入 null code,应返回空列表(不抛异常)
|
||||
List<MesWmBatchDO> result = batchService.getForwardBatchList(null);
|
||||
assertNotNull(result);
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetForwardBatchList_noResults() {
|
||||
// 传入一个不存在的批次号,XML 查询返回空(因为关联表无数据)
|
||||
List<MesWmBatchDO> result = batchService.getForwardBatchList("NOT_EXIST_CODE");
|
||||
assertNotNull(result);
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
// ==================== 向后追溯 ====================
|
||||
|
||||
@Test
|
||||
public void testGetBackwardBatchList_nullCode() {
|
||||
// 传入 null code,应返回空列表(不抛异常)
|
||||
List<MesWmBatchDO> result = batchService.getBackwardBatchList(null);
|
||||
assertNotNull(result);
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBackwardBatchList_noResults() {
|
||||
// 传入一个不存在的批次号,XML 查询返回空
|
||||
List<MesWmBatchDO> result = batchService.getBackwardBatchList("NOT_EXIST_CODE");
|
||||
assertNotNull(result);
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
// ==================== 循环检测 ====================
|
||||
|
||||
@Test
|
||||
public void testForwardBatchList_sameCodeDoesNotRecurse() {
|
||||
// 验证:传入相同 code 多次调用不会 StackOverflow
|
||||
// 由于数据库中没有环路数据,这里主要验证 visited set 的 null + 空数据安全
|
||||
List<MesWmBatchDO> result = batchService.getForwardBatchList("SAME_CODE");
|
||||
assertNotNull(result);
|
||||
assertTrue(result.isEmpty());
|
||||
|
||||
// 二次调用同一个 code,不应有任何异常
|
||||
List<MesWmBatchDO> result2 = batchService.getForwardBatchList("SAME_CODE");
|
||||
assertNotNull(result2);
|
||||
assertTrue(result2.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackwardBatchList_sameCodeDoesNotRecurse() {
|
||||
// 验证向后追溯的 visited set 安全
|
||||
List<MesWmBatchDO> result = batchService.getBackwardBatchList("SAME_CODE");
|
||||
assertNotNull(result);
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
// ==================== selectFirst ====================
|
||||
|
||||
@Test
|
||||
public void testSelectFirst_noMatch() {
|
||||
// 查询不存在的 itemId,应返回 null
|
||||
MesWmBatchDO query = MesWmBatchDO.builder().itemId(randomLongId()).build();
|
||||
MesWmBatchDO result = batchMapper.selectFirst(query);
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectFirst_returnsSmallestId() {
|
||||
// 准备数据:插入两条相同条件的批次
|
||||
Long itemId = randomLongId();
|
||||
MesWmBatchDO batch1 = MesWmBatchDO.builder()
|
||||
.itemId(itemId).code("BATCH_001").build();
|
||||
MesWmBatchDO batch2 = MesWmBatchDO.builder()
|
||||
.itemId(itemId).code("BATCH_002").build();
|
||||
batchMapper.insert(batch1);
|
||||
batchMapper.insert(batch2);
|
||||
|
||||
// 查询
|
||||
MesWmBatchDO query = MesWmBatchDO.builder().itemId(itemId).build();
|
||||
MesWmBatchDO result = batchMapper.selectFirst(query);
|
||||
|
||||
// 断言:返回 ID 最小的
|
||||
assertNotNull(result);
|
||||
assertEquals(batch1.getId(), result.getId());
|
||||
assertEquals("BATCH_001", result.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectFirst_nullFieldMatching() {
|
||||
// 准备数据:一条有 vendorId,一条没有 vendorId
|
||||
Long itemId = randomLongId();
|
||||
Long vendorId = randomLongId();
|
||||
|
||||
MesWmBatchDO batchWithVendor = MesWmBatchDO.builder()
|
||||
.itemId(itemId).code("BATCH_WITH_V").vendorId(vendorId).build();
|
||||
MesWmBatchDO batchWithoutVendor = MesWmBatchDO.builder()
|
||||
.itemId(itemId).code("BATCH_NO_V").build();
|
||||
batchMapper.insert(batchWithVendor);
|
||||
batchMapper.insert(batchWithoutVendor);
|
||||
|
||||
// 查询:vendorId 为 null -> 应该只匹配 vendorId IS NULL 的记录
|
||||
MesWmBatchDO query = MesWmBatchDO.builder().itemId(itemId).build();
|
||||
MesWmBatchDO result = batchMapper.selectFirst(query);
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("BATCH_NO_V", result.getCode());
|
||||
assertNull(result.getVendorId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectFirst_withVendorId() {
|
||||
// 准备数据
|
||||
Long itemId = randomLongId();
|
||||
Long vendorId = randomLongId();
|
||||
|
||||
MesWmBatchDO batchWithVendor = MesWmBatchDO.builder()
|
||||
.itemId(itemId).code("BATCH_V1").vendorId(vendorId).build();
|
||||
MesWmBatchDO batchWithoutVendor = MesWmBatchDO.builder()
|
||||
.itemId(itemId).code("BATCH_NV").build();
|
||||
batchMapper.insert(batchWithVendor);
|
||||
batchMapper.insert(batchWithoutVendor);
|
||||
|
||||
// 查询:指定 vendorId -> 只匹配有 vendorId 的
|
||||
MesWmBatchDO query = MesWmBatchDO.builder().itemId(itemId).vendorId(vendorId).build();
|
||||
MesWmBatchDO result = batchMapper.selectFirst(query);
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("BATCH_V1", result.getCode());
|
||||
assertEquals(vendorId, result.getVendorId());
|
||||
}
|
||||
|
||||
// ==================== selectByCode ====================
|
||||
|
||||
@Test
|
||||
public void testSelectByCode() {
|
||||
// 准备数据
|
||||
MesWmBatchDO batch = MesWmBatchDO.builder()
|
||||
.itemId(randomLongId()).code("TEST_CODE_001").build();
|
||||
batchMapper.insert(batch);
|
||||
|
||||
// 查询
|
||||
MesWmBatchDO result = batchMapper.selectByCode("TEST_CODE_001");
|
||||
assertNotNull(result);
|
||||
assertEquals(batch.getId(), result.getId());
|
||||
|
||||
// 查询不存在的
|
||||
MesWmBatchDO notFound = batchMapper.selectByCode("NOT_EXIST");
|
||||
assertNull(notFound);
|
||||
}
|
||||
|
||||
// ==================== 向前追溯(集成测试)====================
|
||||
|
||||
/**
|
||||
* 向前追溯集成测试:构造完整链路数据
|
||||
* 链路:消耗明细(icd, batchCode=RAW_BATCH) → 消耗单(ic) → 报工(pf) → 入库单(pp) → 入库行(ppl, batchCode=PRODUCT_BATCH)
|
||||
* 预期:查 RAW_BATCH 能找到 PRODUCT_BATCH
|
||||
*/
|
||||
@Test
|
||||
public void testSelectListByForward_withData() {
|
||||
// 准备:公共 ID
|
||||
Long workOrderId = randomLongId();
|
||||
Long rawItemId = randomLongId();
|
||||
Long productItemId = randomLongId();
|
||||
Long productBatchId = randomLongId();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// 1. 报工记录
|
||||
MesProFeedbackDO feedback = MesProFeedbackDO.builder()
|
||||
.workOrderId(workOrderId).itemId(productItemId).build();
|
||||
feedbackMapper.insert(feedback);
|
||||
|
||||
// 2. 消耗单
|
||||
MesWmItemConsumeDO consume = MesWmItemConsumeDO.builder()
|
||||
.workOrderId(workOrderId).taskId(randomLongId()).workstationId(randomLongId())
|
||||
.processId(randomLongId()).feedbackId(feedback.getId()).consumeDate(now).build();
|
||||
consumeMapper.insert(consume);
|
||||
|
||||
// 3. 消耗明细(关联原材料批次 RAW_BATCH)
|
||||
MesWmItemConsumeDetailDO consumeDetail = MesWmItemConsumeDetailDO.builder()
|
||||
.consumeId(consume.getId()).lineId(randomLongId()).itemId(rawItemId)
|
||||
.quantity(BigDecimal.TEN).batchCode("RAW_BATCH")
|
||||
.warehouseId(randomLongId()).locationId(randomLongId()).areaId(randomLongId()).build();
|
||||
consumeDetailMapper.insert(consumeDetail);
|
||||
|
||||
// 4. 生产入库单
|
||||
MesWmProductProduceDO produce = MesWmProductProduceDO.builder()
|
||||
.workOrderId(workOrderId).build();
|
||||
produceMapper.insert(produce);
|
||||
|
||||
// 5. 入库行(关联成品批次 PRODUCT_BATCH)
|
||||
MesWmProductProduceLineDO produceLine = MesWmProductProduceLineDO.builder()
|
||||
.produceId(produce.getId()).itemId(productItemId)
|
||||
.batchId(productBatchId).batchCode("PRODUCT_BATCH").build();
|
||||
produceLineMapper.insert(produceLine);
|
||||
|
||||
// 执行向前追溯
|
||||
List<MesWmBatchDO> result = batchMapper.selectListByForward("RAW_BATCH");
|
||||
|
||||
// 断言
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.size());
|
||||
assertEquals("PRODUCT_BATCH", result.get(0).getCode());
|
||||
assertEquals(productBatchId, result.get(0).getId());
|
||||
assertEquals(workOrderId, result.get(0).getWorkOrderId());
|
||||
assertEquals(productItemId, result.get(0).getItemId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 向前追溯:已删除的消耗明细应被过滤(icd.deleted = 1)
|
||||
*/
|
||||
@Test
|
||||
public void testSelectListByForward_deletedDataFiltered() {
|
||||
Long workOrderId = randomLongId();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// 1. 报工记录
|
||||
MesProFeedbackDO feedback = MesProFeedbackDO.builder()
|
||||
.workOrderId(workOrderId).itemId(randomLongId()).build();
|
||||
feedbackMapper.insert(feedback);
|
||||
|
||||
// 2. 消耗单
|
||||
MesWmItemConsumeDO consume = MesWmItemConsumeDO.builder()
|
||||
.workOrderId(workOrderId).taskId(randomLongId()).workstationId(randomLongId())
|
||||
.processId(randomLongId()).feedbackId(feedback.getId()).consumeDate(now).build();
|
||||
consumeMapper.insert(consume);
|
||||
|
||||
// 3. 消耗明细(逻辑删除)
|
||||
MesWmItemConsumeDetailDO consumeDetail = MesWmItemConsumeDetailDO.builder()
|
||||
.consumeId(consume.getId()).lineId(randomLongId()).itemId(randomLongId())
|
||||
.quantity(BigDecimal.TEN).batchCode("RAW_DEL_TEST")
|
||||
.warehouseId(randomLongId()).locationId(randomLongId()).areaId(randomLongId()).build();
|
||||
consumeDetailMapper.insert(consumeDetail);
|
||||
consumeDetailMapper.deleteById(consumeDetail.getId()); // 逻辑删除消耗明细
|
||||
|
||||
// 4. 生产入库单 + 入库行
|
||||
MesWmProductProduceDO produce = MesWmProductProduceDO.builder()
|
||||
.workOrderId(workOrderId).build();
|
||||
produceMapper.insert(produce);
|
||||
MesWmProductProduceLineDO produceLine = MesWmProductProduceLineDO.builder()
|
||||
.produceId(produce.getId()).itemId(randomLongId())
|
||||
.batchId(randomLongId()).batchCode("PRODUCT_DEL_TEST").build();
|
||||
produceLineMapper.insert(produceLine);
|
||||
|
||||
// 执行:消耗明细已删除,应该追溯不到
|
||||
List<MesWmBatchDO> result = batchMapper.selectListByForward("RAW_DEL_TEST");
|
||||
assertNotNull(result);
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
// ==================== 向后追溯(集成测试)====================
|
||||
|
||||
/**
|
||||
* 向后追溯集成测试:构造完整链路数据
|
||||
* 链路:入库明细(ppd, batchCode=PRODUCT_BATCH) → 入库单(pp) → 消耗单(ic) → 消耗明细(icd, batchCode=RAW_BATCH)
|
||||
* 预期:查 PRODUCT_BATCH 能找到 RAW_BATCH
|
||||
*/
|
||||
@Test
|
||||
public void testSelectListByBackward_withData() {
|
||||
Long workOrderId = randomLongId();
|
||||
Long rawItemId = randomLongId();
|
||||
Long rawBatchId = randomLongId();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// 1. 生产入库单
|
||||
MesWmProductProduceDO produce = MesWmProductProduceDO.builder()
|
||||
.workOrderId(workOrderId).build();
|
||||
produceMapper.insert(produce);
|
||||
|
||||
// 2. 入库明细(关联成品批次 PRODUCT_BACK_BATCH)
|
||||
MesWmProductProduceDetailDO produceDetail = MesWmProductProduceDetailDO.builder()
|
||||
.produceId(produce.getId()).itemId(randomLongId())
|
||||
.batchCode("PRODUCT_BACK_BATCH").build();
|
||||
produceDetailMapper.insert(produceDetail);
|
||||
|
||||
// 3. 消耗单
|
||||
MesWmItemConsumeDO consume = MesWmItemConsumeDO.builder()
|
||||
.workOrderId(workOrderId).taskId(randomLongId()).workstationId(randomLongId())
|
||||
.processId(randomLongId()).feedbackId(randomLongId()).consumeDate(now).build();
|
||||
consumeMapper.insert(consume);
|
||||
|
||||
// 4. 消耗明细(关联原材料批次 RAW_BACK_BATCH)
|
||||
MesWmItemConsumeDetailDO consumeDetail = MesWmItemConsumeDetailDO.builder()
|
||||
.consumeId(consume.getId()).lineId(randomLongId()).itemId(rawItemId)
|
||||
.quantity(BigDecimal.ONE).batchId(rawBatchId).batchCode("RAW_BACK_BATCH")
|
||||
.warehouseId(randomLongId()).locationId(randomLongId()).areaId(randomLongId()).build();
|
||||
consumeDetailMapper.insert(consumeDetail);
|
||||
|
||||
// 执行向后追溯
|
||||
List<MesWmBatchDO> result = batchMapper.selectListByBackward("PRODUCT_BACK_BATCH");
|
||||
|
||||
// 断言
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.size());
|
||||
assertEquals("RAW_BACK_BATCH", result.get(0).getCode());
|
||||
assertEquals(rawBatchId, result.get(0).getId());
|
||||
assertEquals(workOrderId, result.get(0).getWorkOrderId());
|
||||
assertEquals(rawItemId, result.get(0).getItemId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 向后追溯:消耗明细 batchCode 为 NULL 的应被过滤
|
||||
*/
|
||||
@Test
|
||||
public void testSelectListByBackward_nullBatchCodeFiltered() {
|
||||
Long workOrderId = randomLongId();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// 生产入库单 + 入库明细
|
||||
MesWmProductProduceDO produce = MesWmProductProduceDO.builder()
|
||||
.workOrderId(workOrderId).build();
|
||||
produceMapper.insert(produce);
|
||||
MesWmProductProduceDetailDO produceDetail = MesWmProductProduceDetailDO.builder()
|
||||
.produceId(produce.getId()).itemId(randomLongId())
|
||||
.batchCode("PRODUCT_NULL_TEST").build();
|
||||
produceDetailMapper.insert(produceDetail);
|
||||
|
||||
// 消耗单 + 消耗明细(batchCode 为 null)
|
||||
MesWmItemConsumeDO consume = MesWmItemConsumeDO.builder()
|
||||
.workOrderId(workOrderId).taskId(randomLongId()).workstationId(randomLongId())
|
||||
.processId(randomLongId()).feedbackId(randomLongId()).consumeDate(now).build();
|
||||
consumeMapper.insert(consume);
|
||||
MesWmItemConsumeDetailDO consumeDetail = MesWmItemConsumeDetailDO.builder()
|
||||
.consumeId(consume.getId()).lineId(randomLongId()).itemId(randomLongId())
|
||||
.quantity(BigDecimal.ONE).batchCode(null) // 无批次
|
||||
.warehouseId(randomLongId()).locationId(randomLongId()).areaId(randomLongId()).build();
|
||||
consumeDetailMapper.insert(consumeDetail);
|
||||
|
||||
// 执行:应过滤掉 batchCode IS NULL 的消耗明细
|
||||
List<MesWmBatchDO> result = batchMapper.selectListByBackward("PRODUCT_NULL_TEST");
|
||||
assertNotNull(result);
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,525 @@
|
||||
package cn.iocoder.yudao.module.mes.service.wm.itemconsume;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockListReqVO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductBomDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeDetailDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeLineDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.wm.itemconsume.MesWmItemConsumeMapper;
|
||||
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
|
||||
import cn.iocoder.yudao.module.mes.enums.wm.MesWmItemConsumeStatusEnum;
|
||||
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
|
||||
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductBomService;
|
||||
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
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.module.mes.enums.ErrorCodeConstants.PRO_FEEDBACK_ROUTE_PROCESS_INVALID;
|
||||
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_ROUTE_NOT_EXISTS;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link MesWmItemConsumeServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesWmItemConsumeServiceImpl.class)
|
||||
public class MesWmItemConsumeServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesWmItemConsumeServiceImpl itemConsumeService;
|
||||
|
||||
@Resource
|
||||
private MesWmItemConsumeMapper itemConsumeMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesWmItemConsumeLineService itemConsumeLineService;
|
||||
@MockitoBean
|
||||
private MesWmItemConsumeDetailService itemConsumeDetailService;
|
||||
@MockitoBean
|
||||
private MesProRouteProductBomService routeProductBomService;
|
||||
@MockitoBean
|
||||
private MesProRouteService routeService;
|
||||
@MockitoBean
|
||||
private MesWmTransactionService wmTransactionService;
|
||||
@MockitoBean
|
||||
private MesWmWarehouseService warehouseService;
|
||||
@MockitoBean
|
||||
private MesWmWarehouseLocationService locationService;
|
||||
@MockitoBean
|
||||
private MesWmWarehouseAreaService areaService;
|
||||
@MockitoBean
|
||||
private MesWmMaterialStockService materialStockService;
|
||||
|
||||
// ========== 公共 mock 数据 ==========
|
||||
|
||||
private static final Long WAREHOUSE_ID = 100L;
|
||||
private static final Long LOCATION_ID = 200L;
|
||||
private static final Long AREA_ID = 300L;
|
||||
|
||||
private MesWmWarehouseDO virtualWarehouse;
|
||||
private MesWmWarehouseLocationDO virtualLocation;
|
||||
private MesWmWarehouseAreaDO virtualArea;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
virtualWarehouse = new MesWmWarehouseDO();
|
||||
virtualWarehouse.setId(WAREHOUSE_ID);
|
||||
virtualWarehouse.setCode(MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE);
|
||||
|
||||
virtualLocation = new MesWmWarehouseLocationDO();
|
||||
virtualLocation.setId(LOCATION_ID);
|
||||
virtualLocation.setCode(MesWmWarehouseLocationDO.WIP_VIRTUAL_LOCATION);
|
||||
|
||||
virtualArea = new MesWmWarehouseAreaDO();
|
||||
virtualArea.setId(AREA_ID);
|
||||
virtualArea.setCode(MesWmWarehouseAreaDO.WIP_VIRTUAL_AREA);
|
||||
|
||||
when(warehouseService.getWarehouseByCode(MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE))
|
||||
.thenReturn(virtualWarehouse);
|
||||
when(locationService.getWarehouseLocationByCode(MesWmWarehouseLocationDO.WIP_VIRTUAL_LOCATION))
|
||||
.thenReturn(virtualLocation);
|
||||
when(areaService.getWarehouseAreaByCode(MesWmWarehouseAreaDO.WIP_VIRTUAL_AREA))
|
||||
.thenReturn(virtualArea);
|
||||
|
||||
lenient().when(routeService.validateRouteExists(anyLong())).thenAnswer(invocation -> {
|
||||
Long routeId = invocation.getArgument(0);
|
||||
return new MesProRouteDO().setId(routeId);
|
||||
});
|
||||
|
||||
// mock createItemConsumeLineBatch:模拟 insertBatch 后填充 ID
|
||||
AtomicLong lineIdSeq = new AtomicLong(1000L);
|
||||
doAnswer(invocation -> {
|
||||
List<MesWmItemConsumeLineDO> lines = invocation.getArgument(0);
|
||||
for (MesWmItemConsumeLineDO line : lines) {
|
||||
line.setId(lineIdSeq.getAndIncrement());
|
||||
}
|
||||
return null;
|
||||
}).when(itemConsumeLineService).createItemConsumeLineBatch(any());
|
||||
}
|
||||
|
||||
private MesProFeedbackDO buildFeedback() {
|
||||
return MesProFeedbackDO.builder()
|
||||
.id(randomLongId()).workOrderId(randomLongId()).taskId(randomLongId())
|
||||
.workstationId(randomLongId()).processId(randomLongId()).routeId(randomLongId())
|
||||
.itemId(randomLongId()).feedbackQuantity(BigDecimal.TEN)
|
||||
.build();
|
||||
}
|
||||
|
||||
// ========== generateItemConsume ==========
|
||||
|
||||
@Test
|
||||
public void testGenerateItemConsume_routeContextIncomplete() {
|
||||
MesProFeedbackDO feedback = buildFeedback();
|
||||
feedback.setRouteId(null);
|
||||
assertServiceException(() -> itemConsumeService.generateItemConsume(feedback), PRO_FEEDBACK_ROUTE_PROCESS_INVALID);
|
||||
verify(routeProductBomService, never()).getRouteProductBomList(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateItemConsume_routeNotExists() {
|
||||
MesProFeedbackDO feedback = buildFeedback();
|
||||
doThrow(ServiceExceptionUtil.exception(PRO_ROUTE_NOT_EXISTS)).when(routeService)
|
||||
.validateRouteExists(feedback.getRouteId());
|
||||
assertServiceException(() -> itemConsumeService.generateItemConsume(feedback), PRO_ROUTE_NOT_EXISTS);
|
||||
verify(routeProductBomService, never()).getRouteProductBomList(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateItemConsume_noBom() {
|
||||
// 准备
|
||||
MesProFeedbackDO feedback = buildFeedback();
|
||||
when(routeProductBomService.getRouteProductBomList(
|
||||
feedback.getRouteId(), feedback.getProcessId(), feedback.getItemId()))
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
// 调用
|
||||
MesWmItemConsumeDO result = itemConsumeService.generateItemConsume(feedback);
|
||||
|
||||
// 断言
|
||||
assertNull(result);
|
||||
verify(itemConsumeLineService, never()).createItemConsumeLineBatch(any());
|
||||
verify(itemConsumeDetailService, never()).createItemConsumeDetailBatch(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateItemConsume_noStock() {
|
||||
// 准备:1 个 BOM 物料,线边库无库存
|
||||
MesProFeedbackDO feedback = buildFeedback();
|
||||
Long bomItemId = randomLongId();
|
||||
MesProRouteProductBomDO bom = new MesProRouteProductBomDO();
|
||||
bom.setItemId(bomItemId);
|
||||
bom.setQuantity(new BigDecimal("2")); // 用料比例 = 2
|
||||
when(routeProductBomService.getRouteProductBomList(any(), any(), any()))
|
||||
.thenReturn(ListUtil.of(bom));
|
||||
when(materialStockService.getMaterialStockList(any(MesWmMaterialStockListReqVO.class)))
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
// 调用
|
||||
MesWmItemConsumeDO result = itemConsumeService.generateItemConsume(feedback);
|
||||
|
||||
// 断言 1:消耗单头已入库
|
||||
assertNotNull(result);
|
||||
assertNotNull(result.getId());
|
||||
assertEquals(MesWmItemConsumeStatusEnum.PREPARE.getStatus(), result.getStatus());
|
||||
assertEquals(feedback.getId(), result.getFeedbackId());
|
||||
|
||||
// 断言 2:消耗行正确
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<List<MesWmItemConsumeLineDO>> lineCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(itemConsumeLineService).createItemConsumeLineBatch(lineCaptor.capture());
|
||||
List<MesWmItemConsumeLineDO> lines = lineCaptor.getValue();
|
||||
assertEquals(1, lines.size());
|
||||
assertEquals(bomItemId, lines.get(0).getItemId());
|
||||
// 消耗数量 = 2 × 10 = 20
|
||||
assertEquals(0, new BigDecimal("20").compareTo(lines.get(0).getQuantity()));
|
||||
|
||||
// 断言 3:生成 1 条无批次的 detail
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<List<MesWmItemConsumeDetailDO>> detailCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(itemConsumeDetailService).createItemConsumeDetailBatch(detailCaptor.capture());
|
||||
List<MesWmItemConsumeDetailDO> details = detailCaptor.getValue();
|
||||
assertEquals(1, details.size());
|
||||
MesWmItemConsumeDetailDO detail = details.get(0);
|
||||
assertEquals(bomItemId, detail.getItemId());
|
||||
assertEquals(0, new BigDecimal("20").compareTo(detail.getQuantity()));
|
||||
assertNull(detail.getMaterialStockId());
|
||||
assertNull(detail.getBatchId());
|
||||
assertEquals(WAREHOUSE_ID, detail.getWarehouseId());
|
||||
assertEquals(LOCATION_ID, detail.getLocationId());
|
||||
assertEquals(AREA_ID, detail.getAreaId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateItemConsume_singleBatchSufficient() {
|
||||
// 准备:1 个 BOM 物料,线边库有 1 个批次且库存充足
|
||||
MesProFeedbackDO feedback = buildFeedback();
|
||||
Long bomItemId = randomLongId();
|
||||
MesProRouteProductBomDO bom = new MesProRouteProductBomDO();
|
||||
bom.setItemId(bomItemId);
|
||||
bom.setQuantity(new BigDecimal("3")); // 消耗 = 3 × 10 = 30
|
||||
when(routeProductBomService.getRouteProductBomList(any(), any(), any()))
|
||||
.thenReturn(ListUtil.of(bom));
|
||||
|
||||
Long stockId = randomLongId();
|
||||
Long batchId = randomLongId();
|
||||
MesWmMaterialStockDO stock = new MesWmMaterialStockDO();
|
||||
stock.setId(stockId);
|
||||
stock.setItemId(bomItemId);
|
||||
stock.setQuantity(new BigDecimal("50")); // 库存 50 > 需求 30
|
||||
stock.setBatchId(batchId);
|
||||
stock.setBatchCode("BATCH-001");
|
||||
when(materialStockService.getMaterialStockList(any(MesWmMaterialStockListReqVO.class)))
|
||||
.thenReturn(ListUtil.of(stock));
|
||||
|
||||
// 调用
|
||||
MesWmItemConsumeDO result = itemConsumeService.generateItemConsume(feedback);
|
||||
|
||||
// 断言:生成 1 条带批次的 detail
|
||||
assertNotNull(result);
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<List<MesWmItemConsumeDetailDO>> detailCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(itemConsumeDetailService).createItemConsumeDetailBatch(detailCaptor.capture());
|
||||
List<MesWmItemConsumeDetailDO> details = detailCaptor.getValue();
|
||||
assertEquals(1, details.size());
|
||||
MesWmItemConsumeDetailDO detail = details.get(0);
|
||||
assertEquals(0, new BigDecimal("30").compareTo(detail.getQuantity()));
|
||||
assertEquals(stockId, detail.getMaterialStockId());
|
||||
assertEquals(batchId, detail.getBatchId());
|
||||
assertEquals("BATCH-001", detail.getBatchCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateItemConsume_multiBatchFifo() {
|
||||
// 准备:1 个 BOM 物料,需消耗 30,线边库有 2 个批次(20 + 15)
|
||||
MesProFeedbackDO feedback = buildFeedback();
|
||||
Long bomItemId = randomLongId();
|
||||
MesProRouteProductBomDO bom = new MesProRouteProductBomDO();
|
||||
bom.setItemId(bomItemId);
|
||||
bom.setQuantity(new BigDecimal("3")); // 消耗 = 3 × 10 = 30
|
||||
when(routeProductBomService.getRouteProductBomList(any(), any(), any()))
|
||||
.thenReturn(ListUtil.of(bom));
|
||||
|
||||
Long stockId1 = randomLongId();
|
||||
Long stockId2 = randomLongId();
|
||||
MesWmMaterialStockDO stock1 = new MesWmMaterialStockDO();
|
||||
stock1.setId(stockId1);
|
||||
stock1.setItemId(bomItemId);
|
||||
stock1.setQuantity(new BigDecimal("20")); // 先进先出:先消耗这批
|
||||
stock1.setBatchId(1L);
|
||||
stock1.setBatchCode("BATCH-001");
|
||||
|
||||
MesWmMaterialStockDO stock2 = new MesWmMaterialStockDO();
|
||||
stock2.setId(stockId2);
|
||||
stock2.setItemId(bomItemId);
|
||||
stock2.setQuantity(new BigDecimal("15"));
|
||||
stock2.setBatchId(2L);
|
||||
stock2.setBatchCode("BATCH-002");
|
||||
|
||||
// FIFO 顺序返回
|
||||
when(materialStockService.getMaterialStockList(any(MesWmMaterialStockListReqVO.class)))
|
||||
.thenReturn(Arrays.asList(stock1, stock2));
|
||||
|
||||
// 调用
|
||||
MesWmItemConsumeDO result = itemConsumeService.generateItemConsume(feedback);
|
||||
|
||||
// 断言:生成 2 条带批次的 detail(20 + 10)
|
||||
assertNotNull(result);
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<List<MesWmItemConsumeDetailDO>> detailCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(itemConsumeDetailService).createItemConsumeDetailBatch(detailCaptor.capture());
|
||||
List<MesWmItemConsumeDetailDO> details = detailCaptor.getValue();
|
||||
assertEquals(2, details.size());
|
||||
|
||||
// 第 1 条:批次 1 全部消耗 20
|
||||
assertEquals(stockId1, details.get(0).getMaterialStockId());
|
||||
assertEquals(0, new BigDecimal("20").compareTo(details.get(0).getQuantity()));
|
||||
assertEquals("BATCH-001", details.get(0).getBatchCode());
|
||||
|
||||
// 第 2 条:批次 2 消耗 10(30 - 20 = 10)
|
||||
assertEquals(stockId2, details.get(1).getMaterialStockId());
|
||||
assertEquals(0, new BigDecimal("10").compareTo(details.get(1).getQuantity()));
|
||||
assertEquals("BATCH-002", details.get(1).getBatchCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateItemConsume_insufficientStock() {
|
||||
// 准备:需消耗 30,线边库只有 1 个批次(库存 18),不足部分生成无批次 detail
|
||||
MesProFeedbackDO feedback = buildFeedback();
|
||||
Long bomItemId = randomLongId();
|
||||
MesProRouteProductBomDO bom = new MesProRouteProductBomDO();
|
||||
bom.setItemId(bomItemId);
|
||||
bom.setQuantity(new BigDecimal("3")); // 消耗 = 3 × 10 = 30
|
||||
when(routeProductBomService.getRouteProductBomList(any(), any(), any()))
|
||||
.thenReturn(ListUtil.of(bom));
|
||||
|
||||
Long stockId = randomLongId();
|
||||
MesWmMaterialStockDO stock = new MesWmMaterialStockDO();
|
||||
stock.setId(stockId);
|
||||
stock.setItemId(bomItemId);
|
||||
stock.setQuantity(new BigDecimal("18")); // 不够 30
|
||||
stock.setBatchId(1L);
|
||||
stock.setBatchCode("BATCH-001");
|
||||
when(materialStockService.getMaterialStockList(any(MesWmMaterialStockListReqVO.class)))
|
||||
.thenReturn(ListUtil.of(stock));
|
||||
|
||||
// 调用
|
||||
MesWmItemConsumeDO result = itemConsumeService.generateItemConsume(feedback);
|
||||
|
||||
// 断言:2 条 detail(18 带批次 + 12 无批次)
|
||||
assertNotNull(result);
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<List<MesWmItemConsumeDetailDO>> detailCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(itemConsumeDetailService).createItemConsumeDetailBatch(detailCaptor.capture());
|
||||
List<MesWmItemConsumeDetailDO> details = detailCaptor.getValue();
|
||||
assertEquals(2, details.size());
|
||||
|
||||
// 第 1 条:批次全部消耗 18
|
||||
assertEquals(stockId, details.get(0).getMaterialStockId());
|
||||
assertEquals(0, new BigDecimal("18").compareTo(details.get(0).getQuantity()));
|
||||
assertEquals("BATCH-001", details.get(0).getBatchCode());
|
||||
|
||||
// 第 2 条:剩余 12 无批次
|
||||
assertNull(details.get(1).getMaterialStockId());
|
||||
assertEquals(0, new BigDecimal("12").compareTo(details.get(1).getQuantity()));
|
||||
assertNull(details.get(1).getBatchId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateItemConsume_multipleBomItems() {
|
||||
// 准备:2 个 BOM 物料
|
||||
MesProFeedbackDO feedback = buildFeedback();
|
||||
Long itemA = randomLongId();
|
||||
Long itemB = randomLongId();
|
||||
|
||||
MesProRouteProductBomDO bomA = new MesProRouteProductBomDO();
|
||||
bomA.setItemId(itemA);
|
||||
bomA.setQuantity(new BigDecimal("1")); // 消耗 = 1 × 10 = 10
|
||||
|
||||
MesProRouteProductBomDO bomB = new MesProRouteProductBomDO();
|
||||
bomB.setItemId(itemB);
|
||||
bomB.setQuantity(new BigDecimal("2")); // 消耗 = 2 × 10 = 20
|
||||
|
||||
when(routeProductBomService.getRouteProductBomList(any(), any(), any()))
|
||||
.thenReturn(Arrays.asList(bomA, bomB));
|
||||
|
||||
// 物料 A 有库存,物料 B 无库存
|
||||
MesWmMaterialStockDO stockA = new MesWmMaterialStockDO();
|
||||
stockA.setId(randomLongId());
|
||||
stockA.setItemId(itemA);
|
||||
stockA.setQuantity(new BigDecimal("100"));
|
||||
stockA.setBatchId(1L);
|
||||
stockA.setBatchCode("BATCH-A");
|
||||
|
||||
when(materialStockService.getMaterialStockList(argThat((MesWmMaterialStockListReqVO req) ->
|
||||
req != null && itemA.equals(req.getItemId()))))
|
||||
.thenReturn(ListUtil.of(stockA));
|
||||
when(materialStockService.getMaterialStockList(argThat((MesWmMaterialStockListReqVO req) ->
|
||||
req != null && itemB.equals(req.getItemId()))))
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
// 调用
|
||||
MesWmItemConsumeDO result = itemConsumeService.generateItemConsume(feedback);
|
||||
|
||||
// 断言:2 条消耗行,2 条 detail
|
||||
assertNotNull(result);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<List<MesWmItemConsumeLineDO>> lineCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(itemConsumeLineService).createItemConsumeLineBatch(lineCaptor.capture());
|
||||
assertEquals(2, lineCaptor.getValue().size());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<List<MesWmItemConsumeDetailDO>> detailCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(itemConsumeDetailService).createItemConsumeDetailBatch(detailCaptor.capture());
|
||||
List<MesWmItemConsumeDetailDO> details = detailCaptor.getValue();
|
||||
assertEquals(2, details.size());
|
||||
|
||||
// 物料 A:有批次
|
||||
MesWmItemConsumeDetailDO detailA = CollUtil.findOne(details,
|
||||
d -> itemA.equals(d.getItemId()));
|
||||
assertEquals(0, new BigDecimal("10").compareTo(detailA.getQuantity()));
|
||||
assertNotNull(detailA.getMaterialStockId());
|
||||
assertEquals("BATCH-A", detailA.getBatchCode());
|
||||
|
||||
// 物料 B:无批次
|
||||
MesWmItemConsumeDetailDO detailB = CollUtil.findOne(details,
|
||||
d -> itemB.equals(d.getItemId()));
|
||||
assertEquals(0, new BigDecimal("20").compareTo(detailB.getQuantity()));
|
||||
assertNull(detailB.getMaterialStockId());
|
||||
}
|
||||
|
||||
// ========== finishItemConsume ==========
|
||||
|
||||
@Test
|
||||
public void testFinishItemConsume_withDetails() {
|
||||
// 准备:插入一条消耗记录
|
||||
MesWmItemConsumeDO consume = MesWmItemConsumeDO.builder()
|
||||
.workOrderId(randomLongId()).taskId(randomLongId())
|
||||
.workstationId(randomLongId()).processId(randomLongId())
|
||||
.feedbackId(randomLongId()).consumeDate(java.time.LocalDateTime.now())
|
||||
.status(MesWmItemConsumeStatusEnum.PREPARE.getStatus()).build();
|
||||
itemConsumeMapper.insert(consume);
|
||||
|
||||
// mock detail 列表
|
||||
Long lineId1 = randomLongId();
|
||||
Long lineId2 = randomLongId();
|
||||
MesWmItemConsumeDetailDO detail1 = MesWmItemConsumeDetailDO.builder()
|
||||
.consumeId(consume.getId()).lineId(lineId1).itemId(randomLongId())
|
||||
.quantity(new BigDecimal("20")).batchId(1L).batchCode("BATCH-001")
|
||||
.warehouseId(WAREHOUSE_ID).locationId(LOCATION_ID).areaId(AREA_ID).build();
|
||||
MesWmItemConsumeDetailDO detail2 = MesWmItemConsumeDetailDO.builder()
|
||||
.consumeId(consume.getId()).lineId(lineId2).itemId(randomLongId())
|
||||
.quantity(new BigDecimal("12"))
|
||||
.warehouseId(WAREHOUSE_ID).locationId(LOCATION_ID).areaId(AREA_ID).build();
|
||||
when(itemConsumeDetailService.getItemConsumeDetailListByConsumeId(consume.getId()))
|
||||
.thenReturn(Arrays.asList(detail1, detail2));
|
||||
|
||||
// 调用
|
||||
itemConsumeService.finishItemConsume(consume.getId());
|
||||
|
||||
// 断言 1:状态更新为已完成
|
||||
MesWmItemConsumeDO updated = itemConsumeMapper.selectById(consume.getId());
|
||||
assertEquals(MesWmItemConsumeStatusEnum.FINISHED.getStatus(), updated.getStatus());
|
||||
|
||||
// 断言 2:创建了 2 次库存事务
|
||||
ArgumentCaptor<MesWmTransactionSaveReqDTO> txCaptor = ArgumentCaptor.forClass(MesWmTransactionSaveReqDTO.class);
|
||||
verify(wmTransactionService, times(2)).createTransaction(txCaptor.capture());
|
||||
List<MesWmTransactionSaveReqDTO> txList = txCaptor.getAllValues();
|
||||
|
||||
// 验证第 1 条事务
|
||||
MesWmTransactionSaveReqDTO tx1 = txList.get(0);
|
||||
assertEquals(MesWmTransactionTypeEnum.OUT.getType(), tx1.getType());
|
||||
assertEquals(detail1.getItemId(), tx1.getItemId());
|
||||
assertEquals(0, new BigDecimal("-20").compareTo(tx1.getQuantity()));
|
||||
assertEquals(1L, tx1.getBatchId());
|
||||
assertEquals("BATCH-001", tx1.getBatchCode());
|
||||
assertEquals(WAREHOUSE_ID, tx1.getWarehouseId());
|
||||
assertFalse(tx1.getCheckFlag());
|
||||
assertEquals(MesBizTypeConstants.WM_ITEM_CONSUME, tx1.getBizType());
|
||||
assertEquals(consume.getId(), tx1.getBizId());
|
||||
assertEquals(lineId1, tx1.getBizLineId());
|
||||
|
||||
// 验证第 2 条事务(无批次)
|
||||
MesWmTransactionSaveReqDTO tx2 = txList.get(1);
|
||||
assertEquals(detail2.getItemId(), tx2.getItemId());
|
||||
assertEquals(0, new BigDecimal("-12").compareTo(tx2.getQuantity()));
|
||||
assertNull(tx2.getBatchId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishItemConsume_emptyDetails() {
|
||||
// 准备:插入一条消耗记录,但没有 detail
|
||||
MesWmItemConsumeDO consume = MesWmItemConsumeDO.builder()
|
||||
.workOrderId(randomLongId()).taskId(randomLongId())
|
||||
.workstationId(randomLongId()).processId(randomLongId())
|
||||
.feedbackId(randomLongId()).consumeDate(java.time.LocalDateTime.now())
|
||||
.status(MesWmItemConsumeStatusEnum.PREPARE.getStatus()).build();
|
||||
itemConsumeMapper.insert(consume);
|
||||
|
||||
when(itemConsumeDetailService.getItemConsumeDetailListByConsumeId(consume.getId()))
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
// 调用
|
||||
itemConsumeService.finishItemConsume(consume.getId());
|
||||
|
||||
// 断言:状态更新为已完成,但无事务创建
|
||||
MesWmItemConsumeDO updated = itemConsumeMapper.selectById(consume.getId());
|
||||
assertEquals(MesWmItemConsumeStatusEnum.FINISHED.getStatus(), updated.getStatus());
|
||||
verify(wmTransactionService, never()).createTransaction(any());
|
||||
}
|
||||
|
||||
// ========== getByFeedbackId ==========
|
||||
|
||||
@Test
|
||||
public void testGetByFeedbackId_exists() {
|
||||
Long feedbackId = randomLongId();
|
||||
MesWmItemConsumeDO consume = MesWmItemConsumeDO.builder()
|
||||
.workOrderId(randomLongId()).taskId(randomLongId())
|
||||
.workstationId(randomLongId()).processId(randomLongId())
|
||||
.feedbackId(feedbackId).consumeDate(java.time.LocalDateTime.now())
|
||||
.status(MesWmItemConsumeStatusEnum.PREPARE.getStatus()).build();
|
||||
itemConsumeMapper.insert(consume);
|
||||
|
||||
MesWmItemConsumeDO result = itemConsumeService.getByFeedbackId(feedbackId);
|
||||
assertNotNull(result);
|
||||
assertEquals(consume.getId(), result.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByFeedbackId_notExists() {
|
||||
MesWmItemConsumeDO result = itemConsumeService.getByFeedbackId(randomLongId());
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package cn.iocoder.yudao.module.mes.service.wm.itemreceipt;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptDetailDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.wm.itemreceipt.MesWmItemReceiptMapper;
|
||||
import cn.iocoder.yudao.module.mes.enums.wm.MesWmItemReceiptStatusEnum;
|
||||
import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService;
|
||||
import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
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.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link MesWmItemReceiptServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesWmItemReceiptServiceImpl.class)
|
||||
public class MesWmItemReceiptServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesWmItemReceiptServiceImpl itemReceiptService;
|
||||
|
||||
@Resource
|
||||
private MesWmItemReceiptMapper itemReceiptMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesWmItemReceiptLineService itemReceiptLineService;
|
||||
@MockitoBean
|
||||
private MesWmItemReceiptDetailService itemReceiptDetailService;
|
||||
@MockitoBean
|
||||
private MesWmArrivalNoticeService arrivalNoticeService;
|
||||
@MockitoBean
|
||||
private MesMdVendorService vendorService;
|
||||
@MockitoBean
|
||||
private MesQcIqcService iqcService;
|
||||
@MockitoBean
|
||||
private MesWmTransactionService wmTransactionService;
|
||||
|
||||
// ========== finishItemReceipt ==========
|
||||
|
||||
@Test
|
||||
public void testFinishItemReceipt_success_withNoticeId() {
|
||||
// mock 数据:插入一条待入库状态的入库单(有关联到货通知单)
|
||||
Long noticeId = randomLongId();
|
||||
MesWmItemReceiptDO receipt = randomPojo(MesWmItemReceiptDO.class, o -> {
|
||||
o.setStatus(MesWmItemReceiptStatusEnum.APPROVED.getStatus());
|
||||
o.setNoticeId(noticeId);
|
||||
});
|
||||
itemReceiptMapper.insert(receipt);
|
||||
Long receiptId = receipt.getId();
|
||||
|
||||
// mock 明细列表
|
||||
MesWmItemReceiptDetailDO detail1 = randomPojo(MesWmItemReceiptDetailDO.class, o -> {
|
||||
o.setReceiptId(receiptId);
|
||||
o.setQuantity(BigDecimal.valueOf(100));
|
||||
});
|
||||
MesWmItemReceiptDetailDO detail2 = randomPojo(MesWmItemReceiptDetailDO.class, o -> {
|
||||
o.setReceiptId(receiptId);
|
||||
o.setQuantity(BigDecimal.valueOf(50));
|
||||
});
|
||||
when(itemReceiptDetailService.getItemReceiptDetailListByReceiptId(eq(receiptId)))
|
||||
.thenReturn(Arrays.asList(detail1, detail2));
|
||||
|
||||
// 调用
|
||||
itemReceiptService.finishItemReceipt(receiptId);
|
||||
|
||||
// 断言 1:状态更新为已完成
|
||||
MesWmItemReceiptDO updated = itemReceiptMapper.selectById(receiptId);
|
||||
assertEquals(MesWmItemReceiptStatusEnum.FINISHED.getStatus(), updated.getStatus());
|
||||
// 断言 2:库存事务被创建(2 条明细)
|
||||
verify(wmTransactionService).createTransactionList(anyList());
|
||||
// 断言 3:到货通知单被完成
|
||||
verify(arrivalNoticeService).finishArrivalNotice(eq(noticeId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishItemReceipt_success_withoutNoticeId() {
|
||||
// mock 数据:插入一条待入库状态的入库单(无关联到货通知单)
|
||||
MesWmItemReceiptDO receipt = randomPojo(MesWmItemReceiptDO.class, o -> {
|
||||
o.setStatus(MesWmItemReceiptStatusEnum.APPROVED.getStatus());
|
||||
o.setNoticeId(null); // 无到货通知单
|
||||
});
|
||||
itemReceiptMapper.insert(receipt);
|
||||
Long receiptId = receipt.getId();
|
||||
|
||||
// mock 空明细
|
||||
when(itemReceiptDetailService.getItemReceiptDetailListByReceiptId(eq(receiptId)))
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
// 调用
|
||||
itemReceiptService.finishItemReceipt(receiptId);
|
||||
|
||||
// 断言 1:状态更新为已完成
|
||||
MesWmItemReceiptDO updated = itemReceiptMapper.selectById(receiptId);
|
||||
assertEquals(MesWmItemReceiptStatusEnum.FINISHED.getStatus(), updated.getStatus());
|
||||
// 断言 2:库存事务仍被调用(空列表)
|
||||
verify(wmTransactionService).createTransactionList(anyList());
|
||||
// 断言 3:到货通知单完成方法不被调用
|
||||
verify(arrivalNoticeService, never()).finishArrivalNotice(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishItemReceipt_notExists() {
|
||||
// 准备参数
|
||||
Long receiptId = randomLongId();
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(
|
||||
() -> itemReceiptService.finishItemReceipt(receiptId),
|
||||
WM_ITEM_RECEIPT_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishItemReceipt_statusNotApproved() {
|
||||
// mock 数据:插入一条草稿状态
|
||||
MesWmItemReceiptDO receipt = randomPojo(MesWmItemReceiptDO.class, o -> {
|
||||
o.setStatus(MesWmItemReceiptStatusEnum.PREPARE.getStatus());
|
||||
});
|
||||
itemReceiptMapper.insert(receipt);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(
|
||||
() -> itemReceiptService.finishItemReceipt(receipt.getId()),
|
||||
WM_ITEM_RECEIPT_STATUS_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishItemReceipt_statusAlreadyFinished() {
|
||||
// mock 数据:插入一条已完成状态
|
||||
MesWmItemReceiptDO receipt = randomPojo(MesWmItemReceiptDO.class, o -> {
|
||||
o.setStatus(MesWmItemReceiptStatusEnum.FINISHED.getStatus());
|
||||
});
|
||||
itemReceiptMapper.insert(receipt);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(
|
||||
() -> itemReceiptService.finishItemReceipt(receipt.getId()),
|
||||
WM_ITEM_RECEIPT_STATUS_ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package cn.iocoder.yudao.module.mes.service.wm.materialstock;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockPageReqVO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper;
|
||||
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
|
||||
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link MesWmMaterialStockServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesWmMaterialStockServiceImpl.class)
|
||||
public class MesWmMaterialStockServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesWmMaterialStockServiceImpl materialStockService;
|
||||
|
||||
@Resource
|
||||
private MesWmMaterialStockMapper materialStockMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesMdItemService itemService;
|
||||
@MockitoBean
|
||||
private MesMdItemTypeService itemTypeService;
|
||||
@MockitoBean
|
||||
private MesWmWarehouseAreaService areaService;
|
||||
@MockitoBean
|
||||
private MesWmWarehouseService warehouseService;
|
||||
|
||||
@Test
|
||||
public void testGetMaterialStockPage_virtualFilterOnly() {
|
||||
// 准备数据:虚拟仓库存 + 普通仓库存
|
||||
Long virtualWarehouseId = 100L;
|
||||
Long normalWarehouseId = 200L;
|
||||
MesWmMaterialStockDO virtualStock = createMaterialStockPojo(o -> o.setWarehouseId(virtualWarehouseId));
|
||||
MesWmMaterialStockDO normalStock = createMaterialStockPojo(o -> o.setWarehouseId(normalWarehouseId));
|
||||
materialStockMapper.insert(virtualStock);
|
||||
materialStockMapper.insert(normalStock);
|
||||
mockVirtualWarehouse(virtualWarehouseId);
|
||||
|
||||
// 调用:只看虚拟仓
|
||||
MesWmMaterialStockPageReqVO reqVO = new MesWmMaterialStockPageReqVO();
|
||||
reqVO.setVirtualFilter(MesWmMaterialStockPageReqVO.VIRTUAL_FILTER_ONLY);
|
||||
PageResult<MesWmMaterialStockDO> result = materialStockService.getMaterialStockPage(reqVO);
|
||||
|
||||
// 断言:只返回虚拟仓库存,且 total 与列表一致
|
||||
assertEquals(1, result.getTotal());
|
||||
assertEquals(1, result.getList().size());
|
||||
assertEquals(virtualStock.getId(), result.getList().get(0).getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMaterialStockPage_virtualFilterExclude() {
|
||||
// 准备数据:虚拟仓库存 + 普通仓库存
|
||||
Long virtualWarehouseId = 100L;
|
||||
Long normalWarehouseId = 200L;
|
||||
MesWmMaterialStockDO virtualStock = createMaterialStockPojo(o -> o.setWarehouseId(virtualWarehouseId));
|
||||
MesWmMaterialStockDO normalStock = createMaterialStockPojo(o -> o.setWarehouseId(normalWarehouseId));
|
||||
materialStockMapper.insert(virtualStock);
|
||||
materialStockMapper.insert(normalStock);
|
||||
mockVirtualWarehouse(virtualWarehouseId);
|
||||
|
||||
// 调用:排除虚拟仓
|
||||
MesWmMaterialStockPageReqVO reqVO = new MesWmMaterialStockPageReqVO();
|
||||
reqVO.setVirtualFilter(MesWmMaterialStockPageReqVO.VIRTUAL_FILTER_EXCLUDE);
|
||||
PageResult<MesWmMaterialStockDO> result = materialStockService.getMaterialStockPage(reqVO);
|
||||
|
||||
// 断言:只返回普通仓库存,且 total 与列表一致
|
||||
assertEquals(1, result.getTotal());
|
||||
assertEquals(1, result.getList().size());
|
||||
assertEquals(normalStock.getId(), result.getList().get(0).getId());
|
||||
}
|
||||
|
||||
private MesWmMaterialStockDO createMaterialStockPojo(Consumer<MesWmMaterialStockDO> consumer) {
|
||||
return randomPojo(MesWmMaterialStockDO.class, o -> {
|
||||
o.setQuantity(new BigDecimal("10.00"));
|
||||
o.setReceiptTime(LocalDateTime.of(2026, 1, 1, 0, 0));
|
||||
o.setFrozen(false);
|
||||
o.setDeleted(false);
|
||||
consumer.accept(o);
|
||||
});
|
||||
}
|
||||
|
||||
private void mockVirtualWarehouse(Long virtualWarehouseId) {
|
||||
MesWmWarehouseDO virtualWarehouse = new MesWmWarehouseDO();
|
||||
virtualWarehouse.setId(virtualWarehouseId);
|
||||
when(warehouseService.getWarehouseByCode(MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE))
|
||||
.thenReturn(virtualWarehouse);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package cn.iocoder.yudao.module.mes.service.wm.outsourcereceipt;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.outsourcereceipt.MesWmOutsourceReceiptDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.outsourcereceipt.MesWmOutsourceReceiptLineDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.wm.outsourcereceipt.MesWmOutsourceReceiptMapper;
|
||||
import cn.iocoder.yudao.module.mes.enums.wm.MesWmOutsourceReceiptStatusEnum;
|
||||
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
|
||||
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
|
||||
import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService;
|
||||
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
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.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link MesWmOutsourceReceiptServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesWmOutsourceReceiptServiceImpl.class)
|
||||
public class MesWmOutsourceReceiptServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesWmOutsourceReceiptServiceImpl outsourceReceiptService;
|
||||
|
||||
@Resource
|
||||
private MesWmOutsourceReceiptMapper outsourceReceiptMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesWmOutsourceReceiptLineService outsourceReceiptLineService;
|
||||
@MockitoBean
|
||||
private MesWmOutsourceReceiptDetailService outsourceReceiptDetailService;
|
||||
@MockitoBean
|
||||
private MesWmTransactionService wmTransactionService;
|
||||
@MockitoBean
|
||||
private MesMdVendorService vendorService;
|
||||
@MockitoBean
|
||||
private MesMdItemService itemService;
|
||||
@MockitoBean
|
||||
private MesProWorkOrderService workOrderService;
|
||||
|
||||
@Test
|
||||
public void testUpdateOutsourceReceiptWhenIqcFinish_success_bothQualifiedAndUnqualified() {
|
||||
// mock 数据:插入一条待检验状态的外协入库单
|
||||
MesWmOutsourceReceiptDO receipt = randomPojo(MesWmOutsourceReceiptDO.class, o -> {
|
||||
o.setStatus(MesWmOutsourceReceiptStatusEnum.CONFIRMED.getStatus());
|
||||
});
|
||||
outsourceReceiptMapper.insert(receipt);
|
||||
// 准备参数
|
||||
Long receiptId = receipt.getId();
|
||||
Long lineId = randomLongId();
|
||||
Long iqcId = randomLongId();
|
||||
BigDecimal qualifiedQuantity = BigDecimal.valueOf(80);
|
||||
BigDecimal unqualifiedQuantity = BigDecimal.valueOf(20);
|
||||
|
||||
// mock 行查询
|
||||
MesWmOutsourceReceiptLineDO line = randomPojo(MesWmOutsourceReceiptLineDO.class, o -> {
|
||||
o.setId(lineId);
|
||||
o.setReceiptId(receiptId);
|
||||
o.setQuantity(BigDecimal.valueOf(100));
|
||||
});
|
||||
when(outsourceReceiptLineService.getOutsourceReceiptLine(eq(lineId))).thenReturn(line);
|
||||
|
||||
// 调用
|
||||
outsourceReceiptService.updateOutsourceReceiptWhenIqcFinish(
|
||||
receiptId, lineId, iqcId, qualifiedQuantity, unqualifiedQuantity);
|
||||
|
||||
// 断言:合格品行更新
|
||||
verify(outsourceReceiptLineService).updateOutsourceReceiptLineDO(argThat(updateLine ->
|
||||
lineId.equals(updateLine.getId())
|
||||
&& MesWmQualityStatusEnum.PASS.getStatus().equals(updateLine.getQualityStatus())
|
||||
&& iqcId.equals(updateLine.getIqcId())
|
||||
&& qualifiedQuantity.equals(updateLine.getQuantity())));
|
||||
// 断言:不合格品新增行
|
||||
verify(outsourceReceiptLineService).createOutsourceReceiptLineDO(argThat(newLine ->
|
||||
newLine.getId() == null
|
||||
&& MesWmQualityStatusEnum.FAIL.getStatus().equals(newLine.getQualityStatus())
|
||||
&& iqcId.equals(newLine.getIqcId())
|
||||
&& unqualifiedQuantity.equals(newLine.getQuantity())));
|
||||
// 断言:主表状态更新为待上架
|
||||
MesWmOutsourceReceiptDO updatedReceipt = outsourceReceiptMapper.selectById(receiptId);
|
||||
assertEquals(MesWmOutsourceReceiptStatusEnum.APPROVING.getStatus(), updatedReceipt.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateOutsourceReceiptWhenIqcFinish_success_onlyQualified() {
|
||||
// mock 数据:插入一条待检验状态的外协入库单
|
||||
MesWmOutsourceReceiptDO receipt = randomPojo(MesWmOutsourceReceiptDO.class, o -> {
|
||||
o.setStatus(MesWmOutsourceReceiptStatusEnum.CONFIRMED.getStatus());
|
||||
});
|
||||
outsourceReceiptMapper.insert(receipt);
|
||||
// 准备参数
|
||||
Long receiptId = receipt.getId();
|
||||
Long lineId = randomLongId();
|
||||
Long iqcId = randomLongId();
|
||||
BigDecimal qualifiedQuantity = BigDecimal.valueOf(100);
|
||||
BigDecimal unqualifiedQuantity = BigDecimal.ZERO; // 没有不合格品
|
||||
|
||||
// mock 行查询
|
||||
MesWmOutsourceReceiptLineDO line = randomPojo(MesWmOutsourceReceiptLineDO.class, o -> {
|
||||
o.setId(lineId);
|
||||
o.setReceiptId(receiptId);
|
||||
});
|
||||
when(outsourceReceiptLineService.getOutsourceReceiptLine(eq(lineId))).thenReturn(line);
|
||||
|
||||
// 调用
|
||||
outsourceReceiptService.updateOutsourceReceiptWhenIqcFinish(
|
||||
receiptId, lineId, iqcId, qualifiedQuantity, unqualifiedQuantity);
|
||||
|
||||
// 断言:合格品行更新
|
||||
verify(outsourceReceiptLineService).updateOutsourceReceiptLineDO(argThat(updateLine ->
|
||||
lineId.equals(updateLine.getId())));
|
||||
// 断言:不合格品不应该新增行(数量为 0)
|
||||
verify(outsourceReceiptLineService, never()).createOutsourceReceiptLineDO(any());
|
||||
// 断言:主表状态更新为待上架
|
||||
MesWmOutsourceReceiptDO updatedReceipt = outsourceReceiptMapper.selectById(receiptId);
|
||||
assertEquals(MesWmOutsourceReceiptStatusEnum.APPROVING.getStatus(), updatedReceipt.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateOutsourceReceiptWhenIqcFinish_receiptNotExists() {
|
||||
// 准备参数
|
||||
Long receiptId = randomLongId();
|
||||
Long lineId = randomLongId();
|
||||
Long iqcId = randomLongId();
|
||||
BigDecimal qualifiedQuantity = BigDecimal.valueOf(100);
|
||||
BigDecimal unqualifiedQuantity = BigDecimal.ZERO;
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(
|
||||
() -> outsourceReceiptService.updateOutsourceReceiptWhenIqcFinish(
|
||||
receiptId, lineId, iqcId, qualifiedQuantity, unqualifiedQuantity),
|
||||
WM_OUTSOURCE_RECEIPT_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateOutsourceReceiptWhenIqcFinish_statusNotConfirmed() {
|
||||
// mock 数据:插入一条草稿状态的外协入库单(非待检验状态)
|
||||
MesWmOutsourceReceiptDO receipt = randomPojo(MesWmOutsourceReceiptDO.class, o -> {
|
||||
o.setStatus(MesWmOutsourceReceiptStatusEnum.PREPARE.getStatus());
|
||||
});
|
||||
outsourceReceiptMapper.insert(receipt);
|
||||
// 准备参数
|
||||
Long receiptId = receipt.getId();
|
||||
Long lineId = randomLongId();
|
||||
Long iqcId = randomLongId();
|
||||
BigDecimal qualifiedQuantity = BigDecimal.valueOf(100);
|
||||
BigDecimal unqualifiedQuantity = BigDecimal.ZERO;
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(
|
||||
() -> outsourceReceiptService.updateOutsourceReceiptWhenIqcFinish(
|
||||
receiptId, lineId, iqcId, qualifiedQuantity, unqualifiedQuantity),
|
||||
WM_OUTSOURCE_RECEIPT_STATUS_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateOutsourceReceiptWhenIqcFinish_lineNotExists() {
|
||||
// mock 数据:插入一条待检验状态的外协入库单
|
||||
MesWmOutsourceReceiptDO receipt = randomPojo(MesWmOutsourceReceiptDO.class, o -> {
|
||||
o.setStatus(MesWmOutsourceReceiptStatusEnum.CONFIRMED.getStatus());
|
||||
});
|
||||
outsourceReceiptMapper.insert(receipt);
|
||||
// 准备参数
|
||||
Long receiptId = receipt.getId();
|
||||
Long lineId = randomLongId();
|
||||
Long iqcId = randomLongId();
|
||||
BigDecimal qualifiedQuantity = BigDecimal.valueOf(100);
|
||||
BigDecimal unqualifiedQuantity = BigDecimal.ZERO;
|
||||
|
||||
// mock 行查询返回 null
|
||||
when(outsourceReceiptLineService.getOutsourceReceiptLine(eq(lineId))).thenReturn(null);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(
|
||||
() -> outsourceReceiptService.updateOutsourceReceiptWhenIqcFinish(
|
||||
receiptId, lineId, iqcId, qualifiedQuantity, unqualifiedQuantity),
|
||||
WM_OUTSOURCE_RECEIPT_LINE_NOT_EXISTS);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
package cn.iocoder.yudao.module.mes.service.wm.productproduce;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDetailDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceMapper;
|
||||
import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductProduceStatusEnum;
|
||||
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
|
||||
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
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.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link MesWmProductProduceServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesWmProductProduceServiceImpl.class)
|
||||
public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesWmProductProduceServiceImpl productProduceService;
|
||||
|
||||
@Resource
|
||||
private MesWmProductProduceMapper productProduceMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesWmProductProduceLineService productProduceLineService;
|
||||
@MockitoBean
|
||||
private MesWmProductProduceDetailService productProduceDetailService;
|
||||
@MockitoBean
|
||||
private MesProWorkOrderService workOrderService;
|
||||
@MockitoBean
|
||||
private MesWmBatchService batchService;
|
||||
@MockitoBean
|
||||
private MesWmTransactionService wmTransactionService;
|
||||
@MockitoBean
|
||||
private MesWmWarehouseService warehouseService;
|
||||
@MockitoBean
|
||||
private MesWmWarehouseLocationService locationService;
|
||||
@MockitoBean
|
||||
private MesWmWarehouseAreaService areaService;
|
||||
|
||||
@Test
|
||||
public void testSplitPendingAndFinishProduce_withUnqualified() {
|
||||
// 准备数据:插入一个 PREPARE 状态的产出单
|
||||
Long feedbackId = randomLongId();
|
||||
Long sourceLineId = 100L;
|
||||
Long itemId = randomLongId();
|
||||
Long batchId = randomLongId();
|
||||
String batchCode = "BATCH-001";
|
||||
|
||||
MesWmProductProduceDO produce = randomPojo(MesWmProductProduceDO.class, o -> {
|
||||
o.setFeedbackId(feedbackId);
|
||||
o.setStatus(MesWmProductProduceStatusEnum.PREPARE.getStatus());
|
||||
});
|
||||
productProduceMapper.insert(produce);
|
||||
|
||||
// mock: 虚拟线边库
|
||||
Long whId = randomLongId(), locId = randomLongId(), areaId = randomLongId();
|
||||
MesWmWarehouseDO wh = new MesWmWarehouseDO(); wh.setId(whId);
|
||||
MesWmWarehouseLocationDO loc = new MesWmWarehouseLocationDO(); loc.setId(locId);
|
||||
MesWmWarehouseAreaDO area = new MesWmWarehouseAreaDO(); area.setId(areaId);
|
||||
when(warehouseService.getWarehouseByCode(MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE)).thenReturn(wh);
|
||||
when(locationService.getWarehouseLocationByCode(MesWmWarehouseLocationDO.WIP_VIRTUAL_LOCATION)).thenReturn(loc);
|
||||
when(areaService.getWarehouseAreaByCode(MesWmWarehouseAreaDO.WIP_VIRTUAL_AREA)).thenReturn(area);
|
||||
|
||||
// mock: 通过 sourceLineId 直接定位 PENDING 产出行
|
||||
MesWmProductProduceLineDO pendingLine = MesWmProductProduceLineDO.builder()
|
||||
.id(sourceLineId).produceId(produce.getId()).feedbackId(feedbackId)
|
||||
.itemId(itemId).quantity(BigDecimal.valueOf(100))
|
||||
.batchId(batchId).batchCode(batchCode)
|
||||
.qualityStatus(MesWmQualityStatusEnum.PENDING.getStatus())
|
||||
.build();
|
||||
when(productProduceLineService.validateProductProduceLineExists(sourceLineId))
|
||||
.thenReturn(pendingLine);
|
||||
|
||||
// mock: finishProductProduce 内部查行和明细(用于创建库存事务)
|
||||
MesWmProductProduceLineDO qualifiedLine = MesWmProductProduceLineDO.builder()
|
||||
.id(100L).produceId(produce.getId()).itemId(itemId)
|
||||
.quantity(BigDecimal.valueOf(80))
|
||||
.qualityStatus(MesWmQualityStatusEnum.PASS.getStatus())
|
||||
.build();
|
||||
MesWmProductProduceLineDO unqualifiedLine = MesWmProductProduceLineDO.builder()
|
||||
.id(101L).produceId(produce.getId()).itemId(itemId)
|
||||
.quantity(BigDecimal.valueOf(20))
|
||||
.qualityStatus(MesWmQualityStatusEnum.FAIL.getStatus())
|
||||
.build();
|
||||
when(productProduceLineService.getProductProduceLineListByProduceId(produce.getId()))
|
||||
.thenReturn(ListUtil.of(qualifiedLine, unqualifiedLine));
|
||||
|
||||
// mock: finishProductProduce 中按行查明细
|
||||
MesWmProductProduceDetailDO qualifiedDetail = MesWmProductProduceDetailDO.builder()
|
||||
.lineId(100L).quantity(BigDecimal.valueOf(80)).build();
|
||||
MesWmProductProduceDetailDO unqualifiedDetail = MesWmProductProduceDetailDO.builder()
|
||||
.lineId(101L).quantity(BigDecimal.valueOf(20)).build();
|
||||
when(productProduceDetailService.getProductProduceDetailListByLineId(100L))
|
||||
.thenReturn(ListUtil.of(qualifiedDetail));
|
||||
when(productProduceDetailService.getProductProduceDetailListByLineId(101L))
|
||||
.thenReturn(ListUtil.of(unqualifiedDetail));
|
||||
|
||||
// 调用
|
||||
productProduceService.splitPendingAndFinishProduce(feedbackId, sourceLineId,
|
||||
BigDecimal.valueOf(80), BigDecimal.valueOf(20));
|
||||
|
||||
// 断言 1:不合格品行 - 新建了一行
|
||||
ArgumentCaptor<MesWmProductProduceLineDO> lineCaptor = ArgumentCaptor.forClass(MesWmProductProduceLineDO.class);
|
||||
verify(productProduceLineService).createProductProduceLine(lineCaptor.capture());
|
||||
MesWmProductProduceLineDO createdLine = lineCaptor.getValue();
|
||||
assertEquals(MesWmQualityStatusEnum.FAIL.getStatus(), createdLine.getQualityStatus());
|
||||
assertEquals(0, BigDecimal.valueOf(20).compareTo(createdLine.getQuantity()));
|
||||
assertEquals(itemId, createdLine.getItemId());
|
||||
|
||||
// 断言 2:原待检行 - 更新为合格品
|
||||
ArgumentCaptor<MesWmProductProduceLineDO> updateCaptor = ArgumentCaptor.forClass(MesWmProductProduceLineDO.class);
|
||||
verify(productProduceLineService).updateProductProduceLine(updateCaptor.capture());
|
||||
MesWmProductProduceLineDO updatedLine = updateCaptor.getValue();
|
||||
assertEquals(MesWmQualityStatusEnum.PASS.getStatus(), updatedLine.getQualityStatus());
|
||||
assertEquals(0, BigDecimal.valueOf(80).compareTo(updatedLine.getQuantity()));
|
||||
|
||||
// 断言 3:生成了 2 条明细(不合格品明细 + 合格品明细)
|
||||
verify(productProduceDetailService, times(2)).createProductProduceDetail(any());
|
||||
|
||||
// 断言 4:产出单状态更新为已完成
|
||||
MesWmProductProduceDO updatedProduce = productProduceMapper.selectById(produce.getId());
|
||||
assertEquals(MesWmProductProduceStatusEnum.FINISHED.getStatus(), updatedProduce.getStatus());
|
||||
|
||||
// 断言 5:创建了库存事务
|
||||
verify(wmTransactionService, times(2)).createTransaction(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitPendingAndFinishProduce_allQualified() {
|
||||
// 准备数据:全部合格品场景
|
||||
Long feedbackId = randomLongId();
|
||||
Long sourceLineId = 200L;
|
||||
Long itemId = randomLongId();
|
||||
|
||||
MesWmProductProduceDO produce = randomPojo(MesWmProductProduceDO.class, o -> {
|
||||
o.setFeedbackId(feedbackId);
|
||||
o.setStatus(MesWmProductProduceStatusEnum.PREPARE.getStatus());
|
||||
});
|
||||
productProduceMapper.insert(produce);
|
||||
|
||||
// mock: 虚拟线边库
|
||||
Long whId = randomLongId(), locId = randomLongId(), areaId = randomLongId();
|
||||
MesWmWarehouseDO wh = new MesWmWarehouseDO(); wh.setId(whId);
|
||||
MesWmWarehouseLocationDO loc = new MesWmWarehouseLocationDO(); loc.setId(locId);
|
||||
MesWmWarehouseAreaDO area = new MesWmWarehouseAreaDO(); area.setId(areaId);
|
||||
when(warehouseService.getWarehouseByCode(any())).thenReturn(wh);
|
||||
when(locationService.getWarehouseLocationByCode(any())).thenReturn(loc);
|
||||
when(areaService.getWarehouseAreaByCode(any())).thenReturn(area);
|
||||
|
||||
// mock: 通过 sourceLineId 直接定位 PENDING 产出行
|
||||
MesWmProductProduceLineDO pendingLine = MesWmProductProduceLineDO.builder()
|
||||
.id(sourceLineId).produceId(produce.getId()).feedbackId(feedbackId)
|
||||
.itemId(itemId).quantity(BigDecimal.valueOf(50))
|
||||
.qualityStatus(MesWmQualityStatusEnum.PENDING.getStatus())
|
||||
.build();
|
||||
when(productProduceLineService.validateProductProduceLineExists(sourceLineId))
|
||||
.thenReturn(pendingLine);
|
||||
|
||||
// mock: finishProductProduce 查行和明细
|
||||
MesWmProductProduceLineDO qualifiedLine = MesWmProductProduceLineDO.builder()
|
||||
.id(200L).produceId(produce.getId()).itemId(itemId)
|
||||
.quantity(BigDecimal.valueOf(50))
|
||||
.qualityStatus(MesWmQualityStatusEnum.PASS.getStatus())
|
||||
.build();
|
||||
when(productProduceLineService.getProductProduceLineListByProduceId(produce.getId()))
|
||||
.thenReturn(ListUtil.of(qualifiedLine));
|
||||
MesWmProductProduceDetailDO detail = MesWmProductProduceDetailDO.builder()
|
||||
.lineId(200L).quantity(BigDecimal.valueOf(50)).build();
|
||||
when(productProduceDetailService.getProductProduceDetailListByLineId(200L))
|
||||
.thenReturn(ListUtil.of(detail));
|
||||
|
||||
// 调用:不合格品数量 = 0
|
||||
productProduceService.splitPendingAndFinishProduce(feedbackId, sourceLineId,
|
||||
BigDecimal.valueOf(50), BigDecimal.ZERO);
|
||||
|
||||
// 断言 1:没有新建行(不合格品数量为 0)
|
||||
verify(productProduceLineService, never()).createProductProduceLine(any());
|
||||
|
||||
// 断言 2:更新了原待检行为合格
|
||||
ArgumentCaptor<MesWmProductProduceLineDO> updateCaptor = ArgumentCaptor.forClass(MesWmProductProduceLineDO.class);
|
||||
verify(productProduceLineService).updateProductProduceLine(updateCaptor.capture());
|
||||
assertEquals(MesWmQualityStatusEnum.PASS.getStatus(), updateCaptor.getValue().getQualityStatus());
|
||||
|
||||
// 断言 3:只生成了 1 条明细(全部合格品)
|
||||
verify(productProduceDetailService, times(1)).createProductProduceDetail(any());
|
||||
|
||||
// 断言 4:产出单状态更新为已完成
|
||||
MesWmProductProduceDO updatedProduce = productProduceMapper.selectById(produce.getId());
|
||||
assertEquals(MesWmProductProduceStatusEnum.FINISHED.getStatus(), updatedProduce.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitPendingAndFinishProduce_produceNotExists() {
|
||||
// 调用不存在的 feedbackId,应该抛异常
|
||||
Long feedbackId = randomLongId();
|
||||
Long sourceLineId = randomLongId();
|
||||
ServiceException ex = assertThrows(ServiceException.class, () ->
|
||||
productProduceService.splitPendingAndFinishProduce(feedbackId, sourceLineId,
|
||||
BigDecimal.TEN, BigDecimal.ZERO));
|
||||
assertEquals(WM_PRODUCT_PRODUCE_NOT_EXISTS.getCode(), ex.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitPendingAndFinishProduce_lineNotExists() {
|
||||
// 准备数据:产出单存在,但 sourceLineId 对应的行不存在
|
||||
Long feedbackId = randomLongId();
|
||||
Long sourceLineId = randomLongId();
|
||||
MesWmProductProduceDO produce = randomPojo(MesWmProductProduceDO.class, o -> {
|
||||
o.setFeedbackId(feedbackId);
|
||||
o.setStatus(MesWmProductProduceStatusEnum.PREPARE.getStatus());
|
||||
});
|
||||
productProduceMapper.insert(produce);
|
||||
|
||||
// mock: 虚拟线边库
|
||||
MesWmWarehouseDO wh = new MesWmWarehouseDO(); wh.setId(1L);
|
||||
MesWmWarehouseLocationDO loc = new MesWmWarehouseLocationDO(); loc.setId(1L);
|
||||
MesWmWarehouseAreaDO area = new MesWmWarehouseAreaDO(); area.setId(1L);
|
||||
when(warehouseService.getWarehouseByCode(any())).thenReturn(wh);
|
||||
when(locationService.getWarehouseLocationByCode(any())).thenReturn(loc);
|
||||
when(areaService.getWarehouseAreaByCode(any())).thenReturn(area);
|
||||
|
||||
// mock: validateProductProduceLineExists 抛异常
|
||||
when(productProduceLineService.validateProductProduceLineExists(sourceLineId))
|
||||
.thenThrow(new ServiceException(WM_PRODUCT_PRODUCE_LINE_NOT_EXISTS));
|
||||
|
||||
// 调用,应该抛异常
|
||||
ServiceException ex = assertThrows(ServiceException.class, () ->
|
||||
productProduceService.splitPendingAndFinishProduce(feedbackId, sourceLineId,
|
||||
BigDecimal.TEN, BigDecimal.ZERO));
|
||||
assertEquals(WM_PRODUCT_PRODUCE_LINE_NOT_EXISTS.getCode(), ex.getCode());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package cn.iocoder.yudao.module.mes.service.wm.productsales;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesLineMapper;
|
||||
import cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum;
|
||||
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
|
||||
|
||||
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} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesWmProductSalesLineServiceImpl.class)
|
||||
public class MesWmProductSalesLineServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesWmProductSalesLineServiceImpl productSalesLineService;
|
||||
|
||||
@Resource
|
||||
private MesWmProductSalesLineMapper productSalesLineMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesWmProductSalesService productSalesService;
|
||||
|
||||
@MockitoBean
|
||||
private MesWmProductSalesDetailService productSalesDetailService;
|
||||
|
||||
@MockitoBean
|
||||
private MesMdItemService itemService;
|
||||
@MockitoBean
|
||||
private MesWmBatchService batchService;
|
||||
|
||||
@Test
|
||||
public void testUpdateProductSalesLineWhenOqcFinish_pass() {
|
||||
// 准备参数
|
||||
Long oqcId = randomLongId();
|
||||
Integer checkResult = MesQcCheckResultEnum.PASS.getType();
|
||||
|
||||
// mock 数据
|
||||
MesWmProductSalesLineDO lineDO = randomPojo(MesWmProductSalesLineDO.class, o -> {
|
||||
o.setOqcCheckFlag(true); // 需要 OQC 检验
|
||||
o.setQualityStatus(null); // 初始为空
|
||||
});
|
||||
productSalesLineMapper.insert(lineDO);
|
||||
|
||||
// 调用
|
||||
productSalesLineService.updateProductSalesLineWhenOqcFinish(lineDO.getId(), oqcId, checkResult);
|
||||
|
||||
// 断言:验证行字段更新
|
||||
MesWmProductSalesLineDO updateLine = productSalesLineMapper.selectById(lineDO.getId());
|
||||
assertEquals(oqcId, updateLine.getOqcId());
|
||||
assertEquals(MesWmQualityStatusEnum.PASS.getStatus(), updateLine.getQualityStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateProductSalesLineWhenOqcFinish_fail() {
|
||||
// 准备参数
|
||||
Long oqcId = randomLongId();
|
||||
Integer checkResult = MesQcCheckResultEnum.FAIL.getType();
|
||||
|
||||
// mock 数据
|
||||
MesWmProductSalesLineDO lineDO = randomPojo(MesWmProductSalesLineDO.class, o -> {
|
||||
o.setOqcCheckFlag(true);
|
||||
o.setQualityStatus(null);
|
||||
});
|
||||
productSalesLineMapper.insert(lineDO);
|
||||
|
||||
// 调用
|
||||
productSalesLineService.updateProductSalesLineWhenOqcFinish(lineDO.getId(), oqcId, checkResult);
|
||||
|
||||
// 断言:验证行字段更新
|
||||
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.setOqcCheckFlag(true);
|
||||
o.setQualityStatus(MesWmQualityStatusEnum.PASS.getStatus());
|
||||
});
|
||||
productSalesLineMapper.insert(line1);
|
||||
|
||||
MesWmProductSalesLineDO line2 = randomPojo(MesWmProductSalesLineDO.class, o -> {
|
||||
o.setSalesId(salesId);
|
||||
o.setOqcCheckFlag(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.setOqcCheckFlag(true);
|
||||
o.setQualityStatus(MesWmQualityStatusEnum.PASS.getStatus());
|
||||
});
|
||||
productSalesLineMapper.insert(line1);
|
||||
|
||||
MesWmProductSalesLineDO line2 = randomPojo(MesWmProductSalesLineDO.class, o -> {
|
||||
o.setSalesId(salesId);
|
||||
o.setOqcCheckFlag(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 检验(oqcCheckFlag=false),第 2 行需要检验且不合格
|
||||
Long salesId = randomLongId();
|
||||
MesWmProductSalesLineDO line1 = randomPojo(MesWmProductSalesLineDO.class, o -> {
|
||||
o.setSalesId(salesId);
|
||||
o.setOqcCheckFlag(false); // 不需要 OQC 检验
|
||||
o.setQualityStatus(null);
|
||||
});
|
||||
productSalesLineMapper.insert(line1);
|
||||
|
||||
MesWmProductSalesLineDO line2 = randomPojo(MesWmProductSalesLineDO.class, o -> {
|
||||
o.setSalesId(salesId);
|
||||
o.setOqcCheckFlag(true);
|
||||
o.setQualityStatus(null);
|
||||
});
|
||||
productSalesLineMapper.insert(line2);
|
||||
|
||||
// 调用:第 2 行质检不合格
|
||||
productSalesLineService.updateProductSalesLineWhenOqcFinish(
|
||||
line2.getId(), randomLongId(), MesQcCheckResultEnum.FAIL.getType());
|
||||
|
||||
// 断言:第 2 行不合格 → 应该取消出库单
|
||||
verify(productSalesService).cancelProductSales(eq(salesId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package cn.iocoder.yudao.module.mes.service.wm.returnissue;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnissue.MesWmReturnIssueLineDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnissue.MesWmReturnIssueLineMapper;
|
||||
import cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum;
|
||||
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
/**
|
||||
* {@link MesWmReturnIssueLineServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesWmReturnIssueLineServiceImpl.class)
|
||||
public class MesWmReturnIssueLineServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesWmReturnIssueLineServiceImpl returnIssueLineService;
|
||||
|
||||
@Resource
|
||||
private MesWmReturnIssueLineMapper returnIssueLineMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesWmReturnIssueService issueService;
|
||||
|
||||
@MockitoBean
|
||||
private MesMdItemService itemService;
|
||||
|
||||
@Test
|
||||
public void testUpdateReturnIssueLineWhenRqcFinish_allPass() {
|
||||
// 准备参数:全部合格
|
||||
Integer checkResult = MesQcCheckResultEnum.PASS.getType();
|
||||
BigDecimal qualifiedQty = new BigDecimal("100");
|
||||
BigDecimal unqualifiedQty = BigDecimal.ZERO;
|
||||
|
||||
// mock 数据
|
||||
MesWmReturnIssueLineDO lineDO = randomPojo(MesWmReturnIssueLineDO.class, o -> o.setQuantity(qualifiedQty));
|
||||
returnIssueLineMapper.insert(lineDO);
|
||||
|
||||
// 调用(sourceDocId 传 null 不联动主表状态)
|
||||
returnIssueLineService.updateReturnIssueLineWhenRqcFinish(
|
||||
lineDO.getId(), null, checkResult, qualifiedQty, unqualifiedQty);
|
||||
|
||||
// 断言:验证质量状态更新为合格
|
||||
MesWmReturnIssueLineDO updateLine = returnIssueLineMapper.selectById(lineDO.getId());
|
||||
assertEquals(MesWmQualityStatusEnum.PASS.getStatus(), updateLine.getQualityStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateReturnIssueLineWhenRqcFinish_allFail() {
|
||||
// 准备参数:全部不合格
|
||||
Integer checkResult = MesQcCheckResultEnum.FAIL.getType();
|
||||
BigDecimal qualifiedQty = BigDecimal.ZERO;
|
||||
BigDecimal unqualifiedQty = new BigDecimal("100");
|
||||
|
||||
// mock 数据
|
||||
MesWmReturnIssueLineDO lineDO = randomPojo(MesWmReturnIssueLineDO.class, o -> o.setQuantity(unqualifiedQty));
|
||||
returnIssueLineMapper.insert(lineDO);
|
||||
|
||||
// 调用
|
||||
returnIssueLineService.updateReturnIssueLineWhenRqcFinish(
|
||||
lineDO.getId(), null, checkResult, qualifiedQty, unqualifiedQty);
|
||||
|
||||
// 断言:验证质量状态更新为不合格
|
||||
MesWmReturnIssueLineDO updateLine = returnIssueLineMapper.selectById(lineDO.getId());
|
||||
assertEquals(MesWmQualityStatusEnum.FAIL.getStatus(), updateLine.getQualityStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateReturnIssueLineWhenRqcFinish_partialSplit() {
|
||||
// 准备参数:部分合格部分不合格,触发行拆分
|
||||
Integer checkResult = MesQcCheckResultEnum.PASS.getType();
|
||||
BigDecimal qualifiedQty = new BigDecimal("80");
|
||||
BigDecimal unqualifiedQty = new BigDecimal("20");
|
||||
|
||||
// mock 数据
|
||||
MesWmReturnIssueLineDO lineDO = randomPojo(MesWmReturnIssueLineDO.class, o ->
|
||||
o.setQuantity(new BigDecimal("100")));
|
||||
returnIssueLineMapper.insert(lineDO);
|
||||
|
||||
// 调用
|
||||
returnIssueLineService.updateReturnIssueLineWhenRqcFinish(
|
||||
lineDO.getId(), null, checkResult, qualifiedQty, unqualifiedQty);
|
||||
|
||||
// 断言:原行变为合格,数量调整为合格数量
|
||||
MesWmReturnIssueLineDO originalLine = returnIssueLineMapper.selectById(lineDO.getId());
|
||||
assertEquals(MesWmQualityStatusEnum.PASS.getStatus(), originalLine.getQualityStatus());
|
||||
assertEquals(0, qualifiedQty.compareTo(originalLine.getQuantity()));
|
||||
|
||||
// 断言:新增了一行不合格品
|
||||
List<MesWmReturnIssueLineDO> allLines = returnIssueLineMapper.selectListByIssueId(lineDO.getIssueId());
|
||||
assertEquals(2, allLines.size());
|
||||
MesWmReturnIssueLineDO newLine = allLines.stream()
|
||||
.filter(l -> !l.getId().equals(lineDO.getId()))
|
||||
.findFirst().orElse(null);
|
||||
assertNotNull(newLine);
|
||||
assertEquals(MesWmQualityStatusEnum.FAIL.getStatus(), newLine.getQualityStatus());
|
||||
assertEquals(0, unqualifiedQty.compareTo(newLine.getQuantity()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package cn.iocoder.yudao.module.mes.service.wm.returnsales;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesLineDO;
|
||||
import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnsales.MesWmReturnSalesLineMapper;
|
||||
import cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum;
|
||||
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
|
||||
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
/**
|
||||
* {@link MesWmReturnSalesLineServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MesWmReturnSalesLineServiceImpl.class)
|
||||
public class MesWmReturnSalesLineServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MesWmReturnSalesLineServiceImpl returnSalesLineService;
|
||||
|
||||
@Resource
|
||||
private MesWmReturnSalesLineMapper returnSalesLineMapper;
|
||||
|
||||
@MockitoBean
|
||||
private MesWmReturnSalesService returnSalesService;
|
||||
|
||||
@MockitoBean
|
||||
private MesMdItemService itemService;
|
||||
|
||||
@MockitoBean
|
||||
private MesWmBatchService batchService;
|
||||
|
||||
@MockitoBean
|
||||
private MesWmReturnSalesDetailService returnSalesDetailService;
|
||||
|
||||
@Test
|
||||
public void testUpdateReturnSalesLineWhenRqcFinish_allPass() {
|
||||
// 准备参数:全部合格
|
||||
Integer checkResult = MesQcCheckResultEnum.PASS.getType();
|
||||
BigDecimal qualifiedQty = new BigDecimal("100");
|
||||
BigDecimal unqualifiedQty = BigDecimal.ZERO;
|
||||
|
||||
// mock 数据
|
||||
MesWmReturnSalesLineDO lineDO = randomPojo(MesWmReturnSalesLineDO.class, o -> o.setQuantity(qualifiedQty));
|
||||
returnSalesLineMapper.insert(lineDO);
|
||||
|
||||
// 调用(sourceDocId 传 null 不联动主表状态)
|
||||
returnSalesLineService.updateReturnSalesLineWhenRqcFinish(
|
||||
lineDO.getId(), null, checkResult, qualifiedQty, unqualifiedQty);
|
||||
|
||||
// 断言:验证质量状态更新为合格
|
||||
MesWmReturnSalesLineDO updateLine = returnSalesLineMapper.selectById(lineDO.getId());
|
||||
assertEquals(MesWmQualityStatusEnum.PASS.getStatus(), updateLine.getQualityStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateReturnSalesLineWhenRqcFinish_allFail() {
|
||||
// 准备参数:全部不合格
|
||||
Integer checkResult = MesQcCheckResultEnum.FAIL.getType();
|
||||
BigDecimal qualifiedQty = BigDecimal.ZERO;
|
||||
BigDecimal unqualifiedQty = new BigDecimal("100");
|
||||
|
||||
// mock 数据
|
||||
MesWmReturnSalesLineDO lineDO = randomPojo(MesWmReturnSalesLineDO.class, o -> o.setQuantity(unqualifiedQty));
|
||||
returnSalesLineMapper.insert(lineDO);
|
||||
|
||||
// 调用
|
||||
returnSalesLineService.updateReturnSalesLineWhenRqcFinish(
|
||||
lineDO.getId(), null, checkResult, qualifiedQty, unqualifiedQty);
|
||||
|
||||
// 断言:验证质量状态更新为不合格
|
||||
MesWmReturnSalesLineDO updateLine = returnSalesLineMapper.selectById(lineDO.getId());
|
||||
assertEquals(MesWmQualityStatusEnum.FAIL.getStatus(), updateLine.getQualityStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateReturnSalesLineWhenRqcFinish_partialSplit() {
|
||||
// 准备参数:部分合格部分不合格,触发行拆分
|
||||
Integer checkResult = MesQcCheckResultEnum.PASS.getType();
|
||||
BigDecimal qualifiedQty = new BigDecimal("70");
|
||||
BigDecimal unqualifiedQty = new BigDecimal("30");
|
||||
|
||||
// mock 数据
|
||||
MesWmReturnSalesLineDO lineDO = randomPojo(MesWmReturnSalesLineDO.class, o ->
|
||||
o.setQuantity(new BigDecimal("100")));
|
||||
returnSalesLineMapper.insert(lineDO);
|
||||
|
||||
// 调用
|
||||
returnSalesLineService.updateReturnSalesLineWhenRqcFinish(
|
||||
lineDO.getId(), null, checkResult, qualifiedQty, unqualifiedQty);
|
||||
|
||||
// 断言:原行变为合格,数量调整为合格数量
|
||||
MesWmReturnSalesLineDO originalLine = returnSalesLineMapper.selectById(lineDO.getId());
|
||||
assertEquals(MesWmQualityStatusEnum.PASS.getStatus(), originalLine.getQualityStatus());
|
||||
assertEquals(0, qualifiedQty.compareTo(originalLine.getQuantity()));
|
||||
|
||||
// 断言:新增了一行不合格品
|
||||
List<MesWmReturnSalesLineDO> allLines = returnSalesLineMapper.selectListByReturnId(lineDO.getReturnId());
|
||||
assertEquals(2, allLines.size());
|
||||
MesWmReturnSalesLineDO newLine = allLines.stream()
|
||||
.filter(l -> !l.getId().equals(lineDO.getId()))
|
||||
.findFirst().orElse(null);
|
||||
assertNotNull(newLine);
|
||||
assertEquals(MesWmQualityStatusEnum.FAIL.getStatus(), newLine.getQualityStatus());
|
||||
assertEquals(0, unqualifiedQty.compareTo(newLine.getQuantity()));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user