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/controller/admin/pro/ai/vo/MesProAiMaterialPredictReqVO.java |    7 +
 yudao-server/src/main/resources/application-dev.yaml                                                                    |    2 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiDurationPredictReqVO.java |    7 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiRiskPredictReqVO.java     |    7 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiDeliveryPredictReqVO.java |    7 +
 yudao-server/src/main/resources/application-test.yaml                                                                   |   16 ++++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/ai/MesProAiServiceImpl.java                      |  177 +++++++++++++++++++++++++++++++++++++++++---
 7 files changed, 199 insertions(+), 24 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiDeliveryPredictReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiDeliveryPredictReqVO.java
index 79bcffe..ccef9c1 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiDeliveryPredictReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiDeliveryPredictReqVO.java
@@ -1,15 +1,16 @@
 package cn.iocoder.yudao.module.mes.controller.admin.pro.ai.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
 @Schema(description = "绠$悊鍚庡彴 - AI 鎸夋椂浜や粯棰勬祴璇锋眰 VO")
 @Data
 public class MesProAiDeliveryPredictReqVO {
 
-    @Schema(description = "宸ュ崟 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    @NotNull(message = "宸ュ崟 ID 涓嶈兘涓虹┖")
+    @Schema(description = "宸ュ崟 ID", example = "1")
     private Long workOrderId;
 
+    @Schema(description = "MPS ID锛堜笌宸ュ崟 ID 浜岄�変竴锛�", example = "1")
+    private Long mpsId;
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiDurationPredictReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiDurationPredictReqVO.java
index 4de3df9..e81ede4 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiDurationPredictReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiDurationPredictReqVO.java
@@ -1,15 +1,16 @@
 package cn.iocoder.yudao.module.mes.controller.admin.pro.ai.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
 @Schema(description = "绠$悊鍚庡彴 - AI 鐢熶骇鏃堕暱棰勬祴璇锋眰 VO")
 @Data
 public class MesProAiDurationPredictReqVO {
 
-    @Schema(description = "宸ュ崟 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    @NotNull(message = "宸ュ崟 ID 涓嶈兘涓虹┖")
+    @Schema(description = "宸ュ崟 ID", example = "1")
     private Long workOrderId;
 
+    @Schema(description = "MPS ID锛堜笌宸ュ崟 ID 浜岄�変竴锛�", example = "1")
+    private Long mpsId;
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiMaterialPredictReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiMaterialPredictReqVO.java
index 201eef5..7c6a097 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiMaterialPredictReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiMaterialPredictReqVO.java
@@ -1,15 +1,16 @@
 package cn.iocoder.yudao.module.mes.controller.admin.pro.ai.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
 @Schema(description = "绠$悊鍚庡彴 - AI 鐗╂枡鐭己棰勬祴璇锋眰 VO")
 @Data
 public class MesProAiMaterialPredictReqVO {
 
-    @Schema(description = "宸ュ崟 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    @NotNull(message = "宸ュ崟 ID 涓嶈兘涓虹┖")
+    @Schema(description = "宸ュ崟 ID", example = "1")
     private Long workOrderId;
 
+    @Schema(description = "MPS ID锛堜笌宸ュ崟 ID 浜岄�変竴锛�", example = "1")
+    private Long mpsId;
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiRiskPredictReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiRiskPredictReqVO.java
index ef1e7e3..bad7472 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiRiskPredictReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/ai/vo/MesProAiRiskPredictReqVO.java
@@ -1,15 +1,16 @@
 package cn.iocoder.yudao.module.mes.controller.admin.pro.ai.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
 @Schema(description = "绠$悊鍚庡彴 - AI 鐢熶骇椋庨櫓棰勬祴璇锋眰 VO")
 @Data
 public class MesProAiRiskPredictReqVO {
 
-    @Schema(description = "宸ュ崟 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    @NotNull(message = "宸ュ崟 ID 涓嶈兘涓虹┖")
+    @Schema(description = "宸ュ崟 ID", example = "1")
     private Long workOrderId;
 
+    @Schema(description = "MPS ID锛堜笌宸ュ崟 ID 浜岄�変竴锛�", example = "1")
+    private Long mpsId;
+
 }
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 {
diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml
index 1f1fdf4..1901934 100644
--- a/yudao-server/src/main/resources/application-dev.yaml
+++ b/yudao-server/src/main/resources/application-dev.yaml
@@ -11,6 +11,8 @@
       - org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration # 绂佺敤 AI 妯″潡鐨� Redis锛屾墜鍔ㄥ垱寤�
       - org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # 绂佺敤 AI 妯″潡鐨� Milvus锛屾墜鍔ㄥ垱寤�
       - org.dromara.trans.config.TransServiceConfig # TODO 鑺嬭壙锛歟asy-trans 鍏煎 Spring Boot 4 鍚庣Щ闄わ紙TransServiceConfig 寮曠敤浜嗗凡绉婚櫎鐨� RestTemplateBuilder锛�
+      # 绂佺敤鏈厤缃� API Key 鐨勬ā鍨嬭嚜鍔ㄩ厤缃�
+      - org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration
   # 鏁版嵁婧愰厤缃」
   datasource:
     druid: # Druid 銆愮洃鎺с�戠浉鍏崇殑鍏ㄥ眬閰嶇疆
diff --git a/yudao-server/src/main/resources/application-test.yaml b/yudao-server/src/main/resources/application-test.yaml
index 4bf8185..f3dcef3 100644
--- a/yudao-server/src/main/resources/application-test.yaml
+++ b/yudao-server/src/main/resources/application-test.yaml
@@ -11,6 +11,22 @@
       - org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration # 绂佺敤 AI 妯″潡鐨� Redis锛屾墜鍔ㄥ垱寤�
       - org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # 绂佺敤 AI 妯″潡鐨� Milvus锛屾墜鍔ㄥ垱寤�
       - org.dromara.trans.config.TransServiceConfig # TODO 鑺嬭壙锛歟asy-trans 鍏煎 Spring Boot 4 鍚庣Щ闄わ紙TransServiceConfig 寮曠敤浜嗗凡绉婚櫎鐨� RestTemplateBuilder锛�
+      # 绂佺敤鏈厤缃� API Key 鐨勬ā鍨嬭嚜鍔ㄩ厤缃紙鍙繚鐣欓�氫箟鍗冮棶 Embedding 鐨勬墜鍔ㄩ厤缃級
+      - org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration
+      - org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration
+      - org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration
+      - org.springframework.ai.model.openai.autoconfigure.OpenAiAudioSpeechAutoConfiguration
+      - org.springframework.ai.model.openai.autoconfigure.OpenAiAudioTranscriptionAutoConfiguration
+      - org.springframework.ai.model.openai.autoconfigure.OpenAiModerationAutoConfiguration
+      - org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration
+      - org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration
+      - org.springframework.ai.model.google.genai.autoconfigure.chat.GoogleGenAiChatAutoConfiguration
+      - org.springframework.ai.model.google.genai.autoconfigure.embedding.GoogleGenAiEmbeddingConnectionAutoConfiguration
+      - org.springframework.ai.model.google.genai.autoconfigure.embedding.GoogleGenAiTextEmbeddingAutoConfiguration
+      - org.springframework.ai.model.ollama.autoconfigure.OllamaApiAutoConfiguration
+      - org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration
+      - org.springframework.ai.model.ollama.autoconfigure.OllamaEmbeddingAutoConfiguration
+      - org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration
   # 鏁版嵁婧愰厤缃」
   datasource:
     druid: # Druid 銆愮洃鎺с�戠浉鍏崇殑鍏ㄥ眬閰嶇疆

--
Gitblit v1.9.3