From 940ceef4c4adf20c0b9da9270f1cd5ed58e14a4c Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期一, 02 二月 2026 15:22:21 +0800
Subject: [PATCH] feat: 进销质量类分析大屏接口

---
 src/main/java/com/ruoyi/home/controller/HomeController.java       |   53 +++
 src/main/java/com/ruoyi/home/dto/CompletedInspectionCountDto.java |   37 ++
 src/main/java/com/ruoyi/home/dto/NonComplianceWarningDto.java     |   59 ++++
 src/main/java/com/ruoyi/home/dto/UnqualifiedProductRankDto.java   |   39 ++
 src/main/java/com/ruoyi/home/service/HomeService.java             |   16 +
 src/main/java/com/ruoyi/home/dto/QualityQualifiedAnalysisDto.java |   39 ++
 src/main/java/com/ruoyi/home/dto/QualityInspectionCountDto.java   |   48 +++
 src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java    |  548 ++++++++++++++++++++++++++++++++++++++
 8 files changed, 830 insertions(+), 9 deletions(-)

diff --git a/src/main/java/com/ruoyi/home/controller/HomeController.java b/src/main/java/com/ruoyi/home/controller/HomeController.java
index 8d72113..7cf3f95 100644
--- a/src/main/java/com/ruoyi/home/controller/HomeController.java
+++ b/src/main/java/com/ruoyi/home/controller/HomeController.java
@@ -202,6 +202,59 @@
         return AjaxResult.success(homeService.orderCount());
     }
 
+    @GetMapping("/rawMaterialDetection")
+    @ApiOperation("鍘熸潗鏂欐娴�")
+    public AjaxResult rawMaterialDetection(@RequestParam(value = "type", defaultValue = "1") Integer type){
+        return AjaxResult.success(homeService.rawMaterialDetection(type));
+    }
+
+    @GetMapping("/processDetection")
+    @ApiOperation("杩囩▼妫�娴�")
+    public AjaxResult processDetection(@RequestParam(value = "type", defaultValue = "1") Integer type){
+        return AjaxResult.success(homeService.processDetection(type));
+    }
+
+    @GetMapping("/factoryDetection")
+    @ApiOperation("鎴愬搧鍑哄巶妫�娴�")
+    public AjaxResult factoryDetection(@RequestParam(value = "type", defaultValue = "1") Integer type){
+        return AjaxResult.success(homeService.factoryDetection(type));
+    }
+
+    @GetMapping("/qualityInspectionCount")
+    @ApiOperation("璐ㄩ噺妫�楠屾暟閲�")
+    public AjaxResult qualityInspectionCount(){
+        QualityInspectionCountDto qualityInspectionCountDto = homeService.qualityInspectionCount();
+        return AjaxResult.success(qualityInspectionCountDto);
+    }
+
+    @GetMapping("/nonComplianceWarning")
+    @ApiOperation("涓嶅悎鏍奸璀�")
+    public AjaxResult nonComplianceWarning(){
+        NonComplianceWarningDto nonComplianceWarningDto = homeService.nonComplianceWarning();
+        return AjaxResult.success(nonComplianceWarningDto);
+    }
+
+    @GetMapping("/completedInspectionCount")
+    @ApiOperation("瀹屾垚妫�楠屾暟")
+    public AjaxResult completedInspectionCount(){
+        List<CompletedInspectionCountDto> list = homeService.completedInspectionCount();
+        return AjaxResult.success(list);
+    }
+
+    @GetMapping("/unqualifiedProductRanking")
+    @ApiOperation("涓嶅悎鏍间骇鍝佹帓鍚�")
+    public AjaxResult unqualifiedProductRanking(){
+        List<UnqualifiedProductRankDto> list = homeService.unqualifiedProductRanking();
+        return AjaxResult.success(list);
+    }
+
+    @GetMapping("/unqualifiedProductProcessingAnalysis")
+    @ApiOperation("涓嶅悎鏍兼鍝佸鐞嗗垎鏋�")
+    public AjaxResult unqualifiedProductProcessingAnalysis(){
+        List<MapDto> list = homeService.unqualifiedProductProcessingAnalysis();
+        return AjaxResult.success(list);
+    }
+
     /********************************************************钀ラ攢閲囪喘绫�**************************************************/
     @GetMapping("/business")
     @Log(title = "閿�鍞�-閲囪喘-搴撳瓨鏁版嵁", businessType = BusinessType.OTHER)
