/**
* orderList 任务工单
* lineGroupSequences 产线班组优先级
* groupList 班组排班日历
* assignTeamResult 记录班组数据
* remainingTimeMap 记录每个班组每天的剩余时间
* minChangeTime 最小换型时间限制
* changeMap 记录料号换型数据
* filledClasses2 记录锁定工单 <产线_班组_需求日期_班次 : 开工时间_完工时间> 的组合已经排的
* hasMinPercentage 是否考虑20%
*/
public void assignTasks(List<MrpListDTO> orderList,
List<PpLineGroupSequence> lineGroupSequences,
List<PpGroupScheduleDTO> groupList,
Map<String, Map<String, Map<String, Set<AssignLineOrderDTO>>>> assignTeamResult,
Map<String, Map<Date, Integer>> remainingTimeMap,
BigDecimal minChangeTime,
Map<String, List<PpChangeLineTimeDTO>> changeMap,
Map<String, Set<String>> filledClasses2,
boolean hasMinPercentage,
List<PpScheduleOccupyList> hasGroupOccupyList) {
// 按产线分组的可用班组, 并按优先级排序
Map<String, List<PpLineGroupSequence>> lineGroupMap = lineGroupSequences.stream()
.collect(Collectors.groupingBy(
PpLineGroupSequence::getLine,
Collectors.collectingAndThen(
Collectors.toList(),
list -> {
list.sort(Comparator.comparingInt(PpLineGroupSequence::getSequence));
return list;
}
)
));
// 记录<工单号:占用时间>
Map<String, BigDecimal> orderTotalTimeMap = orderList
.stream()
.filter(it -> !PpShiftTypeEnum.换型班次.getCode().equals(it.getShiftType()) &&
!it.isDelFlag())
.collect(Collectors.toMap(
MrpListDTO::getOrderNo,
MrpListDTO::getTotalTime,
(existing, replacement) -> existing // 这里简单地保留现有的值,可根据需求修改
));
// 记录 <班组_需求日期_className> 的组合已经排满
Set<String> filledClasses = new HashSet<>();
// 产线不允许排的日期的班次<line, <date, className>>
Map<String, Map<Date, List<String>>> tem = new HashMap<>();
// 限制任务调整取舍百分比
BigDecimal minPercentage = BigDecimalUtils.getDecimal(redisService.getCacheObject(CacheConstants.SYS_CONFIG_KEY + CacheConstants.PP_OVER_FLOW_RATE), BigDecimal.valueOf(0.2));
// 缓存的可能是没有除以100
minPercentage = minPercentage.compareTo(BigDecimal.ONE) > 0 ? minPercentage.divide(BigDecimal.valueOf(100), 1, RoundingMode.HALF_UP) : minPercentage;
// 不考虑20%
if (!hasMinPercentage) {
minPercentage = BigDecimal.ZERO;
}
ListIterator<MrpListDTO> iterator = orderList.listIterator();
// 分配过的历史数据都是"过去", 所以也要考虑进来
List<MrpListDTO> prevList = new ArrayList<>();
if (!CollectionUtils.isEmpty(hasGroupOccupyList)) {
for (PpScheduleOccupyList occ : hasGroupOccupyList) {
MrpListDTO _dto = new MrpListDTO();
BeanUtils.copyProperties(occ, _dto);
_dto.setTeamDate(DateUtils.dateTime(DateUtils.YYYY_M_D, occ.getOccupyDate()));
_dto.setStartTime(occ.getPstartTime());
_dto.setEndTime(occ.getPfinishedTime());
_dto.setDescription(occ.getMaterialDesc());
prevList.add(_dto);
}
}
while (iterator.hasNext()) {
int currentIndex = iterator.nextIndex();
MrpListDTO order = iterator.next();
if (currentIndex < 0 || currentIndex > orderList.size()) {
throw new IllegalArgumentException("currentIndex 超出范围");
}
String oee = order.getOee();
String ct = order.getCt();
String line = order.getLine();
Date demandDate = order.getDemandDate();
BigDecimal orginQty = order.getQty();
BigDecimal totalDemandTime = order.getTotalTime();
String finishedProductNo = order.getFinishedProductNo();
String orderNo = order.getOrderNo();
String lockOrder = order.getLockOrder();
String type = order.getType();
BigDecimal orderTotalTime = orderTotalTimeMap.get(orderNo);
if (orderTotalTime == null) {
orderTotalTime = totalDemandTime;
}
BigDecimal minutesQty = BigDecimal.ONE.multiply(BigDecimalUtils.getDecimal(oee)).divide(BigDecimalUtils.getDecimal(ct), 6, RoundingMode.HALF_UP);
// 获取该产线可用的班组列表, 并按优先级排序
List<PpLineGroupSequence> availableGroups = lineGroupMap.getOrDefault(line, Collections.emptyList());
if (availableGroups.isEmpty()) {
throw new ServiceException(String.format("%s产线未配置班组,匹配失败", line));
}
if (finishedProductNo.equals("06-3231-0070-3-25")) {
System.out.println("----");
}
// 过滤只属于这条产线的班组日历
List<String> groupNos = availableGroups.stream().map(PpLineGroupSequence::getGroupNo).collect(Collectors.toList());
List<PpGroupScheduleDTO> newGroupList = groupList.stream().filter(it -> groupNos.contains(it.getGroupNo())).collect(Collectors.toList());
// 匹配最佳班组
List<MrpListDTO> lastList = orderList.subList(currentIndex + 1, orderList.size() - 1);
PpGroupScheduleDTO bestGroup = this.findBestGroup(prevList, order, availableGroups, newGroupList, remainingTimeMap, filledClasses, filledClasses2, tem);
if (bestGroup == null) {
String dateTime = DateUtils.dateTime(demandDate);
if (PpOrderTypeEnum.手工建单.getCode().equals(type)) {
throw new ServiceException("手工单班组排班冲突, 请检查计划开工时间该日是否已存在生产计划以及班组排班白晚班分配是否合理");
} else {
throw new ServiceException(String.format("[%s]产线匹配班组[%s]日历失败, 请检查班组排班数是否满足排产后所需的班组数以及班组排班白晚班分配是否合理", line, dateTime));
}
}
Map<Date, Integer> groupRemainingTime = remainingTimeMap.computeIfAbsent(bestGroup.getGroupNo(), k -> new HashMap<>());
int groupRemaining = groupRemainingTime.computeIfAbsent(bestGroup.getDate(), k -> 660);
// todo 暂时不考虑锁定工单
// 处理换型, 只处理(前面是锁定且本条是锁定)或(前面非锁定且本条非锁定)的情况
if (PpOrderLockStatus.UN_LOCKED.getCode().equals(lockOrder) &&
!order.getShiftType().equals(PpShiftTypeEnum.换型班次.getCode()) &&
!order.isProcessFlag()) {
int intI = processChangeOrder(prevList, order, bestGroup, minChangeTime, changeMap, iterator);
if (intI == 1) {
continue;
}
}
// 处理换型, 锁定工单和本条是否存在换型情况
if (!order.getShiftType().equals(PpShiftTypeEnum.换型班次.getCode())
&& PpOrderLockStatus.UN_LOCKED.getCode().equals(lockOrder)
&& !order.isProcessFlag1()) {
int _intI = processOrderAndLockChange(
prevList,
lastList,
order,
bestGroup,
groupRemaining,
minChangeTime,
changeMap,
filledClasses2,
iterator,
minutesQty,
newGroupList,
filledClasses,
tem
);
if (_intI == 1) {
continue;
}
}
String key = bestGroup.getGroupNo() + "_" + DateUtils.dateTime(bestGroup.getDate()) + "_" + bestGroup.getClassName();
// 重新获取一遍
totalDemandTime = order.getTotalTime();
// 锁定工单, 这里的锁定工单用的是锁定工单明细, 所以有具体的白晚班
if (PpOrderLockStatus.LOCKED.getCode().equals(lockOrder)) {
String key2 = bestGroup.getGroupNo() + "_" + DateUtils.dateTime(bestGroup.getDate()) + "_" + bestGroup.getClassName();
String startTime = DateUtils.format(order.getStartTime(), DateUtils.YYYY_MM_DD_HH_MM_SS);
String endTime = DateUtils.format(order.getEndTime(), DateUtils.YYYY_MM_DD_HH_MM_SS);
String value = startTime + "_" + endTime;
filledClasses2.computeIfAbsent(key2, k -> new HashSet<>()).add(value);
}
if (totalDemandTime.intValue() <= groupRemaining) {
// 需求时间小于等于剩余时间, 直接分配
int remaining = groupRemaining - totalDemandTime.intValue();
groupRemainingTime.put(bestGroup.getDate(), remaining);
order.setTeamNo(bestGroup.getGroupNo());
order.setTeamDate(bestGroup.getDate());
order.setClassName(bestGroup.getClassName());
if (groupRemaining == totalDemandTime.intValue()) {
// 该班组排满, 记录排满信息
filledClasses.add(key);
}
//排了这一单, 没到下班时间
if (remaining > 0) {
// 如果此时是换型工单或者TMC类型, 就不用补了
if (PpShiftTypeEnum.换型班次.getCode().equals(order.getShiftType()) || PpOrderTypeEnum.TMC.getCode().equals(type) || PpOrderLockStatus.LOCKED.getCode().equals(lockOrder)) {
System.out.println("换型工单/TMC工单/锁定不用考虑");
} else {
// 计算比例: 距离下班时间的分钟数 ÷ 工单总用时
BigDecimal ratio = BigDecimal.valueOf(remaining).divide(orderTotalTime, 6, RoundingMode.HALF_UP).abs();
// 调整数量补充
BigDecimal qty = BigDecimal.valueOf(remaining).multiply(minutesQty).setScale(0, RoundingMode.HALF_UP);
// < 0.2 补充
if (ratio.compareTo(minPercentage) <= 0) {
System.out.println("< 0.2 补充 " + ratio);
// 补充数量不能超过 (未来28天的需求量(不含当天) + 当天需求量 - 现有库存数量(触发任务前的 endStock))
BigDecimal demand1 = order.getDemand1();
BigDecimal demand3 = order.getDemand3();
BigDecimal preStock = order.getPreStock();
BigDecimal subtract = demand1.add(demand3).subtract(preStock);
// 超过就用差值数量, 没超过就用补充数量
if (qty.compareTo(subtract) > 0) {
order.setAddQty(order.getAddQty().add(subtract));
} else {
order.setAddQty(order.getAddQty().add(qty));
}
order.setTotalTime(order.getTotalTime().add(BigDecimal.valueOf(remaining)));
// 班组占用情况也要更新
groupRemainingTime.put(bestGroup.getDate(), 0);
filledClasses.add(key);
}
}
}
}
// 换班
else {
// 如果此时是锁定工单, 就不用拆了, 但是班组要占满, 按理说锁定工单不会超出班组剩余时间, 不会走到这里
if (PpOrderLockStatus.LOCKED.getCode().equals(lockOrder)) {
order.setTeamNo(bestGroup.getGroupNo());
order.setTeamDate(bestGroup.getDate());
order.setClassName(bestGroup.getClassName());
// 该班组排满,记录排满信息
groupRemainingTime.put(bestGroup.getDate(), 0);
filledClasses.add(key);
} else {
// 需求时间大于剩余时间, 填满当前班组, 创建新任务
BigDecimal usedTime = BigDecimal.valueOf(groupRemaining);
BigDecimal overtimeMinutes = totalDemandTime.subtract(usedTime);
order.setTeamNo(bestGroup.getGroupNo());
order.setTeamDate(bestGroup.getDate());
order.setClassName(bestGroup.getClassName());
order.setTotalTime(usedTime);
// 重新计算数量
// order.setQty(usedTime.multiply(minutesQty).setScale(0, RoundingMode.HALF_UP));
order.setAddQty(usedTime.multiply(minutesQty).setScale(0, RoundingMode.HALF_UP));
// 该班组排满,记录排满信息
groupRemainingTime.put(bestGroup.getDate(), 0);
filledClasses.add(key);
// 计算比例: 距离下班时间的分钟数 ÷ 工单总用时
BigDecimal ratio = overtimeMinutes.divide(orderTotalTime, 6, RoundingMode.HALF_UP).abs();
BigDecimal qty = overtimeMinutes.multiply(minutesQty).setScale(0, RoundingMode.HALF_UP);
System.out.println("距离下班时间的分钟数 " + overtimeMinutes + " 比例 " + ratio);
//TMC工单直接拆, 换型班次
if (PpOrderTypeEnum.TMC.getCode().equals(type) || PpShiftTypeEnum.换型班次.getCode().equals(order.getShiftType())) {
// 超出部分新建一条工单
MrpListDTO newOrder = new MrpListDTO();
BeanUtils.copyBeanProp(newOrder, order);
newOrder.setTotalTime(overtimeMinutes);
// newOrder.setQty(qty);
newOrder.setAddQty(qty);
newOrder.setId(null);
newOrder.setTeamNo(null);
newOrder.setTeamDate(null);
newOrder.setClassName(null);
iterator.add(newOrder);
// 回退迭代器,以便下一轮处理这个新插入的工单
if (iterator.hasPrevious()) {
iterator.previous();
}
} else {
// 其他类型的工单 还要算一下比例
// > 0.2 拆单; < 0.2 舍弃
if (ratio.compareTo(minPercentage) > 0) {
System.out.println("> 0.2 拆单 " + ratio);
// 超出部分新建一条工单
MrpListDTO newOrder = new MrpListDTO();
BeanUtils.copyBeanProp(newOrder, order);
newOrder.setTotalTime(overtimeMinutes);
// newOrder.setQty(qty);
newOrder.setAddQty(qty);
newOrder.setId(null);
newOrder.setTeamNo(null);
newOrder.setTeamDate(null);
newOrder.setClassName(null);
iterator.add(newOrder);
// 回退迭代器,以便下一轮处理这个新插入的工单
if (iterator.hasPrevious()) {
iterator.previous();
}
} else if (ratio.compareTo(minPercentage) <= 0) {
System.out.println("< 0.2 舍弃 " + ratio);
}
}
}
}
// 锁定工单不需要设置开工和完工时间
if (!PpOrderLockStatus.LOCKED.getCode().equals(lockOrder)) {
//单次设置开工和完工时间
this.setStartEndTime(order, filledClasses2, iterator, prevList);
}
prevList.add(order);
// 填充 assignTeamResult
String teamNo = order.getTeamNo();
String className = order.getClassName();
String dateStr = DateUtils.dateTime(order.getTeamDate());
if (StringUtils.isEmpty(teamNo) || StringUtils.isEmpty(className) || StringUtils.isEmpty(dateStr)) {
continue; // 跳过处理
}
AssignLineOrderDTO assignLineOrder = new AssignLineOrderDTO(order.getOrderNo(), order.getLine());
assignTeamResult.computeIfAbsent(teamNo, k -> new LinkedHashMap<>())
.computeIfAbsent(className, k -> new LinkedHashMap<>())
.computeIfAbsent(dateStr, k -> new HashSet<>())
.add(assignLineOrder);
}
}
// 判断本条工单与前后锁单存在换型情况
private int processOrderAndLockChange(List<MrpListDTO> prevList,
List<MrpListDTO> lastList,
MrpListDTO order,
PpGroupScheduleDTO bestGroup,
int groupRemaining,
BigDecimal minChangeTime,
Map<String, List<PpChangeLineTimeDTO>> changeMap,
Map<String, Set<String>> filledClasses2,
ListIterator<MrpListDTO> iterator,
BigDecimal minutesQty,
List<PpGroupScheduleDTO> newGroupList,
Set<String> filledClasses,
Map<String, Map<Date, List<String>>> tem) {
if (prevList.isEmpty()) {
return 0;
}
MrpListDTO newOrder = null;
MrpListDTO changeOrder = null;
MrpListDTO changeOrder2 = null;
BigDecimal changeTime = BigDecimal.ZERO;
BigDecimal changeTime2 = BigDecimal.ZERO;
BigDecimal totalTime = order.getTotalTime();
// 处理首尾班组情况
MrpListDTO prevLocked = null;
MrpListDTO nextLocked = null;
boolean isDayShift = bestGroup.getClassName().contains("白班");
boolean isNightShift = bestGroup.getClassName().contains("晚班");
if (!isDayShift && !isNightShift) {
throw new IllegalArgumentException("无效的班次名称: " + bestGroup.getClassName());
}
LocalDate groupDate = bestGroup.getDate().toInstant()
.atZone(ZoneId.systemDefault()).toLocalDate();
LocalDateTime shiftStart = isDayShift ?
LocalDateTime.of(groupDate, DAY_SHIFT_START) :
LocalDateTime.of(groupDate, NIGHT_SHIFT_START);
LocalDateTime shiftEnd = isDayShift ?
LocalDateTime.of(groupDate, DAY_SHIFT_END) :
LocalDateTime.of(groupDate.plusDays(1), NIGHT_SHIFT_END);
// 构建 occupiedPeriods
List<Interval> occupiedPeriods = new ArrayList<>();
String key2 = bestGroup.getGroupNo() + "_" + DateUtils.dateTime(bestGroup.getDate()) + "_" + bestGroup.getClassName();
if (filledClasses2.containsKey(key2)) {
Set<String> values = filledClasses2.get(key2);
for (String value : values) {
String[] parts = value.split("_");
if (parts.length != 2) continue;
LocalDateTime start = LocalDateTime.parse(parts[0], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
LocalDateTime end = LocalDateTime.parse(parts[1], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
occupiedPeriods.add(new Interval(start, end));
}
}
// 排序并合并重叠区间(可选)
occupiedPeriods.sort(Comparator.comparing(Interval::start));
// 计算空闲时间段
List<Interval> freePeriods = calculateFreePeriods(shiftStart, shiftEnd, occupiedPeriods);
Interval interval = freePeriods.get(0);
LocalDateTime start = interval.start;
LocalDateTime end = interval.end;
List<MrpListDTO> validItems = prevList.stream()
.filter(item -> item.getLine().equals(order.getLine()))
.filter(item -> PpShiftTypeEnum.正常班次.getCode().equals(item.getShiftType()))
.filter(item -> item.getTeamNo() != null && item.getTeamDate() != null)
.filter(item -> item.getStartTime() != null && item.getEndTime() != null)
.collect(Collectors.toList());
Optional<MrpListDTO> prevOpt = validItems.stream()
.filter(item -> {
LocalDateTime itemEnd = DateUtils.toLocalDateTime(item.getEndTime());
return !itemEnd.isAfter(start); // itemEnd <= start
})
.max(Comparator.comparing(MrpListDTO::getEndTime)); // 取最晚结束的(即最近的一个)
Optional<MrpListDTO> nextOpt = validItems.stream()
.filter(item -> {
LocalDateTime itemStart = DateUtils.toLocalDateTime(item.getStartTime());
return !itemStart.isBefore(end); // itemStart >= end
})
.min(Comparator.comparing(MrpListDTO::getStartTime));
if (prevOpt.isPresent()) {
prevLocked = prevOpt.get();
}
if (nextOpt.isPresent()) {
nextLocked = nextOpt.get();
}
if (prevLocked != null &&
prevLocked.getLockOrder().equals(PpOrderLockStatus.LOCKED.getCode()) &&
!prevLocked.getFinishedProductNo().equals(order.getFinishedProductNo())
) {
changeOrder = createChangeOrderWithConflictCheck(
prevLocked,
order,
minChangeTime,
changeMap,
groupRemaining);
if (changeOrder != null) {
changeTime = changeOrder.getTotalTime();
}
}
if (nextLocked != null &&
nextLocked.getLockOrder().equals(PpOrderLockStatus.LOCKED.getCode()) &&
!nextLocked.getFinishedProductNo().equals(order.getFinishedProductNo())
) {
changeOrder2 = createChangeOrderWithConflictCheck(
order,
nextLocked,
minChangeTime,
changeMap,
groupRemaining);
if (changeOrder2 != null) {
changeTime2 = changeOrder2.getTotalTime();
}
}
// 如果仅两条换型时间之和 >= freeTime, 报错
BigDecimal sum = changeTime.add(changeTime2);
BigDecimal freeTime = BigDecimal.valueOf(groupRemaining);
if (changeOrder != null && changeOrder2 != null && sum.compareTo(freeTime) >= 0) {
throw new ServiceException(String.format(
"[%s]产线[%s]料号与锁定工单[%s]和[%s]在[%s]存在换型时间冲突:换型时间 %d 分钟,仅剩 %d 分钟",
order.getLine(),
order.getFinishedProductNo(),
changeOrder.getOrderNo(),
changeOrder2.getOrderNo(),
DateUtils.dateTime(bestGroup.getDate()),
sum.intValue(),
groupRemaining
));
}
// 如果工单时间 + 换型时间之和 > freeTime, 那就要把工单切掉
if (sum.compareTo(BigDecimal.ZERO) > 0) {
sum = sum.add(totalTime);
if (sum.compareTo(freeTime) > 0) {
BigDecimal diff = sum.subtract(freeTime);
BigDecimal subtract = order.getTotalTime().subtract(diff);
order.setTotalTime(subtract);
// 重新计算数量
order.setAddQty(subtract.multiply(minutesQty).setScale(0, RoundingMode.HALF_UP));
newOrder = new MrpListDTO();
BeanUtils.copyProperties(order, newOrder);
newOrder.setTotalTime(diff);
newOrder.setAddQty(diff.multiply(minutesQty).setScale(0, RoundingMode.HALF_UP));
newOrder.setTeamNo(null);
newOrder.setTeamDate(null);
newOrder.setClassName(null);
}
}
return insertChangeOrders(iterator, changeOrder, changeOrder2, order, newOrder);
}
private int insertChangeOrders(ListIterator<MrpListDTO> iterator,
MrpListDTO changeOrder,
MrpListDTO changeOrder2,
MrpListDTO order,
MrpListDTO newOrder) {
// 场景1: 前后都有换型单
if (changeOrder != null && changeOrder2 != null) {
order.setProcessFlag1(true); // 防止重复触发
iterator.remove(); // 移除当前 order
// 插入 changeOrder
iterator.add(changeOrder); // 插入后,指针位于 changeOrder 之后
iterator.add(order); // 再插入原 order
iterator.add(changeOrder2); // 最后插入 changeOrder2
if (newOrder != null) {
iterator.add(newOrder); // 最后插入 newOrder
if (iterator.hasPrevious()) iterator.previous(); // → newOrder
}
// 回退到 changeOrder(下一轮会处理它)
if (iterator.hasPrevious()) iterator.previous(); // → changeOrder2
if (iterator.hasPrevious()) iterator.previous(); // → order
if (iterator.hasPrevious()) iterator.previous(); // → changeOrder
return 1;
}
// 场景2: 只有前置换型单
else if (changeOrder != null) {
order.setProcessFlag1(true); // 防止重复触发
iterator.remove(); // 删除当前 order
iterator.add(changeOrder);
iterator.add(order);
if (newOrder != null) {
iterator.add(newOrder); // 最后插入 newOrder
if (iterator.hasPrevious()) iterator.previous(); // → newOrder
}
// 回退到 changeOrder
if (iterator.hasPrevious()) iterator.previous();
if (iterator.hasPrevious()) iterator.previous();
return 1;
}
// 场景3: 只有后置换型单
else if (changeOrder2 != null) {
iterator.add(changeOrder2);
if (newOrder != null) {
iterator.add(newOrder); // 最后插入 newOrder
if (iterator.hasPrevious()) iterator.previous(); // → newOrder
}
if (iterator.hasPrevious()) iterator.previous(); // 回到 changeOrder2
return 0;
}
return 0;
}
/**
* 创建换型工单,并检查可用时间段是否足够容纳换型时间。
* 如果所需时间超过可用时间,则抛出 ServiceException。
*
* @param order from工单(通常是即将被换型影响的工单)
* @param sourceOrder to工单 要复制属性的源工单(通常是即将被换型影响的工单)
* @param minChangeTime 最小允许换型时间
* @param changeMap 换型时间映射表
* @param availableMinutes 当前班次中的空闲时间段长度(分钟)
* @return 创建好的换型工单,若不满足条件则返回 null
* @throws ServiceException 如果换型时间大于可用时间,且需插入此换型时抛出
*/
private MrpListDTO createChangeOrderWithConflictCheck(MrpListDTO order,
MrpListDTO sourceOrder,
BigDecimal minChangeTime,
Map<String, List<PpChangeLineTimeDTO>> changeMap,
long availableMinutes) {
// 使用通用方法尝试创建换型工单
MrpListDTO changeOrder = createChangeOrderIfValid(order, sourceOrder, minChangeTime, changeMap);
if (changeOrder == null) {
return null; // 不满足基本条件(如无换型数据、小于最小时间等)
}
// 已经创建了换型工单,现在检查其时间是否超出可用空闲时间
BigDecimal requiredTime = changeOrder.getChangeTime(); // 单位:分钟(假设为数值型分钟)
long requiredMinutes = requiredTime.longValue();
if (requiredMinutes >= availableMinutes) {
throw new ServiceException(String.format(
"[%s]产线[%s]料号与锁定工单[%s]在[%s]存在换型时间冲突:换型时间 %d 分钟,仅剩 %d 分钟",
sourceOrder.getLine(),
sourceOrder.getFinishedProductNo(),
sourceOrder.getOrderNo(),
DateUtils.dateTime(sourceOrder.getStartTime()),
requiredMinutes,
availableMinutes
));
}
return changeOrder;
}
/**
* 根据 fromPart -> toPart 查找换型时间,若满足最小时间要求,则创建对应的换型工单
*
* @param order from工单(通常是即将被换型影响的工单)
* @param sourceOrder to工单 要复制属性的源工单(通常是即将被换型影响的工单)
* @param minChangeTime 最小允许换型时间
* @param changeMap 换型时间映射表:fromPart -> List<换型记录>
* @return 创建好的换型工单,若不满足条件则返回 null
*/
private MrpListDTO createChangeOrderIfValid(
MrpListDTO order,
MrpListDTO sourceOrder,
BigDecimal minChangeTime,
Map<String, List<PpChangeLineTimeDTO>> changeMap) {
String orderNo = order.getOrderNo();
String fromPart = order.getFinishedProductNo();
String toPart = sourceOrder.getFinishedProductNo();
List<PpChangeLineTimeDTO> changes = changeMap.get(fromPart);
if (CollectionUtils.isEmpty(changes)) {
return null;
}
// 查找 fromPart -> toPart 的换型记录
Optional<PpChangeLineTimeDTO> matched = changes.stream()
.filter(dto -> toPart.equals(dto.getToPart()))
.findFirst();
if (!matched.isPresent()) {
return null;
}
String number = matched.get().getNumber();
if (StringUtils.isEmpty(number)) {
return null;
}
BigDecimal changeTime = new BigDecimal(number);
if (changeTime.compareTo(minChangeTime) < 0) {
return null; // 不满足最小换型时间
}
// 创建换型工单
MrpListDTO changeOrder = new MrpListDTO();
BeanUtils.copyProperties(sourceOrder, changeOrder);
// 手动设置专属字段
changeOrder.setChangeTime(changeTime);
changeOrder.setTotalTime(changeTime);
changeOrder.setQty(BigDecimal.ZERO);
changeOrder.setAddQty(BigDecimal.ZERO);
changeOrder.setShiftType(PpShiftTypeEnum.换型班次.getCode());
changeOrder.setId(null);
changeOrder.setTeamNo(null);
changeOrder.setTeamDate(null);
changeOrder.setStartTime(null);
changeOrder.setEndTime(null);
changeOrder.setClassName(null);
changeOrder.setLockOrder(PpOrderLockStatus.UN_LOCKED.getCode());
changeOrder.setFromFinNo(fromPart + "," + orderNo);
return changeOrder;
}
processOrderAndLockChange是判断正常工单在前后锁定工单的情况下产生换型工单的方法, 但是不全面, 因为正常工单->锁定工单之间可能可用班组时间并不只能放下一条换型, 有可能填入其他工单;
我的想法是, 在主方法里, 在prevList里寻找order的前一条换型和后一条锁定, 如果这两条数据的id相等, 那么这条换型失效, 应该被删掉, 并且这条换型和这条锁定工单之间所有时间段排产的工单全部还原, 涉及到修改的filledClasses,filledClasses2,tem,remainingTimeMap的数据也还原到处理这条换型之前的版本. 其实放到processOrderAndLockChange里就是这条换型就是changeOrder2