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());
|
}
|
|
}
|