C 中的getline函数需要=NULL.

本文详细介绍了C语言中的getline函数,包括其使用条件、函数声明、返回值含义及参数说明,并提供了一个完整的示例程序来展示如何使用getline从文件中读取每一行。

C 有 fgets(), gets() 函数,也有getline.
用于读取一行 字符直到换行符,包括换行符.

2使用条件

linux标准C中使用条件:
#define _GNU_SOURCE
  #include <stdio.h>
函数申明:
ssize_t getline(char **lineptr, size_t *n, FILE *stream);

3返回值

成功:返回读取的字节数。
失败:返回-1。
参数:
lineptr:指向存放该行字符的指针,如果是NULL,则有系统帮助malloc,请在使用完成后free释放。
n:如果是由系统malloc的指针,请填0
stream:文件描述符

4应用举例

#define _GNU_SOURCE
  #include <stdio.h>
  #include <stdlib.h>
int main(void)
  {
  FILE * fp;
  char * line = NULL;
  size_t len = 0;
  ssize_t read;
fp = fopen("/etc/motd", "r");
  if (fp == NULL)
  exit(EXIT_FAILURE);
while ((read = getline(&line, &len, fp)) != -1)
{
  printf("Retrieved line of length %zu :\n", read);
  printf("%s", line);
  }
if (line)
  free(line);
  exit(EXIT_SUCCESS);
  }
