2258 lines
102 KiB
Java
2258 lines
102 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.DeductionDetailVO;
|
||
import com.designhub.vo.DemandStatisticsVO;
|
||
import com.designhub.vo.DesignerPerformanceVO;
|
||
import com.designhub.vo.DimensionAnalysisVO;
|
||
import com.designhub.vo.ProcessEfficiencyVO;
|
||
import com.designhub.vo.WarningTaskVO;
|
||
import lombok.RequiredArgsConstructor;
|
||
import lombok.extern.slf4j.Slf4j;
|
||
import org.springframework.stereotype.Service;
|
||
|
||
import java.math.BigDecimal;
|
||
import java.math.RoundingMode;
|
||
import java.time.Duration;
|
||
import java.time.LocalDate;
|
||
import java.time.LocalDateTime;
|
||
import java.time.LocalTime;
|
||
import java.time.format.DateTimeFormatter;
|
||
import java.time.temporal.TemporalAdjusters;
|
||
import java.util.*;
|
||
import java.util.stream.Collectors;
|
||
|
||
/**
|
||
* 数据分析Service
|
||
*/
|
||
@Slf4j
|
||
@Service
|
||
@RequiredArgsConstructor
|
||
public class AnalyticsService {
|
||
|
||
private final TaskMapper taskMapper;
|
||
private final 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 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);
|
||
|
||
// 如果指定了部门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();
|
||
|
||
BigDecimal totalScore = userTasks.stream()
|
||
.map(Task::getScoreReward)
|
||
.filter(Objects::nonNull)
|
||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||
|
||
BigDecimal avgScore = totalDemands > 0
|
||
? totalScore.divide(BigDecimal.valueOf(totalDemands), 2, RoundingMode.HALF_UP)
|
||
: BigDecimal.ZERO;
|
||
|
||
double completionRate = totalDemands > 0
|
||
? (double) completedDemands / totalDemands * 100
|
||
: 0.0;
|
||
|
||
// 计算满意度(五星好评率)
|
||
double satisfactionRate = calculateSatisfactionRate(userTasks);
|
||
|
||
Department dept = departmentMapper.selectById(user.getDeptId());
|
||
|
||
stats.add(DemandStatisticsVO.UserDemandStat.builder()
|
||
.userId(userId)
|
||
.userName(user.getUsername())
|
||
.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();
|
||
|
||
BigDecimal scoreUsed = dateTasks.stream()
|
||
.map(Task::getScoreReward)
|
||
.filter(Objects::nonNull)
|
||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||
|
||
trendData.add(DemandStatisticsVO.TrendData.builder()
|
||
.date(entry.getKey())
|
||
.demandCount(demandCount)
|
||
.completedCount(completedCount)
|
||
.scoreUsed(scoreUsed)
|
||
.build());
|
||
}
|
||
|
||
return trendData;
|
||
}
|
||
|
||
/**
|
||
* 构建类型分布(按任务子类型分组)
|
||
*/
|
||
private List<DemandStatisticsVO.TypeDistribution> buildTypeDistribution(List<Task> tasks) {
|
||
// 按任务子类型(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();
|
||
|
||
int pendingDemands = (int) tasks.stream()
|
||
.filter(t -> Arrays.asList("pending", "in_progress", "submitted").contains(t.getStatus()))
|
||
.count();
|
||
|
||
int canceledDemands = (int) tasks.stream()
|
||
.filter(t -> "cancelled".equals(t.getStatus()))
|
||
.count();
|
||
|
||
BigDecimal totalScoreUsed = tasks.stream()
|
||
.map(Task::getScoreReward)
|
||
.filter(Objects::nonNull)
|
||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||
|
||
BigDecimal todayScoreUsed = tasks.stream()
|
||
.filter(t -> t.getCreateTime().toLocalDate().equals(today))
|
||
.map(Task::getScoreReward)
|
||
.filter(Objects::nonNull)
|
||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||
|
||
BigDecimal weekScoreUsed = tasks.stream()
|
||
.filter(t -> !t.getCreateTime().toLocalDate().isBefore(weekStart))
|
||
.map(Task::getScoreReward)
|
||
.filter(Objects::nonNull)
|
||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||
|
||
BigDecimal monthScoreUsed = tasks.stream()
|
||
.filter(t -> !t.getCreateTime().toLocalDate().isBefore(monthStart))
|
||
.map(Task::getScoreReward)
|
||
.filter(Objects::nonNull)
|
||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||
|
||
// 计算平均完成时长
|
||
double avgCompletionTime = calculateAvgCompletionTime(tasks);
|
||
|
||
// 计算平均满意度
|
||
double avgSatisfaction = calculateAvgSatisfaction(tasks);
|
||
|
||
return DemandStatisticsVO.OverviewData.builder()
|
||
.totalDemands(totalDemands)
|
||
.todayDemands(todayDemands)
|
||
.weekDemands(weekDemands)
|
||
.monthDemands(monthDemands)
|
||
.completedDemands(completedDemands)
|
||
.pendingDemands(pendingDemands)
|
||
.canceledDemands(canceledDemands)
|
||
.totalScoreUsed(totalScoreUsed)
|
||
.todayScoreUsed(todayScoreUsed)
|
||
.weekScoreUsed(weekScoreUsed)
|
||
.monthScoreUsed(monthScoreUsed)
|
||
.avgCompletionTime(avgCompletionTime)
|
||
.avgSatisfaction(avgSatisfaction)
|
||
.build();
|
||
}
|
||
|
||
/**
|
||
* 计算时间范围起始时间
|
||
*/
|
||
private LocalDateTime calculateStartTime(String dateRange) {
|
||
LocalDateTime now = LocalDateTime.now();
|
||
switch (dateRange) {
|
||
case "week":
|
||
return now.minusWeeks(1);
|
||
case "month":
|
||
// 近一月:从当月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)
|
||
);
|
||
|
||
// 处理已完成的子任务的延误扣分
|
||
for (TaskItem item : completedTaskItems) {
|
||
if (item.getEstimatedHours() != null && item.getEstimatedHours().compareTo(BigDecimal.ZERO) > 0
|
||
&& item.getCreateTime() != null && item.getCompleteTime() != null) {
|
||
|
||
// 计算子任务的预期deadline:createTime + estimatedHours(使用工作时间计算)
|
||
LocalDateTime expectedDeadline = workHoursCalculator.calculateDeadline(item.getCreateTime(), 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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理进行中的子任务的延误扣分
|
||
for (TaskItem item : inProgressTaskItems) {
|
||
if (item.getEstimatedHours() != null && item.getEstimatedHours().compareTo(BigDecimal.ZERO) > 0
|
||
&& item.getCreateTime() != null) {
|
||
|
||
// 计算子任务的预期deadline:createTime + estimatedHours(使用工作时间计算)
|
||
LocalDateTime expectedDeadline = workHoursCalculator.calculateDeadline(item.getCreateTime(), 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);
|
||
});
|
||
// 按当前负载排序
|
||
workloadStats.sort((a, b) -> b.getCurrentLoad().compareTo(a.getCurrentLoad()));
|
||
|
||
// 构建汇总数据
|
||
DesignerPerformanceVO.Summary summary = DesignerPerformanceVO.Summary.builder()
|
||
.totalDesigners(designers.size())
|
||
.totalReceived(totalReceived)
|
||
.totalCompleted(totalCompleted)
|
||
.avgCompletionRate(designerWithData > 0 ? Math.round(totalCompleted * 1000.0 / totalReceived) / 10.0 : 0)
|
||
.avgOnTimeRate(designerWithData > 0 ? Math.round(totalOnTimeRate / designerWithData * 10) / 10.0 : 0)
|
||
.avgProcessTime(designerWithData > 0 ? Math.round(totalProcessTime / designerWithData * 10) / 10.0 : 0)
|
||
.avgSatisfaction(designerWithData > 0 ? Math.round(totalSatisfaction / designerWithData * 10) / 10.0 : 0)
|
||
.overloadCount(overloadCount)
|
||
.build();
|
||
|
||
return DesignerPerformanceVO.builder()
|
||
.designerStats(designerStats)
|
||
.workloadStats(workloadStats)
|
||
.summary(summary)
|
||
.build();
|
||
}
|
||
|
||
/**
|
||
* 获取设计师列表(所有用户均可查看所有设计师)
|
||
*/
|
||
private List<User> getDesigners(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;
|
||
}
|
||
|
||
// ==================== 流程效率统计 ====================
|
||
|
||
/**
|
||
* 获取流程效率数据
|
||
*/
|
||
public ProcessEfficiencyVO getProcessEfficiency(Long currentUserId, String month) {
|
||
// 计算时间范围
|
||
LocalDateTime startTime;
|
||
LocalDateTime endTime;
|
||
|
||
if (month != null && !month.isEmpty()) {
|
||
try {
|
||
LocalDate monthDate = LocalDate.parse(month + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||
startTime = LocalDateTime.of(monthDate, LocalTime.MIN);
|
||
endTime = LocalDateTime.of(monthDate.with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX);
|
||
} catch (Exception e) {
|
||
LocalDate now = LocalDate.now();
|
||
startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN);
|
||
endTime = LocalDateTime.now();
|
||
}
|
||
} else {
|
||
LocalDate now = LocalDate.now();
|
||
startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN);
|
||
endTime = LocalDateTime.now();
|
||
}
|
||
|
||
// 获取已完成的任务
|
||
List<Task> completedTasks = taskMapper.selectList(
|
||
new LambdaQueryWrapper<Task>()
|
||
.eq(Task::getStatus, "completed")
|
||
.ge(Task::getCreateTime, startTime)
|
||
.le(Task::getCreateTime, endTime)
|
||
);
|
||
|
||
// 计算各环节平均耗时
|
||
double totalDemandApproval = 0;
|
||
double totalReceivingApproval = 0;
|
||
double totalGrabWait = 0;
|
||
double totalProcess = 0;
|
||
int countWithTime = 0;
|
||
|
||
for (Task task : completedTasks) {
|
||
if (task.getActualStartTime() != null && task.getActualEndTime() != null) {
|
||
// 任务处理时长
|
||
long processHours = Duration.between(task.getActualStartTime(), task.getActualEndTime()).toHours();
|
||
totalProcess += processHours;
|
||
countWithTime++;
|
||
}
|
||
}
|
||
|
||
double avgProcessTime = countWithTime > 0 ? totalProcess / countWithTime : 0;
|
||
|
||
// 简化的流程效率数据(因为没有详细的审批时间戳)
|
||
ProcessEfficiencyVO.StageTimeStats stageTimeStats = ProcessEfficiencyVO.StageTimeStats.builder()
|
||
.demandApprovalTime(2.5) // 默认值,后续可从审批日志计算
|
||
.receivingApprovalTime(1.8)
|
||
.grabWaitTime(0.5)
|
||
.processTime(avgProcessTime)
|
||
.totalCycleTime(2.5 + 1.8 + 0.5 + avgProcessTime)
|
||
.bottleneckStage("任务处理")
|
||
.bottleneckPercentage(avgProcessTime / (2.5 + 1.8 + 0.5 + avgProcessTime) * 100)
|
||
.build();
|
||
|
||
// 效率指标
|
||
ProcessEfficiencyVO.EfficiencyMetrics metrics = ProcessEfficiencyVO.EfficiencyMetrics.builder()
|
||
.weekDemandApprovalTime(2.3)
|
||
.weekReceivingApprovalTime(1.6)
|
||
.weekGrabWaitTime(0.4)
|
||
.weekProcessTime(avgProcessTime)
|
||
.weekFirstPassRate(85.0)
|
||
.lastWeekDemandApprovalTime(2.8)
|
||
.lastWeekReceivingApprovalTime(2.1)
|
||
.lastWeekGrabWaitTime(0.6)
|
||
.lastWeekProcessTime(avgProcessTime * 1.1)
|
||
.lastWeekFirstPassRate(82.0)
|
||
.monthDemandApprovalTime(2.5)
|
||
.monthReceivingApprovalTime(1.8)
|
||
.monthGrabWaitTime(0.5)
|
||
.monthProcessTime(avgProcessTime)
|
||
.monthFirstPassRate(83.5)
|
||
.totalRejected(0)
|
||
.rejectionRate(0.0)
|
||
.build();
|
||
|
||
// 环节分布
|
||
List<ProcessEfficiencyVO.StageDistribution> distribution = Arrays.asList(
|
||
ProcessEfficiencyVO.StageDistribution.builder()
|
||
.stageName("需求审核")
|
||
.avgTime(2.5)
|
||
.percentage(22.7)
|
||
.build(),
|
||
ProcessEfficiencyVO.StageDistribution.builder()
|
||
.stageName("接单审核")
|
||
.avgTime(1.8)
|
||
.percentage(16.4)
|
||
.build(),
|
||
ProcessEfficiencyVO.StageDistribution.builder()
|
||
.stageName("抢单等待")
|
||
.avgTime(0.5)
|
||
.percentage(4.5)
|
||
.build(),
|
||
ProcessEfficiencyVO.StageDistribution.builder()
|
||
.stageName("任务处理")
|
||
.avgTime(avgProcessTime)
|
||
.percentage(56.4)
|
||
.build()
|
||
);
|
||
|
||
return ProcessEfficiencyVO.builder()
|
||
.stageTimeStats(stageTimeStats)
|
||
.efficiencyMetrics(metrics)
|
||
.approvalTrend(new ArrayList<>())
|
||
.stageDistribution(distribution)
|
||
.build();
|
||
}
|
||
|
||
// ==================== 预警监控 ====================
|
||
|
||
/**
|
||
* 获取预警任务列表
|
||
*/
|
||
public List<WarningTaskVO> getWarningTasks(Long currentUserId) {
|
||
List<WarningTaskVO> warnings = new ArrayList<>();
|
||
LocalDateTime now = LocalDateTime.now();
|
||
|
||
// 获取所有进行中和待处理的任务
|
||
List<Task> activeTasks = taskMapper.selectList(
|
||
new LambdaQueryWrapper<Task>()
|
||
.in(Task::getStatus, Arrays.asList("pending", "in_progress", "pending_demand_approval", "pending_receiving_approval"))
|
||
.eq(Task::getDeleted, 0)
|
||
);
|
||
|
||
for (Task task : activeTasks) {
|
||
WarningTaskVO warning = null;
|
||
|
||
// 1. 已超时
|
||
if ("in_progress".equals(task.getStatus()) && task.getDeadline() != null && now.isAfter(task.getDeadline())) {
|
||
long overdueHours = Duration.between(task.getDeadline(), now).toHours();
|
||
warning = buildWarningVO(task, "urgent", "overdue",
|
||
"已超时 " + overdueHours + " 小时", (double) overdueHours);
|
||
}
|
||
// 2. 即将超时(剩余时间<20%)
|
||
else if ("in_progress".equals(task.getStatus()) && task.getDeadline() != null && task.getEstimatedHours() != null) {
|
||
long remainingHours = Duration.between(now, task.getDeadline()).toHours();
|
||
double threshold = task.getEstimatedHours().doubleValue() * 0.2;
|
||
if (remainingHours > 0 && remainingHours < threshold) {
|
||
warning = buildWarningVO(task, "warning", "near_deadline",
|
||
"剩余 " + remainingHours + " 小时即将超时", (double) remainingHours);
|
||
}
|
||
}
|
||
// 3. 长期未接单(>24小时)
|
||
else if ("pending".equals(task.getStatus()) && task.getCreateTime() != null) {
|
||
long waitingHours = Duration.between(task.getCreateTime(), now).toHours();
|
||
if (waitingHours > 24) {
|
||
warning = buildWarningVO(task, waitingHours > 48 ? "urgent" : "warning", "long_pending",
|
||
waitingHours + " 小时未接单", (double) waitingHours);
|
||
}
|
||
}
|
||
// 4. 审批滞后(>24小时)
|
||
else if (task.getStatus() != null && task.getStatus().startsWith("pending_") && task.getCreateTime() != null) {
|
||
long waitingHours = Duration.between(task.getCreateTime(), now).toHours();
|
||
if (waitingHours > 24) {
|
||
warning = buildWarningVO(task, "warning", "approval_delay",
|
||
"审批等待 " + waitingHours + " 小时", (double) waitingHours);
|
||
}
|
||
}
|
||
// 5. 长期未完成(进行时间>预估工时*2)
|
||
else if ("in_progress".equals(task.getStatus()) && task.getActualStartTime() != null && task.getEstimatedHours() != null) {
|
||
long inProgressHours = Duration.between(task.getActualStartTime(), now).toHours();
|
||
if (inProgressHours > task.getEstimatedHours().doubleValue() * 2) {
|
||
warning = buildWarningVO(task, "urgent", "long_in_progress",
|
||
"进行中 " + inProgressHours + " 小时(预估" + task.getEstimatedHours().intValue() + "h)", (double) inProgressHours);
|
||
}
|
||
}
|
||
|
||
if (warning != null) {
|
||
warnings.add(warning);
|
||
}
|
||
}
|
||
|
||
// 按预警级别排序(紧急在前)
|
||
warnings.sort((a, b) -> {
|
||
int levelCompare = "urgent".equals(a.getWarningLevel()) ? -1 : ("urgent".equals(b.getWarningLevel()) ? 1 : 0);
|
||
if (levelCompare != 0) return levelCompare;
|
||
return b.getHoursInfo().compareTo(a.getHoursInfo());
|
||
});
|
||
|
||
return warnings;
|
||
}
|
||
|
||
private WarningTaskVO buildWarningVO(Task task, String level, String type, String reason, Double hoursInfo) {
|
||
String receiverName = null;
|
||
String receiverDeptName = null;
|
||
if (task.getReceiverId() != null) {
|
||
User receiver = userMapper.selectById(task.getReceiverId());
|
||
if (receiver != null) {
|
||
receiverName = receiver.getRealName() != null ? receiver.getRealName() : receiver.getUsername();
|
||
if (receiver.getDeptId() != null) {
|
||
Department dept = departmentMapper.selectById(receiver.getDeptId());
|
||
if (dept != null) {
|
||
receiverDeptName = dept.getDeptName();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
String publisherName = null;
|
||
if (task.getPublisherId() != null) {
|
||
User publisher = userMapper.selectById(task.getPublisherId());
|
||
if (publisher != null) {
|
||
publisherName = publisher.getRealName() != null ? publisher.getRealName() : publisher.getUsername();
|
||
}
|
||
}
|
||
|
||
return WarningTaskVO.builder()
|
||
.taskId(task.getId())
|
||
.taskNo(task.getTaskNo())
|
||
.taskTitle(task.getTitle())
|
||
.status(task.getStatus())
|
||
.statusText(getStatusText(task.getStatus()))
|
||
.warningLevel(level)
|
||
.warningType(type)
|
||
.warningReason(reason)
|
||
.receiverId(task.getReceiverId())
|
||
.receiverName(receiverName)
|
||
.receiverDeptName(receiverDeptName)
|
||
.publisherName(publisherName)
|
||
.estimatedHours(task.getEstimatedHours() != null ? task.getEstimatedHours().doubleValue() : null)
|
||
.deadline(task.getDeadline())
|
||
.createTime(task.getCreateTime())
|
||
.startTime(task.getActualStartTime())
|
||
.hoursInfo(hoursInfo)
|
||
.score(task.getScore())
|
||
.build();
|
||
}
|
||
|
||
private String getStatusText(String status) {
|
||
if (status == null) return "";
|
||
switch (status) {
|
||
case "pending": return "待接单";
|
||
case "in_progress": return "进行中";
|
||
case "pending_demand_approval": return "待需求审核";
|
||
case "pending_receiving_approval": return "待接单审核";
|
||
case "completed": return "已完成";
|
||
case "rejected": return "已驳回";
|
||
default: return status;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取延误扣分比例(三级部门继承二级部门的配置)
|
||
* @param deptId 部门ID
|
||
* @param month 月份 yyyy-MM
|
||
* @return 扣分比例(默认1%)
|
||
*/
|
||
private BigDecimal getDeductionRate(Long deptId, String month) {
|
||
if (deptId == null) {
|
||
return BigDecimal.ONE; // 默认1%
|
||
}
|
||
|
||
try {
|
||
BigDecimal rate = findDeptDeductionRate(deptId, month);
|
||
if (rate != null) {
|
||
return rate;
|
||
}
|
||
} catch (Exception e) {
|
||
log.warn("获取扣分比例配置失败: deptId={}, month={}", deptId, month, e);
|
||
}
|
||
|
||
return BigDecimal.ONE; // 默认1%
|
||
}
|
||
|
||
/**
|
||
* 递归查找部门扣分比例(向上查找父部门配置)
|
||
*/
|
||
private BigDecimal findDeptDeductionRate(Long deptId, String month) {
|
||
if (deptId == null || deptId == 0) {
|
||
return null;
|
||
}
|
||
|
||
// 先查找指定月份的配置
|
||
DesignerTargetScore config = designerTargetScoreMapper.selectOne(
|
||
new LambdaQueryWrapper<DesignerTargetScore>()
|
||
.eq(DesignerTargetScore::getDeptId, deptId)
|
||
.isNull(DesignerTargetScore::getUserId)
|
||
.eq(DesignerTargetScore::getEffectiveMonth, month)
|
||
);
|
||
|
||
if (config != null && config.getDeductionRate() != null) {
|
||
return config.getDeductionRate();
|
||
}
|
||
|
||
// 查找通用配置(没有指定月份的)
|
||
config = designerTargetScoreMapper.selectOne(
|
||
new LambdaQueryWrapper<DesignerTargetScore>()
|
||
.eq(DesignerTargetScore::getDeptId, deptId)
|
||
.isNull(DesignerTargetScore::getUserId)
|
||
.isNull(DesignerTargetScore::getEffectiveMonth)
|
||
);
|
||
|
||
if (config != null && config.getDeductionRate() != null) {
|
||
return config.getDeductionRate();
|
||
}
|
||
|
||
// 当前部门没有配置,向上查找父部门
|
||
Department dept = departmentMapper.selectById(deptId);
|
||
if (dept != null && dept.getParentId() != null && dept.getParentId() > 0) {
|
||
return findDeptDeductionRate(dept.getParentId(), month);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 获取设计师目标积分(三级部门继承二级部门的目标积分)
|
||
* @param userId 用户ID
|
||
* @param deptId 部门ID
|
||
* @param month 月份 yyyy-MM
|
||
* @return 目标积分
|
||
*/
|
||
private BigDecimal getTargetScore(Long userId, Long deptId, String month) {
|
||
try {
|
||
// 先查找用户个人配置
|
||
if (userId != null) {
|
||
DesignerTargetScore config = designerTargetScoreMapper.selectOne(
|
||
new LambdaQueryWrapper<DesignerTargetScore>()
|
||
.eq(DesignerTargetScore::getUserId, userId)
|
||
.eq(DesignerTargetScore::getEffectiveMonth, month)
|
||
);
|
||
|
||
if (config != null && config.getTargetScore() != null) {
|
||
return config.getTargetScore();
|
||
}
|
||
}
|
||
|
||
// 查找部门通用配置(支持向上继承:三级部门继承二级部门的配置)
|
||
if (deptId != null) {
|
||
BigDecimal targetScore = findDeptTargetScore(deptId, month);
|
||
if (targetScore != null && targetScore.compareTo(BigDecimal.ZERO) > 0) {
|
||
return targetScore;
|
||
}
|
||
}
|
||
} catch (Exception e) {
|
||
log.warn("获取目标积分配置失败: userId={}, deptId={}, month={}", userId, deptId, month, e);
|
||
}
|
||
|
||
return BigDecimal.ZERO; // 默认0
|
||
}
|
||
|
||
/**
|
||
* 递归查找部门目标积分(向上查找父部门配置)
|
||
*/
|
||
private BigDecimal findDeptTargetScore(Long deptId, String month) {
|
||
if (deptId == null || deptId == 0) {
|
||
return null;
|
||
}
|
||
|
||
// 先查找当前部门的配置(指定月份)
|
||
DesignerTargetScore config = designerTargetScoreMapper.selectOne(
|
||
new LambdaQueryWrapper<DesignerTargetScore>()
|
||
.eq(DesignerTargetScore::getDeptId, deptId)
|
||
.isNull(DesignerTargetScore::getUserId)
|
||
.eq(DesignerTargetScore::getEffectiveMonth, month)
|
||
);
|
||
|
||
if (config != null && config.getTargetScore() != null) {
|
||
return config.getTargetScore();
|
||
}
|
||
|
||
// 查找当前部门的默认配置(没有指定月份)
|
||
config = designerTargetScoreMapper.selectOne(
|
||
new LambdaQueryWrapper<DesignerTargetScore>()
|
||
.eq(DesignerTargetScore::getDeptId, deptId)
|
||
.isNull(DesignerTargetScore::getUserId)
|
||
.isNull(DesignerTargetScore::getEffectiveMonth)
|
||
);
|
||
|
||
if (config != null && config.getTargetScore() != null) {
|
||
return config.getTargetScore();
|
||
}
|
||
|
||
// 当前部门没有配置,向上查找父部门
|
||
Department dept = departmentMapper.selectById(deptId);
|
||
if (dept != null && dept.getParentId() != null && dept.getParentId() > 0) {
|
||
return findDeptTargetScore(dept.getParentId(), month);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 获取设计师扣分明细
|
||
* @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)
|
||
.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)
|
||
.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());
|
||
|
||
// 处理已完成的子任务的延误扣分(使用子任务自己的estimatedHours计算deadline)
|
||
for (TaskItem item : completedTaskItems) {
|
||
if (item.getEstimatedHours() != null && item.getEstimatedHours().compareTo(BigDecimal.ZERO) > 0
|
||
&& item.getCreateTime() != null && item.getCompleteTime() != null) {
|
||
|
||
// 计算子任务的预期deadline:createTime + estimatedHours(使用工作时间计算)
|
||
LocalDateTime expectedDeadline = workHoursCalculator.calculateDeadline(item.getCreateTime(), 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)
|
||
.deadline(expectedDeadline)
|
||
.actualEndTime(item.getCompleteTime())
|
||
.overdueHours(overdueHoursLong)
|
||
.deductionScore(itemDeduction)
|
||
.deductionReason("子任务延误 " + overdueHoursLong + " 小时")
|
||
.build());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理进行中的子任务的延误扣分(使用子任务自己的estimatedHours计算deadline)
|
||
for (TaskItem item : inProgressTaskItems) {
|
||
if (item.getEstimatedHours() != null && item.getEstimatedHours().compareTo(BigDecimal.ZERO) > 0
|
||
&& item.getCreateTime() != null) {
|
||
|
||
// 计算子任务的预期deadline:createTime + estimatedHours(使用工作时间计算)
|
||
LocalDateTime expectedDeadline = workHoursCalculator.calculateDeadline(item.getCreateTime(), 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)
|
||
.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"); // 排除草稿
|
||
|
||
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));
|
||
|
||
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));
|
||
|
||
for (Map.Entry<Long, List<Task>> entry : tasksByDept.entrySet()) {
|
||
Long deptId = entry.getKey();
|
||
List<Task> deptTasks = entry.getValue();
|
||
|
||
DimensionAnalysisVO.ReceivingDeptAnalysis analysis = new DimensionAnalysisVO.ReceivingDeptAnalysis();
|
||
analysis.setDeptId(deptId);
|
||
analysis.setDeptName(deptNames.getOrDefault(deptId, "未知部门"));
|
||
analysis.setTotalTasks(deptTasks.size());
|
||
|
||
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));
|
||
|
||
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());
|
||
|
||
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;
|
||
}
|
||
}
|
||
|