From f4ef6d1f4489e0d4cbba1845792945b924c16228 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 14 十月 2026 18:29:56 +0800
Subject: [PATCH] feat: 客户签订合同统计 + MES 首页质检合格率

---
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java                     |   21 +++++
 yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerAutoPoolTest.java                |    9 ++
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java         |   13 +++
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java                    |    2 
 yudao-module-mes/src/main/resources/mapper/home/MesHomeStatisticsMapper.xml                                             |   21 +++++
 docs/project-business/data/objects.json                                                                                 |    2 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/home/vo/MesHomeSummaryRespVO.java           |   18 ++++
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java |    9 ++
 docs/project-business/index.html                                                                                        |    2 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java                    |   47 +++++++++++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/home/MesHomeStatisticsMapper.java                  |    9 ++
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java                 |   10 ++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/home/MesHomeStatisticsServiceImpl.java               |   49 ++++++++++++
 13 files changed, 210 insertions(+), 2 deletions(-)

diff --git a/docs/project-business/data/objects.json b/docs/project-business/data/objects.json
index 97cab45..898c98b 100644
--- a/docs/project-business/data/objects.json
+++ b/docs/project-business/data/objects.json
@@ -2,7 +2,7 @@
   "objects": [
     {"name": "鐗╂枡", "module": "鍩虹涓绘暟鎹�", "table": "mdm_item", "doc": "MDM 鏉冨▉鐗╂枡/浜у搧/鍗婃垚鍝佹。妗堬紝鍚� SKU銆佹壒娆″睘鎬с�佷环鏍笺�佸畨鍏ㄥ簱瀛�", "upstream": ["MES 鐗╂枡鍙嶅悜鍚屾"], "downstream": ["鎶ヤ环鍗�", "閿�鍞鍗�", "閲囪喘璁㈠崟", "宸ュ崟BOM", "搴撳瓨鍙拌处"], "keywords": "鐗╂枡 浜у搧 鍘熸潗鏂� item"},
     {"name": "浠撳簱", "module": "鍩虹涓绘暟鎹�", "table": "mdm_warehouse", "doc": "浠撳簱涓绘暟鎹紝type: 1鍘熸枡/2鎴愬搧/3鍗婃垚鍝�/4绾胯竟锛汳ES 鑷湁 mes_wm_warehouse 鎵胯浇搴撳尯搴撲綅", "upstream": [], "downstream": ["搴撳瓨鍙拌处", "鍑哄叆搴撳崟鎹�"], "keywords": "浠撳簱 warehouse 鍘熸枡浠� 鎴愬搧浠� 绾胯竟浠�"},
-    {"name": "瀹㈡埛", "module": "閿�鍞鐞�", "table": "crm_customer", "doc": "瀹㈡埛涓绘。锛屽叕娴锋睜/閿佸畾/鎴愪氦鐘舵�侊紝璐熻矗浜烘暟鎹潈闄�", "upstream": ["绾跨储"], "downstream": ["鑱旂郴浜�", "鍟嗘満", "鎶ヤ环鍗�", "鍚堝悓", "鍞悗宸ュ崟", "鏀舵鍗�"], "keywords": "瀹㈡埛 customer 鍏捣"},
+    {"name": "瀹㈡埛", "module": "閿�鍞鐞�", "table": "crm_customer", "doc": "瀹㈡埛涓绘。锛屽叕娴锋睜/閿佸畾/鎴愪氦鐘舵�侊紝璐熻矗浜烘暟鎹潈闄愶紱瀹㈡埛鍒楄〃鎸夈�屽鏍搁�氳繃銆嶅悎鍚屽疄鏃剁粺璁″凡绛捐鍚堝悓浠芥暟涓庨噾棰濆悎璁�", "upstream": ["绾跨储"], "downstream": ["鑱旂郴浜�", "鍟嗘満", "鎶ヤ环鍗�", "鍚堝悓", "鍞悗宸ュ崟", "鏀舵鍗�"], "keywords": "瀹㈡埛 customer 鍏捣"},
     {"name": "绾跨储", "module": "閿�鍞鐞�", "table": "crm_clue", "doc": "鍘熷閿�鍞嚎绱紝浠呬竴娆¤浆鍖栦负瀹㈡埛", "upstream": [], "downstream": ["瀹㈡埛"], "keywords": "绾跨储 clue"},
     {"name": "鑱旂郴浜�", "module": "閿�鍞鐞�", "table": "crm_contact", "doc": "瀹㈡埛涓嬭仈绯讳汉锛屼笌鍟嗘満澶氬澶�", "upstream": ["瀹㈡埛"], "downstream": ["鍟嗘満"], "keywords": "鑱旂郴浜� contact"},
     {"name": "鍟嗘満", "module": "閿�鍞鐞�", "table": "crm_business", "doc": "閿�鍞満浼氾紝DB 閰嶇疆寮忛樁娈垫帹杩涳紝缁撴潫鎬�: 璧�/杈�/鏃犳晥", "upstream": ["瀹㈡埛"], "downstream": ["鎶ヤ环鍗�", "鍚堝悓"], "keywords": "鍟嗘満 business 璧㈠崟 杈撳崟 闃舵"},
diff --git a/docs/project-business/index.html b/docs/project-business/index.html
index f9036ad..43fa8df 100644
--- a/docs/project-business/index.html
+++ b/docs/project-business/index.html
@@ -445,7 +445,7 @@
   "objects": [
     {"name": "鐗╂枡", "module": "鍩虹涓绘暟鎹�", "table": "mdm_item", "doc": "MDM 鏉冨▉鐗╂枡/浜у搧/鍗婃垚鍝佹。妗堬紝鍚� SKU銆佹壒娆″睘鎬с�佷环鏍笺�佸畨鍏ㄥ簱瀛�", "upstream": ["MES 鐗╂枡鍙嶅悜鍚屾"], "downstream": ["鎶ヤ环鍗�", "閿�鍞鍗�", "閲囪喘璁㈠崟", "宸ュ崟BOM", "搴撳瓨鍙拌处"], "keywords": "鐗╂枡 浜у搧 鍘熸潗鏂� item"},
     {"name": "浠撳簱", "module": "鍩虹涓绘暟鎹�", "table": "mdm_warehouse", "doc": "浠撳簱涓绘暟鎹紝type: 1鍘熸枡/2鎴愬搧/3鍗婃垚鍝�/4绾胯竟锛汳ES 鑷湁 mes_wm_warehouse 鎵胯浇搴撳尯搴撲綅", "upstream": [], "downstream": ["搴撳瓨鍙拌处", "鍑哄叆搴撳崟鎹�"], "keywords": "浠撳簱 warehouse 鍘熸枡浠� 鎴愬搧浠� 绾胯竟浠�"},
-    {"name": "瀹㈡埛", "module": "閿�鍞鐞�", "table": "crm_customer", "doc": "瀹㈡埛涓绘。锛屽叕娴锋睜/閿佸畾/鎴愪氦鐘舵�侊紝璐熻矗浜烘暟鎹潈闄�", "upstream": ["绾跨储"], "downstream": ["鑱旂郴浜�", "鍟嗘満", "鎶ヤ环鍗�", "鍚堝悓", "鍞悗宸ュ崟", "鏀舵鍗�"], "keywords": "瀹㈡埛 customer 鍏捣"},
+    {"name": "瀹㈡埛", "module": "閿�鍞鐞�", "table": "crm_customer", "doc": "瀹㈡埛涓绘。锛屽叕娴锋睜/閿佸畾/鎴愪氦鐘舵�侊紝璐熻矗浜烘暟鎹潈闄愶紱瀹㈡埛鍒楄〃鎸夈�屽鏍搁�氳繃銆嶅悎鍚屽疄鏃剁粺璁″凡绛捐鍚堝悓浠芥暟涓庨噾棰濆悎璁�", "upstream": ["绾跨储"], "downstream": ["鑱旂郴浜�", "鍟嗘満", "鎶ヤ环鍗�", "鍚堝悓", "鍞悗宸ュ崟", "鏀舵鍗�"], "keywords": "瀹㈡埛 customer 鍏捣"},
     {"name": "绾跨储", "module": "閿�鍞鐞�", "table": "crm_clue", "doc": "鍘熷閿�鍞嚎绱紝浠呬竴娆¤浆鍖栦负瀹㈡埛", "upstream": [], "downstream": ["瀹㈡埛"], "keywords": "绾跨储 clue"},
     {"name": "鑱旂郴浜�", "module": "閿�鍞鐞�", "table": "crm_contact", "doc": "瀹㈡埛涓嬭仈绯讳汉锛屼笌鍟嗘満澶氬澶�", "upstream": ["瀹㈡埛"], "downstream": ["鍟嗘満"], "keywords": "鑱旂郴浜� contact"},
     {"name": "鍟嗘満", "module": "閿�鍞鐞�", "table": "crm_business", "doc": "閿�鍞満浼氾紝DB 閰嶇疆寮忛樁娈垫帹杩涳紝缁撴潫鎬�: 璧�/杈�/鏃犳晥", "upstream": ["瀹㈡埛"], "downstream": ["鎶ヤ环鍗�", "鍚堝悓"], "keywords": "鍟嗘満 business 璧㈠崟 杈撳崟 闃舵"},
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
index 5438515..7053219 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
@@ -15,6 +15,7 @@
 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
+import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService;
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@@ -33,10 +34,12 @@
 import org.springframework.web.bind.annotation.*;
 
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Stream;
 
 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
@@ -56,6 +59,8 @@
     private CrmCustomerService customerService;
     @Resource
     private CrmCustomerPoolConfigService customerPoolConfigService;
+    @Resource
+    private CrmContractService contractService;
 
     @Resource
     private DeptApi deptApi;
@@ -139,6 +144,10 @@
         Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
         // 1.2 鑾峰彇璺濈杩涘叆鍏捣鐨勬椂闂�
         Map<Long, Long> poolDayMap = getPoolDayMap(list);
+        // 1.3 鑾峰彇銆屽凡绛捐鍚堝悓銆嶇殑鏁伴噺涓庨噾棰濆悎璁★紙鎸夋湰椤靛鎴锋壒閲忕粺璁★紝瀹℃牳閫氳繃鐨勫悎鍚屾墠绠楋級
+        Set<Long> customerIds = convertSet(list, CrmCustomerDO::getId);
+        Map<Long, Long> contractCountMap = contractService.getContractCountMapByCustomerIds(customerIds);
+        Map<Long, BigDecimal> contractPriceMap = contractService.getContractPriceMapByCustomerIds(customerIds);
         // 2. 杞崲鎴� VO
         return BeanUtils.toBean(list, CrmCustomerRespVO.class, customerVO -> {
             customerVO.setAreaName(AreaUtils.format(customerVO.getAreaId()));
@@ -153,6 +162,10 @@
             if (customerVO.getOwnerUserId() != null) {
                 customerVO.setPoolDay(poolDayMap.get(customerVO.getId()));
             }
+            // 2.3 璁剧疆宸茬璁㈠悎鍚岀殑鏁伴噺涓庨噾棰濆悎璁★紙娌℃湁鍚堝悓鐨勫鎴凤細鏁伴噺 0銆侀噾棰� null锛屽墠绔樉绀恒�屸�斻�嶏級
+            customerVO.setContractCount(
+                    contractCountMap.getOrDefault(customerVO.getId(), 0L).intValue());
+            customerVO.setContractTotalPrice(contractPriceMap.get(customerVO.getId()));
         });
     }
 
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java
index 75d82b9..cbe2823 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java
@@ -9,6 +9,7 @@
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
 @Schema(description = "绠$悊鍚庡彴 - CRM 瀹㈡埛 Response VO")
@@ -132,4 +133,12 @@
     @Schema(description = "璺濈鍔犲叆鍏捣鏃堕棿", example = "1")
     private Long poolDay;
 
+    // ========== 绛捐鍚堝悓锛堢敱 crm_contract 鐨勫鏍搁�氳繃鍚堝悓瀹炴椂缁熻锛岄潪瀹㈡埛琛ㄥ瓧娈碉級 ==========
+
+    @Schema(description = "宸茬璁㈠悎鍚屾暟閲忥紙瀹℃壒鐘舵�佷负瀹℃牳閫氳繃锛夛紝娌℃湁鍒欎负 0", example = "2")
+    private Integer contractCount;
+
+    @Schema(description = "宸茬璁㈠悎鍚岄噾棰濆悎璁★紙瀹℃壒鐘舵�佷负瀹℃牳閫氳繃锛夛紝娌℃湁鍒欎负 null", example = "3235289.45")
+    private BigDecimal contractTotalPrice;
+
 }
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
index aaaa4cf..296d4d5 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
@@ -13,11 +13,17 @@
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
 import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 
 /**
  * CRM 鍚堝悓 Mapper
@@ -207,4 +213,45 @@
                 .reduce(BigDecimal.ZERO, BigDecimal::add);
     }
 
+    // ========== 鎸夊鎴锋壒閲忕粺璁★紙瀹㈡埛鍒楄〃灞曠ず銆岀璁㈠悎鍚屻�嶏級 ==========
+
+    /**
+     * 鎵归噺缁熻鍚勫鎴枫�屽凡绛捐銆嶏紙瀹℃牳閫氳繃锛夌殑鍚堝悓鏁伴噺
+     *
+     * @param customerIds 瀹㈡埛缂栧彿闆嗗悎
+     * @return key锛氬鎴风紪鍙凤紝value锛氬悎鍚屾暟閲忋�傛病鏈夊悎鍚岀殑瀹㈡埛涓嶅湪 map 涓�
+     */
+    default Map<Long, Long> selectContractCountMapByCustomerIds(Collection<Long> customerIds) {
+        if (CollUtil.isEmpty(customerIds)) {
+            return Collections.emptyMap();
+        }
+        List<Map<String, Object>> result = selectMaps(new QueryWrapper<CrmContractDO>()
+                .select("customer_id, COUNT(*) AS total")
+                .eq("audit_status", CrmAuditStatusEnum.APPROVE.getStatus())
+                .groupBy("customer_id")
+                .in("customer_id", customerIds));
+        return convertMap(result, obj -> (Long) obj.get("customer_id"),
+                obj -> ((Number) obj.get("total")).longValue());
+    }
+
+    /**
+     * 鎵归噺缁熻鍚勫鎴枫�屽凡绛捐銆嶏紙瀹℃牳閫氳繃锛夌殑鍚堝悓閲戦鍚堣
+     *
+     * @param customerIds 瀹㈡埛缂栧彿闆嗗悎
+     * @return key锛氬鎴风紪鍙凤紝value锛氶噾棰濆悎璁°�傛病鏈夊悎鍚岀殑瀹㈡埛涓嶅湪 map 涓�
+     */
+    default Map<Long, BigDecimal> selectContractPriceMapByCustomerIds(Collection<Long> customerIds) {
+        if (CollUtil.isEmpty(customerIds)) {
+            return Collections.emptyMap();
+        }
+        // 閲戦鐢� IFNULL 鍏滃簳锛歝onvertMap 鍐呴儴鏄� Collectors.toMap锛寁alue 涓� null 浼氭姏 NPE
+        List<Map<String, Object>> result = selectMaps(new QueryWrapper<CrmContractDO>()
+                .select("customer_id, IFNULL(SUM(total_price), 0) AS total_price")
+                .eq("audit_status", CrmAuditStatusEnum.APPROVE.getStatus())
+                .groupBy("customer_id")
+                .in("customer_id", customerIds));
+        return convertMap(result, obj -> (Long) obj.get("customer_id"),
+                obj -> (BigDecimal) obj.get("total_price"));
+    }
+
 }
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
index 5c277a4..cb70ab8 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
@@ -92,6 +92,8 @@
                 throw new IllegalArgumentException("鏈煡鑱旂郴鐘舵�侊細" + pageReqVO.getContactStatus());
             }
         }
