package com.designhub.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.designhub.entity.*; import com.designhub.mapper.*; import com.designhub.vo.DemandStatisticsVO; import com.designhub.vo.DesignerPerformanceVO; import com.designhub.vo.ProcessEfficiencyVO; import com.designhub.vo.WarningTaskVO; 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 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; /** * 获取需求统计数据(根据角色返回不同维度的数据) * @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) { // 获取当前用户角色和部门信息 List roles = roleService.getRolesByUserId(currentUserId); User currentUser = userMapper.selectById(currentUserId); boolean isAdmin = roles.stream() .anyMatch(role -> "ADMIN".equals(role.getRoleCode()) || "SUPER_ADMIN".equals(role.getRoleCode())); // 检查 isAdmin 字段 if (!isAdmin && currentUser != null && Integer.valueOf(1).equals(currentUser.getIsAdmin())) { isAdmin = true; } boolean isDeptHead = roles.stream() .anyMatch(role -> "DEMAND_DEPT_HEAD".equals(role.getRoleCode()) || "RECEIVING_DEPT_SUPERVISOR".equals(role.getRoleCode())); log.info("获取需求统计数据: currentUserId={}, isAdmin={}, isDeptHead={}, userDeptId={}, dateRange={}, deptId={}, userId={}, month={}", currentUserId, isAdmin, isDeptHead, currentUser != null ? currentUser.getDeptId() : null, 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); // 根据角色和参数决定查询范围 if (isAdmin) { // 管理员:可以通过参数查看特定部门或用户的数据,否则查看所有数据 if (deptId != null) { queryWrapper.eq(Task::getPublisherDeptId, deptId); log.info("管理员查看部门数据: deptId={}", deptId); } else if (userId != null) { queryWrapper.eq(Task::getPublisherId, userId); log.info("管理员查看用户数据: userId={}", userId); } else { log.info("管理员查看全部数据"); } } else if (isDeptHead && currentUser != null && currentUser.getDeptId() != null) { // 部门负责人:查看本部门的所有任务 queryWrapper.eq(Task::getPublisherDeptId, currentUser.getDeptId()); log.info("部门负责人查看部门数据: deptId={}", currentUser.getDeptId()); } else { // 普通用户:只能查看自己发布的任务 queryWrapper.eq(Task::getPublisherId, currentUserId); log.info("普通用户查看个人数据: userId={}", currentUserId); } 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(); BigDecimal totalScore = userTasks.stream() .map(Task::getScoreReward) .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()) .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(); BigDecimal scoreUsed = dateTasks.stream() .map(Task::getScoreReward) .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) { // 优先使用 taskTypeId,如果为空则使用 demandTypeId Map> tasksByType = tasks.stream() .filter(t -> t.getTaskTypeId() != null || t.getDemandTypeId() != null) .collect(Collectors.groupingBy(t -> { if (t.getTaskTypeId() != null) { return t.getTaskTypeId(); } return t.getDemandTypeId(); })); int totalTasks = tasks.size(); List distribution = new ArrayList<>(); // 预加载所有任务类型名称 Map typeNameMap = new HashMap<>(); for (Long typeId : tasksByType.keySet()) { try { DemandType demandType = demandTypeMapper.selectById(typeId); if (demandType != null) { typeNameMap.put(typeId, demandType.getName()); } else { typeNameMap.put(typeId, "未知类型"); } } catch (Exception e) { typeNameMap.put(typeId, "类型" + typeId); } } for (Map.Entry> entry : tasksByType.entrySet()) { List typeTasks = entry.getValue(); int count = typeTasks.size(); double percentage = totalTasks > 0 ? (double) count / totalTasks * 100 : 0.0; // 优先使用 scoreReward,其次使用 score BigDecimal totalScore = typeTasks.stream() .map(t -> { if (t.getScoreReward() != null && t.getScoreReward().compareTo(BigDecimal.ZERO) > 0) { return t.getScoreReward(); } return t.getScore() != null ? BigDecimal.valueOf(t.getScore()) : BigDecimal.ZERO; }) .reduce(BigDecimal.ZERO, BigDecimal::add); String typeName = typeNameMap.getOrDefault(entry.getKey(), "类型" + entry.getKey()); distribution.add(DemandStatisticsVO.TypeDistribution.builder() .typeName(typeName) .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(); int pendingDemands = (int) tasks.stream() .filter(t -> Arrays.asList("pending", "in_progress", "submitted").contains(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) .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": return now.minusMonths(1); 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) { // 权限检查 List roles = roleService.getRolesByUserId(currentUserId); User currentUser = userMapper.selectById(currentUserId); boolean isAdmin = roles.stream() .anyMatch(role -> "ADMIN".equals(role.getRoleCode()) || "SUPER_ADMIN".equals(role.getRoleCode())); if (!isAdmin && currentUser != null && Integer.valueOf(1).equals(currentUser.getIsAdmin())) { isAdmin = true; } // 计算时间范围和当前月份 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(isAdmin, currentUser, 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()); int receivedCount = designerTasks.size(); int completedCount = (int) designerTasks.stream() .filter(t -> "completed".equals(t.getStatus())) .count(); int inProgressCount = (int) designerTasks.stream() .filter(t -> "in_progress".equals(t.getStatus())) .count(); // 计算准时完成数 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的默认准时 } } } 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); for (Task task : designerTasks) { if ("completed".equals(task.getStatus())) { // 计算任务积分 BigDecimal taskScore = BigDecimal.ZERO; if (task.getScoreReward() != null && task.getScoreReward().compareTo(BigDecimal.ZERO) > 0) { taskScore = task.getScoreReward(); } else if (task.getScore() != null) { taskScore = BigDecimal.valueOf(task.getScore()); } totalScore = totalScore.add(taskScore); // 计算延误扣分(如果超时完成) if (task.getDeadline() != null && task.getActualEndTime() != null && task.getActualEndTime().isAfter(task.getDeadline())) { // 计算延误小时数 long overdueHours = Duration.between(task.getDeadline(), task.getActualEndTime()).toHours(); if (overdueHours > 0 && taskScore.compareTo(BigDecimal.ZERO) > 0) { // 扣分 = 任务积分 * 延误小时数 * 扣分比例 BigDecimal taskDeduction = taskScore .multiply(BigDecimal.valueOf(overdueHours)) .multiply(deductionRate) .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); // 扣分不能超过任务积分 if (taskDeduction.compareTo(taskScore) > 0) { taskDeduction = taskScore; } deductionScore = deductionScore.add(taskDeduction); } } } } // 净积分 = 总积分 - 扣分 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) -> b.getCompletedCount().compareTo(a.getCompletedCount())); // 按当前负载排序 workloadStats.sort((a, b) -> b.getCurrentLoad().compareTo(a.getCurrentLoad())); // 构建汇总数据 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(boolean isAdmin, User currentUser, Long deptId) { LambdaQueryWrapper userQuery = new LambdaQueryWrapper() .eq(User::getStatus, 1); if (deptId != null) { userQuery.eq(User::getDeptId, deptId); } else if (!isAdmin && currentUser != null && currentUser.getDeptId() != null) { // 非管理员只能看本部门 userQuery.eq(User::getDeptId, currentUser.getDeptId()); } 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; } // ==================== 流程效率统计 ==================== /** * 获取流程效率数据 */ 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) -> { int levelCompare = "urgent".equals(a.getWarningLevel()) ? -1 : ("urgent".equals(b.getWarningLevel()) ? 1 : 0); if (levelCompare != 0) return levelCompare; return b.getHoursInfo().compareTo(a.getHoursInfo()); }); 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; } }