gongchunyi
2 天以前 940ceef4c4adf20c0b9da9270f1cd5ed58e14a4c
feat: 进销质量类分析大屏接口
已添加5个文件
已修改3个文件
839 ■■■■■ 文件已修改
src/main/java/com/ruoyi/home/controller/HomeController.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/CompletedInspectionCountDto.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/NonComplianceWarningDto.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/QualityInspectionCountDto.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/QualityQualifiedAnalysisDto.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/UnqualifiedProductRankDto.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/service/HomeService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java 548 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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)
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;
}
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;
    }
}
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;
}
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;
}
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;
}
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();
}
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: // å­£åº¦
                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;
    }
}