package com.designhub.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.designhub.entity.*; import com.designhub.mapper.*; import com.designhub.vo.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAdjusters; import java.util.*; import java.util.stream.Collectors; /** * 数据分析Service */ @Slf4j @Service @RequiredArgsConstructor public class AnalyticsService { private final TaskMapper taskMapper; private final TaskItemMapper taskItemMapper; private final UserMapper userMapper; private final DepartmentMapper departmentMapper; private final TeamScorePoolMapper teamScorePoolMapper; private final TaskReviewMapper taskReviewMapper; private final RoleService roleService; private final DesignerTargetScoreMapper designerTargetScoreMapper; private final DemandTypeMapper demandTypeMapper; private final ProgressDeductionLogMapper progressDeductionLogMapper; private final BrandMapper brandMapper; private final CategoryMapper categoryMapper; private final PlatformMapper platformMapper; private final DepartmentService departmentService; private final com.designhub.util.WorkHoursCalculator workHoursCalculator; /** * 获取需求统计数据(根据角色返回不同维度的数据) * @param currentUserId 当前登录用户ID * @param dateRange 时间范围 * @param deptId 部门ID(可选,用于管理员查看特定部门) * @param userId 用户ID(可选,用于管理员查看特定用户) * @param month 指定月份(可选,格式 yyyy-MM) */ public DemandStatisticsVO getDemandStatistics(Long currentUserId, String dateRange, Long deptId, Long userId, String month) { log.info("获取需求统计数据: currentUserId={}, dateRange={}, deptId={}, userId={}, month={}", currentUserId, dateRange, deptId, userId, month); // 计算时间范围 LocalDateTime startTime; LocalDateTime endTime; // 如果指定了月份,则使用月份范围 if (month != null && !month.isEmpty()) { try { LocalDate monthDate = LocalDate.parse(month + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd")); startTime = LocalDateTime.of(monthDate, LocalTime.MIN); endTime = LocalDateTime.of(monthDate.with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX); log.info("使用指定月份范围: {} ~ {}", startTime, endTime); } catch (Exception e) { log.warn("月份格式错误: {}, 使用默认范围", month); startTime = calculateStartTime(dateRange); endTime = LocalDateTime.now(); } } else { startTime = calculateStartTime(dateRange); endTime = LocalDateTime.now(); } // 构建查询条件(所有用户查看所有数据,可通过参数过滤) LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() .ge(startTime != null, Task::getCreateTime, startTime) .le(Task::getCreateTime, endTime) .ne(Task::getStatus, "draft") // 排除草稿状态的任务 .eq(Task::getDeleted, 0); // 排除已删除的任务 // 如果指定了部门ID,则按部门过滤 if (deptId != null) { queryWrapper.eq(Task::getPublisherDeptId, deptId); log.info("按部门过滤: deptId={}", deptId); } // 如果指定了用户ID,则按用户过滤 if (userId != null) { queryWrapper.eq(Task::getPublisherId, userId); log.info("按用户过滤: userId={}", userId); } log.info("查看全部数据(所有用户均可访问)"); List tasks = taskMapper.selectList(queryWrapper); log.info("查询到任务数量: {}", tasks.size()); return DemandStatisticsVO.builder() .userStats(buildUserStats(tasks)) .deptStats(buildDeptStats(tasks)) .trendData(buildTrendData(tasks, dateRange)) .typeDistribution(buildTypeDistribution(tasks)) .overview(buildOverview(tasks)) .build(); } /** * 构建用户维度统计 */ private List buildUserStats(List tasks) { Map> tasksByUser = tasks.stream() .collect(Collectors.groupingBy(Task::getPublisherId)); List stats = new ArrayList<>(); for (Map.Entry> entry : tasksByUser.entrySet()) { Long userId = entry.getKey(); List userTasks = entry.getValue(); User user = userMapper.selectById(userId); if (user == null) continue; int totalDemands = userTasks.size(); int completedDemands = (int) userTasks.stream() .filter(t -> "completed".equals(t.getStatus())) .count(); int pendingDemands = (int) userTasks.stream() .filter(t -> Arrays.asList("pending", "in_progress", "submitted").contains(t.getStatus())) .count(); // 计算消耗积分(使用Task::getScore,不是ScoreReward) BigDecimal totalScore = userTasks.stream() .filter(t -> !"draft".equals(t.getStatus()) && !"rejected_by_demand".equals(t.getApprovalStatus())) .map(Task::getScore) .filter(Objects::nonNull) .reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal avgScore = totalDemands > 0 ? totalScore.divide(BigDecimal.valueOf(totalDemands), 2, RoundingMode.HALF_UP) : BigDecimal.ZERO; double completionRate = totalDemands > 0 ? (double) completedDemands / totalDemands * 100 : 0.0; // 计算满意度(五星好评率) double satisfactionRate = calculateSatisfactionRate(userTasks); Department dept = departmentMapper.selectById(user.getDeptId()); stats.add(DemandStatisticsVO.UserDemandStat.builder() .userId(userId) .userName(user.getUsername()) .realName(user.getRealName() != null ? user.getRealName() : user.getUsername()) .deptName(dept != null ? dept.getDeptName() : "") .totalDemands(totalDemands) .completedDemands(completedDemands) .pendingDemands(pendingDemands) .totalScore(totalScore) .avgScore(avgScore) .completionRate(completionRate) .satisfactionRate(satisfactionRate) .build()); } // 按总需求数降序排序 stats.sort((a, b) -> b.getTotalDemands().compareTo(a.getTotalDemands())); return stats; } /** * 构建部门维度统计 */ private List buildDeptStats(List tasks) { Map> tasksByDept = tasks.stream() .filter(t -> t.getPublisherDeptId() != null) .collect(Collectors.groupingBy(Task::getPublisherDeptId)); List stats = new ArrayList<>(); for (Map.Entry> entry : tasksByDept.entrySet()) { Long deptId = entry.getKey(); List deptTasks = entry.getValue(); Department dept = departmentMapper.selectById(deptId); if (dept == null) continue; TeamScorePool scorePool = teamScorePoolMapper.selectOne( new LambdaQueryWrapper().eq(TeamScorePool::getDeptId, deptId) ); int totalDemands = deptTasks.size(); int completedDemands = (int) deptTasks.stream() .filter(t -> "completed".equals(t.getStatus())) .count(); int pendingDemands = (int) deptTasks.stream() .filter(t -> Arrays.asList("pending", "in_progress", "submitted").contains(t.getStatus())) .count(); BigDecimal totalScore = deptTasks.stream() .map(Task::getScoreReward) .filter(Objects::nonNull) .reduce(BigDecimal.ZERO, BigDecimal::add); // 计算使用率和预警级别 double utilizationRate = 0.0; String alertLevel = "safe"; if (scorePool != null) { BigDecimal totalAvailable = BigDecimal.valueOf(scorePool.getTotalScore() != null ? scorePool.getTotalScore() : 0); if (totalAvailable.compareTo(BigDecimal.ZERO) > 0) { utilizationRate = totalScore.divide(totalAvailable, 4, RoundingMode.HALF_UP) .multiply(BigDecimal.valueOf(100)) .doubleValue(); } // 计算预警级别 BigDecimal remainingScore = BigDecimal.valueOf(scorePool.getRemainingScore() != null ? scorePool.getRemainingScore() : 0); if (totalAvailable.compareTo(BigDecimal.ZERO) > 0) { double remainingRate = remainingScore.divide(totalAvailable, 4, RoundingMode.HALF_UP) .multiply(BigDecimal.valueOf(100)) .doubleValue(); if (remainingRate < 10) { alertLevel = "danger"; } else if (remainingRate < 30) { alertLevel = "warning"; } } stats.add(DemandStatisticsVO.DeptDemandStat.builder() .deptId(deptId) .deptName(dept.getDeptName()) .totalDemands(totalDemands) .completedDemands(completedDemands) .pendingDemands(pendingDemands) .totalScore(totalScore) .remainingScore(BigDecimal.valueOf(scorePool.getRemainingScore() != null ? scorePool.getRemainingScore() : 0)) .dailyLimit(BigDecimal.valueOf(scorePool.getDailyLimit() != null ? scorePool.getDailyLimit() : 0)) .weeklyLimit(BigDecimal.valueOf(scorePool.getWeeklyLimit() != null ? scorePool.getWeeklyLimit() : 0)) .monthlyLimit(BigDecimal.valueOf(scorePool.getMonthlyLimit() != null ? scorePool.getMonthlyLimit() : 0)) .dailyUsed(BigDecimal.valueOf(scorePool.getDailyUsed() != null ? scorePool.getDailyUsed() : 0)) .weeklyUsed(BigDecimal.valueOf(scorePool.getWeeklyUsed() != null ? scorePool.getWeeklyUsed() : 0)) .monthlyUsed(BigDecimal.valueOf(scorePool.getMonthlyUsed() != null ? scorePool.getMonthlyUsed() : 0)) .utilizationRate(utilizationRate) .alertLevel(alertLevel) .build()); } } // 按总需求数降序排序 stats.sort((a, b) -> b.getTotalDemands().compareTo(a.getTotalDemands())); return stats; } /** * 构建趋势数据 */ private List buildTrendData(List tasks, String dateRange) { Map> tasksByDate = new LinkedHashMap<>(); // 根据dateRange生成日期范围 int days = "week".equals(dateRange) ? 7 : "month".equals(dateRange) ? 30 : 90; LocalDate endDate = LocalDate.now(); for (int i = days - 1; i >= 0; i--) { LocalDate date = endDate.minusDays(i); tasksByDate.put(date.format(DateTimeFormatter.ISO_LOCAL_DATE), new ArrayList<>()); } // 将任务按日期分组 for (Task task : tasks) { String dateKey = task.getCreateTime().toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE); if (tasksByDate.containsKey(dateKey)) { tasksByDate.get(dateKey).add(task); } } List trendData = new ArrayList<>(); for (Map.Entry> entry : tasksByDate.entrySet()) { List dateTasks = entry.getValue(); int demandCount = dateTasks.size(); int completedCount = (int) dateTasks.stream() .filter(t -> "completed".equals(t.getStatus())) .count(); // 使用 score 字段(预估积分)作为积分消耗,因为 scoreReward 字段未被使用 BigDecimal scoreUsed = dateTasks.stream() .map(Task::getScore) .filter(Objects::nonNull) .reduce(BigDecimal.ZERO, BigDecimal::add); trendData.add(DemandStatisticsVO.TrendData.builder() .date(entry.getKey()) .demandCount(demandCount) .completedCount(completedCount) .scoreUsed(scoreUsed) .build()); } return trendData; } /** * 构建类型分布(按任务子类型分组) */ private List buildTypeDistribution(List tasks) { // 按任务子类型(taskSubType)分组 Map> tasksBySubType = tasks.stream() .filter(t -> t.getTaskSubType() != null && !t.getTaskSubType().trim().isEmpty()) .collect(Collectors.groupingBy(t -> t.getTaskSubType())); int totalTasks = tasks.size(); List distribution = new ArrayList<>(); for (Map.Entry> entry : tasksBySubType.entrySet()) { List subTypeTasks = entry.getValue(); int count = subTypeTasks.size(); double percentage = totalTasks > 0 ? (double) count / totalTasks * 100 : 0.0; // 优先使用 scoreReward,其次使用 score BigDecimal totalScore = subTypeTasks.stream() .map(t -> { if (t.getScoreReward() != null && t.getScoreReward().compareTo(BigDecimal.ZERO) > 0) { return t.getScoreReward(); } return t.getScore() != null ? t.getScore() : BigDecimal.ZERO; }) .reduce(BigDecimal.ZERO, BigDecimal::add); String subTypeName = entry.getKey(); distribution.add(DemandStatisticsVO.TypeDistribution.builder() .typeName(subTypeName) .count(count) .percentage(percentage) .totalScore(totalScore) .build()); } // 按数量降序排序 distribution.sort((a, b) -> b.getCount().compareTo(a.getCount())); return distribution; } /** * 构建总览数据 */ private DemandStatisticsVO.OverviewData buildOverview(List tasks) { LocalDateTime now = LocalDateTime.now(); LocalDate today = now.toLocalDate(); LocalDate weekStart = today.minusDays(today.getDayOfWeek().getValue() - 1); LocalDate monthStart = today.withDayOfMonth(1); int totalDemands = tasks.size(); int todayDemands = (int) tasks.stream() .filter(t -> t.getCreateTime().toLocalDate().equals(today)) .count(); int weekDemands = (int) tasks.stream() .filter(t -> !t.getCreateTime().toLocalDate().isBefore(weekStart)) .count(); int monthDemands = (int) tasks.stream() .filter(t -> !t.getCreateTime().toLocalDate().isBefore(monthStart)) .count(); int completedDemands = (int) tasks.stream() .filter(t -> "completed".equals(t.getStatus())) .count(); // 待处理:只包含 pending 状态(不包含 in_progress) int pendingDemands = (int) tasks.stream() .filter(t -> "pending".equals(t.getStatus())) .count(); // 进行中:只包含 in_progress 状态 int inProgressDemands = (int) tasks.stream() .filter(t -> "in_progress".equals(t.getStatus())) .count(); int canceledDemands = (int) tasks.stream() .filter(t -> "cancelled".equals(t.getStatus())) .count(); BigDecimal totalScoreUsed = tasks.stream() .map(Task::getScoreReward) .filter(Objects::nonNull) .reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal todayScoreUsed = tasks.stream() .filter(t -> t.getCreateTime().toLocalDate().equals(today)) .map(Task::getScoreReward) .filter(Objects::nonNull) .reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal weekScoreUsed = tasks.stream() .filter(t -> !t.getCreateTime().toLocalDate().isBefore(weekStart)) .map(Task::getScoreReward) .filter(Objects::nonNull) .reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal monthScoreUsed = tasks.stream() .filter(t -> !t.getCreateTime().toLocalDate().isBefore(monthStart)) .map(Task::getScoreReward) .filter(Objects::nonNull) .reduce(BigDecimal.ZERO, BigDecimal::add); // 计算平均完成时长 double avgCompletionTime = calculateAvgCompletionTime(tasks); // 计算平均满意度 double avgSatisfaction = calculateAvgSatisfaction(tasks); return DemandStatisticsVO.OverviewData.builder() .totalDemands(totalDemands) .todayDemands(todayDemands) .weekDemands(weekDemands) .monthDemands(monthDemands) .completedDemands(completedDemands) .pendingDemands(pendingDemands) .inProgressDemands(inProgressDemands) .canceledDemands(canceledDemands) .totalScoreUsed(totalScoreUsed) .todayScoreUsed(todayScoreUsed) .weekScoreUsed(weekScoreUsed) .monthScoreUsed(monthScoreUsed) .avgCompletionTime(avgCompletionTime) .avgSatisfaction(avgSatisfaction) .build(); } /** * 计算时间范围起始时间 */ private LocalDateTime calculateStartTime(String dateRange) { LocalDateTime now = LocalDateTime.now(); switch (dateRange) { case "week": return now.minusWeeks(1); case "month": // 近一月:从当月1号开始计算 LocalDateTime monthStart = LocalDateTime.of(LocalDate.now().withDayOfMonth(1), LocalTime.MIN); log.info("计算近一月范围: dateRange={}, 从当月1号开始: {}", dateRange, monthStart); return monthStart; case "quarter": return now.minusMonths(3); case "year": return now.minusYears(1); default: return null; // 全部 } } /** * 计算满意度(五星好评率) */ private double calculateSatisfactionRate(List tasks) { try { List completedTaskIds = tasks.stream() .filter(t -> "completed".equals(t.getStatus())) .map(Task::getId) .collect(Collectors.toList()); if (completedTaskIds.isEmpty()) { return 0.0; } long fiveStarCount = taskReviewMapper.selectCount( new LambdaQueryWrapper() .in(TaskReview::getTaskId, completedTaskIds) .eq(TaskReview::getRating, 5) ); return (double) fiveStarCount / completedTaskIds.size() * 100; } catch (Exception e) { log.warn("计算满意度失败(可能task_review表不存在): {}", e.getMessage()); return 0.0; } } /** * 计算平均完成时长(小时) */ private double calculateAvgCompletionTime(List tasks) { List completedTasks = tasks.stream() .filter(t -> "completed".equals(t.getStatus()) && t.getActualEndTime() != null) .collect(Collectors.toList()); if (completedTasks.isEmpty()) { return 0.0; } long totalHours = completedTasks.stream() .mapToLong(t -> { LocalDateTime start = t.getActualStartTime() != null ? t.getActualStartTime() : t.getCreateTime(); LocalDateTime end = t.getActualEndTime(); return java.time.Duration.between(start, end).toHours(); }) .sum(); return (double) totalHours / completedTasks.size(); } /** * 计算平均满意度 */ private double calculateAvgSatisfaction(List tasks) { try { List completedTaskIds = tasks.stream() .filter(t -> "completed".equals(t.getStatus())) .map(Task::getId) .collect(Collectors.toList()); if (completedTaskIds.isEmpty()) { return 0.0; } List reviews = taskReviewMapper.selectList( new LambdaQueryWrapper() .in(TaskReview::getTaskId, completedTaskIds) ); if (reviews.isEmpty()) { return 0.0; } double totalRating = reviews.stream() .mapToInt(TaskReview::getRating) .sum(); return totalRating / reviews.size(); } catch (Exception e) { log.warn("计算平均满意度失败(可能task_review表不存在): {}", e.getMessage()); return 0.0; } } // ==================== 设计师绩效统计 ==================== /** * 获取设计师绩效数据 */ public DesignerPerformanceVO getDesignerPerformance(Long currentUserId, String month, Long deptId) { log.info("获取设计师绩效数据: currentUserId={}, month={}, deptId={}", currentUserId, month, deptId); // 计算时间范围和当前月份 LocalDateTime startTime; LocalDateTime endTime; String currentMonth; // 用于目标积分查询 if (month != null && !month.isEmpty()) { currentMonth = month; try { LocalDate monthDate = LocalDate.parse(month + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd")); startTime = LocalDateTime.of(monthDate, LocalTime.MIN); endTime = LocalDateTime.of(monthDate.with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX); } catch (Exception e) { LocalDate now = LocalDate.now(); startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN); endTime = LocalDateTime.now(); currentMonth = now.format(DateTimeFormatter.ofPattern("yyyy-MM")); } } else { LocalDate now = LocalDate.now(); startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN); endTime = LocalDateTime.now(); currentMonth = now.format(DateTimeFormatter.ofPattern("yyyy-MM")); } // 获取所有设计师(接单部门成员,所有用户均可查看所有设计师) List designers = getDesigners(deptId); // 获取时间范围内的任务 LambdaQueryWrapper taskQuery = new LambdaQueryWrapper() .ge(Task::getCreateTime, startTime) .le(Task::getCreateTime, endTime) .isNotNull(Task::getReceiverId); List tasks = taskMapper.selectList(taskQuery); // 计算本周范围 LocalDate today = LocalDate.now(); LocalDate weekStart = today.minusDays(today.getDayOfWeek().getValue() - 1); LocalDateTime weekStartTime = LocalDateTime.of(weekStart, LocalTime.MIN); // 构建设计师绩效列表 List designerStats = new ArrayList<>(); List workloadStats = new ArrayList<>(); int totalReceived = 0; int totalCompleted = 0; double totalOnTimeRate = 0; double totalProcessTime = 0; double totalSatisfaction = 0; int overloadCount = 0; int designerWithData = 0; for (User designer : designers) { // 获取该设计师的主任务 List designerTasks = tasks.stream() .filter(t -> designer.getId().equals(t.getReceiverId())) .collect(Collectors.toList()); // 获取该设计师的子任务(流水线任务中的子任务) List designerTaskItems = taskItemMapper.selectList( new LambdaQueryWrapper() .eq(TaskItem::getReceiverId, designer.getId()) .ge(TaskItem::getCreateTime, startTime) .le(TaskItem::getCreateTime, endTime) ); // 主任务统计 int mainReceivedCount = designerTasks.size(); int mainCompletedCount = (int) designerTasks.stream() .filter(t -> "completed".equals(t.getStatus())) .count(); int mainInProgressCount = (int) designerTasks.stream() .filter(t -> "in_progress".equals(t.getStatus())) .count(); // 子任务统计 int subReceivedCount = designerTaskItems.size(); int subCompletedCount = (int) designerTaskItems.stream() .filter(t -> "completed".equals(t.getStatus())) .count(); int subInProgressCount = (int) designerTaskItems.stream() .filter(t -> "in_progress".equals(t.getStatus())) .count(); // 合计(主任务 + 子任务) int receivedCount = mainReceivedCount + subReceivedCount; int completedCount = mainCompletedCount + subCompletedCount; int inProgressCount = mainInProgressCount + subInProgressCount; // 计算准时完成数(主任务 + 子任务) int onTimeCount = 0; int overdueCount = 0; double totalTime = 0; int timeCount = 0; // 主任务准时率计算 for (Task task : designerTasks) { if ("completed".equals(task.getStatus()) && task.getActualEndTime() != null) { LocalDateTime start = task.getActualStartTime() != null ? task.getActualStartTime() : task.getStartTime(); if (start != null) { long hours = Duration.between(start, task.getActualEndTime()).toHours(); totalTime += hours; timeCount++; } // 判断是否准时 if (task.getDeadline() != null) { if (!task.getActualEndTime().isAfter(task.getDeadline())) { onTimeCount++; } else { overdueCount++; } } else { onTimeCount++; // 没有deadline的默认准时 } } } // 子任务准时率计算 for (TaskItem item : designerTaskItems) { if ("completed".equals(item.getStatus()) && item.getCompleteTime() != null) { // 判断是否准时 if (item.getDeadline() != null) { if (!item.getCompleteTime().isAfter(item.getDeadline())) { onTimeCount++; } else { overdueCount++; } } else { onTimeCount++; // 没有deadline的默认准时 } } } double completionRate = receivedCount > 0 ? (double) completedCount / receivedCount * 100 : 0; double onTimeRate = completedCount > 0 ? (double) onTimeCount / completedCount * 100 : 0; double avgProcessTime = timeCount > 0 ? totalTime / timeCount : 0; // 计算评分和好评率 double avgRating = 0; int fiveStarCount = 0; double satisfactionRate = 0; try { List completedTaskIds = designerTasks.stream() .filter(t -> "completed".equals(t.getStatus())) .map(Task::getId) .collect(Collectors.toList()); if (!completedTaskIds.isEmpty()) { List reviews = taskReviewMapper.selectList( new LambdaQueryWrapper() .in(TaskReview::getTaskId, completedTaskIds) ); if (!reviews.isEmpty()) { avgRating = reviews.stream().mapToInt(TaskReview::getRating).average().orElse(0); fiveStarCount = (int) reviews.stream().filter(r -> r.getRating() == 5).count(); satisfactionRate = (double) fiveStarCount / reviews.size() * 100; } } } catch (Exception e) { log.warn("获取评价数据失败: {}", e.getMessage()); } // 计算获得积分和延误扣分 BigDecimal totalScore = BigDecimal.ZERO; BigDecimal deductionScore = BigDecimal.ZERO; // 获取扣分比例配置(默认每延误1小时扣任务积分的1%) BigDecimal deductionRate = getDeductionRate(designer.getDeptId(), currentMonth); LocalDateTime now = LocalDateTime.now(); for (Task task : designerTasks) { // 计算任务积分 BigDecimal taskScore = BigDecimal.ZERO; if (task.getScoreReward() != null && task.getScoreReward().compareTo(BigDecimal.ZERO) > 0) { taskScore = task.getScoreReward(); } else if (task.getScore() != null) { taskScore = task.getScore(); } // 计算已完成任务的积分和延误扣分 if ("completed".equals(task.getStatus()) && task.getActualEndTime() != null && !task.getActualEndTime().isBefore(startTime) && !task.getActualEndTime().isAfter(endTime)) { totalScore = totalScore.add(taskScore); // 计算延误扣分(如果超时完成) if (task.getDeadline() != null && task.getActualEndTime() != null && task.getActualEndTime().isAfter(task.getDeadline())) { // 计算延误小时数(只计算工作时间段内的延误) BigDecimal overdueHours = workHoursCalculator.calculateDelayHours(task.getDeadline(), task.getActualEndTime()); if (overdueHours.compareTo(BigDecimal.ZERO) > 0 && taskScore.compareTo(BigDecimal.ZERO) > 0) { // 扣分 = 任务积分 * 延误小时数 * 扣分比例 BigDecimal taskDeduction = taskScore .multiply(overdueHours) .multiply(deductionRate) .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); // 扣分不能超过任务积分 if (taskDeduction.compareTo(taskScore) > 0) { taskDeduction = taskScore; } deductionScore = deductionScore.add(taskDeduction); } } } // 计算进行中任务的延误扣分(当前时间超过deadline时) else if ("in_progress".equals(task.getStatus()) && task.getDeadline() != null && task.getDeadline().isBefore(endTime) // deadline在查询月份范围内 && now.isAfter(task.getDeadline()) // 当前时间已超过deadline && taskScore.compareTo(BigDecimal.ZERO) > 0) { // 计算延误小时数(只计算工作时间段内的延误) BigDecimal overdueHours = workHoursCalculator.calculateDelayHours(task.getDeadline(), now); if (overdueHours.compareTo(BigDecimal.ZERO) > 0) { // 扣分 = 任务积分 * 延误小时数 * 扣分比例 BigDecimal taskDeduction = taskScore .multiply(overdueHours) .multiply(deductionRate) .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); // 扣分不能超过任务积分 if (taskDeduction.compareTo(taskScore) > 0) { taskDeduction = taskScore; } deductionScore = deductionScore.add(taskDeduction); } } } // 计算子任务的获得积分 for (TaskItem item : designerTaskItems) { if ("completed".equals(item.getStatus()) && item.getCompleteTime() != null && !item.getCompleteTime().isBefore(startTime) && !item.getCompleteTime().isAfter(endTime)) { BigDecimal itemScore = item.getTotalScore() != null ? item.getTotalScore() : BigDecimal.ZERO; totalScore = totalScore.add(itemScore); } } // 计算子任务的延误扣分(与getDesignerDeductionDetails保持一致) try { // 查询已完成的子任务(按完成时间查询) List completedTaskItems = taskItemMapper.selectList( new LambdaQueryWrapper() .eq(TaskItem::getReceiverId, designer.getId()) .eq(TaskItem::getStatus, "completed") .isNotNull(TaskItem::getCompleteTime) .ge(TaskItem::getCompleteTime, startTime) .le(TaskItem::getCompleteTime, endTime) ); // 查询进行中的子任务 List inProgressTaskItems = taskItemMapper.selectList( new LambdaQueryWrapper() .eq(TaskItem::getReceiverId, designer.getId()) .eq(TaskItem::getStatus, "in_progress") .isNotNull(TaskItem::getEstimatedHours) .isNotNull(TaskItem::getCreateTime) ); // 处理已完成的子任务的延误扣分(与getDesignerDeductionDetails保持一致,使用deadline或claimTime) for (TaskItem item : completedTaskItems) { if (item.getCompleteTime() != null) { // 优先使用数据库中已存储的deadline,如果没有则根据claimTime计算 LocalDateTime expectedDeadline = item.getDeadline(); if (expectedDeadline == null && item.getClaimTime() != null && item.getEstimatedHours() != null && item.getEstimatedHours().compareTo(BigDecimal.ZERO) > 0) { expectedDeadline = workHoursCalculator.calculateDeadline(item.getClaimTime(), item.getEstimatedHours()); } // 如果完成时间超过预期deadline,计算延误扣分 if (expectedDeadline != null && item.getCompleteTime().isAfter(expectedDeadline)) { BigDecimal itemScore = item.getTotalScore() != null ? item.getTotalScore() : BigDecimal.ZERO; if (itemScore.compareTo(BigDecimal.ZERO) > 0) { // 计算延误小时数(只计算工作时间段内的延误) BigDecimal overdueHours = workHoursCalculator.calculateDelayHours(expectedDeadline, item.getCompleteTime()); if (overdueHours.compareTo(BigDecimal.ZERO) > 0) { // 计算扣分 BigDecimal itemDeduction = itemScore .multiply(overdueHours) .multiply(deductionRate) .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); // 扣分不能超过任务积分 if (itemDeduction.compareTo(itemScore) > 0) { itemDeduction = itemScore; } deductionScore = deductionScore.add(itemDeduction); } } } } } // 处理进行中的子任务的延误扣分(与getDesignerDeductionDetails保持一致,使用deadline或claimTime) for (TaskItem item : inProgressTaskItems) { // 优先使用数据库中已存储的deadline,如果没有则根据claimTime计算 LocalDateTime expectedDeadline = item.getDeadline(); if (expectedDeadline == null && item.getClaimTime() != null && item.getEstimatedHours() != null && item.getEstimatedHours().compareTo(BigDecimal.ZERO) > 0) { expectedDeadline = workHoursCalculator.calculateDeadline(item.getClaimTime(), item.getEstimatedHours()); } // 如果当前时间超过预期deadline,计算延误扣分 if (expectedDeadline != null && now.isAfter(expectedDeadline)) { BigDecimal itemScore = item.getTotalScore() != null ? item.getTotalScore() : BigDecimal.ZERO; if (itemScore.compareTo(BigDecimal.ZERO) > 0) { // 计算延误小时数(只计算工作时间段内的延误) BigDecimal overdueHours = workHoursCalculator.calculateDelayHours(expectedDeadline, now); if (overdueHours.compareTo(BigDecimal.ZERO) > 0) { // 计算扣分 BigDecimal itemDeduction = itemScore .multiply(overdueHours) .multiply(deductionRate) .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); // 扣分不能超过任务积分 if (itemDeduction.compareTo(itemScore) > 0) { itemDeduction = itemScore; } deductionScore = deductionScore.add(itemDeduction); } } } } } catch (Exception e) { log.warn("计算子任务延误扣分失败: {}", e.getMessage()); } // 添加进度未更新扣分 try { if (progressDeductionLogMapper != null) { // 解析月份 String[] monthParts = currentMonth.split("-"); if (monthParts.length == 2) { int yearNum = Integer.parseInt(monthParts[0]); int monthNum = Integer.parseInt(monthParts[1]); BigDecimal progressDeduction = progressDeductionLogMapper.sumMonthlyDeduction(designer.getId(), yearNum, monthNum); if (progressDeduction != null && progressDeduction.compareTo(BigDecimal.ZERO) > 0) { deductionScore = deductionScore.add(progressDeduction); log.debug("设计师 {} 本月进度未更新扣分: {}", designer.getRealName(), progressDeduction); } } } } catch (Exception e) { log.warn("获取进度未更新扣分失败: {}", e.getMessage()); } // 净积分 = 总积分 - 扣分 BigDecimal netScore = totalScore.subtract(deductionScore); if (netScore.compareTo(BigDecimal.ZERO) < 0) { netScore = BigDecimal.ZERO; } // 获取目标积分 BigDecimal targetScore = getTargetScore(designer.getId(), designer.getDeptId(), currentMonth); // 计算目标完成率 Double targetCompletionRate = 0.0; if (targetScore != null && targetScore.compareTo(BigDecimal.ZERO) > 0) { targetCompletionRate = netScore.divide(targetScore, 4, RoundingMode.HALF_UP) .multiply(BigDecimal.valueOf(100)) .doubleValue(); targetCompletionRate = Math.round(targetCompletionRate * 10) / 10.0; } // 本周完成数 int weekCompletedCount = (int) designerTasks.stream() .filter(t -> "completed".equals(t.getStatus()) && t.getActualEndTime() != null && !t.getActualEndTime().isBefore(weekStartTime)) .count(); Department dept = designer.getDeptId() != null ? departmentMapper.selectById(designer.getDeptId()) : null; designerStats.add(DesignerPerformanceVO.DesignerStat.builder() .userId(designer.getId()) .userName(designer.getUsername()) .realName(designer.getRealName()) .deptName(dept != null ? dept.getDeptName() : "") .deptId(designer.getDeptId()) .receivedCount(receivedCount) .completedCount(completedCount) .inProgressCount(inProgressCount) .completionRate(Math.round(completionRate * 10) / 10.0) .onTimeRate(Math.round(onTimeRate * 10) / 10.0) .onTimeCount(onTimeCount) .overdueCount(overdueCount) .avgProcessTime(Math.round(avgProcessTime * 10) / 10.0) .avgRating(Math.round(avgRating * 10) / 10.0) .fiveStarCount(fiveStarCount) .satisfactionRate(Math.round(satisfactionRate * 10) / 10.0) .totalScore(totalScore) .deductionScore(deductionScore) .netScore(netScore) .targetScore(targetScore) .targetCompletionRate(targetCompletionRate) .weekCompletedCount(weekCompletedCount) .monthCompletedCount(completedCount) .build()); // 工作负载 String loadLevel = "normal"; if (inProgressCount > 8) { loadLevel = "overload"; overloadCount++; } else if (inProgressCount > 5) { loadLevel = "high"; } else if (inProgressCount < 2) { loadLevel = "low"; } // 获取最早截止时间 String earliestDeadline = null; Optional earliestTask = designerTasks.stream() .filter(t -> "in_progress".equals(t.getStatus()) && t.getDeadline() != null) .min(Comparator.comparing(Task::getDeadline)); if (earliestTask.isPresent()) { earliestDeadline = earliestTask.get().getDeadline().format(DateTimeFormatter.ofPattern("MM-dd HH:mm")); } workloadStats.add(DesignerPerformanceVO.WorkloadStat.builder() .userId(designer.getId()) .userName(designer.getUsername()) .realName(designer.getRealName()) .deptName(dept != null ? dept.getDeptName() : "") .currentLoad(inProgressCount) .loadLevel(loadLevel) .earliestDeadline(earliestDeadline) .avgDailyTasks(0.0) // 可后续优化 .build()); // 汇总 if (receivedCount > 0) { totalReceived += receivedCount; totalCompleted += completedCount; totalOnTimeRate += onTimeRate; totalProcessTime += avgProcessTime; totalSatisfaction += avgRating; designerWithData++; } } // 按净积分(有效积分)降序排序 designerStats.sort((a, b) -> { BigDecimal netA = a.getNetScore() != null ? a.getNetScore() : BigDecimal.ZERO; BigDecimal netB = b.getNetScore() != null ? b.getNetScore() : BigDecimal.ZERO; return netB.compareTo(netA); }); // 按当前负载排序(null 值排在最后) workloadStats.sort((a, b) -> { Integer loadA = a.getCurrentLoad(); Integer loadB = b.getCurrentLoad(); if (loadA == null && loadB == null) { return 0; } if (loadA == null) { return 1; // a 排在后面 } if (loadB == null) { return -1; // b 排在后面 } return loadB.compareTo(loadA); // 降序排序 }); // 构建汇总数据 DesignerPerformanceVO.Summary summary = DesignerPerformanceVO.Summary.builder() .totalDesigners(designers.size()) .totalReceived(totalReceived) .totalCompleted(totalCompleted) .avgCompletionRate(designerWithData > 0 ? Math.round(totalCompleted * 1000.0 / totalReceived) / 10.0 : 0) .avgOnTimeRate(designerWithData > 0 ? Math.round(totalOnTimeRate / designerWithData * 10) / 10.0 : 0) .avgProcessTime(designerWithData > 0 ? Math.round(totalProcessTime / designerWithData * 10) / 10.0 : 0) .avgSatisfaction(designerWithData > 0 ? Math.round(totalSatisfaction / designerWithData * 10) / 10.0 : 0) .overloadCount(overloadCount) .build(); return DesignerPerformanceVO.builder() .designerStats(designerStats) .workloadStats(workloadStats) .summary(summary) .build(); } /** * 获取设计师列表(所有用户均可查看所有设计师) */ private List getDesigners(Long deptId) { LambdaQueryWrapper userQuery = new LambdaQueryWrapper() .eq(User::getStatus, 1); // 如果指定了部门ID,获取该部门及其所有子部门 if (deptId != null) { List targetDeptIds = getDeptAndChildIds(deptId); log.debug("筛选部门: deptId={}, targetDeptIds={}", deptId, targetDeptIds); userQuery.in(User::getDeptId, targetDeptIds); } List allUsers = userMapper.selectList(userQuery); // 过滤出接单部门的成员(排除部门负责人) List designers = new ArrayList<>(); for (User user : allUsers) { if (user.getDeptId() != null) { Department dept = departmentMapper.selectById(user.getDeptId()); if (dept != null && "receiving".equals(dept.getDeptType())) { // 排除部门负责人 if (!user.getId().equals(dept.getLeaderId())) { designers.add(user); } } } } return designers; } /** * 递归获取部门及其所有子部门ID */ private List getDeptAndChildIds(Long deptId) { List deptIds = new ArrayList<>(); deptIds.add(deptId); List children = departmentMapper.selectList( new LambdaQueryWrapper() .eq(Department::getParentId, deptId) .eq(Department::getStatus, 1) ); for (Department child : children) { deptIds.addAll(getDeptAndChildIds(child.getId())); } return deptIds; } /** * 统计部门负责人中,dept_id 不在该部门内的人数 * 用于部门人数统计时,把"挂靠"在其他部门但实际负责该部门的负责人算进去 */ private int countLeadersNotInDept(List deptIds) { if (deptIds == null || deptIds.isEmpty()) { return 0; } // 获取这些部门的所有负责人ID List depts = departmentMapper.selectList( new LambdaQueryWrapper() .in(Department::getId, deptIds) .isNotNull(Department::getLeaderId) ); if (depts.isEmpty()) { return 0; } Set leaderIds = depts.stream() .map(Department::getLeaderId) .filter(id -> id != null) .collect(Collectors.toSet()); if (leaderIds.isEmpty()) { return 0; } // 统计这些负责人中,dept_id 不在 deptIds 内的人数(且状态为启用) Long count = userMapper.selectCount(new LambdaQueryWrapper() .in(User::getId, leaderIds) .notIn(User::getDeptId, deptIds) .eq(User::getStatus, 1)); return count.intValue(); } // ==================== 流程效率统计 ==================== /** * 获取流程效率数据 */ public ProcessEfficiencyVO getProcessEfficiency(Long currentUserId, String month) { // 计算时间范围 LocalDateTime startTime; LocalDateTime endTime; if (month != null && !month.isEmpty()) { try { LocalDate monthDate = LocalDate.parse(month + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd")); startTime = LocalDateTime.of(monthDate, LocalTime.MIN); endTime = LocalDateTime.of(monthDate.with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX); } catch (Exception e) { LocalDate now = LocalDate.now(); startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN); endTime = LocalDateTime.now(); } } else { LocalDate now = LocalDate.now(); startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN); endTime = LocalDateTime.now(); } // 获取已完成的任务 List completedTasks = taskMapper.selectList( new LambdaQueryWrapper() .eq(Task::getStatus, "completed") .ge(Task::getCreateTime, startTime) .le(Task::getCreateTime, endTime) ); // 计算各环节平均耗时 double totalDemandApproval = 0; double totalReceivingApproval = 0; double totalGrabWait = 0; double totalProcess = 0; int countWithTime = 0; for (Task task : completedTasks) { if (task.getActualStartTime() != null && task.getActualEndTime() != null) { // 任务处理时长 long processHours = Duration.between(task.getActualStartTime(), task.getActualEndTime()).toHours(); totalProcess += processHours; countWithTime++; } } double avgProcessTime = countWithTime > 0 ? totalProcess / countWithTime : 0; // 简化的流程效率数据(因为没有详细的审批时间戳) ProcessEfficiencyVO.StageTimeStats stageTimeStats = ProcessEfficiencyVO.StageTimeStats.builder() .demandApprovalTime(2.5) // 默认值,后续可从审批日志计算 .receivingApprovalTime(1.8) .grabWaitTime(0.5) .processTime(avgProcessTime) .totalCycleTime(2.5 + 1.8 + 0.5 + avgProcessTime) .bottleneckStage("任务处理") .bottleneckPercentage(avgProcessTime / (2.5 + 1.8 + 0.5 + avgProcessTime) * 100) .build(); // 效率指标 ProcessEfficiencyVO.EfficiencyMetrics metrics = ProcessEfficiencyVO.EfficiencyMetrics.builder() .weekDemandApprovalTime(2.3) .weekReceivingApprovalTime(1.6) .weekGrabWaitTime(0.4) .weekProcessTime(avgProcessTime) .weekFirstPassRate(85.0) .lastWeekDemandApprovalTime(2.8) .lastWeekReceivingApprovalTime(2.1) .lastWeekGrabWaitTime(0.6) .lastWeekProcessTime(avgProcessTime * 1.1) .lastWeekFirstPassRate(82.0) .monthDemandApprovalTime(2.5) .monthReceivingApprovalTime(1.8) .monthGrabWaitTime(0.5) .monthProcessTime(avgProcessTime) .monthFirstPassRate(83.5) .totalRejected(0) .rejectionRate(0.0) .build(); // 环节分布 List distribution = Arrays.asList( ProcessEfficiencyVO.StageDistribution.builder() .stageName("需求审核") .avgTime(2.5) .percentage(22.7) .build(), ProcessEfficiencyVO.StageDistribution.builder() .stageName("接单审核") .avgTime(1.8) .percentage(16.4) .build(), ProcessEfficiencyVO.StageDistribution.builder() .stageName("抢单等待") .avgTime(0.5) .percentage(4.5) .build(), ProcessEfficiencyVO.StageDistribution.builder() .stageName("任务处理") .avgTime(avgProcessTime) .percentage(56.4) .build() ); return ProcessEfficiencyVO.builder() .stageTimeStats(stageTimeStats) .efficiencyMetrics(metrics) .approvalTrend(new ArrayList<>()) .stageDistribution(distribution) .build(); } // ==================== 预警监控 ==================== /** * 获取预警任务列表 */ public List getWarningTasks(Long currentUserId) { List warnings = new ArrayList<>(); LocalDateTime now = LocalDateTime.now(); // 获取所有进行中和待处理的任务 List activeTasks = taskMapper.selectList( new LambdaQueryWrapper() .in(Task::getStatus, Arrays.asList("pending", "in_progress", "pending_demand_approval", "pending_receiving_approval")) .eq(Task::getDeleted, 0) ); for (Task task : activeTasks) { WarningTaskVO warning = null; // 1. 已超时 if ("in_progress".equals(task.getStatus()) && task.getDeadline() != null && now.isAfter(task.getDeadline())) { long overdueHours = Duration.between(task.getDeadline(), now).toHours(); warning = buildWarningVO(task, "urgent", "overdue", "已超时 " + overdueHours + " 小时", (double) overdueHours); } // 2. 即将超时(剩余时间<20%) else if ("in_progress".equals(task.getStatus()) && task.getDeadline() != null && task.getEstimatedHours() != null) { long remainingHours = Duration.between(now, task.getDeadline()).toHours(); double threshold = task.getEstimatedHours().doubleValue() * 0.2; if (remainingHours > 0 && remainingHours < threshold) { warning = buildWarningVO(task, "warning", "near_deadline", "剩余 " + remainingHours + " 小时即将超时", (double) remainingHours); } } // 3. 长期未接单(>24小时) else if ("pending".equals(task.getStatus()) && task.getCreateTime() != null) { long waitingHours = Duration.between(task.getCreateTime(), now).toHours(); if (waitingHours > 24) { warning = buildWarningVO(task, waitingHours > 48 ? "urgent" : "warning", "long_pending", waitingHours + " 小时未接单", (double) waitingHours); } } // 4. 审批滞后(>24小时) else if (task.getStatus() != null && task.getStatus().startsWith("pending_") && task.getCreateTime() != null) { long waitingHours = Duration.between(task.getCreateTime(), now).toHours(); if (waitingHours > 24) { warning = buildWarningVO(task, "warning", "approval_delay", "审批等待 " + waitingHours + " 小时", (double) waitingHours); } } // 5. 长期未完成(进行时间>预估工时*2) else if ("in_progress".equals(task.getStatus()) && task.getActualStartTime() != null && task.getEstimatedHours() != null) { long inProgressHours = Duration.between(task.getActualStartTime(), now).toHours(); if (inProgressHours > task.getEstimatedHours().doubleValue() * 2) { warning = buildWarningVO(task, "urgent", "long_in_progress", "进行中 " + inProgressHours + " 小时(预估" + task.getEstimatedHours().intValue() + "h)", (double) inProgressHours); } } if (warning != null) { warnings.add(warning); } } // 按预警级别排序(紧急在前) warnings.sort((a, b) -> { // 先按预警级别排序:urgent 在前 boolean aIsUrgent = "urgent".equals(a.getWarningLevel()); boolean bIsUrgent = "urgent".equals(b.getWarningLevel()); if (aIsUrgent && !bIsUrgent) { return -1; // a 排在前面 } if (!aIsUrgent && bIsUrgent) { return 1; // b 排在前面 } // 如果级别相同,按 hoursInfo 降序排序(null 值排在最后) Double hoursA = a.getHoursInfo(); Double hoursB = b.getHoursInfo(); if (hoursA == null && hoursB == null) { return 0; } if (hoursA == null) { return 1; // a 排在后面 } if (hoursB == null) { return -1; // b 排在后面 } return Double.compare(hoursB, hoursA); // 降序排序 }); return warnings; } private WarningTaskVO buildWarningVO(Task task, String level, String type, String reason, Double hoursInfo) { String receiverName = null; String receiverDeptName = null; if (task.getReceiverId() != null) { User receiver = userMapper.selectById(task.getReceiverId()); if (receiver != null) { receiverName = receiver.getRealName() != null ? receiver.getRealName() : receiver.getUsername(); if (receiver.getDeptId() != null) { Department dept = departmentMapper.selectById(receiver.getDeptId()); if (dept != null) { receiverDeptName = dept.getDeptName(); } } } } String publisherName = null; if (task.getPublisherId() != null) { User publisher = userMapper.selectById(task.getPublisherId()); if (publisher != null) { publisherName = publisher.getRealName() != null ? publisher.getRealName() : publisher.getUsername(); } } return WarningTaskVO.builder() .taskId(task.getId()) .taskNo(task.getTaskNo()) .taskTitle(task.getTitle()) .status(task.getStatus()) .statusText(getStatusText(task.getStatus())) .warningLevel(level) .warningType(type) .warningReason(reason) .receiverId(task.getReceiverId()) .receiverName(receiverName) .receiverDeptName(receiverDeptName) .publisherName(publisherName) .estimatedHours(task.getEstimatedHours() != null ? task.getEstimatedHours().doubleValue() : null) .deadline(task.getDeadline()) .createTime(task.getCreateTime()) .startTime(task.getActualStartTime()) .hoursInfo(hoursInfo) .score(task.getScore()) .build(); } private String getStatusText(String status) { if (status == null) return ""; switch (status) { case "pending": return "待接单"; case "in_progress": return "进行中"; case "pending_demand_approval": return "待需求审核"; case "pending_receiving_approval": return "待接单审核"; case "completed": return "已完成"; case "rejected": return "已驳回"; default: return status; } } /** * 获取延误扣分比例(三级部门继承二级部门的配置) * @param deptId 部门ID * @param month 月份 yyyy-MM * @return 扣分比例(默认1%) */ private BigDecimal getDeductionRate(Long deptId, String month) { if (deptId == null) { return BigDecimal.ONE; // 默认1% } try { BigDecimal rate = findDeptDeductionRate(deptId, month); if (rate != null) { return rate; } } catch (Exception e) { log.warn("获取扣分比例配置失败: deptId={}, month={}", deptId, month, e); } return BigDecimal.ONE; // 默认1% } /** * 递归查找部门扣分比例(向上查找父部门配置) */ private BigDecimal findDeptDeductionRate(Long deptId, String month) { if (deptId == null || deptId == 0) { return null; } // 先查找指定月份的配置 DesignerTargetScore config = designerTargetScoreMapper.selectOne( new LambdaQueryWrapper() .eq(DesignerTargetScore::getDeptId, deptId) .isNull(DesignerTargetScore::getUserId) .eq(DesignerTargetScore::getEffectiveMonth, month) ); if (config != null && config.getDeductionRate() != null) { return config.getDeductionRate(); } // 查找通用配置(没有指定月份的) config = designerTargetScoreMapper.selectOne( new LambdaQueryWrapper() .eq(DesignerTargetScore::getDeptId, deptId) .isNull(DesignerTargetScore::getUserId) .isNull(DesignerTargetScore::getEffectiveMonth) ); if (config != null && config.getDeductionRate() != null) { return config.getDeductionRate(); } // 当前部门没有配置,向上查找父部门 Department dept = departmentMapper.selectById(deptId); if (dept != null && dept.getParentId() != null && dept.getParentId() > 0) { return findDeptDeductionRate(dept.getParentId(), month); } return null; } /** * 获取设计师目标积分(三级部门继承二级部门的目标积分) * @param userId 用户ID * @param deptId 部门ID * @param month 月份 yyyy-MM * @return 目标积分 */ private BigDecimal getTargetScore(Long userId, Long deptId, String month) { try { // 先查找用户个人配置 if (userId != null) { DesignerTargetScore config = designerTargetScoreMapper.selectOne( new LambdaQueryWrapper() .eq(DesignerTargetScore::getUserId, userId) .eq(DesignerTargetScore::getEffectiveMonth, month) ); if (config != null && config.getTargetScore() != null) { return config.getTargetScore(); } } // 查找部门通用配置(支持向上继承:三级部门继承二级部门的配置) if (deptId != null) { BigDecimal targetScore = findDeptTargetScore(deptId, month); if (targetScore != null && targetScore.compareTo(BigDecimal.ZERO) > 0) { return targetScore; } } } catch (Exception e) { log.warn("获取目标积分配置失败: userId={}, deptId={}, month={}", userId, deptId, month, e); } return BigDecimal.ZERO; // 默认0 } /** * 递归查找部门目标积分(向上查找父部门配置) */ private BigDecimal findDeptTargetScore(Long deptId, String month) { if (deptId == null || deptId == 0) { return null; } // 先查找当前部门的配置(指定月份) DesignerTargetScore config = designerTargetScoreMapper.selectOne( new LambdaQueryWrapper() .eq(DesignerTargetScore::getDeptId, deptId) .isNull(DesignerTargetScore::getUserId) .eq(DesignerTargetScore::getEffectiveMonth, month) ); if (config != null && config.getTargetScore() != null) { return config.getTargetScore(); } // 查找当前部门的默认配置(没有指定月份) config = designerTargetScoreMapper.selectOne( new LambdaQueryWrapper() .eq(DesignerTargetScore::getDeptId, deptId) .isNull(DesignerTargetScore::getUserId) .isNull(DesignerTargetScore::getEffectiveMonth) ); if (config != null && config.getTargetScore() != null) { return config.getTargetScore(); } // 当前部门没有配置,向上查找父部门 Department dept = departmentMapper.selectById(deptId); if (dept != null && dept.getParentId() != null && dept.getParentId() > 0) { return findDeptTargetScore(dept.getParentId(), month); } return null; } /** * 获取设计师扣分明细 * @param userId 设计师ID * @param month 月份 yyyy-MM * @return 扣分明细 */ public DeductionDetailVO getDesignerDeductionDetails(Long userId, String month) { log.info("获取设计师扣分明细: userId={}, month={}", userId, month); // 计算时间范围 LocalDateTime startTime; LocalDateTime endTime; String currentMonth; if (month != null && !month.isEmpty()) { currentMonth = month; try { LocalDate monthDate = LocalDate.parse(month + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd")); startTime = LocalDateTime.of(monthDate, LocalTime.MIN); endTime = LocalDateTime.of(monthDate.with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX); } catch (Exception e) { LocalDate now = LocalDate.now(); startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN); endTime = LocalDateTime.now(); currentMonth = now.format(DateTimeFormatter.ofPattern("yyyy-MM")); } } else { LocalDate now = LocalDate.now(); startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN); endTime = LocalDateTime.now(); currentMonth = now.format(DateTimeFormatter.ofPattern("yyyy-MM")); } // 获取用户信息 User designer = userMapper.selectById(userId); if (designer == null) { log.warn("设计师不存在: userId={}", userId); return DeductionDetailVO.builder() .totalDeduction(BigDecimal.ZERO) .delayDeductions(new ArrayList<>()) .progressDeductions(new ArrayList<>()) .build(); } // 获取时间范围内的已完成任务(按完成时间查询) List completedTasks = taskMapper.selectList( new LambdaQueryWrapper() .eq(Task::getReceiverId, userId) .eq(Task::getStatus, "completed") .isNotNull(Task::getActualEndTime) .ge(Task::getActualEndTime, startTime) .le(Task::getActualEndTime, endTime) ); // 获取所有进行中的任务(不限制deadline范围,因为可能deadline在之前月份,但任务仍在进行中) List inProgressTasks = taskMapper.selectList( new LambdaQueryWrapper() .eq(Task::getReceiverId, userId) .eq(Task::getStatus, "in_progress") .isNotNull(Task::getDeadline) ); log.info("获取设计师扣分明细 - 用户: {}, 月份: {}, 已完成任务数: {}, 进行中任务数: {}", designer.getRealName() != null ? designer.getRealName() : designer.getUsername(), currentMonth, completedTasks.size(), inProgressTasks.size()); // 获取扣分比例配置 BigDecimal deductionRate = getDeductionRate(designer.getDeptId(), currentMonth); log.info("扣分比例: {}%", deductionRate); // 计算延误扣分明细 List delayDeductions = new ArrayList<>(); BigDecimal totalDelayDeduction = BigDecimal.ZERO; LocalDateTime now = LocalDateTime.now(); // 处理已完成任务的延误扣分 for (Task task : completedTasks) { log.info("检查已完成任务: taskNo={}, deadline={}, actualEndTime={}, scoreReward={}, score={}", task.getTaskNo(), task.getDeadline(), task.getActualEndTime(), task.getScoreReward(), task.getScore()); if (task.getDeadline() != null && task.getActualEndTime() != null && task.getActualEndTime().isAfter(task.getDeadline())) { // 计算任务积分 BigDecimal taskScore = BigDecimal.ZERO; if (task.getScoreReward() != null && task.getScoreReward().compareTo(BigDecimal.ZERO) > 0) { taskScore = task.getScoreReward(); } else if (task.getScore() != null) { taskScore = task.getScore(); } if (taskScore.compareTo(BigDecimal.ZERO) > 0) { // 计算延误小时数(只计算工作时间段内的延误) BigDecimal overdueHours = workHoursCalculator.calculateDelayHours(task.getDeadline(), task.getActualEndTime()); if (overdueHours.compareTo(BigDecimal.ZERO) > 0) { // 计算扣分 BigDecimal taskDeduction = taskScore .multiply(overdueHours) .multiply(deductionRate) .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); // 扣分不能超过任务积分 if (taskDeduction.compareTo(taskScore) > 0) { taskDeduction = taskScore; } totalDelayDeduction = totalDelayDeduction.add(taskDeduction); long overdueHoursLong = overdueHours.longValue(); log.info("发现已完成延误任务: taskNo={}, 任务积分={}, 延误小时={}, 扣分={}", task.getTaskNo(), taskScore, overdueHoursLong, taskDeduction); delayDeductions.add(DeductionDetailVO.DelayDeductionItem.builder() .taskId(task.getId()) .taskNo(task.getTaskNo()) .taskTitle(task.getTitle()) .taskScore(taskScore) .claimTime(task.getActualStartTime()) // 主任务使用actualStartTime作为抢单时间 .deadline(task.getDeadline()) .actualEndTime(task.getActualEndTime()) .overdueHours(overdueHoursLong) .deductionScore(taskDeduction) .deductionReason("任务延误 " + overdueHoursLong + " 小时") .build()); } } } } // 处理进行中任务的延误扣分(当前时间超过deadline时) for (Task task : inProgressTasks) { log.info("检查进行中任务: taskNo={}, deadline={}, now={}, scoreReward={}, score={}", task.getTaskNo(), task.getDeadline(), now, task.getScoreReward(), task.getScore()); if (task.getDeadline() != null && now.isAfter(task.getDeadline())) { // 计算任务积分 BigDecimal taskScore = BigDecimal.ZERO; if (task.getScoreReward() != null && task.getScoreReward().compareTo(BigDecimal.ZERO) > 0) { taskScore = task.getScoreReward(); } else if (task.getScore() != null) { taskScore = task.getScore(); } log.info("进行中任务积分计算: taskNo={}, taskScore={}", task.getTaskNo(), taskScore); if (taskScore.compareTo(BigDecimal.ZERO) > 0) { // 计算延误小时数(只计算工作时间段内的延误) BigDecimal overdueHours = workHoursCalculator.calculateDelayHours(task.getDeadline(), now); log.info("进行中任务延误计算: taskNo={}, deadline={}, now={}, overdueHours={}", task.getTaskNo(), task.getDeadline(), now, overdueHours); if (overdueHours.compareTo(BigDecimal.ZERO) > 0) { // 计算扣分 BigDecimal taskDeduction = taskScore .multiply(overdueHours) .multiply(deductionRate) .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); // 扣分不能超过任务积分 if (taskDeduction.compareTo(taskScore) > 0) { taskDeduction = taskScore; } totalDelayDeduction = totalDelayDeduction.add(taskDeduction); long overdueHoursLong = overdueHours.longValue(); log.info("发现进行中延误任务: taskNo={}, 任务积分={}, 延误小时={}, 扣分={}", task.getTaskNo(), taskScore, overdueHoursLong, taskDeduction); delayDeductions.add(DeductionDetailVO.DelayDeductionItem.builder() .taskId(task.getId()) .taskNo(task.getTaskNo()) .taskTitle(task.getTitle()) .taskScore(taskScore) .claimTime(task.getActualStartTime()) // 主任务使用actualStartTime作为抢单时间 .deadline(task.getDeadline()) .actualEndTime(null) // 进行中任务没有完成时间 .overdueHours(overdueHoursLong) .deductionScore(taskDeduction) .deductionReason("任务进行中,已延误 " + overdueHoursLong + " 小时") .build()); } } } } // 查询已完成的子任务(按完成时间查询) List completedTaskItems = taskItemMapper.selectList( new LambdaQueryWrapper() .eq(TaskItem::getReceiverId, userId) .eq(TaskItem::getStatus, "completed") .isNotNull(TaskItem::getCompleteTime) .ge(TaskItem::getCompleteTime, startTime) .le(TaskItem::getCompleteTime, endTime) ); // 查询进行中的子任务 List inProgressTaskItems = taskItemMapper.selectList( new LambdaQueryWrapper() .eq(TaskItem::getReceiverId, userId) .eq(TaskItem::getStatus, "in_progress") .isNotNull(TaskItem::getEstimatedHours) ); log.info("获取设计师扣分明细 - 用户: {}, 月份: {}, 已完成子任务数: {}, 进行中子任务数: {}", designer.getRealName() != null ? designer.getRealName() : designer.getUsername(), currentMonth, completedTaskItems.size(), inProgressTaskItems.size()); // 处理已完成的子任务的延误扣分(使用子任务自己的deadline和claimTime) for (TaskItem item : completedTaskItems) { if (item.getCompleteTime() != null) { // 优先使用数据库中已存储的deadline,如果没有则根据claimTime计算 LocalDateTime expectedDeadline = item.getDeadline(); if (expectedDeadline == null && item.getClaimTime() != null && item.getEstimatedHours() != null && item.getEstimatedHours().compareTo(BigDecimal.ZERO) > 0) { expectedDeadline = workHoursCalculator.calculateDeadline(item.getClaimTime(), item.getEstimatedHours()); } // 如果完成时间超过预期deadline,计算延误扣分 if (expectedDeadline != null && item.getCompleteTime().isAfter(expectedDeadline)) { BigDecimal itemScore = item.getTotalScore() != null ? item.getTotalScore() : BigDecimal.ZERO; if (itemScore.compareTo(BigDecimal.ZERO) > 0) { // 计算延误小时数(只计算工作时间段内的延误) BigDecimal overdueHours = workHoursCalculator.calculateDelayHours(expectedDeadline, item.getCompleteTime()); if (overdueHours.compareTo(BigDecimal.ZERO) > 0) { // 计算扣分 BigDecimal itemDeduction = itemScore .multiply(overdueHours) .multiply(deductionRate) .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); // 扣分不能超过任务积分 if (itemDeduction.compareTo(itemScore) > 0) { itemDeduction = itemScore; } totalDelayDeduction = totalDelayDeduction.add(itemDeduction); // 获取主任务信息用于显示 Task mainTask = taskMapper.selectById(item.getTaskId()); String taskNo = item.getItemNo() != null ? item.getItemNo() : ""; String taskTitle = mainTask != null ? mainTask.getTitle() : "子任务"; long overdueHoursLong = overdueHours.longValue(); log.info("发现已完成延误子任务: itemNo={}, 任务积分={}, 延误小时={}, 扣分={}", taskNo, itemScore, overdueHoursLong, itemDeduction); delayDeductions.add(DeductionDetailVO.DelayDeductionItem.builder() .taskId(item.getTaskId()) // 使用主任务ID .taskNo(taskNo) .taskTitle(taskTitle + " (" + taskNo + ")") .taskScore(itemScore) .claimTime(item.getClaimTime()) // 子任务使用claimTime .deadline(expectedDeadline) .actualEndTime(item.getCompleteTime()) .overdueHours(overdueHoursLong) .deductionScore(itemDeduction) .deductionReason("子任务延误 " + overdueHoursLong + " 小时") .build()); } } } } } // 处理进行中的子任务的延误扣分(使用子任务自己的deadline和claimTime) for (TaskItem item : inProgressTaskItems) { // 优先使用数据库中已存储的deadline,如果没有则根据claimTime计算 LocalDateTime expectedDeadline = item.getDeadline(); if (expectedDeadline == null && item.getClaimTime() != null && item.getEstimatedHours() != null && item.getEstimatedHours().compareTo(BigDecimal.ZERO) > 0) { expectedDeadline = workHoursCalculator.calculateDeadline(item.getClaimTime(), item.getEstimatedHours()); } // 如果当前时间超过预期deadline,计算延误扣分 if (expectedDeadline != null && now.isAfter(expectedDeadline)) { BigDecimal itemScore = item.getTotalScore() != null ? item.getTotalScore() : BigDecimal.ZERO; if (itemScore.compareTo(BigDecimal.ZERO) > 0) { // 计算延误小时数(只计算工作时间段内的延误) BigDecimal overdueHours = workHoursCalculator.calculateDelayHours(expectedDeadline, now); if (overdueHours.compareTo(BigDecimal.ZERO) > 0) { // 计算扣分 BigDecimal itemDeduction = itemScore .multiply(overdueHours) .multiply(deductionRate) .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); // 扣分不能超过任务积分 if (itemDeduction.compareTo(itemScore) > 0) { itemDeduction = itemScore; } totalDelayDeduction = totalDelayDeduction.add(itemDeduction); // 获取主任务信息用于显示 Task mainTask = taskMapper.selectById(item.getTaskId()); String taskNo = item.getItemNo() != null ? item.getItemNo() : ""; String taskTitle = mainTask != null ? mainTask.getTitle() : "子任务"; long overdueHoursLong = overdueHours.longValue(); log.info("发现进行中延误子任务: itemNo={}, 任务积分={}, 延误小时={}, 扣分={}", taskNo, itemScore, overdueHoursLong, itemDeduction); delayDeductions.add(DeductionDetailVO.DelayDeductionItem.builder() .taskId(item.getTaskId()) // 使用主任务ID .taskNo(taskNo) .taskTitle(taskTitle + " (" + taskNo + ")") .taskScore(itemScore) .claimTime(item.getClaimTime()) // 子任务使用claimTime .deadline(expectedDeadline) .actualEndTime(null) // 进行中子任务没有完成时间 .overdueHours(overdueHoursLong) .deductionScore(itemDeduction) .deductionReason("子任务进行中,已延误 " + overdueHoursLong + " 小时") .build()); } } } } log.info("延误扣分总数: {}, 延误扣分记录数: {}", totalDelayDeduction, delayDeductions.size()); // 获取进度未更新扣分明细 List progressDeductions = new ArrayList<>(); BigDecimal totalProgressDeduction = BigDecimal.ZERO; try { if (progressDeductionLogMapper != null) { // 解析月份 String[] monthParts = currentMonth.split("-"); if (monthParts.length == 2) { int yearNum = Integer.parseInt(monthParts[0]); int monthNum = Integer.parseInt(monthParts[1]); // 查询该月的所有进度未更新扣分记录 LocalDate monthStartDate = LocalDate.of(yearNum, monthNum, 1); LocalDate monthEndDate = monthStartDate.with(TemporalAdjusters.lastDayOfMonth()); List progressLogs = progressDeductionLogMapper.selectList( new LambdaQueryWrapper() .eq(ProgressDeductionLog::getUserId, userId) .ge(ProgressDeductionLog::getDeductionDate, monthStartDate) .le(ProgressDeductionLog::getDeductionDate, monthEndDate) .isNull(ProgressDeductionLog::getTaskItemId) // 只查询主任务的扣分 .orderByDesc(ProgressDeductionLog::getDeductionDate) ); for (ProgressDeductionLog log : progressLogs) { if (log.getDeductionScore() != null && log.getDeductionScore().compareTo(BigDecimal.ZERO) > 0) { totalProgressDeduction = totalProgressDeduction.add(log.getDeductionScore()); // 获取任务信息 Task task = taskMapper.selectById(log.getTaskId()); String taskNo = task != null ? task.getTaskNo() : ""; String taskTitle = task != null ? task.getTitle() : "任务已删除"; progressDeductions.add(DeductionDetailVO.ProgressDeductionItem.builder() .taskId(log.getTaskId()) .taskNo(taskNo) .taskTitle(taskTitle) .deductionDate(log.getDeductionDate()) .deductionScore(log.getDeductionScore()) .reason(log.getReason() != null ? log.getReason() : "当日未更新任务进度") .build()); } } } } } catch (Exception e) { log.warn("获取进度未更新扣分明细失败: {}", e.getMessage()); } BigDecimal totalDeduction = totalDelayDeduction.add(totalProgressDeduction); return DeductionDetailVO.builder() .totalDeduction(totalDeduction) .delayDeductions(delayDeductions) .progressDeductions(progressDeductions) .build(); } /** * 获取多维度分析数据 * @param dateRange 时间范围 * @param month 指定月份 */ public DimensionAnalysisVO getDimensionAnalysis(String dateRange, String month) { log.info("获取多维度分析数据: dateRange={}, month={}", dateRange, month); // 计算时间范围 LocalDateTime startTime; LocalDateTime endTime; if (month != null && !month.isEmpty()) { try { LocalDate monthDate = LocalDate.parse(month + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd")); startTime = LocalDateTime.of(monthDate, LocalTime.MIN); endTime = LocalDateTime.of(monthDate.with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX); } catch (Exception e) { log.warn("月份格式错误: {}, 使用默认范围", month); startTime = calculateStartTime(dateRange); endTime = LocalDateTime.now(); } } else { startTime = calculateStartTime(dateRange); endTime = LocalDateTime.now(); } // 查询时间范围内的所有任务 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() .ge(startTime != null, Task::getCreateTime, startTime) .le(Task::getCreateTime, endTime) .ne(Task::getStatus, "draft") // 排除草稿 .eq(Task::getDeleted, 0); // 排除已删除的任务 List tasks = taskMapper.selectList(queryWrapper); log.info("查询到任务数量: {}", tasks.size()); DimensionAnalysisVO result = new DimensionAnalysisVO(); result.setCategoryAnalysis(buildCategoryAnalysis(tasks)); result.setBrandAnalysis(buildBrandAnalysis(tasks)); result.setReceivingDeptAnalysis(buildReceivingDeptAnalysis(tasks)); result.setDemandDeptAnalysis(buildDemandDeptAnalysis(tasks)); result.setPlatformAnalysis(buildPlatformAnalysis(tasks)); // 构建任务类型分析(一级、二级、三级) Map> taskTypeAnalysisMap = buildTaskTypeAnalysis(tasks); result.setTaskTypeAnalysis(taskTypeAnalysisMap.getOrDefault(1, new ArrayList<>())); result.setTaskTypeLevel2Analysis(taskTypeAnalysisMap.getOrDefault(2, new ArrayList<>())); result.setTaskTypeLevel3Analysis(taskTypeAnalysisMap.getOrDefault(3, new ArrayList<>())); return result; } /** * 构建类目分析数据 */ private List buildCategoryAnalysis(List tasks) { // 按类目ID分组 Map> tasksByCategory = tasks.stream() .filter(t -> t.getCategoryId() != null) .collect(Collectors.groupingBy(Task::getCategoryId)); List result = new ArrayList<>(); // 获取所有类目 List categories = categoryMapper.selectList(null); Map categoryNames = categories.stream() .collect(Collectors.toMap(Category::getId, Category::getName, (a, b) -> a)); for (Map.Entry> entry : tasksByCategory.entrySet()) { Long categoryId = entry.getKey(); List categoryTasks = entry.getValue(); DimensionAnalysisVO.CategoryAnalysis analysis = new DimensionAnalysisVO.CategoryAnalysis(); analysis.setCategoryId(categoryId); analysis.setCategoryName(categoryNames.getOrDefault(categoryId, "未知类目")); analysis.setTotalTasks(categoryTasks.size()); long completedCount = categoryTasks.stream() .filter(t -> "completed".equals(t.getStatus())) .count(); analysis.setCompletedTasks((int) completedCount); long inProgressCount = categoryTasks.stream() .filter(t -> "in_progress".equals(t.getStatus())) .count(); analysis.setInProgressTasks((int) inProgressCount); long pendingCount = categoryTasks.stream() .filter(t -> t.getStatus() != null && t.getStatus().contains("pending")) .count(); analysis.setPendingTasks((int) pendingCount); // 计算总积分 BigDecimal totalScore = categoryTasks.stream() .filter(t -> t.getScore() != null) .map(Task::getScore) .reduce(BigDecimal.ZERO, BigDecimal::add); analysis.setTotalScore(totalScore); // 计算完成率 if (categoryTasks.size() > 0) { analysis.setCompletionRate(BigDecimal.valueOf(completedCount * 100.0 / categoryTasks.size()) .setScale(2, RoundingMode.HALF_UP)); } else { analysis.setCompletionRate(BigDecimal.ZERO); } // 计算总数量 int totalQuantity = categoryTasks.stream() .filter(t -> t.getQuantity() != null) .mapToInt(Task::getQuantity) .sum(); analysis.setTotalQuantity(totalQuantity); result.add(analysis); } // 按任务数量降序排序 result.sort((a, b) -> Integer.compare(b.getTotalTasks(), a.getTotalTasks())); return result; } /** * 构建品牌分析数据 */ private List buildBrandAnalysis(List tasks) { // 按品牌ID分组 Map> tasksByBrand = tasks.stream() .filter(t -> t.getBrandId() != null) .collect(Collectors.groupingBy(Task::getBrandId)); List result = new ArrayList<>(); // 获取所有品牌 List brands = brandMapper.selectList(null); Map brandNames = brands.stream() .collect(Collectors.toMap(Brand::getId, Brand::getName, (a, b) -> a)); for (Map.Entry> entry : tasksByBrand.entrySet()) { Long brandId = entry.getKey(); List brandTasks = entry.getValue(); DimensionAnalysisVO.BrandAnalysis analysis = new DimensionAnalysisVO.BrandAnalysis(); analysis.setBrandId(brandId); analysis.setBrandName(brandNames.getOrDefault(brandId, "未知品牌")); analysis.setTotalTasks(brandTasks.size()); long completedCount = brandTasks.stream() .filter(t -> "completed".equals(t.getStatus())) .count(); analysis.setCompletedTasks((int) completedCount); long inProgressCount = brandTasks.stream() .filter(t -> "in_progress".equals(t.getStatus())) .count(); analysis.setInProgressTasks((int) inProgressCount); // 计算总积分 BigDecimal totalScore = brandTasks.stream() .filter(t -> t.getScore() != null) .map(Task::getScore) .reduce(BigDecimal.ZERO, BigDecimal::add); analysis.setTotalScore(totalScore); // 计算完成率 if (brandTasks.size() > 0) { analysis.setCompletionRate(BigDecimal.valueOf(completedCount * 100.0 / brandTasks.size()) .setScale(2, RoundingMode.HALF_UP)); } else { analysis.setCompletionRate(BigDecimal.ZERO); } // 计算总数量 int totalQuantity = brandTasks.stream() .filter(t -> t.getQuantity() != null) .mapToInt(Task::getQuantity) .sum(); analysis.setTotalQuantity(totalQuantity); result.add(analysis); } // 按任务数量降序排序 result.sort((a, b) -> Integer.compare(b.getTotalTasks(), a.getTotalTasks())); return result; } /** * 构建接单部门分析数据 */ private List buildReceivingDeptAnalysis(List tasks) { // 按接单部门ID分组 Map> tasksByDept = tasks.stream() .filter(t -> t.getReceiverDeptId() != null) .collect(Collectors.groupingBy(Task::getReceiverDeptId)); List result = new ArrayList<>(); // 获取所有部门 List departments = departmentMapper.selectList(null); Map deptNames = departments.stream() .collect(Collectors.toMap(d -> d.getId(), d -> d.getDeptName(), (a, b) -> a)); Map deptParentIds = departments.stream() .collect(Collectors.toMap(d -> d.getId(), d -> d.getParentId() != null ? d.getParentId() : 0L, (a, b) -> a)); // 🔥 获取所有接单类型的部门(包括没有任务的) List receivingDepts = departments.stream() .filter(d -> "receiving".equals(d.getDeptType()) && d.getStatus() != null && d.getStatus() == 1) .collect(Collectors.toList()); // 🔥 获取每个接单部门的人数(包含部门负责人) Map deptMemberCounts = new HashMap<>(); for (Department dept : receivingDepts) { Long deptId = dept.getId(); // 查询该部门及其子部门的所有用户数量 List deptAndChildIds = getDeptAndChildIds(deptId); Long memberCount = userMapper.selectCount(new LambdaQueryWrapper() .in(User::getDeptId, deptAndChildIds) .eq(User::getStatus, 1)); // 只统计启用状态的用户 // 🔥 检查部门负责人是否已被统计,如果负责人的dept_id不在该部门内,则额外加上 int leaderCount = countLeadersNotInDept(deptAndChildIds); deptMemberCounts.put(deptId, memberCount.intValue() + leaderCount); } // 🔥 遍历所有接单部门(包括没有任务的) for (Department dept : receivingDepts) { Long deptId = dept.getId(); List deptTasks = tasksByDept.getOrDefault(deptId, Collections.emptyList()); DimensionAnalysisVO.ReceivingDeptAnalysis analysis = new DimensionAnalysisVO.ReceivingDeptAnalysis(); analysis.setDeptId(deptId); // 🔥 如果是子部门且有同名部门,显示为"父部门-子部门"格式 String deptName = deptNames.getOrDefault(deptId, "未知部门"); Long parentId = deptParentIds.getOrDefault(deptId, 0L); if (parentId != null && parentId > 0) { String parentName = deptNames.get(parentId); if (parentName != null && !parentName.isEmpty()) { deptName = parentName + "-" + deptName; } } analysis.setDeptName(deptName); analysis.setTotalTasks(deptTasks.size()); // 🔥 设置部门人数 int memberCount = deptMemberCounts.getOrDefault(deptId, 0); analysis.setMemberCount(memberCount); // 🔥 计算人均任务数 if (memberCount > 0) { analysis.setAvgTasksPerMember(BigDecimal.valueOf((double) deptTasks.size() / memberCount) .setScale(2, RoundingMode.HALF_UP)); } else { analysis.setAvgTasksPerMember(BigDecimal.ZERO); } long completedCount = deptTasks.stream() .filter(t -> "completed".equals(t.getStatus())) .count(); analysis.setCompletedTasks((int) completedCount); long inProgressCount = deptTasks.stream() .filter(t -> "in_progress".equals(t.getStatus())) .count(); analysis.setInProgressTasks((int) inProgressCount); long pendingCount = deptTasks.stream() .filter(t -> t.getStatus() != null && t.getStatus().contains("pending")) .count(); analysis.setPendingTasks((int) pendingCount); // 计算总积分 BigDecimal totalScore = deptTasks.stream() .filter(t -> t.getScore() != null) .map(Task::getScore) .reduce(BigDecimal.ZERO, BigDecimal::add); analysis.setTotalScore(totalScore); // 计算完成率 if (deptTasks.size() > 0) { analysis.setCompletionRate(BigDecimal.valueOf(completedCount * 100.0 / deptTasks.size()) .setScale(2, RoundingMode.HALF_UP)); } else { analysis.setCompletionRate(BigDecimal.ZERO); } // 计算总数量 int totalQuantity = deptTasks.stream() .filter(t -> t.getQuantity() != null) .mapToInt(Task::getQuantity) .sum(); analysis.setTotalQuantity(totalQuantity); // 计算平均完成时长(小时) List completedTasks = deptTasks.stream() .filter(t -> "completed".equals(t.getStatus()) && t.getCompleteTime() != null && t.getUpdateTime() != null) .collect(Collectors.toList()); if (!completedTasks.isEmpty()) { double avgHours = completedTasks.stream() .mapToDouble(t -> { Duration duration = Duration.between(t.getCreateTime(), t.getCompleteTime()); return duration.toHours(); }) .average() .orElse(0); analysis.setAvgCompletionHours(BigDecimal.valueOf(avgHours).setScale(2, RoundingMode.HALF_UP)); } else { analysis.setAvgCompletionHours(BigDecimal.ZERO); } result.add(analysis); } // 按任务数量降序排序(有任务的排前面) result.sort((a, b) -> Integer.compare(b.getTotalTasks(), a.getTotalTasks())); return result; } /** * 构建需求部门分析数据 */ private List buildDemandDeptAnalysis(List tasks) { // 按需求部门ID分组 Map> tasksByDept = tasks.stream() .filter(t -> t.getPublisherDeptId() != null) .collect(Collectors.groupingBy(Task::getPublisherDeptId)); List result = new ArrayList<>(); // 获取所有部门 List departments = departmentMapper.selectList(null); Map deptNames = departments.stream() .collect(Collectors.toMap(d -> d.getId(), d -> d.getDeptName(), (a, b) -> a)); // 🔥 获取每个部门的人数(包含部门负责人) Map deptMemberCounts = new HashMap<>(); for (Long deptId : tasksByDept.keySet()) { // 查询该部门及其子部门的所有用户数量 List deptAndChildIds = getDeptAndChildIds(deptId); Long memberCount = userMapper.selectCount(new LambdaQueryWrapper() .in(User::getDeptId, deptAndChildIds) .eq(User::getStatus, 1)); // 只统计启用状态的用户 // 🔥 检查部门负责人是否已被统计,如果负责人的dept_id不在该部门内,则额外加上 int leaderCount = countLeadersNotInDept(deptAndChildIds); deptMemberCounts.put(deptId, memberCount.intValue() + leaderCount); } for (Map.Entry> entry : tasksByDept.entrySet()) { Long deptId = entry.getKey(); List deptTasks = entry.getValue(); DimensionAnalysisVO.DemandDeptAnalysis analysis = new DimensionAnalysisVO.DemandDeptAnalysis(); analysis.setDeptId(deptId); analysis.setDeptName(deptNames.getOrDefault(deptId, "未知部门")); analysis.setTotalTasks(deptTasks.size()); // 🔥 设置部门人数 int memberCount = deptMemberCounts.getOrDefault(deptId, 0); analysis.setMemberCount(memberCount); // 🔥 计算人均任务数 if (memberCount > 0) { analysis.setAvgTasksPerMember(BigDecimal.valueOf((double) deptTasks.size() / memberCount) .setScale(2, RoundingMode.HALF_UP)); } else { analysis.setAvgTasksPerMember(BigDecimal.ZERO); } long completedCount = deptTasks.stream() .filter(t -> "completed".equals(t.getStatus())) .count(); analysis.setCompletedTasks((int) completedCount); long inProgressCount = deptTasks.stream() .filter(t -> "in_progress".equals(t.getStatus())) .count(); analysis.setInProgressTasks((int) inProgressCount); long pendingCount = deptTasks.stream() .filter(t -> t.getStatus() != null && t.getStatus().contains("pending")) .count(); analysis.setPendingTasks((int) pendingCount); // 计算总积分 BigDecimal totalScore = deptTasks.stream() .filter(t -> t.getScore() != null) .map(Task::getScore) .reduce(BigDecimal.ZERO, BigDecimal::add); analysis.setTotalScore(totalScore); // 计算完成率 if (deptTasks.size() > 0) { analysis.setCompletionRate(BigDecimal.valueOf(completedCount * 100.0 / deptTasks.size()) .setScale(2, RoundingMode.HALF_UP)); } else { analysis.setCompletionRate(BigDecimal.ZERO); } // 计算总数量 int totalQuantity = deptTasks.stream() .filter(t -> t.getQuantity() != null) .mapToInt(Task::getQuantity) .sum(); analysis.setTotalQuantity(totalQuantity); // 需求部门不计算平均完成时长,设置为null analysis.setAvgCompletionHours(null); result.add(analysis); } // 按任务数量降序排序 result.sort((a, b) -> Integer.compare(b.getTotalTasks(), a.getTotalTasks())); return result; } /** * 构建平台分析数据 */ private List buildPlatformAnalysis(List tasks) { // 按平台ID分组 Map> tasksByPlatform = tasks.stream() .filter(t -> t.getPlatformId() != null) .collect(Collectors.groupingBy(Task::getPlatformId)); List result = new ArrayList<>(); // 获取所有平台 List platforms = platformMapper.selectList(null); Map platformNames = platforms.stream() .collect(Collectors.toMap(Platform::getId, Platform::getName, (a, b) -> a)); for (Map.Entry> entry : tasksByPlatform.entrySet()) { Long platformId = entry.getKey(); List platformTasks = entry.getValue(); DimensionAnalysisVO.PlatformAnalysis analysis = new DimensionAnalysisVO.PlatformAnalysis(); analysis.setPlatformId(platformId); analysis.setPlatformName(platformNames.getOrDefault(platformId, "未知平台")); analysis.setTotalTasks(platformTasks.size()); long completedCount = platformTasks.stream() .filter(t -> "completed".equals(t.getStatus())) .count(); analysis.setCompletedTasks((int) completedCount); long inProgressCount = platformTasks.stream() .filter(t -> "in_progress".equals(t.getStatus())) .count(); analysis.setInProgressTasks((int) inProgressCount); // 计算总积分 BigDecimal totalScore = platformTasks.stream() .filter(t -> t.getScore() != null) .map(Task::getScore) .reduce(BigDecimal.ZERO, BigDecimal::add); analysis.setTotalScore(totalScore); // 计算完成率 if (platformTasks.size() > 0) { analysis.setCompletionRate(BigDecimal.valueOf(completedCount * 100.0 / platformTasks.size()) .setScale(2, RoundingMode.HALF_UP)); } else { analysis.setCompletionRate(BigDecimal.ZERO); } // 计算总数量 int totalQuantity = platformTasks.stream() .filter(t -> t.getQuantity() != null) .mapToInt(Task::getQuantity) .sum(); analysis.setTotalQuantity(totalQuantity); result.add(analysis); } // 按任务数量降序排序 result.sort((a, b) -> Integer.compare(b.getTotalTasks(), a.getTotalTasks())); return result; } // ==================== 一级需求部门统计 ==================== /** * 获取一级需求部门统计数据 */ public PrimaryDeptStatisticsVO getPrimaryDeptStatistics(Long deptId, LocalDateTime startTime, LocalDateTime endTime) { log.info("获取一级需求部门统计: deptId={}, startTime={}, endTime={}", deptId, startTime, endTime); PrimaryDeptStatisticsVO vo = new PrimaryDeptStatisticsVO(); // 查询一级需求部门的任务 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Task::getIsPrimaryTask, 1) .ge(Task::getCreateTime, startTime) .le(Task::getCreateTime, endTime) .eq(Task::getDeleted, 0); if (deptId != null) { queryWrapper.eq(Task::getPrimaryPublisherDeptId, deptId); } List tasks = taskMapper.selectList(queryWrapper); // 统计各项数据 vo.setTotalTasks(tasks.size()); long approvedCount = tasks.stream() .filter(t -> !"rejected_by_demand".equals(t.getApprovalStatus()) && !"draft".equals(t.getStatus())) .count(); vo.setApprovedTasks((int) approvedCount); long rejectedCount = tasks.stream() .filter(t -> "rejected_by_demand".equals(t.getApprovalStatus())) .count(); vo.setRejectedTasks((int) rejectedCount); long pendingCount = tasks.stream() .filter(t -> "pending_primary_approval".equals(t.getApprovalStatus()) || "pending_detail_completion".equals(t.getApprovalStatus())) .count(); vo.setPendingTasks((int) pendingCount); long inProgressCount = tasks.stream() .filter(t -> "in_progress".equals(t.getStatus())) .count(); vo.setInProgressTasks((int) inProgressCount); long completedCount = tasks.stream() .filter(t -> "completed".equals(t.getStatus())) .count(); vo.setCompletedTasks((int) completedCount); // 计算消耗积分 BigDecimal consumedScore = tasks.stream() .filter(t -> t.getScore() != null && !"draft".equals(t.getStatus()) && !"rejected_by_demand".equals(t.getApprovalStatus())) .map(Task::getScore) .reduce(BigDecimal.ZERO, BigDecimal::add); vo.setConsumedScore(consumedScore); // TODO: 计算增长率(需要对比上一周期) vo.setTaskGrowthRate(BigDecimal.ZERO); vo.setApprovalGrowthRate(BigDecimal.ZERO); vo.setRejectGrowthRate(BigDecimal.ZERO); vo.setScoreGrowthRate(BigDecimal.ZERO); return vo; } /** * 获取一级需求部门排名 * 显示所有一级需求部门(包括没有发起过任务的部门) */ public List getPrimaryDeptRanking(LocalDateTime startTime, LocalDateTime endTime) { log.info("获取一级需求部门排名: startTime={}, endTime={}", startTime, endTime); List result = new java.util.ArrayList<>(); // 1. 获取所有一级需求部门 List allPrimaryDepts = departmentMapper.selectList( new LambdaQueryWrapper() .eq(Department::getDeptType, "primary_demand") .eq(Department::getStatus, 1) .eq(Department::getDeleted, 0) ); log.info("找到 {} 个一级需求部门", allPrimaryDepts.size()); // 2. 查询时间范围内的一级需求任务 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Task::getIsPrimaryTask, 1) .ge(Task::getCreateTime, startTime) .le(Task::getCreateTime, endTime) .eq(Task::getDeleted, 0); List allTasks = taskMapper.selectList(queryWrapper); // 3. 按一级部门分组 java.util.Map> tasksByDept = allTasks.stream() .filter(t -> t.getPrimaryPublisherDeptId() != null) .collect(java.util.stream.Collectors.groupingBy(Task::getPrimaryPublisherDeptId)); // 4. 为每个一级需求部门创建统计数据 for (Department dept : allPrimaryDepts) { Long deptId = dept.getId(); List deptTasks = tasksByDept.getOrDefault(deptId, new java.util.ArrayList<>()); PrimaryDeptRankingVO vo = new PrimaryDeptRankingVO(); vo.setDeptId(deptId); vo.setDeptName(dept.getDeptName()); // 获取一级部门和二级部门名称(向上查找部门层级) List hierarchy = new java.util.ArrayList<>(); Department current = dept; while (current != null && current.getParentId() != null && current.getParentId() > 0) { Department parent = departmentMapper.selectById(current.getParentId()); if (parent != null) { hierarchy.add(0, parent); // 插入到列表开头 } current = parent; } // hierarchy 现在从根部门到当前部门的父部门 // 一级部门是 hierarchy[0],二级部门是 hierarchy[1](如果存在) if (hierarchy.size() >= 1) { vo.setFirstLevelDeptName(hierarchy.get(0).getDeptName()); } if (hierarchy.size() >= 2) { vo.setSecondLevelDeptName(hierarchy.get(1).getDeptName()); } vo.setTaskCount(deptTasks.size()); // 计算审核通过率 if (deptTasks.size() > 0) { long approvedCount = deptTasks.stream() .filter(t -> !"rejected_by_demand".equals(t.getApprovalStatus()) && !"draft".equals(t.getStatus())) .count(); vo.setApprovalRate(BigDecimal.valueOf(approvedCount * 100.0 / deptTasks.size()) .setScale(2, RoundingMode.HALF_UP)); } else { vo.setApprovalRate(BigDecimal.ZERO); } // 计算消耗积分 BigDecimal consumedScore = deptTasks.stream() .filter(t -> t.getScore() != null) .map(Task::getScore) .reduce(BigDecimal.ZERO, BigDecimal::add); vo.setConsumedScore(consumedScore); // 计算平均响应时间(从发起到审核的时间) double avgResponseHours = deptTasks.stream() .filter(t -> t.getDemandApprovalTime() != null && t.getCreateTime() != null) .mapToDouble(t -> { long minutes = java.time.Duration.between(t.getCreateTime(), t.getDemandApprovalTime()).toMinutes(); return minutes / 60.0; }) .average() .orElse(0.0); vo.setAvgResponseHours(Math.round(avgResponseHours * 10) / 10.0); // 完成和进行中数量 vo.setCompletedCount((int) deptTasks.stream().filter(t -> "completed".equals(t.getStatus())).count()); vo.setInProgressCount((int) deptTasks.stream().filter(t -> "in_progress".equals(t.getStatus())).count()); result.add(vo); } // 按任务数量降序排序并设置排名 result.sort((a, b) -> Integer.compare(b.getTaskCount(), a.getTaskCount())); for (int i = 0; i < result.size(); i++) { result.get(i).setRank(i + 1); } log.info("一级需求部门排名统计完成,共 {} 个部门", result.size()); return result; } /** * 获取一级需求流转效率分析 */ public PrimaryTaskFlowEfficiencyVO getPrimaryTaskFlowEfficiency(LocalDateTime startTime, LocalDateTime endTime) { log.info("获取一级需求流转效率: startTime={}, endTime={}", startTime, endTime); PrimaryTaskFlowEfficiencyVO vo = new PrimaryTaskFlowEfficiencyVO(); // 查询已完成的一级需求任务 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Task::getIsPrimaryTask, 1) .eq(Task::getStatus, "completed") .ge(Task::getCreateTime, startTime) .le(Task::getCreateTime, endTime) .eq(Task::getDeleted, 0); List completedTasks = taskMapper.selectList(queryWrapper); vo.setTaskCount(completedTasks.size()); if (completedTasks.isEmpty()) { vo.setAvgSubmitToReviewHours(0.0); vo.setAvgReviewToCompleteHours(0.0); vo.setAvgCompleteToPublishHours(0.0); vo.setAvgPublishToFinishHours(0.0); vo.setAvgTotalHours(0.0); return vo; } // 计算各阶段平均耗时 double avgSubmitToReview = completedTasks.stream() .filter(t -> t.getDemandApprovalTime() != null && t.getCreateTime() != null) .mapToDouble(t -> java.time.Duration.between(t.getCreateTime(), t.getDemandApprovalTime()).toMinutes() / 60.0) .average() .orElse(0.0); vo.setAvgSubmitToReviewHours(Math.round(avgSubmitToReview * 10) / 10.0); // 审核到完善(这里简化处理,实际需要记录明细完善时间) vo.setAvgReviewToCompleteHours(0.0); // TODO: 需要增加明细完善时间字段 // 完善到发布(接单部门审核时间) double avgCompleteToPublish = completedTasks.stream() .filter(t -> t.getReceivingApprovalTime() != null && t.getDemandApprovalTime() != null) .mapToDouble(t -> java.time.Duration.between(t.getDemandApprovalTime(), t.getReceivingApprovalTime()).toMinutes() / 60.0) .average() .orElse(0.0); vo.setAvgCompleteToPublishHours(Math.round(avgCompleteToPublish * 10) / 10.0); // 发布到完成 double avgPublishToFinish = completedTasks.stream() .filter(t -> t.getCompleteTime() != null && t.getReceivingApprovalTime() != null) .mapToDouble(t -> java.time.Duration.between(t.getReceivingApprovalTime(), t.getCompleteTime()).toMinutes() / 60.0) .average() .orElse(0.0); vo.setAvgPublishToFinishHours(Math.round(avgPublishToFinish * 10) / 10.0); // 总耗时 double avgTotal = completedTasks.stream() .filter(t -> t.getCompleteTime() != null && t.getCreateTime() != null) .mapToDouble(t -> java.time.Duration.between(t.getCreateTime(), t.getCompleteTime()).toMinutes() / 60.0) .average() .orElse(0.0); vo.setAvgTotalHours(Math.round(avgTotal * 10) / 10.0); return vo; } /** * 获取品牌任务分布(一级需求) */ public List getPrimaryTaskBrandDistribution(LocalDateTime startTime, LocalDateTime endTime) { log.info("获取品牌任务分布: startTime={}, endTime={}", startTime, endTime); List result = new java.util.ArrayList<>(); // 查询一级需求部门的任务 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Task::getIsPrimaryTask, 1) .ge(Task::getCreateTime, startTime) .le(Task::getCreateTime, endTime) .eq(Task::getDeleted, 0); List allTasks = taskMapper.selectList(queryWrapper); int totalTaskCount = allTasks.size(); // 按品牌分组 java.util.Map> tasksByBrand = allTasks.stream() .filter(t -> t.getBrandId() != null) .collect(java.util.stream.Collectors.groupingBy(Task::getBrandId)); for (java.util.Map.Entry> entry : tasksByBrand.entrySet()) { Long brandId = entry.getKey(); List brandTasks = entry.getValue(); BrandTaskDistributionVO vo = new BrandTaskDistributionVO(); vo.setBrandId(brandId); // 获取品牌名称 Brand brand = brandMapper.selectById(brandId); vo.setBrandName(brand != null ? brand.getName() : "未知品牌"); vo.setTaskCount(brandTasks.size()); // 计算占比 if (totalTaskCount > 0) { vo.setPercentage(BigDecimal.valueOf(brandTasks.size() * 100.0 / totalTaskCount) .setScale(2, RoundingMode.HALF_UP)); } else { vo.setPercentage(BigDecimal.ZERO); } // 获取负责部门 Department responsibleDept = departmentService.getDemandDeptByBrandId(brandId); if (responsibleDept != null) { vo.setResponsibleDeptId(responsibleDept.getId()); vo.setResponsibleDeptName(responsibleDept.getDeptName()); } // 计算消耗积分 BigDecimal consumedScore = brandTasks.stream() .filter(t -> t.getScore() != null) .map(Task::getScore) .reduce(BigDecimal.ZERO, BigDecimal::add); vo.setConsumedScore(consumedScore); // 完成数量 vo.setCompletedCount((int) brandTasks.stream().filter(t -> "completed".equals(t.getStatus())).count()); result.add(vo); } // 按任务数量降序排序 result.sort((a, b) -> Integer.compare(b.getTaskCount(), a.getTaskCount())); return result; } /** * 获取一级需求部门用户排名(用户维度统计) */ public List getPrimaryDeptUserRanking(LocalDateTime startTime, LocalDateTime endTime) { log.info("获取一级需求部门用户排名: startTime={}, endTime={}", startTime, endTime); List result = new java.util.ArrayList<>(); // 1. 查询时间范围内的一级需求任务 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Task::getIsPrimaryTask, 1) .ge(Task::getCreateTime, startTime) .le(Task::getCreateTime, endTime) .eq(Task::getDeleted, 0) .isNotNull(Task::getPrimaryPublisherId); List allTasks = taskMapper.selectList(queryWrapper); // 2. 按用户分组 java.util.Map> tasksByUser = allTasks.stream() .filter(t -> t.getPrimaryPublisherId() != null) .collect(java.util.stream.Collectors.groupingBy(Task::getPrimaryPublisherId)); // 3. 为每个用户创建统计数据 for (java.util.Map.Entry> entry : tasksByUser.entrySet()) { Long userId = entry.getKey(); List userTasks = entry.getValue(); // 获取用户信息 User user = userMapper.selectById(userId); if (user == null || user.getDeleted() == 1 || user.getStatus() == 0) { continue; // 跳过已删除或禁用的用户 } PrimaryDeptUserRankingVO vo = new PrimaryDeptUserRankingVO(); vo.setUserId(userId); vo.setUserName(user.getRealName() != null && !user.getRealName().isEmpty() ? user.getRealName() : user.getUsername()); // 获取用户部门信息 if (user.getDeptId() != null) { Department userDept = departmentMapper.selectById(user.getDeptId()); if (userDept != null) { vo.setDeptId(userDept.getId()); vo.setDeptName(userDept.getDeptName()); // 获取一级部门和二级部门名称(向上查找部门层级) List hierarchy = new java.util.ArrayList<>(); Department current = userDept; while (current != null && current.getParentId() != null && current.getParentId() > 0) { Department parent = departmentMapper.selectById(current.getParentId()); if (parent != null) { hierarchy.add(0, parent); // 插入到列表开头 } current = parent; } // hierarchy 现在从根部门到当前部门的父部门 // 一级部门是 hierarchy[0],二级部门是 hierarchy[1](如果存在) if (hierarchy.size() >= 1) { vo.setFirstLevelDeptName(hierarchy.get(0).getDeptName()); } if (hierarchy.size() >= 2) { vo.setSecondLevelDeptName(hierarchy.get(1).getDeptName()); } } } vo.setTaskCount(userTasks.size()); // 统计审核通过数 long approvedCount = userTasks.stream() .filter(t -> !"rejected_by_demand".equals(t.getApprovalStatus()) && !"draft".equals(t.getStatus())) .count(); vo.setApprovedCount((int) approvedCount); // 统计审核驳回数 long rejectedCount = userTasks.stream() .filter(t -> "rejected_by_demand".equals(t.getApprovalStatus())) .count(); vo.setRejectedCount((int) rejectedCount); // 计算审核通过率 if (userTasks.size() > 0) { vo.setApprovalRate(BigDecimal.valueOf(approvedCount * 100.0 / userTasks.size()) .setScale(2, RoundingMode.HALF_UP)); } else { vo.setApprovalRate(BigDecimal.ZERO); } // 计算消耗积分 BigDecimal consumedScore = userTasks.stream() .filter(t -> t.getScore() != null) .map(Task::getScore) .reduce(BigDecimal.ZERO, BigDecimal::add); vo.setConsumedScore(consumedScore); // 统计已完成任务数 long completedCount = userTasks.stream() .filter(t -> "completed".equals(t.getStatus())) .count(); vo.setCompletedCount((int) completedCount); // 统计进行中任务数 long inProgressCount = userTasks.stream() .filter(t -> "in_progress".equals(t.getStatus())) .count(); vo.setInProgressCount((int) inProgressCount); // 统计待审核任务数 long pendingCount = userTasks.stream() .filter(t -> "pending_primary_approval".equals(t.getApprovalStatus()) || "pending_detail_completion".equals(t.getApprovalStatus())) .count(); vo.setPendingCount((int) pendingCount); result.add(vo); } // 4. 按任务数量降序排序 result.sort((a, b) -> Integer.compare(b.getTaskCount(), a.getTaskCount())); // 5. 设置排名 for (int i = 0; i < result.size(); i++) { result.get(i).setRank(i + 1); } return result; } /** * 构建任务类型分析数据(一级、二级、三级分类) * 任务通常只关联三级类型,需要向上聚合到二级和一级 * @param tasks 任务列表 * @return 按层级分组的任务类型分析数据,key为层级(1/2/3) */ private Map> buildTaskTypeAnalysis(List tasks) { log.info("开始构建任务类型分析数据,任务数量: {}", tasks.size()); // 获取所有任务类型 List allTypes = demandTypeMapper.selectList( new LambdaQueryWrapper() .eq(DemandType::getDeleted, 0) ); // 构建类型ID到类型的映射 Map typeMap = allTypes.stream() .collect(Collectors.toMap(DemandType::getId, t -> t, (a, b) -> a)); // 构建父类型名称映射 Map typeNameMap = allTypes.stream() .collect(Collectors.toMap(DemandType::getId, t -> t.getName() != null ? t.getName() : "未知", (a, b) -> a)); // 计算每个类型的层级 Map typeLevelMap = new HashMap<>(); for (DemandType type : allTypes) { int level = calculateTypeLevel(type.getId(), typeMap); typeLevelMap.put(type.getId(), level); } // 按各层级类型ID分组统计任务(向上聚合) // key: 类型ID, value: 任务列表 Map> tasksByLevel1TypeId = new HashMap<>(); Map> tasksByLevel2TypeId = new HashMap<>(); Map> tasksByLevel3TypeId = new HashMap<>(); for (Task task : tasks) { Set typeIds = new HashSet<>(); if (task.getTaskTypeId() != null) { typeIds.add(task.getTaskTypeId()); } if (task.getTaskTypeIds() != null && !task.getTaskTypeIds().trim().isEmpty()) { String[] ids = task.getTaskTypeIds().split(","); for (String id : ids) { try { typeIds.add(Long.parseLong(id.trim())); } catch (NumberFormatException e) { // 忽略无效ID } } } for (Long typeId : typeIds) { DemandType type = typeMap.get(typeId); if (type == null) { continue; } int level = typeLevelMap.getOrDefault(typeId, 1); // 根据当前类型的层级,向上聚合到各级 if (level == 3) { // 三级类型:直接加入三级统计 tasksByLevel3TypeId.computeIfAbsent(typeId, k -> new ArrayList<>()).add(task); // 向上聚合到二级 Long level2Id = type.getParentId(); if (level2Id != null) { tasksByLevel2TypeId.computeIfAbsent(level2Id, k -> new ArrayList<>()).add(task); // 向上聚合到一级 DemandType level2Type = typeMap.get(level2Id); if (level2Type != null && level2Type.getParentId() != null) { Long level1Id = level2Type.getParentId(); tasksByLevel1TypeId.computeIfAbsent(level1Id, k -> new ArrayList<>()).add(task); } } } else if (level == 2) { // 二级类型:加入二级统计 tasksByLevel2TypeId.computeIfAbsent(typeId, k -> new ArrayList<>()).add(task); // 向上聚合到一级 Long level1Id = type.getParentId(); if (level1Id != null) { tasksByLevel1TypeId.computeIfAbsent(level1Id, k -> new ArrayList<>()).add(task); } } else if (level == 1) { // 一级类型:直接加入一级统计 tasksByLevel1TypeId.computeIfAbsent(typeId, k -> new ArrayList<>()).add(task); } } } log.info("各层级涉及的类型数量: 一级={}, 二级={}, 三级={}", tasksByLevel1TypeId.size(), tasksByLevel2TypeId.size(), tasksByLevel3TypeId.size()); // 构建各层级的分析数据 Map> result = new HashMap<>(); result.put(1, new ArrayList<>()); result.put(2, new ArrayList<>()); result.put(3, new ArrayList<>()); // 构建一级分析数据 for (Map.Entry> entry : tasksByLevel1TypeId.entrySet()) { Long typeId = entry.getKey(); List typeTasks = entry.getValue(); DemandType type = typeMap.get(typeId); if (type != null && !typeTasks.isEmpty()) { DimensionAnalysisVO.TaskTypeAnalysis analysis = buildSingleTaskTypeAnalysis( type, typeTasks, 1, typeMap, typeNameMap ); result.get(1).add(analysis); } } // 构建二级分析数据 for (Map.Entry> entry : tasksByLevel2TypeId.entrySet()) { Long typeId = entry.getKey(); List typeTasks = entry.getValue(); DemandType type = typeMap.get(typeId); if (type != null && !typeTasks.isEmpty()) { DimensionAnalysisVO.TaskTypeAnalysis analysis = buildSingleTaskTypeAnalysis( type, typeTasks, 2, typeMap, typeNameMap ); result.get(2).add(analysis); } } // 构建三级分析数据 for (Map.Entry> entry : tasksByLevel3TypeId.entrySet()) { Long typeId = entry.getKey(); List typeTasks = entry.getValue(); DemandType type = typeMap.get(typeId); if (type != null && !typeTasks.isEmpty()) { DimensionAnalysisVO.TaskTypeAnalysis analysis = buildSingleTaskTypeAnalysis( type, typeTasks, 3, typeMap, typeNameMap ); result.get(3).add(analysis); } } // 对各层级按任务数量降序排序 for (int level = 1; level <= 3; level++) { List levelList = result.get(level); levelList.sort((a, b) -> { Integer totalA = a.getTotalTasks() != null ? a.getTotalTasks() : 0; Integer totalB = b.getTotalTasks() != null ? b.getTotalTasks() : 0; return totalB.compareTo(totalA); }); } log.info("任务类型分析完成: 一级={}, 二级={}, 三级={}", result.get(1).size(), result.get(2).size(), result.get(3).size()); return result; } /** * 计算任务类型的层级 * @param typeId 类型ID * @param typeMap 类型映射 * @return 层级(1/2/3) */ private int calculateTypeLevel(Long typeId, Map typeMap) { DemandType type = typeMap.get(typeId); if (type == null) { return 1; } int level = 1; Long parentId = type.getParentId(); while (parentId != null && level < 3) { level++; DemandType parent = typeMap.get(parentId); if (parent == null) { break; } parentId = parent.getParentId(); } return level; } /** * 构建单个任务类型的分析数据 */ private DimensionAnalysisVO.TaskTypeAnalysis buildSingleTaskTypeAnalysis( DemandType type, List typeTasks, int level, Map typeMap, Map typeNameMap) { DimensionAnalysisVO.TaskTypeAnalysis analysis = new DimensionAnalysisVO.TaskTypeAnalysis(); analysis.setTypeId(type.getId()); analysis.setTypeName(type.getName() != null ? type.getName() : "未知"); analysis.setParentId(type.getParentId()); analysis.setLevel(level); analysis.setUnitScore(type.getUnitScore()); analysis.setEstimatedHours(type.getEstimatedHours()); // 设置父级名称 if (type.getParentId() != null) { analysis.setParentName(typeNameMap.getOrDefault(type.getParentId(), "未知")); } // 统计任务数量 analysis.setTotalTasks(typeTasks.size()); long completedCount = typeTasks.stream() .filter(t -> "completed".equals(t.getStatus())) .count(); analysis.setCompletedTasks((int) completedCount); long inProgressCount = typeTasks.stream() .filter(t -> "in_progress".equals(t.getStatus())) .count(); analysis.setInProgressTasks((int) inProgressCount); long pendingCount = typeTasks.stream() .filter(t -> t.getStatus() != null && t.getStatus().contains("pending")) .count(); analysis.setPendingTasks((int) pendingCount); // 计算总数量 int totalQuantity = typeTasks.stream() .filter(t -> t.getQuantity() != null) .mapToInt(Task::getQuantity) .sum(); analysis.setTotalQuantity(totalQuantity); // 计算总积分(优先使用 scoreReward) BigDecimal totalScore = typeTasks.stream() .map(t -> { if (t.getScoreReward() != null && t.getScoreReward().compareTo(BigDecimal.ZERO) > 0) { return t.getScoreReward(); } return t.getScore() != null ? t.getScore() : BigDecimal.ZERO; }) .reduce(BigDecimal.ZERO, BigDecimal::add); analysis.setTotalScore(totalScore); // 计算平均积分 if (typeTasks.size() > 0) { analysis.setAvgScore(totalScore.divide(BigDecimal.valueOf(typeTasks.size()), 2, RoundingMode.HALF_UP)); } else { analysis.setAvgScore(BigDecimal.ZERO); } // 计算完成率 if (typeTasks.size() > 0) { BigDecimal completionRate = BigDecimal.valueOf(completedCount) .divide(BigDecimal.valueOf(typeTasks.size()), 4, RoundingMode.HALF_UP) .multiply(BigDecimal.valueOf(100)); analysis.setCompletionRate(completionRate); } else { analysis.setCompletionRate(BigDecimal.ZERO); } // 计算准时率(已完成任务中准时完成的比例) if (completedCount > 0) { long onTimeCount = typeTasks.stream() .filter(t -> "completed".equals(t.getStatus())) .filter(t -> { if (t.getDeadline() == null || t.getActualEndTime() == null) { return true; // 没有截止时间或完成时间的视为准时 } return !t.getActualEndTime().isAfter(t.getDeadline()); }) .count(); analysis.setOnTimeRate((double) onTimeCount / completedCount * 100); } else { analysis.setOnTimeRate(0.0); } // 计算平均完成时长(小时) List completedTasks = typeTasks.stream() .filter(t -> "completed".equals(t.getStatus()) && t.getActualStartTime() != null && t.getActualEndTime() != null) .collect(Collectors.toList()); if (!completedTasks.isEmpty()) { double totalHours = completedTasks.stream() .mapToDouble(t -> Duration.between(t.getActualStartTime(), t.getActualEndTime()).toMinutes() / 60.0) .sum(); analysis.setAvgCompletionHours(totalHours / completedTasks.size()); } else { analysis.setAvgCompletionHours(0.0); } return analysis; } }