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