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. 其它代码别动.别改
最新发布