2 Angajamente cf8a0d0542 ... e5fd04d323

Autor SHA1 Permisiunea de a trimite mesaje. Dacă este dezactivată, utilizatorul nu va putea trimite nici un fel de mesaj Data
  chrislee e5fd04d323 增加MRP算法的注释 1 lună în urmă
  chrislee 4aeb0392b8 增加MRP算法的注释 1 lună în urmă

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

@@ -73,78 +73,104 @@ public class MrpOperationServiceImpl implements MrpOperationService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void mrpOperation(List<OrderInformation> orderInformationList, PlanOperationProgress planOperationProgress) {
+        // 总体说明:
+        // 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++) {
+            // 3、订单处理的子步骤(序号示例)
+            // 1)、初始化每订单的中间集合:建议计划(suggestedPlans)、采购计划(purchasePlanes);
+            // 2)、更新计划进度(进度写入 Redis,用于前端展示);
+            // 3)、计算计划数量(考虑完工率的反算);
+            // 4)、顶层库存优先校验:若库存充足则直接消耗并关闭订单;
+            // 5)、若库存不足则展开 BOM,逐级计算并生成建议计划/采购计划;
             List<SuggestedPlan> suggestedPlans = new ArrayList<>();
             List<PurchasePlan> purchasePlanes = new ArrayList<>();
-            //更新计划进度
+            // 更新计划进度(将进度写入 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) {
+                // 1)、确定父物料编码(用于从 suggestedPlanMap 中读取父级计划量)
                 String materialParentCode = getMaterialParentCode(materialBom, orderMaterial, materialBomMap);
+                // 2)、计算子级计划数量 = 父级计划量 * 单位用量,向上取整;对外购件再做单位换算(保留小数情况)
                 subPlanQuantity = getSubPlanQuantity(planQuantity, orderInformation.getOrderNumber() + materialParentCode, suggestedPlanMap).multiply(materialBom.getUnitUsage()).setScale(0, RoundingMode.UP);
                 if (ProductionType.OUTSIDE_PURCHASE.equals(materialBom.getProductionType())) {
+                    // 若为外购件,按产品/采购单位比率换算为采购单位数量(保留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())) {
                         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())) {
                     if (inventoryCheck(subActualInventory, subSafetyInventory, subPlanQuantity)) {
                         subActualInventory = calculateActualInventory(subActualInventory, subPlanQuantity);
@@ -157,6 +183,11 @@ public class MrpOperationServiceImpl implements MrpOperationService {
                     }
                     continue;
                 }
+                // 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;
@@ -169,30 +200,28 @@ public class MrpOperationServiceImpl implements MrpOperationService {
                 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;
                 subActualInventory = BigDecimal.ZERO;
-                //更新子级物料信息
                 updateMaterialBom(materialBom, subActualInventory);
                 addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, materialBom, subDemandQuantity, subPlanQuantity, materialBom.getLevel(), possessionInventory, materialParentCode);
                 addPurchasePlan(purchasePlanes, orderInformation, materialBom, shortageQuantity);
             }
