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