3453 lines
159 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.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.designhub.common.ResultCode;
import com.designhub.dto.TaskAuditRequest;
import com.designhub.dto.TaskAssignRequest;
import com.designhub.dto.TaskUpdateDTO;
import com.designhub.entity.*;
import com.designhub.exception.BusinessException;
import com.designhub.mapper.TaskAuditLogMapper;
import com.designhub.mapper.TaskAssignLogMapper;
import com.designhub.mapper.TaskMapper;
import com.designhub.mapper.BrandMapper;
import com.designhub.mapper.CategoryMapper;
import com.designhub.mapper.PlatformMapper;
import com.designhub.mapper.DemandTypeMapper;
import com.designhub.vo.DashboardStatsVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.util.StringUtils;
/**
* 任务服务类
*/
@Service
@RequiredArgsConstructor
public class TaskService {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TaskService.class);
private final TaskMapper taskMapper;
private final TaskAuditLogMapper taskAuditLogMapper;
private final TaskAssignLogMapper taskAssignLogMapper;
private final BrandMapper brandMapper;
private final CategoryMapper categoryMapper;
private final PlatformMapper platformMapper;
private final DemandTypeMapper demandTypeMapper;
private final TeamScorePoolService teamScorePoolService;
private final ConfigService configService;
private final DelayPenaltyService delayPenaltyService;
private final UserService userService;
private final DepartmentService departmentService;
private final RoleService roleService;
private final NotificationService notificationService;
private final com.designhub.mapper.TaskItemMapper taskItemMapper;
private final DeptMonthlyScoreService deptMonthlyScoreService;
private final SystemConfigService systemConfigService;
private final com.designhub.mapper.DepartmentMapper departmentMapper;
private final com.designhub.util.WorkHoursCalculator workHoursCalculator;
/**
* 创建任务
*/
@Transactional(rollbackFor = Exception.class)
public Long createTask(Task task) {
log.info("创建任务: {}", task.getTitle());
// 验证用户权限:只有需求部门可以发起任务(管理员除外)
if (task.getPublisherId() != null) {
var user = userService.getUserById(task.getPublisherId());
if (user == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "用户不存在");
}
// 检查是否是管理员(管理员可以同时拥有需求部门和接单部门权限)
boolean isAdmin = isAdminUser(user.getId());
if (!isAdmin) {
if (user.getDeptId() == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "用户部门信息不完整");
}
var department = departmentService.getDepartmentById(user.getDeptId());
if (department == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "用户部门不存在");
}
if (!"demand".equals(department.getDeptType())) {
throw new BusinessException(ResultCode.FORBIDDEN, "只有需求部门可以发起任务,您所在的部门是接单部门");
}
}
// 设置发布人部门ID
if (task.getPublisherDeptId() == null && user.getDeptId() != null) {
task.setPublisherDeptId(user.getDeptId());
}
}
// 验证任务类型并计算积分(支持多类型)
if (task.getQuantity() != null) {
// 优先处理多类型
if (task.getTaskTypeIds() != null && !task.getTaskTypeIds().isEmpty()) {
String[] typeIds = task.getTaskTypeIds().split(",");
// 验证所有类型ID有效
for (String typeIdStr : typeIds) {
Long typeId = Long.parseLong(typeIdStr.trim());
var demandType = configService.getDemandTypeById(typeId);
if (demandType == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "任务类型ID无效: " + typeId);
}
}
// 计算总积分和工时:取第一个类型的单价(保留2位小数)
Long firstTypeId = Long.parseLong(typeIds[0].trim());
var firstType = configService.getDemandTypeById(firstTypeId);
BigDecimal totalScoreDecimal = firstType.getUnitScore().multiply(BigDecimal.valueOf(task.getQuantity())).setScale(2, java.math.RoundingMode.HALF_UP);
BigDecimal totalHours = firstType.getEstimatedHours() != null ? firstType.getEstimatedHours().multiply(BigDecimal.valueOf(task.getQuantity())) : BigDecimal.ZERO;
task.setScore(totalScoreDecimal);
task.setEstimatedHours(totalHours);
// 兼容性:同时保存第一个类型到task_type_id
task.setTaskTypeId(firstTypeId);
log.info("创建多类型任务: typeIds={}, score={}", task.getTaskTypeIds(), totalScoreDecimal);
}
// 兼容单类型模式
else if (task.getTaskTypeId() != null) {
var demandType = configService.getDemandTypeById(task.getTaskTypeId());
if (demandType == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "任务类型不存在");
}
// 计算总积分和工时(保留2位小数)
BigDecimal totalScoreSingle = demandType.getUnitScore().multiply(BigDecimal.valueOf(task.getQuantity())).setScale(2, java.math.RoundingMode.HALF_UP);
BigDecimal totalHours = demandType.getEstimatedHours() != null ? demandType.getEstimatedHours().multiply(BigDecimal.valueOf(task.getQuantity())) : BigDecimal.ZERO;
task.setScore(totalScoreSingle);
task.setEstimatedHours(totalHours);
// 自动填充taskTypeIds字段
task.setTaskTypeIds(String.valueOf(task.getTaskTypeId()));
log.info("创建单类型任务: typeId={}, score={}", task.getTaskTypeId(), totalScoreSingle);
} else {
throw new BusinessException(ResultCode.PARAM_ERROR, "请选择任务类型");
}
}
// 检查部门积分是否充足(管理员跳过积分检查)
boolean isAdminPublisher = task.getPublisherId() != null && isAdminUser(task.getPublisherId());
if (!isAdminPublisher && task.getPublisherDeptId() != null && task.getScore() != null) {
int scoreInt = task.getScore().intValue();
if (!teamScorePoolService.checkBalance(task.getPublisherDeptId(), scoreInt)) {
throw new BusinessException(ResultCode.INSUFFICIENT_SCORE, "部门积分余额不足");
}
// 检查周期消耗限制
if (!teamScorePoolService.checkPeriodLimit(task.getPublisherDeptId(), scoreInt)) {
throw new BusinessException(ResultCode.SCORE_LIMIT_EXCEEDED, "超过本周期积分消耗限制");
}
}
if (isAdminPublisher) {
log.info("管理员发布任务,跳过积分检查: publisherId={}", task.getPublisherId());
}
// 生成任务编号
task.setTaskNo(generateTaskNo());
// 生成第三方平台任务编号
task.setThirdPartyTaskNo(generateThirdPartyTaskNo());
// 设置任务状态:默认为草稿,需要审核
task.setStatus("draft");
// 设置审核状态:初始为草稿状态
task.setAuditStatus("draft");
// 设置进度为0
task.setProgress(0);
// 设置紧急标识默认值
if (task.getIsUrgent() == null) {
task.setIsUrgent(0);
}
// 设置延误标识默认值
if (task.getIsDelayed() == null) {
task.setIsDelayed(0);
}
// 【两级审核流程】初始化审核状态
initApprovalStatus(task);
// 保存任务(草稿状态不扣除积分)
taskMapper.insert(task);
// 注意:草稿状态不扣除积分,只有审核通过后才扣除
// 积分扣除将在审核通过时进行
// 【两级审核流程】发送审核通知
sendApprovalNotification(task);
return task.getId();
}
/**
* 创建流水线任务
*/
@Transactional(rollbackFor = Exception.class)
public Long createPipelineTask(com.designhub.dto.PipelineTaskDTO dto, Long publisherId) {
log.info("创建流水线任务: {}, 模式: {}", dto.getTitle(), dto.getPipelineMode());
// 验证用户权限
var user = userService.getUserById(publisherId);
if (user == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "用户不存在");
}
// 检查是否是管理员
boolean isAdmin = isAdminUser(user.getId());
if (!isAdmin) {
if (user.getDeptId() == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "用户部门信息不完整");
}
var department = departmentService.getDepartmentById(user.getDeptId());
if (department == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "用户部门不存在");
}
if (!"demand".equals(department.getDeptType())) {
throw new BusinessException(ResultCode.FORBIDDEN, "只有需求部门可以发起任务");
}
}
// 验证流水线模式
if (!"sequential".equals(dto.getPipelineMode()) && !"parallel".equals(dto.getPipelineMode())) {
throw new BusinessException(ResultCode.PARAM_ERROR, "流水线模式必须是 sequential 或 parallel");
}
// 验证子任务列表
if (dto.getItems() == null || dto.getItems().isEmpty()) {
throw new BusinessException(ResultCode.PARAM_ERROR, "流水线任务至少需要一个子任务");
}
// 计算总积分、工时和数量(使用BigDecimal保留小数精度)
BigDecimal totalScoreDecimal = BigDecimal.ZERO;
double totalHours = 0;
int totalQuantity = 0;
for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : dto.getItems()) {
var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId());
if (demandType == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "任务类型ID无效: " + itemDto.getTaskTypeId());
}
BigDecimal itemScore = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity()));
double itemHours = demandType.getEstimatedHours() != null ? demandType.getEstimatedHours().multiply(BigDecimal.valueOf(itemDto.getQuantity())).doubleValue() : 0;
totalScoreDecimal = totalScoreDecimal.add(itemScore);
totalHours += itemHours;
totalQuantity += itemDto.getQuantity();
}
// 保留2位小数
totalScoreDecimal = totalScoreDecimal.setScale(2, java.math.RoundingMode.HALF_UP);
int totalScoreInt = totalScoreDecimal.intValue(); // 用于积分检查
// 检查部门积分是否充足(管理员跳过积分检查)
if (!isAdmin && user.getDeptId() != null) {
if (!teamScorePoolService.checkBalance(user.getDeptId(), totalScoreInt)) {
throw new BusinessException(ResultCode.INSUFFICIENT_SCORE, "部门积分余额不足");
}
if (!teamScorePoolService.checkPeriodLimit(user.getDeptId(), totalScoreInt)) {
throw new BusinessException(ResultCode.SCORE_LIMIT_EXCEEDED, "超过本周期积分消耗限制");
}
}
if (isAdmin) {
log.info("管理员创建流水线任务,跳过积分检查: publisherId={}", publisherId);
}
// 创建主任务
Task task = new Task();
task.setTaskNo(generateTaskNo());
task.setThirdPartyTaskNo(generateThirdPartyTaskNo());
task.setTitle(dto.getTitle());
task.setPublisherId(publisherId);
task.setPublisherDeptId(user.getDeptId());
task.setIsPipeline(1);
task.setPipelineMode(dto.getPipelineMode());
task.setScore(totalScoreDecimal);
task.setEstimatedHours(BigDecimal.valueOf(totalHours));
task.setQuantity(totalQuantity);
task.setBrandId(dto.getBrandId());
task.setCategoryId(dto.getCategoryId());
task.setPlatformId(dto.getPlatformId());
task.setSpuInfo(dto.getSpuInfo());
task.setTaskSubType(dto.getTaskSubType());
task.setAttachmentUrl(dto.getAttachmentUrl());
task.setPreviewImage(dto.getPreviewImage());
task.setRemark(dto.getRemark());
task.setPriority(dto.getPriority() != null ? dto.getPriority() : 1); // 默认优先级:1-普通
// 设置商品条码列表(如果有)
if (dto.getProductBarcodes() != null && !dto.getProductBarcodes().isEmpty()) {
task.setProductBarcodes(String.join(",", dto.getProductBarcodes()));
}
// 设置时间字段
task.setCreateTime(LocalDateTime.now());
// 如果前端没有传递截止时间,默认为创建时间+7天
if (dto.getDeadline() != null) {
task.setDeadline(dto.getDeadline());
} else {
task.setDeadline(LocalDateTime.now().plusDays(7));
}
// 设置任务类型ID(使用第一个任务类型作为主任务类型)
if (dto.getItems() != null && !dto.getItems().isEmpty()) {
task.setTaskTypeId(dto.getItems().get(0).getTaskTypeId());
// 设置所有任务类型ID(逗号分隔)
String taskTypeIds = dto.getItems().stream()
.map(item -> String.valueOf(item.getTaskTypeId()))
.collect(Collectors.joining(","));
task.setTaskTypeIds(taskTypeIds);
}
task.setStatus("draft");
task.setAuditStatus("draft");
task.setProgress(0);
task.setIsUrgent(0);
task.setIsDelayed(0);
// 设置发布类型(从前端传递,默认为recruitment)
task.setReleaseType(dto.getReleaseType() != null ? dto.getReleaseType() : "recruitment");
// 如果是assigned模式,设置接收人
// 对于流水线任务,如果主任务receiverId为空,从第一个子任务获取
Long mainReceiverId = dto.getReceiverId();
if ("assigned".equals(dto.getReleaseType()) && mainReceiverId == null && dto.getItems() != null && !dto.getItems().isEmpty()) {
// 从第一个子任务获取指定的设计师
mainReceiverId = dto.getItems().get(0).getReceiverId();
log.info("流水线任务assigned模式,从第一个子任务获取receiverId: {}", mainReceiverId);
}
if ("assigned".equals(dto.getReleaseType()) && mainReceiverId != null) {
task.setReceiverId(mainReceiverId);
// 获取接收人的部门ID
User receiver = userService.getUserById(mainReceiverId);
if (receiver != null && receiver.getDeptId() != null) {
task.setReceiverDeptId(receiver.getDeptId());
}
}
// 【两级审核流程】初始化审核状态
initApprovalStatus(task);
taskMapper.insert(task);
log.info("主任务创建成功,ID: {}, 任务编号: {}", task.getId(), task.getTaskNo());
// **步骤1:按接单部门和设计师自动分组**
// 遍历任务类型列表,根据接单部门和设计师分组
// 规则:同一部门内如果指定了不同的设计师,也需要拆分为不同的子任务
int currentGroupNo = 1;
String lastGroupKey = null; // 格式:deptKey_receiverId
for (int i = 0; i < dto.getItems().size(); i++) {
com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto = dto.getItems().get(i);
var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId());
// 获取当前任务类型的接单部门配置
String currentDeptKey;
// 🔥 优先使用用户选择的接单部门
if (itemDto.getSelectedDeptId() != null) {
currentDeptKey = String.valueOf(itemDto.getSelectedDeptId());
log.info("任务类型 {} 使用用户选择的接单部门: {}", demandType.getName(), currentDeptKey);
} else {
// 否则使用任务类型配置的 allowedDeptIds(支持三级分类继承二级分类的配置)
currentDeptKey = demandType.getAllowedDeptIds();
if (currentDeptKey == null && demandType.getParentId() != null) {
// 如果当前类型没有配置,尝试获取父级的配置
var parentType = configService.getDemandTypeById(demandType.getParentId());
if (parentType != null) {
currentDeptKey = parentType.getAllowedDeptIds();
log.info("三级分类 {} 继承父级 {} 的接单部门配置: {}",
demandType.getName(), parentType.getName(), currentDeptKey);
}
}
}
// 🔥 构建分组键:部门 + 设计师(如果指定了设计师)
String currentGroupKey;
if (itemDto.getReceiverId() != null) {
// 如果指定了设计师,分组键 = 部门_设计师ID
currentGroupKey = (currentDeptKey != null ? currentDeptKey : "null") + "_" + itemDto.getReceiverId();
log.info("任务类型 {} 指定了设计师 {},分组键: {}", demandType.getName(), itemDto.getReceiverId(), currentGroupKey);
} else {
// 没有指定设计师,分组键 = 部门_0
currentGroupKey = (currentDeptKey != null ? currentDeptKey : "null") + "_0";
}
// 判断是否需要创建新分组
boolean needNewGroup = false;
if (i > 0) {
// 如果分组键不同,则需要创建新分组
if (lastGroupKey == null || !lastGroupKey.equals(currentGroupKey)) {
needNewGroup = true;
}
}
if (needNewGroup) {
currentGroupNo++;
log.info("检测到分组变化,创建新分组: groupNo={}, 前一分组键={}, 当前分组键={}",
currentGroupNo, lastGroupKey, currentGroupKey);
}
// 记录当前分组号(临时存储在sortOrder字段,稍后会恢复)
itemDto.setSortOrder(currentGroupNo * 1000 + i); // 高位存group_no,低位存原始顺序
lastGroupKey = currentGroupKey;
}
log.info("分组完成,共 {} 个子任务组", currentGroupNo);
// **步骤2:两级分组 - 先按部门分组,再按设计师分组**
// 第一级:按部门分组(用于编号的第一段)
// 第二级:同一部门内按设计师分组(用于编号的第二段)
// 收集所有分组键和对应的任务项
Map<Integer, List<com.designhub.dto.PipelineTaskDTO.TaskItemDTO>> groupedItems = new java.util.LinkedHashMap<>();
for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : dto.getItems()) {
int itemGroupNo = itemDto.getSortOrder() / 1000;
groupedItems.computeIfAbsent(itemGroupNo, k -> new java.util.ArrayList<>()).add(itemDto);
}
// 按部门重新组织分组,计算部门序号
// 格式:deptKey -> List<groupNo>
Map<String, List<Integer>> deptToGroups = new java.util.LinkedHashMap<>();
Map<Integer, String> groupToDeptKey = new java.util.HashMap<>();
for (int i = 0; i < dto.getItems().size(); i++) {
com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto = dto.getItems().get(i);
int itemGroupNo = itemDto.getSortOrder() / 1000;
// 获取部门键
String deptKey;
if (itemDto.getSelectedDeptId() != null) {
deptKey = String.valueOf(itemDto.getSelectedDeptId());
} else {
var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId());
deptKey = demandType.getAllowedDeptIds();
if (deptKey == null && demandType.getParentId() != null) {
var parentType = configService.getDemandTypeById(demandType.getParentId());
if (parentType != null) {
deptKey = parentType.getAllowedDeptIds();
}
}
}
if (deptKey == null) deptKey = "null";
// 记录分组到部门的映射
if (!groupToDeptKey.containsKey(itemGroupNo)) {
groupToDeptKey.put(itemGroupNo, deptKey);
deptToGroups.computeIfAbsent(deptKey, k -> new java.util.ArrayList<>()).add(itemGroupNo);
}
}
// 计算每个部门的序号
Map<String, Integer> deptIndexMap = new java.util.LinkedHashMap<>();
int deptIndex = 1;
for (String deptKey : deptToGroups.keySet()) {
deptIndexMap.put(deptKey, deptIndex++);
}
// 计算每个分组在其部门内的子序号
Map<Integer, Integer> groupSubIndexMap = new java.util.HashMap<>();
for (Map.Entry<String, List<Integer>> entry : deptToGroups.entrySet()) {
List<Integer> groups = entry.getValue();
for (int subIdx = 0; subIdx < groups.size(); subIdx++) {
groupSubIndexMap.put(groups.get(subIdx), subIdx + 1);
}
}
log.info("部门分组统计: 共 {} 个部门, 各部门子任务数: {}", deptToGroups.size(),
deptToGroups.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue().size())
.collect(Collectors.joining(", ")));
// 为每个分组创建一个子任务
int itemIndex = 1;
for (Map.Entry<Integer, List<com.designhub.dto.PipelineTaskDTO.TaskItemDTO>> entry : groupedItems.entrySet()) {
int groupNo = entry.getKey();
List<com.designhub.dto.PipelineTaskDTO.TaskItemDTO> groupItems = entry.getValue();
// 获取该分组的部门序号和部门内子序号
String deptKey = groupToDeptKey.get(groupNo);
int currentDeptIndex = deptIndexMap.getOrDefault(deptKey, 1);
int subIndex = groupSubIndexMap.getOrDefault(groupNo, 1);
// 汇总该分组的积分、工时、数量(使用 BigDecimal 保留小数精度)
BigDecimal groupTotalScoreDecimal = BigDecimal.ZERO;
BigDecimal groupTotalHours = BigDecimal.ZERO;
int groupTotalQuantity = 0;
Long firstTaskTypeId = null;
// 构建任务类型明细 JSON 数组
java.util.List<java.util.Map<String, Object>> typeDetailsList = new java.util.ArrayList<>();
for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : groupItems) {
var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId());
// 使用 BigDecimal 保留小数精度
BigDecimal itemScoreDecimal = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity()));
BigDecimal itemHours = demandType.getEstimatedHours() != null
? demandType.getEstimatedHours().multiply(BigDecimal.valueOf(itemDto.getQuantity()))
: BigDecimal.ZERO;
groupTotalScoreDecimal = groupTotalScoreDecimal.add(itemScoreDecimal);
groupTotalHours = groupTotalHours.add(itemHours);
groupTotalQuantity += itemDto.getQuantity();
if (firstTaskTypeId == null) {
firstTaskTypeId = itemDto.getTaskTypeId();
}
// 添加任务类型明细(包含预先指定的设计师信息)
java.util.Map<String, Object> detail = new java.util.LinkedHashMap<>();
detail.put("typeId", itemDto.getTaskTypeId());
detail.put("typeName", demandType.getName());
detail.put("quantity", itemDto.getQuantity());
detail.put("unitScore", demandType.getUnitScore());
// 保存精确的小数积分值(保留2位小数)
detail.put("totalScore", itemScoreDecimal.setScale(2, java.math.RoundingMode.HALF_UP).doubleValue());
// 如果指定了设计师,也保存到明细中
if (itemDto.getReceiverId() != null) {
detail.put("receiverId", itemDto.getReceiverId());
User receiver = userService.getUserById(itemDto.getReceiverId());
if (receiver != null) {
detail.put("receiverName", receiver.getRealName());
}
}
typeDetailsList.add(detail);
}
// 保留2位小数
BigDecimal groupTotalScore = groupTotalScoreDecimal.setScale(2, java.math.RoundingMode.HALF_UP);
// 创建子任务
com.designhub.entity.TaskItem item = new com.designhub.entity.TaskItem();
item.setTaskId(task.getId());
// 使用新的编号格式:任务编号-部门序号-部门内子任务序号
item.setItemNo(generateTaskItemNoWithDept(task.getTaskNo(), currentDeptIndex, subIndex));
item.setGroupNo(groupNo);
item.setTaskTypeId(firstTaskTypeId);
// 保存任务类型明细 JSON
try {
com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
item.setTypeDetails(objectMapper.writeValueAsString(typeDetailsList));
} catch (Exception e) {
log.warn("序列化任务类型明细失败: {}", e.getMessage());
}
item.setQuantity(groupTotalQuantity);
// 计算平均单价(保留2位小数)
item.setUnitScore(groupTotalQuantity > 0
? groupTotalScore.divide(BigDecimal.valueOf(groupTotalQuantity), 2, java.math.RoundingMode.HALF_UP)
: BigDecimal.ZERO);
item.setTotalScore(groupTotalScore);
item.setEstimatedHours(groupTotalHours);
item.setSortOrder(itemIndex);
item.setProgress(0);
// 如果是assigned模式,设置子任务的接收人(从该组的第一个任务项获取)
if ("assigned".equals(dto.getReleaseType()) && !groupItems.isEmpty()) {
Long itemReceiverId = groupItems.get(0).getReceiverId();
if (itemReceiverId != null) {
item.setReceiverId(itemReceiverId);
User itemReceiver = userService.getUserById(itemReceiverId);
if (itemReceiver != null) {
item.setReceiverName(itemReceiver.getRealName());
if (itemReceiver.getDeptId() != null) {
item.setReceiverDeptId(itemReceiver.getDeptId());
}
}
log.info("子任务 {} 已预先指定设计师: {}", item.getItemNo(), itemReceiverId);
}
}
// 关键:根据流水线模式和分组设置初始状态
if ("sequential".equals(dto.getPipelineMode())) {
// 顺序流水线:第一个部门的所有子任务都可接单(部门内并行)
// 只有当第一个部门的所有子任务完成后,才解锁下一个部门的子任务
if (currentDeptIndex == 1) {
item.setStatus("pending");
item.setIsLocked(0);
item.setPrevItemId(null);
log.info("子任务 {} 属于第一个部门,设置为待接单状态", item.getItemNo());
} else {
// 其他部门的子任务都是 locked(锁定),等待前一个部门完成后解锁
item.setStatus("locked");
item.setIsLocked(1);
log.info("子任务 {} 属于第{}个部门,设置为锁定状态", item.getItemNo(), currentDeptIndex);
}
} else {
// 并行流水线:全部待接单
item.setStatus("pending");
item.setIsLocked(0);
}
taskItemMapper.insert(item);
log.info("子任务创建成功,ID: {}, 编号: {}, 部门序号: {}, 部门内序号: {}, 积分: {}, 工时: {}, 状态: {}",
item.getId(), item.getItemNo(), currentDeptIndex, subIndex, groupTotalScore, groupTotalHours, item.getStatus());
itemIndex++;
}
log.info("流水线任务创建完成,主任务ID: {}, 子任务数: {}, 部门数: {}",
task.getId(), groupedItems.size(), deptToGroups.size());
// 【两级审核流程】发送审核通知给部门负责人
sendApprovalNotification(task);
return task.getId();
}
/**
* 生成子任务编号(旧格式,保留兼容)
* 格式:任务编号-子任务序号
*/
private String generateTaskItemNo(String taskNo, int itemIndex) {
return taskNo + "-" + String.format("%02d", itemIndex);
}
/**
* 生成子任务编号(新格式,支持部门内多子任务)
* 格式:任务编号-部门序号-部门内子任务序号
* @param taskNo 主任务编号
* @param deptIndex 部门序号(从1开始)
* @param subIndex 部门内子任务序号(从1开始)
*/
private String generateTaskItemNoWithDept(String taskNo, int deptIndex, int subIndex) {
return taskNo + "-" + String.format("%02d", deptIndex) + "-" + String.format("%02d", subIndex);
}
/**
* 生成任务编号
* 格式:TASK + yyyyMMddHHmmss + 4位随机数,确保唯一性
*/
private String generateTaskNo() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
String datetime = LocalDateTime.now().format(formatter);
String random = String.format("%04d", new Random().nextInt(10000));
String taskNo = "TASK" + datetime + random;
// 检查是否已存在,如果存在则重新生成
int maxRetry = 10;
while (maxRetry-- > 0) {
if (taskMapper.selectOne(new LambdaQueryWrapper<Task>().eq(Task::getTaskNo, taskNo)) == null) {
return taskNo;
}
// 重新生成
datetime = LocalDateTime.now().format(formatter);
random = String.format("%04d", new Random().nextInt(10000));
taskNo = "TASK" + datetime + random;
}
// 如果重试10次仍然重复,添加毫秒级时间戳确保唯一
return taskNo + System.currentTimeMillis() % 1000;
}
/**
* 生成第三方平台任务编号
* 格式:THIRD-yyyyMMddHHmmss-随机数
*/
private String generateThirdPartyTaskNo() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
String datetime = LocalDateTime.now().format(formatter);
String random = String.format("%06d", new Random().nextInt(1000000));
return "THIRD-" + datetime + "-" + random;
}
/**
* 获取任务列表
*/
public IPage<Task> getTaskList(Integer page, Integer pageSize, String status, Long taskTypeId, Long publisherDeptId, List<Long> publisherDeptIds, Long receiverDeptId) {
Page<Task> pageParam = new Page<>(page, pageSize);
LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
// 根据状态筛选
if (StringUtils.hasText(status)) {
wrapper.eq(Task::getStatus, status);
}
// 根据任务类型筛选
if (taskTypeId != null) {
wrapper.eq(Task::getTaskTypeId, taskTypeId);
}
// 根据发布者部门筛选(部门负责人视角切换时使用)
// 优先使用 publisherDeptIds(多个部门ID列表)
if (publisherDeptIds != null && !publisherDeptIds.isEmpty()) {
wrapper.in(Task::getPublisherDeptId, publisherDeptIds);
log.info("按多个发布者部门筛选: publisherDeptIds={}", publisherDeptIds);
} else if (publisherDeptId != null) {
wrapper.eq(Task::getPublisherDeptId, publisherDeptId);
}
// 根据接收部门筛选(部门负责人视角切换时使用,支持部门ID列表)
if (receiverDeptId != null) {
// 获取该部门及其所有子部门
List<Long> deptAndChildIds = getDeptAndChildIds(receiverDeptId);
wrapper.in(Task::getReceiverDeptId, deptAndChildIds);
}
// 未删除的任务
wrapper.eq(Task::getDeleted, 0);
// 按更新时间倒序
wrapper.orderByDesc(Task::getUpdateTime);
IPage<Task> result = taskMapper.selectPage(pageParam, wrapper);
// 填充关联信息
for (Task task : result.getRecords()) {
fillTaskRelatedInfo(task);
// 填充子任务统计信息(与getMyPublishedTasks保持一致)
fillSubTaskStats(task);
}
return result;
}
/**
* 获取所有待审核任务(处于审批流程中的任务)
* 包括:pending_demand_approval 和 pending_receiving_approval 状态的任务
* 非管理员只能看到分配给自己的待审核任务
*/
public IPage<Task> getAllPendingApprovalTasks(Long userId, Integer page, Integer pageSize, Long taskTypeId) {
Page<Task> pageParam = new Page<>(page, pageSize);
LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
// 查询处于审批流程中的任务
wrapper.in(Task::getApprovalStatus, "pending_demand_approval", "pending_receiving_approval");
// 检查用户是否是管理员
User user = userService.getUserById(userId);
boolean isAdmin = user != null && Integer.valueOf(1).equals(user.getIsAdmin());
// 非管理员只能看到分配给自己的待审核任务
if (!isAdmin) {
wrapper.eq(Task::getCurrentApproverId, userId);
}
// 根据任务类型筛选
if (taskTypeId != null) {
wrapper.eq(Task::getTaskTypeId, taskTypeId);
}
// 未删除的任务
wrapper.eq(Task::getDeleted, 0);
// 按创建时间倒序
wrapper.orderByDesc(Task::getCreateTime);
IPage<Task> result = taskMapper.selectPage(pageParam, wrapper);
// 填充关联信息
for (Task task : result.getRecords()) {
// 填充发布人名字
if (task.getPublisherId() != null) {
User publisher = userService.getUserById(task.getPublisherId());
if (publisher != null) {
task.setPublisherName(publisher.getRealName() != null ? publisher.getRealName() : publisher.getUsername());
}
}
// 填充接收人名字
if (task.getReceiverId() != null) {
User receiver = userService.getUserById(task.getReceiverId());
if (receiver != null) {
task.setReceiverName(receiver.getRealName() != null ? receiver.getRealName() : receiver.getUsername());
}
}
// 填充当前审核人姓名
if (task.getCurrentApproverId() != null) {
User approver = userService.getUserById(task.getCurrentApproverId());
if (approver != null) {
task.setCurrentApproverName(approver.getRealName() != null ? approver.getRealName() : approver.getUsername());
}
}
// 填充品牌名称
if (task.getBrandId() != null) {
Brand brand = brandMapper.selectById(task.getBrandId());
if (brand != null) {
task.setBrandName(brand.getName());
}
}
// 填充类目名称
if (task.getCategoryId() != null) {
Category category = categoryMapper.selectById(task.getCategoryId());
if (category != null) {
task.setCategoryName(category.getName());
}
}
// 填充平台名称
if (task.getPlatformId() != null) {
Platform platform = platformMapper.selectById(task.getPlatformId());
if (platform != null) {
task.setPlatformName(platform.getName());
}
}
// 从子任务聚合任务类型明细
if (task.getIsPipeline() != null && task.getIsPipeline() == 1) {
java.util.List<TaskItem> items = taskItemMapper.selectByTaskId(task.getId());
if (items != null && !items.isEmpty()) {
// 聚合所有子任务的 typeDetails
java.util.List<java.util.Map<String, Object>> allDetails = new java.util.ArrayList<>();
for (TaskItem item : items) {
if (item.getTypeDetails() != null && !item.getTypeDetails().isEmpty()) {
try {
com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
java.util.List<java.util.Map<String, Object>> itemDetails = objectMapper.readValue(
item.getTypeDetails(),
new com.fasterxml.jackson.core.type.TypeReference<java.util.List<java.util.Map<String, Object>>>() {}
);
allDetails.addAll(itemDetails);
} catch (Exception e) {
log.warn("解析子任务 {} 的 typeDetails 失败: {}", item.getId(), e.getMessage());
}
}
}
if (!allDetails.isEmpty()) {
try {
com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
task.setTypeDetails(objectMapper.writeValueAsString(allDetails));
} catch (Exception e) {
log.warn("序列化任务 {} 的 typeDetails 失败: {}", task.getId(), e.getMessage());
}
}
}
}
}
return result;
}
/**
* 获取已完成任务列表(根据角色权限)
* - 需求部门用户:看自己发起的已完成任务
* - 接单部门用户:看自己接单的已完成任务
* - 部门负责人:看本部门所有已完成任务
* - 管理员:看所有已完成任务
*/
public IPage<Task> getCompletedTasks(Integer page, Integer pageSize,
Long userId, String keyword, String sortBy, Long publisherDeptId) {
// 1. 获取用户信息和角色
User user = userService.getUserById(userId);
if (user == null) {
throw new BusinessException("用户不存在");
}
boolean isAdmin = isAdminUser(userId);
boolean isDeptHead = isDeptHeadUser(userId);
// 获取用户所属部门类型
String deptType = null;
if (user.getDeptId() != null) {
Department dept = departmentService.getDepartmentById(user.getDeptId());
if (dept != null) {
deptType = dept.getDeptType();
}
}
// 2. 构建查询条件
Page<Task> pageParam = new Page<>(page, pageSize);
LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
// 状态必须是已完成
wrapper.eq(Task::getStatus, "completed");
// 3. 如果指定了 publisherDeptId(部门负责人视角切换),优先使用
if (publisherDeptId != null) {
wrapper.eq(Task::getPublisherDeptId, publisherDeptId);
log.info("按发布者部门筛选已完成任务: publisherDeptId={}", publisherDeptId);
}
// 根据角色筛选数据范围
else if (isAdmin) {
// 管理员:查看所有
log.info("管理员查看所有已完成任务");
} else if (isDeptHead) {
// 部门负责人:查看本部门的
wrapper.and(w -> w
.eq(Task::getPublisherDeptId, user.getDeptId()) // 本部门发起的
.or()
.eq(Task::getReceiverDeptId, user.getDeptId()) // 或本部门接的
);
log.info("部门负责人查看部门已完成任务: deptId={}", user.getDeptId());
} else if ("demand".equals(deptType)) {
// 需求部门用户:看自己发起的已完成任务
wrapper.eq(Task::getPublisherId, userId);
log.info("需求部门用户查看自己发起的已完成任务: userId={}", userId);
} else {
// 接单部门用户:看自己接单的已完成任务
wrapper.eq(Task::getReceiverId, userId);
log.info("接单部门用户查看自己接单的已完成任务: userId={}", userId);
}
// 4. 关键词搜索
if (org.springframework.util.StringUtils.hasText(keyword)) {
wrapper.and(w -> w
.like(Task::getTitle, keyword)
.or()
.like(Task::getTaskNo, keyword)
);
}
// 5. 排序
if ("score".equals(sortBy)) {
wrapper.orderByDesc(Task::getScore);
} else {
wrapper.orderByDesc(Task::getCompleteTime); // 默认按完成时间降序
}
// 6. 查询并填充用户名
IPage<Task> result = taskMapper.selectPage(pageParam, wrapper);
for (Task task : result.getRecords()) {
// 填充发布人名字
if (task.getPublisherId() != null) {
User publisher = userService.getUserById(task.getPublisherId());
if (publisher != null) {
task.setPublisherName(publisher.getRealName());
}
}
// 填充接收人名字
if (task.getReceiverId() != null) {
User receiver = userService.getUserById(task.getReceiverId());
if (receiver != null) {
task.setReceiverName(receiver.getRealName());
}
}
// 填充当前审核人姓名
if (task.getCurrentApproverId() != null) {
User approver = userService.getUserById(task.getCurrentApproverId());
if (approver != null) {
task.setCurrentApproverName(approver.getRealName() != null ? approver.getRealName() : approver.getUsername());
}
}
// 对于流水线任务,如果主任务没有接单人,从子任务中获取
if ((task.getReceiverName() == null || task.getReceiverName().isEmpty())
&& task.getIsPipeline() != null && task.getIsPipeline() == 1) {
try {
List<TaskItem> subTasks = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, task.getId())
.isNotNull(TaskItem::getReceiverId)
.orderByAsc(TaskItem::getGroupNo)
);
if (!subTasks.isEmpty()) {
// 获取所有不同的接单人
java.util.Set<String> receiverNames = new java.util.LinkedHashSet<>();
for (TaskItem item : subTasks) {
if (item.getReceiverId() != null) {
User receiver = userService.getUserById(item.getReceiverId());
if (receiver != null && receiver.getRealName() != null) {
receiverNames.add(receiver.getRealName());
}
}
}
if (!receiverNames.isEmpty()) {
if (receiverNames.size() == 1) {
task.setReceiverName(receiverNames.iterator().next());
} else {
task.setReceiverName(receiverNames.iterator().next() + "等" + receiverNames.size() + "人");
}
}
}
} catch (Exception e) {
log.warn("获取流水线任务子任务接单人失败: taskId={}", task.getId());
}
}
}
return result;
}
/**
* 获取任务大厅(根据用户部门筛选可接单的任务)
*/
public IPage<Task> getTaskHall(Integer page, Integer pageSize, Long taskTypeId, Long demandTypeId,
Long brandId, Long categoryId, Long platformId, String taskSubType,
Integer minScore, Integer maxScore, String keyword, String publisherName,
String timeRange, String sortBy, Long userDeptId) {
Page<Task> pageParam = new Page<>(page, pageSize);
// 获取当前用户部门可以接单的任务类型ID列表(支持三级分类继承二级分类的配置)
List<Long> allowedTaskTypeIds = null;
if (userDeptId != null) {
List<DemandType> allDemandTypes = demandTypeMapper.selectList(null);
// 构建ID到DemandType的映射,用于快速查找父级
Map<Long, DemandType> demandTypeMap = allDemandTypes.stream()
.collect(Collectors.toMap(DemandType::getId, dt -> dt));
allowedTaskTypeIds = allDemandTypes.stream()
.filter(dt -> {
// 获取允许的部门ID(支持继承)
String allowedDepts = dt.getAllowedDeptIds();
if ((allowedDepts == null || allowedDepts.isEmpty()) && dt.getParentId() != null) {
// 三级分类没有配置,尝试获取父级(二级分类)的配置
DemandType parentType = demandTypeMap.get(dt.getParentId());
if (parentType != null) {
allowedDepts = parentType.getAllowedDeptIds();
}
}
// 如果allowedDeptIds为空,表示所有部门都可接单
if (allowedDepts == null || allowedDepts.isEmpty()) {
return true;
}
// 检查用户部门是否在允许列表中
String[] deptIds = allowedDepts.split(",");
for (String deptId : deptIds) {
if (deptId.trim().equals(String.valueOf(userDeptId))) {
return true;
}
}
return false;
})
.map(DemandType::getId)
.collect(Collectors.toList());
}
return taskMapper.selectTaskHallByDept(pageParam, taskTypeId, demandTypeId, brandId, categoryId,
platformId, taskSubType, minScore, maxScore, keyword,
publisherName, timeRange, sortBy, allowedTaskTypeIds);
}
/**
* 获取用户所属部门ID
*/
public Long getUserDeptId(Long userId) {
User user = userService.getUserById(userId);
return user != null ? user.getDeptId() : null;
}
/**
* 获取我发布的任务
*/
public IPage<Task> getMyPublishedTasks(Integer page, Integer pageSize, Long userId, String status) {
Page<Task> pageParam = new Page<>(page, pageSize);
IPage<Task> taskPage = taskMapper.selectMyPublishedTasks(pageParam, userId, status);
// 填充子任务统计信息
for (Task task : taskPage.getRecords()) {
fillSubTaskStats(task);
}
return taskPage;
}
/**
* 获取我接收的任务
*/
public IPage<Task> getMyReceivedTasks(Integer page, Integer pageSize, Long userId, String status) {
Page<Task> pageParam = new Page<>(page, pageSize);
IPage<Task> taskPage = taskMapper.selectMyReceivedTasks(pageParam, userId, status);
// 填充子任务统计信息
for (Task task : taskPage.getRecords()) {
fillSubTaskStats(task);
}
return taskPage;
}
/**
* 填充流水线任务的子任务统计信息
*/
private void fillSubTaskStats(Task task) {
// 只有流水线任务才需要填充子任务信息
if (task.getIsPipeline() == null || task.getIsPipeline() != 1) {
return;
}
try {
// 查询所有子任务
List<TaskItem> subTasks = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, task.getId())
.orderByAsc(TaskItem::getSortOrder)
);
// 汇总所有子任务的交付路径
if (subTasks != null && !subTasks.isEmpty()) {
List<String> allDeliveryPaths = subTasks.stream()
.filter(item -> item.getDeliveryPath() != null && !item.getDeliveryPath().isEmpty())
.map(item -> "【" + item.getItemNo() + "】" + item.getDeliveryPath())
.collect(Collectors.toList());
task.setAllDeliveryPaths(allDeliveryPaths);
}
if (subTasks != null && !subTasks.isEmpty()) {
task.setSubTaskTotal(subTasks.size());
// 统计各状态子任务数量
long completedCount = subTasks.stream().filter(item -> "completed".equals(item.getStatus())).count();
long inProgressCount = subTasks.stream().filter(item -> "in_progress".equals(item.getStatus())).count();
long pendingCount = subTasks.stream().filter(item -> "pending".equals(item.getStatus())).count();
task.setSubTaskCompleted((int) completedCount);
task.setSubTaskInProgress((int) inProgressCount);
task.setSubTaskPending((int) pendingCount);
// 填充子任务的接单人名称和deadline
for (TaskItem item : subTasks) {
if (item.getReceiverId() != null && (item.getReceiverName() == null || item.getReceiverName().isEmpty())) {
User receiver = userService.getUserById(item.getReceiverId());
if (receiver != null) {
item.setReceiverName(receiver.getRealName());
}
}
// 为没有deadline的进行中子任务动态计算deadline
log.info("fillSubTaskStats: 子任务{}: status={}, deadline={}, estimatedHours={}, updateTime={}",
item.getId(), item.getStatus(), item.getDeadline(),
item.getEstimatedHours(), item.getUpdateTime());
if (item.getDeadline() == null
&& "in_progress".equals(item.getStatus())
&& item.getEstimatedHours() != null
&& item.getEstimatedHours().compareTo(BigDecimal.ZERO) > 0
&& item.getUpdateTime() != null) {
// 使用updateTime作为接单时间(因为接单时会更新updateTime)
LocalDateTime deadline = workHoursCalculator.calculateDeadline(
item.getUpdateTime(),
item.getEstimatedHours()
);
item.setDeadline(deadline);
log.info("fillSubTaskStats: 为子任务{}动态计算deadline: {}", item.getId(), deadline);
}
}
// 设置子任务列表(包含处理人信息和deadline)
task.setSubTasks(subTasks);
// 聚合所有子任务的 typeDetails
java.util.List<java.util.Map<String, Object>> allDetails = new java.util.ArrayList<>();
for (TaskItem item : subTasks) {
if (item.getTypeDetails() != null && !item.getTypeDetails().isEmpty()) {
try {
com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
java.util.List<java.util.Map<String, Object>> itemDetails = objectMapper.readValue(
item.getTypeDetails(),
new com.fasterxml.jackson.core.type.TypeReference<java.util.List<java.util.Map<String, Object>>>() {}
);
allDetails.addAll(itemDetails);
} catch (Exception e) {
log.warn("解析子任务 {} 的 typeDetails 失败: {}", item.getId(), e.getMessage());
}
}
}
if (!allDetails.isEmpty()) {
try {
com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
task.setTypeDetails(objectMapper.writeValueAsString(allDetails));
} catch (Exception e) {
log.warn("序列化任务 {} 的 typeDetails 失败: {}", task.getId(), e.getMessage());
}
}
// 如果主任务没有receiverName,从子任务中获取接单人
if (task.getReceiverName() == null || task.getReceiverName().isEmpty()) {
// 收集所有不同的接单人名称
java.util.Set<String> receiverNames = new java.util.LinkedHashSet<>();
for (TaskItem item : subTasks) {
if (item.getReceiverName() != null && !item.getReceiverName().isEmpty()) {
receiverNames.add(item.getReceiverName());
}
}
if (!receiverNames.isEmpty()) {
if (receiverNames.size() == 1) {
task.setReceiverName(receiverNames.iterator().next());
} else {
task.setReceiverName(receiverNames.iterator().next() + "等" + receiverNames.size() + "人");
}
}
}
}
} catch (Exception e) {
log.error("填充子任务统计信息失败:taskId={}", task.getId(), e);
}
}
/**
* 获取任务详情
*/
public Task getTaskById(Long id) {
Task task = taskMapper.selectById(id);
if (task == null) {
throw new BusinessException(ResultCode.TASK_NOT_EXIST);
}
// 填充关联信息
fillTaskRelatedInfo(task);
return task;
}
/**
* 填充任务关联信息(品牌、类目、平台、发布人等)
*/
private void fillTaskRelatedInfo(Task task) {
// 填充品牌名称
if (task.getBrandId() != null) {
Brand brand = brandMapper.selectById(task.getBrandId());
if (brand != null) {
task.setBrandName(brand.getName());
}
}
// 填充类目名称
if (task.getCategoryId() != null) {
Category category = categoryMapper.selectById(task.getCategoryId());
if (category != null) {
task.setCategoryName(category.getName());
}
}
// 填充平台名称
if (task.getPlatformId() != null) {
Platform platform = platformMapper.selectById(task.getPlatformId());
if (platform != null) {
task.setPlatformName(platform.getName());
}
}
// 填充发布人名称
if (task.getPublisherId() != null) {
User publisher = userService.getUserById(task.getPublisherId());
if (publisher != null) {
task.setPublisherName(publisher.getRealName() != null ? publisher.getRealName() : publisher.getUsername());
}
}
// 填充发布人部门名称
if (task.getPublisherDeptId() != null) {
Department dept = departmentService.getDepartmentById(task.getPublisherDeptId());
if (dept != null) {
task.setPublisherDeptName(dept.getDeptName());
}
}
// 填充接收人名称
if (task.getReceiverId() != null) {
User receiver = userService.getUserById(task.getReceiverId());
if (receiver != null) {
task.setReceiverName(receiver.getRealName() != null ? receiver.getRealName() : receiver.getUsername());
}
}
// 填充当前审核人名称
if (task.getCurrentApproverId() != null) {
User approver = userService.getUserById(task.getCurrentApproverId());
if (approver != null) {
task.setCurrentApproverName(approver.getRealName() != null ? approver.getRealName() : approver.getUsername());
}
}
// 填充子任务统计信息
fillSubTaskStats(task);
}
/**
* 更新任务(普通任务)
*/
@Transactional(rollbackFor = Exception.class)
public void updateTask(Long id, Task task, Long userId) {
Task existTask = getTaskById(id);
// 验证权限:只有任务发布人才能更新任务
boolean isAdmin = isAdminUser(userId);
if (!isAdmin && !userId.equals(existTask.getPublisherId())) {
throw new BusinessException(ResultCode.FORBIDDEN, "只有任务发布人可以更新任务");
}
// 允许更新的任务状态:草稿、待审核(需求/接单部门审核)、被驳回
// 不允许更新的状态:进行中(in_progress)、已完成(completed)、已取消(cancelled)
String status = existTask.getStatus();
String approvalStatus = existTask.getApprovalStatus();
boolean canUpdate = "draft".equals(status) ||
"pending".equals(status) ||
(approvalStatus != null && (
"draft".equals(approvalStatus) ||
"pending_demand_approval".equals(approvalStatus) ||
"pending_receiving_approval".equals(approvalStatus) ||
approvalStatus.contains("rejected")
));
if (!canUpdate) {
throw new BusinessException(ResultCode.PARAM_ERROR, "任务已在进行中或已完成,无法编辑");
}
task.setId(id);
// 更新后重置审核状态为draft,以便重新提交审核
task.setApprovalStatus("draft");
task.setStatus("draft");
task.setRejectReason(null); // 清空驳回原因
taskMapper.updateById(task);
log.info("任务更新成功: taskId={}, userId={}, canUpdate={}", id, userId, canUpdate);
}
/**
* 更新流水线任务
*/
@Transactional(rollbackFor = Exception.class)
public void updatePipelineTask(Long id, com.designhub.dto.PipelineTaskDTO dto, Long userId) {
Task existTask = getTaskById(id);
// 验证权限:只有任务发布人才能更新任务
boolean isAdmin = isAdminUser(userId);
if (!isAdmin && !userId.equals(existTask.getPublisherId())) {
throw new BusinessException(ResultCode.FORBIDDEN, "只有任务发布人可以更新任务");
}
// 验证任务是否是流水线任务
if (existTask.getIsPipeline() == null || existTask.getIsPipeline() != 1) {
throw new BusinessException(ResultCode.PARAM_ERROR, "该任务不是流水线任务");
}
// 允许更新的任务状态:草稿、待审核(需求/接单部门审核)、被驳回
// 不允许更新的状态:进行中(in_progress)、已完成(completed)、已取消(cancelled)
String status = existTask.getStatus();
String approvalStatus = existTask.getApprovalStatus();
boolean canUpdate = "draft".equals(status) ||
"pending".equals(status) || // 待接单状态也允许编辑
(approvalStatus != null && (
"draft".equals(approvalStatus) ||
"pending_demand_approval".equals(approvalStatus) ||
"pending_receiving_approval".equals(approvalStatus) ||
approvalStatus.contains("rejected")
));
if (!canUpdate) {
throw new BusinessException(ResultCode.PARAM_ERROR, "任务已在进行中或已完成,无法编辑");
}
// 验证流水线模式
if (!"sequential".equals(dto.getPipelineMode()) && !"parallel".equals(dto.getPipelineMode())) {
throw new BusinessException(ResultCode.PARAM_ERROR, "流水线模式必须是 sequential 或 parallel");
}
// 验证子任务列表
if (dto.getItems() == null || dto.getItems().isEmpty()) {
throw new BusinessException(ResultCode.PARAM_ERROR, "流水线任务至少需要一个子任务");
}
// 计算总积分、工时和数量(使用BigDecimal保留小数精度)
BigDecimal totalScoreDecimal = BigDecimal.ZERO;
double totalHours = 0;
int totalQuantity = 0;
for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : dto.getItems()) {
var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId());
if (demandType == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "任务类型ID无效: " + itemDto.getTaskTypeId());
}
BigDecimal itemScore = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity()));
double itemHours = demandType.getEstimatedHours() != null ?
demandType.getEstimatedHours().multiply(BigDecimal.valueOf(itemDto.getQuantity())).doubleValue() : 0;
totalScoreDecimal = totalScoreDecimal.add(itemScore);
totalHours += itemHours;
totalQuantity += itemDto.getQuantity();
}
// 保留2位小数
totalScoreDecimal = totalScoreDecimal.setScale(2, java.math.RoundingMode.HALF_UP);
int totalScoreInt = totalScoreDecimal.intValue(); // 用于积分检查
// 检查部门积分是否充足(管理员跳过积分检查)
User user = userService.getUserById(userId);
if (!isAdmin && user.getDeptId() != null) {
if (!teamScorePoolService.checkBalance(user.getDeptId(), totalScoreInt)) {
throw new BusinessException(ResultCode.INSUFFICIENT_SCORE, "部门积分余额不足");
}
if (!teamScorePoolService.checkPeriodLimit(user.getDeptId(), totalScoreInt)) {
throw new BusinessException(ResultCode.SCORE_LIMIT_EXCEEDED, "超过本周期积分消耗限制");
}
}
// 更新主任务信息
existTask.setTitle(dto.getTitle());
existTask.setPipelineMode(dto.getPipelineMode());
existTask.setScore(totalScoreDecimal);
existTask.setEstimatedHours(BigDecimal.valueOf(totalHours));
existTask.setQuantity(totalQuantity);
existTask.setBrandId(dto.getBrandId());
existTask.setCategoryId(dto.getCategoryId());
existTask.setPlatformId(dto.getPlatformId());
existTask.setSpuInfo(dto.getSpuInfo());
existTask.setTaskSubType(dto.getTaskSubType());
existTask.setAttachmentUrl(dto.getAttachmentUrl());
existTask.setPreviewImage(dto.getPreviewImage());
existTask.setRemark(dto.getRemark());
existTask.setPriority(dto.getPriority() != null ? dto.getPriority() : 1);
existTask.setReleaseType(dto.getReleaseType() != null ? dto.getReleaseType() : "recruitment");
// 设置商品条码列表
if (dto.getProductBarcodes() != null && !dto.getProductBarcodes().isEmpty()) {
existTask.setProductBarcodes(String.join(",", dto.getProductBarcodes()));
}
// 设置任务类型ID
if (dto.getItems() != null && !dto.getItems().isEmpty()) {
existTask.setTaskTypeId(dto.getItems().get(0).getTaskTypeId());
String taskTypeIds = dto.getItems().stream()
.map(item -> String.valueOf(item.getTaskTypeId()))
.collect(Collectors.joining(","));
existTask.setTaskTypeIds(taskTypeIds);
}
// 重置审核状态为draft,以便重新提交审核
existTask.setApprovalStatus("draft");
existTask.setStatus("draft");
existTask.setRejectReason(null); // 清空驳回原因
existTask.setCurrentApproverId(null);
existTask.setCurrentApproverRole(null);
existTask.setUpdateTime(LocalDateTime.now());
// 如果是assigned模式,设置接收人
Long mainReceiverId = dto.getReceiverId();
if ("assigned".equals(dto.getReleaseType()) && mainReceiverId == null && dto.getItems() != null && !dto.getItems().isEmpty()) {
mainReceiverId = dto.getItems().get(0).getReceiverId();
}
if ("assigned".equals(dto.getReleaseType()) && mainReceiverId != null) {
existTask.setReceiverId(mainReceiverId);
User receiver = userService.getUserById(mainReceiverId);
if (receiver != null && receiver.getDeptId() != null) {
existTask.setReceiverDeptId(receiver.getDeptId());
}
} else {
existTask.setReceiverId(null);
existTask.setReceiverDeptId(null);
}
// 更新主任务
taskMapper.updateById(existTask);
log.info("主任务更新成功: taskId={}, taskNo={}", existTask.getId(), existTask.getTaskNo());
// 删除旧的子任务
taskItemMapper.delete(new LambdaQueryWrapper<com.designhub.entity.TaskItem>()
.eq(com.designhub.entity.TaskItem::getTaskId, id));
log.info("已删除旧的子任务: taskId={}", id);
// 重新创建子任务(使用与createPipelineTask相同的逻辑)
createPipelineTaskItemsForUpdate(existTask, dto);
// 重新初始化审核状态并发送通知
initApprovalStatus(existTask);
// 再次保存任务,因为initApprovalStatus修改了approvalStatus、currentApproverId等字段
taskMapper.updateById(existTask);
log.info("任务审核状态已重新初始化: taskId={}, approvalStatus={}, currentApproverId={}",
existTask.getId(), existTask.getApprovalStatus(), existTask.getCurrentApproverId());
sendApprovalNotification(existTask);
log.info("流水线任务更新成功: taskId={}, userId={}", id, userId);
}
/**
* 为更新操作创建流水线子任务(复用createPipelineTask的子任务创建逻辑)
*/
private void createPipelineTaskItemsForUpdate(Task mainTask, com.designhub.dto.PipelineTaskDTO dto) {
// **步骤1:按接单部门和设计师自动分组**
// 规则:同一部门内如果指定了不同的设计师,也需要拆分为不同的子任务
int currentGroupNo = 1;
String lastGroupKey = null; // 格式:deptKey_receiverId
for (int i = 0; i < dto.getItems().size(); i++) {
com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto = dto.getItems().get(i);
var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId());
// 获取当前任务类型的接单部门配置
String currentDeptKey;
if (itemDto.getSelectedDeptId() != null) {
currentDeptKey = String.valueOf(itemDto.getSelectedDeptId());
} else {
currentDeptKey = demandType.getAllowedDeptIds();
if (currentDeptKey == null && demandType.getParentId() != null) {
var parentType = configService.getDemandTypeById(demandType.getParentId());
if (parentType != null) {
currentDeptKey = parentType.getAllowedDeptIds();
}
}
}
// 🔥 构建分组键:部门 + 设计师(如果指定了设计师)
String currentGroupKey;
if (itemDto.getReceiverId() != null) {
// 如果指定了设计师,分组键 = 部门_设计师ID
currentGroupKey = (currentDeptKey != null ? currentDeptKey : "null") + "_" + itemDto.getReceiverId();
} else {
// 没有指定设计师,分组键 = 部门_0
currentGroupKey = (currentDeptKey != null ? currentDeptKey : "null") + "_0";
}
// 判断是否需要创建新分组
boolean needNewGroup = false;
if (i > 0) {
// 如果分组键不同,则需要创建新分组
if (lastGroupKey == null || !lastGroupKey.equals(currentGroupKey)) {
needNewGroup = true;
}
}
if (needNewGroup) {
currentGroupNo++;
log.info("更新任务分组变化: groupNo={}, 前一分组键={}, 当前分组键={}",
currentGroupNo, lastGroupKey, currentGroupKey);
}
itemDto.setSortOrder(currentGroupNo * 1000 + i);
lastGroupKey = currentGroupKey;
}
// **步骤2:两级分组 - 先按部门分组,再按设计师分组**
Map<Integer, List<com.designhub.dto.PipelineTaskDTO.TaskItemDTO>> groupedItems = new java.util.LinkedHashMap<>();
for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : dto.getItems()) {
int itemGroupNo = itemDto.getSortOrder() / 1000;
groupedItems.computeIfAbsent(itemGroupNo, k -> new java.util.ArrayList<>()).add(itemDto);
}
// 按部门重新组织分组,计算部门序号
Map<String, List<Integer>> deptToGroups = new java.util.LinkedHashMap<>();
Map<Integer, String> groupToDeptKey = new java.util.HashMap<>();
for (int i = 0; i < dto.getItems().size(); i++) {
com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto = dto.getItems().get(i);
int itemGroupNo = itemDto.getSortOrder() / 1000;
String deptKey;
if (itemDto.getSelectedDeptId() != null) {
deptKey = String.valueOf(itemDto.getSelectedDeptId());
} else {
var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId());
deptKey = demandType.getAllowedDeptIds();
if (deptKey == null && demandType.getParentId() != null) {
var parentType = configService.getDemandTypeById(demandType.getParentId());
if (parentType != null) {
deptKey = parentType.getAllowedDeptIds();
}
}
}
if (deptKey == null) deptKey = "null";
if (!groupToDeptKey.containsKey(itemGroupNo)) {
groupToDeptKey.put(itemGroupNo, deptKey);
deptToGroups.computeIfAbsent(deptKey, k -> new java.util.ArrayList<>()).add(itemGroupNo);
}
}
// 计算每个部门的序号
Map<String, Integer> deptIndexMap = new java.util.LinkedHashMap<>();
int deptIndex = 1;
for (String deptKey : deptToGroups.keySet()) {
deptIndexMap.put(deptKey, deptIndex++);
}
// 计算每个分组在其部门内的子序号
Map<Integer, Integer> groupSubIndexMap = new java.util.HashMap<>();
for (Map.Entry<String, List<Integer>> entry : deptToGroups.entrySet()) {
List<Integer> groups = entry.getValue();
for (int subIdx = 0; subIdx < groups.size(); subIdx++) {
groupSubIndexMap.put(groups.get(subIdx), subIdx + 1);
}
}
// 为每个分组创建一个子任务
int itemIndex = 1;
for (Map.Entry<Integer, List<com.designhub.dto.PipelineTaskDTO.TaskItemDTO>> entry : groupedItems.entrySet()) {
int groupNo = entry.getKey();
List<com.designhub.dto.PipelineTaskDTO.TaskItemDTO> groupItems = entry.getValue();
String deptKey = groupToDeptKey.get(groupNo);
int currentDeptIndex = deptIndexMap.getOrDefault(deptKey, 1);
int subIndex = groupSubIndexMap.getOrDefault(groupNo, 1);
// 汇总该分组的积分、工时、数量(使用 BigDecimal 保留小数精度)
BigDecimal groupTotalScoreDecimal = BigDecimal.ZERO;
BigDecimal groupTotalHours = BigDecimal.ZERO;
int groupTotalQuantity = 0;
Long firstTaskTypeId = null;
java.util.List<java.util.Map<String, Object>> typeDetailsList = new java.util.ArrayList<>();
for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : groupItems) {
var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId());
// 使用 BigDecimal 保留小数精度
BigDecimal itemScoreDecimal = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity()));
BigDecimal itemHours = demandType.getEstimatedHours() != null
? demandType.getEstimatedHours().multiply(BigDecimal.valueOf(itemDto.getQuantity()))
: BigDecimal.ZERO;
groupTotalScoreDecimal = groupTotalScoreDecimal.add(itemScoreDecimal);
groupTotalHours = groupTotalHours.add(itemHours);
groupTotalQuantity += itemDto.getQuantity();
if (firstTaskTypeId == null) {
firstTaskTypeId = itemDto.getTaskTypeId();
}
// 添加任务类型明细(包含预先指定的设计师信息)
java.util.Map<String, Object> detail = new java.util.LinkedHashMap<>();
detail.put("typeId", itemDto.getTaskTypeId());
detail.put("typeName", demandType.getName());
detail.put("quantity", itemDto.getQuantity());
detail.put("unitScore", demandType.getUnitScore());
// 保存精确的小数积分值(保留2位小数)
detail.put("totalScore", itemScoreDecimal.setScale(2, java.math.RoundingMode.HALF_UP).doubleValue());
// 如果指定了设计师,也保存到明细中
if (itemDto.getReceiverId() != null) {
detail.put("receiverId", itemDto.getReceiverId());
User receiver = userService.getUserById(itemDto.getReceiverId());
if (receiver != null) {
detail.put("receiverName", receiver.getRealName());
}
}
typeDetailsList.add(detail);
}
// 保留2位小数
BigDecimal groupTotalScore = groupTotalScoreDecimal.setScale(2, java.math.RoundingMode.HALF_UP);
// 创建子任务
com.designhub.entity.TaskItem item = new com.designhub.entity.TaskItem();
item.setTaskId(mainTask.getId());
// 使用新的编号格式:任务编号-部门序号-部门内子任务序号
item.setItemNo(generateTaskItemNoWithDept(mainTask.getTaskNo(), currentDeptIndex, subIndex));
item.setGroupNo(groupNo);
item.setTaskTypeId(firstTaskTypeId);
try {
com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
item.setTypeDetails(objectMapper.writeValueAsString(typeDetailsList));
} catch (Exception e) {
log.warn("序列化任务类型明细失败: {}", e.getMessage());
}
item.setQuantity(groupTotalQuantity);
// 计算平均单价(保留2位小数)
item.setUnitScore(groupTotalQuantity > 0
? groupTotalScore.divide(BigDecimal.valueOf(groupTotalQuantity), 2, java.math.RoundingMode.HALF_UP)
: BigDecimal.ZERO);
item.setTotalScore(groupTotalScore);
item.setEstimatedHours(groupTotalHours);
item.setSortOrder(itemIndex);
item.setProgress(0);
// 如果是assigned模式,设置子任务的接收人
if ("assigned".equals(dto.getReleaseType()) && !groupItems.isEmpty()) {
Long itemReceiverId = groupItems.get(0).getReceiverId();
if (itemReceiverId != null) {
item.setReceiverId(itemReceiverId);
User itemReceiver = userService.getUserById(itemReceiverId);
if (itemReceiver != null) {
item.setReceiverName(itemReceiver.getRealName());
if (itemReceiver.getDeptId() != null) {
item.setReceiverDeptId(itemReceiver.getDeptId());
}
}
}
}
// 根据流水线模式和分组设置初始状态
if ("sequential".equals(dto.getPipelineMode())) {
// 顺序流水线:第一个部门的所有子任务都可接单(部门内并行)
if (currentDeptIndex == 1) {
item.setStatus("pending");
item.setIsLocked(0);
item.setPrevItemId(null);
} else {
// 其他部门的子任务锁定,等待前一个部门完成
item.setStatus("locked");
item.setIsLocked(1);
}
} else {
item.setStatus("pending");
item.setIsLocked(0);
}
taskItemMapper.insert(item);
log.info("子任务创建成功,ID: {}, 编号: {}, 部门序号: {}, 部门内序号: {}",
item.getId(), item.getItemNo(), currentDeptIndex, subIndex);
itemIndex++;
}
log.info("流水线子任务创建完成,主任务ID: {}, 子任务数: {}", mainTask.getId(), groupedItems.size());
}
/**
* 抢单
*/
@Transactional(rollbackFor = Exception.class)
public void grabTask(Long taskId, Long userId) {
log.info("用户 {} 抢单任务 {}", userId, taskId);
Task task = getTaskById(taskId);
if (!"pending".equals(task.getStatus())) {
throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "任务不是待接单状态");
}
if (task.getReceiverId() != null) {
throw new BusinessException(ResultCode.TASK_ALREADY_TAKEN);
}
if (!"recruitment".equalsIgnoreCase(task.getReleaseType())) {
throw new BusinessException(ResultCode.FORBIDDEN, "该任务不支持抢单");
}
// 获取用户信息
var user = userService.getUserById(userId);
if (user == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "用户不存在");
}
// 检查是否是管理员(管理员可以抢单,绕过所有限制)
boolean isAdmin = isAdminUser(userId);
Long deptId = user.getDeptId();
// 非管理员需要进行权限验证
if (!isAdmin) {
if (deptId == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "您未分配部门,无法接单");
}
// 获取用户所属部门
Department department = departmentService.getDepartmentById(deptId);
if (department == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "未找到用户所属部门,请联系管理员");
}
// 检查1:需求部门不能抢单
if ("demand".equals(department.getDeptType())) {
throw new BusinessException(ResultCode.FORBIDDEN, "需求部门成员不能接单,只能发起需求");
}
// 检查2:接单部门类型验证
if (!"receiving".equals(department.getDeptType())) {
throw new BusinessException(ResultCode.FORBIDDEN, "只有接单部门成员可以接收任务");
}
// 检查3:部门负责人不能抢单
if (department.getLeaderId() != null && department.getLeaderId().equals(userId)) {
throw new BusinessException(ResultCode.FORBIDDEN, "部门负责人不能接单,请分配给设计师");
}
// 检查4:验证部门是否允许接该需求类型
if (task.getDemandTypeId() != null) {
validateAllowedDemandTypes(department, task.getDemandTypeId());
}
} else {
log.info("管理员 {} 抢单任务 {}", user.getRealName(), taskId);
}
LocalDateTime now = LocalDateTime.now();
// 重新计算deadline:从接单时间开始计算,无论之前是否有deadline(使用工作时间计算)
LocalDateTime deadline = null;
if (task.getEstimatedHours() != null && task.getEstimatedHours().compareTo(BigDecimal.ZERO) > 0) {
deadline = workHoursCalculator.calculateDeadline(now, task.getEstimatedHours());
log.info("任务deadline已重新计算:从接单时间 {} 开始,预估 {} 小时,截止时间 {}",
now, task.getEstimatedHours(), deadline);
}
int updated = taskMapper.grabTask(taskId, userId, deptId, now, deadline);
if (updated == 0) {
throw new BusinessException(ResultCode.TASK_ALREADY_TAKEN, "任务已被其他设计师抢走");
}
log.info("用户 {} 成功抢单任务 {}", userId, taskId);
// 发送通知给任务发起人
if (task.getPublisherId() != null) {
try {
notificationService.sendNotification(
task.getPublisherId(),
"任务已被接单",
String.format("您的任务【%s】已被 %s 接单", task.getTitle(), user.getRealName() != null ? user.getRealName() : user.getUsername()),
"task_grabbed",
taskId
);
} catch (Exception e) {
log.error("发送抢单通知失败", e);
}
}
}
/**
* 提交任务
*/
@Transactional(rollbackFor = Exception.class)
public void submitTask(Long taskId, Long userId, String deliveryPath) {
log.info("用户 {} 提交任务 {}", userId, taskId);
Task task = getTaskById(taskId);
// 验证权限(必须是任务接收人)
if (!userId.equals(task.getReceiverId())) {
throw new BusinessException(ResultCode.FORBIDDEN, "只有任务接收人可以提交");
}
// 验证任务状态:允许 in_progress 或 rejected(驳回后重新提交)
if (!"in_progress".equals(task.getStatus()) && !"rejected".equals(task.getStatus())) {
throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "任务状态不允许提交,当前状态:" + task.getStatus());
}
// 更新任务
task.setDeliveryPath(deliveryPath);
task.setStatus("review");
task.setCompleteTime(LocalDateTime.now());
task.setProgress(100);
taskMapper.updateById(task);
// 发送通知给发布人
if (task.getPublisherId() != null) {
try {
User receiver = userService.getUserById(userId);
String receiverName = receiver != null ?
(receiver.getRealName() != null ? receiver.getRealName() : receiver.getUsername()) : "设计师";
notificationService.sendTaskNotification(
task.getPublisherId(),
taskId,
"任务已提交待审核",
String.format("您发布的任务【%s】已由 %s 提交,请及时审核", task.getTitle(), receiverName)
);
} catch (Exception e) {
log.error("发送任务提交通知失败", e);
}
}
}
/**
* 审核任务
*/
@Transactional(rollbackFor = Exception.class)
public void reviewTask(Long taskId, Long userId, Boolean approved, String comment, Integer rating) {
log.info("用户 {} 审核任务 {}, 结果: {}", userId, taskId, approved);
Task task = getTaskById(taskId);
// 验证权限(必须是任务发布人)
if (!userId.equals(task.getPublisherId())) {
throw new BusinessException(ResultCode.FORBIDDEN, "只有任务发布人可以审核");
}
// 验证任务状态
if (!"review".equals(task.getStatus())) {
throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "任务不是待审核状态");
}
if (approved) {
// 审核通过
task.setStatus("completed");
task.setReviewComment(comment);
task.setRating(rating);
// 设置完成时间(如果还没有设置)
if (task.getCompleteTime() == null) {
task.setCompleteTime(LocalDateTime.now());
}
// 检查是否延误并计算扣分
if (task.getDeadline() != null && task.getCompleteTime() != null
&& task.getCompleteTime().isAfter(task.getDeadline())) {
task.setIsDelayed(1);
// 计算延误天数
long delayDays = java.time.temporal.ChronoUnit.DAYS.between(
task.getDeadline(), task.getCompleteTime());
task.setDelayDays((int) delayDays);
}
// 计算延误扣分(移到外层作用域)
DelayPenaltyRecord delayRecord = null;
if (task.getCompleteTime() != null && task.getDeadline() != null
&& task.getCompleteTime().isAfter(task.getDeadline())
&& task.getReceiverId() != null) {
delayRecord = delayPenaltyService.calculateDelayPenalty(
taskId,
task.getReceiverId(),
task.getDeadline(),
task.getCompleteTime()
);
if (delayRecord != null) {
// 保存延误扣分记录
delayPenaltyService.saveDelayPenaltyRecord(delayRecord);
log.info("任务延误扣分: taskId={}, designerId={}, penaltyScore={}",
taskId, task.getReceiverId(), delayRecord.getPenaltyScore());
}
}
// 奖励设计师所在部门积分(任务奖励)
if (task.getReceiverDeptId() != null && task.getScore() != null) {
teamScorePoolService.addScore(task.getReceiverDeptId(), task.getScore().intValue());
// 如果有延误扣分,从积分池中扣除
if (delayRecord != null && delayRecord.getPenaltyScore() > 0
&& "confirmed".equals(delayRecord.getStatus())) {
try {
teamScorePoolService.deductScore(task.getReceiverDeptId(), delayRecord.getPenaltyScore());
log.info("已扣除延误扣分: deptId={}, penaltyScore={}",
task.getReceiverDeptId(), delayRecord.getPenaltyScore());
} catch (Exception e) {
log.error("扣除延误扣分失败: {}", e.getMessage());
}
}
}
} else {
// 审核驳回
task.setStatus("rejected");
task.setRejectReason(comment);
// 驳回后设计师可以重新提交,无需更改积分
}
taskMapper.updateById(task);
// 发送通知给设计师
if (task.getReceiverId() != null) {
try {
if (approved) {
// 审核通过通知
notificationService.sendTaskNotification(
task.getReceiverId(),
taskId,
"任务审核通过",
String.format("恭喜!您完成的任务【%s】已审核通过,积分已发放", task.getTitle())
);
} else {
// 驳回通知
notificationService.sendTaskNotification(
task.getReceiverId(),
taskId,
"任务被驳回",
String.format("您提交的任务【%s】被驳回,原因:%s,请修改后重新提交", task.getTitle(), comment)
);
}
} catch (Exception e) {
log.error("发送任务审核通知失败", e);
}
}
}
/**
* 对已完成的任务进行评分(需求方)
*/
@Transactional(rollbackFor = Exception.class)
public void rateTask(Long taskId, Long userId, Integer rating, String comment) {
log.info("用户 {} 对任务 {} 评分: {}", userId, taskId, rating);
Task task = getTaskById(taskId);
// 验证权限(必须是任务发布人)
if (!userId.equals(task.getPublisherId())) {
throw new BusinessException(ResultCode.FORBIDDEN, "只有任务发布人可以评分");
}
// 验证任务状态(必须是已完成)
if (!"completed".equals(task.getStatus())) {
throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "只能对已完成的任务评分");
}
// 验证评分是否已存在
if (task.getRating() != null && task.getRating() > 0) {
throw new BusinessException(ResultCode.PARAM_ERROR, "该任务已经评分过了");
}
// 验证评分值
if (rating == null || rating < 1 || rating > 5) {
throw new BusinessException(ResultCode.PARAM_ERROR, "评分必须在1-5之间");
}
// 更新评分
task.setRating(rating);
if (comment != null && !comment.trim().isEmpty()) {
task.setReviewComment(comment);
}
taskMapper.updateById(task);
// 发送通知给设计师
if (task.getReceiverId() != null) {
try {
User publisher = userService.getUserById(userId);
String publisherName = publisher != null ?
(publisher.getRealName() != null ? publisher.getRealName() : publisher.getUsername()) : "需求方";
String ratingDesc = rating == 5 ? "五星好评" : rating + "星评价";
notificationService.sendTaskNotification(
task.getReceiverId(),
taskId,
"任务获得评分",
String.format("您完成的任务【%s】已被 %s 评为 %s", task.getTitle(), publisherName, ratingDesc)
);
} catch (Exception e) {
log.error("发送任务评分通知失败", e);
}
}
log.info("任务 {} 评分成功: {} 星", taskId, rating);
}
/**
* 取消任务
*/
@Transactional(rollbackFor = Exception.class)
public void cancelTask(Long taskId, Long userId, String reason) {
log.info("用户 {} 取消任务 {}", userId, taskId);
Task task = getTaskById(taskId);
// 验证权限(必须是任务发布人)
if (!userId.equals(task.getPublisherId())) {
throw new BusinessException(ResultCode.FORBIDDEN, "只有任务发布人可以取消");
}
// 验证任务状态(不能是已完成)
if ("completed".equals(task.getStatus())) {
throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "已完成的任务不能取消");
}
// 更新任务状态
task.setStatus("cancelled");
task.setRejectReason(reason);
taskMapper.updateById(task);
// 返还积分(任务取消时返还给发布部门)
if (task.getPublisherDeptId() != null && task.getScore() != null) {
teamScorePoolService.addScore(task.getPublisherDeptId(), task.getScore().intValue());
}
// 发送通知给接单设计师
if (task.getReceiverId() != null) {
try {
notificationService.sendTaskNotification(
task.getReceiverId(),
taskId,
"任务已取消",
String.format("任务【%s】已被发起人取消,原因:%s", task.getTitle(), reason != null ? reason : "无")
);
} catch (Exception e) {
log.error("发送任务取消通知失败", e);
}
}
}
/**
* 检查用户是否是管理员(ADMIN或SUPER_ADMIN)
*/
private boolean isAdminUser(Long userId) {
try {
// 首先检查用户的 isAdmin 字段
User user = userService.getUserById(userId);
if (user != null && Integer.valueOf(1).equals(user.getIsAdmin())) {
return true;
}
// 其次检查角色
var roles = roleService.getRolesByUserId(userId);
return roles.stream().anyMatch(role ->
"ADMIN".equals(role.getRoleCode()) ||
"SUPER_ADMIN".equals(role.getRoleCode())
);
} catch (Exception e) {
log.warn("检查用户角色失败: userId={}, error={}", userId, e.getMessage());
return false;
}
}
/**
* 检查用户是否是部门负责人
*/
private boolean isDeptHeadUser(Long userId) {
try {
User user = userService.getUserById(userId);
if (user == null || user.getDeptId() == null) {
return false;
}
Department dept = departmentService.getDepartmentById(user.getDeptId());
if (dept == null) {
return false;
}
// 检查是否是部门负责人
return userId.equals(dept.getLeaderId());
} catch (Exception e) {
log.warn("检查部门负责人失败: userId={}, error={}", userId, e.getMessage());
return false;
}
}
private void validateAllowedDemandTypes(Department department, Long demandTypeId) {
if (department == null || !StringUtils.hasText(department.getAllowedDemandTypes()) || demandTypeId == null) {
return;
}
Set<Long> allowed = Arrays.stream(department.getAllowedDemandTypes().split(","))
.map(String::trim)
.filter(str -> !str.isEmpty())
.map(Long::valueOf)
.collect(Collectors.toSet());
if (!allowed.isEmpty() && !allowed.contains(demandTypeId)) {
throw new BusinessException(ResultCode.FORBIDDEN, "您所在部门不支持该需求类型的任务");
}
}
/**
* 提交任务审核
*/
@Transactional(rollbackFor = Exception.class)
public boolean submitTaskForAudit(Long taskId, Long userId) {
log.info("提交任务审核: taskId={}, userId={}", taskId, userId);
Task task = taskMapper.selectById(taskId);
if (task == null) {
throw new BusinessException("任务不存在");
}
// 检查是否是任务发布人
if (!task.getPublisherId().equals(userId)) {
throw new BusinessException("只有任务发布人才能提交审核");
}
// 检查任务状态
if (!"draft".equals(task.getAuditStatus())) {
throw new BusinessException("只有草稿状态的任务才能提交审核");
}
// 更新审核状态
task.setAuditStatus("pending_audit");
taskMapper.updateById(task);
// 记录审核日志
TaskAuditLog auditLog = new TaskAuditLog();
auditLog.setTaskId(taskId);
auditLog.setAuditorId(userId);
auditLog.setAction("submit");
auditLog.setComment("提交审核");
taskAuditLogMapper.insert(auditLog);
// 发送通知给部门负责人
Department department = departmentService.getDepartmentById(task.getPublisherDeptId());
if (department != null && department.getLeaderId() != null) {
User publisher = userService.getUserById(userId);
String publisherName = publisher != null ?
(publisher.getRealName() != null ? publisher.getRealName() : publisher.getUsername())
: "未知用户";
notificationService.sendTaskNotification(
department.getLeaderId(),
taskId,
"【待审核】新任务提交审核",
String.format("%s 提交了任务《%s》,请及时审核", publisherName, task.getTitle())
);
log.info("已发送审核通知给负责人: leaderId={}, taskId={}", department.getLeaderId(), taskId);
}
return true;
}
/**
* 审核任务
*/
@Transactional(rollbackFor = Exception.class)
public boolean auditTask(TaskAuditRequest request, Long auditorId) {
log.info("审核任务: taskId={}, result={}, auditorId={}",
request.getTaskId(), request.getResult(), auditorId);
Task task = taskMapper.selectById(request.getTaskId());
if (task == null) {
throw new BusinessException("任务不存在");
}
// 检查是否是部门负责人
User auditor = userService.getUserById(auditorId);
if (auditor == null) {
throw new BusinessException("用户不存在");
}
Department publisherDept = departmentService.getDepartmentById(task.getPublisherDeptId());
if (publisherDept == null || !auditorId.equals(publisherDept.getLeaderId())) {
throw new BusinessException("只有发起部门的负责人才能审核任务");
}
// 检查任务状态
if (!"pending_audit".equals(task.getAuditStatus())) {
throw new BusinessException("只有待审核状态的任务才能审核");
}
// 更新审核状态
if ("pass".equals(request.getResult())) {
task.setAuditStatus("audit_pass");
task.setStatus("pending"); // 审核通过后变为待接单状态
// 审核通过后扣除部门积分
if (task.getPublisherDeptId() != null && task.getScore() != null) {
int scoreInt = task.getScore().intValue();
// 检查积分是否充足
if (!teamScorePoolService.checkBalance(task.getPublisherDeptId(), scoreInt)) {
throw new BusinessException("部门积分余额不足,无法通过审核");
}
// 扣除积分
teamScorePoolService.consumeScore(task.getPublisherDeptId(), scoreInt);
log.info("审核通过,扣除部门积分: deptId={}, score={}", task.getPublisherDeptId(), task.getScore());
}
} else if ("reject".equals(request.getResult())) {
task.setAuditStatus("audit_reject");
task.setStatus("draft"); // 审核驳回后回到草稿状态
} else {
throw new BusinessException("审核结果参数错误");
}
task.setAuditorId(auditorId);
task.setAuditTime(LocalDateTime.now());
task.setAuditComment(request.getComment());
taskMapper.updateById(task);
// 记录审核日志
TaskAuditLog auditLog = new TaskAuditLog();
auditLog.setTaskId(task.getId());
auditLog.setAuditorId(auditorId);
auditLog.setAuditorName(auditor.getRealName() != null ? auditor.getRealName() : auditor.getUsername());
auditLog.setAction(request.getResult());
auditLog.setComment(request.getComment());
taskAuditLogMapper.insert(auditLog);
// 发送审核结果通知给任务发起人
if (task.getPublisherId() != null) {
String auditorName = auditor.getRealName() != null ? auditor.getRealName() : auditor.getUsername();
String resultText = "pass".equals(request.getResult()) ? "通过" : "驳回";
String title = String.format("【审核%s】任务《%s》", resultText, task.getTitle());
String content;
if ("pass".equals(request.getResult())) {
content = String.format("%s 审核通过了您的任务《%s》,任务已发布到任务大厅",
auditorName, task.getTitle());
if (request.getComment() != null && !request.getComment().isEmpty()) {
content += "。审核意见:" + request.getComment();
}
} else {
content = String.format("%s 驳回了您的任务《%s》", auditorName, task.getTitle());
if (request.getComment() != null && !request.getComment().isEmpty()) {
content += "。驳回原因:" + request.getComment();
}
}
notificationService.sendTaskNotification(
task.getPublisherId(),
task.getId(),
title,
content
);
log.info("已发送审核结果通知: publisherId={}, result={}, taskId={}",
task.getPublisherId(), request.getResult(), task.getId());
}
return true;
}
/**
* 强制指派任务给部门成员
*/
@Transactional(rollbackFor = Exception.class)
public boolean assignTask(TaskAssignRequest request, Long assignerId) {
log.info("强制指派任务: taskId={}, assigneeId={}, assignerId={}",
request.getTaskId(), request.getAssigneeId(), assignerId);
Task task = taskMapper.selectById(request.getTaskId());
if (task == null) {
throw new BusinessException("任务不存在");
}
// 检查任务状态(只有待接单或招募中的任务可以指派)
if (!"pending".equals(task.getStatus())) {
throw new BusinessException("只有待接单状态的任务才能指派");
}
// 检查是否已有人接单
if (task.getReceiverId() != null) {
throw new BusinessException("该任务已被接单");
}
// 检查指派人是否是部门负责人
User assigner = userService.getUserById(assignerId);
if (assigner == null || assigner.getDeptId() == null) {
throw new BusinessException("用户不存在或未分配部门");
}
Department assignerDept = departmentService.getDepartmentById(assigner.getDeptId());
if (assignerDept == null || !assignerId.equals(assignerDept.getLeaderId())) {
throw new BusinessException("只有部门负责人才能指派任务");
}
// 检查被指派人是否在该部门
User assignee = userService.getUserById(request.getAssigneeId());
if (assignee == null || !assigner.getDeptId().equals(assignee.getDeptId())) {
throw new BusinessException("只能指派给本部门成员");
}
// 更新任务
task.setReceiverId(request.getAssigneeId());
task.setReceiverDeptId(assignee.getDeptId());
task.setStatus("in_progress");
task.setStartTime(LocalDateTime.now());
taskMapper.updateById(task);
// 记录指派日志
TaskAssignLog log = new TaskAssignLog();
log.setTaskId(task.getId());
log.setAssignerId(assignerId);
log.setAssigneeId(request.getAssigneeId());
log.setAssignType(request.getAssignType());
log.setComment(request.getComment());
taskAssignLogMapper.insert(log);
return true;
}
/**
* 获取部门负责人的任务列表(需求部门负责人视图)
*/
public IPage<Task> getLeaderTaskList(Long userId, Integer page, Integer pageSize, String auditStatus) {
log.info("查询负责人任务列表: userId={}, auditStatus={}", userId, auditStatus);
User user = userService.getUserById(userId);
if (user == null || user.getDeptId() == null) {
throw new BusinessException("用户不存在或未分配部门");
}
Department dept = departmentService.getDepartmentById(user.getDeptId());
if (dept == null) {
throw new BusinessException("部门不存在");
}
// 检查是否是部门负责人
if (!userId.equals(dept.getLeaderId())) {
throw new BusinessException("只有部门负责人才能查看该视图");
}
// 查询部门内所有任务
Page<Task> pageParam = new Page<>(page, pageSize);
return taskMapper.selectLeaderTaskPage(pageParam, user.getDeptId(), auditStatus);
}
/**
* 获取接单部门负责人的接单明细(接单部门负责人视图)
*/
public IPage<Task> getLeaderReceivingTaskList(Long userId, Integer page, Integer pageSize, String status) {
log.info("查询接单部门负责人视图: userId={}, status={}", userId, status);
User user = userService.getUserById(userId);
if (user == null || user.getDeptId() == null) {
throw new BusinessException("用户不存在或未分配部门");
}
Department dept = departmentService.getDepartmentById(user.getDeptId());
if (dept == null) {
throw new BusinessException("部门不存在");
}
// 检查是否是部门负责人
if (!userId.equals(dept.getLeaderId())) {
throw new BusinessException("只有部门负责人才能查看该视图");
}
// 查询部门成员接单的所有任务
Page<Task> pageParam = new Page<>(page, pageSize);
return taskMapper.selectDeptReceivingTaskPage(pageParam, user.getDeptId(), status);
}
/**
* 获取工作台统计数据
*/
public DashboardStatsVO getDashboardStats(Long userId, String month, Long deptId) {
try {
// 获取用户角色,判断是否为管理员
List<Role> roles = roleService.getRolesByUserId(userId);
boolean isAdmin = roles.stream()
.anyMatch(role -> "ADMIN".equals(role.getRoleCode()) || "SUPER_ADMIN".equals(role.getRoleCode()));
// 检查是否是部门负责人切换视角(传入了deptId)
boolean isDeptView = deptId != null;
List<Long> deptAndChildIds = null;
String deptType = null;
if (isDeptView) {
// 获取指定部门及其所有子部门
deptAndChildIds = getDeptAndChildIds(deptId);
// 获取部门类型
Department dept = departmentMapper.selectById(deptId);
if (dept != null) {
deptType = dept.getDeptType();
}
log.info("部门负责人视角: deptId={}, deptType={}, childDeptCount={}", deptId, deptType, deptAndChildIds.size());
}
// 获取用户自己的部门类型(用于普通用户统计)
String userDeptType = null;
User currentUser = userService.getUserById(userId);
if (currentUser != null && currentUser.getDeptId() != null) {
Department userDept = departmentMapper.selectById(currentUser.getDeptId());
if (userDept != null) {
userDeptType = userDept.getDeptType();
}
}
log.info("获取工作台统计数据: userId={}, isAdmin={}, month={}, deptId={}, isDeptView={}, deptType={}, userDeptType={}, deptAndChildIds={}",
userId, isAdmin, month, deptId, isDeptView, deptType, userDeptType, deptAndChildIds);
// 解析月份,如果不传则使用当前月份
LocalDate targetMonth;
if (month != null && !month.isEmpty()) {
try {
targetMonth = LocalDate.parse(month + "-01", java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd"));
} catch (Exception e) {
log.warn("月份格式错误: {}, 使用当前月份", month);
targetMonth = LocalDate.now().withDayOfMonth(1);
}
} else {
targetMonth = LocalDate.now().withDayOfMonth(1);
}
// 计算月份的开始和结束时间
LocalDateTime monthStart = LocalDateTime.of(targetMonth, LocalTime.MIN);
LocalDateTime monthEnd = monthStart.plusMonths(1).minusSeconds(1);
// 当前日期范围
LocalDateTime todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
LocalDateTime todayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
// 本周范围
LocalDate now = LocalDate.now();
LocalDate weekStart = now.minusDays(now.getDayOfWeek().getValue() - 1);
LocalDate weekEnd = weekStart.plusDays(6);
LocalDateTime weekStartTime = LocalDateTime.of(weekStart, LocalTime.MIN);
LocalDateTime weekEndTime = LocalDateTime.of(weekEnd, LocalTime.MAX);
// 上周范围
LocalDate lastWeekStart = weekStart.minusDays(7);
LocalDate lastWeekEnd = lastWeekStart.plusDays(6);
LocalDateTime lastWeekStartTime = LocalDateTime.of(lastWeekStart, LocalTime.MIN);
LocalDateTime lastWeekEndTime = LocalDateTime.of(lastWeekEnd, LocalTime.MAX);
// 进行中的任务数
Long inProgressCount = 0L;
if (isDeptView && "receiving".equals(deptType)) {
// 接单部门视角:统计该部门(含子部门)设计师接单的任务
inProgressCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "in_progress")
.in(Task::getReceiverDeptId, deptAndChildIds));
} else if (isDeptView && "demand".equals(deptType)) {
// 需求部门视角:统计该部门发起的任务
inProgressCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "in_progress")
.in(Task::getPublisherDeptId, deptAndChildIds));
} else if (!isAdmin) {
// 普通用户:根据部门类型决定统计方式
if ("demand".equals(userDeptType)) {
// 需求部门用户:统计自己发起的任务
inProgressCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "in_progress")
.eq(Task::getPublisherId, userId));
} else {
// 接单部门用户:统计自己接收的任务
inProgressCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "in_progress")
.eq(Task::getReceiverId, userId));
}
} else {
inProgressCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "in_progress"));
}
// 已完成的任务数
Long completedCount = 0L;
if (isDeptView && "receiving".equals(deptType)) {
completedCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "completed")
.in(Task::getReceiverDeptId, deptAndChildIds));
} else if (isDeptView && "demand".equals(deptType)) {
completedCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "completed")
.in(Task::getPublisherDeptId, deptAndChildIds));
} else if (!isAdmin) {
// 普通用户:根据部门类型决定统计方式
if ("demand".equals(userDeptType)) {
// 需求部门用户:统计自己发起的任务
completedCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "completed")
.eq(Task::getPublisherId, userId));
} else {
// 接单部门用户:统计自己接收的任务
completedCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "completed")
.eq(Task::getReceiverId, userId));
}
} else {
completedCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "completed"));
}
// 待审核的任务数
Long reviewCount = 0L;
if (isDeptView && "receiving".equals(deptType)) {
reviewCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "review")
.in(Task::getReceiverDeptId, deptAndChildIds));
} else if (isDeptView && "demand".equals(deptType)) {
reviewCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "review")
.in(Task::getPublisherDeptId, deptAndChildIds));
} else if (!isAdmin) {
// 普通用户:根据部门类型决定统计方式
if ("demand".equals(userDeptType)) {
// 需求部门用户:统计自己发起的任务
reviewCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "review")
.eq(Task::getPublisherId, userId));
} else {
// 接单部门用户:统计自己接收的任务
reviewCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "review")
.eq(Task::getReceiverId, userId));
}
} else {
reviewCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "review"));
}
// 今日开始的任务数
LambdaQueryWrapper<Task> todayStartedQuery = new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "in_progress")
.ge(Task::getStartTime, todayStart)
.le(Task::getStartTime, todayEnd);
if (!isAdmin) {
todayStartedQuery.eq(Task::getReceiverId, userId);
}
Long todayStartedCount = taskMapper.selectCount(todayStartedQuery);
// 今日完成的任务数
LambdaQueryWrapper<Task> todayCompletedQuery = new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "completed")
.ge(Task::getCompleteTime, todayStart)
.le(Task::getCompleteTime, todayEnd);
if (!isAdmin) {
todayCompletedQuery.eq(Task::getReceiverId, userId);
}
Long todayCompletedCount = taskMapper.selectCount(todayCompletedQuery);
// 本周完成的任务数
LambdaQueryWrapper<Task> weekCompletedQuery = new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "completed")
.ge(Task::getCompleteTime, weekStartTime)
.le(Task::getCompleteTime, weekEndTime);
if (!isAdmin) {
weekCompletedQuery.eq(Task::getReceiverId, userId);
}
Long weekCompletedCount = taskMapper.selectCount(weekCompletedQuery);
// 上周完成的任务数
LambdaQueryWrapper<Task> lastWeekCompletedQuery = new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "completed")
.ge(Task::getCompleteTime, lastWeekStartTime)
.le(Task::getCompleteTime, lastWeekEndTime);
if (!isAdmin) {
lastWeekCompletedQuery.eq(Task::getReceiverId, userId);
}
Long lastWeekCompletedCount = taskMapper.selectCount(lastWeekCompletedQuery);
// 可接任务数(状态为pending的任务)
LambdaQueryWrapper<Task> availableQuery = new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "pending");
// 可接任务对所有人都是全局的,不需要过滤用户
Long availableCount = taskMapper.selectCount(availableQuery);
// 当日新增任务数
LambdaQueryWrapper<Task> todayNewQuery = new LambdaQueryWrapper<Task>()
.ge(Task::getCreateTime, todayStart)
.le(Task::getCreateTime, todayEnd);
if (!isAdmin) {
// 普通用户看自己相关的任务(作为发起人或接单人)
todayNewQuery.and(wrapper -> wrapper
.eq(Task::getPublisherId, userId)
.or()
.eq(Task::getReceiverId, userId));
}
Long todayNewCount = taskMapper.selectCount(todayNewQuery);
// 获取总积分
int totalScore = 0;
if (isDeptView && "demand".equals(deptType)) {
// 需求部门视角:统计该部门发布任务消耗的总积分
LambdaQueryWrapper<Task> scoreQuery = new LambdaQueryWrapper<Task>()
.in(Task::getPublisherDeptId, deptAndChildIds)
.select(Task::getScore);
List<Task> publishedTasks = taskMapper.selectList(scoreQuery);
totalScore = publishedTasks.stream()
.filter(t -> t.getScore() != null)
.map(Task::getScore)
.reduce(BigDecimal.ZERO, BigDecimal::add)
.intValue();
log.debug("需求部门积分统计: deptIds={}, totalScore={}", deptAndChildIds, totalScore);
} else if (isDeptView && "receiving".equals(deptType)) {
// 接单部门视角:统计该部门设计师获得的总积分
LambdaQueryWrapper<Task> scoreQuery = new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "completed")
.in(Task::getReceiverDeptId, deptAndChildIds)
.select(Task::getScoreReward);
List<Task> completedTasks = taskMapper.selectList(scoreQuery);
totalScore = completedTasks.stream()
.filter(t -> t.getScoreReward() != null)
.mapToInt(t -> t.getScoreReward().intValue())
.sum();
log.debug("接单部门积分统计: deptIds={}, totalScore={}", deptAndChildIds, totalScore);
} else {
// 个人视角:统计当前用户获得的积分
LambdaQueryWrapper<Task> scoreQuery = new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "completed")
.select(Task::getScoreReward);
if (!isAdmin) {
scoreQuery.eq(Task::getReceiverId, userId);
}
List<Task> completedTasks = taskMapper.selectList(scoreQuery);
totalScore = completedTasks.stream()
.filter(t -> t.getScoreReward() != null)
.mapToInt(t -> t.getScoreReward().intValue())
.sum();
}
// 获取当月发布的任务数
Long publishedCount = 0L;
if (isDeptView && "demand".equals(deptType)) {
// 需求部门视角:统计该部门发起的任务数
publishedCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.in(Task::getPublisherDeptId, deptAndChildIds)
.ge(Task::getCreateTime, monthStart)
.le(Task::getCreateTime, monthEnd)
.eq(Task::getDeleted, 0));
} else if (isDeptView && "receiving".equals(deptType)) {
// 接单部门视角:统计该部门接单的任务数
publishedCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.in(Task::getReceiverDeptId, deptAndChildIds)
.ge(Task::getCreateTime, monthStart)
.le(Task::getCreateTime, monthEnd)
.eq(Task::getDeleted, 0));
} else {
// 当前用户发布的任务数
publishedCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getPublisherId, userId)
.ge(Task::getCreateTime, monthStart)
.le(Task::getCreateTime, monthEnd)
.eq(Task::getDeleted, 0));
}
DashboardStatsVO result = DashboardStatsVO.builder()
.availableCount(availableCount.intValue())
.publishedCount(publishedCount.intValue())
.inProgressCount(inProgressCount.intValue())
.completedCount(completedCount.intValue())
.reviewCount(reviewCount.intValue())
.todayNewCount(todayNewCount.intValue())
.totalScore(totalScore)
.todayStartedCount(todayStartedCount.intValue())
.todayCompletedCount(todayCompletedCount.intValue())
.weekCompletedCount(weekCompletedCount.intValue())
.lastWeekCompletedCount(lastWeekCompletedCount.intValue())
.build();
log.info("工作台统计数据查询完成: userId={}, isAdmin={}, result={}", userId, isAdmin, result);
return result;
} catch (Exception e) {
log.error("获取工作台统计数据失败: userId={}", userId, e);
// 返回空数据而不是抛异常
return DashboardStatsVO.builder()
.availableCount(0)
.publishedCount(0)
.inProgressCount(0)
.completedCount(0)
.reviewCount(0)
.todayNewCount(0)
.totalScore(0)
.todayStartedCount(0)
.todayCompletedCount(0)
.weekCompletedCount(0)
.lastWeekCompletedCount(0)
.build();
}
}
/**
* 获取我的进行中任务列表
*/
public List<Task> getMyInProgressTasks(Long userId) {
return taskMapper.selectList(
new LambdaQueryWrapper<Task>()
.eq(Task::getReceiverId, userId)
.eq(Task::getStatus, "in_progress")
.orderByDesc(Task::getStartTime)
);
}
/**
* 批量更新任务进度
*/
@Transactional(rollbackFor = Exception.class)
public void batchUpdateProgress(Long userId, List<java.util.Map<String, Object>> updates) {
for (java.util.Map<String, Object> update : updates) {
Long taskId = ((Number) update.get("taskId")).longValue();
Integer progress = ((Number) update.get("progress")).intValue();
String remark = (String) update.get("remark");
// 验证任务归属
Task task = taskMapper.selectById(taskId);
if (task == null) {
throw new BusinessException("任务不存在: taskId=" + taskId);
}
if (!task.getReceiverId().equals(userId)) {
throw new BusinessException("无权更新此任务进度: taskId=" + taskId);
}
if (!"in_progress".equals(task.getStatus())) {
throw new BusinessException("只能更新进行中的任务进度: taskId=" + taskId);
}
// 验证进度值
if (progress < 0 || progress > 100) {
throw new BusinessException("进度值必须在0-100之间: taskId=" + taskId);
}
Integer oldProgress = task.getProgress() != null ? task.getProgress() : 0;
if (progress < oldProgress) {
throw new BusinessException("新进度不能小于当前进度: taskId=" + taskId);
}
// 更新进度
Task updateTask = new Task();
updateTask.setId(taskId);
updateTask.setProgress(progress);
updateTask.setUpdateTime(LocalDateTime.now());
// 如果进度达到100%,自动标记为待审核
if (progress == 100) {
updateTask.setStatus("pending_review");
}
taskMapper.updateById(updateTask);
log.info("任务进度更新成功: taskId={}, userId={}, oldProgress={}, newProgress={}, remark={}",
taskId, userId, oldProgress, progress, remark);
}
}
/**
* 更新任务进度
*/
@Transactional
public void updateTaskProgress(Long taskId, Long userId, Integer progress) {
log.info("更新任务进度: taskId={}, userId={}, progress={}", taskId, userId, progress);
// 验证进度值
if (progress < 0 || progress > 100) {
throw new BusinessException("进度值必须在0-100之间");
}
Task task = taskMapper.selectById(taskId);
if (task == null) {
throw new BusinessException("任务不存在");
}
// 验证是接单人
if (!userId.equals(task.getReceiverId())) {
throw new BusinessException("只有接单人才能更新任务进度");
}
// 验证任务状态(只有进行中的任务才能更新进度)
if (!"in_progress".equals(task.getStatus())) {
throw new BusinessException("只有进行中的任务才能更新进度");
}
// 更新进度
task.setProgress(progress);
task.setUpdateTime(LocalDateTime.now());
taskMapper.updateById(task);
log.info("任务进度更新成功: taskId={}, progress={}", taskId, progress);
}
/**
* 完结任务(提交交付路径)
*/
@Transactional
public void completeTask(Long taskId, Long userId, String deliveryPath) {
log.info("完结任务: taskId={}, userId={}, deliveryPath={}", taskId, userId, deliveryPath);
Task task = taskMapper.selectById(taskId);
if (task == null) {
throw new BusinessException("任务不存在");
}
// 验证是接单人
if (!userId.equals(task.getReceiverId())) {
throw new BusinessException("只有接单人才能完结任务");
}
// 验证任务状态(只有进行中的任务才能完结)
if (!"in_progress".equals(task.getStatus())) {
throw new BusinessException("只有进行中的任务才能完结");
}
// 更新任务状态为待审核
task.setStatus("review");
task.setDeliveryPath(deliveryPath);
task.setProgress(100); // 完结时进度设为100%
task.setSubmitTime(LocalDateTime.now());
task.setUpdateTime(LocalDateTime.now());
taskMapper.updateById(task);
// 发送通知给发起人
try {
Notification notification = new Notification();
notification.setReceiverId(task.getPublisherId());
notification.setTitle("任务提交审核");
notification.setContent("任务【" + task.getTitle() + "】已提交审核,请查看");
notification.setNotificationType("TASK_REVIEW");
notification.setRelatedType("task");
notification.setRelatedId(taskId);
notification.setPushDingtalk(1); // 推送到钉钉
notificationService.sendNotification(notification);
} catch (Exception e) {
log.error("发送审核通知失败", e);
}
log.info("任务已提交审核: taskId={}", taskId);
}
/**
* 审核通过任务
*/
@Transactional
public void approveTask(Long taskId, Long userId) {
log.info("审核通过任务: taskId={}, userId={}", taskId, userId);
Task task = taskMapper.selectById(taskId);
if (task == null) {
throw new BusinessException("任务不存在");
}
// 验证权限(发起人或部门负责人)
User user = userService.getUserById(userId);
if (user == null) {
throw new BusinessException("用户不存在");
}
boolean isPublisher = userId.equals(task.getPublisherId());
boolean isDeptLeader = false;
if (user.getDeptId() != null) {
Department dept = departmentService.getDepartmentById(user.getDeptId());
if (dept != null && userId.equals(dept.getLeaderId())) {
isDeptLeader = true;
}
}
if (!isPublisher && !isDeptLeader) {
throw new BusinessException("只有任务发起人或部门负责人才能审核任务");
}
// 验证任务状态
if (!"review".equals(task.getStatus())) {
throw new BusinessException("只有待审核的任务才能审核");
}
LocalDateTime completeTime = LocalDateTime.now();
// 更新任务状态为已完成
task.setStatus("completed");
task.setCompleteTime(completeTime);
task.setUpdateTime(completeTime);
task.setRating(5); // 默认5分
// 计算是否延误以及延误扣分
boolean isDelayed = false;
BigDecimal delayHours = BigDecimal.ZERO;
BigDecimal deductedScore = BigDecimal.ZERO;
if (task.getStartTime() != null && task.getEstimatedHours() != null && task.getEstimatedHours().compareTo(BigDecimal.ZERO) > 0) {
// 计算预计完成时间(接单时间 + 预估工时,使用工作时间计算)
LocalDateTime expectedCompleteTime = workHoursCalculator.calculateDeadline(task.getStartTime(), task.getEstimatedHours());
// 判断是否延误
if (expectedCompleteTime != null && completeTime.isAfter(expectedCompleteTime)) {
isDelayed = true;
task.setIsDelayed(1);
// 计算延误小时数(只计算工作时间段内的延误)
delayHours = workHoursCalculator.calculateDelayHours(expectedCompleteTime, completeTime);
// 计算延误天数(基于每天工作小时数)
com.designhub.util.WorkHoursCalculator.WorkHoursConfig config = workHoursCalculator.getWorkHoursConfig();
int workHoursPerDay = config.getWorkHoursPerDay();
task.setDelayDays(delayHours.divide(BigDecimal.valueOf(workHoursPerDay), 0, java.math.RoundingMode.CEILING).intValue());
// 从系统配置获取延误每小时扣分数
String penaltyPerHourStr = systemConfigService.getConfigValue("score.delay_penalty_per_hour");
BigDecimal penaltyPerHour = penaltyPerHourStr != null ? new BigDecimal(penaltyPerHourStr) : BigDecimal.valueOf(0.5);
// 计算扣分
deductedScore = delayHours.multiply(penaltyPerHour).setScale(2, java.math.RoundingMode.HALF_UP);
log.info("任务延误: taskId={}, 延误小时数={}, 扣分={}", taskId, delayHours, deductedScore);
} else {
task.setIsDelayed(0);
task.setDelayDays(0);
}
}
// 计算实际积分奖励(原积分 - 延误扣分)
BigDecimal earnedScore = BigDecimal.ZERO;
if (task.getScore() != null) {
earnedScore = task.getScore();
BigDecimal actualReward = earnedScore.subtract(deductedScore);
if (actualReward.compareTo(BigDecimal.ZERO) < 0) {
actualReward = BigDecimal.ZERO; // 最少为0,不会变成负数
}
task.setScoreReward(actualReward);
}
taskMapper.updateById(task);
// 更新接单部门的月度积分统计
if (task.getReceiverDeptId() != null) {
try {
deptMonthlyScoreService.addEarnedScore(
task.getReceiverDeptId(),
task.getScoreReward() != null ? task.getScoreReward() : BigDecimal.ZERO,
isDelayed,
delayHours
);
// 如果有延误扣分,单独记录扣除
if (deductedScore.compareTo(BigDecimal.ZERO) > 0) {
deptMonthlyScoreService.addDeductedScore(task.getReceiverDeptId(), deductedScore);
}
log.info("更新部门月度积分: deptId={}, earned={}, deducted={}",
task.getReceiverDeptId(), task.getScoreReward(), deductedScore);
} catch (Exception e) {
log.error("更新部门月度积分失败", e);
}
}
// 发送通知给接单人
try {
Notification notification = new Notification();
notification.setReceiverId(task.getReceiverId());
notification.setTitle("任务审核通过");
String content = "任务【" + task.getTitle() + "】审核通过";
if (isDelayed && deductedScore.compareTo(BigDecimal.ZERO) > 0) {
content += ",但因延误" + delayHours + "小时,扣除" + deductedScore + "分,实得" + task.getScoreReward() + "分";
} else {
content += ",积分已到账";
}
notification.setContent(content);
notification.setNotificationType("TASK_COMPLETED");
notification.setRelatedType("task");
notification.setRelatedId(taskId);
notification.setPushDingtalk(1); // 推送到钉钉
notificationService.sendNotification(notification);
} catch (Exception e) {
log.error("发送完成通知失败", e);
}
log.info("任务审核通过: taskId={}, isDelayed={}, deductedScore={}", taskId, isDelayed, deductedScore);
}
/**
* 驳回任务
*/
@Transactional
public void rejectTask(Long taskId, Long userId, String reason) {
log.info("驳回任务: taskId={}, userId={}, reason={}", taskId, userId, reason);
Task task = taskMapper.selectById(taskId);
if (task == null) {
throw new BusinessException("任务不存在");
}
// 验证权限(发起人或部门负责人)
User user = userService.getUserById(userId);
if (user == null) {
throw new BusinessException("用户不存在");
}
boolean isPublisher = userId.equals(task.getPublisherId());
boolean isDeptLeader = false;
if (user.getDeptId() != null) {
Department dept = departmentService.getDepartmentById(user.getDeptId());
if (dept != null && userId.equals(dept.getLeaderId())) {
isDeptLeader = true;
}
}
if (!isPublisher && !isDeptLeader) {
throw new BusinessException("只有任务发起人或部门负责人才能驳回任务");
}
// 验证任务状态
if (!"review".equals(task.getStatus())) {
throw new BusinessException("只有待审核的任务才能驳回");
}
// 更新任务状态为进行中(驳回后重新修改)
task.setStatus("in_progress");
task.setRejectReason(reason);
task.setUpdateTime(LocalDateTime.now());
taskMapper.updateById(task);
// 发送通知给接单人
try {
Notification notification = new Notification();
notification.setReceiverId(task.getReceiverId());
notification.setTitle("任务被驳回");
notification.setContent("任务【" + task.getTitle() + "】被驳回,原因:" + reason);
notification.setNotificationType("TASK_REJECTED");
notification.setRelatedType("task");
notification.setRelatedId(taskId);
notification.setPriority(2); // 高优先级
notification.setPushDingtalk(1); // 推送到钉钉
notificationService.sendNotification(notification);
} catch (Exception e) {
log.error("发送驳回通知失败", e);
}
log.info("任务已驳回: taskId={}, reason={}", taskId, reason);
}
/**
* 初始化审核状态(两级审核流程)
*/
private void initApprovalStatus(Task task) {
try {
// 设置审核状态为待需求部门审核
task.setApprovalStatus("pending_demand_approval");
// 确定需求部门负责人
if (task.getPublisherDeptId() != null) {
Department demandDept = departmentService.getDepartmentById(task.getPublisherDeptId());
if (demandDept != null && demandDept.getLeaderId() != null) {
task.setCurrentApproverId(demandDept.getLeaderId());
task.setCurrentApproverRole("demand_leader");
log.info("任务已设置待审核状态,等待需求部门负责人审核:leaderId={}", demandDept.getLeaderId());
} else {
log.warn("需求部门未配置负责人,任务将直接发布:deptId={}", task.getPublisherDeptId());
// 如果没有配置负责人,跳过审核直接发布
task.setApprovalStatus("approved");
task.setStatus("pending");
}
}
} catch (Exception e) {
log.error("初始化审核状态失败", e);
// 失败时跳过审核直接发布
task.setApprovalStatus("approved");
task.setStatus("pending");
}
}
/**
* 发送审核通知(两级审核流程)- 包含丰富的任务详情
*/
private void sendApprovalNotification(Task task) {
try {
if (task.getCurrentApproverId() != null) {
User approver = userService.getUserById(task.getCurrentApproverId());
if (approver != null) {
// 构建丰富的通知内容
StringBuilder contentBuilder = new StringBuilder();
contentBuilder.append("**任务名称**:").append(task.getTitle()).append(" \n");
contentBuilder.append("**任务编号**:").append(task.getTaskNo()).append(" \n");
// 品牌、平台、SPU信息
if (task.getBrandId() != null) {
Brand brand = brandMapper.selectById(task.getBrandId());
if (brand != null) {
contentBuilder.append("**品牌**:").append(brand.getName()).append(" \n");
}
}
if (task.getPlatformId() != null) {
Platform platform = platformMapper.selectById(task.getPlatformId());
if (platform != null) {
contentBuilder.append("**平台**:").append(platform.getName()).append(" \n");
}
}
if (task.getSpuInfo() != null && !task.getSpuInfo().isEmpty()) {
contentBuilder.append("**SPU**:").append(task.getSpuInfo()).append(" \n");
}
if (task.getTaskSubType() != null && !task.getTaskSubType().isEmpty()) {
contentBuilder.append("**任务类型**:").append(task.getTaskSubType()).append(" \n");
}
contentBuilder.append(" \n");
// 查询子任务获取任务类型明细
List<TaskItem> taskItems = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, task.getId())
.orderByAsc(TaskItem::getGroupNo)
);
if (!taskItems.isEmpty()) {
contentBuilder.append("**任务明细**: \n");
for (TaskItem item : taskItems) {
// 获取接单部门名称
String deptName = "未指定";
if (item.getTaskTypeId() != null) {
DemandType demandType = configService.getDemandTypeById(item.getTaskTypeId());
if (demandType != null && demandType.getAllowedDeptIds() != null) {
String[] deptIds = demandType.getAllowedDeptIds().split(",");
Long firstDeptId = Long.parseLong(deptIds[0].trim());
Department dept = departmentService.getDepartmentById(firstDeptId);
if (dept != null) {
deptName = dept.getDeptName();
}
}
}
contentBuilder.append("▸ ").append(deptName)
.append(" | ").append(item.getTotalScore() != null ? item.getTotalScore() : 0).append("分")
.append(" | ").append(formatHours(item.getEstimatedHours())).append(" \n");
}
}
contentBuilder.append(" \n");
// 发布人信息
if (task.getPublisherId() != null) {
User publisher = userService.getUserById(task.getPublisherId());
if (publisher != null) {
contentBuilder.append("**发起人**:").append(publisher.getRealName() != null ? publisher.getRealName() : publisher.getUsername()).append(" \n");
}
}
if (task.getPublisherDeptId() != null) {
Department dept = departmentService.getDepartmentById(task.getPublisherDeptId());
if (dept != null) {
contentBuilder.append("**发起部门**:").append(dept.getDeptName()).append(" \n");
}
}
Notification notification = new Notification();
notification.setReceiverId(task.getCurrentApproverId());
notification.setTitle("新任务待审核");
notification.setContent(contentBuilder.toString());
notification.setNotificationType("TASK_APPROVAL");
notification.setRelatedType("task");
notification.setRelatedId(task.getId());
notification.setPushDingtalk(1); // 推送到钉钉
notificationService.sendNotification(notification);
log.info("已发送审核通知给用户:userId={}, taskId={}", task.getCurrentApproverId(), task.getId());
}
}
} catch (Exception e) {
log.error("发送审核通知失败", e);
}
}
/**
* 格式化工时(小时转天+小时,基于工作时间配置)
*/
private String formatHours(BigDecimal hours) {
return workHoursCalculator.formatWorkHours(hours);
}
/**
* 计算任务总积分(用于审核时修改)- 返回整数
*/
public int calculateTotalScore(List<TaskUpdateDTO.TaskTypeUpdateDTO> taskTypes) {
return calculateTotalScoreDecimal(taskTypes).intValue();
}
/**
* 计算任务总积分(用于审核时修改)- 返回BigDecimal保留2位小数
*/
public BigDecimal calculateTotalScoreDecimal(List<TaskUpdateDTO.TaskTypeUpdateDTO> taskTypes) {
BigDecimal totalScore = BigDecimal.ZERO;
for (TaskUpdateDTO.TaskTypeUpdateDTO taskType : taskTypes) {
DemandType demandType = configService.getDemandTypeById(taskType.getTaskTypeId());
if (demandType != null && demandType.getUnitScore() != null) {
totalScore = totalScore.add(demandType.getUnitScore().multiply(BigDecimal.valueOf(taskType.getQuantity())));
}
}
return totalScore.setScale(2, java.math.RoundingMode.HALF_UP);
}
/**
* 创建流水线任务的子任务项(用于审核通过后拆分)
*/
public void createPipelineTaskItems(Task mainTask, com.designhub.dto.PipelineTaskDTO dto) {
// 委托给已有的createPipelineTask逻辑中的子任务创建部分
// 这里简化处理,实际应该提取createPipelineTask中的子任务创建逻辑
log.info("开始拆分流水线子任务:taskId={}, mode={}", mainTask.getId(), dto.getPipelineMode());
// TODO: 实现流水线子任务拆分逻辑
// 可以参考createPipelineTask方法中的实现
}
/**
* 更新指派模式流水线任务的子任务状态为in_progress
* @param taskId 主任务ID
* @param pipelineMode 流水线模式:sequential(顺序)或 parallel(并行)
*/
public void updatePipelineSubTasksForAssigned(Long taskId, String pipelineMode) {
log.info("更新指派流水线任务的子任务状态:taskId={}, mode={}", taskId, pipelineMode);
// 获取主任务信息
Task mainTask = taskMapper.selectById(taskId);
if (mainTask == null) {
log.warn("主任务不存在:taskId={}", taskId);
return;
}
Long receiverId = mainTask.getReceiverId();
Long receiverDeptId = mainTask.getReceiverDeptId();
List<TaskItem> taskItems = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, taskId)
.orderByAsc(TaskItem::getGroupNo)
);
if (taskItems.isEmpty()) {
log.warn("未找到任务的子任务:taskId={}", taskId);
return;
}
LocalDateTime now = LocalDateTime.now();
if ("parallel".equals(pipelineMode)) {
// 并行模式:所有子任务都进入in_progress状态,并设置接收人
for (TaskItem item : taskItems) {
item.setStatus("in_progress");
item.setIsLocked(0);
// 指派模式下:强制更新所有子任务的接收人为主任务的接收人
if (receiverId != null) {
item.setReceiverId(receiverId);
if (receiverDeptId != null) {
item.setReceiverDeptId(receiverDeptId);
}
log.info("并行流水线子任务 {} 已设置/更新接收人: receiverId={}, deptId={}",
item.getId(), receiverId, receiverDeptId);
}
// 计算并设置deadline(使用工作时间计算)
if (item.getEstimatedHours() != null && item.getEstimatedHours().compareTo(BigDecimal.ZERO) > 0) {
LocalDateTime deadline = workHoursCalculator.calculateDeadline(now, item.getEstimatedHours());
item.setDeadline(deadline);
log.info("并行流水线子任务 {} 设置deadline: {}", item.getId(), deadline);
}
taskItemMapper.updateById(item);
}
log.info("并行流水线任务的所有子任务已更新为in_progress:count={}", taskItems.size());
} else {
// 顺序模式:第一个部门的所有子任务进入in_progress状态
// 从子任务编号解析部门序号,找出第一个部门的所有子任务
int firstDeptIndex = -1;
for (TaskItem item : taskItems) {
int deptIndex = parseDeptIndexFromItemNo(item.getItemNo());
if (firstDeptIndex == -1 || deptIndex < firstDeptIndex) {
firstDeptIndex = deptIndex;
}
}
log.info("顺序流水线任务,第一个部门序号: {}", firstDeptIndex);
for (TaskItem item : taskItems) {
int itemDeptIndex = parseDeptIndexFromItemNo(item.getItemNo());
if (itemDeptIndex == firstDeptIndex) {
item.setStatus("in_progress");
item.setIsLocked(0);
// 指派模式下:如果子任务已有预先指定的接收人,保留;否则使用主任务的接收人
if (item.getReceiverId() == null && receiverId != null) {
item.setReceiverId(receiverId);
if (receiverDeptId != null) {
item.setReceiverDeptId(receiverDeptId);
}
log.info("顺序流水线第一部门子任务 {} 设置接收人: receiverId={}, deptId={}",
item.getId(), receiverId, receiverDeptId);
} else {
log.info("顺序流水线第一部门子任务 {} 保留预先指定的接收人: receiverId={}",
item.getId(), item.getReceiverId());
}
// 计算并设置deadline(使用工作时间计算)
if (item.getEstimatedHours() != null && item.getEstimatedHours().compareTo(BigDecimal.ZERO) > 0) {
LocalDateTime deadline = workHoursCalculator.calculateDeadline(now, item.getEstimatedHours());
item.setDeadline(deadline);
log.info("顺序流水线第一部门子任务 {} 设置deadline: {}", item.getId(), deadline);
}
taskItemMapper.updateById(item);
log.info("顺序流水线任务第一部门子任务已更新为in_progress:itemId={}, itemNo={}",
item.getId(), item.getItemNo());
}
}
}
}
/**
* 从子任务编号解析部门序号
* 编号格式:任务编号-部门序号-部门内序号,例如 TASK20260114-01-02
*/
private int parseDeptIndexFromItemNo(String itemNo) {
if (itemNo == null || itemNo.isEmpty()) {
return 1;
}
String[] parts = itemNo.split("-");
if (parts.length >= 3) {
try {
// 新格式:TASK...-部门序号-部门内序号
// 倒数第二段是部门序号
return Integer.parseInt(parts[parts.length - 2]);
} catch (NumberFormatException e) {
log.warn("解析子任务编号部门序号失败: {}", itemNo);
}
} else if (parts.length == 2) {
// 旧格式:TASK...-序号,认为是第一个部门
return 1;
}
return 1;
}
/**
* 获取指定部门及其所有子部门的ID列表
*/
private List<Long> getDeptAndChildIds(Long deptId) {
List<Long> result = new java.util.ArrayList<>();
result.add(deptId);
collectChildDeptIds(deptId, result);
return result;
}
/**
* 递归收集所有子部门ID
*/
private void collectChildDeptIds(Long parentId, List<Long> result) {
List<Department> children = departmentMapper.selectList(
new LambdaQueryWrapper<Department>()
.eq(Department::getParentId, parentId)
.eq(Department::getStatus, 1)
);
for (Department child : children) {
result.add(child.getId());
collectChildDeptIds(child.getId(), result);
}
}
/**
* 根据任务编号删除任务及其所有子任务
* @param taskNo 任务编号
*/
@Transactional
public void deleteTaskByTaskNo(String taskNo) {
log.info("根据任务编号删除任务: taskNo={}", taskNo);
// 查找任务
Task task = taskMapper.selectOne(
new LambdaQueryWrapper<Task>()
.eq(Task::getTaskNo, taskNo)
);
if (task == null) {
throw new RuntimeException("任务不存在,请检查任务编号是否正确");
}
Long taskId = task.getId();
// 1. 如果任务已完成,需要回退积分
if ("completed".equals(task.getStatus())) {
// 回退主任务积分
if (task.getReceiverDeptId() != null && task.getScoreReward() != null
&& task.getScoreReward().compareTo(BigDecimal.ZERO) > 0) {
try {
// 从团队积分池扣除已奖励的积分
teamScorePoolService.deductScore(task.getReceiverDeptId(), task.getScoreReward().intValue());
log.info("已回退主任务积分: deptId={}, score={}", task.getReceiverDeptId(), task.getScoreReward());
// 回退部门月度积分统计
deptMonthlyScoreService.deductEarnedScore(task.getReceiverDeptId(), task.getScoreReward());
log.info("已回退部门月度积分: deptId={}, score={}", task.getReceiverDeptId(), task.getScoreReward());
} catch (Exception e) {
log.warn("回退主任务积分失败: {}", e.getMessage());
}
}
}
// 2. 查询所有子任务,回退已完成子任务的积分
List<TaskItem> subTasks = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, taskId)
);
for (TaskItem item : subTasks) {
if ("completed".equals(item.getStatus()) && item.getReceiverDeptId() != null
&& item.getTotalScore() != null && item.getTotalScore().compareTo(BigDecimal.ZERO) > 0) {
try {
// 从团队积分池扣除已奖励的积分
teamScorePoolService.deductScore(item.getReceiverDeptId(), item.getTotalScore().intValue());
log.info("已回退子任务积分: itemNo={}, deptId={}, score={}",
item.getItemNo(), item.getReceiverDeptId(), item.getTotalScore());
// 回退部门月度积分统计
deptMonthlyScoreService.deductEarnedScore(item.getReceiverDeptId(), item.getTotalScore());
} catch (Exception e) {
log.warn("回退子任务积分失败: itemNo={}, error={}", item.getItemNo(), e.getMessage());
}
}
}
// 3. 删除所有子任务
int deletedItems = taskItemMapper.delete(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, taskId)
);
log.info("已删除 {} 个子任务", deletedItems);
// 4. 删除主任务
taskMapper.deleteById(taskId);
log.info("已删除主任务: taskNo={}, taskId={}", taskNo, taskId);
}
/**
* 获取用户信息(供Controller调用)
*/
public User getUserById(Long userId) {
return userService.getUserById(userId);
}
}