package cn.iocoder.yudao.module.mes.service.pro.task.schedule; import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.time.*; import java.util.*; import static org.junit.jupiter.api.Assertions.*; class MesProScheduleEngineTest { private final MesProScheduleEngine.Calendar calendar = new MesProScheduleEngine.Calendar(Set.of(), LocalDate.of(2027, 12, 31)); private LocalDateTime at(String text) { return LocalDateTime.parse(text); } @Test void openingClosingAndWeekend() { assertEquals(at("2026-10-16T09:00"), calendar.nextWorkingTime(at("2026-10-16T07:20"))); assertEquals(at("2026-10-19T09:00"), calendar.nextWorkingTime(at("2026-10-16T18:00"))); assertEquals(at("2026-10-19T09:00"), calendar.nextWorkingTime(at("2026-10-17T12:00"))); } @Test void exactShiftAndCrossWeekend() { assertEquals(at("2026-10-16T18:00"), calendar.endTime(at("2026-10-16T09:00"), 9)); assertEquals(at("2026-10-19T10:00"), calendar.endTime(at("2026-10-16T17:00"), 2)); } @Test void skipsConfiguredHoliday() { var custom = new MesProScheduleEngine.Calendar(Set.of(LocalDate.of(2026, 10, 19)), LocalDate.of(2027, 1, 1)); assertEquals(at("2026-10-20T10:00"), custom.endTime(at("2026-10-16T17:00"), 2)); } @Test void rejectsNoWorkingWindowAndInvalidCapacity() { var closed = new MesProScheduleEngine.Calendar(Set.of(LocalDate.of(2026, 10, 16)), LocalDate.of(2026, 10, 16)); assertThrows(IllegalArgumentException.class, () -> closed.nextWorkingTime(at("2026-10-16T09:00"))); assertThrows(IllegalArgumentException.class, () -> MesProScheduleEngine.duration(BigDecimal.ONE, BigDecimal.ZERO)); assertThrows(IllegalArgumentException.class, () -> MesProScheduleEngine.duration(BigDecimal.ZERO, BigDecimal.ONE)); } @Test void quantitiesRoundUpWithoutDroppingDecimals() { assertEquals(2, MesProScheduleEngine.duration(new BigDecimal("100.01"), new BigDecimal("100"))); assertEquals(1, MesProScheduleEngine.duration(new BigDecimal("0.01"), new BigDecimal("100"))); } @Test void sharedMachineAndStationCannotOverlap() { var busy = List.of(new MesProScheduleEngine.Busy(1L, Set.of(10L), at("2026-10-15T09:00"), at("2026-10-15T11:00"))); var sharedMachine = MesProScheduleEngine.findSlot(2L, Set.of(10L), BigDecimal.TEN, BigDecimal.TEN, at("2026-10-15T09:00"), busy, calendar); assertEquals(at("2026-10-15T11:00"), sharedMachine.start()); var sameStation = MesProScheduleEngine.findSlot(1L, Set.of(11L), BigDecimal.TEN, BigDecimal.TEN, at("2026-10-15T09:00"), busy, calendar); assertEquals(at("2026-10-15T11:00"), sameStation.start()); var independent = MesProScheduleEngine.findSlot(2L, Set.of(11L), BigDecimal.TEN, BigDecimal.TEN, at("2026-10-15T09:00"), busy, calendar); assertEquals(at("2026-10-15T09:00"), independent.start()); } @Test void boundaryTouchAndMultipleConflicts() { var busy = List.of( new MesProScheduleEngine.Busy(1L, Set.of(), at("2026-10-15T10:00"), at("2026-10-15T12:00")), new MesProScheduleEngine.Busy(1L, Set.of(), at("2026-10-15T12:00"), at("2026-10-15T15:00"))); var shortSlot = MesProScheduleEngine.findSlot(1L, Set.of(), BigDecimal.ONE, BigDecimal.ONE, at("2026-10-15T09:00"), busy, calendar); assertEquals(at("2026-10-15T10:00"), shortSlot.end()); var longSlot = MesProScheduleEngine.findSlot(1L, Set.of(), BigDecimal.TEN, new BigDecimal("5"), at("2026-10-15T09:00"), busy, calendar); assertEquals(at("2026-10-15T15:00"), longSlot.start()); } @Test void randomizedPlansAlwaysStayWithinShiftsAndDoNotOverlap() { var random = new Random(17); List busy = new ArrayList<>(); for (int i = 0; i < 150; i++) { var slot = MesProScheduleEngine.findSlot(1L, Set.of(10L), BigDecimal.valueOf(random.nextInt(400) + 1), new BigDecimal("100"), at("2026-10-15T09:00"), busy, calendar); assertTrue(calendar.isWorkingDay(slot.start().toLocalDate())); assertTrue(calendar.isWorkingDay(slot.end().toLocalDate())); assertFalse(slot.start().toLocalTime().isBefore(LocalTime.of(9, 0))); assertFalse(slot.end().toLocalTime().isAfter(LocalTime.of(18, 0))); assertTrue(busy.stream().noneMatch(interval -> MesProScheduleEngine.overlaps(slot.start(), slot.end(), interval))); busy.add(new MesProScheduleEngine.Busy(1L, Set.of(10L), slot.start(), slot.end())); } } }