+        // 閲嶇偣瀹㈡埛锛圓/level=1锛夐粯璁ゆ帓鍦ㄥ墠闈�
+        query.orderByAsc(CrmCustomerDO::getLevel);
         return selectJoinPage(pageReqVO, CrmCustomerDO.class, query);
     }
 
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
index e4a9837..cac77ea 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
@@ -12,6 +12,7 @@
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import jakarta.validation.Valid;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
@@ -188,6 +189,26 @@
     Long getContractCountByCustomerId(Long customerId);
 
     /**
+     * 鎵归噺缁熻鍚勫鎴枫�屽凡绛捐銆嶇殑鍚堝悓鏁伴噺
+     *
+     * 銆屽凡绛捐銆�= 瀹℃壒鐘舵�佷负瀹℃牳閫氳繃鐨勫悎鍚�
+     *
+     * @param customerIds 瀹㈡埛缂栧彿闆嗗悎
+     * @return key锛氬鎴风紪鍙凤紝value锛氬悎鍚屾暟閲忋�傛病鏈夊凡绛捐鍚堝悓鐨勫鎴蜂笉鍦� map 涓�
+     */
+    Map<Long, Long> getContractCountMapByCustomerIds(Collection<Long> customerIds);
+
+    /**
+     * 鎵归噺缁熻鍚勫鎴枫�屽凡绛捐銆嶇殑鍚堝悓閲戦鍚堣
+     *
+     * 銆屽凡绛捐銆�= 瀹℃壒鐘舵�佷负瀹℃牳閫氳繃鐨勫悎鍚�
+     *
+     * @param customerIds 瀹㈡埛缂栧彿闆嗗悎
+     * @return key锛氬鎴风紪鍙凤紝value锛氶噾棰濆悎璁°�傛病鏈夊凡绛捐鍚堝悓鐨勫鎴蜂笉鍦� map 涓�
+     */
+    Map<Long, BigDecimal> getContractPriceMapByCustomerIds(Collection<Long> customerIds);
+
+    /**
      * 鏍规嵁鍟嗘満缂栧彿锛岃幏鍙栧叧鑱斿鎴风殑鍚堝悓鏁伴噺
      *
      * @param businessId 鍟嗘満缂栧彿
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
index 0e59d84..7936917 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
@@ -737,6 +737,16 @@
     }
 
     @Override
+    public Map<Long, Long> getContractCountMapByCustomerIds(Collection<Long> customerIds) {
+        return contractMapper.selectContractCountMapByCustomerIds(customerIds);
+    }
+
+    @Override
+    public Map<Long, BigDecimal> getContractPriceMapByCustomerIds(Collection<Long> customerIds) {
+        return contractMapper.selectContractPriceMapByCustomerIds(customerIds);
+    }
+
+    @Override
     public List<CrmContractProductDO> getContractProductListByContractId(Long contactId) {
         return contractProductMapper.selectListByContractId(contactId);
     }
diff --git a/yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerAutoPoolTest.java b/yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerAutoPoolTest.java
index 4111aad..057d37a 100644
--- a/yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerAutoPoolTest.java
+++ b/yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerAutoPoolTest.java
@@ -11,6 +11,7 @@
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
 import cn.iocoder.yudao.module.crm.controller.admin.customer.CrmCustomerController;
+import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@@ -91,9 +92,17 @@
                 }));
         var config = CrmCustomerPoolConfigService.class.cast(Proxy.newProxyInstance(getClass().getClassLoader(),
                 new Class<?>[]{CrmCustomerPoolConfigService.class}, (proxy, method, args) -> null));
+        // 瀹㈡埛鍒楄〃鎷兼帴浼氶『甯︾粺璁°�屽凡绛捐鍚堝悓銆嶏紝杩欓噷妗╂帀锛岃繑鍥炵┖ Map 琛ㄧず鏈敤渚嬬殑瀹㈡埛娌℃湁鍚堝悓
+        var contracts = CrmContractService.class.cast(Proxy.newProxyInstance(getClass().getClassLoader(),
+                new Class<?>[]{CrmContractService.class}, (proxy, method, args) ->
+                        switch (method.getName()) {
+                            case "getContractCountMapByCustomerIds", "getContractPriceMapByCustomerIds" -> Map.of();
+                            default -> java.lang.reflect.InvocationHandler.invokeDefault(proxy, method, args);
+                        }));
         ReflectionTestUtils.setField(controller, "adminUserApi", users);
         ReflectionTestUtils.setField(controller, "deptApi", departments);
         ReflectionTestUtils.setField(controller, "customerPoolConfigService", config);
+        ReflectionTestUtils.setField(controller, "contractService", contracts);
         var customer = controller.buildCustomerDetailList(List.of(new CrmCustomerDO().setId(1L)
                 .setOwnerUserId(10L).setAutoPoolEnabled(true))).getFirst();
         assertEquals(10L, customer.getOwnerUserId());
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/home/vo/MesHomeSummaryRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/home/vo/MesHomeSummaryRespVO.java
index 4a36bb1..825b36a 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/home/vo/MesHomeSummaryRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/home/vo/MesHomeSummaryRespVO.java
@@ -36,6 +36,24 @@
     @Schema(description = "浠婃棩涓嶈壇鍝佹暟閲�", requiredMode = Schema.RequiredMode.REQUIRED, example = "34")
     private BigDecimal todayUnqualifiedQuantity;
 
+    @Schema(description = "鏉ユ枡妫�楠屽悎鏍肩巼(%)锛屾棤妫�楠屾暟鎹椂涓� null", example = "99.93")
+    private BigDecimal iqcQualifiedRate;
+
+    @Schema(description = "杩囩▼妫�楠屽悎鏍肩巼(%)锛屾棤妫�楠屾暟鎹椂涓� null", example = "99.97")
+    private BigDecimal ipqcQualifiedRate;
+
+    @Schema(description = "鍑鸿揣妫�楠屽悎鏍肩巼(%)锛屾棤妫�楠屾暟鎹椂涓� null", example = "100.00")
+    private BigDecimal oqcQualifiedRate;
+
+    @Schema(description = "鏉ユ枡妫�楠岀殑妫�楠屾暟閲忓悎璁�(鍚堟牸鐜囩殑鍒嗘瘝)锛屾棤鏁版嵁鏃朵负 0", example = "16782")
+    private Long iqcCheckQuantity;
+
+    @Schema(description = "杩囩▼妫�楠岀殑妫�楠屾暟閲忓悎璁�(鍚堟牸鐜囩殑鍒嗘瘝)锛屾棤鏁版嵁鏃朵负 0", example = "12485")
+    private Long ipqcCheckQuantity;
+
+    @Schema(description = "鍑鸿揣妫�楠岀殑妫�楠屾暟閲忓悎璁�(鍚堟牸鐜囩殑鍒嗘瘝)锛屾棤鏁版嵁鏃朵负 0", example = "2389")
+    private Long oqcCheckQuantity;
+
     // ========== 璁惧缁熻 ==========
 
     @Schema(description = "璁惧鎬绘暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/home/MesHomeStatisticsMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/home/MesHomeStatisticsMapper.java
index c30b7f5..022b777 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/home/MesHomeStatisticsMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/home/MesHomeStatisticsMapper.java
@@ -55,6 +55,15 @@
     Long selectRepairActiveCount();
 
     /**
+     * 鎸夋楠岀被鍨嬬粺璁¤川妫�鍗曠殑妫�楠岄噺涓庡悎鏍奸噺
+     *
+     * 涓嶈繃婊ゅ崟鎹姸鎬侊紙鑽夌涓庡凡瀹屾垚閮界畻锛夛紝涔熶笉鍋氭椂闂磋寖鍥撮檺鍒讹紙鍏ㄩ噺绱锛夈��
+     *
+     * @return [{ "type": "IQC", "checkQuantity": 16782, "qualifiedQuantity": 16770 }, ...]
+     */
+    List<Map<String, Object>> selectQcQualifiedSummary();
+
+    /**
      * 鎸夊ぉ鑱氬悎鎶ュ伐浜ч噺锛堣繎 N 澶╋級
      *
      * @param beginTime >= 寮�濮嬫椂闂�
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/home/MesHomeStatisticsServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/home/MesHomeStatisticsServiceImpl.java
index 5782ee8..474e9fd 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/home/MesHomeStatisticsServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/home/MesHomeStatisticsServiceImpl.java
@@ -11,6 +11,7 @@
 import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
@@ -90,6 +91,23 @@
         summary.setAndonActiveCount(homeStatisticsMapper.selectAndonActiveCount());
         summary.setRepairActiveCount(homeStatisticsMapper.selectRepairActiveCount());
 
+        // 6. 璐ㄦ鍚堟牸鐜囷紙鏉ユ枡 / 杩囩▼ / 鍑鸿揣锛�
+        //    鏁伴噺鍙e緞 = 鍚堟牸鏁伴噺 / 妫�楠屾暟閲忥紱涓嶈繃婊ゅ崟鎹姸鎬侊紝涔熶笉鍋氭椂闂磋寖鍥撮檺鍒讹紙鍏ㄩ噺绱锛�
+        Map<String, Map<String, BigDecimal>> qcSummary = new HashMap<>();
+        for (Map<String, Object> row : homeStatisticsMapper.selectQcQualifiedSummary()) {
+            Map<String, BigDecimal> metrics = new HashMap<>();
+            metrics.put("checkQuantity", getDecimalFromObj(row.get("checkQuantity")));
+            metrics.put("qualifiedQuantity", getDecimalFromObj(row.get("qualifiedQuantity")));
+            qcSummary.put(String.valueOf(row.get("type")), metrics);
+        }
+        summary.setIqcQualifiedRate(calcQcQualifiedRate(qcSummary.get("IQC")));
+        summary.setIpqcQualifiedRate(calcQcQualifiedRate(qcSummary.get("IPQC")));
+        summary.setOqcQualifiedRate(calcQcQualifiedRate(qcSummary.get("OQC")));
+        // 涓変釜鐜妭鍚勮嚜鐨勬楠屾暟閲忓悎璁★紙鍚堟牸鐜囩殑鍒嗘瘝锛夛紝渚涘崱鐗囧壇琛屽睍绀烘牱鏈噺
+        summary.setIqcCheckQuantity(calcCheckQuantity(qcSummary.get("IQC")));
+        summary.setIpqcCheckQuantity(calcCheckQuantity(qcSummary.get("IPQC")));
+        summary.setOqcCheckQuantity(calcCheckQuantity(qcSummary.get("OQC")));
+
         return summary;
     }
 
@@ -165,4 +183,35 @@
         return new BigDecimal(obj.toString());
     }
 
+    /**
+     * 璁$畻璐ㄦ鍚堟牸鐜囷紙鐧惧垎姣旓紝淇濈暀涓や綅灏忔暟锛�
+     *
+     * 妫�楠屾暟閲忎负 0锛堝嵆娌℃湁宸插畬鎴愮殑璐ㄦ鍗曪級鏃惰繑鍥� null锛�
+     * 璁╅〉闈㈡樉绀恒�屸�斻�嶏紝鑰屼笉鏄瀵兼�х殑 0%銆�
+     */
+    private BigDecimal calcQcQualifiedRate(Map<String, BigDecimal> qcSummary) {
+        if (qcSummary == null) {
+            return null;
+        }
+        BigDecimal checkedQuantity = getDecimal(qcSummary, "checkQuantity");
+        if (checkedQuantity.compareTo(BigDecimal.ZERO) <= 0) {
+            return null;
+        }
+        return getDecimal(qcSummary, "qualifiedQuantity")
+                .multiply(BigDecimal.valueOf(100))
+                .divide(checkedQuantity, 2, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 鍙栬妫�楠岀幆鑺傜殑妫�楠屾暟閲忓悎璁★紙鍚堟牸鐜囩殑鍒嗘瘝锛屽嵆鍗$墖鍓灞曠ず鐨勬牱鏈噺锛�
+     *
+     * 璇ョ幆鑺傛病鏈変换浣曡川妫�鍗曟椂杩斿洖 0銆�
+     */
+    private Long calcCheckQuantity(Map<String, BigDecimal> qcSummary) {
+        if (qcSummary == null) {
+            return 0L;
+        }
+        return getDecimal(qcSummary, "checkQuantity").longValue();
+    }
+
 }
diff --git a/yudao-module-mes/src/main/resources/mapper/home/MesHomeStatisticsMapper.xml b/yudao-module-mes/src/main/resources/mapper/home/MesHomeStatisticsMapper.xml
index 33d6f00..b5ce7b0 100644
--- a/yudao-module-mes/src/main/resources/mapper/home/MesHomeStatisticsMapper.xml
+++ b/yudao-module-mes/src/main/resources/mapper/home/MesHomeStatisticsMapper.xml
@@ -50,6 +50,27 @@
                         ${@cn.iocoder.yudao.module.mes.enums.dv.MesDvRepairStatusEnum@APPROVING.status})
     </select>
 