-            //保存建议计划、齐套分析、采购计划初始值
+            // 7、批量保存:将本订单产生的建议计划与采购计划批量入库
             saveMrpPlans(suggestedPlans, purchasePlanes);
         }
     }
 
     /**
      * 校验工序设备日历信息
-     *
-     * @param orderInformation 订单信息
-     * @param orderMaterial    订单物料
-     * @param materials        物料
+     * 注释:该方法在 BOM 展开前被调用,若校验失败会抛出异常,阻断整个排程流程
      */
     private void checkProcessEquipmentCalendar(OrderInformation orderInformation, Material orderMaterial, List<MaterialBom> materials) {
         Map<String, String> materialsMap = materials.stream().collect(Collectors.toMap(MaterialBom::getMaterialCode, MaterialBom::getMaterialName));
@@ -392,11 +421,7 @@ public class MrpOperationServiceImpl implements MrpOperationService {
 
     /**
      * 获取子级计划数量
-     *
-     * @param planQuantity       计划数量
-     * @param materialParentCode 父级
-     * @param suggestedPlanMap   计划数量
-     * @return 计划数量
+     * 注释:如果父级编码为空返回顶层 planQuantity;否则尝试从 suggestedPlanMap 中读取父级 plannedQuantity
      */
     private BigDecimal getSubPlanQuantity(BigDecimal planQuantity, String materialParentCode, Map<String, SuggestedPlan> suggestedPlanMap) {
         if ("".equals(materialParentCode)) {
@@ -411,9 +436,7 @@ public class MrpOperationServiceImpl implements MrpOperationService {
 
     /**
      * 批量保存MRP计划初始值
-     *
-     * @param suggestedPlans  建议计划
-     * @param purchasePlanes  采购计划
+     * 注释:分别批量保存建议计划与采购计划,避免多次数据库交互
      */
     private void saveMrpPlans(List<SuggestedPlan> suggestedPlans, List<PurchasePlan> purchasePlanes) {
         if (CollectionUtil.isNotEmpty(suggestedPlans)) {
@@ -426,10 +449,7 @@ public class MrpOperationServiceImpl implements MrpOperationService {
 
     /**
      * 更新计划排程进度
-     *
-     * @param planOperationProgress 计划排程进度model
-     * @param i                     订单下标
-     * @param size                  订单数
+     * 注释:将订单处理进度的前 20% 写入 Redis,表示这是排程中的第一阶段进度
      */
     private void updatePlanOperationProgress(PlanOperationProgress planOperationProgress, int i, int size) {
         if (planOperationProgress == null) {
@@ -443,11 +463,7 @@ public class MrpOperationServiceImpl implements MrpOperationService {
 
     /**
      * 增加采购计划
-     *
-     * @param purchasePlans    采购计划信息list
-     * @param orderInformation 订单信息
-     * @param materialBom      物料bom
-     * @param shortageQuantity 缺料数量
+     * 注释:构造 PurchasePlan;若外购件则按采购单位设置 unit,并保持数量按采购单位换算(注释处显示可扩展)
      */
     private void addPurchasePlan(List<PurchasePlan> purchasePlans, OrderInformation orderInformation, MaterialBom materialBom, BigDecimal shortageQuantity) {
         PurchasePlan plan = PurchasePlan.builder()
@@ -471,10 +487,8 @@ public class MrpOperationServiceImpl implements MrpOperationService {
     }
 
     /**
-     * 更新物料信息
-     *
-     * @param materialBom     物料bom
-     * @param actualInventory 实际库存
+     * 更新物料信息(MaterialBom -> Material)
+     * 注释:把 bom 的最新库存同步回物料表
      */
     private void updateMaterialBom(MaterialBom materialBom, BigDecimal actualInventory) {
         Material material = new Material();
@@ -484,9 +498,6 @@ public class MrpOperationServiceImpl implements MrpOperationService {
 
     /**
      * 更新物料信息
-     *
-     * @param actualInventory 实际库存
-     * @param material        物料信息
      */
     private void updateMaterial(BigDecimal actualInventory, Material material) {
         material.setActualInventory(actualInventory);
@@ -494,11 +505,7 @@ public class MrpOperationServiceImpl implements MrpOperationService {
     }
 
     /**
-     * 计算实际库存
-     *
-     * @param actualInventory 实际库存
-     * @param planQuantity    计划数量
-     * @return 实际库存
+     * 计算实际库存(扣减计划量)
      */
     private BigDecimal calculateActualInventory(BigDecimal actualInventory, BigDecimal planQuantity) {
         actualInventory = actualInventory.subtract(planQuantity);
@@ -507,46 +514,22 @@ public class MrpOperationServiceImpl implements MrpOperationService {
 
     /**
      * 实际库存、安全库存、计划数量校验逻辑
-     *
-     * @param actualInventory 实际库存
-     * @param safetyInventory 安全库存
-     * @param planQuantity    计划适量
-     * @return true or false
+     * 注释:当 actual - safety >= plan 返回 true,表示库存可直接支持计划
      */
     private boolean inventoryCheck(BigDecimal actualInventory, BigDecimal safetyInventory, BigDecimal planQuantity) {
         return actualInventory.subtract(safetyInventory).compareTo(planQuantity) >= 0;
     }
 
     /**
-     * 增加建议计划
-     *
-     * @param suggestedPlanMap    建议计划map
-     * @param suggestedPlans      建议计划list
-     * @param orderInformation    订单信息
-     * @param material            物料信息
-     * @param demandQuantity      需求数量
-     * @param planQuantity        计划数量
-     * @param level               bom层级
-     * @param possessionInventory 物料占用量
-     * @param materialParentCode  父级物料编码
+     * 增加建议计划(默认 NOTARRANGED)
      */
     private void addSuggestPlan(Map<String, SuggestedPlan> suggestedPlanMap, List<SuggestedPlan> suggestedPlans, OrderInformation orderInformation, Material material, BigDecimal demandQuantity, BigDecimal planQuantity, int level, BigDecimal possessionInventory, String materialParentCode) {
         addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, material, demandQuantity, planQuantity, level, possessionInventory, materialParentCode, SchedulingStatusEnum.NOTARRANGED);
     }
 
     /**
-     * 增加建议计划
-     *
-     * @param suggestedPlanMap    建议计划map
-     * @param suggestedPlans      建议计划list
-     * @param orderInformation    订单信息
-     * @param material            物料信息
-     * @param demandQuantity      需求数量
-     * @param planQuantity        计划数量
-     * @param level               bom层级
-     * @param possessionInventory 物料占用量
-     * @param materialParentCode  父级物料编码
-     * @SchedulingStatusEnum 计划状态
+     * 增加建议计划(带状态重载)
+     * 注释:构造 SuggestedPlan,并放入 map(key = orderNumber + materialCode)用于父子传递
      */
     private void addSuggestPlan(Map<String, SuggestedPlan> suggestedPlanMap, List<SuggestedPlan> suggestedPlans, OrderInformation orderInformation, Material material, BigDecimal demandQuantity, BigDecimal planQuantity, int level, BigDecimal possessionInventory, String materialParentCode, SchedulingStatusEnum schedulingStatusEnum) {
         SuggestedPlan suggestedPlan = SuggestedPlan.builder().
@@ -567,17 +550,14 @@ public class MrpOperationServiceImpl implements MrpOperationService {
                 possessionInventory(possessionInventory).
                 creator(ISecurity.userMust().getUsername()).
                 build();
+        // 使用 orderNumber + materialCode 作为 key,方便父级传递计划量给子级
         suggestedPlanMap.put(orderInformation.getOrderNumber() + material.getMaterialCode(), suggestedPlan);
         suggestedPlans.add(suggestedPlan);
     }
 
     /**
      * 获取父级物料编码
-     *
-     * @param materialBom    物料bom
-     * @param orderMaterial  父级物料
-     * @param materialBomMap 物料bomMap
-     * @return 父级物料编码
+     * 注释:用于将子项关联到父项的 materialCode(顶层父使用订单物料的 code)
      */
     private String getMaterialParentCode(MaterialBom materialBom, Material orderMaterial, Map<Long, MaterialBom> materialBomMap) {
         Long parentId = materialBom.getParentId();