Explorar o código

增加MRP算法的注释

chrislee hai 1 mes
pai
achega
e5fd04d323

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

@@ -74,118 +74,129 @@ public class MrpOperationServiceImpl implements MrpOperationService {
     @Transactional(rollbackFor = Exception.class)
     public void mrpOperation(List<OrderInformation> orderInformationList, PlanOperationProgress planOperationProgress) {
         // 总体说明:
-        // 1) 方法为事务方法,针对每个订单进行逐个处理;
-        // 2) 主流程:预处理 -> 优先消耗库存(顶层物料)-> BOM 展开 -> 逐层计算需求 -> 库存优先消耗 -> 生成建议计划和采购计划 -> 批量保存
+        // 1、方法概述
+        // 1)、本方法为事务方法,针对传入的订单列表逐个执行 MRP 排程;
+        // 2)、主流程(高层次):预处理 -> 订单循环(库存优先 -> BOM 展开 -> 逐层需求计算 -> 建议/采购计划生成) -> 批量保存
         if (CollectionUtils.isEmpty(orderInformationList)) {
             return;
         }
-        // 事务开始前的预处理:清除计划延迟标签并把调度状态置为正在排程
+        // 2、预处理(对所有订单进行统一处理)
+        // 1)、清除计划延迟标签(LabelStatus.PLAN_LATE);
+        // 2)、将订单调度状态置为 ARRANGING(表示正在排程);
         orderInformationList.forEach(orderInformation -> {
-            //排程清除计划延迟标签,所选中的终止排程订单没有影响
+            // 排程清除计划延迟标签
             orderInformation.removeLabelsValue(LabelStatus.PLAN_LATE);
+            // 设置调度状态为正在排程
             orderInformation.setSchedulingStatus(SchedulingStatusEnum.ARRANGING);
         });
         orderInformationService.updateBatchById(orderInformationList);
         int size = orderInformationList.size();
+        // 3、按订单循环处理(每个订单在事务中顺序处理)
         for (int i = 0; i < size; i++) {
-            // 每个订单维护两类中间集合:建议计划(SuggestedPlan)和采购计划(PurchasePlan)
+            // 3、订单处理的子步骤(序号示例)
+            // 1)、初始化每订单的中间集合:建议计划(suggestedPlans)、采购计划(purchasePlanes);
+            // 2)、更新计划进度(进度写入 Redis,用于前端展示);
+            // 3)、计算计划数量(考虑完工率的反算);
+            // 4)、顶层库存优先校验:若库存充足则直接消耗并关闭订单;
+            // 5)、若库存不足则展开 BOM,逐级计算并生成建议计划/采购计划;
             List<SuggestedPlan> suggestedPlans = new ArrayList<>();
             List<PurchasePlan> purchasePlanes = new ArrayList<>();
-            //更新计划进度(将进度写入 Redis,供前端或监控读取)
+            // 更新计划进度(将进度写入 Redis,供前端或监控读取)
             updatePlanOperationProgress(planOperationProgress, i + 1, size);
             OrderInformation orderInformation = orderInformationList.get(i);
-            // 计划数量:以订单数量为基准,若存在完工率需要反算
+            // 3)、计算计划数量:以订单数量为基准,若顶层物料存在完工率则进行反算
             BigDecimal planQuantity = orderInformation.getOrderQuantity() == null ? BigDecimal.ZERO :
                     orderInformation.getOrderQuantity();
             Material orderMaterial = materialService.getOne(new LambdaQueryWrapper<Material>().eq(Material::getMaterialCode, orderInformation.getItemCode()));
-            // 实际库存 & 安全库存
+            // 3)、获取顶层物料的实际库存与安全库存
             BigDecimal actualInventory = orderMaterial.getActualInventory();
             BigDecimal safetyInventory = orderMaterial.getSafetyInventory();
             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)
             if (inventoryCheck(actualInventory, safetyInventory, planQuantity)) {
                 actualInventory = calculateActualInventory(actualInventory, planQuantity);
-                //更新库存
+                // 更新物料库存并持久化
                 updateMaterial(actualInventory, orderMaterial);
-                // 订单关闭,不需要生成建议计划与采购计划
+                // 标记订单为已关闭(无需生成下层计划)
                 orderInformation.setSchedulingStatus(SchedulingStatusEnum.CLOSE);
                 orderInformation.setPlannedQuantity(planQuantity);
                 orderInformationService.updateById(orderInformation);
                 continue;
             }
-            // 若库存不足,开始生成建议计划与采购计划的初始化
+            // 5、库存不足时初始化建议计划与采购计划的生成
             Map<String, SuggestedPlan> suggestedPlanMap = new HashMap<>();
-            // 加顶层建议计划(作为父级,用于向下传递需求)
+            // 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);
-            // BOM 层级展开:获取子件清单并按层级排序
+            // 2、BOM 层级展开与校验:
+            // 1)、获取子件清单并按层级排序;
+            // 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;
-            // 对 BOM 中每个子项逐个计算需求、校验库存并生成相应计划
+            // 6、逐层处理 BOM:对每个子项计算需求、校验库存并决定是自制/外购的处理分支
             for (MaterialBom materialBom : materials) {
-                // 获取父级编码,用于从 suggestedPlanMap 中查找父级计划量
+                // 1)、确定父物料编码(用于从 suggestedPlanMap 中读取父级计划量)
                 String materialParentCode = getMaterialParentCode(materialBom, orderMaterial, materialBomMap);
-                // 子级计划数量 = 父级计划(或父级建议计划中的 plannedQuantity) * 单位用量,向上取整
+                // 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位小数)
                 if (materialBom.getFinishedProductRate() != null && materialBom.getFinishedProductRate().compareTo(BigDecimal.ZERO) > 0) {
                     if (ProductionType.OUTSIDE_PURCHASE.equals(materialBom.getProductionType())) {
-                        // 外购按保留2位小数进行换算
                         subPlanQuantity = subPlanQuantity.multiply(new BigDecimal(100)).divide(materialBom.getFinishedProductRate(), 2, RoundingMode.UP);
-                    }else {
-                        // 自制按整数向上取整
+                    } else {
                         subPlanQuantity = subPlanQuantity.multiply(new BigDecimal(100)).divide(materialBom.getFinishedProductRate(), 0, RoundingMode.UP);
                     }
                 }
                 BigDecimal subActualInventory = materialBom.getActualInventory();
                 BigDecimal subSafetyInventory = materialBom.getSafetyInventory();
-                // 父级建议计划(用于判断是否需要展开或采购)
+                // 4)、读取父级建议计划以判断是否需要展开或采购
                 SuggestedPlan suggestedPlan = suggestedPlanMap.get(orderInformation.getOrderNumber() + materialParentCode);
-                // 自制件处理逻辑
+                // 5)、自制件(SELF_MADE)处理:
+                // 1)、若库存充足:占用库存并生成 CLOSE 建议计划;
+                // 2)、若库存不足且父级存在建议计划:生成 NOTARRANGED 建议计划;
+                // 3)、若父级不存在建议计划:不展开
                 if (ProductionType.SELF_MADE.equals(materialBom.getProductionType())) {
-                    // 库存充足:直接占用并生成 CLOSE 建议计划
                     if (inventoryCheck(subActualInventory, subSafetyInventory, subPlanQuantity)) {
                         subActualInventory = calculateActualInventory(subActualInventory, subPlanQuantity);
                         updateMaterialBom(materialBom, subActualInventory);
                         addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, materialBom, subDemandQuantity, subPlanQuantity, materialBom.getLevel(), subPlanQuantity, materialParentCode, SchedulingStatusEnum.CLOSE);
                         continue;
                     }
-                    // 库存不足但父级存在建议计划:生成 NOTARRANGED 建议计划,等待后续排程
                     if (suggestedPlan != null) {
                         addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, materialBom, subDemandQuantity, subPlanQuantity, materialBom.getLevel(), BigDecimal.ZERO, materialParentCode);
                     }
-                    // 若父级无建议计划,则不展开(上层没有计划传递)
                     continue;
                 }
-                // 外购件处理逻辑:如果父级无建议计划或父级已 CLOSE,则当前不需要采购或展开
+                // 6)、外购件(OUTSIDE_PURCHASE)处理:
+                // 1)、若父级无建议计划或父级已 CLOSE:生成 CLOSE 建议计划并跳过;
+                // 2)、若库存充足:占用库存并更新;
+                // 3)、若库存部分满足:占用现有库存并生成采购计划补足;
+                // 4)、若库存为 0:全部生成采购计划
                 if (suggestedPlan == null || suggestedPlan.getSchedulingStatus().equals(SchedulingStatusEnum.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) {
@@ -195,18 +206,15 @@ public class MrpOperationServiceImpl implements MrpOperationService {
                     addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, materialBom, subDemandQuantity, subPlanQuantity, materialBom.getLevel(), possessionInventory, materialParentCode);
                     continue;
                 }
-                // 剩余不足量计算
+                // 计算不足量并生成采购计划
                 shortageQuantity = subPlanQuantity.subtract(subActualInventory);
                 possessionInventory = subActualInventory;
                 subActualInventory = BigDecimal.ZERO;
-                // 更新子级物料实际库存为 0
                 updateMaterialBom(materialBom, subActualInventory);
-                // 记录占用量(已有库存)
                 addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, materialBom, subDemandQuantity, subPlanQuantity, materialBom.getLevel(), possessionInventory, materialParentCode);
-                // 生成采购计划(不足量)
                 addPurchasePlan(purchasePlanes, orderInformation, materialBom, shortageQuantity);
             }
-            // 保存建议计划和采购计划(批量入库)
+            // 7、批量保存:将本订单产生的建议计划与采购计划批量入库
             saveMrpPlans(suggestedPlans, purchasePlanes);
         }
     }