private void setStartEndTime(PpMachiningOrderList order, PpMachiningOrderList prevOrder, Map<String, Set<String>> filledClasses, ListIterator<PpMachiningOrderList> iterator, Map<String, Map<String, Set<String>>> changeOccupyMap) { String finishedProductNo = order.getFinishedProductNo(); String line = order.getLine(); String otherLine = returnOtherLine(line); String device = parseDevice(line); String groupNo = order.getTeamNo(); Date groupDate = order.getTeamDate(); String groupDateStr = DateUtils.dateTime(groupDate); LocalDate localGroupDate = groupDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); String className = order.getClassName(); boolean isDayShift = className.contains("白班"); boolean isNightShift = className.contains("晚班"); List<PpMachiningOrderList> tem = new ArrayList<>(); if (!isDayShift && !isNightShift) { throw new IllegalArgumentException("计算开工/完工时间时, 发现无效的班次名称: " + className); } // 班次时间窗口:起始与结束(考虑跨天) LocalDateTime shiftStart; LocalDateTime shiftEnd; if (isDayShift) { shiftStart = LocalDateTime.of(localGroupDate, DAY_SHIFT_START); shiftEnd = LocalDateTime.of(localGroupDate, DAY_SHIFT_END); } else { // 晚班 shiftStart = LocalDateTime.of(localGroupDate, NIGHT_SHIFT_START); shiftEnd = LocalDateTime.of(localGroupDate.plusDays(1), NIGHT_SHIFT_END); } BigDecimal totalTime = order.getTotalTime(); // 单位是分钟 long totalSeconds = totalTime.multiply(BigDecimal.valueOf(60)).longValue(); // 转为秒 Duration requiredDuration = Duration.ofSeconds(totalSeconds); LocalDateTime startTime = null; LocalDateTime endTime = null; Set<String> values = new HashSet<>(); Map<String, Set<String>> flagChangeMap = null; if (changeOccupyMap != null && changeOccupyMap.get(device) != null && !changeOccupyMap.get(device).isEmpty()) { flagChangeMap = changeOccupyMap.get(device); if (flagChangeMap != null && !flagChangeMap.isEmpty()) { Set<String> map_1 = flagChangeMap.get(line); Set<String> map_2 = flagChangeMap.get(otherLine); if (map_1 != null && !map_1.isEmpty()) { values.addAll(map_1); } if (map_2 != null && !map_2.isEmpty()) { values.addAll(map_2); } } } // 如果有被占时间段,需基于空闲时间段安排 if (!values.isEmpty()) { // 解析 occupiedPeriods 并排序 List<Interval> occupiedPeriods = new ArrayList<>(); for (String value : values) { String[] parts = value.split("_"); if (parts.length != 2) continue; try { 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)); } catch (Exception e) { continue; } } occupiedPeriods.sort(Comparator.comparing(Interval::start)); // 计算空闲时间段 List<Interval> freePeriods = calculateFreePeriods(shiftStart, shiftEnd, occupiedPeriods); // 找到第一个能放下任务的空闲段 for (Interval free : freePeriods) { LocalDateTime freeStart = free.start(); LocalDateTime freeEnd = free.end(); freeEnd = handleBreakTimes(freeStart, freeEnd, 0); Duration freeDuration = Duration.between(freeStart, freeEnd); if (freeDuration.compareTo(requiredDuration) >= 0) { startTime = freeStart; break; } else { // 截断 startTime = freeStart; endTime = freeEnd; // 用完整个空闲段 // 创建剩余任务 long allocatedMinutes = freeDuration.toMinutes(); long remainingMinutes = requiredDuration.toMinutes() - allocatedMinutes; order.setTotalTime(BigDecimal.valueOf(allocatedMinutes)); requiredDuration = Duration.ofMinutes(allocatedMinutes); PpMachiningOrderList newOrder = new PpMachiningOrderList(); BeanUtils.copyProperties(order, newOrder); newOrder.setTotalTime(BigDecimal.valueOf(remainingMinutes)); newOrder.setTeamNo(null); newOrder.setTeamDate(null); newOrder.setClassName(null); iterator.add(newOrder); // 回退迭代器,以便下一轮处理这个新插入的工单 if (iterator.hasPrevious()) { iterator.previous(); } break; // 当前段只处理这一部分 } } // 若无合适空闲段,则从 shiftStart 开始(或抛异常/排队到下一个班次?可扩展) if (startTime == null) { throw new ServiceException(String.format("[产线 %s] [料号 %s] [日期 %s] [班组 %s] 无足够空闲时间安排任务", line, finishedProductNo, groupDateStr, groupNo)); } } // 如果没有被占时间段 else { // 第一条数据:从班次开始时间排 if (prevOrder == null) { startTime = shiftStart; } else { // 存在上一个任务,但没有被占时间段,则从上一个任务结束时间开始 startTime = prevOrder.getPfinishedTime().toInstant() .atZone(ZoneId.systemDefault()).toLocalDateTime(); } } // 现在有了 startTime,计算不考虑休息的 endTime endTime = startTime.plus(requiredDuration); // 考虑休息时间暂停(假设 BREAK_TIMES 是每日固定休息点,如 [12:00, 17:00] 等) LocalDateTime _endTime = handleBreakTimes(startTime, endTime, 1); // 设置最终时间 order.setPstartTime(Date.from(startTime.atZone(ZoneId.systemDefault()).toInstant())); order.setPfinishedTime(Date.from(_endTime.atZone(ZoneId.systemDefault()).toInstant())); } 改一下这个方法, 因为 Duration requiredDuration = Duration.ofSeconds(totalSeconds); 我用了秒, 所以后绪的也应该是秒比较
10-26
ListIterator 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) { // continue; 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)); } // 过滤只属于这条产线的班组日历 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()); // 匹配最佳班组 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); // 处理换型, 锁定工单和本条是否存在换型情况 if (!order.getShiftType().equals(PpShiftTypeEnum.换型班次.getCode()) && PpOrderLockStatus.UN_LOCKED.getCode().equals(lockOrder) && !order.isProcessFlag1()) { int _intI = processOrderAndLockChange( prevList, order, bestGroup, groupRemaining, minChangeTime, changeMap, filledClasses2, iterator, minutesQty ); if (_intI == 1) { continue; } } // 判断本条工单与前后锁单存在换型情况 private int processOrderAndLockChange(List prevList, MrpListDTO order, PpGroupScheduleDTO bestGroup, int groupRemaining, BigDecimal minChangeTime, Map<String, List> changeMap, Map<String, Set> filledClasses2, ListIterator iterator, BigDecimal minutesQty) { 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(); } } // 判断换型是否合理, // 如果两条换型时间之和 >= 可用时间, 报错 BigDecimal sum = changeTime.add(changeTime2); BigDecimal preeTime = BigDecimal.valueOf(groupRemaining); if (sum.compareTo(preeTime) >= 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 )); } // 如果工单时间 + 换型时间 > 可用时间, 那就要把工单切掉 if (sum.compareTo(BigDecimal.ZERO) > 0) { sum = sum.add(totalTime); if (sum.compareTo(preeTime) > 0) { BigDecimal diff = sum.subtract(preeTime); 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 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; } private static final LocalTime DAY_SHIFT_START = LocalTime.of(8, 0); // 白班开始 private static final LocalTime DAY_SHIFT_END = LocalTime.of(20, 0); // 白班结束 private static final LocalTime NIGHT_SHIFT_START = LocalTime.of(20, 0); // 晚班开始 private static final LocalTime NIGHT_SHIFT_END = LocalTime.of(8, 0); // 晚班结束(次日) 目前 在processOrderAndLockChange处理本条正常工单和前后时段都是锁定工单时, 判断换型不够严谨. 锁定工单先排, 且是固定时段, 所以中间会有班组空着时段, 正常工单就见缝插针, 这时前后可能产生换型工单, 但是后面是锁定时, 不能直接这样判断, 因为如果后绪还有同产线不同料号的正常工单依旧分配这个时段(上一条正常工单时间占比少), 那么上一条的理论"下一条"应该是本条正常工单而不是下一条锁定工单. 这怎么改? 其实我想了一下, 1. 把两条锁定工单之间所有本条产线可用的班组时段找出来, 如果 前一条换型时间 + totalTime >= 这个时段, 说明只能放下这条产线了, 那么后一条换型合理 2. 否则再找待排的列表里下一条本产线的工单, 如果没有这条, 那么后一条换型合理; 如果有且能排到这里(因为后绪可能还有不同产线的任务工单, 说不定也能排到这里, 但是换型只在同产线中, 所以他们排到这里也不影响后一条换型), 那么后一条换型不合理
12-02
/** * 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
12-03
for (MrpListDTO order : list) { //需求日期 Date demandDate = order.getDemandDate(); String onlyDaily = order.getOnlyDaily(); String lockOrder = order.getLockOrder(); Date endDate = order.getEndDate(); order.setVersion(version); //历史工单不用排产, 时间已经在前面占用了, 这里不用处理, 除非是手工单 if (PpOrderLockStatus.LOCKED.getCode().equals(lockOrder) && !PpOrderTypeEnum.手工建单.getCode().equals(order.getType())) { continue; } orderStartDate = null; needOccupy = false; occupyTime = 0L; classMinute = 0; orderClassTypeMap.clear(); if (PpOrderTypeEnum.手工建单.getCode().equals(order.getType())) { orderStartDate = DateUtils.format(order.getStartTime(), DateUtils.YYYY_M_D); currentScheduleTime = order.getStartTimeV2() == null ? order.getStartTime() : order.getStartTimeV2(); } totalQty = BigDecimal.ZERO; addQty = BigDecimal.ZERO; actualClassNumber = BigDecimal.ZERO; originalQty = order.getQty(); totalTime = order.getTotalTime(); order.calculateTimeV3(); order.calculateTimeV4(); unitTime = order.getMinutesQty(); //计算实际班次 从当前日期到订单需求日期,根据日历信息 实际有多少个班 order.setActualClassNumber(actualClassNumber(order)); preChangeTime = order.getPreChangeTime(); List<PpCalendarDTO> availableDays = calendarMap.get(order.getLine()); if (availableDays.isEmpty()) { emptyCalendarList.add(order.getLine()); continue; } if (PpOrderTypeEnum.手工建单.getCode().equals(order.getType())) { /* * 判断是否有当天的出勤 * */ checkWorkDay(currentScheduleTime, availableDays, order.getLine()); } int len = occupyLists.size(); //availableDays 生产日历 for (int i = 0; i < availableDays.size(); i++) { day = availableDays.get(i); if (orderStartDate != null && !orderStartDate.equals(day.getDateTime())) { continue; } if (PpOrderTypeEnum.手工建单.getCode().equals(order.getType())) { orderStartDate = null; } //根据该产线的前一个工单对应的班次是否占用完 判断是否需要颠倒白晚班 if (lastClassMap.containsKey(order.getLine())) { if (lastClassMap.get(order.getLine())) { classList.sort((t1, t2) -> t2.getSort() - t1.getSort()); } else { classList.sort(Comparator.comparingInt(PpClassDTO::getSort)); } } else { classList.sort(Comparator.comparingInt(PpClassDTO::getSort)); } for (int shiftIndex = 0; shiftIndex < classList.size(); shiftIndex++) { shift = classList.get(shiftIndex); //如果是手工单,开工时间是手工指定的,需要在当天开工 首个班次必须要在指定的时间 if (PpOrderTypeEnum.手工建单.getCode().equals(order.getType()) && totalQty.compareTo(BigDecimal.ZERO) == 0) { if (!order.getClassName().equals(shift.getClassName())) continue; } //记录每个工单用了几种种类型的班次 if (!orderClassTypeMap.containsKey(shift.getClassName())) { orderClassTypeMap.put(shift.getClassName(), shift.getClassName()); classMinute += shift.getEffectiveTime(); } fillDate(day, shift); int availableTime = shift.getEffectiveTime(); int _availableTime = availableTime; //如果是手工单,开工时间可能是在班次的中间,此时需要去掉班次开工时间到指定的开工时间之间的时间 否则结束时间会溢出 if (PpOrderTypeEnum.手工建单.getCode().equals(order.getType()) && day.getDateTime().equals(DateUtils.format(order.getStartTime(), DateUtils.YYYY_M_D))) { /* * 手工前台给定的时间可能是晚班时间(超过了晚上12点那种) 此时需要特殊处理 * 手工单的实际开工时间需要特殊处理 * */ setHandOrderTime(order); availableTime = getEffectiveTime(shift, order.getStartTime()); //说明手工单是在班次的中间开工 那么该班次需要设为占用日期, needOccupy = true; order.setMiddle(true); } String occupyDate = day.getDateTime(); shiftKey = occupyDate + "_" + order.getLine() + "_" + shift.getClassName(); occupiedTime = occupiedTimeByShift .computeIfAbsent(shiftKey, k -> new HashMap<>()) .getOrDefault(shift.getClassName(), 0); // 计算停产期间影响的时间 int stopOverlapMinutes = calculateStopOverlap(shift.getBeginTime(), shift.getEndTime(), day.getDateTime()); if (!needOccupy) { remainingTime = availableTime - occupiedTime - stopOverlapMinutes; } else { remainingTime = Math.min(_availableTime - occupiedTime - stopOverlapMinutes, availableTime - stopOverlapMinutes); } if (remainingTime > 0) { int timeToAllocate = Math.min(remainingTime, order.getTotalTime().intValue()); if (preChangeTime.intValue() > 0) { timeToAllocate = Math.min(remainingTime, preChangeTime.intValue()); } remainingTime = remainingTime - timeToAllocate; //占用 BigDecimal augend; if (preChangeTime.compareTo(BigDecimal.ZERO) > 0) { augend = BigDecimal.ZERO; } else { augend = BigDecimal.valueOf(timeToAllocate).multiply(unitTime).setScale(0, RoundingMode.HALF_UP); if (augend.compareTo(originalQty.subtract(totalQty)) > 0) { //防止数量溢出 augend = originalQty.subtract(totalQty); } } occupyTime = occupyTime + timeToAllocate; addQty = BigDecimal.ZERO; // TMC工单不需要 if (!"2".equals(order.getType())) { // if (order.getOperation() == PpOrderOperationEnum.正常排程 && remainingTime > 0 && // BigDecimal.valueOf(occupyTime).compareTo(order.getTotalTime().add(order.getPreChangeTime())) >= 0) { // //工单本班次可以做完并且本班次还有剩余时间 // //ABS(当班剩余可用时间对应的产量)/生产批量<20% 时,将该班次排满(将20%时间的产量都加到当前的工单上,工单数量+20%产量) // addQty = order.getQty() // .divide(totalTime, 4, RoundingMode.HALF_UP) // .multiply(BigDecimal.valueOf(remainingTime)) // .setScale(4, RoundingMode.HALF_UP); // // if (addQty.divide(order.getQty(), 2, RoundingMode.HALF_UP).compareTo(assemblyPercent) > 0) { // addQty = BigDecimal.ZERO; // } else { // addQty = addQty.setScale(0, RoundingMode.HALF_UP); // timeToAllocate = timeToAllocate + remainingTime; // totalTime = totalTime.add(BigDecimal.valueOf(remainingTime)); // remainingTime = 0; // } // } } ppClassDTO = shift; Date shiftEndTime; if (order.getClassName() == null) { order.setClassName(shift.getClassName()); } //手工单的首个班次需要指定开工时间:目前只有手工单在排程时有首班次开工时间 if (currentScheduleTime == null) { currentScheduleTime = shift.getBeginTime(); } //判断开始时间是否在休息时间内 如果是 开始时间等于休息结束时间 currentScheduleTime = parseTimeByClassStop(ppClassDTO, currentScheduleTime); shiftEndTime = new Date(currentScheduleTime.getTime() + (timeToAllocate * 60000L)); if (order.getStartTime() == null || (totalQty.compareTo(BigDecimal.ZERO) <= 0 && PpOrderTypeEnum.手工建单.getCode().equals(order.getType()))) { //0515改动后 order.setStartTime(currentScheduleTime); } //班组内时间已经用完 //判断结束时间是否等于班次结束时间 如果不等于,则判断是否需要加上中间的休息时间 if (shiftEndTime.compareTo(ppClassDTO.getEndTime()) != 0) { shiftEndTime = parseTimeByClassStop(currentScheduleTime, shiftEndTime, ppClassDTO); } //班次完工时间 order.setEndTime(shiftEndTime); PpScheduleOccupyList occList = getOccList(order, timeToAllocate, shift.getClassName(), augend, occupyDate, currentScheduleTime); if (augend.compareTo(BigDecimal.ZERO) == 0) { occList.setShiftType(PpShiftTypeEnum.换型班次.getCode()); } else if (order.isClimb() && order.getClimQty() > 0) { occList.setShiftType(PpShiftTypeEnum.爬坡班次.getCode()); } else { occList.setShiftType(PpShiftTypeEnum.正常班次.getCode()); } List<PpScheduleOccupyList> _occList = null; Date shiftBeginTime; if (this.lineOccupyTimeMap.containsKey(order.getLine()) && this.lineOccupyTimeMap.get(order.getLine()).size() > 0) { //判断是否需要将一个班次拆分为多个 _occList = spiltOccupy(occList, shift); PpScheduleOccupyList occupyList = _occList.get(_occList.size() - 1); shiftBeginTime = occupyList.getPstartTime(); } else { shiftBeginTime = occList.getPstartTime(); } //获取爬坡阶梯数据,如果存在爬坡阶梯数据并且:1.当前班次的开工时间晚于爬坡阶梯的开始时间 && 2.已爬坡班次数小于需要爬坡的班次数;则从该班次开始爬坡 //返回需要爬坡的班次以及oee、ct数据 if (order.getOeeCtData() == null) { order.setOeeCtData(getClimeData(order, shiftBeginTime)); if (order.getOeeCtData() != null) { /** * 需要爬坡 * */ //去掉已经生产的产量 order.setQty(order.getQty().subtract(totalQty)); //用新的oee和ct计算剩余时间 order.setOee(order.getOeeCtData().oee.toPlainString()); order.setCt(order.getOeeCtData().ct.toPlainString()); order.calculateTime(); //更新单位分钟内生产的数量 unitTime = order.getQty().divide(order.getTotalTime(), 4, RoundingMode.HALF_UP); shiftIndex--; continue; } } if (this.lineOccupyTimeMap.containsKey(order.getLine()) && this.lineOccupyTimeMap.get(order.getLine()).size() > 0) { //判断是否需要将一个班次拆分为多个 _occList = spiltOccupy(occList, shift); PpScheduleOccupyList occupyList = _occList.get(_occList.size() - 1); shiftBeginTime = occupyList.getPstartTime(); /* * 最后一个班次加上 20% 的产量 * */ occupyList.setAddQty(occupyList.getQty().add(addQty)); shiftEndTime = occupyList.getPfinishedTime(); if (totalQty.compareTo(BigDecimal.ZERO) <= 0) { order.setStartTime(_occList.get(0).getPstartTime()); } //班次完工时间 order.setEndTime(shiftEndTime); this.occupyLists.addAll(_occList); } else { shiftBeginTime = occList.getPstartTime(); this.occupyLists.add(occList); } occupiedTimeByShift.get(shiftKey).put(shift.getClassName(), occupiedTime + timeToAllocate); this.lineOccupyTimeMap.putIfAbsent(order.getLine(), new ArrayList<>()); if (_occList != null) { this.lineOccupyTimeMap.get(order.getLine()).addAll(buildPpClassRemoveTime(_occList)); } else { this.lineOccupyTimeMap.get(order.getLine()).add(buildPpClassRemoveTime(occList)); } totalQty = totalQty.add(augend); //优先消耗掉换型时间 if (preChangeTime.intValue() <= 0) { order.setTotalTime(order.getTotalTime().subtract(new BigDecimal(timeToAllocate))); // // TMC工单不需要 // if (!"2".equals(order.getType())) { //// 工单本班次可以做完并且本班次还有剩余时间, ABS(当班剩余可用时间对应的产量)/生产批量<20% 时,将工单后续数量舍去 // if (order.getOperation() == PpOrderOperationEnum.正常排程 && // (order.getTotalTime().multiply(unitTime).setScale(6, RoundingMode.HALF_UP)) // .divide(BigDecimalUtils.getDecimal(order.getPbatch()), 4, RoundingMode.HALF_UP) // .compareTo(assemblyPercent) < 0) { // // order.setTotalTime(BigDecimal.ZERO); // totalTime = totalTime.subtract(order.getTotalTime()); // } // } } else { preChangeTime = preChangeTime.subtract(new BigDecimal(timeToAllocate)); if (remainingTime > 0) { i--; break; } } currentScheduleTime = null; if (order.isClimb() && order.getClimQty() > 0) { //爬坡班次数-1 order.setClimQty(order.getClimQty() - 1); order.getOeeCtData().climeData.setClimedQty(order.getOeeCtData().climeData.getClimedQty().add(BigDecimal.ONE)); //添加爬坡阶梯和工单班次数对照记录 if (order.getOeeCtData().climeData.getOrderQtyMap().containsKey(order.getOrderNo())) { order.getOeeCtData().climeData.getOrderQtyMap().put(order.getOrderNo(), order.getOeeCtData().climeData.getOrderQtyMap().get(order.getOrderNo()).add(BigDecimal.ONE)); } else { order.getOeeCtData().climeData.getOrderQtyMap().put(order.getOrderNo(), BigDecimal.ONE); } if (order.getClimQty() <= 0) { //重新计算工单工时 //去掉已经生产的产量 order.setQty(originalQty.subtract(totalQty)); //用新的oee和ct计算剩余时间 order.setOee(order.getOeeCtData().oeeOrigin); order.setCt(order.getOeeCtData().ctOrigin); order.calculateTime(); if (order.getTotalTime().compareTo(BigDecimal.ZERO) <= 0) { //爬坡完成 工单也完成了 break; } //更新单位分钟内生产的数量 unitTime = order.getQty().divide(order.getTotalTime(), 4, RoundingMode.HALF_UP); } } if (order.getTotalTime().compareTo(BigDecimal.ZERO) <= 0) { break; } } } if (order.getTotalTime().compareTo(BigDecimal.ZERO) <= 0) { order.setQty(totalQty); order.setAddQty(order.getQty().add(addQty)); } if (occupyLists.size() > len) { //判断该产线下个工单是否需要颠倒白晚班次 extracted(order, lastClassMap, ppClassDTO, remainingTime, lineClassFlag, day); len = occupyLists.size(); } if (order.getTotalTime().compareTo(BigDecimal.ZERO) <= 0) { break; } } if (order.getEndTime() == null) { //日历没维护 throw new ServiceException(String.format("请先维护%s区域%s产线%s的生产日历", order.getArea(), order.getLine(), getMsg(availableDays.get(availableDays.size() - 1).getDateTime()))); } if (DateUtils.format(order.getEndTime(), DateUtils.YYYY_MM_DD).compareTo(DateUtils.format(demandDate, DateUtils.YYYY_MM_DD)) > 0) { //延期 order.setDelay("1"); } if (order.getActualClassNumber().compareTo(actualClassNumber) < 0) { //爆产 order.setOverflow("1"); } //实际排班班次 order.setTotalTime(getTotalTime(occupyLists, order.getOrderNo())); order.setActualClassNumber(BigDecimal.valueOf(occupyTime).divide(BigDecimal.valueOf(classMinute).divide(BigDecimal.valueOf(orderClassTypeMap.size())), 6, RoundingMode.HALF_UP)); order.setCreateTime(now); order.setCreateBy(username); order.setTimeDesc(getTimeDesc(order.getTotalTime())); //手工单自动锁定 if (PpOrderTypeEnum.手工建单.getCode().equals(order.getType())) { autoLockOrderList.add(buildLockOrder(order)); } //工单数量不能改 // order.setQty(originalQty); } 日历类public class PpCalendarDTO { private Long id; private String dateTime;//yyyy-MM-dd private String line;} 1. onlyDaily="2"的数据通常会先排,="1"的通常后排, 这个已经处理好了 2. 日历类已升序 3. onlyDaily="1"的数据排的时候要注意, 因为数据都是一条条紧跟着排的, 有可能上一条的结束时间就是下一条的开始时间, 但是有新规: onlyDaily="1"的数据不能默认跟着="2"的数据排. 因为他们的数据通常demandDate会比较靠后, 不能提前太多生产: a. onlyDaily="1"的数据按照demandDate, 最多提前到demandDate所在周的周一, 如果有多条公用周一, 那就接着排, 例如, 第一条onlyDaily="1"排在周一, 那第二条就跟着第一条的结束时间排, 这个其实源代码逻辑就有; b. 如果onlyDaily="2"的数据排的最后一条结束时间与第一条="1"的数据在同一周, 那="1"的数据也不用提前到周一了, 就跟着="2"的排; c. ="1"的数据还是要看前一条数据结束时间是否和当条demandDate在同一周, 如果是, 那就不用提前到周一, 就跟着前一条的结束时间排 d. 其它代码别动.别改
11-11
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值