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 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 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 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 machines, BigDecimal quantity, BigDecimal capacity, LocalDateTime earliest, List busy, Calendar calendar) { int hours = duration(quantity, capacity); return findSlotHours(station, machines, hours, earliest, busy, calendar); } public static Slot findSlotHours(Long station, Set machines, int hours, LocalDateTime earliest, List 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("无法找到无冲突时段,请检查已有任务计划"); } }