From dd3d2882b06cc7c148ed5a28bd012de322ef8fc2 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 03 八月 2026 13:43:10 +0800
Subject: [PATCH] feat(mes): 支持 MPS ID 的 AI 预测功能
---
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/ai/MesProAiServiceImpl.java | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 files changed, 165 insertions(+), 12 deletions(-)
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/ai/MesProAiServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/ai/MesProAiServiceImpl.java
index f76db29..208b2d9 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/ai/MesProAiServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/ai/MesProAiServiceImpl.java
@@ -30,6 +30,7 @@
import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderBomMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper;
+import cn.iocoder.yudao.module.mes.service.pro.mps.MesProMpsService;
import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
@@ -169,6 +170,8 @@
@Resource
private MesProMpsMapper mpsMapper;
@Resource
+ private MesProMpsService mpsService;
+ @Resource
private MesProProcessService processService;
@Resource
private MdmItemApi mdmItemApi;
@@ -182,12 +185,20 @@
@Override
public MesProAiMaterialPredictRespVO predictMaterial(MesProAiMaterialPredictReqVO reqVO) {
try {
- Map<String, Object> data = gatherMaterialData(reqVO.getWorkOrderId());
+ Map<String, Object> data;
+ String logPrefix;
+ if (reqVO.getMpsId() != null) {
+ data = gatherMaterialDataFromMps(reqVO.getMpsId());
+ logPrefix = "mpsId=" + reqVO.getMpsId();
+ } else {
+ data = gatherMaterialData(reqVO.getWorkOrderId());
+ logPrefix = "workOrderId=" + reqVO.getWorkOrderId();
+ }
String aiResponse = aiChatApi.chat(SYSTEM_PROMPT_MATERIAL, JSONUtil.toJsonPrettyStr(data));
- log.info("AI 鐗╂枡棰勬祴: workOrderId={}, response={}", reqVO.getWorkOrderId(), aiResponse);
+ log.info("AI 鐗╂枡棰勬祴: {}, response={}", logPrefix, aiResponse);
return parseMaterialResponse(aiResponse);
} catch (Exception e) {
- log.warn("AI 鐗╂枡棰勬祴澶辫触锛寃orkOrderId={}", reqVO.getWorkOrderId(), e);
+ log.warn("AI 鐗╂枡棰勬祴澶辫触锛寃orkOrderId={}, mpsId={}", reqVO.getWorkOrderId(), reqVO.getMpsId(), e);
MesProAiMaterialPredictRespVO fallback = new MesProAiMaterialPredictRespVO();
fallback.setReasoning("AI 鍒嗘瀽鏆傛椂涓嶅彲鐢紝璇风◢鍚庨噸璇�");
return fallback;
@@ -258,6 +269,38 @@
return data;
}
+ private Map<String, Object> gatherMaterialDataFromMps(Long mpsId) {
+ Map<String, Object> data = new LinkedHashMap<>();
+ MesProMpsDO mps = mpsService.getMps(mpsId);
+ if (mps == null) throw exception(PRO_WORK_ORDER_NOT_EXISTS);
+
+ data.put("MPS 缂栧彿", mps.getCode());
+ data.put("鏉ユ簮", "MPS 鏈笅鍙戯紝鍩轰簬浜у搧淇℃伅棰勬祴");
+ data.put("璁″垝鏁伴噺", mps.getQuantity());
+ data.put("宸茬敓浜ф暟閲�", mps.getQuantityProduced() != null ? mps.getQuantityProduced() : BigDecimal.ZERO);
+ fillItemInfo(data, mps.getProductId());
+
+ // 鏌ヨ浜у搧鍦ㄥ簱鐗╂枡搴撳瓨锛堥�氱敤锛岄潪宸ュ崟鐗瑰畾 BOM锛�
+ List<MesWmMaterialStockDO> stocks = materialStockMapper.selectList(
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesWmMaterialStockDO>()
+ .ne(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO));
+ if (CollUtil.isNotEmpty(stocks)) {
+ List<Map<String, Object>> materials = new ArrayList<>();
+ for (MesWmMaterialStockDO stock : stocks) {
+ Map<String, Object> item = new LinkedHashMap<>();
+ item.put("鐗╂枡ID", stock.getItemId());
+ item.put("鐗╂枡鍚嶇О", resolveItemName(stock.getItemId()));
+ item.put("搴撳瓨鍙敤閲�", stock.getQuantity());
+ materials.add(item);
+ }
+ data.put("褰撳墠搴撳瓨鐗╂枡", materials);
+ } else {
+ data.put("鎻愮ず", "鏆傛棤搴撳瓨鏁版嵁锛岃鍙傝�冧骇鍝佺紪鐮佽瘎浼扮墿鏂欓渶姹�");
+ }
+
+ return data;
+ }
+
private MesProAiMaterialPredictRespVO parseMaterialResponse(String aiResponse) {
MesProAiMaterialPredictRespVO respVO = new MesProAiMaterialPredictRespVO();
try {
@@ -293,12 +336,20 @@
@Override
public MesProAiDurationPredictRespVO predictDuration(MesProAiDurationPredictReqVO reqVO) {
try {
- Map<String, Object> data = gatherDurationData(reqVO.getWorkOrderId());
+ Map<String, Object> data;
+ String logPrefix;
+ if (reqVO.getMpsId() != null) {
+ data = gatherDurationDataFromMps(reqVO.getMpsId());
+ logPrefix = "mpsId=" + reqVO.getMpsId();
+ } else {
+ data = gatherDurationData(reqVO.getWorkOrderId());
+ logPrefix = "workOrderId=" + reqVO.getWorkOrderId();
+ }
String aiResponse = aiChatApi.chat(SYSTEM_PROMPT_DURATION, JSONUtil.toJsonPrettyStr(data));
- log.info("AI 鏃堕暱棰勬祴: workOrderId={}, response={}", reqVO.getWorkOrderId(), aiResponse);
+ log.info("AI 鏃堕暱棰勬祴: {}, response={}", logPrefix, aiResponse);
return parseDurationResponse(aiResponse);
} catch (Exception e) {
- log.warn("AI 鏃堕暱棰勬祴澶辫触锛寃orkOrderId={}", reqVO.getWorkOrderId(), e);
+ log.warn("AI 鏃堕暱棰勬祴澶辫触锛寃orkOrderId={}, mpsId={}", reqVO.getWorkOrderId(), reqVO.getMpsId(), e);
MesProAiDurationPredictRespVO fallback = new MesProAiDurationPredictRespVO();
fallback.setReasoning("AI 鍒嗘瀽鏆傛椂涓嶅彲鐢紝璇风◢鍚庨噸璇�");
return fallback;
@@ -389,6 +440,22 @@
return totalEfficiency;
}
+ private Map<String, Object> gatherDurationDataFromMps(Long mpsId) {
+ Map<String, Object> data = new LinkedHashMap<>();
+ MesProMpsDO mps = mpsService.getMps(mpsId);
+ if (mps == null) throw exception(PRO_WORK_ORDER_NOT_EXISTS);
+
+ data.put("MPS 缂栧彿", mps.getCode());
+ data.put("鏉ユ簮", "MPS 鏈笅鍙戯紝鍩轰簬浜у搧淇℃伅浼扮畻");
+ data.put("璁″垝鏁伴噺", mps.getQuantity());
+ data.put("宸茬敓浜ф暟閲�", mps.getQuantityProduced() != null ? mps.getQuantityProduced() : BigDecimal.ZERO);
+ data.put("闇�姹傛棩鏈�", mps.getRequestDate() != null ? mps.getRequestDate().toString() : "鏈缃�");
+ fillItemInfo(data, mps.getProductId());
+ data.put("鎻愮ず", "MPS 鏈叧鑱斿伐鍗曞伐搴忥紝璇峰熀浜庝骇鍝佺紪鐮佸拰缁忛獙浼扮畻鐢熶骇鏃堕暱");
+
+ return data;
+ }
+
private MesProAiDurationPredictRespVO parseDurationResponse(String aiResponse) {
MesProAiDurationPredictRespVO respVO = new MesProAiDurationPredictRespVO();
try {
@@ -426,12 +493,20 @@
@Override
public MesProAiRiskPredictRespVO predictRisk(MesProAiRiskPredictReqVO reqVO) {
try {
- Map<String, Object> data = gatherRiskData(reqVO.getWorkOrderId());
+ Map<String, Object> data;
+ String logPrefix;
+ if (reqVO.getMpsId() != null) {
+ data = gatherRiskDataFromMps(reqVO.getMpsId());
+ logPrefix = "mpsId=" + reqVO.getMpsId();
+ } else {
+ data = gatherRiskData(reqVO.getWorkOrderId());
+ logPrefix = "workOrderId=" + reqVO.getWorkOrderId();
+ }
String aiResponse = aiChatApi.chat(SYSTEM_PROMPT_RISK, JSONUtil.toJsonPrettyStr(data));
- log.info("AI 椋庨櫓棰勬祴: workOrderId={}, response={}", reqVO.getWorkOrderId(), aiResponse);
+ log.info("AI 椋庨櫓棰勬祴: {}, response={}", logPrefix, aiResponse);
return parseRiskResponse(aiResponse);
} catch (Exception e) {
- log.warn("AI 椋庨櫓棰勬祴澶辫触锛寃orkOrderId={}", reqVO.getWorkOrderId(), e);
+ log.warn("AI 椋庨櫓棰勬祴澶辫触锛寃orkOrderId={}, mpsId={}", reqVO.getWorkOrderId(), reqVO.getMpsId(), e);
MesProAiRiskPredictRespVO fallback = new MesProAiRiskPredictRespVO();
fallback.setReasoning("AI 鍒嗘瀽鏆傛椂涓嶅彲鐢紝璇风◢鍚庨噸璇�");
return fallback;
@@ -560,6 +635,40 @@
return data;
}
+ private Map<String, Object> gatherRiskDataFromMps(Long mpsId) {
+ Map<String, Object> data = new LinkedHashMap<>();
+ MesProMpsDO mps = mpsService.getMps(mpsId);
+ if (mps == null) throw exception(PRO_WORK_ORDER_NOT_EXISTS);
+
+ data.put("MPS 缂栧彿", mps.getCode());
+ data.put("鏉ユ簮", "MPS 鏈笅鍙戯紝椋庨櫓璇勪及鍩轰簬浜у搧淇℃伅");
+ data.put("璁″垝鏁伴噺", mps.getQuantity());
+ data.put("宸茬敓浜ф暟閲�", mps.getQuantityProduced() != null ? mps.getQuantityProduced() : BigDecimal.ZERO);
+ data.put("闇�姹傛棩鏈�", mps.getRequestDate() != null ? mps.getRequestDate().toString() : "鏈缃�");
+ data.put("涓嬪彂鏃ユ湡", mps.getIssueDate() != null ? mps.getIssueDate().toString() : "鏈笅鍙�");
+ fillItemInfo(data, mps.getProductId());
+
+ // 浜у搧搴撳瓨鎯呭喌
+ List<MesWmMaterialStockDO> stocks = materialStockMapper.selectList(
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesWmMaterialStockDO>()
+ .ne(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO));
+ if (CollUtil.isNotEmpty(stocks)) {
+ List<Map<String, Object>> stockList = new ArrayList<>();
+ for (MesWmMaterialStockDO stock : stocks) {
+ Map<String, Object> item = new LinkedHashMap<>();
+ item.put("鐗╂枡ID", stock.getItemId());
+ item.put("鐗╂枡鍚嶇О", resolveItemName(stock.getItemId()));
+ item.put("鍙敤閲�", stock.getQuantity());
+ stockList.add(item);
+ }
+ data.put("搴撳瓨鐗╂枡", stockList);
+ }
+
+ data.put("鎻愮ず", "MPS 鏈笅鍙戞棤宸ュ崟鏁版嵁锛岃缁撳悎浜у搧鐗规�у拰搴撳瓨鑷鍒ゆ柇椋庨櫓");
+
+ return data;
+ }
+
private MesProAiRiskPredictRespVO parseRiskResponse(String aiResponse) {
MesProAiRiskPredictRespVO respVO = new MesProAiRiskPredictRespVO();
try {
@@ -595,12 +704,20 @@
@Override
public MesProAiDeliveryPredictRespVO predictDelivery(MesProAiDeliveryPredictReqVO reqVO) {
try {
- Map<String, Object> data = gatherDeliveryData(reqVO.getWorkOrderId());
+ Map<String, Object> data;
+ String logPrefix;
+ if (reqVO.getMpsId() != null) {
+ data = gatherDeliveryDataFromMps(reqVO.getMpsId());
+ logPrefix = "mpsId=" + reqVO.getMpsId();
+ } else {
+ data = gatherDeliveryData(reqVO.getWorkOrderId());
+ logPrefix = "workOrderId=" + reqVO.getWorkOrderId();
+ }
String aiResponse = aiChatApi.chat(SYSTEM_PROMPT_DELIVERY, JSONUtil.toJsonPrettyStr(data));
- log.info("AI 浜や粯棰勬祴: workOrderId={}, response={}", reqVO.getWorkOrderId(), aiResponse);
+ log.info("AI 浜や粯棰勬祴: {}, response={}", logPrefix, aiResponse);
return parseDeliveryResponse(aiResponse);
} catch (Exception e) {
- log.warn("AI 浜や粯棰勬祴澶辫触锛寃orkOrderId={}", reqVO.getWorkOrderId(), e);
+ log.warn("AI 浜や粯棰勬祴澶辫触锛寃orkOrderId={}, mpsId={}", reqVO.getWorkOrderId(), reqVO.getMpsId(), e);
MesProAiDeliveryPredictRespVO fallback = new MesProAiDeliveryPredictRespVO();
fallback.setReasoning("AI 鍒嗘瀽鏆傛椂涓嶅彲鐢紝璇风◢鍚庨噸璇�");
return fallback;
@@ -681,6 +798,42 @@
return data;
}
+ private Map<String, Object> gatherDeliveryDataFromMps(Long mpsId) {
+ Map<String, Object> data = new LinkedHashMap<>();
+ MesProMpsDO mps = mpsService.getMps(mpsId);
+ if (mps == null) throw exception(PRO_WORK_ORDER_NOT_EXISTS);
+
+ data.put("MPS 缂栧彿", mps.getCode());
+ data.put("鏉ユ簮", "MPS 鏈笅鍙戯紝鍩轰簬浜у搧淇℃伅璇勪及");
+ data.put("璁″垝鏁伴噺", mps.getQuantity());
+ data.put("宸茬敓浜ф暟閲�", mps.getQuantityProduced() != null ? mps.getQuantityProduced() : BigDecimal.ZERO);
+ data.put("MPS 鐘舵��", mps.getStatus());
+ data.put("閿�鍞鍗曞彿", mps.getSaleOrderNo());
+ data.put("闇�姹傛棩鏈�", mps.getRequestDate() != null ? mps.getRequestDate().toString() : "鏈缃�");
+ data.put("涓嬪彂鏃ユ湡", mps.getIssueDate() != null ? mps.getIssueDate().toString() : "鏈笅鍙�");
+ fillItemInfo(data, mps.getProductId());
+ fillClientInfo(data, mps.getClientId());
+
+ // 杩涘害璁$畻
+ BigDecimal progressPercent = BigDecimal.ZERO;
+ if (mps.getQuantity() != null && mps.getQuantity().compareTo(BigDecimal.ZERO) > 0
+ && mps.getQuantityProduced() != null) {
+ progressPercent = mps.getQuantityProduced()
+ .divide(mps.getQuantity(), 4, RoundingMode.HALF_UP)
+ .multiply(BigDecimal.valueOf(100));
+ }
+ data.put("鐢熶骇杩涘害(%)", progressPercent.setScale(1, RoundingMode.HALF_UP));
+
+ // 鍓╀綑澶╂暟
+ int remainingDays = 0;
+ if (mps.getRequestDate() != null) {
+ remainingDays = (int) ChronoUnit.DAYS.between(LocalDate.now(), mps.getRequestDate().toLocalDate());
+ }
+ data.put("璺濈闇�姹傛棩鏈熷墿浣欏ぉ鏁�", Math.max(0, remainingDays));
+
+ return data;
+ }
+
private MesProAiDeliveryPredictRespVO parseDeliveryResponse(String aiResponse) {
MesProAiDeliveryPredictRespVO respVO = new MesProAiDeliveryPredictRespVO();
try {
--
Gitblit v1.9.3