package cn.iocoder.yudao.module.mes.service.pro.task.schedule;
|
|
import cn.hutool.crypto.digest.DigestUtil;
|
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
|
import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.*;
|
import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProBatchSchedulePreviewRespVO.*;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
|
import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.dv.machinery.MesDvMachineryMapper;
|
import cn.iocoder.yudao.module.mes.enums.pro.*;
|
import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
|
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
|
import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
|
import cn.iocoder.yudao.module.mes.service.pro.route.*;
|
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
|
import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
|
import cn.iocoder.yudao.module.mes.service.cal.holiday.MesCalHolidayService;
|
import jakarta.annotation.Resource;
|
import org.springframework.context.annotation.Lazy;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Isolation;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.time.*;
|
import java.util.*;
|
import java.util.stream.Collectors;
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
|
|
@Service
|
public class MesProBatchScheduleService {
|
@Resource private MesProTaskMapper taskMapper;
|
@Resource private MesProWorkOrderMapper workOrderMapper;
|
@Resource private MesDvMachineryMapper machineryMapper;
|
@Resource private MesMdWorkstationService workstationService;
|
@Resource private MesProWorkOrderProcessService workOrderProcessService;
|
@Resource private MesProRouteService routeService;
|
@Resource private MesProRouteProcessService routeProcessService;
|
@Resource private MesProRouteProductService routeProductService;
|
@Resource private MesProProcessService processService;
|
@Resource private MesMdItemService itemService;
|
@Resource private MesCalHolidayService holidayService;
|
@Resource @Lazy private cn.iocoder.yudao.module.mes.service.pd.processparamtemplate.MesPdProcessParamTemplateService paramTemplateService;
|
@Resource private MesProScheduleGuardService guard;
|
@Resource private CrmCustomerApi customerApi;
|
@Resource @Lazy private MesProTaskService taskService;
|
|
private LocalDateTime now() { return LocalDateTime.now(ZoneId.of("Asia/Shanghai")).withNano(0); }
|
|
public MesProBatchSchedulePreviewRespVO preview(MesProBatchScheduleReqVO request) {
|
validateRequest(request);
|
return build(request);
|
}
|
|
@Transactional(rollbackFor = Exception.class, isolation = Isolation.SERIALIZABLE, timeout = 60)
|
public List<Long> execute(MesProBatchScheduleReqVO request) {
|
validateRequest(request);
|
if (request.getPreviewTime() == null || request.getPreviewHash() == null
|
|| request.getPreviewTime().isAfter(now()) || request.getPreviewTime().plusMinutes(15).isBefore(now())) {
|
throw exception(PRO_TASK_SCHEDULE_PREVIEW_CHANGED);
|
}
|
guard.lock();
|
workOrderMapper.lockScheduleOrders(request.getWorkOrderIds().stream().sorted().toList());
|
var preview = build(request);
|
if (!preview.isCanExecute()) {
|
throw exception(PRO_TASK_SCHEDULE_INVALID, preview.getOrders().stream()
|
.filter(order -> order.getError() != null).map(OrderPlan::getError).collect(Collectors.joining(";")));
|
}
|
if (!Objects.equals(request.getPreviewHash(), preview.getPreviewHash())) {
|
throw exception(PRO_TASK_SCHEDULE_PREVIEW_CHANGED);
|
}
|
if (preview.getTasks().stream().anyMatch(task -> task.getStartTime().isBefore(now()))) {
|
throw exception(PRO_TASK_SCHEDULE_PREVIEW_CHANGED);
|
}
|
if (!preview.getWarnings().isEmpty() && !Boolean.TRUE.equals(request.getAllowLate())) {
|
throw exception(PRO_TASK_SCHEDULE_INVALID, "请先确认预览中的交期延期风险");
|
}
|
List<Long> ids = new ArrayList<>();
|
for (var plan : preview.getTasks()) {
|
var task = new MesProTaskSaveReqVO();
|
task.setWorkOrderId(plan.getWorkOrderId());
|
task.setRouteId(plan.getRouteId());
|
task.setProcessId(plan.getProcessId());
|
task.setItemId(plan.getItemId());
|
task.setParamTemplateId(plan.getParamTemplateId());
|
task.setColorCode(plan.getColorCode());
|
task.setWorkstationId(plan.getWorkstationId());
|
task.setQuantity(plan.getQuantity());
|
task.setDuration(plan.getDuration());
|
task.setStartTime(plan.getStartTime());
|
task.setEndTime(plan.getEndTime());
|
task.setRemark("一键排产:09:00–18:00,排除周末及节假日");
|
ids.add(taskService.createTask(task));
|
}
|
// 每道工序均补齐后,工单的累计排产数量只记一次,不能按工序数累加。
|
for (var order : preview.getOrders()) {
|
workOrderMapper.updateById(new MesProWorkOrderDO().setId(order.getWorkOrderId())
|
.setQuantityScheduled(order.getQuantity()));
|
}
|
return ids;
|
}
|
|
private void validateRequest(MesProBatchScheduleReqVO request) {
|
var ids = request.getWorkOrderIds();
|
if (ids == null || ids.isEmpty() || ids.size() > 30 || ids.stream().anyMatch(id -> id == null || id <= 0)
|
|| new HashSet<>(ids).size() != ids.size()) {
|
throw exception(PRO_TASK_SCHEDULE_INVALID, "请选择1至30个不同的有效工单");
|
}
|
if (request.getStartTime() == null || request.getStartTime().isBefore(now().minusMinutes(15))
|
|| request.getStartTime().isAfter(now().plusDays(365))) {
|
throw exception(PRO_TASK_SCHEDULE_INVALID, "最早开工时间应为当前时间至未来一年以内,请重新选择");
|
}
|
}
|
|
private MesProBatchSchedulePreviewRespVO build(MesProBatchScheduleReqVO request) {
|
var result = new MesProBatchSchedulePreviewRespVO();
|
result.setPreviewTime(now());
|
var orders = workOrderMapper.selectByIds(request.getWorkOrderIds());
|
var orderMap = orders.stream().collect(Collectors.toMap(MesProWorkOrderDO::getId, order -> order));
|
List<Long> permitted = customerApi.getPermittedCustomerIds();
|
var stations = workstationService.getWorkstationListByStatus(0).stream()
|
.sorted(Comparator.comparing(MesMdWorkstationDO::getId)).toList();
|
var machineMap = machineryMapper.selectList().stream().collect(Collectors.toMap(MesDvMachineryDO::getId, machine -> machine));
|
var stationMachines = guard.machineIdsByStation();
|
Map<Long, MesProTaskDO> allRelevantTasks = new TreeMap<>();
|
taskMapper.selectSchedulingTasks().forEach(task -> allRelevantTasks.put(task.getId(), task));
|
taskMapper.selectListByWorkOrderIds(request.getWorkOrderIds()).stream()
|
.filter(task -> !Objects.equals(task.getStatus(), MesProTaskStatusEnum.CANCELED.getStatus()))
|
.forEach(task -> allRelevantTasks.put(task.getId(), task));
|
var existingTasks = new ArrayList<>(allRelevantTasks.values());
|
var holidays = holidayService.getHolidayList(request.getStartTime().toLocalDate().atStartOfDay(), request.getStartTime().plusYears(1));
|
Set<LocalDate> holidayDates = holidays.stream().filter(day -> Objects.equals(day.getType(), 2))
|
.map(day -> day.getDay().toLocalDate()).collect(Collectors.toSet());
|
var calendar = new MesProScheduleEngine.Calendar(holidayDates, request.getStartTime().toLocalDate().plusYears(1));
|
List<MesProScheduleEngine.Busy> busy = new ArrayList<>();
|
Set<Long> unknownStations = new HashSet<>();
|
for (var task : existingTasks) {
|
if (MesProTaskStatusEnum.isEndStatus(task.getStatus()) || task.getWorkstationId() == null) continue;
|
if (task.getStartTime() == null || task.getEndTime() == null || !task.getEndTime().isAfter(task.getStartTime())
|
|| task.getEndTime().isBefore(now())) {
|
unknownStations.add(task.getWorkstationId());
|
} else {
|
busy.add(new MesProScheduleEngine.Busy(task.getWorkstationId(),
|
stationMachines.getOrDefault(task.getWorkstationId(), Set.of()), task.getStartTime(), task.getEndTime()));
|
}
|
}
|
var sorted = orders.stream().sorted(Comparator.comparing(MesProWorkOrderDO::getRequestDate,
|
Comparator.nullsLast(Comparator.naturalOrder())).thenComparing(MesProWorkOrderDO::getId)).toList();
|
List<Long> orderSequence = new ArrayList<>();
|
Set<Long> visiting = new HashSet<>();
|
for (var order : sorted) addChildrenFirst(order.getId(), orderMap, orderSequence, visiting);
|
for (Long id : request.getWorkOrderIds()) if (!orderMap.containsKey(id)) orderSequence.add(id);
|
Map<Long, LocalDateTime> ends = new HashMap<>();
|
Map<Long, String> templateHashes = new TreeMap<>();
|
final List<MesProScheduleEngine.Busy> existingBusy = List.copyOf(busy);
|
for (Long id : orderSequence) {
|
var order = orderMap.get(id);
|
var orderPlan = new OrderPlan();
|
orderPlan.setWorkOrderId(id);
|
result.getOrders().add(orderPlan);
|
try {
|
if (order == null) throw new IllegalArgumentException("工单不存在或已删除");
|
if (permitted != null && order.getClientId() != null && !permitted.contains(order.getClientId())) {
|
throw new IllegalArgumentException("无权操作该客户的工单");
|
}
|
orderPlan.setWorkOrderCode(order.getCode());
|
orderPlan.setWorkOrderName(order.getName());
|
orderPlan.setQuantity(order.getQuantity());
|
orderPlan.setRequestDate(order.getRequestDate());
|
if (!Objects.equals(order.getStatus(), MesProWorkOrderStatusEnum.CONFIRMED.getStatus())
|
|| !Objects.equals(order.getType(), MesProWorkOrderTypeEnum.SELF.getType())) {
|
throw new IllegalArgumentException("仅支持已确认的自行生产工单");
|
}
|
if (order.getQuantity() == null || order.getQuantity().signum() <= 0
|
|| (order.getQuantityProduced() != null && order.getQuantityProduced().compareTo(order.getQuantity()) >= 0)) {
|
throw new IllegalArgumentException("工单数量无效或已生产完成");
|
}
|
LocalDateTime earliest = calendar.nextWorkingTime(request.getStartTime());
|
// 父工单必须等子工单;同一批次按依赖顺序排,未纳入的未完成子工单阻止排产。
|
for (var child : workOrderMapper.selectList(MesProWorkOrderDO::getParentId, id)) {
|
if (Objects.equals(child.getStatus(), MesProWorkOrderStatusEnum.FINISHED.getStatus())) continue;
|
if (!ends.containsKey(child.getId())) throw new IllegalArgumentException("请将未完成子工单" + child.getCode() + "一并选中并先完成排产");
|
if (ends.get(child.getId()).isAfter(earliest)) earliest = ends.get(child.getId());
|
}
|
Long routeId = order.getRouteId();
|
if (routeId == null) {
|
var relation = routeProductService.getRouteProductByItemId(order.getProductId());
|
if (relation != null) routeId = relation.getRouteId();
|
}
|
if (routeId == null) throw new IllegalArgumentException("未配置工艺路线,请先维护工单工序及产品路线");
|
var route = routeService.validateRouteExists(routeId);
|
if (!Objects.equals(route.getStatus(), 0)) throw new IllegalArgumentException("工艺路线已停用");
|
List<MesProRouteProcessDO> processes = processes(id, routeId);
|
if (processes.isEmpty()) throw new IllegalArgumentException("没有工序,请先维护工单工序");
|
Set<Long> processIds = new HashSet<>();
|
Set<Integer> sorts = new HashSet<>();
|
Map<Long, Integer> processSorts = processes.stream().filter(process -> process.getProcessId() != null && process.getSort() != null)
|
.collect(Collectors.toMap(MesProRouteProcessDO::getProcessId, MesProRouteProcessDO::getSort, (first, duplicate) -> first));
|
List<TaskPlan> plans = new ArrayList<>();
|
Map<Long, BigDecimal> targetQuantities = calcTargetQuantities(processes, order.getQuantity());
|
List<MesProScheduleEngine.Busy> trialBusy = new ArrayList<>(busy);
|
for (var process : processes) {
|
if (process.getProcessId() == null || process.getSort() == null || !processIds.add(process.getProcessId()) || !sorts.add(process.getSort())) {
|
throw new IllegalArgumentException("工序及顺序必须完整且不能重复,请先维护工单工序");
|
}
|
if (process.getNextProcessId() != null && (!processSorts.containsKey(process.getNextProcessId())
|
|| processSorts.get(process.getNextProcessId()) <= process.getSort())) {
|
throw new IllegalArgumentException("工序衔接与顺序不一致,请先维护工单工序");
|
}
|
int prepareMinutes = process.getPrepareTime() == null ? 0 : process.getPrepareTime();
|
int waitMinutes = process.getWaitTime() == null ? 0 : process.getWaitTime();
|
if (prepareMinutes < 0 || waitMinutes < 0) throw new IllegalArgumentException("工序准备时间和等待时间不能为负数");
|
if (process.getParamTemplateId() != null) {
|
paramTemplateService.validateProcessParamTemplateExists(process.getParamTemplateId());
|
var details = paramTemplateService.getProcessParamTemplateDetailListByTemplateId(process.getParamTemplateId());
|
if (details.isEmpty()) {
|
throw new IllegalArgumentException("工序参数模板没有明细,请先完善参数模板");
|
}
|
templateHashes.put(process.getParamTemplateId(), DigestUtil.sha256Hex(JsonUtils.toJsonString(details)));
|
}
|
processService.validateProcessExistsAndEnable(process.getProcessId());
|
var processData = processService.getProcess(process.getProcessId());
|
if (processData == null) throw new IllegalArgumentException("工序配置已变化,请重新维护后预览");
|
var ownTasks = existingTasks.stream().filter(task -> Objects.equals(task.getWorkOrderId(), id)
|
&& Objects.equals(task.getProcessId(), process.getProcessId())).toList();
|
if (ownTasks.stream().anyMatch(task -> task.getQuantity() == null || task.getQuantity().signum() <= 0)) {
|
throw new IllegalArgumentException("当前工序已有任务数量无效,请先修正历史任务数量");
|
}
|
var assigned = ownTasks.stream().map(task -> task.getQuantity() == null ? BigDecimal.ZERO : task.getQuantity()).reduce(BigDecimal.ZERO, BigDecimal::add);
|
// 各道工序按收率倒推的目标产出量:末道=订单量,前面各道逐级放大
|
var targetQuantity = targetQuantities.getOrDefault(process.getProcessId(), order.getQuantity());
|
var remaining = targetQuantity.subtract(assigned);
|
if (remaining.signum() < 0) throw new IllegalArgumentException("工序" + processData.getName() + "已有排产数量超限,请先核对");
|
for (var task : ownTasks) {
|
if (!MesProTaskStatusEnum.isEndStatus(task.getStatus()) && (task.getStartTime() == null
|
|| task.getEndTime() == null || !task.getEndTime().isAfter(task.getStartTime()) || task.getEndTime().isBefore(now()))) {
|
throw new IllegalArgumentException("工序" + processData.getName() + "已有任务时间不完整或已超时未完成,请先维护");
|
}
|
}
|
LocalDateTime existingEnd = ownTasks.stream().map(task -> task.getEndTime() != null ? task.getEndTime() : task.getFinishDate())
|
.filter(Objects::nonNull).max(Comparator.naturalOrder()).orElse(earliest);
|
if (remaining.signum() == 0) {
|
if (existingEnd.isAfter(earliest)) earliest = existingEnd;
|
earliest = earliest.plusMinutes(waitMinutes);
|
continue;
|
}
|
Long itemId = order.getProductId();
|
if (process.getOutputItemId() != null && !process.getOutputItemId().isBlank()) {
|
String[] output = process.getOutputItemId().split(",");
|
if (output.length != 1) throw new IllegalArgumentException("多产出工序请使用手工排产确认各产出数量");
|
itemId = Long.valueOf(output[0].trim());
|
}
|
var item = itemService.validateItemExists(itemId);
|
if (!Objects.equals(item.getStatus(), 0)) throw new IllegalArgumentException("工序产出产品已停用");
|
TaskPlan best = null;
|
for (var station : stations) {
|
if (!Objects.equals(station.getProcessId(), process.getProcessId())) continue;
|
Set<Long> machineIds = stationMachines.getOrDefault(station.getId(), Set.of());
|
if (unknownStations.contains(station.getId()) || machineIds.isEmpty()) continue;
|
if (unknownStations.stream().anyMatch(other -> !Collections.disjoint(machineIds, stationMachines.getOrDefault(other, Set.of())))) continue;
|
List<MesDvMachineryDO> machines = machineIds.stream().map(machineMap::get).filter(Objects::nonNull)
|
.filter(machine -> Objects.equals(machine.getStatus(), 1) || Objects.equals(machine.getStatus(), 2)).toList();
|
if (machines.isEmpty() || machines.stream().anyMatch(machine -> machine.getProductionEfficiency() == null || machine.getProductionEfficiency().signum() <= 0)) continue;
|
Set<Long> usableIds = machines.stream().map(MesDvMachineryDO::getId).collect(Collectors.toCollection(TreeSet::new));
|
if (machines.stream().anyMatch(machine -> Objects.equals(machine.getStatus(), 2) && existingBusy.stream().noneMatch(interval -> interval.machineryIds().contains(machine.getId())))) continue;
|
BigDecimal capacity = machines.stream().map(machine -> machine.getProductionEfficiency().multiply(BigDecimal.valueOf(
|
machine.getConveyorBelt() == null || machine.getConveyorBelt() <= 0 ? 1 : machine.getConveyorBelt())))
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
MesProScheduleEngine.Slot slot;
|
try {
|
int hours = Math.addExact(MesProScheduleEngine.duration(remaining, capacity),
|
Math.toIntExact(((long) prepareMinutes + 59) / 60));
|
slot = MesProScheduleEngine.findSlotHours(station.getId(), machineIds, hours, earliest, trialBusy, calendar);
|
} catch (IllegalArgumentException | ArithmeticException unavailable) {
|
continue; // 一台低产能工作站超出排产窗口时,仍继续尝试其他工作站。
|
}
|
if (best == null || slot.end().isBefore(best.getEndTime())) {
|
best = new TaskPlan();
|
best.setWorkOrderId(id); best.setWorkOrderCode(order.getCode());
|
best.setRouteId(routeId); best.setRouteName(route.getName());
|
best.setProcessId(process.getProcessId()); best.setProcessName(processData.getName()); best.setSort(process.getSort());
|
best.setItemId(itemId); best.setItemName(item.getName()); best.setParamTemplateId(process.getParamTemplateId()); best.setColorCode(process.getColorCode());
|
best.setWorkstationId(station.getId()); best.setWorkstationName(station.getName());
|
best.setMachineryIds(new ArrayList<>(usableIds)); best.setMachineryNames(machines.stream().map(MesDvMachineryDO::getName).collect(Collectors.joining("、")));
|
best.setHourlyCapacity(capacity); best.setQuantity(remaining); best.setDuration(slot.hours()); best.setStartTime(slot.start()); best.setEndTime(slot.end());
|
}
|
}
|
if (best == null) throw new IllegalArgumentException("工序" + processData.getName() + "没有可用工作站及有效设备产能,或资源存在未知/超时任务,请先维护");
|
plans.add(best);
|
trialBusy.add(new MesProScheduleEngine.Busy(best.getWorkstationId(), stationMachines.getOrDefault(best.getWorkstationId(), Set.of()), best.getStartTime(), best.getEndTime()));
|
earliest = best.getEndTime().isAfter(existingEnd) ? best.getEndTime() : existingEnd;
|
earliest = earliest.plusMinutes(waitMinutes);
|
}
|
if (plans.isEmpty()) throw new IllegalArgumentException("所有工序已完成排产,不可重复提交");
|
if (result.getTasks().size() + plans.size() > 200) throw new IllegalArgumentException("一次最多生成200个任务,请拆分工单批次排产");
|
result.getTasks().addAll(plans); busy = trialBusy;
|
orderPlan.setTaskCount(plans.size()); orderPlan.setEndTime(earliest); ends.put(id, earliest);
|
if (order.getRequestDate() != null && earliest.toLocalDate().isAfter(order.getRequestDate().toLocalDate())) {
|
orderPlan.setLate(true); result.getWarnings().add("工单" + order.getCode() + "预计完成时间晚于交期");
|
}
|
} catch (ServiceException | IllegalArgumentException | ArithmeticException error) {
|
orderPlan.setError((orderPlan.getWorkOrderCode() == null ? "工单#" + id : orderPlan.getWorkOrderCode()) + ":" + error.getMessage());
|
}
|
}
|
result.setCanExecute(result.getOrders().stream().noneMatch(order -> order.getError() != null) && !result.getTasks().isEmpty());
|
// 不包含生成时间;同一输入与业务数据应得到稳定的预览指纹。
|
var fingerprint = new LinkedHashMap<String, Object>();
|
fingerprint.put("startTime", request.getStartTime()); fingerprint.put("orders", result.getOrders());
|
fingerprint.put("tasks", result.getTasks()); fingerprint.put("holidays", holidayDates.stream().sorted().toList());
|
fingerprint.put("workOrders", orders.stream().sorted(Comparator.comparing(MesProWorkOrderDO::getId)).toList());
|
fingerprint.put("paramTemplates", templateHashes);
|
result.setPreviewHash(DigestUtil.sha256Hex(JsonUtils.toJsonString(fingerprint)));
|
return result;
|
}
|
|
private List<MesProRouteProcessDO> processes(Long id, Long routeId) {
|
var saved = workOrderProcessService.getWorkOrderProcessListByWorkOrderId(id);
|
List<MesProRouteProcessDO> result = new ArrayList<>();
|
if (!saved.isEmpty()) {
|
for (var row : saved) result.add(new MesProRouteProcessDO().setProcessId(row.getProcessId()).setSort(row.getSort())
|
.setParamTemplateId(row.getParamTemplateId()).setYieldRate(row.getYieldRate()).setOutputItemId(row.getOutputItemId()).setColorCode(row.getColorCode())
|
.setPrepareTime(row.getPrepareTime()).setWaitTime(row.getWaitTime()).setNextProcessId(row.getNextProcessId()).setLinkType(row.getLinkType()));
|
} else result.addAll(routeProcessService.getRouteProcessListByRouteId(routeId));
|
result.sort(Comparator.comparing(MesProRouteProcessDO::getSort, Comparator.nullsLast(Comparator.naturalOrder())));
|
return result;
|
}
|
|
/**
|
* 按工序收率从末道往前倒推各道工序的目标产出数量:末道=订单量,前面各道除以后方工序的累计收率后向上取整;
|
* 收率为空按 1.0 处理。
|
*/
|
private Map<Long, BigDecimal> calcTargetQuantities(List<MesProRouteProcessDO> processes, BigDecimal orderQuantity) {
|
return MesProScheduleQuantityService.targetQuantities(processes, orderQuantity);
|
}
|
|
private void addChildrenFirst(Long id, Map<Long, MesProWorkOrderDO> orders, List<Long> sequence, Set<Long> visiting) {
|
if (sequence.contains(id)) return;
|
if (!visiting.add(id)) throw exception(PRO_TASK_SCHEDULE_INVALID, "工单父子关系存在循环,请先修复");
|
orders.values().stream().filter(order -> Objects.equals(order.getParentId(), id)).sorted(Comparator.comparing(MesProWorkOrderDO::getId))
|
.forEach(order -> addChildrenFirst(order.getId(), orders, sequence, visiting));
|
visiting.remove(id); sequence.add(id);
|
}
|
}
|