云
7 小时以前 e09f103259fa602382bb5ce2a03361a6397af46a
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package cn.iocoder.yudao.module.mes.service.pro.task.schedule;
 
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.*;
import java.util.*;
 
/** 纯计算:日班09–18点,排除周末和节假日,工作站及共享设备不重叠。 */
public final class MesProScheduleEngine {
    private MesProScheduleEngine() {}
 
    public record Calendar(Set<LocalDate> holidays, LocalDate horizon) {
        public boolean isWorkingDay(LocalDate date) {
            return date.getDayOfWeek() != DayOfWeek.SATURDAY
                    && date.getDayOfWeek() != DayOfWeek.SUNDAY && !holidays.contains(date);
        }
 
        public LocalDateTime nextWorkingTime(LocalDateTime value) {
            LocalDateTime result = value.withNano(0);
            while (!result.toLocalDate().isAfter(horizon)) {
                if (!isWorkingDay(result.toLocalDate()) || !result.toLocalTime().isBefore(LocalTime.of(18, 0))) {
                    result = result.toLocalDate().plusDays(1).atTime(9, 0);
                } else if (result.toLocalTime().isBefore(LocalTime.of(9, 0))) {
                    return result.toLocalDate().atTime(9, 0);
                } else {
                    return result;
                }
            }
            throw new IllegalArgumentException("未来一年内没有足够可用工作时间,请调整排产配置");
        }
 
        public LocalDateTime endTime(LocalDateTime start, int hours) {
            long seconds = Math.multiplyExact((long) hours, 3600L);
            LocalDateTime current = nextWorkingTime(start);
            while (seconds > 0) {
                LocalDateTime close = current.toLocalDate().atTime(18, 0);
                long available = Duration.between(current, close).getSeconds();
                if (seconds <= available) return current.plusSeconds(seconds);
                seconds -= available;
                current = nextWorkingTime(close);
            }
            return current;
        }
    }
 
    public record Busy(Long workstationId, Set<Long> machineryIds, LocalDateTime start, LocalDateTime end) {}
    public record Slot(LocalDateTime start, LocalDateTime end, int hours) {}
 
    public static int duration(BigDecimal quantity, BigDecimal capacity) {
        if (quantity == null || quantity.signum() <= 0 || capacity == null || capacity.signum() <= 0) {
            throw new IllegalArgumentException("排产数量和设备小时产能必须大于0");
        }
        return quantity.divide(capacity, 0, RoundingMode.CEILING).intValueExact();
    }
 
    public static boolean sharesResources(Long station, Set<Long> machines, Busy busy) {
        return Objects.equals(station, busy.workstationId())
                || !Collections.disjoint(machines, busy.machineryIds());
    }
 
    public static boolean overlaps(LocalDateTime start, LocalDateTime end, Busy busy) {
        return start.isBefore(busy.end()) && end.isAfter(busy.start());
    }
 
    public static Slot findSlot(Long station, Set<Long> machines, BigDecimal quantity, BigDecimal capacity,
                                LocalDateTime earliest, List<Busy> busy, Calendar calendar) {
        int hours = duration(quantity, capacity);
        return findSlotHours(station, machines, hours, earliest, busy, calendar);
    }
 
    public static Slot findSlotHours(Long station, Set<Long> machines, int hours,
                                     LocalDateTime earliest, List<Busy> busy, Calendar calendar) {
        if (hours <= 0) throw new IllegalArgumentException("生产工时必须大于0");
        LocalDateTime start = calendar.nextWorkingTime(earliest);
        // 每次至少跳过一个占用区间,避免无限尝试。
        for (int i = 0; i <= busy.size(); i++) {
            LocalDateTime end = calendar.endTime(start, hours);
            LocalDateTime conflictEnd = null;
            for (Busy interval : busy) {
                if (sharesResources(station, machines, interval) && overlaps(start, end, interval)) {
                    if (conflictEnd == null || interval.end().isAfter(conflictEnd)) conflictEnd = interval.end();
                }
            }
            if (conflictEnd == null) return new Slot(start, end, hours);
            start = calendar.nextWorkingTime(conflictEnd);
        }
        throw new IllegalArgumentException("无法找到无冲突时段,请检查已有任务计划");
    }
}