515 lines
20 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
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;
/**
* 获取需求统计数据(根据角色返回不同维度的数据)
* @param currentUserId 当前登录用户ID
* @param dateRange 时间范围
* @param deptId 部门ID(可选,用于管理员查看特定部门)
* @param userId 用户ID(可选,用于管理员查看特定用户)
*/
public DemandStatisticsVO getDemandStatistics(Long currentUserId, String dateRange, Long deptId, Long userId) {
// 获取当前用户角色和部门信息
List<Role> roles = roleService.getRolesByUserId(currentUserId);
User currentUser = userMapper.selectById(currentUserId);
boolean isAdmin = roles.stream()
.anyMatch(role -> "ADMIN".equals(role.getRoleCode()) || "SUPER_ADMIN".equals(role.getRoleCode()));
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={}",
currentUserId, isAdmin, isDeptHead, currentUser != null ? currentUser.getDeptId() : null, dateRange, deptId, userId);
// 计算时间范围
LocalDateTime startTime = calculateStartTime(dateRange);
LocalDateTime endTime = LocalDateTime.now();
// 构建查询条件
LambdaQueryWrapper<Task> queryWrapper = new LambdaQueryWrapper<Task>()
.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<Task> 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<DemandStatisticsVO.UserDemandStat> buildUserStats(List<Task> tasks) {
Map<Long, List<Task>> tasksByUser = tasks.stream()
.collect(Collectors.groupingBy(Task::getPublisherId));
List<DemandStatisticsVO.UserDemandStat> stats = new ArrayList<>();
for (Map.Entry<Long, List<Task>> entry : tasksByUser.entrySet()) {
Long userId = entry.getKey();
List<Task> 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<DemandStatisticsVO.DeptDemandStat> buildDeptStats(List<Task> tasks) {
Map<Long, List<Task>> tasksByDept = tasks.stream()
.filter(t -> t.getPublisherDeptId() != null)
.collect(Collectors.groupingBy(Task::getPublisherDeptId));
List<DemandStatisticsVO.DeptDemandStat> stats = new ArrayList<>();
for (Map.Entry<Long, List<Task>> entry : tasksByDept.entrySet()) {
Long deptId = entry.getKey();
List<Task> deptTasks = entry.getValue();
Department dept = departmentMapper.selectById(deptId);
if (dept == null) continue;
TeamScorePool scorePool = teamScorePoolMapper.selectOne(
new LambdaQueryWrapper<TeamScorePool>().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<DemandStatisticsVO.TrendData> buildTrendData(List<Task> tasks, String dateRange) {
Map<String, List<Task>> 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<DemandStatisticsVO.TrendData> trendData = new ArrayList<>();
for (Map.Entry<String, List<Task>> entry : tasksByDate.entrySet()) {
List<Task> 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<DemandStatisticsVO.TypeDistribution> buildTypeDistribution(List<Task> tasks) {
Map<String, List<Task>> tasksByType = tasks.stream()
.filter(t -> t.getDemandTypeId() != null)
.collect(Collectors.groupingBy(t -> String.valueOf(t.getDemandTypeId())));
int totalTasks = tasks.size();
List<DemandStatisticsVO.TypeDistribution> distribution = new ArrayList<>();
for (Map.Entry<String, List<Task>> entry : tasksByType.entrySet()) {
List<Task> typeTasks = entry.getValue();
int count = typeTasks.size();
double percentage = totalTasks > 0 ? (double) count / totalTasks * 100 : 0.0;
BigDecimal totalScore = typeTasks.stream()
.map(Task::getScoreReward)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
distribution.add(DemandStatisticsVO.TypeDistribution.builder()
.typeName("类型" + entry.getKey())
.count(count)
.percentage(percentage)
.totalScore(totalScore)
.build());
}
// 按数量降序排序
distribution.sort((a, b) -> b.getCount().compareTo(a.getCount()));
return distribution;
}
/**
* 构建总览数据
*/
private DemandStatisticsVO.OverviewData buildOverview(List<Task> 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<Task> tasks) {
try {
List<Long> 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<TaskReview>()
.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<Task> tasks) {
List<Task> 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<Task> tasks) {
try {
List<Long> completedTaskIds = tasks.stream()
.filter(t -> "completed".equals(t.getStatus()))
.map(Task::getId)
.collect(Collectors.toList());
if (completedTaskIds.isEmpty()) {
return 0.0;
}
List<TaskReview> reviews = taskReviewMapper.selectList(
new LambdaQueryWrapper<TaskReview>()
.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;
}
}
}