From b630f868ae5cb4bc2e92c48d275341d36d260499 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 22 九月 2026 12:58:07 +0800
Subject: [PATCH] feat(qc): 新增OQC出货检验功能及优化质量流程
---
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