From 5b56af3a13d7f36ad0d5b24fac83d14d7b622954 Mon Sep 17 00:00:00 2001
From: “mitao” <“2763622819@qq.com”>
Date: 星期三, 21 一月 2026 21:06:30 +0800
Subject: [PATCH] feat:新增委托方大屏首页接口

---
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/ClientServiceImpl.java |  495 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 495 insertions(+), 0 deletions(-)

diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/ClientServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/ClientServiceImpl.java
index b760b87..76ffe50 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/ClientServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/ClientServiceImpl.java
@@ -1,11 +1,37 @@
 package com.ruoyi.system.service.impl;
 
 
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.system.mapper.CheckMapper;
 import com.ruoyi.system.mapper.ClientMapper;
+import com.ruoyi.system.mapper.DeviceUsageMapper;
+import com.ruoyi.system.mapper.PlanMapper;
+import com.ruoyi.system.mapper.ProjectMapper;
+import com.ruoyi.system.mapper.RecordMapper;
+import com.ruoyi.system.model.Check;
 import com.ruoyi.system.model.Client;
+import com.ruoyi.system.model.DeviceUsage;
+import com.ruoyi.system.model.Plan;
+import com.ruoyi.system.model.Project;
+import com.ruoyi.system.model.Record;
 import com.ruoyi.system.service.ClientService;
+import com.ruoyi.system.service.ProjectCategoryCalculator;
+import com.ruoyi.system.vo.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.time.LocalDate;
+import java.time.YearMonth;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * <p>
@@ -16,6 +42,475 @@
  * @since 2026-01-12
  */
 @Service
