1295 lines
54 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 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<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()));
// 检查 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<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) {
// 优先使用 taskTypeId,如果为空则使用 demandTypeId
Map<Long, List<Task>> 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<DemandStatisticsVO.TypeDistribution> distribution = new ArrayList<>();
// 预加载所有任务类型名称
Map<Long, String> 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<Long, List<Task>> entry : tasksByType.entrySet()) {
List<Task> 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<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;
}
}
// ==================== 设计师绩效统计 ====================
/**
* 获取设计师绩效数据
*/
public DesignerPerformanceVO getDesignerPerformance(Long currentUserId, String month, Long deptId) {
// 权限检查
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()));
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<User> designers = getDesigners(isAdmin, currentUser, deptId);
// 获取时间范围内的任务
LambdaQueryWrapper<Task> taskQuery = new LambdaQueryWrapper<Task>()
.ge(Task::getCreateTime, startTime)
.le(Task::getCreateTime, endTime)
.isNotNull(Task::getReceiverId);
List<Task> tasks = taskMapper.selectList(taskQuery);
// 计算本周范围
LocalDate today = LocalDate.now();
LocalDate weekStart = today.minusDays(today.getDayOfWeek().getValue() - 1);
LocalDateTime weekStartTime = LocalDateTime.of(weekStart, LocalTime.MIN);
// 构建设计师绩效列表
List<DesignerPerformanceVO.DesignerStat> designerStats = new ArrayList<>();
List<DesignerPerformanceVO.WorkloadStat> 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<Task> 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<Long> completedTaskIds = designerTasks.stream()
.filter(t -> "completed".equals(t.getStatus()))
.map(Task::getId)
.collect(Collectors.toList());
if (!completedTaskIds.isEmpty()) {
List<TaskReview> reviews = taskReviewMapper.selectList(
new LambdaQueryWrapper<TaskReview>()
.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<Task> 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<User> getDesigners(boolean isAdmin, User currentUser, Long deptId) {
LambdaQueryWrapper<User> userQuery = new LambdaQueryWrapper<User>()
.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<User> allUsers = userMapper.selectList(userQuery);
// 过滤出接单部门的成员(排除部门负责人)
List<User> 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<Task> completedTasks = taskMapper.selectList(
new LambdaQueryWrapper<Task>()
.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<ProcessEfficiencyVO.StageDistribution> 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<WarningTaskVO> getWarningTasks(Long currentUserId) {
List<WarningTaskVO> warnings = new ArrayList<>();
LocalDateTime now = LocalDateTime.now();
// 获取所有进行中和待处理的任务
List<Task> activeTasks = taskMapper.selectList(
new LambdaQueryWrapper<Task>()
.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<DesignerTargetScore>()
.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<DesignerTargetScore>()
.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<DesignerTargetScore>()
.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<DesignerTargetScore>()
.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<DesignerTargetScore>()
.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;
}
}