|
|
@@ -196,6 +196,16 @@ public class PlanningOperationServiceImpl implements PlanningOperationService {
|
|
|
* @param technologicalProcess 工序工艺信息
|
|
|
* @return 设备工序信息
|
|
|
*/
|
|
|
+ /*
|
|
|
+ 1、设备选择与最新可用时间计算说明:
|
|
|
+ 1)从工艺(TechnologicalProcess)查出所有可以执行该工序的设备集合
|
|
|
+ 2)查询这些设备在ProcessDetails中最近一次的planned_end_time,作为设备当前的占用截止时间
|
|
|
+ 3)根据设备的生产节拍(productionBeat)与加工批量(processingBatch)计算单位产出时间(spt),并结合计划数量(plannedQuantity)来比较各设备的最早完工时间
|
|
|
+ 1) 当多个设备预计完工时间相同,保留这些设备作为候选(processEquipmentsEqu)以便后续基于辅助资源(auxiliaryDataId)进行进一步筛选
|
|
|
+ 2) 若发现某设备预计更早可完成,则切换为该设备并清空候选列表
|
|
|
+ 4)若存在辅助资源冲突,优先选择可满足辅助资源时间窗口的设备
|
|
|
+ 5)最终返回包含所选设备、选型后的 spt 与计算出的 latestTime 的 EquipmentProcess,用作后续排产起点
|
|
|
+ */
|
|
|
private EquipmentProcess getEquipmentProcess(TechnologicalProcess technologicalProcess,Integer plannedQuantity) {
|
|
|
Set<Long> equipmentids = equipmentMapper.getEquipments(technologicalProcess.getId(), technologicalProcess.getProcessId())
|
|
|
.stream().map(Equipment::getId).collect(Collectors.toSet());
|
|
|
@@ -298,13 +308,12 @@ public class PlanningOperationServiceImpl implements PlanningOperationService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 计算排程时间段
|
|
|
+ * 计算排程时间段(基于 SuggestedPlan 的封装方法)
|
|
|
*
|
|
|
- * @param suggestedPlan 建议计划
|
|
|
- * @param deviceId 设备id
|
|
|
- * @param startTime 开始时间
|
|
|
- * @param workTime 工作时间
|
|
|
- * @return 排程时间段
|
|
|
+ * 1、功能:为上层的排产流程提供方便的接口,接收 SuggestedPlan 与设备id 并返回该设备在日历中可占用的时间片段
|
|
|
+ * 1) 校验最小时间阈值(小于1分钟认为异常)
|
|
|
+ * 2) 查询设备从 startTime 开始的工作日历(getDeviceWorkingaCalendar)
|
|
|
+ * 3) 使用底层的 calculateTimePeriods(List<DeviceWorkingCalendar>, LocalDateTime, long) 进行日历对齐并返回结果
|
|
|
*/
|
|
|
private List<String> calculateTimePeriods(SuggestedPlan suggestedPlan, Long deviceId, LocalDateTime startTime, long workTime) {
|
|
|
if (workTime < 60000) {
|
|
|
@@ -334,6 +343,12 @@ public class PlanningOperationServiceImpl implements PlanningOperationService {
|
|
|
* @param materialCode 物料编码
|
|
|
* @return 设备工艺信息
|
|
|
*/
|
|
|
+ /*
|
|
|
+ 1、工序检索与设备映射说明:
|
|
|
+ 1)根据物料编码查询该物料的所有工艺(TechnologicalProcess)
|
|
|
+ 2)对每个工艺调用 getEquipmentProcess(TechnologicalProcess) 选择最合适的设备并构造 EquipmentProcess
|
|
|
+ 3)将所有工序按工序号(processNumber)排序返回,作为该物料的执行工序列表
|
|
|
+ */
|
|
|
private List<EquipmentProcess> getProcessEquipments(String materialCode,Integer plannedQuantity) {
|
|
|
List<EquipmentProcess> equipmentProcesses;
|
|
|
try {
|
|
|
@@ -348,25 +363,45 @@ public class PlanningOperationServiceImpl implements PlanningOperationService {
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public void planingOperation(PlanOperationProgress planOperationProgress) throws Exception {
|
|
|
+ /*
|
|
|
+ 1、入口方法:计划排程的高层流程说明
|
|
|
+ 1)读取所有未排产的建议计划(SuggestedPlan),若为空则直接标记进度并返回
|
|
|
+ 2)根据订单号分组建议计划,统一更新这些建议计划的排产状态为“正在排产”(ARRANGING)
|
|
|
+ 3)筛选父级建议计划(level==0),遍历每个父订单:
|
|
|
+ a. 更新该订单下的子建议计划的计划时间(调用私有 planingOperation(...) 进行具体排程运算)
|
|
|
+ b. 在所有父级订单完成后,将建议计划状态更新为“已排产”(ARRANGED)
|
|
|
+ c. 更新进度到Redis
|
|
|
+ */
|
|
|
+
|
|
|
List<SuggestedPlan> suggestedPlans = getSuggestedPlans();
|
|
|
+ // 1)、查询未排产建议计划
|
|
|
if (CollectionUtil.isEmpty(suggestedPlans)) {
|
|
|
+ // 2)、如果没有需要排产的计划,则更新进度并返回
|
|
|
updatePlanOperationProgress(planOperationProgress);
|
|
|
return;
|
|
|
}
|
|
|
+ // 3)、按订单号分组,便于按订单进行逐单排产(父子料关系在一个订单内部处理)
|
|
|
Map<String, List<SuggestedPlan>> suggestedPlansMap = getSuggestedPlansMap(suggestedPlans);
|
|
|
+ // 4)、先把所有建议计划状态设为'正在排产',防止并发重复计算
|
|
|
updateSchedulingStatus(suggestedPlans, SchedulingStatusEnum.ARRANGING);
|
|
|
+ // 5)、只取父级建议计划(level == 0)作为外层循环对象
|
|
|
List<SuggestedPlan> parentSuggestPlans = suggestedPlans.stream().filter(s -> s.getLevel() == 0).collect(Collectors.toList());
|
|
|
if (CollectionUtil.isEmpty(parentSuggestPlans)) {
|
|
|
throw new GlobalException("没有查询到需要排程的订单!");
|
|
|
}
|
|
|
+ // 6)、获取全局排产开始时间(可由全局配置覆盖),用于初始化排产起点
|
|
|
GlobalSettingsForScheduling globalSettingsForScheduling = globalSettingsForSchedulingService.getOne(new LambdaQueryWrapper<GlobalSettingsForScheduling>());
|
|
|
LocalDateTime groubleStartTime = getGroubleStartTime(globalSettingsForScheduling);
|
|
|
List<LocalDateTime> groubleEndTime = new ArrayList<>();
|
|
|
+ // 7)、将订单信息状态置为正在排产
|
|
|
updateOrderInformations(parentSuggestPlans);
|
|
|
+ // 8)、委托给私有方法执行具体的排产运算(逐订单计算)
|
|
|
if (planingOperation(parentSuggestPlans, suggestedPlansMap, groubleStartTime, groubleEndTime, planOperationProgress)) {
|
|
|
throw new GlobalException("建议计划运算失败");
|
|
|
}
|
|
|
+ // 9)、全部排产成功后更新建议计划状态为'已排产'
|
|
|
updateSchedulingStatus(suggestedPlans, SchedulingStatusEnum.ARRANGED);
|
|
|
+ // 10)、最后更新排程进度信息
|
|
|
updatePlanOperationProgress(planOperationProgress);
|
|
|
}
|
|
|
|
|
|
@@ -413,31 +448,46 @@ public class PlanningOperationServiceImpl implements PlanningOperationService {
|
|
|
* @return true,false
|
|
|
*/
|
|
|
private boolean planingOperation(List<SuggestedPlan> parentSuggestPlans, Map<String, List<SuggestedPlan>> suggestedPlansMap, LocalDateTime groubleStartTime, List<LocalDateTime> groubleEndTime, PlanOperationProgress planOperationProgress) {
|
|
|
+ /*
|
|
|
+ 1、核心排产流程(按父订单逐个排产)
|
|
|
+ 1)遍历每个父级订单(parentSuggestPlans)
|
|
|
+ 2)把该订单下所有子建议计划分类为:自制件(SELF_MADE)与外购/外协件
|
|
|
+ 3)对自制件:按工序逐道排程(调用 getScheduleTime)——得到每个工序的设备时间段、更新设备最新可用时间、保存工序明细以及生产任务视图
|
|
|
+ 4)对非自制件:计算采购/交期等(调用 updateNoSelfProductsMap)
|
|
|
+ 5)确定父订单的最早开始时间与最晚完工时间,并更新订单信息
|
|
|
+ 6)更新进度到Redis,继续下一个父订单
|
|
|
+ */
|
|
|
+
|
|
|
int size = parentSuggestPlans.size();
|
|
|
List<OrderInformation> orderInformations = new ArrayList<>();
|
|
|
for (int i = 0; i < size; i++) {
|
|
|
SuggestedPlan suggestedPlan = parentSuggestPlans.get(i);
|
|
|
List<SuggestedPlan> allSuggestedPlans = new ArrayList<>();
|
|
|
+ // 1)、当前订单的所有建议计划(按订单号分组获得)
|
|
|
List<SuggestedPlan> childrenSuggestedPlans = suggestedPlansMap.get(suggestedPlan.getOrderNumber());
|
|
|
+ // 2)、将子件按自制/非自制区分
|
|
|
List<SuggestedPlan> selfProducts = childrenSuggestedPlans.stream().filter(s -> s.getProductionType().equals(ProductionType.SELF_MADE)).toList();
|
|
|
List<SuggestedPlan> noSelfProducts = childrenSuggestedPlans.stream().filter(s -> !s.getProductionType().equals(ProductionType.SELF_MADE)).toList();
|
|
|
+ // 3)、把自制件按层级分组(用于从末级物料到父级物料的倒序排产)
|
|
|
SortedMap<Integer, List<SuggestedPlan>> selfProductsMap = selfProducts.stream().collect(Collectors.groupingBy(SuggestedPlan::getLevel, TreeMap::new, Collectors.toList())).descendingMap();
|
|
|
SortedMap<Integer, List<SuggestedPlan>> noSelfProductsMap = noSelfProducts.stream().collect(Collectors.groupingBy(SuggestedPlan::getLevel, TreeMap::new, Collectors.toList())).descendingMap();
|
|
|
Map<String, List<SuggestedPlan>> selfAllProductsMap = selfProducts.stream().filter(s -> !s.getMaterialCode().equals(s.getMaterialParentCode())).collect(Collectors.groupingBy(s -> String.join(",", s.getOrderNumber(), s.getMaterialParentCode())));
|
|
|
if (selfProductsMap.isEmpty() && noSelfProductsMap.isEmpty()) {
|
|
|
return true;
|
|
|
}
|
|
|
- //计算自制件
|
|
|
+ // 4、对自制件进行工艺工序排产(包括设备选择、时间段计算、保存工序明细)
|
|
|
getScheduleTime(groubleStartTime, selfAllProductsMap, selfProductsMap, allSuggestedPlans);
|
|
|
- //计算非自制件
|
|
|
+ // 5、对非自制件(外购/外协)进行采购到货期、建议计划时间更新
|
|
|
if (CollectionUtil.isNotEmpty(noSelfProductsMap)) {
|
|
|
updateNoSelfProductsMap(allSuggestedPlans, noSelfProductsMap);
|
|
|
}
|
|
|
+ // 6、计算该父订单的开始与结束时间(聚合所有子件/工序)
|
|
|
LocalDateTime parentStartTime = allSuggestedPlans.stream().map(SuggestedPlan::getPlanStartTime).min(LocalDateTime::compareTo).get();
|
|
|
LocalDateTime parentEndTime = allSuggestedPlans.stream().map(SuggestedPlan::getPlanCompletionTime).max(LocalDateTime::compareTo).get();
|
|
|
groubleEndTime.add(parentEndTime);
|
|
|
- //更新需求订单的时间
|
|
|
+ // 7、更新订单信息(计划开始、计划结束、以及延迟标签)
|
|
|
updateOrderInformation(orderInformations, suggestedPlan, parentStartTime, parentEndTime);
|
|
|
+ // 8)、更新排产进度(按父订单进度计算)
|
|
|
updatePlanOperationProgress(planOperationProgress, i + 1, size);
|
|
|
}
|
|
|
if (CollectionUtil.isNotEmpty(orderInformations)) {
|
|
|
@@ -446,6 +496,10 @@ public class PlanningOperationServiceImpl implements PlanningOperationService {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 删除重复的方法定义(已在文件前位置保留唯一实现)
|
|
|
+ */
|
|
|
+
|
|
|
/**
|
|
|
* 计算自制时间
|
|
|
*
|
|
|
@@ -455,41 +509,60 @@ public class PlanningOperationServiceImpl implements PlanningOperationService {
|
|
|
* @param allSuggestedPlans 全部建议计划map
|
|
|
*/
|
|
|
private void getScheduleTime(LocalDateTime groubleStartTime, Map<String, List<SuggestedPlan>> selfAllProductsMap, SortedMap<Integer, List<SuggestedPlan>> selfProductsMap, List<SuggestedPlan> allSuggestedPlans) {
|
|
|
+ /*
|
|
|
+ 1、计算自制件排产流程说明
|
|
|
+ 1)按层级倒序(从末级/子件开始)处理每个自制件SuggestedPlan
|
|
|
+ 2)获取该物料的工艺流程和可用设备列表(getProcessEquipments)
|
|
|
+ 3)按工序顺序(processNumber升序)对设备进行排产:
|
|
|
+ a. 计算该工序的总工作时间(getWorkTime)
|
|
|
+ b. 确定该工序可开始的最晚时间(设备latestTime与上游排产完成时间的较大者)
|
|
|
+ c. 根据设备工作日历计算实际占用的时间段(calculateTimePeriods)
|
|
|
+ d. 将每道工序的明细保存到ProcessDetails,并更新设备最新可用时间
|
|
|
+ 4)每个物料的子工序集合得到后,汇总形成SchedulingTaskView并更新该SuggestedPlan的计划开始/结束时间
|
|
|
+ */
|
|
|
+
|
|
|
List<SuggestedPlan> suggestedPlans = new ArrayList<>();
|
|
|
List<SchedulingTaskView> scheduledTaskViews = new ArrayList<>();
|
|
|
List<ProcessDetails> processDetailss = new ArrayList<>();
|
|
|
selfProductsMap.forEach((k, v) -> v.forEach(sp -> {
|
|
|
+ // 1)、获取该物料的所有工艺工序(包含设备信息)
|
|
|
List<EquipmentProcess> equipmentProcesses = getProcessEquipments(sp.getMaterialCode(),
|
|
|
sp.getPlannedQuantity() == null ? 0 : sp.getPlannedQuantity().intValue());
|
|
|
equipmentProcesses.sort(Comparator.comparing(EquipmentProcess::getProcessNumber));
|
|
|
long workTime;
|
|
|
+ // 2)、scheduleTime 初始化为全局排产起始时间或上游物料的完工时间
|
|
|
LocalDateTime scheduleTime = groubleStartTime;
|
|
|
List<SuggestedPlan> suggestedPlanList = selfAllProductsMap.get(String.join(",", sp.getOrderNumber(), sp.getMaterialCode()));
|
|
|
if (CollectionUtil.isNotEmpty(suggestedPlanList)) {
|
|
|
Optional<LocalDateTime> planCompletionTime = suggestedPlanList.stream().map(SuggestedPlan::getPlanCompletionTime).max(LocalDateTime::compareTo);
|
|
|
if (planCompletionTime.isPresent()) {
|
|
|
+ // 3)、如果有下级物料已经排产,则该物料应在下级完工之后开始排产
|
|
|
scheduleTime = planCompletionTime.get();
|
|
|
}
|
|
|
}
|
|
|
List<ProcessDetails> subProcessDetailss = new ArrayList<>();
|
|
|
- //计算工艺工序
|
|
|
+ // 4)、对该物料的每道工序进行排产计算(顺序执行)
|
|
|
for (EquipmentProcess pe : equipmentProcesses) {
|
|
|
Long equipmentId = pe.getEquipmentId();
|
|
|
+ // 4.1)、计算此工序所需的总工作时间(包含节拍、换线时间与搬运/换批时间)
|
|
|
workTime = getWorkTime(pe, sp.getPlannedQuantity() == null ? 0 : sp.getPlannedQuantity().intValue());
|
|
|
+ // 4.2)、计算工序实际可开始时间(设备最新可用时间与本物料上游完成时间的较大者)
|
|
|
LocalDateTime latestTime = pe.getLatestTime().isBefore(scheduleTime) ? scheduleTime : pe.getLatestTime();
|
|
|
latestTime = getLatestTime(pe, processDetailss, equipmentId, latestTime);
|
|
|
+ // 4.3)、根据设备工作日历计算实际占用的时间段列表(可能跨时间区、跨天)
|
|
|
List<String> timePeriods = calculateTimePeriods(sp, equipmentId, latestTime, workTime);
|
|
|
String endTime = timePeriods.get(timePeriods.size() - 1).split(CommonConstants.WAVY_LINE)[1];
|
|
|
+ // 4.4)、更新下道工序/下一级物料的排产起始时间为本工序的结束时间
|
|
|
scheduleTime = LocalDateTime.parse(endTime, DatePattern.NORM_DATETIME_MINUTE_FORMATTER);
|
|
|
- //插入工序明细,插入设备计划表,更新设备的最新工作时间
|
|
|
+ // 4.5)、保存工序明细并记录设备最新工作时间
|
|
|
Equipment equipment = saveProcessDetails(subProcessDetailss, processDetailss, sp, pe, timePeriods);
|
|
|
saveEquipmentLatestTime(equipment, LocalDateTime.parse(timePeriods.get(timePeriods.size() - 1).split(CommonConstants.WAVY_LINE)[1], DatePattern.NORM_DATETIME_MINUTE_FORMATTER));
|
|
|
}
|
|
|
- //插入子集排产视图
|
|
|
+ // 5)、该物料所有子工序明细明确后,计算物料的计划开始/结束时间并存储视图/建议计划
|
|
|
LocalDateTime planStartTime = subProcessDetailss.stream().map(ProcessDetails::getPlannedStartTime).min(LocalDateTime::compareTo).get();
|
|
|
LocalDateTime planEndTime = subProcessDetailss.stream().map(ProcessDetails::getPlannedEndTime).max(LocalDateTime::compareTo).get();
|
|
|
addSchedulingTaskView(scheduledTaskViews, sp, planStartTime, planEndTime);
|
|
|
- // 更新子集建议计划相关的时间
|
|
|
+ // 6)、更新SuggestedPlan为已排产并写入时间
|
|
|
addSuggestedPlan(suggestedPlans, sp, planStartTime, planEndTime);
|
|
|
allSuggestedPlans.add(sp);
|
|
|
}));
|
|
|
@@ -570,6 +643,12 @@ public class PlanningOperationServiceImpl implements PlanningOperationService {
|
|
|
* @param noSelfProductsMap 非自制件
|
|
|
*/
|
|
|
private void updateNoSelfProductsMap(List<SuggestedPlan> allSuggestedPlans, SortedMap<Integer, List<SuggestedPlan>> noSelfProductsMap) {
|
|
|
+ /*
|
|
|
+ 1、非自制件处理说明(外购/外协)
|
|
|
+ 1)通过已排产的自制件(allSuggestedPlans)映射父物料的时间窗口
|
|
|
+ 2)计算采购提前期(getTime)并倒推建议的采购/到货时间
|
|
|
+ 3)将采购计划(PurchasePlan)及建议计划的时间写回数据库
|
|
|
+ */
|
|
|
Map<String, SuggestedPlan> allSuggestedPlansMap = allSuggestedPlans.stream().collect(Collectors.toMap(SuggestedPlan::getMaterialCode, sp -> sp, (k1, k2) -> k2));
|
|
|
List<SuggestedPlan> suggestedPlans = new ArrayList<>();
|
|
|
List<PurchasePlan> purchasePlanes = new ArrayList<>();
|
|
|
@@ -679,6 +758,12 @@ public class PlanningOperationServiceImpl implements PlanningOperationService {
|
|
|
* @return 设备信息
|
|
|
*/
|
|
|
private Equipment saveProcessDetails(List<ProcessDetails> subProcessDetailss, List<ProcessDetails> processDetailss, SuggestedPlan sp, EquipmentProcess pe, List<String> timePeriods) {
|
|
|
+ /*
|
|
|
+ 1、保存工序明细的说明:
|
|
|
+ 1)将工序与物料、设备、计划时间段等信息组合成ProcessDetails并保存
|
|
|
+ 2)如果存在辅助资源(auxiliaryData),同时记录辅助资源的占用时间段
|
|
|
+ 3)返回设备对象以便上层更新设备的最新可用时间
|
|
|
+ */
|
|
|
Material material = materialService.getById(pe.getMaterialId());
|
|
|
Equipment equipment = equipmentService.getById(pe.getEquipmentId());
|
|
|
AuxiliaryData auxiliaryData = auxiliaryDataService.getById(pe.getAuxiliaryDataId());
|
|
|
@@ -769,6 +854,14 @@ public class PlanningOperationServiceImpl implements PlanningOperationService {
|
|
|
* @return 工作时间
|
|
|
*/
|
|
|
private long getWorkTime(EquipmentProcess equipmentProcess, int orderNum) {
|
|
|
+ /*
|
|
|
+ 1、工作时间计算说明:
|
|
|
+ 1)productionBeat:单位生产节拍(秒)
|
|
|
+ 2)processingBatch:生产批量(台/批),用于把总产量拆分为批次
|
|
|
+ 3)transshipmentBatch/transshipmentTime:换批/转运相关时间
|
|
|
+ 4)replacementTime:换线/换模时间
|
|
|
+ 5)最终返回值为毫秒级的总耗时(用于与设备工作日历对齐)
|
|
|
+ */
|
|
|
Long processingBatch = equipmentProcess.getProcessingBatch();
|
|
|
Long productionBeat = equipmentProcess.getProductionBeat();
|
|
|
Long replacementTime = equipmentProcess.getReplacementTime();
|
|
|
@@ -788,6 +881,13 @@ public class PlanningOperationServiceImpl implements PlanningOperationService {
|
|
|
* @return 工作时段
|
|
|
*/
|
|
|
private List<String> calculateTimePeriods(List<DeviceWorkingCalendar> workTimes, LocalDateTime startTime, long workTime) {
|
|
|
+ /*
|
|
|
+ 1、设备工作日历对齐说明:
|
|
|
+ 1)把连续的工作日和每天的工作时段拼接成可占用的时间片段
|
|
|
+ 2)如果startTime在工作时段外,会向后移动到最近的工作时段开始
|
|
|
+ 3)如果需要的总工作时间超过日历范围,返回空表示排产失败
|
|
|
+ 4)getSchedule会处理同天内跨时段与跨天的切分细节
|
|
|
+ */
|
|
|
Map<LocalDate, DeviceWorkingCalendar> workingPeriodMap = workTimes.stream().collect(Collectors.toMap(DeviceWorkingCalendar::getDate, workingPeriod -> workingPeriod));
|
|
|
List<LocalDate> workDays = workTimes.stream().map(DeviceWorkingCalendar::getDate).collect(Collectors.toList());
|
|
|
List<String> schedule = new ArrayList<>();
|