From 3ce57820e618b704ce61487b7cdb38fdd6b0f44a Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 22 九月 2026 14:06:10 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_银川_九鸿化工' into dev_银川_九鸿化工

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/ai/MesProAiServiceImpl.java |  214 ++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 171 insertions(+), 43 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..00591b4 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
@@ -19,7 +19,6 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderBomDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO;
-import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.dv.machinery.MesDvMachineryMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.md.workstation.MesMdWorkstationMachineMapper;
@@ -28,8 +27,8 @@
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskIssueMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskMapper;
 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;
@@ -100,14 +99,13 @@
             浣犻渶瑕佺患鍚堝垎鏋愪互涓嬩俊鎭紝缁欏嚭涓撲笟鐨勯闄╄瘎浼帮細
 
             ## 鍒嗘瀽缁村害
-            1. 璐ㄩ噺椋庨櫓锛氳繃绋嬫楠�(IPQC)鐨勭己闄风巼鏁版嵁锛堣嚧鍛�/涓ラ噸/杞诲井缂洪櫡鐜囷級
-            2. 璁惧椋庨櫓锛氳澶囩姸鎬併�佷笂娆′繚鍏绘椂闂淬�佷笂娆$偣妫�鏃堕棿銆佺敓浜ф晥鐜�
-            3. 鐗╂枡椋庨櫓锛氱墿鏂欏簱瀛樺彲鐢ㄩ噺涓庨渶姹傞噺鐨勫姣�
-            4. 宸ヨ壓鐡堕锛氬叧閿伐搴忕殑璁惧浜ц兘銆佷换鍔℃帓闃熸儏鍐�
+            1. 璁惧椋庨櫓锛氳澶囩姸鎬併�佷笂娆′繚鍏绘椂闂淬�佷笂娆$偣妫�鏃堕棿銆佺敓浜ф晥鐜�
+            2. 鐗╂枡椋庨櫓锛氱墿鏂欏簱瀛樺彲鐢ㄩ噺涓庨渶姹傞噺鐨勫姣�
+            3. 宸ヨ壓鐡堕锛氬叧閿伐搴忕殑璁惧浜ц兘銆佷换鍔℃帓闃熸儏鍐�
 
             ## 椋庨櫓绛夌骇瀹氫箟
-            - 楂橈細瀛樺湪鑷村懡缂洪櫡鐜�>5% 鎴� 璁惧鏁呴殰 鎴� 鍏抽敭鐗╂枡鐭己>50% 鎴� 鍏抽敭宸ュ簭涓ラ噸绉帇
-            - 涓細瀛樺湪涓ラ噸缂洪櫡鐜�>10% 鎴� 璁惧闀挎湡鏈繚鍏� 鎴� 鐗╂枡绱у紶
+            - 楂橈細瀛樺湪璁惧鏁呴殰 鎴� 鍏抽敭鐗╂枡鐭己>50% 鎴� 鍏抽敭宸ュ簭涓ラ噸绉帇
+            - 涓細瀛樺湪璁惧闀挎湡鏈繚鍏� 鎴� 鐗╂枡绱у紶
             - 浣庯細鍚勯」鎸囨爣姝e父
 
             ## 杈撳嚭鏍煎紡
@@ -117,7 +115,7 @@
             overallRiskLevel 鍙栧�硷細1=浣庨闄� 2=涓闄� 3=楂橀闄�
             riskLevel 鍙栧�硷細鍏呰冻/绱у紶/鐭己
             severity 鍙栧�硷細楂�/涓�/浣�
-            category 鍙栧�硷細璐ㄩ噺/璁惧/鐗╂枡/宸ヨ壓鐡堕
+            category 鍙栧�硷細璁惧/鐗╂枡/宸ヨ壓鐡堕
             """;
 
     private static final String SYSTEM_PROMPT_DELIVERY = """
@@ -159,8 +157,6 @@
     @Resource
     private MesProFeedbackMapper feedbackMapper;
     @Resource
-    private MesQcIpqcMapper ipqcMapper;
-    @Resource
     private MesWmMaterialStockMapper materialStockMapper;
     @Resource
     private MesMdWorkstationMachineMapper workstationMachineMapper;
@@ -168,6 +164,8 @@
     private MesDvMachineryMapper machineryMapper;
     @Resource
     private MesProMpsMapper mpsMapper;
+    @Resource
+    private MesProMpsService mpsService;
     @Resource
     private MesProProcessService processService;
     @Resource
@@ -182,12 +180,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 +264,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 +331,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 +435,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 +488,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;
@@ -448,26 +518,6 @@
         data.put("璁″垝鏁伴噺", workOrder.getQuantity());
         data.put("宸茬敓浜ф暟閲�", workOrder.getQuantityProduced());
         fillItemInfo(data, workOrder.getProductId());
-
-        // 璐ㄩ噺椋庨櫓 - IPQC
-        List<MesQcIpqcDO> ipqcList = ipqcMapper.selectList(
-                new LambdaQueryWrapper<MesQcIpqcDO>()
-                        .eq(MesQcIpqcDO::getWorkOrderId, workOrderId));
-        if (CollUtil.isNotEmpty(ipqcList)) {
-            List<Map<String, Object>> qcData = ipqcList.stream().map(ipqc -> {
-                Map<String, Object> item = new LinkedHashMap<>();
-                item.put("妫�楠屽崟缂栧彿", ipqc.getCode());
-                item.put("鑷村懡缂洪櫡鐜�", ipqc.getCriticalRate() != null ? ipqc.getCriticalRate() + "%" : "0%");
-                item.put("涓ラ噸缂洪櫡鐜�", ipqc.getMajorRate() != null ? ipqc.getMajorRate() + "%" : "0%");
-                item.put("杞诲井缂洪櫡鐜�", ipqc.getMinorRate() != null ? ipqc.getMinorRate() + "%" : "0%");
-                item.put("鑷村懡缂洪櫡鏁�", ipqc.getCriticalQuantity() != null ? ipqc.getCriticalQuantity() : 0);
-                item.put("涓ラ噸缂洪櫡鏁�", ipqc.getMajorQuantity() != null ? ipqc.getMajorQuantity() : 0);
-                item.put("杞诲井缂洪櫡鏁�", ipqc.getMinorQuantity() != null ? ipqc.getMinorQuantity() : 0);
-                item.put("妫�楠岀粨鏋�", ipqc.getCheckResult());
-                return item;
-            }).collect(Collectors.toList());
-            data.put("杩囩▼妫�楠�(IPQC)璁板綍", qcData);
-        }
 
         // 璁惧椋庨櫓
         List<MesProTaskDO> tasks = taskMapper.selectListByWorkOrderId(workOrderId);
@@ -560,6 +610,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 +679,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 +773,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