1843 lines
85 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.DeductionDetailVO;
import com.designhub.vo.DemandStatisticsVO;
import com.designhub.vo.DesignerPerformanceVO;
import com.designhub.vo.ProcessEfficiencyVO;
import com.designhub.vo.WarningTaskVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.stream.Collectors;
/**
* 数据分析Service
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AnalyticsService {
private final TaskMapper taskMapper;
private final 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 com.designhub.util.WorkHoursCalculator workHoursCalculator;
/**
* 获取需求统计数据(根据角色返回不同维度的数据)
* @param currentUserId 当前登录用户ID
* @param dateRange 时间范围
* @param deptId 部门ID(可选,用于管理员查看特定部门)
* @param userId 用户ID(可选,用于管理员查看特定用户)
* @param month 指定月份(可选,格式 yyyy-MM)
*/
public DemandStatisticsVO getDemandStatistics(Long currentUserId, String dateRange, Long deptId, Long userId, String month) {
log.info("获取需求统计数据: currentUserId={}, dateRange={}, deptId={}, userId={}, month={}",
currentUserId, dateRange, deptId, userId, month);
// 计算时间范围
LocalDateTime startTime;
LocalDateTime endTime;
// 如果指定了月份,则使用月份范围
if (month != null && !month.isEmpty()) {
try {
LocalDate monthDate = LocalDate.parse(month + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
startTime = LocalDateTime.of(monthDate, LocalTime.MIN);
endTime = LocalDateTime.of(monthDate.with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX);
log.info("使用指定月份范围: {} ~ {}", startTime, endTime);
} catch (Exception e) {
log.warn("月份格式错误: {}, 使用默认范围", month);
startTime = calculateStartTime(dateRange);
endTime = LocalDateTime.now();
}
} else {
startTime = calculateStartTime(dateRange);
endTime = LocalDateTime.now();
}
// 构建查询条件(所有用户查看所有数据,可通过参数过滤)
LambdaQueryWrapper<Task> queryWrapper = new LambdaQueryWrapper<Task>()
.ge(startTime != null, Task::getCreateTime, startTime)
.le(Task::getCreateTime, endTime);
// 如果指定了部门ID,则按部门过滤
if (deptId != null) {
queryWrapper.eq(Task::getPublisherDeptId, deptId);
log.info("按部门过滤: deptId={}", deptId);
}
// 如果指定了用户ID,则按用户过滤
if (userId != null) {
queryWrapper.eq(Task::getPublisherId, userId);
log.info("按用户过滤: userId={}", userId);
}
log.info("查看全部数据(所有用户均可访问)");
List<Task> tasks = taskMapper.selectList(queryWrapper);
log.info("查询到任务数量: {}", tasks.size());
return DemandStatisticsVO.builder()
.userStats(buildUserStats(tasks))
.deptStats(buildDeptStats(tasks))
.trendData(buildTrendData(tasks, dateRange))
.typeDistribution(buildTypeDistribution(tasks))
.overview(buildOverview(tasks))
.build();
}
/**
* 构建用户维度统计
*/
private List<DemandStatisticsVO.UserDemandStat> buildUserStats(List<Task> tasks) {
Map<Long, List<Task>> tasksByUser = tasks.stream()
.collect(Collectors.groupingBy(Task::getPublisherId));
List<DemandStatisticsVO.UserDemandStat> stats = new ArrayList<>();
for (Map.Entry<Long, List<Task>> entry : tasksByUser.entrySet()) {
Long userId = entry.getKey();
List<Task> userTasks = entry.getValue();
User user = userMapper.selectById(userId);
if (user == null) continue;
int totalDemands = userTasks.size();
int completedDemands = (int) userTasks.stream()
.filter(t -> "completed".equals(t.getStatus()))
.count();
int pendingDemands = (int) userTasks.stream()
.filter(t -> Arrays.asList("pending", "in_progress", "submitted").contains(t.getStatus()))
.count();
BigDecimal totalScore = userTasks.stream()
.map(Task::getScoreReward)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal avgScore = totalDemands > 0
? totalScore.divide(BigDecimal.valueOf(totalDemands), 2, RoundingMode.HALF_UP)
: BigDecimal.ZERO;
double completionRate = totalDemands > 0
? (double) completedDemands / totalDemands * 100
: 0.0;
// 计算满意度(五星好评率)
double satisfactionRate = calculateSatisfactionRate(userTasks);
Department dept = departmentMapper.selectById(user.getDeptId());
stats.add(DemandStatisticsVO.UserDemandStat.builder()
.userId(userId)
.userName(user.getUsername())
.realName(user.getRealName() != null ? user.getRealName() : user.getUsername())
.deptName(dept != null ? dept.getDeptName() : "")
.totalDemands(totalDemands)
.completedDemands(completedDemands)
.pendingDemands(pendingDemands)
.totalScore(totalScore)
.avgScore(avgScore)
.completionRate(completionRate)
.satisfactionRate(satisfactionRate)
.build());
}
// 按总需求数降序排序
stats.sort((a, b) -> b.getTotalDemands().compareTo(a.getTotalDemands()));
return stats;
}
/**
* 构建部门维度统计
*/
private List<DemandStatisticsVO.DeptDemandStat> buildDeptStats(List<Task> tasks) {
Map<Long, List<Task>> tasksByDept = tasks.stream()
.filter(t -> t.getPublisherDeptId() != null)
.collect(Collectors.groupingBy(Task::getPublisherDeptId));
List<DemandStatisticsVO.DeptDemandStat> stats = new ArrayList<>();
for (Map.Entry<Long, List<Task>> entry : tasksByDept.entrySet()) {
Long deptId = entry.getKey();
List<Task> deptTasks = entry.getValue();
Department dept = departmentMapper.selectById(deptId);
if (dept == null) continue;
TeamScorePool scorePool = teamScorePoolMapper.selectOne(
new LambdaQueryWrapper<TeamScorePool>().eq(TeamScorePool::getDeptId, deptId)
);
int totalDemands = deptTasks.size();
int completedDemands = (int) deptTasks.stream()
.filter(t -> "completed".equals(t.getStatus()))
.count();
int pendingDemands = (int) deptTasks.stream()
.filter(t -> Arrays.asList("pending", "in_progress", "submitted").contains(t.getStatus()))
.count();
BigDecimal totalScore = deptTasks.stream()
.map(Task::getScoreReward)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 计算使用率和预警级别
double utilizationRate = 0.0;
String alertLevel = "safe";
if (scorePool != null) {
BigDecimal totalAvailable = BigDecimal.valueOf(scorePool.getTotalScore() != null ? scorePool.getTotalScore() : 0);
if (totalAvailable.compareTo(BigDecimal.ZERO) > 0) {
utilizationRate = totalScore.divide(totalAvailable, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100))
.doubleValue();
}
// 计算预警级别
BigDecimal remainingScore = BigDecimal.valueOf(scorePool.getRemainingScore() != null ? scorePool.getRemainingScore() : 0);
if (totalAvailable.compareTo(BigDecimal.ZERO) > 0) {
double remainingRate = remainingScore.divide(totalAvailable, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100))
.doubleValue();
if (remainingRate < 10) {
alertLevel = "danger";
} else if (remainingRate < 30) {
alertLevel = "warning";
}
}
stats.add(DemandStatisticsVO.DeptDemandStat.builder()
.deptId(deptId)
.deptName(dept.getDeptName())
.totalDemands(totalDemands)
.completedDemands(completedDemands)
.pendingDemands(pendingDemands)
.totalScore(totalScore)
.remainingScore(BigDecimal.valueOf(scorePool.getRemainingScore() != null ? scorePool.getRemainingScore() : 0))
.dailyLimit(BigDecimal.valueOf(scorePool.getDailyLimit() != null ? scorePool.getDailyLimit() : 0))
.weeklyLimit(BigDecimal.valueOf(scorePool.getWeeklyLimit() != null ? scorePool.getWeeklyLimit() : 0))
.monthlyLimit(BigDecimal.valueOf(scorePool.getMonthlyLimit() != null ? scorePool.getMonthlyLimit() : 0))
.dailyUsed(BigDecimal.valueOf(scorePool.getDailyUsed() != null ? scorePool.getDailyUsed() : 0))
.weeklyUsed(BigDecimal.valueOf(scorePool.getWeeklyUsed() != null ? scorePool.getWeeklyUsed() : 0))
.monthlyUsed(BigDecimal.valueOf(scorePool.getMonthlyUsed() != null ? scorePool.getMonthlyUsed() : 0))
.utilizationRate(utilizationRate)
.alertLevel(alertLevel)
.build());
}
}
// 按总需求数降序排序
stats.sort((a, b) -> b.getTotalDemands().compareTo(a.getTotalDemands()));
return stats;
}
/**
* 构建趋势数据
*/
private List<DemandStatisticsVO.TrendData> buildTrendData(List<Task> tasks, String dateRange) {
Map<String, List<Task>> tasksByDate = new LinkedHashMap<>();
// 根据dateRange生成日期范围
int days = "week".equals(dateRange) ? 7 : "month".equals(dateRange) ? 30 : 90;
LocalDate endDate = LocalDate.now();
for (int i = days - 1; i >= 0; i--) {
LocalDate date = endDate.minusDays(i);
tasksByDate.put(date.format(DateTimeFormatter.ISO_LOCAL_DATE), new ArrayList<>());
}
// 将任务按日期分组
for (Task task : tasks) {
String dateKey = task.getCreateTime().toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE);
if (tasksByDate.containsKey(dateKey)) {
tasksByDate.get(dateKey).add(task);
}
}
List<DemandStatisticsVO.TrendData> trendData = new ArrayList<>();
for (Map.Entry<String, List<Task>> entry : tasksByDate.entrySet()) {
List<Task> dateTasks = entry.getValue();
int demandCount = dateTasks.size();
int completedCount = (int) dateTasks.stream()
.filter(t -> "completed".equals(t.getStatus()))
.count();
BigDecimal scoreUsed = dateTasks.stream()
.map(Task::getScoreReward)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
trendData.add(DemandStatisticsVO.TrendData.builder()
.date(entry.getKey())
.demandCount(demandCount)
.completedCount(completedCount)
.scoreUsed(scoreUsed)
.build());
}
return trendData;
}
/**
* 构建类型分布(按任务子类型分组)
*/
private List<DemandStatisticsVO.TypeDistribution> buildTypeDistribution(List<Task> tasks) {
// 按任务子类型(taskSubType)分组
Map<String, List<Task>> tasksBySubType = tasks.stream()
.filter(t -> t.getTaskSubType() != null && !t.getTaskSubType().trim().isEmpty())
.collect(Collectors.groupingBy(t -> t.getTaskSubType()));
int totalTasks = tasks.size();
List<DemandStatisticsVO.TypeDistribution> distribution = new ArrayList<>();
for (Map.Entry<String, List<Task>> entry : tasksBySubType.entrySet()) {
List<Task> subTypeTasks = entry.getValue();
int count = subTypeTasks.size();
double percentage = totalTasks > 0 ? (double) count / totalTasks * 100 : 0.0;
// 优先使用 scoreReward,其次使用 score
BigDecimal totalScore = subTypeTasks.stream()
.map(t -> {
if (t.getScoreReward() != null && t.getScoreReward().compareTo(BigDecimal.ZERO) > 0) {
return t.getScoreReward();
}
return t.getScore() != null ? t.getScore() : BigDecimal.ZERO;
})
.reduce(BigDecimal.ZERO, BigDecimal::add);
String subTypeName = entry.getKey();
distribution.add(DemandStatisticsVO.TypeDistribution.builder()
.typeName(subTypeName)
.count(count)
.percentage(percentage)
.totalScore(totalScore)
.build());
}
// 按数量降序排序
distribution.sort((a, b) -> b.getCount().compareTo(a.getCount()));
return distribution;
}
/**
* 构建总览数据
*/
private DemandStatisticsVO.OverviewData buildOverview(List<Task> tasks) {
LocalDateTime now = LocalDateTime.now();
LocalDate today = now.toLocalDate();
LocalDate weekStart = today.minusDays(today.getDayOfWeek().getValue() - 1);
LocalDate monthStart = today.withDayOfMonth(1);
int totalDemands = tasks.size();
int todayDemands = (int) tasks.stream()
.filter(t -> t.getCreateTime().toLocalDate().equals(today))
.count();
int weekDemands = (int) tasks.stream()
.filter(t -> !t.getCreateTime().toLocalDate().isBefore(weekStart))
.count();
int monthDemands = (int) tasks.stream()
.filter(t -> !t.getCreateTime().toLocalDate().isBefore(monthStart))
.count();
int completedDemands = (int) tasks.stream()
.filter(t -> "completed".equals(t.getStatus()))
.count();
int pendingDemands = (int) tasks.stream()
.filter(t -> Arrays.asList("pending", "in_progress", "submitted").contains(t.getStatus()))
.count();
int canceledDemands = (int) tasks.stream()
.filter(t -> "cancelled".equals(t.getStatus()))
.count();
BigDecimal totalScoreUsed = tasks.stream()
.map(Task::getScoreReward)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal todayScoreUsed = tasks.stream()
.filter(t -> t.getCreateTime().toLocalDate().equals(today))
.map(Task::getScoreReward)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal weekScoreUsed = tasks.stream()
.filter(t -> !t.getCreateTime().toLocalDate().isBefore(weekStart))
.map(Task::getScoreReward)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal monthScoreUsed = tasks.stream()
.filter(t -> !t.getCreateTime().toLocalDate().isBefore(monthStart))
.map(Task::getScoreReward)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 计算平均完成时长
double avgCompletionTime = calculateAvgCompletionTime(tasks);
// 计算平均满意度
double avgSatisfaction = calculateAvgSatisfaction(tasks);
return DemandStatisticsVO.OverviewData.builder()
.totalDemands(totalDemands)
.todayDemands(todayDemands)
.weekDemands(weekDemands)
.monthDemands(monthDemands)
.completedDemands(completedDemands)
.pendingDemands(pendingDemands)
.canceledDemands(canceledDemands)
.totalScoreUsed(totalScoreUsed)
.todayScoreUsed(todayScoreUsed)
.weekScoreUsed(weekScoreUsed)
.monthScoreUsed(monthScoreUsed)
.avgCompletionTime(avgCompletionTime)
.avgSatisfaction(avgSatisfaction)
.build();
}
/**
* 计算时间范围起始时间
*/
private LocalDateTime calculateStartTime(String dateRange) {
LocalDateTime now = LocalDateTime.now();
switch (dateRange) {
case "week":
return now.minusWeeks(1);
case "month":
// 近一月:从当月1号开始计算
LocalDateTime monthStart = LocalDateTime.of(LocalDate.now().withDayOfMonth(1), LocalTime.MIN);
log.info("计算近一月范围: dateRange={}, 从当月1号开始: {}", dateRange, monthStart);
return monthStart;
case "quarter":
return now.minusMonths(3);
case "year":
return now.minusYears(1);
default:
return null; // 全部
}
}
/**
* 计算满意度(五星好评率)
*/
private double calculateSatisfactionRate(List<Task> tasks) {
try {
List<Long> completedTaskIds = tasks.stream()
.filter(t -> "completed".equals(t.getStatus()))
.map(Task::getId)
.collect(Collectors.toList());
if (completedTaskIds.isEmpty()) {
return 0.0;
}
long fiveStarCount = taskReviewMapper.selectCount(
new LambdaQueryWrapper<TaskReview>()
.in(TaskReview::getTaskId, completedTaskIds)
.eq(TaskReview::getRating, 5)
);
return (double) fiveStarCount / completedTaskIds.size() * 100;
} catch (Exception e) {
log.warn("计算满意度失败(可能task_review表不存在): {}", e.getMessage());
return 0.0;
}
}
/**
* 计算平均完成时长(小时)
*/
private double calculateAvgCompletionTime(List<Task> tasks) {
List<Task> completedTasks = tasks.stream()
.filter(t -> "completed".equals(t.getStatus()) && t.getActualEndTime() != null)
.collect(Collectors.toList());
if (completedTasks.isEmpty()) {
return 0.0;
}
long totalHours = completedTasks.stream()
.mapToLong(t -> {
LocalDateTime start = t.getActualStartTime() != null ? t.getActualStartTime() : t.getCreateTime();
LocalDateTime end = t.getActualEndTime();
return java.time.Duration.between(start, end).toHours();
})
.sum();
return (double) totalHours / completedTasks.size();
}
/**
* 计算平均满意度
*/
private double calculateAvgSatisfaction(List<Task> tasks) {
try {
List<Long> completedTaskIds = tasks.stream()
.filter(t -> "completed".equals(t.getStatus()))
.map(Task::getId)
.collect(Collectors.toList());
if (completedTaskIds.isEmpty()) {
return 0.0;
}
List<TaskReview> reviews = taskReviewMapper.selectList(
new LambdaQueryWrapper<TaskReview>()
.in(TaskReview::getTaskId, completedTaskIds)
);
if (reviews.isEmpty()) {
return 0.0;
}
double totalRating = reviews.stream()
.mapToInt(TaskReview::getRating)
.sum();
return totalRating / reviews.size();
} catch (Exception e) {
log.warn("计算平均满意度失败(可能task_review表不存在): {}", e.getMessage());
return 0.0;
}
}
// ==================== 设计师绩效统计 ====================
/**
* 获取设计师绩效数据
*/
public DesignerPerformanceVO getDesignerPerformance(Long currentUserId, String month, Long deptId) {
log.info("获取设计师绩效数据: currentUserId={}, month={}, deptId={}", currentUserId, month, deptId);
// 计算时间范围和当前月份
LocalDateTime startTime;
LocalDateTime endTime;
String currentMonth; // 用于目标积分查询
if (month != null && !month.isEmpty()) {
currentMonth = month;
try {
LocalDate monthDate = LocalDate.parse(month + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
startTime = LocalDateTime.of(monthDate, LocalTime.MIN);
endTime = LocalDateTime.of(monthDate.with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX);
} catch (Exception e) {
LocalDate now = LocalDate.now();
startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN);
endTime = LocalDateTime.now();
currentMonth = now.format(DateTimeFormatter.ofPattern("yyyy-MM"));
}
} else {
LocalDate now = LocalDate.now();
startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN);
endTime = LocalDateTime.now();
currentMonth = now.format(DateTimeFormatter.ofPattern("yyyy-MM"));
}
// 获取所有设计师(接单部门成员,所有用户均可查看所有设计师)
List<User> designers = getDesigners(deptId);
// 获取时间范围内的任务
LambdaQueryWrapper<Task> taskQuery = new LambdaQueryWrapper<Task>()
.ge(Task::getCreateTime, startTime)
.le(Task::getCreateTime, endTime)
.isNotNull(Task::getReceiverId);
List<Task> tasks = taskMapper.selectList(taskQuery);
// 计算本周范围
LocalDate today = LocalDate.now();
LocalDate weekStart = today.minusDays(today.getDayOfWeek().getValue() - 1);
LocalDateTime weekStartTime = LocalDateTime.of(weekStart, LocalTime.MIN);
// 构建设计师绩效列表
List<DesignerPerformanceVO.DesignerStat> designerStats = new ArrayList<>();
List<DesignerPerformanceVO.WorkloadStat> workloadStats = new ArrayList<>();
int totalReceived = 0;
int totalCompleted = 0;
double totalOnTimeRate = 0;
double totalProcessTime = 0;
double totalSatisfaction = 0;
int overloadCount = 0;
int designerWithData = 0;
for (User designer : designers) {
// 获取该设计师的主任务
List<Task> designerTasks = tasks.stream()
.filter(t -> designer.getId().equals(t.getReceiverId()))
.collect(Collectors.toList());
// 获取该设计师的子任务(流水线任务中的子任务)
List<TaskItem> designerTaskItems = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getReceiverId, designer.getId())
.ge(TaskItem::getCreateTime, startTime)
.le(TaskItem::getCreateTime, endTime)
);
// 主任务统计
int mainReceivedCount = designerTasks.size();
int mainCompletedCount = (int) designerTasks.stream()
.filter(t -> "completed".equals(t.getStatus()))
.count();
int mainInProgressCount = (int) designerTasks.stream()
.filter(t -> "in_progress".equals(t.getStatus()))
.count();
// 子任务统计
int subReceivedCount = designerTaskItems.size();
int subCompletedCount = (int) designerTaskItems.stream()
.filter(t -> "completed".equals(t.getStatus()))
.count();
int subInProgressCount = (int) designerTaskItems.stream()
.filter(t -> "in_progress".equals(t.getStatus()))
.count();
// 合计(主任务 + 子任务)
int receivedCount = mainReceivedCount + subReceivedCount;
int completedCount = mainCompletedCount + subCompletedCount;
int inProgressCount = mainInProgressCount + subInProgressCount;
// 计算准时完成数(主任务 + 子任务)
int onTimeCount = 0;
int overdueCount = 0;
double totalTime = 0;
int timeCount = 0;
// 主任务准时率计算
for (Task task : designerTasks) {
if ("completed".equals(task.getStatus()) && task.getActualEndTime() != null) {
LocalDateTime start = task.getActualStartTime() != null ? task.getActualStartTime() : task.getStartTime();
if (start != null) {
long hours = Duration.between(start, task.getActualEndTime()).toHours();
totalTime += hours;
timeCount++;
}
// 判断是否准时
if (task.getDeadline() != null) {
if (!task.getActualEndTime().isAfter(task.getDeadline())) {
onTimeCount++;
} else {
overdueCount++;
}
} else {
onTimeCount++; // 没有deadline的默认准时
}
}
}
// 子任务准时率计算
for (TaskItem item : designerTaskItems) {
if ("completed".equals(item.getStatus()) && item.getCompleteTime() != null) {
// 判断是否准时
if (item.getDeadline() != null) {
if (!item.getCompleteTime().isAfter(item.getDeadline())) {
onTimeCount++;
} else {
overdueCount++;
}
} else {
onTimeCount++; // 没有deadline的默认准时
}
}
}
double completionRate = receivedCount > 0 ? (double) completedCount / receivedCount * 100 : 0;
double onTimeRate = completedCount > 0 ? (double) onTimeCount / completedCount * 100 : 0;
double avgProcessTime = timeCount > 0 ? totalTime / timeCount : 0;
// 计算评分和好评率
double avgRating = 0;
int fiveStarCount = 0;
double satisfactionRate = 0;
try {
List<Long> completedTaskIds = designerTasks.stream()
.filter(t -> "completed".equals(t.getStatus()))
.map(Task::getId)
.collect(Collectors.toList());
if (!completedTaskIds.isEmpty()) {
List<TaskReview> reviews = taskReviewMapper.selectList(
new LambdaQueryWrapper<TaskReview>()
.in(TaskReview::getTaskId, completedTaskIds)
);
if (!reviews.isEmpty()) {
avgRating = reviews.stream().mapToInt(TaskReview::getRating).average().orElse(0);
fiveStarCount = (int) reviews.stream().filter(r -> r.getRating() == 5).count();
satisfactionRate = (double) fiveStarCount / reviews.size() * 100;
}
}
} catch (Exception e) {
log.warn("获取评价数据失败: {}", e.getMessage());
}
// 计算获得积分和延误扣分
BigDecimal totalScore = BigDecimal.ZERO;
BigDecimal deductionScore = BigDecimal.ZERO;
// 获取扣分比例配置(默认每延误1小时扣任务积分的1%)
BigDecimal deductionRate = getDeductionRate(designer.getDeptId(), currentMonth);
LocalDateTime now = LocalDateTime.now();
for (Task task : designerTasks) {
// 计算任务积分
BigDecimal taskScore = BigDecimal.ZERO;
if (task.getScoreReward() != null && task.getScoreReward().compareTo(BigDecimal.ZERO) > 0) {
taskScore = task.getScoreReward();
} else if (task.getScore() != null) {
taskScore = task.getScore();
}
// 计算已完成任务的积分和延误扣分
if ("completed".equals(task.getStatus()) && task.getActualEndTime() != null
&& !task.getActualEndTime().isBefore(startTime)
&& !task.getActualEndTime().isAfter(endTime)) {
totalScore = totalScore.add(taskScore);
// 计算延误扣分(如果超时完成)
if (task.getDeadline() != null && task.getActualEndTime() != null
&& task.getActualEndTime().isAfter(task.getDeadline())) {
// 计算延误小时数(只计算工作时间段内的延误)
BigDecimal overdueHours = workHoursCalculator.calculateDelayHours(task.getDeadline(), task.getActualEndTime());
if (overdueHours.compareTo(BigDecimal.ZERO) > 0 && taskScore.compareTo(BigDecimal.ZERO) > 0) {
// 扣分 = 任务积分 * 延误小时数 * 扣分比例
BigDecimal taskDeduction = taskScore
.multiply(overdueHours)
.multiply(deductionRate)
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
// 扣分不能超过任务积分
if (taskDeduction.compareTo(taskScore) > 0) {
taskDeduction = taskScore;
}
deductionScore = deductionScore.add(taskDeduction);
}
}
}
// 计算进行中任务的延误扣分(当前时间超过deadline时)
else if ("in_progress".equals(task.getStatus()) && task.getDeadline() != null
&& task.getDeadline().isBefore(endTime) // deadline在查询月份范围内
&& now.isAfter(task.getDeadline()) // 当前时间已超过deadline
&& taskScore.compareTo(BigDecimal.ZERO) > 0) {
// 计算延误小时数(只计算工作时间段内的延误)
BigDecimal overdueHours = workHoursCalculator.calculateDelayHours(task.getDeadline(), now);
if (overdueHours.compareTo(BigDecimal.ZERO) > 0) {
// 扣分 = 任务积分 * 延误小时数 * 扣分比例
BigDecimal taskDeduction = taskScore
.multiply(overdueHours)
.multiply(deductionRate)
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
// 扣分不能超过任务积分
if (taskDeduction.compareTo(taskScore) > 0) {
taskDeduction = taskScore;
}
deductionScore = deductionScore.add(taskDeduction);
}
}
}
// 计算子任务的获得积分
for (TaskItem item : designerTaskItems) {
if ("completed".equals(item.getStatus()) && item.getCompleteTime() != null
&& !item.getCompleteTime().isBefore(startTime)
&& !item.getCompleteTime().isAfter(endTime)) {
BigDecimal itemScore = item.getTotalScore() != null ? item.getTotalScore() : BigDecimal.ZERO;
totalScore = totalScore.add(itemScore);
}
}
// 计算子任务的延误扣分(与getDesignerDeductionDetails保持一致)
try {
// 查询已完成的子任务(按完成时间查询)
List<TaskItem> completedTaskItems = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getReceiverId, designer.getId())
.eq(TaskItem::getStatus, "completed")
.isNotNull(TaskItem::getCompleteTime)
.ge(TaskItem::getCompleteTime, startTime)
.le(TaskItem::getCompleteTime, endTime)
);
// 查询进行中的子任务
List<TaskItem> inProgressTaskItems = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getReceiverId, designer.getId())
.eq(TaskItem::getStatus, "in_progress")
.isNotNull(TaskItem::getEstimatedHours)
.isNotNull(TaskItem::getCreateTime)
);
// 处理已完成的子任务的延误扣分
for (TaskItem item : completedTaskItems) {
if (item.getEstimatedHours() != null && item.getEstimatedHours().compareTo(BigDecimal.ZERO) > 0
&& item.getCreateTime() != null && item.getCompleteTime() != null) {
// 计算子任务的预期deadline:createTime + estimatedHours(使用工作时间计算)
LocalDateTime expectedDeadline = workHoursCalculator.calculateDeadline(item.getCreateTime(), item.getEstimatedHours());
// 如果完成时间超过预期deadline,计算延误扣分
if (expectedDeadline != null && item.getCompleteTime().isAfter(expectedDeadline)) {
BigDecimal itemScore = item.getTotalScore() != null ? item.getTotalScore() : BigDecimal.ZERO;
if (itemScore.compareTo(BigDecimal.ZERO) > 0) {
// 计算延误小时数(只计算工作时间段内的延误)
BigDecimal overdueHours = workHoursCalculator.calculateDelayHours(expectedDeadline, item.getCompleteTime());
if (overdueHours.compareTo(BigDecimal.ZERO) > 0) {
// 计算扣分
BigDecimal itemDeduction = itemScore
.multiply(overdueHours)
.multiply(deductionRate)
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
// 扣分不能超过任务积分
if (itemDeduction.compareTo(itemScore) > 0) {
itemDeduction = itemScore;
}
deductionScore = deductionScore.add(itemDeduction);
}
}
}
}
}
// 处理进行中的子任务的延误扣分
for (TaskItem item : inProgressTaskItems) {
if (item.getEstimatedHours() != null && item.getEstimatedHours().compareTo(BigDecimal.ZERO) > 0
&& item.getCreateTime() != null) {
// 计算子任务的预期deadline:createTime + estimatedHours(使用工作时间计算)
LocalDateTime expectedDeadline = workHoursCalculator.calculateDeadline(item.getCreateTime(), item.getEstimatedHours());
// 如果当前时间超过预期deadline,计算延误扣分
if (expectedDeadline != null && now.isAfter(expectedDeadline)) {
BigDecimal itemScore = item.getTotalScore() != null ? item.getTotalScore() : BigDecimal.ZERO;
if (itemScore.compareTo(BigDecimal.ZERO) > 0) {
// 计算延误小时数(只计算工作时间段内的延误)
BigDecimal overdueHours = workHoursCalculator.calculateDelayHours(expectedDeadline, now);
if (overdueHours.compareTo(BigDecimal.ZERO) > 0) {
// 计算扣分
BigDecimal itemDeduction = itemScore
.multiply(overdueHours)
.multiply(deductionRate)
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
// 扣分不能超过任务积分
if (itemDeduction.compareTo(itemScore) > 0) {
itemDeduction = itemScore;
}
deductionScore = deductionScore.add(itemDeduction);
}
}
}
}
}
} catch (Exception e) {
log.warn("计算子任务延误扣分失败: {}", e.getMessage());
}
// 添加进度未更新扣分
try {
if (progressDeductionLogMapper != null) {
// 解析月份
String[] monthParts = currentMonth.split("-");
if (monthParts.length == 2) {
int yearNum = Integer.parseInt(monthParts[0]);
int monthNum = Integer.parseInt(monthParts[1]);
BigDecimal progressDeduction = progressDeductionLogMapper.sumMonthlyDeduction(designer.getId(), yearNum, monthNum);
if (progressDeduction != null && progressDeduction.compareTo(BigDecimal.ZERO) > 0) {
deductionScore = deductionScore.add(progressDeduction);
log.debug("设计师 {} 本月进度未更新扣分: {}", designer.getRealName(), progressDeduction);
}
}
}
} catch (Exception e) {
log.warn("获取进度未更新扣分失败: {}", e.getMessage());
}
// 净积分 = 总积分 - 扣分
BigDecimal netScore = totalScore.subtract(deductionScore);
if (netScore.compareTo(BigDecimal.ZERO) < 0) {
netScore = BigDecimal.ZERO;
}
// 获取目标积分
BigDecimal targetScore = getTargetScore(designer.getId(), designer.getDeptId(), currentMonth);
// 计算目标完成率
Double targetCompletionRate = 0.0;
if (targetScore != null && targetScore.compareTo(BigDecimal.ZERO) > 0) {
targetCompletionRate = netScore.divide(targetScore, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100))
.doubleValue();
targetCompletionRate = Math.round(targetCompletionRate * 10) / 10.0;
}
// 本周完成数
int weekCompletedCount = (int) designerTasks.stream()
.filter(t -> "completed".equals(t.getStatus()) &&
t.getActualEndTime() != null &&
!t.getActualEndTime().isBefore(weekStartTime))
.count();
Department dept = designer.getDeptId() != null ? departmentMapper.selectById(designer.getDeptId()) : null;
designerStats.add(DesignerPerformanceVO.DesignerStat.builder()
.userId(designer.getId())
.userName(designer.getUsername())
.realName(designer.getRealName())
.deptName(dept != null ? dept.getDeptName() : "")
.deptId(designer.getDeptId())
.receivedCount(receivedCount)
.completedCount(completedCount)
.inProgressCount(inProgressCount)
.completionRate(Math.round(completionRate * 10) / 10.0)
.onTimeRate(Math.round(onTimeRate * 10) / 10.0)
.onTimeCount(onTimeCount)
.overdueCount(overdueCount)
.avgProcessTime(Math.round(avgProcessTime * 10) / 10.0)
.avgRating(Math.round(avgRating * 10) / 10.0)
.fiveStarCount(fiveStarCount)
.satisfactionRate(Math.round(satisfactionRate * 10) / 10.0)
.totalScore(totalScore)
.deductionScore(deductionScore)
.netScore(netScore)
.targetScore(targetScore)
.targetCompletionRate(targetCompletionRate)
.weekCompletedCount(weekCompletedCount)
.monthCompletedCount(completedCount)
.build());
// 工作负载
String loadLevel = "normal";
if (inProgressCount > 8) {
loadLevel = "overload";
overloadCount++;
} else if (inProgressCount > 5) {
loadLevel = "high";
} else if (inProgressCount < 2) {
loadLevel = "low";
}
// 获取最早截止时间
String earliestDeadline = null;
Optional<Task> earliestTask = designerTasks.stream()
.filter(t -> "in_progress".equals(t.getStatus()) && t.getDeadline() != null)
.min(Comparator.comparing(Task::getDeadline));
if (earliestTask.isPresent()) {
earliestDeadline = earliestTask.get().getDeadline().format(DateTimeFormatter.ofPattern("MM-dd HH:mm"));
}
workloadStats.add(DesignerPerformanceVO.WorkloadStat.builder()
.userId(designer.getId())
.userName(designer.getUsername())
.realName(designer.getRealName())
.deptName(dept != null ? dept.getDeptName() : "")
.currentLoad(inProgressCount)
.loadLevel(loadLevel)
.earliestDeadline(earliestDeadline)
.avgDailyTasks(0.0) // 可后续优化
.build());
// 汇总
if (receivedCount > 0) {
totalReceived += receivedCount;
totalCompleted += completedCount;
totalOnTimeRate += onTimeRate;
totalProcessTime += avgProcessTime;
totalSatisfaction += avgRating;
designerWithData++;
}
}
// 按净积分(有效积分)降序排序
designerStats.sort((a, b) -> {
BigDecimal netA = a.getNetScore() != null ? a.getNetScore() : BigDecimal.ZERO;
BigDecimal netB = b.getNetScore() != null ? b.getNetScore() : BigDecimal.ZERO;
return netB.compareTo(netA);
});
// 按当前负载排序
workloadStats.sort((a, b) -> b.getCurrentLoad().compareTo(a.getCurrentLoad()));
// 构建汇总数据
DesignerPerformanceVO.Summary summary = DesignerPerformanceVO.Summary.builder()
.totalDesigners(designers.size())
.totalReceived(totalReceived)
.totalCompleted(totalCompleted)
.avgCompletionRate(designerWithData > 0 ? Math.round(totalCompleted * 1000.0 / totalReceived) / 10.0 : 0)
.avgOnTimeRate(designerWithData > 0 ? Math.round(totalOnTimeRate / designerWithData * 10) / 10.0 : 0)
.avgProcessTime(designerWithData > 0 ? Math.round(totalProcessTime / designerWithData * 10) / 10.0 : 0)
.avgSatisfaction(designerWithData > 0 ? Math.round(totalSatisfaction / designerWithData * 10) / 10.0 : 0)
.overloadCount(overloadCount)
.build();
return DesignerPerformanceVO.builder()
.designerStats(designerStats)
.workloadStats(workloadStats)
.summary(summary)
.build();
}
/**
* 获取设计师列表(所有用户均可查看所有设计师)
*/
private List<User> getDesigners(Long deptId) {
LambdaQueryWrapper<User> userQuery = new LambdaQueryWrapper<User>()
.eq(User::getStatus, 1);
// 如果指定了部门ID,获取该部门及其所有子部门
if (deptId != null) {
List<Long> targetDeptIds = getDeptAndChildIds(deptId);
log.debug("筛选部门: deptId={}, targetDeptIds={}", deptId, targetDeptIds);
userQuery.in(User::getDeptId, targetDeptIds);
}
List<User> allUsers = userMapper.selectList(userQuery);
// 过滤出接单部门的成员(排除部门负责人)
List<User> designers = new ArrayList<>();
for (User user : allUsers) {
if (user.getDeptId() != null) {
Department dept = departmentMapper.selectById(user.getDeptId());
if (dept != null && "receiving".equals(dept.getDeptType())) {
// 排除部门负责人
if (!user.getId().equals(dept.getLeaderId())) {
designers.add(user);
}
}
}
}
return designers;
}
/**
* 递归获取部门及其所有子部门ID
*/
private List<Long> getDeptAndChildIds(Long deptId) {
List<Long> deptIds = new ArrayList<>();
deptIds.add(deptId);
List<Department> children = departmentMapper.selectList(
new LambdaQueryWrapper<Department>()
.eq(Department::getParentId, deptId)
.eq(Department::getStatus, 1)
);
for (Department child : children) {
deptIds.addAll(getDeptAndChildIds(child.getId()));
}
return deptIds;
}
// ==================== 流程效率统计 ====================
/**
* 获取流程效率数据
*/
public ProcessEfficiencyVO getProcessEfficiency(Long currentUserId, String month) {
// 计算时间范围
LocalDateTime startTime;
LocalDateTime endTime;
if (month != null && !month.isEmpty()) {
try {
LocalDate monthDate = LocalDate.parse(month + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
startTime = LocalDateTime.of(monthDate, LocalTime.MIN);
endTime = LocalDateTime.of(monthDate.with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX);
} catch (Exception e) {
LocalDate now = LocalDate.now();
startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN);
endTime = LocalDateTime.now();
}
} else {
LocalDate now = LocalDate.now();
startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN);
endTime = LocalDateTime.now();
}
// 获取已完成的任务
List<Task> completedTasks = taskMapper.selectList(
new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "completed")
.ge(Task::getCreateTime, startTime)
.le(Task::getCreateTime, endTime)
);
// 计算各环节平均耗时
double totalDemandApproval = 0;
double totalReceivingApproval = 0;
double totalGrabWait = 0;
double totalProcess = 0;
int countWithTime = 0;
for (Task task : completedTasks) {
if (task.getActualStartTime() != null && task.getActualEndTime() != null) {
// 任务处理时长
long processHours = Duration.between(task.getActualStartTime(), task.getActualEndTime()).toHours();
totalProcess += processHours;
countWithTime++;
}
}
double avgProcessTime = countWithTime > 0 ? totalProcess / countWithTime : 0;
// 简化的流程效率数据(因为没有详细的审批时间戳)
ProcessEfficiencyVO.StageTimeStats stageTimeStats = ProcessEfficiencyVO.StageTimeStats.builder()
.demandApprovalTime(2.5) // 默认值,后续可从审批日志计算
.receivingApprovalTime(1.8)
.grabWaitTime(0.5)
.processTime(avgProcessTime)
.totalCycleTime(2.5 + 1.8 + 0.5 + avgProcessTime)
.bottleneckStage("任务处理")
.bottleneckPercentage(avgProcessTime / (2.5 + 1.8 + 0.5 + avgProcessTime) * 100)
.build();
// 效率指标
ProcessEfficiencyVO.EfficiencyMetrics metrics = ProcessEfficiencyVO.EfficiencyMetrics.builder()
.weekDemandApprovalTime(2.3)
.weekReceivingApprovalTime(1.6)
.weekGrabWaitTime(0.4)
.weekProcessTime(avgProcessTime)
.weekFirstPassRate(85.0)
.lastWeekDemandApprovalTime(2.8)
.lastWeekReceivingApprovalTime(2.1)
.lastWeekGrabWaitTime(0.6)
.lastWeekProcessTime(avgProcessTime * 1.1)
.lastWeekFirstPassRate(82.0)
.monthDemandApprovalTime(2.5)
.monthReceivingApprovalTime(1.8)
.monthGrabWaitTime(0.5)
.monthProcessTime(avgProcessTime)
.monthFirstPassRate(83.5)
.totalRejected(0)
.rejectionRate(0.0)
.build();
// 环节分布
List<ProcessEfficiencyVO.StageDistribution> distribution = Arrays.asList(
ProcessEfficiencyVO.StageDistribution.builder()
.stageName("需求审核")
.avgTime(2.5)
.percentage(22.7)
.build(),
ProcessEfficiencyVO.StageDistribution.builder()
.stageName("接单审核")
.avgTime(1.8)
.percentage(16.4)
.build(),
ProcessEfficiencyVO.StageDistribution.builder()
.stageName("抢单等待")
.avgTime(0.5)
.percentage(4.5)
.build(),
ProcessEfficiencyVO.StageDistribution.builder()
.stageName("任务处理")
.avgTime(avgProcessTime)
.percentage(56.4)
.build()
);
return ProcessEfficiencyVO.builder()
.stageTimeStats(stageTimeStats)
.efficiencyMetrics(metrics)
.approvalTrend(new ArrayList<>())
.stageDistribution(distribution)
.build();
}
// ==================== 预警监控 ====================
/**
* 获取预警任务列表
*/
public List<WarningTaskVO> getWarningTasks(Long currentUserId) {
List<WarningTaskVO> warnings = new ArrayList<>();
LocalDateTime now = LocalDateTime.now();
// 获取所有进行中和待处理的任务
List<Task> activeTasks = taskMapper.selectList(
new LambdaQueryWrapper<Task>()
.in(Task::getStatus, Arrays.asList("pending", "in_progress", "pending_demand_approval", "pending_receiving_approval"))
.eq(Task::getDeleted, 0)
);
for (Task task : activeTasks) {
WarningTaskVO warning = null;
// 1. 已超时
if ("in_progress".equals(task.getStatus()) && task.getDeadline() != null && now.isAfter(task.getDeadline())) {
long overdueHours = Duration.between(task.getDeadline(), now).toHours();
warning = buildWarningVO(task, "urgent", "overdue",
"已超时 " + overdueHours + " 小时", (double) overdueHours);
}
// 2. 即将超时(剩余时间<20%)
else if ("in_progress".equals(task.getStatus()) && task.getDeadline() != null && task.getEstimatedHours() != null) {
long remainingHours = Duration.between(now, task.getDeadline()).toHours();
double threshold = task.getEstimatedHours().doubleValue() * 0.2;
if (remainingHours > 0 && remainingHours < threshold) {
warning = buildWarningVO(task, "warning", "near_deadline",
"剩余 " + remainingHours + " 小时即将超时", (double) remainingHours);
}
}
// 3. 长期未接单(>24小时)
else if ("pending".equals(task.getStatus()) && task.getCreateTime() != null) {
long waitingHours = Duration.between(task.getCreateTime(), now).toHours();
if (waitingHours > 24) {
warning = buildWarningVO(task, waitingHours > 48 ? "urgent" : "warning", "long_pending",
waitingHours + " 小时未接单", (double) waitingHours);
}
}
// 4. 审批滞后(>24小时)
else if (task.getStatus() != null && task.getStatus().startsWith("pending_") && task.getCreateTime() != null) {
long waitingHours = Duration.between(task.getCreateTime(), now).toHours();
if (waitingHours > 24) {
warning = buildWarningVO(task, "warning", "approval_delay",
"审批等待 " + waitingHours + " 小时", (double) waitingHours);
}
}
// 5. 长期未完成(进行时间>预估工时*2)
else if ("in_progress".equals(task.getStatus()) && task.getActualStartTime() != null && task.getEstimatedHours() != null) {
long inProgressHours = Duration.between(task.getActualStartTime(), now).toHours();
if (inProgressHours > task.getEstimatedHours().doubleValue() * 2) {
warning = buildWarningVO(task, "urgent", "long_in_progress",
"进行中 " + inProgressHours + " 小时(预估" + task.getEstimatedHours().intValue() + "h)", (double) inProgressHours);
}
}
if (warning != null) {
warnings.add(warning);
}
}
// 按预警级别排序(紧急在前)
warnings.sort((a, b) -> {
int levelCompare = "urgent".equals(a.getWarningLevel()) ? -1 : ("urgent".equals(b.getWarningLevel()) ? 1 : 0);
if (levelCompare != 0) return levelCompare;
return b.getHoursInfo().compareTo(a.getHoursInfo());
});
return warnings;
}
private WarningTaskVO buildWarningVO(Task task, String level, String type, String reason, Double hoursInfo) {
String receiverName = null;
String receiverDeptName = null;
if (task.getReceiverId() != null) {
User receiver = userMapper.selectById(task.getReceiverId());
if (receiver != null) {
receiverName = receiver.getRealName() != null ? receiver.getRealName() : receiver.getUsername();
if (receiver.getDeptId() != null) {
Department dept = departmentMapper.selectById(receiver.getDeptId());
if (dept != null) {
receiverDeptName = dept.getDeptName();
}
}
}
}
String publisherName = null;
if (task.getPublisherId() != null) {
User publisher = userMapper.selectById(task.getPublisherId());
if (publisher != null) {
publisherName = publisher.getRealName() != null ? publisher.getRealName() : publisher.getUsername();
}
}
return WarningTaskVO.builder()
.taskId(task.getId())
.taskNo(task.getTaskNo())
.taskTitle(task.getTitle())
.status(task.getStatus())
.statusText(getStatusText(task.getStatus()))
.warningLevel(level)
.warningType(type)
.warningReason(reason)
.receiverId(task.getReceiverId())
.receiverName(receiverName)
.receiverDeptName(receiverDeptName)
.publisherName(publisherName)
.estimatedHours(task.getEstimatedHours() != null ? task.getEstimatedHours().doubleValue() : null)
.deadline(task.getDeadline())
.createTime(task.getCreateTime())
.startTime(task.getActualStartTime())
.hoursInfo(hoursInfo)
.score(task.getScore())
.build();
}
private String getStatusText(String status) {
if (status == null) return "";
switch (status) {
case "pending": return "待接单";
case "in_progress": return "进行中";
case "pending_demand_approval": return "待需求审核";
case "pending_receiving_approval": return "待接单审核";
case "completed": return "已完成";
case "rejected": return "已驳回";
default: return status;
}
}
/**
* 获取延误扣分比例(三级部门继承二级部门的配置)
* @param deptId 部门ID
* @param month 月份 yyyy-MM
* @return 扣分比例(默认1%)
*/
private BigDecimal getDeductionRate(Long deptId, String month) {
if (deptId == null) {
return BigDecimal.ONE; // 默认1%
}
try {
BigDecimal rate = findDeptDeductionRate(deptId, month);
if (rate != null) {
return rate;
}
} catch (Exception e) {
log.warn("获取扣分比例配置失败: deptId={}, month={}", deptId, month, e);
}
return BigDecimal.ONE; // 默认1%
}
/**
* 递归查找部门扣分比例(向上查找父部门配置)
*/
private BigDecimal findDeptDeductionRate(Long deptId, String month) {
if (deptId == null || deptId == 0) {
return null;
}
// 先查找指定月份的配置
DesignerTargetScore config = designerTargetScoreMapper.selectOne(
new LambdaQueryWrapper<DesignerTargetScore>()
.eq(DesignerTargetScore::getDeptId, deptId)
.isNull(DesignerTargetScore::getUserId)
.eq(DesignerTargetScore::getEffectiveMonth, month)
);
if (config != null && config.getDeductionRate() != null) {
return config.getDeductionRate();
}
// 查找通用配置(没有指定月份的)
config = designerTargetScoreMapper.selectOne(
new LambdaQueryWrapper<DesignerTargetScore>()
.eq(DesignerTargetScore::getDeptId, deptId)
.isNull(DesignerTargetScore::getUserId)
.isNull(DesignerTargetScore::getEffectiveMonth)
);
if (config != null && config.getDeductionRate() != null) {
return config.getDeductionRate();
}
// 当前部门没有配置,向上查找父部门
Department dept = departmentMapper.selectById(deptId);
if (dept != null && dept.getParentId() != null && dept.getParentId() > 0) {
return findDeptDeductionRate(dept.getParentId(), month);
}
return null;
}
/**
* 获取设计师目标积分(三级部门继承二级部门的目标积分)
* @param userId 用户ID
* @param deptId 部门ID
* @param month 月份 yyyy-MM
* @return 目标积分
*/
private BigDecimal getTargetScore(Long userId, Long deptId, String month) {
try {
// 先查找用户个人配置
if (userId != null) {
DesignerTargetScore config = designerTargetScoreMapper.selectOne(
new LambdaQueryWrapper<DesignerTargetScore>()
.eq(DesignerTargetScore::getUserId, userId)
.eq(DesignerTargetScore::getEffectiveMonth, month)
);
if (config != null && config.getTargetScore() != null) {
return config.getTargetScore();
}
}
// 查找部门通用配置(支持向上继承:三级部门继承二级部门的配置)
if (deptId != null) {
BigDecimal targetScore = findDeptTargetScore(deptId, month);
if (targetScore != null && targetScore.compareTo(BigDecimal.ZERO) > 0) {
return targetScore;
}
}
} catch (Exception e) {
log.warn("获取目标积分配置失败: userId={}, deptId={}, month={}", userId, deptId, month, e);
}
return BigDecimal.ZERO; // 默认0
}
/**
* 递归查找部门目标积分(向上查找父部门配置)
*/
private BigDecimal findDeptTargetScore(Long deptId, String month) {
if (deptId == null || deptId == 0) {
return null;
}
// 先查找当前部门的配置(指定月份)
DesignerTargetScore config = designerTargetScoreMapper.selectOne(
new LambdaQueryWrapper<DesignerTargetScore>()
.eq(DesignerTargetScore::getDeptId, deptId)
.isNull(DesignerTargetScore::getUserId)
.eq(DesignerTargetScore::getEffectiveMonth, month)
);
if (config != null && config.getTargetScore() != null) {
return config.getTargetScore();
}
// 查找当前部门的默认配置(没有指定月份)
config = designerTargetScoreMapper.selectOne(
new LambdaQueryWrapper<DesignerTargetScore>()
.eq(DesignerTargetScore::getDeptId, deptId)
.isNull(DesignerTargetScore::getUserId)
.isNull(DesignerTargetScore::getEffectiveMonth)
);
if (config != null && config.getTargetScore() != null) {
return config.getTargetScore();
}
// 当前部门没有配置,向上查找父部门
Department dept = departmentMapper.selectById(deptId);
if (dept != null && dept.getParentId() != null && dept.getParentId() > 0) {
return findDeptTargetScore(dept.getParentId(), month);
}
return null;
}
/**
* 获取设计师扣分明细
* @param userId 设计师ID
* @param month 月份 yyyy-MM
* @return 扣分明细
*/
public DeductionDetailVO getDesignerDeductionDetails(Long userId, String month) {
log.info("获取设计师扣分明细: userId={}, month={}", userId, month);
// 计算时间范围
LocalDateTime startTime;
LocalDateTime endTime;
String currentMonth;
if (month != null && !month.isEmpty()) {
currentMonth = month;
try {
LocalDate monthDate = LocalDate.parse(month + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
startTime = LocalDateTime.of(monthDate, LocalTime.MIN);
endTime = LocalDateTime.of(monthDate.with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX);
} catch (Exception e) {
LocalDate now = LocalDate.now();
startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN);
endTime = LocalDateTime.now();
currentMonth = now.format(DateTimeFormatter.ofPattern("yyyy-MM"));
}
} else {
LocalDate now = LocalDate.now();
startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN);
endTime = LocalDateTime.now();
currentMonth = now.format(DateTimeFormatter.ofPattern("yyyy-MM"));
}
// 获取用户信息
User designer = userMapper.selectById(userId);
if (designer == null) {
log.warn("设计师不存在: userId={}", userId);
return DeductionDetailVO.builder()
.totalDeduction(BigDecimal.ZERO)
.delayDeductions(new ArrayList<>())
.progressDeductions(new ArrayList<>())
.build();
}
// 获取时间范围内的已完成任务(按完成时间查询)
List<Task> completedTasks = taskMapper.selectList(
new LambdaQueryWrapper<Task>()
.eq(Task::getReceiverId, userId)
.eq(Task::getStatus, "completed")
.isNotNull(Task::getActualEndTime)
.ge(Task::getActualEndTime, startTime)
.le(Task::getActualEndTime, endTime)
);
// 获取所有进行中的任务(不限制deadline范围,因为可能deadline在之前月份,但任务仍在进行中)
List<Task> inProgressTasks = taskMapper.selectList(
new LambdaQueryWrapper<Task>()
.eq(Task::getReceiverId, userId)
.eq(Task::getStatus, "in_progress")
.isNotNull(Task::getDeadline)
);
log.info("获取设计师扣分明细 - 用户: {}, 月份: {}, 已完成任务数: {}, 进行中任务数: {}",
designer.getRealName() != null ? designer.getRealName() : designer.getUsername(),
currentMonth, completedTasks.size(), inProgressTasks.size());
// 获取扣分比例配置
BigDecimal deductionRate = getDeductionRate(designer.getDeptId(), currentMonth);
log.info("扣分比例: {}%", deductionRate);
// 计算延误扣分明细
List<DeductionDetailVO.DelayDeductionItem> delayDeductions = new ArrayList<>();
BigDecimal totalDelayDeduction = BigDecimal.ZERO;
LocalDateTime now = LocalDateTime.now();
// 处理已完成任务的延误扣分
for (Task task : completedTasks) {
log.info("检查已完成任务: taskNo={}, deadline={}, actualEndTime={}, scoreReward={}, score={}",
task.getTaskNo(), task.getDeadline(), task.getActualEndTime(),
task.getScoreReward(), task.getScore());
if (task.getDeadline() != null && task.getActualEndTime() != null
&& task.getActualEndTime().isAfter(task.getDeadline())) {
// 计算任务积分
BigDecimal taskScore = BigDecimal.ZERO;
if (task.getScoreReward() != null && task.getScoreReward().compareTo(BigDecimal.ZERO) > 0) {
taskScore = task.getScoreReward();
} else if (task.getScore() != null) {
taskScore = task.getScore();
}
if (taskScore.compareTo(BigDecimal.ZERO) > 0) {
// 计算延误小时数(只计算工作时间段内的延误)
BigDecimal overdueHours = workHoursCalculator.calculateDelayHours(task.getDeadline(), task.getActualEndTime());
if (overdueHours.compareTo(BigDecimal.ZERO) > 0) {
// 计算扣分
BigDecimal taskDeduction = taskScore
.multiply(overdueHours)
.multiply(deductionRate)
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
// 扣分不能超过任务积分
if (taskDeduction.compareTo(taskScore) > 0) {
taskDeduction = taskScore;
}
totalDelayDeduction = totalDelayDeduction.add(taskDeduction);
long overdueHoursLong = overdueHours.longValue();
log.info("发现已完成延误任务: taskNo={}, 任务积分={}, 延误小时={}, 扣分={}",
task.getTaskNo(), taskScore, overdueHoursLong, taskDeduction);
delayDeductions.add(DeductionDetailVO.DelayDeductionItem.builder()
.taskId(task.getId())
.taskNo(task.getTaskNo())
.taskTitle(task.getTitle())
.taskScore(taskScore)
.deadline(task.getDeadline())
.actualEndTime(task.getActualEndTime())
.overdueHours(overdueHoursLong)
.deductionScore(taskDeduction)
.deductionReason("任务延误 " + overdueHoursLong + " 小时")
.build());
}
}
}
}
// 处理进行中任务的延误扣分(当前时间超过deadline时)
for (Task task : inProgressTasks) {
log.info("检查进行中任务: taskNo={}, deadline={}, now={}, scoreReward={}, score={}",
task.getTaskNo(), task.getDeadline(), now, task.getScoreReward(), task.getScore());
if (task.getDeadline() != null && now.isAfter(task.getDeadline())) {
// 计算任务积分
BigDecimal taskScore = BigDecimal.ZERO;
if (task.getScoreReward() != null && task.getScoreReward().compareTo(BigDecimal.ZERO) > 0) {
taskScore = task.getScoreReward();
} else if (task.getScore() != null) {
taskScore = task.getScore();
}
log.info("进行中任务积分计算: taskNo={}, taskScore={}", task.getTaskNo(), taskScore);
if (taskScore.compareTo(BigDecimal.ZERO) > 0) {
// 计算延误小时数(只计算工作时间段内的延误)
BigDecimal overdueHours = workHoursCalculator.calculateDelayHours(task.getDeadline(), now);
log.info("进行中任务延误计算: taskNo={}, deadline={}, now={}, overdueHours={}",
task.getTaskNo(), task.getDeadline(), now, overdueHours);
if (overdueHours.compareTo(BigDecimal.ZERO) > 0) {
// 计算扣分
BigDecimal taskDeduction = taskScore
.multiply(overdueHours)
.multiply(deductionRate)
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
// 扣分不能超过任务积分
if (taskDeduction.compareTo(taskScore) > 0) {
taskDeduction = taskScore;
}
totalDelayDeduction = totalDelayDeduction.add(taskDeduction);
long overdueHoursLong = overdueHours.longValue();
log.info("发现进行中延误任务: taskNo={}, 任务积分={}, 延误小时={}, 扣分={}",
task.getTaskNo(), taskScore, overdueHoursLong, taskDeduction);
delayDeductions.add(DeductionDetailVO.DelayDeductionItem.builder()
.taskId(task.getId())
.taskNo(task.getTaskNo())
.taskTitle(task.getTitle())
.taskScore(taskScore)
.deadline(task.getDeadline())
.actualEndTime(null) // 进行中任务没有完成时间
.overdueHours(overdueHoursLong)
.deductionScore(taskDeduction)
.deductionReason("任务进行中,已延误 " + overdueHoursLong + " 小时")
.build());
}
}
}
}
// 查询已完成的子任务(按完成时间查询)
List<TaskItem> completedTaskItems = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getReceiverId, userId)
.eq(TaskItem::getStatus, "completed")
.isNotNull(TaskItem::getCompleteTime)
.ge(TaskItem::getCompleteTime, startTime)
.le(TaskItem::getCompleteTime, endTime)
);
// 查询进行中的子任务
List<TaskItem> inProgressTaskItems = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getReceiverId, userId)
.eq(TaskItem::getStatus, "in_progress")
.isNotNull(TaskItem::getEstimatedHours)
);
log.info("获取设计师扣分明细 - 用户: {}, 月份: {}, 已完成子任务数: {}, 进行中子任务数: {}",
designer.getRealName() != null ? designer.getRealName() : designer.getUsername(),
currentMonth, completedTaskItems.size(), inProgressTaskItems.size());
// 处理已完成的子任务的延误扣分(使用子任务自己的estimatedHours计算deadline)
for (TaskItem item : completedTaskItems) {
if (item.getEstimatedHours() != null && item.getEstimatedHours().compareTo(BigDecimal.ZERO) > 0
&& item.getCreateTime() != null && item.getCompleteTime() != null) {
// 计算子任务的预期deadline:createTime + estimatedHours(使用工作时间计算)
LocalDateTime expectedDeadline = workHoursCalculator.calculateDeadline(item.getCreateTime(), item.getEstimatedHours());
// 如果完成时间超过预期deadline,计算延误扣分
if (expectedDeadline != null && item.getCompleteTime().isAfter(expectedDeadline)) {
BigDecimal itemScore = item.getTotalScore() != null ? item.getTotalScore() : BigDecimal.ZERO;
if (itemScore.compareTo(BigDecimal.ZERO) > 0) {
// 计算延误小时数(只计算工作时间段内的延误)
BigDecimal overdueHours = workHoursCalculator.calculateDelayHours(expectedDeadline, item.getCompleteTime());
if (overdueHours.compareTo(BigDecimal.ZERO) > 0) {
// 计算扣分
BigDecimal itemDeduction = itemScore
.multiply(overdueHours)
.multiply(deductionRate)
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
// 扣分不能超过任务积分
if (itemDeduction.compareTo(itemScore) > 0) {
itemDeduction = itemScore;
}
totalDelayDeduction = totalDelayDeduction.add(itemDeduction);
// 获取主任务信息用于显示
Task mainTask = taskMapper.selectById(item.getTaskId());
String taskNo = item.getItemNo() != null ? item.getItemNo() : "";
String taskTitle = mainTask != null ? mainTask.getTitle() : "子任务";
long overdueHoursLong = overdueHours.longValue();
log.info("发现已完成延误子任务: itemNo={}, 任务积分={}, 延误小时={}, 扣分={}",
taskNo, itemScore, overdueHoursLong, itemDeduction);
delayDeductions.add(DeductionDetailVO.DelayDeductionItem.builder()
.taskId(item.getTaskId()) // 使用主任务ID
.taskNo(taskNo)
.taskTitle(taskTitle + " (" + taskNo + ")")
.taskScore(itemScore)
.deadline(expectedDeadline)
.actualEndTime(item.getCompleteTime())
.overdueHours(overdueHoursLong)
.deductionScore(itemDeduction)
.deductionReason("子任务延误 " + overdueHoursLong + " 小时")
.build());
}
}
}
}
}
// 处理进行中的子任务的延误扣分(使用子任务自己的estimatedHours计算deadline)
for (TaskItem item : inProgressTaskItems) {
if (item.getEstimatedHours() != null && item.getEstimatedHours().compareTo(BigDecimal.ZERO) > 0
&& item.getCreateTime() != null) {
// 计算子任务的预期deadline:createTime + estimatedHours(使用工作时间计算)
LocalDateTime expectedDeadline = workHoursCalculator.calculateDeadline(item.getCreateTime(), item.getEstimatedHours());
// 如果当前时间超过预期deadline,计算延误扣分
if (expectedDeadline != null && now.isAfter(expectedDeadline)) {
BigDecimal itemScore = item.getTotalScore() != null ? item.getTotalScore() : BigDecimal.ZERO;
if (itemScore.compareTo(BigDecimal.ZERO) > 0) {
// 计算延误小时数(只计算工作时间段内的延误)
BigDecimal overdueHours = workHoursCalculator.calculateDelayHours(expectedDeadline, now);
if (overdueHours.compareTo(BigDecimal.ZERO) > 0) {
// 计算扣分
BigDecimal itemDeduction = itemScore
.multiply(overdueHours)
.multiply(deductionRate)
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
// 扣分不能超过任务积分
if (itemDeduction.compareTo(itemScore) > 0) {
itemDeduction = itemScore;
}
totalDelayDeduction = totalDelayDeduction.add(itemDeduction);
// 获取主任务信息用于显示
Task mainTask = taskMapper.selectById(item.getTaskId());
String taskNo = item.getItemNo() != null ? item.getItemNo() : "";
String taskTitle = mainTask != null ? mainTask.getTitle() : "子任务";
long overdueHoursLong = overdueHours.longValue();
log.info("发现进行中延误子任务: itemNo={}, 任务积分={}, 延误小时={}, 扣分={}",
taskNo, itemScore, overdueHoursLong, itemDeduction);
delayDeductions.add(DeductionDetailVO.DelayDeductionItem.builder()
.taskId(item.getTaskId()) // 使用主任务ID
.taskNo(taskNo)
.taskTitle(taskTitle + " (" + taskNo + ")")
.taskScore(itemScore)
.deadline(expectedDeadline)
.actualEndTime(null) // 进行中子任务没有完成时间
.overdueHours(overdueHoursLong)
.deductionScore(itemDeduction)
.deductionReason("子任务进行中,已延误 " + overdueHoursLong + " 小时")
.build());
}
}
}
}
}
log.info("延误扣分总数: {}, 延误扣分记录数: {}", totalDelayDeduction, delayDeductions.size());
// 获取进度未更新扣分明细
List<DeductionDetailVO.ProgressDeductionItem> progressDeductions = new ArrayList<>();
BigDecimal totalProgressDeduction = BigDecimal.ZERO;
try {
if (progressDeductionLogMapper != null) {
// 解析月份
String[] monthParts = currentMonth.split("-");
if (monthParts.length == 2) {
int yearNum = Integer.parseInt(monthParts[0]);
int monthNum = Integer.parseInt(monthParts[1]);
// 查询该月的所有进度未更新扣分记录
LocalDate monthStartDate = LocalDate.of(yearNum, monthNum, 1);
LocalDate monthEndDate = monthStartDate.with(TemporalAdjusters.lastDayOfMonth());
List<ProgressDeductionLog> progressLogs = progressDeductionLogMapper.selectList(
new LambdaQueryWrapper<ProgressDeductionLog>()
.eq(ProgressDeductionLog::getUserId, userId)
.ge(ProgressDeductionLog::getDeductionDate, monthStartDate)
.le(ProgressDeductionLog::getDeductionDate, monthEndDate)
.isNull(ProgressDeductionLog::getTaskItemId) // 只查询主任务的扣分
.orderByDesc(ProgressDeductionLog::getDeductionDate)
);
for (ProgressDeductionLog log : progressLogs) {
if (log.getDeductionScore() != null && log.getDeductionScore().compareTo(BigDecimal.ZERO) > 0) {
totalProgressDeduction = totalProgressDeduction.add(log.getDeductionScore());
// 获取任务信息
Task task = taskMapper.selectById(log.getTaskId());
String taskNo = task != null ? task.getTaskNo() : "";
String taskTitle = task != null ? task.getTitle() : "任务已删除";
progressDeductions.add(DeductionDetailVO.ProgressDeductionItem.builder()
.taskId(log.getTaskId())
.taskNo(taskNo)
.taskTitle(taskTitle)
.deductionDate(log.getDeductionDate())
.deductionScore(log.getDeductionScore())
.reason(log.getReason() != null ? log.getReason() : "当日未更新任务进度")
.build());
}
}
}
}
} catch (Exception e) {
log.warn("获取进度未更新扣分明细失败: {}", e.getMessage());
}
BigDecimal totalDeduction = totalDelayDeduction.add(totalProgressDeduction);
return DeductionDetailVO.builder()
.totalDeduction(totalDeduction)
.delayDeductions(delayDeductions)
.progressDeductions(progressDeductions)
.build();
}
}