diff --git a/src/main/java/com/ruoyi/home/dto/CompletedInspectionCountDto.java b/src/main/java/com/ruoyi/home/dto/CompletedInspectionCountDto.java
new file mode 100644
index 0000000..68dd371
--- /dev/null
+++ b/src/main/java/com/ruoyi/home/dto/CompletedInspectionCountDto.java
@@ -0,0 +1,37 @@
+package com.ruoyi.home.dto;
+
+import java.math.BigDecimal;
+
+import lombok.Data;
+
+/**
+ * <br>
+ * 瀹屾垚妫�楠屾暟
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/2/2
+ */
+@Data
+public class CompletedInspectionCountDto {
+    /**
+     * 鏃ユ湡
+     */
+    private String dateStr;
+
+    /**
+     * 鍚堟牸鏁�
+     */
+    private BigDecimal qualifiedCount;
+
+    /**
+     * 涓嶅悎鏍兼暟
+     */
+    private BigDecimal unqualifiedCount;
+
+    /**
+     * 鍚堟牸鐜�
+     */
+    private BigDecimal passRate;
+}
diff --git a/src/main/java/com/ruoyi/home/dto/NonComplianceWarningDto.java b/src/main/java/com/ruoyi/home/dto/NonComplianceWarningDto.java
new file mode 100644
index 0000000..7dacafb
--- /dev/null
+++ b/src/main/java/com/ruoyi/home/dto/NonComplianceWarningDto.java
@@ -0,0 +1,59 @@
+package com.ruoyi.home.dto;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import lombok.Data;
+
+/**
+ * <br>
+ * 涓嶅悎鏍奸璀�
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/2/2
+ */
+@Data
+public class NonComplianceWarningDto {
+
+    /**
+     * 鍘熸潗鏂欏崰姣�
+     */
+    private BigDecimal rawMaterialRatio;
+
+    /**
+     * 鍗婃垚鍝佸崰姣�
+     */
+    private BigDecimal semiFinishedProductRatio;
+
+    /**
+     * 鎴愬搧鍗犳瘮
+     */
+    private BigDecimal finishedProductRatio;
+
+    /**
+     * 鏁版嵁
+     */
+
+    private List<Item> children;
+
+    @Data
+    public static class Item {
+        /**
+         * 浜у搧鏍囬
+         */
+        private String productTitle;
+
+        /**
+         * 鎻忚堪
+         */
+        private String description;
+
+        /**
+         * 鏃ユ湡
+         */
+        private String date;
+    }
+
+}
diff --git a/src/main/java/com/ruoyi/home/dto/QualityInspectionCountDto.java b/src/main/java/com/ruoyi/home/dto/QualityInspectionCountDto.java
new file mode 100644
index 0000000..74d5f81
--- /dev/null
+++ b/src/main/java/com/ruoyi/home/dto/QualityInspectionCountDto.java
@@ -0,0 +1,48 @@
+package com.ruoyi.home.dto;
+
+import java.math.BigDecimal;
+
+import lombok.Data;
+
+/**
+ * <br>
+ * 妫�楠屾暟閲�
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/2/2
+ */
+@Data
+public class QualityInspectionCountDto {
+
+    /**
+     * 鎬绘楠屾暟
+     */
+    private BigDecimal totalCount;
+
+    /**
+     * 鎬绘楠屾暟鍚屾瘮澧為暱
+     */
+    private BigDecimal totalCountGrowthRate;
+
+    /**
+     * 浠婃棩寰呭畬鎴愭暟
+     */
+    private BigDecimal todayPendingCount;
+
+    /**
+     * 浠婃棩寰呭畬鎴愭暟鍚屾瘮澧為暱
+     */
+    private BigDecimal todayPendingCountGrowthRate;
+
+    /**
+     * 浠婃棩宸插畬鎴愭暟
+     */
+    private BigDecimal todayCompletedCount;
+
+    /**
+     * 浠婃棩宸插畬鎴愭暟鍚屾瘮澧為暱
+     */
+    private BigDecimal todayCompletedCountGrowthRate;
+}
diff --git a/src/main/java/com/ruoyi/home/dto/QualityQualifiedAnalysisDto.java b/src/main/java/com/ruoyi/home/dto/QualityQualifiedAnalysisDto.java
new file mode 100644
index 0000000..c58887f
--- /dev/null
+++ b/src/main/java/com/ruoyi/home/dto/QualityQualifiedAnalysisDto.java
@@ -0,0 +1,39 @@
+package com.ruoyi.home.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * <br>
+ * 璐ㄩ噺鎸囨爣鍚堟牸鍒嗘瀽
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/2/2
+ */
+
+@Data
+public class QualityQualifiedAnalysisDto {
+
+    /**
+     * 鍚堟牸鏁�
+     */
+    private int qualifiedCount;
+
+    /**
+     * 涓嶅悎鏍兼暟
+     */
+    private int unqualifiedCount;
+
+    /**
+     * 鍚堟牸姣斾緥
+     */
+    private BigDecimal qualifiedRate;
+
+    /**
+     * 涓嶅悎鏍兼瘮渚�
+     */
+    private BigDecimal unqualifiedRate;
+}
diff --git a/src/main/java/com/ruoyi/home/dto/UnqualifiedProductRankDto.java b/src/main/java/com/ruoyi/home/dto/UnqualifiedProductRankDto.java
new file mode 100644
index 0000000..cd9eec1
--- /dev/null
+++ b/src/main/java/com/ruoyi/home/dto/UnqualifiedProductRankDto.java
@@ -0,0 +1,39 @@
+package com.ruoyi.home.dto;
+
+import java.math.BigDecimal;
+
+import lombok.Data;
+
+/**
+ * <br>
+ * 涓嶅悎鏍间骇鍝佹帓鍚�
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/2/2
+ */
+
+@Data
+public class UnqualifiedProductRankDto {
+
+    /**
+     * 浜у搧鍚嶇О
+     */
+    private String productName;
+
+    /**
+     * 鎬诲畬鎴愭暟閲�
+     */
+    private BigDecimal totalCount;
+
+    /**
+     * 宸插畬鎴愭暟閲�
+     */
+    private BigDecimal completedCount;
+
+    /**
+     * 鍚堟牸鐜�
+     */
+    private BigDecimal passRate;
+}
diff --git a/src/main/java/com/ruoyi/home/service/HomeService.java b/src/main/java/com/ruoyi/home/service/HomeService.java
index 77bbf6b..1b9da60 100644
--- a/src/main/java/com/ruoyi/home/service/HomeService.java
+++ b/src/main/java/com/ruoyi/home/service/HomeService.java
@@ -74,4 +74,20 @@
     List<ProductionAccountingDto> productionAccountingAnalysis(Integer type);
 
     List<MapDto> orderCount();
