3162 lines
142 KiB
Java
3162 lines
142 KiB
Java
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<Task> queryWrapper = new LambdaQueryWrapper<Task>()
|
||
.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<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();
|
||
|
||
// 计算消耗积分(使用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<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();
|
||
|
||
// 使用 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<DemandStatisticsVO.TypeDistribution> buildTypeDistribution(List<Task> tasks) {
|
||
// 按任务子类型(taskSubType)分组
|
||
Map<String, List<Task>> tasksBySubType = tasks.stream()
|
||
.filter(t -> t.getTaskSubType() != null && !t.getTaskSubType().trim().isEmpty())
|
||
.collect(Collectors.groupingBy(t -> t.getTaskSubType()));
|
||
|
||
int totalTasks = tasks.size();
|
||
List<DemandStatisticsVO.TypeDistribution> distribution = new ArrayList<>();
|
||
|
||
for (Map.Entry<String, List<Task>> entry : tasksBySubType.entrySet()) {
|
||
List<Task> 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<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();
|
||
|
||
// 待处理:只包含 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<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) {
|
||
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<User> designers = getDesigners(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());
|
||
|
||
// 获取该设计师的子任务(流水线任务中的子任务)
|
||
List<TaskItem> designerTaskItems = taskItemMapper.selectList(
|
||
new LambdaQueryWrapper<TaskItem>()
|
||
.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<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);
|
||
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<TaskItem> completedTaskItems = taskItemMapper.selectList(
|
||
new LambdaQueryWrapper<TaskItem>()
|
||
.eq(TaskItem::getReceiverId, designer.getId())
|
||
.eq(TaskItem::getStatus, "completed")
|
||
.isNotNull(TaskItem::getCompleteTime)
|
||
.ge(TaskItem::getCompleteTime, startTime)
|
||
.le(TaskItem::getCompleteTime, endTime)
|
||
);
|
||
|
||
// 查询进行中的子任务
|
||
List<TaskItem> inProgressTaskItems = taskItemMapper.selectList(
|
||
new LambdaQueryWrapper<TaskItem>()
|
||
.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<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) -> {
|
||
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<User> getDesigners(Long deptId) {
|
||
LambdaQueryWrapper<User> userQuery = new LambdaQueryWrapper<User>()
|
||
.eq(User::getStatus, 1);
|
||
|
||
// 如果指定了部门ID,获取该部门及其所有子部门
|
||
if (deptId != null) {
|
||
List<Long> targetDeptIds = getDeptAndChildIds(deptId);
|
||
log.debug("筛选部门: deptId={}, targetDeptIds={}", deptId, targetDeptIds);
|
||
userQuery.in(User::getDeptId, targetDeptIds);
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* 递归获取部门及其所有子部门ID
|
||
*/
|
||
private List<Long> getDeptAndChildIds(Long deptId) {
|
||
List<Long> deptIds = new ArrayList<>();
|
||
deptIds.add(deptId);
|
||
|
||
List<Department> children = departmentMapper.selectList(
|
||
new LambdaQueryWrapper<Department>()
|
||
.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<Long> deptIds) {
|
||
if (deptIds == null || deptIds.isEmpty()) {
|
||
return 0;
|
||
}
|
||
|
||
// 获取这些部门的所有负责人ID
|
||
List<Department> depts = departmentMapper.selectList(
|
||
new LambdaQueryWrapper<Department>()
|
||
.in(Department::getId, deptIds)
|
||
.isNotNull(Department::getLeaderId)
|
||
);
|
||
|
||
if (depts.isEmpty()) {
|
||
return 0;
|
||
}
|
||
|
||
Set<Long> 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<User>()
|
||
.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<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) -> {
|
||
// 先按预警级别排序: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<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;
|
||
}
|
||
|
||
/**
|
||
* 获取设计师扣分明细
|
||
* @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<Task> completedTasks = taskMapper.selectList(
|
||
new LambdaQueryWrapper<Task>()
|
||
.eq(Task::getReceiverId, userId)
|
||
.eq(Task::getStatus, "completed")
|
||
.isNotNull(Task::getActualEndTime)
|
||
.ge(Task::getActualEndTime, startTime)
|
||
.le(Task::getActualEndTime, endTime)
|
||
);
|
||
|
||
// 获取所有进行中的任务(不限制deadline范围,因为可能deadline在之前月份,但任务仍在进行中)
|
||
List<Task> inProgressTasks = taskMapper.selectList(
|
||
new LambdaQueryWrapper<Task>()
|
||
.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<DeductionDetailVO.DelayDeductionItem> 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<TaskItem> completedTaskItems = taskItemMapper.selectList(
|
||
new LambdaQueryWrapper<TaskItem>()
|
||
.eq(TaskItem::getReceiverId, userId)
|
||
.eq(TaskItem::getStatus, "completed")
|
||
.isNotNull(TaskItem::getCompleteTime)
|
||
.ge(TaskItem::getCompleteTime, startTime)
|
||
.le(TaskItem::getCompleteTime, endTime)
|
||
);
|
||
|
||
// 查询进行中的子任务
|
||
List<TaskItem> inProgressTaskItems = taskItemMapper.selectList(
|
||
new LambdaQueryWrapper<TaskItem>()
|
||
.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<DeductionDetailVO.ProgressDeductionItem> 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<ProgressDeductionLog> progressLogs = progressDeductionLogMapper.selectList(
|
||
new LambdaQueryWrapper<ProgressDeductionLog>()
|
||
.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<Task> queryWrapper = new LambdaQueryWrapper<Task>()
|
||
.ge(startTime != null, Task::getCreateTime, startTime)
|
||
.le(Task::getCreateTime, endTime)
|
||
.ne(Task::getStatus, "draft") // 排除草稿
|
||
.eq(Task::getDeleted, 0); // 排除已删除的任务
|
||
|
||
List<Task> 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<Integer, List<DimensionAnalysisVO.TaskTypeAnalysis>> 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<DimensionAnalysisVO.CategoryAnalysis> buildCategoryAnalysis(List<Task> tasks) {
|
||
// 按类目ID分组
|
||
Map<Long, List<Task>> tasksByCategory = tasks.stream()
|
||
.filter(t -> t.getCategoryId() != null)
|
||
.collect(Collectors.groupingBy(Task::getCategoryId));
|
||
|
||
List<DimensionAnalysisVO.CategoryAnalysis> result = new ArrayList<>();
|
||
|
||
// 获取所有类目
|
||
List<Category> categories = categoryMapper.selectList(null);
|
||
Map<Long, String> categoryNames = categories.stream()
|
||
.collect(Collectors.toMap(Category::getId, Category::getName, (a, b) -> a));
|
||
|
||
for (Map.Entry<Long, List<Task>> entry : tasksByCategory.entrySet()) {
|
||
Long categoryId = entry.getKey();
|
||
List<Task> 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<DimensionAnalysisVO.BrandAnalysis> buildBrandAnalysis(List<Task> tasks) {
|
||
// 按品牌ID分组
|
||
Map<Long, List<Task>> tasksByBrand = tasks.stream()
|
||
.filter(t -> t.getBrandId() != null)
|
||
.collect(Collectors.groupingBy(Task::getBrandId));
|
||
|
||
List<DimensionAnalysisVO.BrandAnalysis> result = new ArrayList<>();
|
||
|
||
// 获取所有品牌
|
||
List<Brand> brands = brandMapper.selectList(null);
|
||
Map<Long, String> brandNames = brands.stream()
|
||
.collect(Collectors.toMap(Brand::getId, Brand::getName, (a, b) -> a));
|
||
|
||
for (Map.Entry<Long, List<Task>> entry : tasksByBrand.entrySet()) {
|
||
Long brandId = entry.getKey();
|
||
List<Task> 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<DimensionAnalysisVO.ReceivingDeptAnalysis> buildReceivingDeptAnalysis(List<Task> tasks) {
|
||
// 按接单部门ID分组
|
||
Map<Long, List<Task>> tasksByDept = tasks.stream()
|
||
.filter(t -> t.getReceiverDeptId() != null)
|
||
.collect(Collectors.groupingBy(Task::getReceiverDeptId));
|
||
|
||
List<DimensionAnalysisVO.ReceivingDeptAnalysis> result = new ArrayList<>();
|
||
|
||
// 获取所有部门
|
||
List<Department> departments = departmentMapper.selectList(null);
|
||
Map<Long, String> deptNames = departments.stream()
|
||
.collect(Collectors.toMap(d -> d.getId(), d -> d.getDeptName(), (a, b) -> a));
|
||
Map<Long, Long> deptParentIds = departments.stream()
|
||
.collect(Collectors.toMap(d -> d.getId(), d -> d.getParentId() != null ? d.getParentId() : 0L, (a, b) -> a));
|
||
|
||
// 🔥 获取所有接单类型的部门(包括没有任务的)
|
||
List<Department> receivingDepts = departments.stream()
|
||
.filter(d -> "receiving".equals(d.getDeptType()) && d.getStatus() != null && d.getStatus() == 1)
|
||
.collect(Collectors.toList());
|
||
|
||
// 🔥 获取每个接单部门的人数(包含部门负责人)
|
||
Map<Long, Integer> deptMemberCounts = new HashMap<>();
|
||
for (Department dept : receivingDepts) {
|
||
Long deptId = dept.getId();
|
||
// 查询该部门及其子部门的所有用户数量
|
||
List<Long> deptAndChildIds = getDeptAndChildIds(deptId);
|
||
Long memberCount = userMapper.selectCount(new LambdaQueryWrapper<User>()
|
||
.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<Task> 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<Task> 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<DimensionAnalysisVO.DemandDeptAnalysis> buildDemandDeptAnalysis(List<Task> tasks) {
|
||
// 按需求部门ID分组
|
||
Map<Long, List<Task>> tasksByDept = tasks.stream()
|
||
.filter(t -> t.getPublisherDeptId() != null)
|
||
.collect(Collectors.groupingBy(Task::getPublisherDeptId));
|
||
|
||
List<DimensionAnalysisVO.DemandDeptAnalysis> result = new ArrayList<>();
|
||
|
||
// 获取所有部门
|
||
List<Department> departments = departmentMapper.selectList(null);
|
||
Map<Long, String> deptNames = departments.stream()
|
||
.collect(Collectors.toMap(d -> d.getId(), d -> d.getDeptName(), (a, b) -> a));
|
||
|
||
// 🔥 获取每个部门的人数(包含部门负责人)
|
||
Map<Long, Integer> deptMemberCounts = new HashMap<>();
|
||
for (Long deptId : tasksByDept.keySet()) {
|
||
// 查询该部门及其子部门的所有用户数量
|
||
List<Long> deptAndChildIds = getDeptAndChildIds(deptId);
|
||
Long memberCount = userMapper.selectCount(new LambdaQueryWrapper<User>()
|
||
.in(User::getDeptId, deptAndChildIds)
|
||
.eq(User::getStatus, 1)); // 只统计启用状态的用户
|
||
|
||
// 🔥 检查部门负责人是否已被统计,如果负责人的dept_id不在该部门内,则额外加上
|
||
int leaderCount = countLeadersNotInDept(deptAndChildIds);
|
||
deptMemberCounts.put(deptId, memberCount.intValue() + leaderCount);
|
||
}
|
||
|
||
for (Map.Entry<Long, List<Task>> entry : tasksByDept.entrySet()) {
|
||
Long deptId = entry.getKey();
|
||
List<Task> 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<DimensionAnalysisVO.PlatformAnalysis> buildPlatformAnalysis(List<Task> tasks) {
|
||
// 按平台ID分组
|
||
Map<Long, List<Task>> tasksByPlatform = tasks.stream()
|
||
.filter(t -> t.getPlatformId() != null)
|
||
.collect(Collectors.groupingBy(Task::getPlatformId));
|
||
|
||
List<DimensionAnalysisVO.PlatformAnalysis> result = new ArrayList<>();
|
||
|
||
// 获取所有平台
|
||
List<Platform> platforms = platformMapper.selectList(null);
|
||
Map<Long, String> platformNames = platforms.stream()
|
||
.collect(Collectors.toMap(Platform::getId, Platform::getName, (a, b) -> a));
|
||
|
||
for (Map.Entry<Long, List<Task>> entry : tasksByPlatform.entrySet()) {
|
||
Long platformId = entry.getKey();
|
||
List<Task> 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<Task> 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<Task> 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<PrimaryDeptRankingVO> getPrimaryDeptRanking(LocalDateTime startTime, LocalDateTime endTime) {
|
||
log.info("获取一级需求部门排名: startTime={}, endTime={}", startTime, endTime);
|
||
|
||
List<PrimaryDeptRankingVO> result = new java.util.ArrayList<>();
|
||
|
||
// 1. 获取所有一级需求部门
|
||
List<Department> allPrimaryDepts = departmentMapper.selectList(
|
||
new LambdaQueryWrapper<Department>()
|
||
.eq(Department::getDeptType, "primary_demand")
|
||
.eq(Department::getStatus, 1)
|
||
.eq(Department::getDeleted, 0)
|
||
);
|
||
|
||
log.info("找到 {} 个一级需求部门", allPrimaryDepts.size());
|
||
|
||
// 2. 查询时间范围内的一级需求任务
|
||
LambdaQueryWrapper<Task> queryWrapper = new LambdaQueryWrapper<>();
|
||
queryWrapper.eq(Task::getIsPrimaryTask, 1)
|
||
.ge(Task::getCreateTime, startTime)
|
||
.le(Task::getCreateTime, endTime)
|
||
.eq(Task::getDeleted, 0);
|
||
|
||
List<Task> allTasks = taskMapper.selectList(queryWrapper);
|
||
|
||
// 3. 按一级部门分组
|
||
java.util.Map<Long, List<Task>> 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<Task> deptTasks = tasksByDept.getOrDefault(deptId, new java.util.ArrayList<>());
|
||
|
||
PrimaryDeptRankingVO vo = new PrimaryDeptRankingVO();
|
||
vo.setDeptId(deptId);
|
||
vo.setDeptName(dept.getDeptName());
|
||
|
||
// 获取一级部门和二级部门名称(向上查找部门层级)
|
||
List<Department> 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<Task> 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<Task> 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<BrandTaskDistributionVO> getPrimaryTaskBrandDistribution(LocalDateTime startTime, LocalDateTime endTime) {
|
||
log.info("获取品牌任务分布: startTime={}, endTime={}", startTime, endTime);
|
||
|
||
List<BrandTaskDistributionVO> result = new java.util.ArrayList<>();
|
||
|
||
// 查询一级需求部门的任务
|
||
LambdaQueryWrapper<Task> queryWrapper = new LambdaQueryWrapper<>();
|
||
queryWrapper.eq(Task::getIsPrimaryTask, 1)
|
||
.ge(Task::getCreateTime, startTime)
|
||
.le(Task::getCreateTime, endTime)
|
||
.eq(Task::getDeleted, 0);
|
||
|
||
List<Task> allTasks = taskMapper.selectList(queryWrapper);
|
||
int totalTaskCount = allTasks.size();
|
||
|
||
// 按品牌分组
|
||
java.util.Map<Long, List<Task>> tasksByBrand = allTasks.stream()
|
||
.filter(t -> t.getBrandId() != null)
|
||
.collect(java.util.stream.Collectors.groupingBy(Task::getBrandId));
|
||
|
||
for (java.util.Map.Entry<Long, List<Task>> entry : tasksByBrand.entrySet()) {
|
||
Long brandId = entry.getKey();
|
||
List<Task> 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<PrimaryDeptUserRankingVO> getPrimaryDeptUserRanking(LocalDateTime startTime, LocalDateTime endTime) {
|
||
log.info("获取一级需求部门用户排名: startTime={}, endTime={}", startTime, endTime);
|
||
|
||
List<PrimaryDeptUserRankingVO> result = new java.util.ArrayList<>();
|
||
|
||
// 1. 查询时间范围内的一级需求任务
|
||
LambdaQueryWrapper<Task> queryWrapper = new LambdaQueryWrapper<>();
|
||
queryWrapper.eq(Task::getIsPrimaryTask, 1)
|
||
.ge(Task::getCreateTime, startTime)
|
||
.le(Task::getCreateTime, endTime)
|
||
.eq(Task::getDeleted, 0)
|
||
.isNotNull(Task::getPrimaryPublisherId);
|
||
|
||
List<Task> allTasks = taskMapper.selectList(queryWrapper);
|
||
|
||
// 2. 按用户分组
|
||
java.util.Map<Long, List<Task>> tasksByUser = allTasks.stream()
|
||
.filter(t -> t.getPrimaryPublisherId() != null)
|
||
.collect(java.util.stream.Collectors.groupingBy(Task::getPrimaryPublisherId));
|
||
|
||
// 3. 为每个用户创建统计数据
|
||
for (java.util.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 || 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<Department> 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<Integer, List<DimensionAnalysisVO.TaskTypeAnalysis>> buildTaskTypeAnalysis(List<Task> tasks) {
|
||
log.info("开始构建任务类型分析数据,任务数量: {}", tasks.size());
|
||
|
||
// 获取所有任务类型
|
||
List<DemandType> allTypes = demandTypeMapper.selectList(
|
||
new LambdaQueryWrapper<DemandType>()
|
||
.eq(DemandType::getDeleted, 0)
|
||
);
|
||
|
||
// 构建类型ID到类型的映射
|
||
Map<Long, DemandType> typeMap = allTypes.stream()
|
||
.collect(Collectors.toMap(DemandType::getId, t -> t, (a, b) -> a));
|
||
|
||
// 构建父类型名称映射
|
||
Map<Long, String> typeNameMap = allTypes.stream()
|
||
.collect(Collectors.toMap(DemandType::getId, t -> t.getName() != null ? t.getName() : "未知", (a, b) -> a));
|
||
|
||
// 计算每个类型的层级
|
||
Map<Long, Integer> typeLevelMap = new HashMap<>();
|
||
for (DemandType type : allTypes) {
|
||
int level = calculateTypeLevel(type.getId(), typeMap);
|
||
typeLevelMap.put(type.getId(), level);
|
||
}
|
||
|
||
// 按各层级类型ID分组统计任务(向上聚合)
|
||
// key: 类型ID, value: 任务列表
|
||
Map<Long, List<Task>> tasksByLevel1TypeId = new HashMap<>();
|
||
Map<Long, List<Task>> tasksByLevel2TypeId = new HashMap<>();
|
||
Map<Long, List<Task>> tasksByLevel3TypeId = new HashMap<>();
|
||
|
||
for (Task task : tasks) {
|
||
Set<Long> 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<Integer, List<DimensionAnalysisVO.TaskTypeAnalysis>> result = new HashMap<>();
|
||
result.put(1, new ArrayList<>());
|
||
result.put(2, new ArrayList<>());
|
||
result.put(3, new ArrayList<>());
|
||
|
||
// 构建一级分析数据
|
||
for (Map.Entry<Long, List<Task>> entry : tasksByLevel1TypeId.entrySet()) {
|
||
Long typeId = entry.getKey();
|
||
List<Task> 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<Long, List<Task>> entry : tasksByLevel2TypeId.entrySet()) {
|
||
Long typeId = entry.getKey();
|
||
List<Task> 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<Long, List<Task>> entry : tasksByLevel3TypeId.entrySet()) {
|
||
Long typeId = entry.getKey();
|
||
List<Task> 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<DimensionAnalysisVO.TaskTypeAnalysis> 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<Long, DemandType> 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<Task> typeTasks,
|
||
int level,
|
||
Map<Long, DemandType> typeMap,
|
||
Map<Long, String> 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<Task> 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;
|
||
}
|
||
}
|
||
|