+    <!-- 鎸夋楠岀被鍨嬬粺璁¤川妫�鍗曠殑妫�楠岄噺涓庡悎鏍奸噺锛堜笉杩囨护鐘舵�侊紝鏃犳椂闂磋寖鍥撮檺鍒讹級 -->
+    <select id="selectQcQualifiedSummary" resultType="java.util.Map">
+        SELECT 'IQC'                               AS type,
+               IFNULL(SUM(check_quantity), 0)      AS checkQuantity,
+               IFNULL(SUM(qualified_quantity), 0)  AS qualifiedQuantity
+        FROM mes_qc_iqc
+        WHERE deleted = 0
+        UNION ALL
+        SELECT 'IPQC',
+               IFNULL(SUM(check_quantity), 0),
+               IFNULL(SUM(qualified_quantity), 0)
+        FROM mes_qc_ipqc
+        WHERE deleted = 0
+        UNION ALL
+        SELECT 'OQC',
+               IFNULL(SUM(check_quantity), 0),
+               IFNULL(SUM(qualified_quantity), 0)
+        FROM mes_qc_oqc
+        WHERE deleted = 0
+    </select>
+
     <!-- 鎸夊ぉ鑱氬悎鎶ュ伐浜ч噺 -->
     <select id="selectDailyFeedbackTrend" resultType="java.util.Map">
         SELECT DATE_FORMAT(feedback_time, '%Y-%m-%d')    AS date,

--
Gitblit v1.9.3