+
+    List<QualityQualifiedAnalysisDto> rawMaterialDetection(Integer type);
+
+    List<QualityQualifiedAnalysisDto> processDetection(Integer type);
+
+    List<QualityQualifiedAnalysisDto> factoryDetection(Integer type);
+
+    QualityInspectionCountDto qualityInspectionCount();
+
+    NonComplianceWarningDto nonComplianceWarning();
+
+    List<CompletedInspectionCountDto> completedInspectionCount();
+
+    List<UnqualifiedProductRankDto> unqualifiedProductRanking();
+
+    List<MapDto> unqualifiedProductProcessingAnalysis();
 }
diff --git a/src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java b/src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java
index a377159..7e96c10 100644
--- a/src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java
+++ b/src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java
@@ -20,6 +20,7 @@
 import com.ruoyi.collaborativeApproval.pojo.Notice;
 import com.ruoyi.common.enums.ApproveTypeEnum;
 import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.device.mapper.DeviceRepairMapper;
 import com.ruoyi.device.pojo.DeviceRepair;
 import com.ruoyi.dto.MapDto;
@@ -48,7 +49,9 @@
 import com.ruoyi.purchase.pojo.PaymentRegistration;
 import com.ruoyi.purchase.pojo.PurchaseLedger;
 import com.ruoyi.quality.mapper.QualityInspectMapper;
+import com.ruoyi.quality.mapper.QualityUnqualifiedMapper;
 import com.ruoyi.quality.pojo.QualityInspect;
+import com.ruoyi.quality.pojo.QualityUnqualified;
 import com.ruoyi.sales.mapper.ReceiptPaymentMapper;
 import com.ruoyi.sales.mapper.SalesLedgerMapper;
 import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
@@ -94,9 +97,6 @@
     private StockInventoryMapper stockInventoryMapper;
 
     @Autowired
-    private ProcurementRecordMapper procurementRecordStorageMapper;
-
-    @Autowired
     private QualityInspectMapper qualityStatisticsMapper;
 
     @Autowired
@@ -113,27 +113,36 @@
 
     @Autowired
     private NoticeMapper noticeMapper;
+
     @Autowired
     private ProductOrderMapper productOrderMapper;
+
     @Autowired
     private ProductWorkOrderMapper productWorkOrderMapper;
-    @Autowired
-    private ProductModelMapper productModelMapper;
+
     @Autowired
     private ProductMapper productMapper;
-    @Autowired
-    private StockUtils stockUtils;
+
     @Autowired
     private StaffOnJobMapper staffOnJobMapper;
+
     @Autowired
     private CustomerMapper customerMapper;
+
     @Autowired
     private SupplierManageMapper supplierManageMapper;
+
     @Autowired
     private HomeMapper homeMapper;
 
     @Autowired
     private ProductionProductOutputMapper productionProductOutputMapper;
