Răsfoiți Sursa

增加MRP算法的注释

chrislee 1 lună în urmă
părinte
comite
a2b1909380

+ 87 - 46
open-source-aps-boot/open-aps-system/src/main/java/com/medusa/aps/business/modules/demand/service/impl/MrpOperationServiceImpl.java

@@ -41,8 +41,7 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
- * @author: xh.yu
- * @createTime: 2023/09/28 09:17
+ * MRP
  */
 @Service("mRPOperationService")
 @Log
@@ -73,16 +72,36 @@ public class MrpOperationServiceImpl implements MrpOperationService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void mrpOperation(List<OrderInformation> orderInformationList, PlanOperationProgress planOperationProgress) {
-        // 总体说明:
-        // 1、方法概述
-        // 1)、本方法为事务方法,针对传入的订单列表逐个执行 MRP 排程;
-        // 2)、主流程(高层次):预处理 -> 订单循环(库存优先 -> BOM 展开 -> 逐层需求计算 -> 建议/采购计划生成) -> 批量保存
+        /*
+         1、方法概览
+            1) 输入:orderInformationList(要排程的订单列表),planOperationProgress(进度回写对象)
+            2) 输出:在数据库中生成或更新 SuggestedPlan(建议计划)、PurchasePlan(采购计划),并更新 OrderInformation 的状态与物料库存;同时将排程进度写入 Redis
+            3) 错误模式:校验失败或配置信息缺失时抛出 GlobalException,事务回滚,阻断后续订单处理
+
+         2、主要阶段(高层次)
+            1)预处理:清理订单延迟标签、将订单置为正在排程(ARRANGING)
+            2)订单循环(逐订单处理):
+                1)、初始化每订单中间集合(suggestedPlans、purchasePlanes)并上报进度
+                2)、计算计划数量(考虑完工率)
+                3)、顶层库存优先判断:若库存充足则直接占用并关闭订单(不展开 BOM)
+                4)、库存不足时展开 BOM:
+                    1)、校验工艺/设备/日历/辅助资料(checkProcessEquipmentCalendar)——若校验失败抛错并回滚
+                    2)、按 BOM 层级逐级计算子件需求量,区分自制/外购分支,分别处理库存占用、建议计划生成与采购计划生成
+                5)、批量保存本订单产生的建议计划与采购计划
+
+         3、关键注意点(实现细节)
+            1) 完工率的反算逻辑:顶层向上反算时对自制件取整、对外购件保留两位小数
+            2) 外购件按产品/采购单位比例换算采购数量
+            3) 部分库存占用时需要同时生成建议计划(占用量)与采购计划(补足量)
+        */
+
+        // 1)、输入校验:若传入订单列表为空直接返回
         if (CollectionUtils.isEmpty(orderInformationList)) {
             return;
         }
-        // 2、预处理(对所有订单进行统一处理)
-        // 1)、清除计划延迟标签(LabelStatus.PLAN_LATE);
-        // 2)、将订单调度状态置为 ARRANGING(表示正在排程);
+
+        // 2、预处理(1)、清除计划延迟标签;2)、设置订单为 ARRANGING)
+        // 2.1) 遍历所有订单:清除 PLAN_LATE 标签并标记为正在排程
         orderInformationList.forEach(orderInformation -> {
             // 排程清除计划延迟标签
             orderInformation.removeLabelsValue(LabelStatus.PLAN_LATE);
@@ -90,72 +109,89 @@ public class MrpOperationServiceImpl implements MrpOperationService {
             orderInformation.setSchedulingStatus(SchedulingStatusEnum.ARRANGING);
         });
         orderInformationService.updateBatchById(orderInformationList);
+
         int size = orderInformationList.size();
-        // 3、按订单循环处理(每个订单在事务中顺序处理)
+
+        // 3、按订单循环处理(逐订单执行以下子步骤)
         for (int i = 0; i < size; i++) {
-            // 3、订单处理的子步骤(序号示例)
-            // 1)、初始化每订单的中间集合:建议计划(suggestedPlans)、采购计划(purchasePlanes);
-            // 2)、更新计划进度(进度写入 Redis,用于前端展示);
-            // 3)、计算计划数量(考虑完工率的反算);
-            // 4)、顶层库存优先校验:若库存充足则直接消耗并关闭订单;
-            // 5)、若库存不足则展开 BOM,逐级计算并生成建议计划/采购计划;
+            /*
+             3.1、每订单处理概览:
+                1) 初始化 suggestedPlans/purchasePlanes 集合
+                2) 更新计划进度(写入 Redis)
+                3) 计算计划数量(考虑完工率)
+                4) 顶层库存优先处理(库存充足则占用并关闭订单)
+                5) 若库存不足则:展开 BOM,逐层计算子件需求并生成建议计划/采购计划
+                6) 将本订单产生的建议计划与采购计划批量保存
+            */
+
+            // 3.1.1)、初始化每订单的中间集合
             List<SuggestedPlan> suggestedPlans = new ArrayList<>();
             List<PurchasePlan> purchasePlanes = new ArrayList<>();
-            // 更新计划进度(将进度写入 Redis,供前端或监控读取)
+
+            // 3.1.2)、更新计划进度(将进度写入 Redis)
             updatePlanOperationProgress(planOperationProgress, i + 1, size);
+
             OrderInformation orderInformation = orderInformationList.get(i);
-            // 3)、计算计划数量:以订单数量为基准,若顶层物料存在完工率则进行反算
+
+            // 3.1.3)、计算计划数量:处理完工率
             BigDecimal planQuantity = orderInformation.getOrderQuantity() == null ? BigDecimal.ZERO :
                     orderInformation.getOrderQuantity();
             Material orderMaterial = materialService.getOne(new LambdaQueryWrapper<Material>().eq(Material::getMaterialCode, orderInformation.getItemCode()));
-            // 3)、获取顶层物料的实际库存与安全库存
+
+            // 3.1.4)、获取顶层物料库存信息
             BigDecimal actualInventory = orderMaterial.getActualInventory();
             BigDecimal safetyInventory = orderMaterial.getSafetyInventory();
+
+            // 3.1.5)、若顶层物料存在完工率,需要按完工率反算投入量(向上取整)
             if (orderMaterial.getFinishedProductRate() != null && orderMaterial.getFinishedProductRate().compareTo(BigDecimal.ZERO) > 0) {
-                // 若顶层物料存在完工率,需要将订单交付量转换为投入量(按完工率反算,向上取整)
                 planQuantity = planQuantity.multiply(new BigDecimal(100)).divide(orderMaterial.getFinishedProductRate(), 0, RoundingMode.UP);
             }
-            // 4、顶层库存优先:若 actual - safety >= planQuantity 则直接占用库存并关闭订单(不展开 BOM)
+
+            // 3.2)、顶层库存优先:若实际库存减安全库存 >= 计划数量,则直接占用库存并关闭订单(不展开 BOM)
             if (inventoryCheck(actualInventory, safetyInventory, planQuantity)) {
+                // 3.2.1) 占用库存并更新
                 actualInventory = calculateActualInventory(actualInventory, planQuantity);
-                // 更新物料库存并持久化
                 updateMaterial(actualInventory, orderMaterial);
-                // 标记订单为已关闭(无需生成下层计划)
+                // 3.2.2) 标记订单为已关闭(无需生成下层计划)并保存
                 orderInformation.setSchedulingStatus(SchedulingStatusEnum.CLOSE);
                 orderInformation.setPlannedQuantity(planQuantity);
                 orderInformationService.updateById(orderInformation);
-                continue;
+                continue; // 跳过 BOM 展开
             }
-            // 5、库存不足时初始化建议计划与采购计划的生成
+
+            // 3.3)、库存不足时初始化建议计划与采购计划的生成
             Map<String, SuggestedPlan> suggestedPlanMap = new HashMap<>();
-            // 1)、添加顶层建议计划(作为父级,用于向下传递需求量)
+            // 3.3.1)、添加顶层建议计划(作为父级,用于向下传递需求量)
             addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, orderMaterial,
                     orderInformation.getOrderQuantity() == null ? BigDecimal.ZERO : orderInformation.getOrderQuantity(),
                     planQuantity, 0, BigDecimal.ZERO, orderMaterial.getMaterialCode());
             orderInformation.setPlannedQuantity(planQuantity);
             orderInformationService.updateById(orderInformation);
-            // 2、BOM 层级展开与校验:
-            // 1)、获取子件清单并按层级排序;
-            // 2)、展开前必须校验工艺/设备/日历/辅助资料等配置信息,校验失败异常中断排程
+
+            // 3.4)、BOM 层级展开与校验:
+            // 3.4.1) 获取子件清单并按层级排序;3.4.2) 校验工艺/设备/日历/辅助资料等配置信息,校验失败抛异常中断
             List<MaterialBom> materials = materialMapper.getMaterialByMaterialCode(orderMaterial.getId());
             checkProcessEquipmentCalendar(orderInformation, orderMaterial, materials);
             materials = materials.stream().sorted(Comparator.comparing(MaterialBom::getLevel)).collect(Collectors.toList());
             Map<Long, MaterialBom> materialBomMap = materials.stream().collect(Collectors.toMap(MaterialBom::getBomItemId, Function.identity()));
+
             BigDecimal subDemandQuantity;
             BigDecimal subPlanQuantity;
-            // 6、逐层处理 BOM:对每个子项计算需求、校验库存并决定是自制/外购的处理分支
+
+            // 3.4.3)、逐层处理 BOM:对每个子项计算需求、校验库存并决定自制/外购处理分支
             for (MaterialBom materialBom : materials) {
-                // 1)、确定父物料编码(用于从 suggestedPlanMap 中读取父级计划量)
+                // 3.4.3.1)、确定父物料编码(用于从 suggestedPlanMap 中读取父级计划量)
                 String materialParentCode = getMaterialParentCode(materialBom, orderMaterial, materialBomMap);
-                // 2)、计算子级计划数量 = 父级计划量 * 单位用量,向上取整;对外购件再做单位换算(保留小数情况
+                // 3.4.3.2)、计算子级计划数量 = 父级计划量 * 单位用量,向上取整;对外购件再做单位换算(保留小数)
                 subPlanQuantity = getSubPlanQuantity(planQuantity, orderInformation.getOrderNumber() + materialParentCode, suggestedPlanMap).multiply(materialBom.getUnitUsage()).setScale(0, RoundingMode.UP);
                 if (ProductionType.OUTSIDE_PURCHASE.equals(materialBom.getProductionType())) {
-                    // 若为外购件,按产品/采购单位比率换算为采购单位数量(保留2位小数)
+                    // 外购件,按产品/采购单位比率换算为采购单位数量(保留2位小数)
                     String[] productPurchaseUnit = materialBom.getProductPurchaseUnitRatio().split(":");
                     subPlanQuantity = subPlanQuantity.multiply(new BigDecimal(productPurchaseUnit[1])).divide(new BigDecimal(productPurchaseUnit[0]), 2, RoundingMode.UP);
                 }
                 subDemandQuantity = subPlanQuantity;
-                // 3)、若子件存在完工率,按完工率进行反算(自制件向上取整,外购保留2位小数)
+
+                // 3.4.3.3)、若子件存在完工率,按完工率进行反算(自制件向上取整,外购保留2位小数)
                 if (materialBom.getFinishedProductRate() != null && materialBom.getFinishedProductRate().compareTo(BigDecimal.ZERO) > 0) {
                     if (ProductionType.OUTSIDE_PURCHASE.equals(materialBom.getProductionType())) {
                         subPlanQuantity = subPlanQuantity.multiply(new BigDecimal(100)).divide(materialBom.getFinishedProductRate(), 2, RoundingMode.UP);
@@ -163,49 +199,53 @@ public class MrpOperationServiceImpl implements MrpOperationService {
                         subPlanQuantity = subPlanQuantity.multiply(new BigDecimal(100)).divide(materialBom.getFinishedProductRate(), 0, RoundingMode.UP);
                     }
                 }
+
                 BigDecimal subActualInventory = materialBom.getActualInventory();
                 BigDecimal subSafetyInventory = materialBom.getSafetyInventory();
-                // 4)、读取父级建议计划以判断是否需要展开或采购
+
+                // 3.4.3.4)、读取父级建议计划以判断是否需要展开或采购
                 SuggestedPlan suggestedPlan = suggestedPlanMap.get(orderInformation.getOrderNumber() + materialParentCode);
-                // 5)、自制件(SELF_MADE)处理:
-                // 1)、若库存充足:占用库存并生成 CLOSE 建议计划;
-                // 2)、若库存不足且父级存在建议计划:生成 NOTARRANGED 建议计划;
-                // 3)、若父级不存在建议计划:不展开
+
+                // 3.4.4)、自制件(SELF_MADE)处理逻辑:
                 if (ProductionType.SELF_MADE.equals(materialBom.getProductionType())) {
                     if (inventoryCheck(subActualInventory, subSafetyInventory, subPlanQuantity)) {
+                        // 库存充足:占用并生成 CLOSE 建议计划
                         subActualInventory = calculateActualInventory(subActualInventory, subPlanQuantity);
                         updateMaterialBom(materialBom, subActualInventory);
                         addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, materialBom, subDemandQuantity, subPlanQuantity, materialBom.getLevel(), subPlanQuantity, materialParentCode, SchedulingStatusEnum.CLOSE);
                         continue;
                     }
                     if (suggestedPlan != null) {
+                        // 父级存在建议计划但库存不足:生成 NOTARRANGED 建议计划
                         addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, materialBom, subDemandQuantity, subPlanQuantity, materialBom.getLevel(), BigDecimal.ZERO, materialParentCode);
                     }
                     continue;
                 }
-                // 6)、外购件(OUTSIDE_PURCHASE)处理:
-                // 1)、若父级无建议计划或父级已 CLOSE:生成 CLOSE 建议计划并跳过;
-                // 2)、若库存充足:占用库存并更新;
-                // 3)、若库存部分满足:占用现有库存并生成采购计划补足;
-                // 4)、若库存为 0:全部生成采购计划
+
+                // 3.4.5)、外购件(OUTSIDE_PURCHASE)处理逻辑:
                 if (suggestedPlan == null || suggestedPlan.getSchedulingStatus().equals(SchedulingStatusEnum.CLOSE)) {
+                    // 父级无建议计划或已 CLOSE:直接生成 CLOSE 建议计划并跳过
                     addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, materialBom, subDemandQuantity, subPlanQuantity, materialBom.getLevel(), BigDecimal.ZERO, materialParentCode, SchedulingStatusEnum.CLOSE);
                     continue;
                 }
                 if (inventoryCheck(subActualInventory, subSafetyInventory, subPlanQuantity)) {
+                    // 库存充足:占用库存并更新
                     subActualInventory = calculateActualInventory(subActualInventory, subPlanQuantity);
                     updateMaterialBom(materialBom, subActualInventory);
                     continue;
                 }
+
+                // 库存部分满足或不足:计算占用与缺口并生成采购计划
                 BigDecimal possessionInventory = subPlanQuantity;
                 BigDecimal shortageQuantity = BigDecimal.ZERO;
                 if (subActualInventory.compareTo(subPlanQuantity) >= 0) {
-                    // 有一部分库存可用于占用
+                    // 部分库存可用于占用
                     subActualInventory = calculateActualInventory(subActualInventory, subPlanQuantity);
                     updateMaterialBom(materialBom, subActualInventory);
                     addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, materialBom, subDemandQuantity, subPlanQuantity, materialBom.getLevel(), possessionInventory, materialParentCode);
                     continue;
                 }
+
                 // 计算不足量并生成采购计划
                 shortageQuantity = subPlanQuantity.subtract(subActualInventory);
                 possessionInventory = subActualInventory;
@@ -214,7 +254,8 @@ public class MrpOperationServiceImpl implements MrpOperationService {
                 addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, materialBom, subDemandQuantity, subPlanQuantity, materialBom.getLevel(), possessionInventory, materialParentCode);
                 addPurchasePlan(purchasePlanes, orderInformation, materialBom, shortageQuantity);
             }
-            // 7、批量保存:将本订单产生的建议计划与采购计划批量入库
+
+            // 3.5)、批量保存:将本订单产生的建议计划与采购计划批量入库
             saveMrpPlans(suggestedPlans, purchasePlanes);
         }
     }

+ 113 - 13
open-source-aps-boot/open-aps-system/src/main/java/com/medusa/aps/business/modules/demand/service/impl/PlanningOperationServiceImpl.java

@@ -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<>();