3162 lines
142 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

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

package com.designhub.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.designhub.entity.*;
import com.designhub.mapper.*;
import com.designhub.vo.*;
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;
}
}