+
+    @Autowired
+    private QualityInspectMapper qualityInspectMapper;
+
+    @Autowired
+    private QualityUnqualifiedMapper qualityUnqualifiedMapper;
 
     @Override
     public HomeBusinessDto business() {
@@ -1812,7 +1821,8 @@
 
         String endStr = endDate.plusDays(1).atStartOfDay().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
 
-        List<Map<String, Object>> wagesList = salesLedgerProductionAccountingMapper.selectDailyWagesStats(startStr, endStr);
+        List<Map<String, Object>> wagesList = salesLedgerProductionAccountingMapper.selectDailyWagesStats(startStr,
+                endStr);
 
         if (CollectionUtils.isEmpty(wagesList)) {
             return new ArrayList<>();
@@ -1882,4 +1892,524 @@
         return dto;
     }
 
-}
+
+    @Override
+    public List<QualityQualifiedAnalysisDto> rawMaterialDetection(Integer type) {
+        return commonDetection(type, 0);
+    }
+
+    @Override
+    public List<QualityQualifiedAnalysisDto> processDetection(Integer type) {
+        return commonDetection(type, 1);
+    }
+
+    @Override
+    public List<QualityQualifiedAnalysisDto> factoryDetection(Integer type) {
+        return commonDetection(type, 2);
+    }
+
+    private List<QualityQualifiedAnalysisDto> commonDetection(Integer type, Integer inspectType) {
+
+        LocalDate[] range = calcDateRange(type);
+        LocalDate startDate = range[0];
+        LocalDate endDate = range[1];
+
+        String startStr = startDate.atStartOfDay()
+                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+        String endStr = endDate.atTime(LocalTime.MAX)
+                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+
+        List<QualityInspect> list = qualityInspectMapper.selectList(
+                new LambdaQueryWrapper<QualityInspect>()
+                        .eq(QualityInspect::getInspectType, inspectType)
+                        .eq(QualityInspect::getInspectState, 1)
+                        .ge(QualityInspect::getCheckTime, startStr)
+                        .le(QualityInspect::getCheckTime, endStr));
+
+        return buildQualifiedAnalysis(list);
+    }
+
+    private LocalDate[] calcDateRange(Integer type) {
+        LocalDate today = LocalDate.now();
+        LocalDate startDate;
+        LocalDate endDate;
+
+        switch (type) {
+            case 1: // 鍛�
+                startDate = today.with(DayOfWeek.MONDAY);
+                endDate = today.with(DayOfWeek.SUNDAY);
+                break;
+            case 2: // 鏈�
+                startDate = today.with(TemporalAdjusters.firstDayOfMonth());
+                endDate = today.with(TemporalAdjusters.lastDayOfMonth());
+                break;
+            case 3: // 瀛e害
+                int currentMonth = today.getMonthValue();
+                int startMonth = ((currentMonth - 1) / 3) * 3 + 1;
+                startDate = LocalDate.of(today.getYear(), startMonth, 1);
+                endDate = LocalDate.of(today.getYear(), startMonth + 2, 1)
+                        .with(TemporalAdjusters.lastDayOfMonth());
+                break;
+            default:
+                startDate = today.with(DayOfWeek.MONDAY);
+                endDate = today.with(DayOfWeek.SUNDAY);
+        }
+
+        return new LocalDate[]{startDate, endDate};
+    }
+
+    private List<QualityQualifiedAnalysisDto> buildQualifiedAnalysis(List<QualityInspect> list) {
+        List<QualityQualifiedAnalysisDto> result = new ArrayList<>();
+        QualityQualifiedAnalysisDto dto = new QualityQualifiedAnalysisDto();
+
+        if (CollectionUtils.isEmpty(list)) {
+            dto.setQualifiedCount(0);
+            dto.setUnqualifiedCount(0);
+            dto.setQualifiedRate(BigDecimal.ZERO.setScale(2));
+            dto.setUnqualifiedRate(BigDecimal.ZERO.setScale(2));
+            result.add(dto);
+            return result;
+        }
+
+        BigDecimal qualifiedCount = BigDecimal.ZERO;
+        BigDecimal unqualifiedCount = BigDecimal.ZERO;
+
+        for (QualityInspect item : list) {
+            if ("鍚堟牸".equals(item.getCheckResult())) {
+                qualifiedCount = qualifiedCount.add(item.getQuantity());
+            } else {
+                unqualifiedCount = unqualifiedCount.add(item.getQuantity());
+            }
+        }
+
+        BigDecimal totalCount = qualifiedCount.add(unqualifiedCount);
+
+        dto.setQualifiedCount(qualifiedCount.intValue());
+        dto.setUnqualifiedCount(unqualifiedCount.intValue());
+
+        if (totalCount.compareTo(BigDecimal.ZERO) == 0) {
+            dto.setQualifiedRate(BigDecimal.ZERO.setScale(2));
+            dto.setUnqualifiedRate(BigDecimal.ZERO.setScale(2));
+            result.add(dto);
+            return result;
+        }
+
+        BigDecimal hundred = BigDecimal.valueOf(100);
+
+        dto.setQualifiedRate(qualifiedCount.divide(totalCount, 4, RoundingMode.HALF_UP)
+                .multiply(hundred)
+                .setScale(2, RoundingMode.HALF_UP));
+
+        dto.setUnqualifiedRate(unqualifiedCount.divide(totalCount, 4, RoundingMode.HALF_UP)
+                .multiply(hundred)
+                .setScale(2, RoundingMode.HALF_UP));
+
+        result.add(dto);
+        return result;
+    }
+
+    @Override
+    public QualityInspectionCountDto qualityInspectionCount() {
+        // 鑾峰彇浠婂ぉ鐨勫紑濮嬪拰缁撴潫鏃ユ湡,鍖呭惈鏃跺垎绉�
+        LocalDateTime todayStart = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0);
+        LocalDateTime todayEnd = LocalDateTime.now().withHour(23).withMinute(59).withSecond(59).withNano(0);
+        // 鑾峰彇鍓嶄竴澶╃殑寮�濮嬪拰缁撴潫鏃ユ湡,鍖呭惈鏃跺垎绉�
+        LocalDateTime prevStart = todayStart.minusDays(1);
+        LocalDateTime prevEnd = todayEnd.minusDays(1);
+        // 鏌ヨ鍑烘埅姝粖鏃ョ殑鎬绘楠屾暟
+        List<QualityInspect> todayList = qualityInspectMapper.selectList(new LambdaQueryWrapper<QualityInspect>()
+                // .eq(QualityInspect::getInspectState, 1)
+                .le(QualityInspect::getCheckTime, todayEnd));
+        // 鏌ヨ鍑烘埅姝㈠墠涓�澶╃殑鎬绘楠屾暟
+        List<QualityInspect> prevList = qualityInspectMapper.selectList(new LambdaQueryWrapper<QualityInspect>()
+                // .eq(QualityInspect::getInspectState, 1)
+                .le(QualityInspect::getCheckTime, prevEnd));
+        // 璁$畻浠婃棩鐨勬�绘楠屾暟
+        BigDecimal todayCount = todayList.stream()
+                .map(QualityInspect::getQuantity)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        // 璁$畻鍓嶄竴澶╃殑鎬绘楠屾暟
+        BigDecimal prevCount = prevList.stream()
+                .map(QualityInspect::getQuantity)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        // 璁$畻浠婃棩鐩稿鏄ㄥぉ鐨勪竴涓�绘瘮澧為暱
+        BigDecimal growthRate = calcGrowthRate(todayCount, prevCount);
+
+        // 璁$畻浠婂ぉ鐨勫緟瀹屾垚鏁伴噺
+        List<QualityInspect> todayPendingList = qualityInspectMapper.selectList(new LambdaQueryWrapper<QualityInspect>()
+                .eq(QualityInspect::getInspectState, 0)
+                .ge(QualityInspect::getCheckTime, todayStart)
+                .le(QualityInspect::getCheckTime, todayEnd));
+
+        // 璁$畻鍓嶄竴澶╃殑寰呭畬鎴愭暟閲�
+        List<QualityInspect> prevPendingList = qualityInspectMapper.selectList(new LambdaQueryWrapper<QualityInspect>()
+                .eq(QualityInspect::getInspectState, 0)
+                .ge(QualityInspect::getCheckTime, prevStart)
+                .le(QualityInspect::getCheckTime, prevEnd));
+        // 璁$畻浠婂ぉ鐨勫緟瀹屾垚鏁伴噺
+        BigDecimal todayPendingCount = todayPendingList.stream()
+                .map(QualityInspect::getQuantity)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        // 璁$畻鍓嶄竴澶╃殑寰呭畬鎴愭暟閲�
+        BigDecimal prevPendingCount = prevPendingList.stream()
+                .map(QualityInspect::getQuantity)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        // 璁$畻浠婂ぉ鐨勫緟瀹屾垚鏁伴噺鐩稿鏄ㄥぉ鐨勪竴涓悓姣斿闀�
+        BigDecimal todayPendingCountGrowthRate = calcGrowthRate(todayPendingCount, prevPendingCount);
+        // 璁$畻浠婂ぉ鐨勫凡瀹屾垚鏁伴噺
+        List<QualityInspect> todayCompletedList = qualityInspectMapper
+                .selectList(new LambdaQueryWrapper<QualityInspect>()
+                        .eq(QualityInspect::getInspectState, 1)
+                        .ge(QualityInspect::getCheckTime, todayStart)
+                        .le(QualityInspect::getCheckTime, todayEnd));
+        // 璁$畻鍓嶄竴澶╃殑宸插畬鎴愭暟閲�
+        List<QualityInspect> prevCompletedList = qualityInspectMapper
+                .selectList(new LambdaQueryWrapper<QualityInspect>()
+                        .eq(QualityInspect::getInspectState, 1)
+                        .ge(QualityInspect::getCheckTime, prevStart)
+                        .le(QualityInspect::getCheckTime, prevEnd));
+        // 璁$畻浠婂ぉ鐨勫凡瀹屾垚鏁伴噺
+        BigDecimal todayCompletedCount = todayCompletedList.stream()
+                .map(QualityInspect::getQuantity)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        // 璁$畻鍓嶄竴澶╃殑宸插畬鎴愭暟閲�
+        BigDecimal prevCompletedCount = prevCompletedList.stream()
+                .map(QualityInspect::getQuantity)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        // 璁$畻浠婂ぉ鐨勫凡瀹屾垚鏁伴噺鐩稿鏄ㄥぉ鐨勪竴涓悓姣斿闀�
+        BigDecimal todayCompletedCountGrowthRate = calcGrowthRate(todayCompletedCount, prevCompletedCount);
+        QualityInspectionCountDto dto = new QualityInspectionCountDto();
+        dto.setTotalCount(todayCount);
+        dto.setTotalCountGrowthRate(growthRate);
+        dto.setTodayPendingCount(todayPendingCount);
+        dto.setTodayPendingCountGrowthRate(todayPendingCountGrowthRate);
+        dto.setTodayCompletedCount(todayCompletedCount);
+        dto.setTodayCompletedCountGrowthRate(todayCompletedCountGrowthRate);
+        return dto;
+    }
+
+    private BigDecimal calcGrowthRate(BigDecimal today, BigDecimal prev) {
+        if (prev == null || prev.compareTo(BigDecimal.ZERO) == 0) {
+            return BigDecimal.ZERO.setScale(2);
+        }
+        return today.subtract(prev)
+                .divide(prev, 4, RoundingMode.HALF_UP)
+                .multiply(BigDecimal.valueOf(100))
+                .setScale(2, RoundingMode.HALF_UP);
+    }
+
+    @Override
+    public NonComplianceWarningDto nonComplianceWarning() {
+
+        // 杩戜竷澶╂椂闂村尯闂�
+        LocalDateTime[] range = lastSevenDaysRange();
+        LocalDateTime startTime = range[0];
+        LocalDateTime endTime = range[1];
+
+        // 鏌ヨ杩戜竷澶╁凡澶勭悊涓嶅悎鏍兼暟鎹�
+        List<QualityUnqualified> list = qualityUnqualifiedMapper.selectList(
+                new LambdaQueryWrapper<QualityUnqualified>()
+                        .eq(QualityUnqualified::getInspectState, 1)
+                        .ge(QualityUnqualified::getCheckTime, startTime)
+                        .le(QualityUnqualified::getCheckTime, endTime));
+
+        NonComplianceWarningDto dto = new NonComplianceWarningDto();
+
+        if (CollectionUtils.isEmpty(list)) {
+            dto.setRawMaterialRatio(BigDecimal.ZERO);
+            dto.setSemiFinishedProductRatio(BigDecimal.ZERO);
+            dto.setFinishedProductRatio(BigDecimal.ZERO);
+            dto.setChildren(new ArrayList<>());
+            return dto;
+        }
+
+        // 鏌ヨ鎵�鏈変骇鍝�
+        List<Product> products = productMapper.selectList(null);
+
+        Map<Long, Product> productMap = products.stream()
+                .collect(Collectors.toMap(Product::getId, p -> p));
+
+        BigDecimal rawMaterialCount = BigDecimal.ZERO;
+        BigDecimal semiFinishedCount = BigDecimal.ZERO;
+        BigDecimal finishedCount = BigDecimal.ZERO;
+
+        List<NonComplianceWarningDto.Item> children = new ArrayList<>();
+
+        for (QualityUnqualified item : list) {
+
+            BigDecimal quantity = item.getQuantity();
+            Long productId = item.getProductId();
+
+            Product product = productMap.get(productId);
+            if (product == null) {
+                continue;
+            }
+
+            // 鎵惧埌浜у搧澶х被
+            Product parent = product.getParentId() == null
+                    ? product
+                    : productMap.get(product.getParentId());
+
+            if (parent == null) {
+                continue;
+            }
+
+            switch (parent.getProductName()) {
+                case "鍘熸潗鏂�":
+                    rawMaterialCount = rawMaterialCount.add(quantity);
+                    break;
+                case "鍗婃垚鍝�":
+                    semiFinishedCount = semiFinishedCount.add(quantity);
+                    break;
+                case "鎴愬搧":
+                    finishedCount = finishedCount.add(quantity);
+                    break;
+                default:
+                    break;
+            }
+
+            // 缁勮鏄庣粏
+            NonComplianceWarningDto.Item child = new NonComplianceWarningDto.Item();
+            // child.setProductTitle(item.getProductName());
+            child.setProductTitle(parent.getProductName());
+            child.setDescription(item.getDefectivePhenomena());
+            child.setDate(formatDate(item.getCheckTime()));
+            children.add(child);
+        }
+
+        BigDecimal total = rawMaterialCount
+                .add(semiFinishedCount)
+                .add(finishedCount);
+
+        dto.setRawMaterialRatio(calcRate(rawMaterialCount, total));
+        dto.setSemiFinishedProductRatio(calcRate(semiFinishedCount, total));
+        dto.setFinishedProductRatio(calcRate(finishedCount, total));
+        dto.setChildren(children);
+
+        return dto;
+    }
+
+    private BigDecimal calcRate(BigDecimal part, BigDecimal total) {
+        if (total == null || total.compareTo(BigDecimal.ZERO) == 0) {
+            return BigDecimal.ZERO.setScale(2);
+        }
+        return part.divide(total, 4, RoundingMode.HALF_UP)
+                .multiply(BigDecimal.valueOf(100))
+                .setScale(2, RoundingMode.HALF_UP);
+    }
+
+    public static String formatDate(Date date) {
+        if (date == null) {
+            return null;
+        }
+        return date.toInstant()
+                .atZone(ZoneId.systemDefault())
+                .toLocalDate()
+                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+    }
+
+    /**
+     * 鑾峰彇杩戜竷澶╃殑鏃堕棿鍖洪棿锛堝寘鍚粖澶╋級
+     */
+    public static LocalDateTime[] lastSevenDaysRange() {
+        LocalDate today = LocalDate.now();
+
+        LocalDateTime startTime = today.minusDays(6).atStartOfDay();
+        LocalDateTime endTime = today.atTime(23, 59, 59);
+
+        return new LocalDateTime[]{startTime, endTime};
+    }
+
+    @Override
+    public List<CompletedInspectionCountDto> completedInspectionCount() {
+        // 杩戜竷澶╂椂闂村尯闂�
+        LocalDateTime[] range = lastSevenDaysRange();
+        LocalDateTime startTime = range[0];
+        LocalDateTime endTime = range[1];
+
+        // 鏌ヨ杩戜竷澶╁凡瀹屾垚鐨勬楠屾暟鎹�
+        List<QualityInspect> list = qualityInspectMapper.selectList(new LambdaQueryWrapper<QualityInspect>()
+                .eq(QualityInspect::getInspectState, 1)
+                .ge(QualityInspect::getCheckTime, startTime)
+                .le(QualityInspect::getCheckTime, endTime));
+
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd");
+
+        Map<String, CompletedInspectionCountDto> resultMap = new LinkedHashMap<>();
+
+        for (int i = 6; i >= 0; i--) {
+            LocalDate date = LocalDate.now().minusDays(i);
+            String dateStr = date.format(formatter);
+
+            CompletedInspectionCountDto dto = new CompletedInspectionCountDto();
+            dto.setDateStr(dateStr);
+            dto.setQualifiedCount(BigDecimal.ZERO);
+            dto.setUnqualifiedCount(BigDecimal.ZERO);
+            dto.setPassRate(BigDecimal.ZERO);
+
+            resultMap.put(dateStr, dto);
+        }
+
+        // 绱姞鍚堟牸 / 涓嶅悎鏍兼暟閲�
+        for (QualityInspect item : list) {
+
+            String dateStr = item.getCheckTime()
+                    .toInstant()
+                    .atZone(ZoneId.systemDefault())
+                    .toLocalDate()
+                    .format(formatter);
+
+            CompletedInspectionCountDto dto = resultMap.get(dateStr);
+            if (dto == null) {
+                continue;
+            }
+
+            BigDecimal quantity = item.getQuantity();
+
+            if ("鍚堟牸".equals(item.getCheckResult())) {
+                dto.setQualifiedCount(dto.getQualifiedCount().add(quantity));
+            } else {
+                dto.setUnqualifiedCount(dto.getUnqualifiedCount().add(quantity));
+            }
+        }
+
+        // 璁$畻鍚堟牸鐜�
+        for (CompletedInspectionCountDto dto : resultMap.values()) {
+            BigDecimal total = dto.getQualifiedCount().add(dto.getUnqualifiedCount());
+            dto.setPassRate(calcRate(dto.getQualifiedCount(), total));
+        }
+
+        return new ArrayList<>(resultMap.values());
+    }
+
+    @Override
+    public List<UnqualifiedProductRankDto> unqualifiedProductRanking() {
+
+        List<QualityInspect> list = qualityInspectMapper.selectList(new LambdaQueryWrapper<QualityInspect>()
+                .eq(QualityInspect::getInspectState, 1));
+
+        if (CollectionUtils.isEmpty(list)) {
+            return new ArrayList<>();
+        }
+
+        Map<Long, String> productNameMap = productMapper.selectList(null)
+                .stream()
+                .collect(Collectors.toMap(Product::getId, Product::getProductName));
+
+        Map<Long, List<QualityInspect>> groupMap = list.stream()
+                .collect(Collectors.groupingBy(QualityInspect::getProductId));
+
+        List<UnqualifiedProductRankDto> resultList = new ArrayList<>();
+
+        for (Map.Entry<Long, List<QualityInspect>> entry : groupMap.entrySet()) {
+
+            Long productId = entry.getKey();
+            List<QualityInspect> items = entry.getValue();
+
+            BigDecimal totalCount = BigDecimal.ZERO;
+            BigDecimal qualifiedCount = BigDecimal.ZERO;
+            BigDecimal unqualifiedCount = BigDecimal.ZERO;
+
+            for (QualityInspect item : items) {
+                BigDecimal qty = item.getQuantity();
+                totalCount = totalCount.add(qty);
+
+                if ("鍚堟牸".equals(item.getCheckResult())) {
+                    qualifiedCount = qualifiedCount.add(qty);
+                } else {
+                    unqualifiedCount = unqualifiedCount.add(qty);
+                }
+            }
+
+            if (totalCount.compareTo(BigDecimal.ZERO) == 0) {
+                continue;
+            }
+
+            BigDecimal passRate = qualifiedCount
+                    .divide(totalCount, 4, RoundingMode.HALF_UP)
+                    .multiply(new BigDecimal("100"))
+                    .setScale(2, RoundingMode.HALF_UP);
+
+            UnqualifiedProductRankDto dto = new UnqualifiedProductRankDto();
+            dto.setProductName(productNameMap.get(productId));
+            dto.setTotalCount(totalCount);
+            dto.setCompletedCount(qualifiedCount);
+            dto.setPassRate(passRate);
+
+            resultList.add(dto);
+        }
+
+        resultList.sort(Comparator.comparing(UnqualifiedProductRankDto::getPassRate));
+
+        return resultList.stream().limit(5).collect(Collectors.toList());
+    }
+
+    @Override
+    public List<MapDto> unqualifiedProductProcessingAnalysis() {
+
+        List<QualityUnqualified> list = qualityUnqualifiedMapper.selectList(null);
+        if (CollectionUtils.isEmpty(list)) {
+            return new ArrayList<>();
+        }
+
+        //  缁熻姣忕澶勭悊缁撴灉鐨勬暟閲�
+        Map<String, BigDecimal> countMap = new HashMap<>();
+        for (QualityUnqualified item : list) {
+            if (StringUtils.isEmpty(item.getDealResult()) || item.getQuantity() == null) {
+                continue;
+            }
+            countMap.merge(item.getDealResult(), item.getQuantity(), BigDecimal::add);
+        }
+
+        if (countMap.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        //  璁$畻鎬绘暟
+        BigDecimal totalCount = countMap.values()
+                .stream()
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        if (totalCount.compareTo(BigDecimal.ZERO) == 0) {
+            return new ArrayList<>();
+        }
+
+        //  鎸夋暟閲忓�掑簭鎺掑簭
+        List<Map.Entry<String, BigDecimal>> sortedList = countMap.entrySet()
+                .stream()
+                .sorted((a, b) -> b.getValue().compareTo(a.getValue()))
+                .collect(Collectors.toList());
+
+        List<MapDto> result = new ArrayList<>();
+
+        int limit = Math.min(3, sortedList.size());
+        BigDecimal otherCount = BigDecimal.ZERO;
+
+        for (int i = 0; i < sortedList.size(); i++) {
+            Map.Entry<String, BigDecimal> entry = sortedList.get(i);
+
+            if (i < limit) {
+                MapDto dto = new MapDto();
+                dto.setName(entry.getKey());
+                dto.setValue(entry.getValue().setScale(2, RoundingMode.HALF_UP).toPlainString());
+                dto.setRate(calcRate(entry.getValue(), totalCount).toString());
+                result.add(dto);
+            } else {
+                otherCount = otherCount.add(entry.getValue());
+            }
+        }
+
+        if (otherCount.compareTo(BigDecimal.ZERO) > 0) {
+            MapDto otherDto = new MapDto();
+            otherDto.setName("鍏朵粬");
+            otherDto.setValue(otherCount.setScale(2, RoundingMode.HALF_UP).toPlainString());
+            otherDto.setRate(calcRate(otherCount, totalCount).toString());
+            result.add(otherDto);
+        }
+
+        return result;
+    }
+
+}
\ No newline at end of file

--
Gitblit v1.9.3