+@RequiredArgsConstructor
+@Slf4j
 public class ClientServiceImpl extends ServiceImpl<ClientMapper, Client> implements ClientService {
 
+    private final ProjectMapper projectMapper;
+    private final PlanMapper planMapper;
+    private final CheckMapper checkMapper;
+    private final DeviceUsageMapper deviceUsageMapper;
+    private final RecordMapper recordMapper;
+    private final ProjectCategoryCalculator projectCategoryCalculator;
+
+    @Override
+    public ClientDashboardVO getClientDashboard(ClientDashboardQuery query) {
+        ClientDashboardVO vo = new ClientDashboardVO();
+        vo.setProjectCategory(query.getProjectCategory());
+
+        // 获取委托方ID(如果未传则需要从当前登录用户获取,这里暂时使用传入的值)
+        Long clientId = query.getClientId();
+
+        // 获取日期范围
+        Date startDate = getStartDate(query.getStartDate());
+        Date endDate = getEndDate(query.getEndDate());
+
+        // 根据项目类型进行不同的统计
+        if ("ongoing".equals(query.getProjectCategory())) {
+            // 在建工程统计
+            OngoingProjectDashboardVO ongoingVO = calculateOngoingProjectStatistics(clientId, startDate, endDate);
+            vo.setOngoingProject(ongoingVO);
+        } else if ("fixed".equals(query.getProjectCategory())) {
+            // 固定资产统计
+            FixedAssetDashboardVO fixedVO = calculateFixedAssetStatistics(clientId, startDate, endDate);
+            vo.setFixedAsset(fixedVO);
+        }
+
+        return vo;
+    }
+
+    /**
+     * 在建工程统计
+     */
+    private OngoingProjectDashboardVO calculateOngoingProjectStatistics(Long clientId, Date monthStart, Date monthEnd) {
+        OngoingProjectDashboardVO vo = new OngoingProjectDashboardVO();
+
+        // 查询在建工程项目(usage_type 表示项目用途类型,假设 0 表示在建工程)
+        LambdaQueryWrapper<Project> projectWrapper = new LambdaQueryWrapper<>();
+        projectWrapper.eq(Project::getDeleted, false)
+                .eq(Project::getUsageType, 0); // 在建工程
+        if (clientId != null) {
+            projectWrapper.eq(Project::getClientId, clientId);
+        }
+        List<Project> projects = projectMapper.selectList(projectWrapper);
+
+        // 1. 统计项目总数及分类
+        vo.setProjectTotal(projects.size());
+        long housingCount = projects.stream().filter(p -> p.getType() != null && p.getType() == 0).count();
+        long municipalCount = projects.stream().filter(p -> p.getType() != null && p.getType() == 1).count();
+        vo.setHousingProjectCount((int) housingCount);
+        vo.setMunicipalProjectCount((int) municipalCount);
+
+        // 计算检查覆盖率
+        List<Integer> projectIds = projects.stream().map(Project::getId).collect(Collectors.toList());
+        if (!projectIds.isEmpty()) {
+            LambdaQueryWrapper<Check> checkWrapper = new LambdaQueryWrapper<>();
+            checkWrapper.in(Check::getProjectId, projectIds);
+            List<Check> checks = checkMapper.selectList(checkWrapper);
+
+            List<Integer> checkedProjectIds = checks.stream()
+                    .map(Check::getProjectId)
+                    .distinct()
+                    .collect(Collectors.toList());
+
+            // 房建检查覆盖率
+            List<Integer> housingProjectIds = projects.stream()
+                    .filter(p -> p.getType() != null && p.getType() == 0)
+                    .map(Project::getId)
+                    .toList();
+            long checkedHousingCount = checkedProjectIds.stream()
+                    .filter(housingProjectIds::contains)
+                    .count();
+            vo.setHousingInspectionCoverage(
+                    housingCount > 0 ? (int) Math.round(checkedHousingCount * 100.0 / housingCount) : 0
+            );
+
+            // 市政检查覆盖率
+            List<Integer> municipalProjectIds = projects.stream()
+                    .filter(p -> p.getType() != null && p.getType() == 1)
+                    .map(Project::getId)
+                    .toList();
+            long checkedMunicipalCount = checkedProjectIds.stream()
+                    .filter(municipalProjectIds::contains)
+                    .count();
+            vo.setMunicipalInspectionCoverage(
+                    municipalCount > 0 ? (int) Math.round(checkedMunicipalCount * 100.0 / municipalCount) : 0
+            );
+        } else {
+            vo.setHousingInspectionCoverage(0);
+            vo.setMunicipalInspectionCoverage(0);
+        }
+
+        // 2. 统计本月检查情况
+        calculateMonthlyInspectionForOngoing(vo, projects, projectIds, monthStart, monthEnd);
+
+        // 3. 统计大型机械设备数
+        vo.setMachineryStatistics(calculateMachineryStatistics(projectIds));
+
+        // 4. 统计重大隐患数
+        calculateHazardStatistics(vo, projectIds);
+
+        // 5. 获取地图标点数据
+        vo.setProjectMapPoints(getProjectMapPoints(projects, clientId, monthStart, monthEnd));
+
+        return vo;
+    }
+
+    /**
+     * 固定资产统计
+     */
+    private FixedAssetDashboardVO calculateFixedAssetStatistics(Long clientId, Date monthStart, Date monthEnd) {
+        FixedAssetDashboardVO vo = new FixedAssetDashboardVO();
+
+        // 查询固定资产项目(usage_type 表示项目用途类型,假设 1 表示固定资产)
+        LambdaQueryWrapper<Project> projectWrapper = new LambdaQueryWrapper<>();
+        projectWrapper.eq(Project::getDeleted, false)
+                .eq(Project::getUsageType, 1); // 固定资产
+        if (clientId != null) {
+            projectWrapper.eq(Project::getClientId, clientId);
+        }
+        List<Project> projects = projectMapper.selectList(projectWrapper);
+
+        // 1. 统计项目总数
+        vo.setProjectTotal(projects.size());
+
+        // 计算检查覆盖率
+        List<Integer> projectIds = projects.stream().map(Project::getId).collect(Collectors.toList());
+        if (!projectIds.isEmpty()) {
+            LambdaQueryWrapper<Check> checkWrapper = new LambdaQueryWrapper<>();
+            checkWrapper.in(Check::getProjectId, projectIds);
+            List<Check> checks = checkMapper.selectList(checkWrapper);
+
+            long checkedProjectCount = checks.stream()
+                    .map(Check::getProjectId)
+                    .distinct()
+                    .count();
+            vo.setInspectionCoverage(
+                    projects.size() > 0 ? (int) Math.round(checkedProjectCount * 100.0 / projects.size()) : 0
+            );
+        } else {
+            vo.setInspectionCoverage(0);
+        }
+
+        // 2. 统计本月检查情况
+        calculateMonthlyInspectionForFixed(vo, projects, projectIds, monthStart, monthEnd);
+
+        // 3. 统计重大隐患数
+        calculateHazardStatisticsForFixed(vo, projectIds);
+
+        // 4. 获取地图标点数据
+        vo.setProjectMapPoints(getProjectMapPoints(projects, clientId, monthStart, monthEnd));
+
+        return vo;
+    }
+
+    /**
+     * 统计在建工程本月检查情况
+     */
+    private void calculateMonthlyInspectionForOngoing(OngoingProjectDashboardVO vo, List<Project> projects,
+                                                      List<Integer> projectIds, Date monthStart, Date monthEnd) {
+        if (projectIds.isEmpty()) {
+            vo.setInspectedCount(0);
+            vo.setInspectedHousingCount(0);
+            vo.setInspectedMunicipalCount(0);
+            vo.setUninspectedCount(projects.size());
+            vo.setUninspectedHousingCount(vo.getHousingProjectCount());
+            vo.setUninspectedMunicipalCount(vo.getMunicipalProjectCount());
+            return;
+        }
+
+        // 查询本月有检查记录的项目
+        LambdaQueryWrapper<Check> checkWrapper = new LambdaQueryWrapper<>();
+        checkWrapper.in(Check::getProjectId, projectIds)
+                .ge(Check::getTime, monthStart)
+                .le(Check::getTime, monthEnd);
+        List<Check> monthlyChecks = checkMapper.selectList(checkWrapper);
+
+        List<Integer> inspectedProjectIds = monthlyChecks.stream()
+                .map(Check::getProjectId)
+                .distinct()
+                .toList();
+
+        // 已检查项目统计
+        List<Project> inspectedProjects = projects.stream()
+                .filter(p -> inspectedProjectIds.contains(p.getId()))
+                .toList();
+
+        vo.setInspectedCount(inspectedProjects.size());
+        vo.setInspectedHousingCount((int) inspectedProjects.stream()
+                .filter(p -> p.getType() != null && p.getType() == 0).count());
+        vo.setInspectedMunicipalCount((int) inspectedProjects.stream()
+                .filter(p -> p.getType() != null && p.getType() == 1).count());
+
+        // 未检查项目统计
+        List<Project> uninspectedProjects = projects.stream()
+                .filter(p -> !inspectedProjectIds.contains(p.getId()))
+                .toList();
+
+        vo.setUninspectedCount(uninspectedProjects.size());
+        vo.setUninspectedHousingCount((int) uninspectedProjects.stream()
+                .filter(p -> p.getType() != null && p.getType() == 0).count());
+        vo.setUninspectedMunicipalCount((int) uninspectedProjects.stream()
+                .filter(p -> p.getType() != null && p.getType() == 1).count());
+    }
+
+    /**
+     * 统计固定资产本月检查情况
+     */
+    private void calculateMonthlyInspectionForFixed(FixedAssetDashboardVO vo, List<Project> projects,
+                                                    List<Integer> projectIds, Date monthStart, Date monthEnd) {
+        if (projectIds.isEmpty()) {
+            vo.setInspectedCount(0);
+            vo.setUninspectedCount(projects.size());
+            return;
+        }
+
+        // 查询本月有检查记录的项目
+        LambdaQueryWrapper<Check> checkWrapper = new LambdaQueryWrapper<>();
+        checkWrapper.in(Check::getProjectId, projectIds)
+                .ge(Check::getTime, monthStart)
+                .le(Check::getTime, monthEnd);
+        List<Check> monthlyChecks = checkMapper.selectList(checkWrapper);
+
+        long inspectedCount = monthlyChecks.stream()
+                .map(Check::getProjectId)
+                .distinct()
+                .count();
+
+        vo.setInspectedCount((int) inspectedCount);
+        vo.setUninspectedCount(projects.size() - (int) inspectedCount);
+    }
+
+    /**
+     * 统计大型机械设备数(含已检查/未检查)
+     */
+    private List<MachineryStatisticsVO> calculateMachineryStatistics(List<Integer> projectIds) {
+        if (projectIds.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 查询设备使用记录
+        LambdaQueryWrapper<DeviceUsage> deviceWrapper = new LambdaQueryWrapper<>();
+        deviceWrapper.eq(DeviceUsage::getDeleted, false)
+                .in(DeviceUsage::getProjectId, projectIds);
+        List<DeviceUsage> devices = deviceUsageMapper.selectList(deviceWrapper);
+
+        // 按设备名称分组统计
+        Map<String, List<DeviceUsage>> deviceGroupMap = devices.stream()
+                .filter(d -> d.getName() != null && !d.getName().isEmpty())
+                .collect(Collectors.groupingBy(DeviceUsage::getName));
+
+        // 查询设备检查记录
+        List<Integer> deviceIds = devices.stream().map(DeviceUsage::getId).collect(Collectors.toList());
+        Map<Integer, Boolean> deviceInspectionMap = new HashMap<>();
+        if (!deviceIds.isEmpty()) {
+            LambdaQueryWrapper<Record> recordWrapper = new LambdaQueryWrapper<>();
+            recordWrapper.eq(Record::getDeleted, false)
+                    .in(Record::getDeviceId, deviceIds);
+            List<Record> records = recordMapper.selectList(recordWrapper);
+
+            Set<Integer> inspectedDeviceIds = records.stream()
+                    .map(Record::getDeviceId)
+                    .collect(Collectors.toSet());
+
+            for (Integer deviceId : deviceIds) {
+                deviceInspectionMap.put(deviceId, inspectedDeviceIds.contains(deviceId));
+            }
+        }
+
+        // 构建统计结果
+        List<MachineryStatisticsVO> result = new ArrayList<>();
+        for (Map.Entry<String, List<DeviceUsage>> entry : deviceGroupMap.entrySet()) {
+            MachineryStatisticsVO vo = new MachineryStatisticsVO();
+            vo.setName(entry.getKey());
+            vo.setTotal(entry.getValue().size());
+
+            long inspectedCount = entry.getValue().stream()
+                    .filter(d -> deviceInspectionMap.getOrDefault(d.getId(), false))
+                    .count();
+            vo.setInspectedCount((int) inspectedCount);
+            vo.setUninspectedCount(entry.getValue().size() - (int) inspectedCount);
+
+            result.add(vo);
+        }
+
+        return result;
+    }
+
+    /**
+     * 统计在建工程重大隐患数
+     */
+    private void calculateHazardStatistics(OngoingProjectDashboardVO vo, List<Integer> projectIds) {
+        if (projectIds.isEmpty()) {
+            vo.setMajorHazardTotal(0);
+            vo.setRectifiedHazardCount(0);
+            vo.setUnrectifiedHazardCount(0);
+            return;
+        }
+
+        // 查询检查任务
+        LambdaQueryWrapper<Check> checkWrapper = new LambdaQueryWrapper<>();
+        checkWrapper.in(Check::getProjectId, projectIds);
+        List<Check> checks = checkMapper.selectList(checkWrapper);
+        List<Integer> checkTaskIds = checks.stream().map(Check::getId).collect(Collectors.toList());
+
+        if (checkTaskIds.isEmpty()) {
+            vo.setMajorHazardTotal(0);
+            vo.setRectifiedHazardCount(0);
+            vo.setUnrectifiedHazardCount(0);
+            return;
+        }
+
+        // 查询重大隐患记录(level 表示隐患等级,假设 3 表示重大隐患)
+        LambdaQueryWrapper<Record> recordWrapper = new LambdaQueryWrapper<>();
+        recordWrapper.eq(Record::getDeleted, false)
+                .in(Record::getCheckTaskId, checkTaskIds)
+                .eq(Record::getLevel, 3); // 重大隐患等级
+        List<Record> majorHazards = recordMapper.selectList(recordWrapper);
+
+        vo.setMajorHazardTotal(majorHazards.size());
+
+        // 统计已整改和未整改数量
+        long rectifiedCount = majorHazards.stream()
+                .filter(r -> r.getReformed() != null && r.getReformed())
+                .count();
+        long unrectifiedCount = majorHazards.stream()
+                .filter(r -> r.getReformed() == null || !r.getReformed())
+                .count();
+
+        vo.setRectifiedHazardCount((int) rectifiedCount);
+        vo.setUnrectifiedHazardCount((int) unrectifiedCount);
+    }
+
+    /**
+     * 统计固定资产重大隐患数
+     */
+    private void calculateHazardStatisticsForFixed(FixedAssetDashboardVO vo, List<Integer> projectIds) {
+        if (projectIds.isEmpty()) {
+            vo.setMajorHazardTotal(0);
+            vo.setRectifiedHazardCount(0);
+            vo.setUnrectifiedHazardCount(0);
+            return;
+        }
+
+        // 查询检查任务
+        LambdaQueryWrapper<Check> checkWrapper = new LambdaQueryWrapper<>();
+        checkWrapper.in(Check::getProjectId, projectIds);
+        List<Check> checks = checkMapper.selectList(checkWrapper);
+        List<Integer> checkTaskIds = checks.stream().map(Check::getId).collect(Collectors.toList());
+
+        if (checkTaskIds.isEmpty()) {
+            vo.setMajorHazardTotal(0);
+            vo.setRectifiedHazardCount(0);
+            vo.setUnrectifiedHazardCount(0);
+            return;
+        }
+
+        // 查询重大隐患记录(level 表示隐患等级,假设 3 表示重大隐患)
+        LambdaQueryWrapper<Record> recordWrapper = new LambdaQueryWrapper<>();
+        recordWrapper.eq(Record::getDeleted, false)
+                .in(Record::getCheckTaskId, checkTaskIds)
+                .eq(Record::getLevel, 3); // 重大隐患等级
+        List<Record> majorHazards = recordMapper.selectList(recordWrapper);
+
+        vo.setMajorHazardTotal(majorHazards.size());
+
+        // 统计已整改和未整改数量
+        long rectifiedCount = majorHazards.stream()
+                .filter(r -> r.getReformed() != null && r.getReformed())
+                .count();
+        long unrectifiedCount = majorHazards.stream()
+                .filter(r -> r.getReformed() == null || !r.getReformed())
+                .count();
+
+        vo.setRectifiedHazardCount((int) rectifiedCount);
+        vo.setUnrectifiedHazardCount((int) unrectifiedCount);
+    }
+
+    /**
+     * 获取项目地图标点数据
+     */
+    private List<ProjectMapPointVO> getProjectMapPoints(
+            List<Project> projects, Long clientId, Date startDate, Date endDate) {
+
+        List<ProjectMapPointVO> result = new ArrayList<>();
+
+        // 计算项目类别
+        Map<Integer, String> categoryMap;
+        if (!projects.isEmpty()) {
+            // 判断项目类型:在建工程 or 固定资产
+            Integer usageType = projects.get(0).getUsageType();
+            if (usageType != null && usageType == 0) {
+                // 在建工程
+                categoryMap = projectCategoryCalculator.calculateOngoingProjectCategories(
+                        projects, clientId, startDate, endDate);
+            } else if (usageType != null && usageType == 1) {
+                // 固定资产
+                categoryMap = projectCategoryCalculator.calculateFixedAssetCategories(
+                        projects, clientId, startDate, endDate);
+            } else {
+                categoryMap = new HashMap<>();
+            }
+        } else {
+            categoryMap = new HashMap<>();
+        }
+
+        // 构建地图标点数据
+        for (Project p : projects) {
+            if (!StringUtils.hasText(p.getAddressInfo())) {
+                continue;
+            }
+
+            try {
+                // 解析 address_info JSON 字符串
+                JSONObject addressJson = JSON.parseObject(p.getAddressInfo());
+                JSONArray gps = addressJson.getJSONArray("gps");
+
+                if (gps != null && gps.size() >= 2) {
+                    ProjectMapPointVO vo = new ProjectMapPointVO();
+                    vo.setProjectId(p.getId());
+                    vo.setProjectName(p.getName());
+                    vo.setProjectType(p.getType());
+
+                    // gps 数组格式:[经度, 纬度]
+                    vo.setLongitude(gps.getString(0));
+                    vo.setLatitude(gps.getString(1));
+
+                    // 设置项目类别
+                    vo.setCategory(categoryMap.get(p.getId()));
+
+                    result.add(vo);
+                }
+            } catch (Exception e) {
+                // 解析失败则跳过该项目
+                log.warn("解析项目地址信息失败,项目ID: {}, address_info: {}", p.getId(), p.getAddressInfo(), e);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * 获取开始日期
+     */
+    private Date getStartDate(Date startDate) {
+        if (startDate != null) {
+            return startDate;
+        }
+        // 默认为当前月份第一天
+        YearMonth currentMonth = YearMonth.now();
+        return Date.from(currentMonth.atDay(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 获取结束日期
+     */
+    private Date getEndDate(Date endDate) {
+        if (endDate != null) {
+            return endDate;
+        }
+        // 默认为当前月份最后一天
+        YearMonth currentMonth = YearMonth.now();
+        return Date.from(currentMonth.atEndOfMonth().atTime(23, 59, 59).atZone(ZoneId.systemDefault()).toInstant());
+    }
 }

--
Gitblit v1.7.1