package cn.iocoder.yudao.module.mes.service.pro.feedback; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryDO; 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.task.MesProTaskDO; import cn.iocoder.yudao.module.mes.dal.mysql.dv.machinery.MesDvMachineryMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskMapper; import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService; import cn.iocoder.yudao.module.mes.service.pro.task.schedule.MesProScheduleGuardService; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import org.apache.ibatis.builder.MapperBuilderAssistant; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.test.util.ReflectionTestUtils; import java.math.BigDecimal; import java.time.*; import java.util.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; class MesProFeedbackEquipmentServiceTest { private final MesProScheduleGuardService guard = mock(MesProScheduleGuardService.class); private final MesProTaskMapper tasks = mock(MesProTaskMapper.class); private final MesProFeedbackMapper feedbacks = mock(MesProFeedbackMapper.class); private final MesDvMachineryMapper machines = mock(MesDvMachineryMapper.class); private final MesMdWorkstationService stations = mock(MesMdWorkstationService.class); private MesProFeedbackEquipmentService service; private MesProTaskDO task; private MesDvMachineryDO machine; private final LocalDateTime now = LocalDateTime.of(2026,10,15,14,0); @BeforeEach void setup() { TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), "test"), MesDvMachineryDO.class); service = new MesProFeedbackEquipmentService(); ReflectionTestUtils.setField(service,"guard",guard); ReflectionTestUtils.setField(service,"taskMapper",tasks); ReflectionTestUtils.setField(service,"feedbackMapper",feedbacks); ReflectionTestUtils.setField(service,"machineryMapper",machines); ReflectionTestUtils.setField(service,"workstationService",stations); ReflectionTestUtils.setField(service,"clock",Clock.fixed(now.atZone(ZoneId.of("Asia/Shanghai")).toInstant(),ZoneId.of("Asia/Shanghai"))); task=new MesProTaskDO().setId(1L).setWorkOrderId(2L).setWorkstationId(3L).setProcessId(4L).setRouteId(5L) .setQuantity(BigDecimal.TEN).setProducedQuantity(BigDecimal.ZERO).setStatus(0); machine=new MesDvMachineryDO().setId(10L).setName("冷热罐").setStatus(1); when(tasks.selectById(1L)).thenReturn(task); when(tasks.selectSchedulingTasks()).thenReturn(List.of(task)); when(tasks.updateById(any(MesProTaskDO.class))).thenAnswer(call->{MesProTaskDO update=call.getArgument(0);if(update.getStatus()!=null)task.setStatus(update.getStatus());return 1;}); when(guard.machineIdsByStation()).thenReturn(Map.of(3L,Set.of(10L),6L,Set.of(10L))); when(stations.getWorkstation(3L)).thenReturn(new MesMdWorkstationDO().setId(3L).setProcessId(4L).setStatus(0)); when(machines.selectByIds(anyCollection())).thenReturn(List.of(machine)); when(machines.update(isNull(),any(Wrapper.class))).thenReturn(1); when(feedbacks.selectListByTaskId(anyLong())).thenReturn(List.of()); when(feedbacks.selectListByTaskIds(anyCollection())).thenReturn(List.of()); } private MesProFeedbackDO report() { return new MesProFeedbackDO().setId(7L).setTaskId(1L).setWorkOrderId(2L).setWorkstationId(3L) .setProcessId(4L).setRouteId(5L).setFeedbackTime(now).setStatus(2); } private MesProTaskDO other() { return new MesProTaskDO().setId(8L).setWorkstationId(6L).setStatus(2) .setQuantity(BigDecimal.TEN).setProducedQuantity(BigDecimal.ONE); } @Test void submitCapturesStoppedEquipmentWithoutStartingIt() { var f=report();service.validateSubmission(f); assertEquals("10",f.getMachineryIds()); verify(machines,never()).update(isNull(),any(Wrapper.class)); } @Test void maintenanceBlocksCurrentSubmission() { machine.setStatus(3); assertThrows(ServiceException.class,()->service.validateSubmission(report())); } @Test void pastDayReportNeitherUsesCurrentMaintenanceNorChangesEquipment() { var f=report().setFeedbackTime(now.minusDays(1));machine.setStatus(3); assertDoesNotThrow(()->service.validateSubmission(f));service.afterApproved(f); assertNull(f.getMachineryIds());verifyNoInteractions(machines); } @Test void futureTimeAndPausedTaskAreRejected() { assertThrows(ServiceException.class,()->service.validateSubmission(report().setFeedbackTime(now.plusHours(1)))); task.setStatus(9);assertThrows(ServiceException.class,()->service.validateSubmission(report())); } @Test void missingStationEquipmentAndChangedTaskAreRejected() { when(guard.machineIdsByStation()).thenReturn(Map.of()); assertThrows(ServiceException.class,()->service.validateSubmission(report())); assertThrows(ServiceException.class,()->service.validateSubmission(report().setProcessId(999L))); } @Test void delayedApprovalDoesNotInvalidateAlreadySubmittedProduction() { machine.setStatus(3); assertDoesNotThrow(()->service.validateApproval(report().setMachineryIds("10"))); service.afterApproved(report().setMachineryIds("10")); verify(machines,never()).update(isNull(),any(Wrapper.class)); } @Test void partialApprovedProductionStartsEquipment() { task.setProducedQuantity(BigDecimal.ONE); service.afterApproved(report().setMachineryIds("10")); assertEquals(2,task.getStatus());verify(machines).update(isNull(),any(Wrapper.class)); } @Test void finishedQuantityStopsEquipment() { task.setProducedQuantity(BigDecimal.TEN);machine.setStatus(2); service.afterApproved(report().setMachineryIds("10")); assertEquals(4, task.getStatus()); verify(machines).update(isNull(),any(Wrapper.class)); } @Test void historicalCompletedTaskClosesWithoutChangingLiveMachineStatus() { task.setProducedQuantity(BigDecimal.TEN);machine.setStatus(2); service.afterApproved(report().setFeedbackTime(now.minusDays(1))); assertEquals(4,task.getStatus());verifyNoInteractions(machines); } @Test void resumeChecksMaintenanceAndSharedOccupancy() { task.setStatus(2).setProducedQuantity(BigDecimal.ONE);machine.setStatus(3); assertThrows(ServiceException.class,()->service.validateResume(task)); machine.setStatus(1); when(tasks.selectSchedulingTasks()).thenReturn(List.of(task,other())); assertThrows(ServiceException.class,()->service.validateResume(task)); } @Test void anotherActiveTaskKeepsSharedEquipmentRunning() { task.setProducedQuantity(BigDecimal.TEN);machine.setStatus(2); when(tasks.selectSchedulingTasks()).thenReturn(List.of(task,other())); service.afterApproved(report().setMachineryIds("10")); verify(machines,never()).update(isNull(),any(Wrapper.class)); } @Test void otherRunningTaskWithZeroReportedQuantityStillBlocksSubmission() { when(tasks.selectSchedulingTasks()).thenReturn(List.of(task,other().setStatus(1).setProducedQuantity(BigDecimal.ZERO))); assertThrows(ServiceException.class,()->service.validateSubmission(report())); } @Test void submittedReportReservesEquipmentWithoutStartingIt() { var pendingTask=other().setStatus(0).setProducedQuantity(BigDecimal.ZERO); when(tasks.selectSchedulingTasks()).thenReturn(List.of(task,pendingTask)); when(feedbacks.selectListByTaskIds(anyCollection())).thenReturn(List.of( report().setId(9L).setTaskId(8L).setMachineryIds("10").setStatus(2))); assertThrows(ServiceException.class,()->service.validateSubmission(report())); task.setProducedQuantity(BigDecimal.TEN);machine.setStatus(2); service.afterApproved(report().setMachineryIds("10")); verify(machines).update(isNull(),any(Wrapper.class)); } @Test void pausedTaskReleasesButFutureDraftDoesNotOccupyEquipment() { task.setStatus(9).setProducedQuantity(BigDecimal.ONE).setFirstFeedbackTime(now);machine.setStatus(2); when(tasks.selectSchedulingTasks()).thenReturn(List.of(task,other().setStatus(0).setProducedQuantity(BigDecimal.ZERO))); service.afterTaskStateChanged(List.of(task));verify(machines).update(isNull(),any(Wrapper.class)); } @Test void cancelingUnstartedDraftDoesNotStopUntrackedEquipment() { machine.setStatus(2);service.afterTaskStateChanged(List.of(task)); verify(machines,never()).selectByIds(anyCollection()); } @Test void existingSnapshotSurvivesWorkstationRebinding() { when(guard.machineIdsByStation()).thenReturn(Map.of(3L,Set.of(99L))); when(feedbacks.selectListByTaskId(1L)).thenReturn(List.of(report().setMachineryIds("10").setStatus(4))); var f=report();service.validateSubmission(f);assertEquals("10",f.getMachineryIds()); } @Test void newTaskCanUseAvailableSubsetButCannotSwapExistingMaintenanceSnapshot() { var maintenance=new MesDvMachineryDO().setId(11L).setStatus(3).setName("维护设备"); when(guard.machineIdsByStation()).thenReturn(Map.of(3L,Set.of(10L,11L))); when(machines.selectByIds(anyCollection())).thenAnswer(call->{Collection ids=call.getArgument(0);return List.of(machine,maintenance).stream().filter(m->ids.contains(m.getId())).toList();}); var f=report();service.validateSubmission(f);assertEquals("10",f.getMachineryIds()); when(feedbacks.selectListByTaskId(1L)).thenReturn(List.of(report().setMachineryIds("11").setStatus(4))); assertThrows(ServiceException.class,()->service.validateSubmission(report())); } @Test void maintenanceIsNeverOverwrittenWhenTaskStops() { task.setStatus(9).setProducedQuantity(BigDecimal.ONE).setFirstFeedbackTime(now);machine.setStatus(3); service.afterTaskStateChanged(List.of(task));verify(machines,never()).update(isNull(),any(Wrapper.class)); } @Test void stoppingReboundStationTouchesSnapshotMachinesOnly() { task.setStatus(9).setProducedQuantity(BigDecimal.ONE).setFirstFeedbackTime(now); when(guard.machineIdsByStation()).thenReturn(Map.of(3L,Set.of(99L))); when(feedbacks.selectListByTaskId(1L)).thenReturn(List.of(report().setMachineryIds("10").setStatus(4))); service.afterTaskStateChanged(List.of(task)); verify(machines).selectByIds(eq(Set.of(10L))); } @Test void concurrentMaintenanceRollsBackStartingProduction() { task.setProducedQuantity(BigDecimal.ONE); when(machines.update(isNull(),any(Wrapper.class))).thenReturn(0); assertThrows(ServiceException.class,()->service.afterApproved(report().setMachineryIds("10"))); } @Test void historicalAutoFinishDoesNotTouchLiveMachinesAndScopeIsReleased() { task.setStatus(2).setProducedQuantity(BigDecimal.ONE); try (var scope=service.beginApproval(report().setFeedbackTime(now.minusDays(1)))) { service.afterTaskStateChanged(List.of(task)); verifyNoInteractions(machines); } service.afterTaskStateChanged(List.of(task)); verify(machines).selectByIds(anyCollection()); } }