From bc31cda78817c62aa552be40f39eccaa03262923 Mon Sep 17 00:00:00 2001
From: xiaoyi <908147524@qq.com>
Date: 星期六, 07 十一月 2026 01:21:22 +0800
Subject: [PATCH] feat: 补齐排产约束与缺料校验,新增风险监测,优化隐患通知与招投标联动
---
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/schedule/MesProBatchScheduleService.java | 66 ++++++++++++++++++++++++++++++---
1 files changed, 60 insertions(+), 6 deletions(-)
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/schedule/MesProBatchScheduleService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/schedule/MesProBatchScheduleService.java
index 936c385..1faa949 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/schedule/MesProBatchScheduleService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/schedule/MesProBatchScheduleService.java
@@ -14,6 +14,13 @@
import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.dv.machinery.MesDvMachineryMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderBomMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.warehouse.MesWmWarehouseMapper;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderBomDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.enums.pro.*;
import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
@@ -22,6 +29,7 @@
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
import cn.iocoder.yudao.module.mes.service.cal.holiday.MesCalHolidayService;
+import cn.iocoder.yudao.module.mes.util.MesOutputItemUtils;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -41,6 +49,9 @@
public class MesProBatchScheduleService {
@Resource private MesProTaskMapper taskMapper;
@Resource private MesProWorkOrderMapper workOrderMapper;
+ @Resource private MesProWorkOrderBomMapper workOrderBomMapper;
+ @Resource private MesWmMaterialStockMapper materialStockMapper;
+ @Resource private MesWmWarehouseMapper warehouseMapper;
@Resource private MesDvMachineryMapper machineryMapper;
@Resource private MesMdWorkstationService workstationService;
@Resource private MesProWorkOrderProcessService workOrderProcessService;
@@ -127,6 +138,34 @@
result.setPreviewTime(now());
var orders = workOrderMapper.selectByIds(request.getWorkOrderIds());
var orderMap = orders.stream().collect(Collectors.toMap(MesProWorkOrderDO::getId, order -> order));
+ var bomByOrder = new HashMap<Long, List<MesProWorkOrderBomDO>>();
+ for (var order : orders) bomByOrder.put(order.getId(), workOrderBomMapper.selectListByWorkOrderId(order.getId()).stream()
+ .sorted(Comparator.comparing(MesProWorkOrderBomDO::getId)).toList());
+ var materialIds = bomByOrder.values().stream().flatMap(Collection::stream).map(MesProWorkOrderBomDO::getItemId)
+ .filter(Objects::nonNull).collect(Collectors.toSet());
+ var warehouses = warehouseMapper.selectList().stream().collect(Collectors.toMap(MesWmWarehouseDO::getId, w -> w));
+ var stocks = materialIds.isEmpty() ? List.<MesWmMaterialStockDO>of() : materialStockMapper.selectList(
+ new LambdaQueryWrapperX<MesWmMaterialStockDO>().in(MesWmMaterialStockDO::getItemId, materialIds));
+ Map<Long, BigDecimal> availableStock = new HashMap<>();
+ for (var stock : stocks) {
+ var warehouse = warehouses.get(stock.getWarehouseId());
+ if (warehouse == null || Boolean.TRUE.equals(warehouse.getFrozen()) || Boolean.TRUE.equals(stock.getFrozen())
+ || MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE.equals(warehouse.getCode())) continue;
+ var available = Optional.ofNullable(stock.getQuantity()).orElse(BigDecimal.ZERO)
+ .subtract(Optional.ofNullable(stock.getFrozenQuantity()).orElse(BigDecimal.ZERO))
+ .subtract(Optional.ofNullable(stock.getReservedQuantity()).orElse(BigDecimal.ZERO)).max(BigDecimal.ZERO);
+ availableStock.merge(stock.getItemId(), available, BigDecimal::add);
+ }
+ var materialInfo = materialIds.isEmpty() ? Map.<Long, cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO>of()
+ : itemService.getItemMap(materialIds);
+ for (Long itemId : materialIds) {
+ var item = materialInfo.get(itemId);
+ if (item != null && Boolean.TRUE.equals(item.getSafeStockFlag()) && item.getMinStock() != null) {
+ availableStock.compute(itemId, (key, value) -> Optional.ofNullable(value).orElse(BigDecimal.ZERO)
+ .subtract(item.getMinStock().max(BigDecimal.ZERO)).max(BigDecimal.ZERO));
+ }
+ }
+ var stockSnapshot = new TreeMap<>(availableStock);
var stations = workstationService.getWorkstationListByStatus(0).stream()
.sorted(Comparator.comparing(MesMdWorkstationDO::getId)).toList();
var machineMap = machineryMapper.selectList().stream().collect(Collectors.toMap(MesDvMachineryDO::getId, machine -> machine));
@@ -153,8 +192,7 @@
stationMachines.getOrDefault(task.getWorkstationId(), Set.of()), task.getStartTime(), task.getEndTime()));
}
}
- var sorted = orders.stream().sorted(Comparator.comparing(MesProWorkOrderDO::getRequestDate,
- Comparator.nullsLast(Comparator.naturalOrder())).thenComparing(MesProWorkOrderDO::getId)).toList();
+ var sorted = orders.stream().sorted(MesSchedulePriority.comparator()).toList();
List<Long> orderSequence = new ArrayList<>();
Set<Long> visiting = new HashSet<>();
for (var order : sorted) addChildrenFirst(order.getId(), orderMap, orderSequence, visiting);
@@ -181,6 +219,20 @@
|| (order.getQuantityProduced() != null && order.getQuantityProduced().compareTo(order.getQuantity()) >= 0)) {
throw new IllegalArgumentException("宸ュ崟鏁伴噺鏃犳晥鎴栧凡鐢熶骇瀹屾垚");
}
+ var boms = bomByOrder.getOrDefault(id, List.of());
+ if (boms.isEmpty()) throw new IllegalArgumentException("宸ュ崟娌℃湁 BOM锛屾棤娉曟牳瀵圭墿鏂欓綈濂楋紝璇峰厛缁存姢");
+ BigDecimal unscheduled = order.getQuantity().subtract(Optional.ofNullable(order.getQuantityScheduled()).orElse(BigDecimal.ZERO));
+ if (unscheduled.signum() <= 0) throw new IllegalArgumentException("宸ュ崟宸插畬鎴愭帓浜�");
+ Map<Long, BigDecimal> demand = new HashMap<>();
+ for (var bom : boms) {
+ if (bom.getItemId() == null || bom.getQuantity() == null || bom.getQuantity().signum() <= 0)
+ throw new IllegalArgumentException("宸ュ崟 BOM 鐗╂枡鎴栭渶姹傛暟閲忔棤鏁堬紝璇峰厛缁存姢");
+ var requiredItem = materialInfo.get(bom.getItemId());
+ if (requiredItem == null || !Objects.equals(requiredItem.getStatus(), 0))
+ throw new IllegalArgumentException("宸ュ崟 BOM 瀛樺湪宸插垹闄ゆ垨鍋滅敤鐨勭墿鏂欙紝璇峰厛缁存姢");
+ demand.merge(bom.getItemId(), bom.getQuantity().multiply(unscheduled).divide(order.getQuantity(), 6, RoundingMode.UP), BigDecimal::add);
+ }
+ var trialStock = MesScheduleMaterialCheck.allocate(availableStock, demand);
LocalDateTime earliest = calendar.nextWorkingTime(request.getStartTime());
// 鐖跺伐鍗曞繀椤荤瓑瀛愬伐鍗曪紱鍚屼竴鎵规鎸変緷璧栭『搴忔帓锛屾湭绾冲叆鐨勬湭瀹屾垚瀛愬伐鍗曢樆姝㈡帓浜с��
for (var child : workOrderMapper.selectList(MesProWorkOrderDO::getParentId, id)) {
@@ -252,9 +304,9 @@
}
Long itemId = order.getProductId();
if (process.getOutputItemId() != null && !process.getOutputItemId().isBlank()) {
- String[] output = process.getOutputItemId().split(",");
- if (output.length != 1) throw new IllegalArgumentException("澶氫骇鍑哄伐搴忚浣跨敤鎵嬪伐鎺掍骇纭鍚勪骇鍑烘暟閲�");
- itemId = Long.valueOf(output[0].trim());
+ List<Long> outputIds = MesOutputItemUtils.parseItemIds(process.getOutputItemId());
+ if (outputIds.size() != 1) throw new IllegalArgumentException("澶氫骇鍑哄伐搴忚浣跨敤鎵嬪伐鎺掍骇纭鍚勪骇鍑烘暟閲�");
+ itemId = outputIds.get(0);
}
var item = itemService.validateItemExists(itemId);
if (!Objects.equals(item.getStatus(), 0)) throw new IllegalArgumentException("宸ュ簭浜у嚭浜у搧宸插仠鐢�");
@@ -299,7 +351,7 @@
}
if (plans.isEmpty()) throw new IllegalArgumentException("鎵�鏈夊伐搴忓凡瀹屾垚鎺掍骇锛屼笉鍙噸澶嶆彁浜�");
if (result.getTasks().size() + plans.size() > 200) throw new IllegalArgumentException("涓�娆℃渶澶氱敓鎴�200涓换鍔★紝璇锋媶鍒嗗伐鍗曟壒娆℃帓浜�");
- result.getTasks().addAll(plans); busy = trialBusy;
+ result.getTasks().addAll(plans); busy = trialBusy; availableStock = trialStock;
orderPlan.setTaskCount(plans.size()); orderPlan.setEndTime(earliest); ends.put(id, earliest);
if (order.getRequestDate() != null && earliest.toLocalDate().isAfter(order.getRequestDate().toLocalDate())) {
orderPlan.setLate(true); result.getWarnings().add("宸ュ崟" + order.getCode() + "棰勮瀹屾垚鏃堕棿鏅氫簬浜ゆ湡");
@@ -315,6 +367,8 @@
fingerprint.put("tasks", result.getTasks()); fingerprint.put("holidays", holidayDates.stream().sorted().toList());
fingerprint.put("workOrders", orders.stream().sorted(Comparator.comparing(MesProWorkOrderDO::getId)).toList());
fingerprint.put("paramTemplates", templateHashes);
+ fingerprint.put("materialStock", stockSnapshot);
+ fingerprint.put("workOrderBoms", new TreeMap<>(bomByOrder));
result.setPreviewHash(DigestUtil.sha256Hex(JsonUtils.toJsonString(fingerprint)));
return result;
}
--
Gitblit v1.9.3