云
9 小时以前 3ff6604c75c887f4056ee03545d204bc9323286c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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()));
        }
    }
}