5515 lines
263 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.conditions.update.LambdaUpdateWrapper;
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.mapper.UserMapper;
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.HashSet;
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;
private final com.designhub.mapper.DelayPenaltyRecordMapper delayPenaltyRecordMapper;
private final UserMapper userMapper;
@org.springframework.beans.factory.annotation.Autowired
@org.springframework.context.annotation.Lazy
private TaskApprovalService taskApprovalService;
/**
* 创建任务
*/
@Transactional(rollbackFor = Exception.class)
public Long createTask(Task task) {
log.info("创建任务: {}", task.getTitle());
// 验证用户权限:只有需求部门或一级需求部门可以发起任务(管理员除外)
boolean isPrimaryTask = false;
if (task.getPublisherId() != null) {
var user = userService.getUserById(task.getPublisherId());
if (user == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "用户不存在");
}
// 检查是否是管理员(管理员可以同时拥有需求部门和接单部门权限)
boolean isAdmin = isAdminUser(user.getId());
Department publisherDept = resolvePublishingDepartment(user, task.getPublisherDeptId(), isAdmin);
isPrimaryTask = "primary_demand".equals(publisherDept.getDeptType());
task.setPublisherDeptId(publisherDept.getId());
}
// 如果是一级需求部门发起的任务,设置相关字段
if (isPrimaryTask) {
// 一级需求部门发起任务必须上传预览图
if (task.getPreviewImage() == null || task.getPreviewImage().trim().isEmpty()) {
throw new BusinessException(ResultCode.PARAM_ERROR, "一级需求部门发起任务必须上传预览图");
}
task.setIsPrimaryTask(1);
task.setPrimaryPublisherId(task.getPublisherId());
task.setPrimaryPublisherDeptId(task.getPublisherDeptId());
task.setDetailCompleted(0); // 明细未完善
log.info("一级需求部门发起任务: publisherId={}, deptId={}", task.getPublisherId(), task.getPublisherDeptId());
} else {
task.setIsPrimaryTask(0);
task.setDetailCompleted(1); // 普通任务明细已完善
}
// 验证任务类型并计算积分(支持多类型)
// 对于一级需求部门且关闭了任务明细填写的情况,允许不填写任务类型
boolean canSkipTaskType = false;
if (isPrimaryTask) {
// 检查系统配置:一级需求部门是否可以填写任务明细
Boolean canFillDetail = systemConfigService.getBooleanValue("task.primary_dept_can_fill_detail", true);
canSkipTaskType = !canFillDetail;
log.info("一级需求部门任务,canFillDetail={}, canSkipTaskType={}", canFillDetail, canSkipTaskType);
}
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 {
// 如果一级需求部门且关闭了任务明细,允许不填写任务类型
if (!canSkipTaskType) {
throw new BusinessException(ResultCode.PARAM_ERROR, "请选择任务类型");
} else {
// 一级需求部门任务,暂时不填写任务类型,积分设为0(后续由需求部门成员填写明细时再计算)
task.setScore(BigDecimal.ZERO);
task.setEstimatedHours(BigDecimal.ZERO);
log.info("一级需求部门任务,允许不填写任务类型,积分暂时设为0");
}
}
} else {
// 如果quantity为null,设置默认值
task.setScore(BigDecimal.ZERO);
task.setEstimatedHours(BigDecimal.ZERO);
log.info("任务数量为null,积分和工时设为0");
}
// 确保score字段不为null(防止数据库插入错误)
if (task.getScore() == null) {
task.setScore(BigDecimal.ZERO);
log.warn("任务积分未设置,使用默认值0");
}
// 检查部门积分是否充足(管理员跳过积分检查)
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);
}
// 设置创建时间(如果未设置)
if (task.getCreateTime() == null) {
task.setCreateTime(LocalDateTime.now());
}
// 【两级审核流程】初始化审核状态
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());
Department publisherDept = resolvePublishingDepartment(user, dto.getPublisherDeptId(), isAdmin);
boolean isPrimaryTask = "primary_demand".equals(publisherDept.getDeptType());
// 验证流水线模式
if (!"sequential".equals(dto.getPipelineMode()) && !"parallel".equals(dto.getPipelineMode())) {
throw new BusinessException(ResultCode.PARAM_ERROR, "流水线模式必须是 sequential 或 parallel");
}
// 检查一级需求部门是否可以跳过任务明细
boolean canSkipTaskDetail = false;
if (isPrimaryTask) {
// 一级需求部门发起任务必须上传预览图
if (dto.getPreviewImage() == null || dto.getPreviewImage().trim().isEmpty()) {
throw new BusinessException(ResultCode.PARAM_ERROR, "一级需求部门发起任务必须上传预览图");
}
Boolean canFillDetail = systemConfigService.getBooleanValue("task.primary_dept_can_fill_detail", true);
canSkipTaskDetail = !canFillDetail;
log.info("一级需求部门流水线任务,canFillDetail={}, canSkipTaskDetail={}", canFillDetail, canSkipTaskDetail);
}
// 验证子任务列表(一级需求部门且关闭任务明细时可以为空)
if ((dto.getItems() == null || dto.getItems().isEmpty()) && !canSkipTaskDetail) {
throw new BusinessException(ResultCode.PARAM_ERROR, "流水线任务至少需要一个子任务");
}
// 计算总积分、工时和数量(使用BigDecimal保留小数精度)
BigDecimal totalScoreDecimal = BigDecimal.ZERO;
double totalHours = 0;
int totalQuantity = 0;
if (dto.getItems() != null && !dto.getItems().isEmpty()) {
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 && publisherDept.getId() != null) {
if (!teamScorePoolService.checkBalance(publisherDept.getId(), totalScoreInt)) {
throw new BusinessException(ResultCode.INSUFFICIENT_SCORE, "部门积分余额不足");
}
if (!teamScorePoolService.checkPeriodLimit(publisherDept.getId(), 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(publisherDept.getId());
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);
// 设置一级需求部门相关字段
if (isPrimaryTask) {
task.setIsPrimaryTask(1);
task.setPrimaryPublisherId(publisherId);
task.setPrimaryPublisherDeptId(publisherDept.getId());
task.setDetailCompleted(0); // 明细未完善
log.info("一级需求部门发起流水线任务: publisherId={}, deptId={}", publisherId, publisherDept.getId());
} else {
task.setIsPrimaryTask(0);
task.setDetailCompleted(1); // 普通任务明细已完善
}
// 设置发布类型(从前端传递,默认为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());
}
}
// 🔥 是否保存为草稿(不触发审核流程)
boolean asDraft = dto.getAsDraft() != null && dto.getAsDraft();
if (asDraft) {
// 草稿模式:保持草稿状态,不初始化审核流程
task.setStatus("draft");
task.setApprovalStatus("draft");
log.info("保存为草稿模式,不触发审核流程");
} else {
// 【两级审核流程】初始化审核状态
initApprovalStatus(task);
}
taskMapper.insert(task);
log.info("主任务创建成功,ID: {}, 任务编号: {}, 草稿模式: {}", task.getId(), task.getTaskNo(), asDraft);
// **步骤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 = resolveEffectiveDeptKey(demandType, itemDto.getSelectedDeptId(), dto.getBrandId());
// 🔥 构建分组键:部门 + 设计师(如果指定了设计师)
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;
// 获取部门键
var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId());
String deptKey = resolveEffectiveDeptKey(demandType, itemDto.getSelectedDeptId(), dto.getBrandId());
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);
// 🔥 设置子任务的接单部门(无论什么模式都要设置)
// 优先使用用户选择的部门,否则尝试从任务类型配置获取
if (!groupItems.isEmpty()) {
Long selectedDeptId = groupItems.get(0).getSelectedDeptId();
if (selectedDeptId != null) {
// 用户明确选择了接单部门
item.setReceiverDeptId(selectedDeptId);
log.info("子任务 {} 设置用户选择的接单部门: {}", item.getItemNo(), selectedDeptId);
} else if (deptKey != null && !deptKey.equals("null") && !deptKey.contains(",")) {
// 任务类型只配置了一个部门,自动设置
try {
Long singleDeptId = Long.parseLong(deptKey.trim());
item.setReceiverDeptId(singleDeptId);
log.info("子任务 {} 自动设置唯一的接单部门: {}", item.getItemNo(), singleDeptId);
} catch (NumberFormatException e) {
log.warn("无法解析部门ID: {}", deptKey);
}
}
}
// 如果是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 (item.getReceiverDeptId() == null && 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());
// 【两级审核流程】发送审核通知给部门负责人(草稿模式不发送)
if (!asDraft) {
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;
}
private String resolveAllowedDeptIds(DemandType demandType) {
if (demandType == null) {
return null;
}
String allowedDeptIds = demandType.getAllowedDeptIds();
if (!StringUtils.hasText(allowedDeptIds) && demandType.getParentId() != null) {
DemandType parentType = configService.getDemandTypeById(demandType.getParentId());
if (parentType != null) {
allowedDeptIds = parentType.getAllowedDeptIds();
log.info("三级分类 {} 继承父级 {} 的接单部门配置: {}",
demandType.getName(), parentType.getName(), allowedDeptIds);
}
}
return allowedDeptIds;
}
private String resolveEffectiveDeptKey(DemandType demandType, Long selectedDeptId, Long brandId) {
String allowedDeptIds = resolveAllowedDeptIds(demandType);
boolean hasBrandConfig = departmentService.hasReceivingBrandConfig(brandId);
if (selectedDeptId != null) {
Department selectedDept = departmentService.getDepartmentById(selectedDeptId);
if (selectedDept == null || !"receiving".equals(selectedDept.getDeptType())) {
throw new BusinessException(ResultCode.PARAM_ERROR, "所选接单部门不存在或不是接单部门");
}
if (StringUtils.hasText(allowedDeptIds) || hasBrandConfig) {
List<Long> effectiveDeptIds = departmentService.filterReceivingDeptIdsByBrand(brandId, allowedDeptIds);
if (effectiveDeptIds.isEmpty() || !effectiveDeptIds.contains(selectedDeptId)) {
throw new BusinessException(ResultCode.PARAM_ERROR,
"任务类型 [" + demandType.getName() + "] 在当前品牌下不可选择接单部门 [" + selectedDept.getDeptName() + "]");
}
}
return String.valueOf(selectedDeptId);
}
if (!hasBrandConfig) {
return StringUtils.hasText(allowedDeptIds) ? allowedDeptIds : null;
}
List<Long> effectiveDeptIds = departmentService.filterReceivingDeptIdsByBrand(brandId, allowedDeptIds);
if (effectiveDeptIds.isEmpty()) {
throw new BusinessException(ResultCode.PARAM_ERROR,
"任务类型 [" + demandType.getName() + "] 在品牌 [" + getBrandName(brandId) + "] 下没有可用的接单部门");
}
if (effectiveDeptIds.size() > 1) {
throw new BusinessException(ResultCode.PARAM_ERROR,
"任务类型 [" + demandType.getName() + "] 在当前品牌下存在多个可用接单部门,请明确选择接单部门");
}
return effectiveDeptIds.stream().map(String::valueOf).collect(Collectors.joining(","));
}
private String getBrandName(Long brandId) {
if (brandId == null) {
return "未选择品牌";
}
Brand brand = brandMapper.selectById(brandId);
return brand != null ? brand.getName() : String.valueOf(brandId);
}
/**
* 获取任务列表
*/
public IPage<Task> getTaskList(Integer page, Integer pageSize, String status, Long taskTypeId, Long publisherDeptId, List<Long> publisherDeptIds, Long receiverDeptId) {
return getTaskList(page, pageSize, status, taskTypeId, publisherDeptId, publisherDeptIds, receiverDeptId, null, null);
}
/**
* 获取任务列表(支持排除指定状态)
* @param excludeStatuses 要排除的状态列表(如 draft,completed,cancelled)
*/
public IPage<Task> getTaskList(Integer page, Integer pageSize, String status, Long taskTypeId, Long publisherDeptId, List<Long> publisherDeptIds, Long receiverDeptId, List<String> excludeStatuses) {
return getTaskList(page, pageSize, status, taskTypeId, publisherDeptId, publisherDeptIds, receiverDeptId, excludeStatuses, null);
}
/**
* 获取任务列表(支持排除指定状态 + 包含一级需求任务的明细完善人所属部门)
* @param excludeStatuses 要排除的状态列表(如 draft,completed,cancelled)
* @param includeDetailCompleterDeptIds 包含明细完善人所属部门ID列表(用于需求部门负责人查看一级需求任务)
*/
public IPage<Task> getTaskList(Integer page, Integer pageSize, String status, Long taskTypeId, Long publisherDeptId, List<Long> publisherDeptIds, Long receiverDeptId, List<String> excludeStatuses, List<Long> includeDetailCompleterDeptIds) {
Page<Task> pageParam = new Page<>(page, pageSize);
LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
// 根据状态筛选
if (StringUtils.hasText(status)) {
wrapper.eq(Task::getStatus, status);
}
// 🔥 排除指定状态(需求部门负责人查看"处理中"任务时使用)
if (excludeStatuses != null && !excludeStatuses.isEmpty()) {
wrapper.notIn(Task::getStatus, excludeStatuses);
log.info("排除状态: excludeStatuses={}", excludeStatuses);
}
// 根据任务类型筛选
if (taskTypeId != null) {
wrapper.eq(Task::getTaskTypeId, taskTypeId);
}
// 根据发布者部门筛选(部门负责人视角切换时使用)
// 优先使用 publisherDeptIds(多个部门ID列表)
// 🔥 如果同时有 includeDetailCompleterDeptIds,需要用 OR 条件组合
if (includeDetailCompleterDeptIds != null && !includeDetailCompleterDeptIds.isEmpty()) {
// 需要查询:(发布者部门在 publisherDeptIds 中) OR (一级需求任务且明细完善人在指定部门中)
// 获取指定部门的所有用户ID
List<Long> detailCompleterUserIds = userMapper.selectList(
new LambdaQueryWrapper<User>()
.in(User::getDeptId, includeDetailCompleterDeptIds)
.eq(User::getStatus, 1)
.eq(User::getDeleted, 0)
).stream().map(User::getId).collect(java.util.stream.Collectors.toList());
log.info("查询一级需求任务,明细完善人部门IDs: {}, 对应用户IDs: {}", includeDetailCompleterDeptIds, detailCompleterUserIds);
if (publisherDeptIds != null && !publisherDeptIds.isEmpty()) {
// 组合条件:(publisherDeptId IN publisherDeptIds) OR (isPrimaryTask=1 AND detailCompleterId IN userIds)
wrapper.and(w -> w
.in(Task::getPublisherDeptId, publisherDeptIds)
.or(orWrapper -> {
orWrapper.eq(Task::getIsPrimaryTask, 1);
if (!detailCompleterUserIds.isEmpty()) {
orWrapper.in(Task::getDetailCompleterId, detailCompleterUserIds);
} else {
// 如果没有用户,添加一个不可能匹配的条件
orWrapper.eq(Task::getDetailCompleterId, -1);
}
})
);
log.info("组合查询: publisherDeptIds={} OR (isPrimaryTask=1 AND detailCompleterId IN {})", publisherDeptIds, detailCompleterUserIds);
} else if (publisherDeptId != null) {
wrapper.and(w -> w
.eq(Task::getPublisherDeptId, publisherDeptId)
.or(orWrapper -> {
orWrapper.eq(Task::getIsPrimaryTask, 1);
if (!detailCompleterUserIds.isEmpty()) {
orWrapper.in(Task::getDetailCompleterId, detailCompleterUserIds);
} else {
orWrapper.eq(Task::getDetailCompleterId, -1);
}
})
);
} else {
// 只查询一级需求任务
wrapper.eq(Task::getIsPrimaryTask, 1);
if (!detailCompleterUserIds.isEmpty()) {
wrapper.in(Task::getDetailCompleterId, detailCompleterUserIds);
} else {
wrapper.eq(Task::getDetailCompleterId, -1);
}
}
} else {
// 原有逻辑:只按发布者部门筛选
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_primary_approval、pending_detail_completion、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_primary_approval", "pending_detail_completion", "pending_demand_approval", "pending_receiving_approval");
// 🔥 过滤掉已经拆分成子任务组的一级需求部门任务
// 一级需求部门任务在接单部门审核时,应该显示子任务组,而不是主任务
// 条件:isPrimaryTask=1 AND isPipeline=1 AND approvalStatus='pending_receiving_approval' 的任务应该被过滤
wrapper.and(w -> w
.ne(Task::getIsPrimaryTask, 1) // 不是一级需求部门任务
.or()
.eq(Task::getIsPipeline, 0) // 或者不是流水线任务
.or()
.ne(Task::getApprovalStatus, "pending_receiving_approval") // 或者不是待接单部门审核状态
);
// 检查用户是否是管理员
User user = userService.getUserById(userId);
boolean isAdmin = user != null && Integer.valueOf(1).equals(user.getIsAdmin());
// 非管理员只能看到分配给自己的待审核任务
if (!isAdmin) {
boolean isDemandDeptMember = userService.getUserDepartments(userId).stream()
.filter(java.util.Objects::nonNull)
.anyMatch(dept -> "demand".equals(dept.getDeptType())
&& dept.getStatus() != null && dept.getStatus() == 1
&& (dept.getDeleted() == null || dept.getDeleted() == 0));
if (isDemandDeptMember) {
wrapper.and(w -> w
.eq(Task::getCurrentApproverId, userId)
.or(ow -> ow
.eq(Task::getApprovalStatus, "pending_detail_completion")
.eq(Task::getIsPrimaryTask, 1)
)
);
log.info("需求部门成员 {} 查询待审核任务,包含一级需求任务待完善明细列表", userId);
} else {
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)
.or()
.like(Task::getSpuInfo, keyword)
.or()
.like(Task::getRemark, keyword)
.or()
.apply("EXISTS (SELECT 1 FROM sys_user u WHERE u.id = publisher_id AND u.real_name LIKE CONCAT('%', {0}, '%'))", keyword)
.or()
.apply("EXISTS (SELECT 1 FROM sys_dept d WHERE d.id = publisher_dept_id AND d.dept_name LIKE CONCAT('%', {0}, '%'))", keyword)
.or()
.apply("EXISTS (SELECT 1 FROM brand b WHERE b.id = brand_id AND b.brand_name LIKE CONCAT('%', {0}, '%'))", keyword)
.or()
.apply("EXISTS (SELECT 1 FROM category c WHERE c.id = category_id AND c.category_name LIKE CONCAT('%', {0}, '%'))", keyword)
.or()
.apply("EXISTS (SELECT 1 FROM platform p WHERE p.id = platform_id AND p.platform_name LIKE CONCAT('%', {0}, '%'))", keyword)
.or()
.apply("EXISTS (SELECT 1 FROM task_type dt WHERE dt.id = task_type_id AND dt.type_name LIKE CONCAT('%', {0}, '%'))", keyword)
.or()
.apply("EXISTS (SELECT 1 FROM sys_user pp WHERE pp.id = primary_publisher_id AND pp.real_name LIKE CONCAT('%', {0}, '%'))", keyword)
.or()
.apply("EXISTS (SELECT 1 FROM sys_dept pd WHERE pd.id = primary_publisher_dept_id AND pd.dept_name LIKE CONCAT('%', {0}, '%'))", keyword)
.or()
.apply("EXISTS (SELECT 1 FROM sys_user dc WHERE dc.id = detail_completer_id AND dc.real_name LIKE CONCAT('%', {0}, '%'))", keyword)
.or()
.apply("EXISTS (SELECT 1 FROM sys_user r WHERE r.id = receiver_id AND r.real_name LIKE CONCAT('%', {0}, '%'))", keyword)
.or()
.apply("COALESCE(product_barcodes, '') LIKE CONCAT('%', {0}, '%')", 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;
}
/**
* 获取任务大厅(根据用户部门筛选可接单的任务)
* @param filterDeptId 筛选只显示该部门可接单的任务(可选)
*/
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, Long filterDeptId) {
Page<Task> pageParam = new Page<>(page, pageSize);
List<DemandType> allDemandTypes = demandTypeMapper.selectList(null);
// 构建ID到DemandType的映射,用于快速查找父级
Map<Long, DemandType> demandTypeMap = allDemandTypes.stream()
.collect(Collectors.toMap(DemandType::getId, dt -> dt));
// 获取当前用户部门可以接单的任务类型ID列表(支持三级分类继承二级分类的配置)
List<Long> allowedTaskTypeIds = null;
if (userDeptId != null) {
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;
}
// 选择父级接单部门时,运行时需要自动覆盖所有子部门。
return expandAllowedDeptIds(allowedDepts).contains(userDeptId);
})
.map(DemandType::getId)
.collect(Collectors.toList());
}
// 🔥 如果指定了筛选部门,进一步过滤只显示该部门可接单的任务类型
List<Long> filteredTaskTypeIds = allowedTaskTypeIds;
if (filterDeptId != null) {
// 🔥 获取筛选部门及其所有子部门的ID列表
List<Long> filterDeptAndChildIds = getDeptAndChildIds(filterDeptId);
Set<Long> filterDeptAndChildSet = new HashSet<>(filterDeptAndChildIds);
log.info("筛选部门ID: {}, 包含子部门: {}", filterDeptId, filterDeptAndChildIds);
List<Long> filterDeptAllowedIds = allDemandTypes.stream()
.filter(dt -> {
String allowedDepts = dt.getAllowedDeptIds();
if ((allowedDepts == null || allowedDepts.isEmpty()) && dt.getParentId() != null) {
DemandType parentType = demandTypeMap.get(dt.getParentId());
if (parentType != null) {
allowedDepts = parentType.getAllowedDeptIds();
}
}
// 只筛选明确指定了该部门(或其子部门)的任务类型(不包括所有部门可接单的)
if (allowedDepts == null || allowedDepts.isEmpty()) {
return false; // 所有部门都可接单的不纳入筛选结果
}
return expandAllowedDeptIds(allowedDepts).stream().anyMatch(filterDeptAndChildSet::contains);
})
.map(DemandType::getId)
.collect(Collectors.toList());
log.info("匹配的任务类型ID: {}", filterDeptAllowedIds);
// 取交集:用户可接单的 ∩ 筛选部门可接单的
if (allowedTaskTypeIds != null) {
filteredTaskTypeIds = allowedTaskTypeIds.stream()
.filter(filterDeptAllowedIds::contains)
.collect(Collectors.toList());
} else {
filteredTaskTypeIds = filterDeptAllowedIds;
}
}
return taskMapper.selectTaskHallByDept(pageParam, taskTypeId, demandTypeId, brandId, categoryId,
platformId, taskSubType, minScore, maxScore, keyword,
publisherName, timeRange, sortBy, filteredTaskTypeIds);
}
/**
* 获取用户所属部门ID
*/
public Long getUserDeptId(Long userId) {
User user = userService.getUserById(userId);
return user != null ? user.getDeptId() : null;
}
private Department resolvePublishingDepartment(User user, Long requestedDeptId, boolean isAdmin) {
if (user == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "用户不存在");
}
List<Department> userDepartments = new java.util.ArrayList<>(userService.getUserDepartments(user.getId()));
if (user.getDeptId() != null && userDepartments.stream().noneMatch(dept -> user.getDeptId().equals(dept.getId()))) {
Department primaryDept = departmentService.getDepartmentById(user.getDeptId());
if (primaryDept != null) {
userDepartments.add(primaryDept);
}
}
List<Department> publishingDepartments = userDepartments.stream()
.filter(java.util.Objects::nonNull)
.filter(dept -> dept.getStatus() != null && dept.getStatus() == 1)
.filter(dept -> dept.getDeleted() == null || dept.getDeleted() == 0)
.filter(dept -> departmentService.isDepartmentActiveWithAncestors(dept.getId()))
.filter(dept -> "demand".equals(dept.getDeptType()) || "primary_demand".equals(dept.getDeptType()))
.distinct()
.collect(Collectors.toList());
// 需求部门负责人可以按其管理的需求部门发起任务,不要求必须挂在 sys_user_department
List<Department> managedPublishingDepartments = departmentService.getUserManagedPublishingDepartments(user.getId());
if (!managedPublishingDepartments.isEmpty()) {
java.util.LinkedHashMap<Long, Department> publishingDeptMap = new java.util.LinkedHashMap<>();
publishingDepartments.forEach(dept -> publishingDeptMap.put(dept.getId(), dept));
managedPublishingDepartments.forEach(dept -> publishingDeptMap.put(dept.getId(), dept));
publishingDepartments = new java.util.ArrayList<>(publishingDeptMap.values());
}
if (requestedDeptId != null) {
Department requestedDept = departmentService.getDepartmentById(requestedDeptId);
if (requestedDept == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "发起部门不存在");
}
if (!departmentService.isDepartmentActiveWithAncestors(requestedDeptId)) {
throw new BusinessException(ResultCode.PARAM_ERROR, "发起部门已停用,请选择其他部门");
}
if (!"demand".equals(requestedDept.getDeptType()) && !"primary_demand".equals(requestedDept.getDeptType())) {
throw new BusinessException(ResultCode.PARAM_ERROR, "发起部门必须是需求部门或一级需求部门");
}
if (!isAdmin && publishingDepartments.stream().noneMatch(dept -> requestedDeptId.equals(dept.getId()))) {
throw new BusinessException(ResultCode.FORBIDDEN, "您不属于所选发起部门");
}
return requestedDept;
}
if (user.getDeptId() != null) {
Department currentDept = departmentService.getDepartmentById(user.getDeptId());
if (currentDept != null && ("demand".equals(currentDept.getDeptType()) || "primary_demand".equals(currentDept.getDeptType()))) {
return currentDept;
}
if (isAdmin && currentDept != null) {
return currentDept;
}
}
if (publishingDepartments.size() == 1) {
return publishingDepartments.get(0);
}
if (publishingDepartments.isEmpty()) {
throw new BusinessException(ResultCode.FORBIDDEN, "只有需求部门或一级需求部门可以发起任务");
}
throw new BusinessException(ResultCode.PARAM_ERROR, "请先选择发起部门");
}
/**
* 获取我发布的任务(支持多维度筛选)
*/
public IPage<Task> getMyPublishedTasks(Integer page, Integer pageSize, Long userId, String status,
Long taskTypeId, Long brandId, Long categoryId, Long platformId, String taskSubType,
Integer minScore, Integer maxScore, String timeRange, String keyword) {
Page<Task> pageParam = new Page<>(page, pageSize);
// 构建查询条件(支持普通发起人、一级需求发起人、明细完善人)
LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
wrapper.and(w -> w
.eq(Task::getPublisherId, userId)
.or().eq(Task::getDetailCompleterId, userId)
.or().eq(Task::getPrimaryPublisherId, userId)
);
wrapper.eq(Task::getDeleted, 0);
// 状态筛选
if (StringUtils.hasText(status)) {
wrapper.eq(Task::getStatus, status);
} else {
// 排除纯草稿(但保留已进入审批流程的任务)
// 如果 approval_status 存在且不是 draft,说明任务已进入审批流程,即使 status 是 draft 也应该显示
wrapper.and(w -> w
.ne(Task::getStatus, "draft")
.or(ow -> ow
.isNotNull(Task::getApprovalStatus)
.ne(Task::getApprovalStatus, "draft")
.ne(Task::getApprovalStatus, "")
)
);
}
// 任务类型筛选
if (taskTypeId != null) {
wrapper.eq(Task::getTaskTypeId, taskTypeId);
}
// 品牌筛选
if (brandId != null) {
wrapper.eq(Task::getBrandId, brandId);
}
// 类目筛选
if (categoryId != null) {
wrapper.eq(Task::getCategoryId, categoryId);
}
// 平台筛选
if (platformId != null) {
wrapper.eq(Task::getPlatformId, platformId);
}
// 需求类型筛选
if (StringUtils.hasText(taskSubType)) {
wrapper.eq(Task::getTaskSubType, taskSubType);
}
// 积分范围筛选
if (minScore != null) {
wrapper.ge(Task::getScore, minScore);
}
if (maxScore != null) {
wrapper.le(Task::getScore, maxScore);
}
// 时间范围筛选
if (StringUtils.hasText(timeRange)) {
LocalDateTime startTime = LocalDateTime.now();
switch (timeRange) {
case "today":
startTime = LocalDateTime.now().toLocalDate().atStartOfDay();
break;
case "week":
startTime = LocalDateTime.now().minusWeeks(1);
break;
case "month":
startTime = LocalDateTime.now().minusMonths(1);
break;
}
wrapper.ge(Task::getCreateTime, startTime);
}
// 关键词筛选
if (StringUtils.hasText(keyword)) {
wrapper.and(w -> w
.like(Task::getTitle, keyword)
.or()
.like(Task::getTaskNo, keyword)
.or()
.like(Task::getSpuInfo, keyword)
);
}
wrapper.orderByDesc(Task::getUpdateTime);
IPage<Task> taskPage = taskMapper.selectPage(pageParam, wrapper);
// 填充关联信息和子任务统计信息
for (Task task : taskPage.getRecords()) {
fillTaskRelatedInfo(task);
fillSubTaskStats(task);
}
return taskPage;
}
/**
* 获取我接收的任务(支持多维度筛选)
*/
public IPage<Task> getMyReceivedTasks(Integer page, Integer pageSize, Long userId, String status,
Long taskTypeId, Long brandId, Long categoryId, Long platformId) {
Page<Task> pageParam = new Page<>(page, pageSize);
// 构建查询条件
LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
wrapper.and(w -> w.eq(Task::getReceiverId, userId).or().eq(Task::getDetailCompleterId, userId));
wrapper.eq(Task::getDeleted, 0);
// 状态筛选
if (StringUtils.hasText(status)) {
wrapper.eq(Task::getStatus, status);
} else {
wrapper.ne(Task::getStatus, "draft"); // 排除草稿
}
// 任务类型筛选
if (taskTypeId != null) {
wrapper.eq(Task::getTaskTypeId, taskTypeId);
}
// 品牌筛选
if (brandId != null) {
wrapper.eq(Task::getBrandId, brandId);
}
// 类目筛选
if (categoryId != null) {
wrapper.eq(Task::getCategoryId, categoryId);
}
// 平台筛选
if (platformId != null) {
wrapper.eq(Task::getPlatformId, platformId);
}
wrapper.orderByDesc(Task::getCreateTime);
IPage<Task> taskPage = taskMapper.selectPage(pageParam, wrapper);
// 填充关联信息和子任务统计信息
for (Task task : taskPage.getRecords()) {
fillTaskRelatedInfo(task);
fillSubTaskStats(task);
}
return taskPage;
}
/**
* 获取我作为明细完善人的一级需求任务(支持多维度筛选)
* 需求部门成员完善任务明细后,可以在"进行中任务"看到这些任务
*/
public IPage<Task> getMyDetailCompleterTasks(Integer page, Integer pageSize, Long userId, String status,
Long taskTypeId, Long brandId, Long categoryId, Long platformId) {
Page<Task> pageParam = new Page<>(page, pageSize);
LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
// 查询条件:一级需求任务 且 明细完善人是当前用户
wrapper.eq(Task::getIsPrimaryTask, 1)
.eq(Task::getDetailCompleterId, userId)
.eq(Task::getDeleted, 0);
// 根据状态筛选
if (StringUtils.hasText(status)) {
wrapper.eq(Task::getStatus, status);
}
// 任务类型筛选
if (taskTypeId != null) {
wrapper.eq(Task::getTaskTypeId, taskTypeId);
}
// 品牌筛选
if (brandId != null) {
wrapper.eq(Task::getBrandId, brandId);
}
// 类目筛选
if (categoryId != null) {
wrapper.eq(Task::getCategoryId, categoryId);
}
// 平台筛选
if (platformId != null) {
wrapper.eq(Task::getPlatformId, platformId);
}
// 按更新时间倒序
wrapper.orderByDesc(Task::getUpdateTime);
IPage<Task> taskPage = taskMapper.selectPage(pageParam, wrapper);
// 填充关联信息和子任务统计
for (Task task : taskPage.getRecords()) {
fillTaskRelatedInfo(task);
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
// 🔥 始终根据receiver_id刷新receiverName,避免存储的旧值不一致
for (TaskItem item : subTasks) {
if (item.getReceiverId() != null) {
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) {
Department effectivePublisherDept = resolveDisplayPublisherDept(task);
if (effectivePublisherDept != null) {
task.setPublisherDeptId(effectivePublisherDept.getId());
task.setPublisherDeptName(effectivePublisherDept.getDeptName());
}
Department effectivePrimaryPublisherDept = resolveDisplayPrimaryPublisherDept(task);
if (effectivePrimaryPublisherDept != null) {
task.setPrimaryPublisherDeptId(effectivePrimaryPublisherDept.getId());
task.setPrimaryPublisherDeptName(effectivePrimaryPublisherDept.getDeptName());
}
// 填充品牌名称
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.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.getPrimaryPublisherId() != null) {
User primaryPublisher = userService.getUserById(task.getPrimaryPublisherId());
if (primaryPublisher != null) {
task.setPrimaryPublisherName(primaryPublisher.getRealName() != null ? primaryPublisher.getRealName() : primaryPublisher.getUsername());
}
}
// 填充明细完善人名称(需求部门成员)
if (task.getDetailCompleterId() != null) {
User detailCompleter = userService.getUserById(task.getDetailCompleterId());
if (detailCompleter != null) {
task.setDetailCompleterName(detailCompleter.getRealName() != null ? detailCompleter.getRealName() : detailCompleter.getUsername());
}
}
// 填充子任务统计信息
fillSubTaskStats(task);
}
/**
* 更新任务(普通任务)
*/
@Transactional(rollbackFor = Exception.class)
public void updateTask(Long id, Task task, Long userId) {
Task existTask = getTaskById(id);
String status = existTask.getStatus();
boolean receivingDeptLeader = isReceivingDeptLeader(userId);
boolean leaderCanEditPending = receivingDeptLeader && "pending".equals(status);
// 验证权限:任务发布人、管理员,或接单部门负责人(仅待接单任务)可以更新任务
boolean isAdmin = isAdminUser(userId);
if (!isAdmin && !userId.equals(existTask.getPublisherId()) && !leaderCanEditPending) {
throw new BusinessException(ResultCode.FORBIDDEN, "只有任务发布人或接单部门负责人(待接单任务)可以更新任务");
}
// 允许更新的任务状态:草稿、待审核(需求/接单部门审核)、被驳回
// 不允许更新的状态:进行中(in_progress)、已完成(completed)、已取消(cancelled)
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, "任务已在进行中或已完成,无法编辑");
}
User editor = userService.getUserById(userId);
boolean allowDeptOverride = isAdmin || userId.equals(existTask.getPublisherId());
Department publisherDept = resolvePublisherDeptForTaskUpdate(existTask, task.getPublisherDeptId(), editor, isAdmin, allowDeptOverride);
task.setPublisherDeptId(publisherDept.getId());
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);
String status = existTask.getStatus();
boolean receivingDeptLeader = isReceivingDeptLeader(userId);
boolean leaderCanEditPending = receivingDeptLeader && "pending".equals(status);
// 验证权限:任务发布人、一级需求任务的明细完善人、接单部门负责人(待接单任务)可以更新任务
boolean isAdmin = isAdminUser(userId);
// 对于一级需求任务,明细完善人在任何可编辑状态下都可以更新(包括草稿状态)
boolean isPrimaryTaskDetailCompleter = existTask.getIsPrimaryTask() != null
&& existTask.getIsPrimaryTask() == 1
&& existTask.getDetailCompleterId() != null
&& userId.equals(existTask.getDetailCompleterId());
// 对于一级需求任务在待完善明细状态,需求部门的成员都可以编辑
boolean isDemandDeptMemberForPrimaryTask = false;
if (existTask.getIsPrimaryTask() != null && existTask.getIsPrimaryTask() == 1
&& "pending_detail_completion".equals(existTask.getApprovalStatus())) {
List<Department> userDepartments = userService.getUserDepartments(userId);
isDemandDeptMemberForPrimaryTask = userDepartments.stream()
.filter(java.util.Objects::nonNull)
.anyMatch(dept -> "demand".equals(dept.getDeptType())
&& dept.getStatus() != null && dept.getStatus() == 1
&& (dept.getDeleted() == null || dept.getDeleted() == 0));
if (isDemandDeptMemberForPrimaryTask) {
log.info("需求部门成员 {} 编辑一级需求任务 {}", userId, id);
}
}
if (!isAdmin && !userId.equals(existTask.getPublisherId()) && !isPrimaryTaskDetailCompleter
&& !isDemandDeptMemberForPrimaryTask && !leaderCanEditPending) {
throw new BusinessException(ResultCode.FORBIDDEN, "只有任务发布人、指定的明细完善人或接单部门负责人(待接单任务)可以更新任务");
}
// 允许更新的任务状态:草稿、待审核(需求/接单部门审核)、被驳回、待完善明细
// 不允许更新的状态:进行中(in_progress)、已完成(completed)、已取消(cancelled)
String approvalStatus = existTask.getApprovalStatus();
// 验证任务是否是流水线任务
// 特殊情况:一级需求部门任务在草稿/驳回/待完善明细状态下,允许将非流水线任务转换为流水线任务
boolean isPrimaryTaskCanConvert = existTask.getIsPrimaryTask() != null
&& existTask.getIsPrimaryTask() == 1
&& ("draft".equals(status)
|| "pending_detail_completion".equals(approvalStatus)
|| (approvalStatus != null && approvalStatus.contains("rejected")));
if ((existTask.getIsPipeline() == null || existTask.getIsPipeline() != 1) && !isPrimaryTaskCanConvert) {
throw new BusinessException(ResultCode.PARAM_ERROR, "该任务不是流水线任务");
}
boolean canUpdate = "draft".equals(status) ||
"pending".equals(status) || // 待接单状态也允许编辑
(approvalStatus != null && (
"draft".equals(approvalStatus) ||
"pending_demand_approval".equals(approvalStatus) ||
"pending_receiving_approval".equals(approvalStatus) ||
"pending_detail_completion".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");
}
// 验证子任务列表(一级需求部门任务在草稿/驳回/待完善明细状态下可以不填写子任务)
boolean allowPrimaryTaskWithoutItems = existTask.getIsPrimaryTask() != null
&& existTask.getIsPrimaryTask() == 1
&& ("draft".equals(status)
|| "pending_detail_completion".equals(approvalStatus)
|| (approvalStatus != null && approvalStatus.contains("rejected")));
if ((dto.getItems() == null || dto.getItems().isEmpty()) && !allowPrimaryTaskWithoutItems) {
throw new BusinessException(ResultCode.PARAM_ERROR, "流水线任务至少需要一个子任务");
}
// 计算总积分、工时和数量(使用BigDecimal保留小数精度)
BigDecimal totalScoreDecimal = BigDecimal.ZERO;
double totalHours = 0;
int totalQuantity = 0;
// 如果有子任务才计算
if (dto.getItems() != null && !dto.getItems().isEmpty()) {
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();
}
} // 结束 if (dto.getItems() != null && !dto.getItems().isEmpty())
// 保留2位小数
totalScoreDecimal = totalScoreDecimal.setScale(2, java.math.RoundingMode.HALF_UP);
int totalScoreInt = totalScoreDecimal.intValue(); // 用于积分检查
// 检查部门积分是否充足(管理员跳过积分检查,一级需求部门无明细提交场景跳过)
User user = userService.getUserById(userId);
boolean allowDeptOverride = isAdmin || userId.equals(existTask.getPublisherId());
Department publisherDept = resolvePublisherDeptForTaskUpdate(existTask, dto.getPublisherDeptId(), user, isAdmin, allowDeptOverride);
existTask.setPublisherDeptId(publisherDept.getId());
if (!isAdmin && !allowPrimaryTaskWithoutItems && publisherDept.getId() != null) {
if (!teamScorePoolService.checkBalance(publisherDept.getId(), totalScoreInt)) {
throw new BusinessException(ResultCode.INSUFFICIENT_SCORE, "部门积分余额不足");
}
if (!teamScorePoolService.checkPeriodLimit(publisherDept.getId(), totalScoreInt)) {
throw new BusinessException(ResultCode.SCORE_LIMIT_EXCEEDED, "超过本周期积分消耗限制");
}
}
// 更新主任务信息
existTask.setTitle(dto.getTitle());
existTask.setPipelineMode(dto.getPipelineMode());
existTask.setIsPipeline(1); // 确保设置为流水线任务
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);
// 判断是否是一级需求任务的明细完善
// 🔥 修复: 一级需求任务在 pending_detail_completion 状态下被编辑提交时,
// 无论是谁编辑(明细完善人、需求部门成员、管理员),都应该进入 handlePrimaryTaskDetailCompletion
// 因为一级审核已经通过了,不需要再次进入一级审核流程
boolean isPrimaryTask = existTask.getIsPrimaryTask() != null && existTask.getIsPrimaryTask() == 1;
boolean wasPendingDetailCompletion = "pending_detail_completion".equals(approvalStatus);
// 🔥 增强判断:如果 approvalStatus 为 null 或其他状态,但一级审批已通过(审批日志中有 approve_primary 记录)
// 则应该直接进入接单部门审核流程,而不是重新启动一级审批
boolean primaryApprovalPassed = false;
if (isPrimaryTask && !wasPendingDetailCompletion) {
// 检查审批日志,判断一级审批是否已通过
primaryApprovalPassed = taskApprovalService.hasPrimaryApprovalPassed(id);
if (primaryApprovalPassed) {
log.info("一级需求任务一级审批已通过(通过审批日志判断): taskId={}", id);
}
}
boolean isPrimaryTaskDetailCompletion = isPrimaryTask && (wasPendingDetailCompletion || primaryApprovalPassed);
log.info("判断明细完善条件: taskId={}, isPrimaryTask={}, approvalStatus={}, wasPendingDetailCompletion={}, primaryApprovalPassed={}, isPrimaryTaskDetailCompletion={}",
id, isPrimaryTask, approvalStatus, wasPendingDetailCompletion, primaryApprovalPassed, isPrimaryTaskDetailCompletion);
if (isPrimaryTaskDetailCompletion) {
// 一级需求任务明细完善后,进入接单部门审核流程(跳过需求部门审核,因为一级审核已通过)
log.info("一级需求任务明细完善: taskId={}, userId={}", id, userId);
handlePrimaryTaskDetailCompletion(existTask, userId);
} else {
// 普通任务:重新初始化审核状态并发送通知
initApprovalStatus(existTask);
}
// 再次保存任务,因为审核状态可能被修改
taskMapper.updateById(existTask);
log.info("任务审核状态已更新: taskId={}, approvalStatus={}, currentApproverId={}",
existTask.getId(), existTask.getApprovalStatus(), existTask.getCurrentApproverId());
sendApprovalNotification(existTask);
log.info("流水线任务更新成功: taskId={}, userId={}", id, userId);
}
private Department resolvePublisherDeptForTaskUpdate(Task existTask, Long requestedDeptId, User editor, boolean isAdmin, boolean allowDeptOverride) {
if (allowDeptOverride && editor != null && requestedDeptId != null) {
try {
return resolvePublishingDepartment(editor, requestedDeptId, isAdmin);
} catch (BusinessException ex) {
log.info("任务 {} 指定的发起部门 {} 无法继续使用,改为自动解析: {}", existTask.getId(), requestedDeptId, ex.getMessage());
}
}
if (allowDeptOverride && editor != null) {
try {
return resolvePublishingDepartment(editor, existTask.getPublisherDeptId(), isAdmin);
} catch (BusinessException ex) {
log.info("任务 {} 原发起部门已失效,尝试按任务信息自动修复: {}", existTask.getId(), ex.getMessage());
}
}
if (!allowDeptOverride && requestedDeptId != null && !requestedDeptId.equals(existTask.getPublisherDeptId())) {
log.info("任务 {} 由非发布人编辑,忽略前端传入的发起部门 {},继续沿用任务归属部门", existTask.getId(), requestedDeptId);
}
Department fallbackDept = resolveDisplayPublisherDept(existTask);
if (fallbackDept != null) {
return fallbackDept;
}
throw new BusinessException(ResultCode.PARAM_ERROR, "任务发起部门已失效,请联系管理员处理后再提交");
}
private Department resolveDisplayPublisherDept(Task task) {
if (task == null) {
return null;
}
Department storedDept = departmentService.getDepartmentById(task.getPublisherDeptId());
if (isUsablePublishingDepartment(storedDept)) {
return storedDept;
}
Department brandDept = resolveBrandMappedPublishingDept(task.getBrandId());
if (brandDept != null) {
return brandDept;
}
return resolveUserPublishingDepartment(task.getPublisherId(), task.getPublisherDeptId());
}
private Department resolveDisplayPrimaryPublisherDept(Task task) {
if (task == null || task.getPrimaryPublisherDeptId() == null) {
return null;
}
Department storedDept = departmentService.getDepartmentById(task.getPrimaryPublisherDeptId());
if (isUsablePublishingDepartment(storedDept)) {
return storedDept;
}
return resolveUserPublishingDepartment(task.getPrimaryPublisherId(), task.getPrimaryPublisherDeptId());
}
private Department resolveBrandMappedPublishingDept(Long brandId) {
if (brandId == null) {
return null;
}
Department brandDept = departmentService.getDemandDeptByBrandId(brandId);
return isUsablePublishingDepartment(brandDept) ? brandDept : null;
}
private Department resolveUserPublishingDepartment(Long userId, Long preferredDeptId) {
if (userId == null) {
return null;
}
List<Department> validDepartments = userService.getUserDepartments(userId).stream()
.filter(java.util.Objects::nonNull)
.filter(this::isUsablePublishingDepartment)
.collect(Collectors.collectingAndThen(
Collectors.toMap(Department::getId, dept -> dept, (left, right) -> left, java.util.LinkedHashMap::new),
map -> map.values().stream().toList()
));
if (validDepartments.isEmpty()) {
return null;
}
if (preferredDeptId != null) {
for (Department department : validDepartments) {
if (preferredDeptId.equals(department.getId())) {
return department;
}
}
}
User user = userService.getUserById(userId);
if (user != null && user.getDeptId() != null) {
for (Department department : validDepartments) {
if (user.getDeptId().equals(department.getId())) {
return department;
}
}
}
return validDepartments.get(0);
}
private boolean isUsablePublishingDepartment(Department department) {
if (department == null || department.getId() == null) {
return false;
}
if (!"demand".equals(department.getDeptType()) && !"primary_demand".equals(department.getDeptType())) {
return false;
}
if (department.getStatus() == null || department.getStatus() != 1) {
return false;
}
if (department.getDeleted() != null && department.getDeleted() == 1) {
return false;
}
return departmentService.isDepartmentActiveWithAncestors(department.getId());
}
/**
* 为更新操作创建流水线子任务(复用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 = resolveEffectiveDeptKey(demandType, itemDto.getSelectedDeptId(), dto.getBrandId());
// 🔥 构建分组键:部门 + 设计师(如果指定了设计师)
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;
var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId());
String deptKey = resolveEffectiveDeptKey(demandType, itemDto.getSelectedDeptId(), dto.getBrandId());
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);
// 🔥 设置子任务的接单部门(无论什么模式都要设置)
String deptKeyForItem = groupToDeptKey.get(groupNo);
if (!groupItems.isEmpty()) {
Long selectedDeptIdForItem = groupItems.get(0).getSelectedDeptId();
if (selectedDeptIdForItem != null) {
// 用户明确选择了接单部门
item.setReceiverDeptId(selectedDeptIdForItem);
log.info("子任务 {} 设置用户选择的接单部门: {}", item.getItemNo(), selectedDeptIdForItem);
} else if (deptKeyForItem != null && !deptKeyForItem.equals("null") && !deptKeyForItem.contains(",")) {
// 任务类型只配置了一个部门,自动设置
try {
Long singleDeptIdForItem = Long.parseLong(deptKeyForItem.trim());
item.setReceiverDeptId(singleDeptIdForItem);
log.info("子任务 {} 自动设置唯一的接单部门: {}", item.getItemNo(), singleDeptIdForItem);
} catch (NumberFormatException e) {
log.warn("无法解析部门ID: {}", deptKeyForItem);
}
}
}
// 如果是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 (item.getReceiverDeptId() == null && 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, item.getReceiverDeptId());
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 (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);
// 验证权限(必须是任务发布人、一级需求发起人或明细完善人)
boolean isPublisher = userId.equals(task.getPublisherId());
boolean isPrimaryPublisher = userId.equals(task.getPrimaryPublisherId());
boolean isDetailCompleter = userId.equals(task.getDetailCompleterId());
if (!isPublisher && !isPrimaryPublisher && !isDetailCompleter) {
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 withdrawTask(Long taskId, Long userId) {
log.info("用户 {} 撤回任务 {} 进行编辑", userId, taskId);
Task task = getTaskById(taskId);
// 验证权限:
// 1. 原始发布人可以撤回
// 2. 对于一级需求任务,明细完善人也可以撤回
boolean hasPermission = userId.equals(task.getPublisherId());
if (!hasPermission && task.getIsPrimaryTask() != null && task.getIsPrimaryTask() == 1) {
hasPermission = userId.equals(task.getDetailCompleterId());
}
if (!hasPermission) {
throw new BusinessException(ResultCode.FORBIDDEN, "只有任务发布人可以撤回任务");
}
// 验证任务状态
// 可以撤回的状态:pending(待接单)、pending_demand_approval(待需求部门审核)、pending_receiving_approval(待接单部门审核)
String status = task.getStatus();
String approvalStatus = task.getApprovalStatus();
boolean canWithdraw = false;
boolean needRefundScore = false; // 是否需要返还积分
// 待接单状态可以撤回(已审核通过,需要返还积分)
if ("pending".equals(status)) {
canWithdraw = true;
needRefundScore = true; // pending状态说明已审核通过,积分已扣除,需要返还
}
// 待审核状态也可以撤回(未审核通过,不需要返还积分)
if (approvalStatus != null && (approvalStatus.contains("pending"))) {
canWithdraw = true;
// 待审核状态积分还未扣除,不需要返还
}
if (!canWithdraw) {
throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "只有待接单或待审核的任务可以撤回");
}
// 检查任务是否已被抢单(receiverId 不为空说明已被抢单)
if (task.getReceiverId() != null) {
throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "任务已被接单,无法撤回");
}
// 检查流水线任务的子任务是否有被抢单的
if (task.getIsPipeline() != null && task.getIsPipeline() == 1) {
List<TaskItem> items = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, taskId)
.isNotNull(TaskItem::getReceiverId)
);
if (!items.isEmpty()) {
throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "部分子任务已被接单,无法撤回");
}
}
// 返还积分(只有pending状态的任务需要返还,因为审核通过后才会扣除积分)
if (needRefundScore && task.getPublisherDeptId() != null && task.getScore() != null) {
int scoreToRefund = task.getScore().intValue();
teamScorePoolService.addScore(task.getPublisherDeptId(), scoreToRefund);
log.info("任务撤回,返还部门积分: taskId={}, deptId={}, score={}",
taskId, task.getPublisherDeptId(), scoreToRefund);
}
// 更新任务状态为草稿(使用 LambdaUpdateWrapper 确保 null 值也能更新)
taskMapper.update(null,
new LambdaUpdateWrapper<Task>()
.eq(Task::getId, taskId)
.set(Task::getStatus, "draft")
.set(Task::getApprovalStatus, null) // 清除审批状态
.set(Task::getCurrentApproverId, null)
.set(Task::getCurrentApproverRole, null)
.set(Task::getAuditStatus, "draft") // 重置审核状态
.set(Task::getUpdateTime, LocalDateTime.now())
);
// 如果是流水线任务,也需要重置子任务状态
if (task.getIsPipeline() != null && task.getIsPipeline() == 1) {
taskItemMapper.update(null,
new LambdaUpdateWrapper<TaskItem>()
.eq(TaskItem::getTaskId, taskId)
.set(TaskItem::getStatus, "pending")
.set(TaskItem::getUpdateTime, LocalDateTime.now())
);
}
log.info("任务 {} 已撤回为草稿状态,可以进行编辑", taskId);
}
/**
* 取消任务
*/
@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 boolean isReceivingDeptLeader(Long userId) {
if (userId == null) {
return false;
}
try {
Long count = departmentMapper.selectCount(new LambdaQueryWrapper<Department>()
.eq(Department::getLeaderId, userId)
.eq(Department::getDeptType, "receiving")
.eq(Department::getStatus, 1)
.eq(Department::getDeleted, 0));
return count != null && count > 0;
} 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);
// 进行中的任务数(处理中的任务,包括 pending、in_progress、review 等,排除 draft、completed、cancelled)
// 🔥 需求部门统计:所有非草稿、非已完成的任务都算"处理中"
Long inProgressCount = 0L;
List<String> excludeStatuses = java.util.Arrays.asList("draft", "completed", "cancelled");
if (isDeptView && "receiving".equals(deptType)) {
// 接单部门视角:统计该部门(含子部门)设计师接单的任务(主任务+子任务)
// 主任务数量
Long mainTaskCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "in_progress")
.in(Task::getReceiverDeptId, deptAndChildIds)
.eq(Task::getDeleted, 0));
// 子任务数量(TaskItem表没有deleted字段)
Long subTaskCount = taskItemMapper.selectCount(new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getStatus, "in_progress")
.in(TaskItem::getReceiverDeptId, deptAndChildIds));
inProgressCount = mainTaskCount + subTaskCount;
log.info("接单部门进行中任务统计: mainTaskCount={}, subTaskCount={}, total={}",
mainTaskCount, subTaskCount, inProgressCount);
} else if (isDeptView && "demand".equals(deptType)) {
// 需求部门视角:统计该部门发起的所有处理中任务(与前端列表显示一致)
inProgressCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.notIn(Task::getStatus, excludeStatuses)
.in(Task::getPublisherDeptId, deptAndChildIds)
.eq(Task::getDeleted, 0));
} else if (!isAdmin) {
// 普通用户:根据部门类型决定统计方式
if ("demand".equals(userDeptType)) {
// 需求部门用户:统计自己发起的所有处理中任务
inProgressCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.notIn(Task::getStatus, excludeStatuses)
.eq(Task::getPublisherId, userId)
.eq(Task::getDeleted, 0));
} else {
// 接单部门用户:统计自己接收的进行中任务
inProgressCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "in_progress")
.eq(Task::getReceiverId, userId)
.eq(Task::getDeleted, 0));
}
} else {
inProgressCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getStatus, "in_progress")
.eq(Task::getDeleted, 0));
}
// 已完成的任务数
Long completedCount = 0L;
if (isDeptView && "receiving".equals(deptType)) {
// 接单部门视角:只统计子任务(与列表显示一致)
completedCount = taskItemMapper.selectCount(new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getStatus, "completed")
.in(TaskItem::getReceiverDeptId, deptAndChildIds));
log.info("接单部门已完成任务统计(子任务): completedCount={}", completedCount);
} 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)
.ne(Task::getStatus, "draft") // 排除草稿
.eq(Task::getDeleted, 0));
} else if (isDeptView && "receiving".equals(deptType)) {
// 接单部门视角:统计该部门接单的任务数(主任务+子任务)
Long mainPublished = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.in(Task::getReceiverDeptId, deptAndChildIds)
.ge(Task::getCreateTime, monthStart)
.le(Task::getCreateTime, monthEnd)
.ne(Task::getStatus, "draft")
.eq(Task::getDeleted, 0));
Long subPublished = taskItemMapper.selectCount(new LambdaQueryWrapper<TaskItem>()
.in(TaskItem::getReceiverDeptId, deptAndChildIds)
.ge(TaskItem::getCreateTime, monthStart)
.le(TaskItem::getCreateTime, monthEnd));
publishedCount = mainPublished + subPublished;
log.info("接单部门接单任务统计: mainPublished={}, subPublished={}, total={}",
mainPublished, subPublished, publishedCount);
} else {
// 当前用户发布的任务数
publishedCount = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
.eq(Task::getPublisherId, userId)
.ge(Task::getCreateTime, monthStart)
.le(Task::getCreateTime, monthEnd)
.ne(Task::getStatus, "draft") // 排除草稿
.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();
boolean isPrimaryTask = task.getIsPrimaryTask() != null && task.getIsPrimaryTask() == 1;
Long receiverId;
if (isPrimaryTask && task.getDetailCompleterId() != null) {
// 一级需求任务:通知明细完善人确认接收并评分
receiverId = task.getDetailCompleterId();
notification.setTitle("一级需求任务待确认");
notification.setContent("一级需求任务【" + task.getTitle() + "】已完成,请确认接收并评分");
} else {
// 普通任务:通知发起人
receiverId = task.getPublisherId();
notification.setTitle("任务提交审核");
notification.setContent("任务【" + task.getTitle() + "】已提交审核,请查看");
}
notification.setReceiverId(receiverId);
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 isPrimaryTask = task.getIsPrimaryTask() != null && task.getIsPrimaryTask() == 1;
boolean isDetailCompleter = isPrimaryTask && userId.equals(task.getDetailCompleterId());
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 (!isDetailCompleter && !isPublisher && !isDeptLeader) {
if (isPrimaryTask) {
throw new BusinessException("只有明细完善人才能确认接收一级需求任务");
} else {
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,
completeTime
);
// 如果有延误扣分,单独记录扣除
if (deductedScore.compareTo(BigDecimal.ZERO) > 0) {
deptMonthlyScoreService.addDeductedScore(task.getReceiverDeptId(), deductedScore, completeTime);
}
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 {
// 判断是否为一级需求部门发起的任务
boolean isPrimaryTask = task.getIsPrimaryTask() != null && task.getIsPrimaryTask() == 1;
if (isPrimaryTask) {
// 一级需求部门任务:根据品牌路由到对应需求部门审核
initPrimaryTaskApprovalStatus(task);
} else {
// 普通需求部门任务:发送给本部门负责人审核
initNormalTaskApprovalStatus(task);
}
} catch (Exception e) {
log.error("初始化审核状态失败", e);
// 失败时跳过审核直接发布
task.setApprovalStatus("approved");
task.setStatus("pending");
}
}
/**
* 处理一级需求任务明细完善后的状态转换
* 明细完善后,任务直接进入接单部门审核流程(因为需求部门已经审核过了)
*/
private void handlePrimaryTaskDetailCompletion(Task task, Long completerId) {
log.info("一级需求任务明细已完善: taskId={}, completerId={}", task.getId(), completerId);
// 记录审核日志 (level=0 表示明细完善阶段)
taskApprovalService.logApproval(task.getId(), completerId, 0, "complete_detail", "已完善任务明细");
// 确定接单部门负责人,并把部门归属同步写入任务,避免后续组织架构调整时待审核任务失联
Department receivingApprovalDept = taskApprovalService.resolveReceivingApprovalDepartment(task);
Long receivingDeptId = receivingApprovalDept != null ? receivingApprovalDept.getId() : null;
Long receivingLeaderId = receivingApprovalDept != null ? receivingApprovalDept.getLeaderId() : null;
if (receivingLeaderId != null) {
// 更新任务状态为待接单部门审核
task.setApprovalStatus("pending_receiving_approval");
task.setStatus("pending_audit");
task.setDemandApprovalTime(LocalDateTime.now());
task.setReceiverDeptId(receivingDeptId);
task.setCurrentApproverId(receivingLeaderId);
task.setCurrentApproverRole("receiving_leader");
log.info("一级需求任务明细完善后,等待接单部门负责人审核:leaderId={}", receivingLeaderId);
// 🔥 更新子任务状态为 pending_approval,以便接单部门负责人审核
// 顺序模式:只更新第一个部门的子任务
// 并行模式:更新所有子任务
List<TaskItem> taskItems;
if ("sequential".equals(task.getPipelineMode())) {
// 顺序模式:只更新第一个部门的子任务(item_no 中部门序号为 01)
taskItems = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, task.getId())
.eq(TaskItem::getStatus, "pending")
.eq(TaskItem::getIsLocked, 0)
);
log.info("一级需求任务 {} 顺序模式,共 {} 个子任务待更新状态", task.getId(), taskItems.size());
} else {
// 并行模式:更新所有子任务
taskItems = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, task.getId())
.eq(TaskItem::getStatus, "pending")
.eq(TaskItem::getIsLocked, 0)
);
log.info("一级需求任务 {} 并行模式,共 {} 个子任务待更新状态", task.getId(), taskItems.size());
}
for (TaskItem item : taskItems) {
item.setStatus("pending_approval");
item.setUpdateTime(LocalDateTime.now());
taskItemMapper.updateById(item);
log.info("一级需求任务子任务 {} 状态已更新为 pending_approval", item.getItemNo());
}
if (taskItems.isEmpty()) {
log.warn("警告: 任务 {} 没有找到可更新的子任务,请检查子任务创建逻辑", task.getId());
}
} else {
// 如果无法确定接单部门负责人,直接发布到任务大厅
log.warn("无法确定接单部门负责人,任务将直接发布到任务大厅");
task.setApprovalStatus("approved");
task.setStatus("pending");
}
}
/**
* 初始化普通任务审核状态
*/
private void initNormalTaskApprovalStatus(Task task) {
// 设置审核状态为待需求部门审核
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");
}
}
}
/**
* 初始化一级需求部门任务审核状态
*/
private void initPrimaryTaskApprovalStatus(Task task) {
// 根据品牌找到对应的需求部门
Long brandId = task.getBrandId();
if (brandId == null) {
log.warn("一级需求任务未选择品牌,无法路由到需求部门");
throw new BusinessException(ResultCode.PARAM_ERROR, "一级需求部门发起任务必须选择品牌");
}
Department demandDept = departmentService.getDemandDeptByBrandId(brandId);
if (demandDept == null) {
Brand brand = brandMapper.selectById(brandId);
String brandName = brand != null ? brand.getName() : String.valueOf(brandId);
log.warn("品牌 {} 未配置负责的需求部门", brandName);
throw new BusinessException(ResultCode.PARAM_ERROR, "品牌 [" + brandName + "] 未配置负责的需求部门,请联系管理员");
}
if (demandDept.getLeaderId() == null) {
log.warn("需求部门 {} 未配置负责人", demandDept.getDeptName());
throw new BusinessException(ResultCode.PARAM_ERROR, "需求部门 [" + demandDept.getDeptName() + "] 未配置负责人,请联系管理员");
}
// 设置任务状态为待审核(pending_audit),区别于草稿状态
task.setStatus("pending_audit");
// 设置审核状态为待一级审核(由需求部门负责人审核)
task.setApprovalStatus("pending_primary_approval");
task.setCurrentApproverId(demandDept.getLeaderId());
task.setCurrentApproverRole("demand_leader");
log.info("一级需求任务已设置待审核状态,品牌={}, 路由到需求部门={}, 负责人={}",
brandId, demandDept.getDeptName(), demandDept.getLeaderId());
// 发送通知给需求部门负责人
try {
User primaryPublisher = userService.getUserById(task.getPrimaryPublisherId());
Department primaryDept = departmentService.getDepartmentById(task.getPrimaryPublisherDeptId());
Notification notification = new Notification();
notification.setReceiverId(demandDept.getLeaderId());
notification.setTitle("新的一级需求任务待审核");
notification.setContent(String.format("[%s-%s] 发起了新的一级需求任务 [%s],请审核并指定完善人",
primaryDept != null ? primaryDept.getDeptName() : "未知部门",
primaryPublisher != null ? primaryPublisher.getRealName() : "未知用户",
task.getTitle()));
notification.setNotificationType("PRIMARY_TASK_APPROVAL");
notification.setRelatedType("task");
notification.setRelatedId(task.getId());
notification.setPriority(2); // 高优先级
notification.setPushDingtalk(1); // 推送到钉钉
notificationService.sendNotification(notification);
log.info("已发送一级需求审核通知给需求部门负责人: {}", demandDept.getLeaderId());
} catch (Exception e) {
log.error("发送一级需求审核通知失败", e);
}
}
/**
* 发送审核通知(两级审核流程)- 包含丰富的任务详情
*/
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();
String receiverName = null;
if (receiverId != null) {
User receiver = userService.getUserById(receiverId);
if (receiver != null) {
receiverName = receiver.getRealName() != null ? receiver.getRealName() : receiver.getUsername();
if (receiverDeptId == null) {
receiverDeptId = receiver.getDeptId();
}
}
}
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 (receiverId != null) {
item.setReceiverId(receiverId);
item.setReceiverName(receiverName);
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: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;
}
private Set<Long> expandAllowedDeptIds(String allowedDeptIds) {
Set<Long> result = new HashSet<>();
if (!StringUtils.hasText(allowedDeptIds)) {
return result;
}
for (String deptIdText : allowedDeptIds.split(",")) {
String trimmed = deptIdText == null ? "" : deptIdText.trim();
if (trimmed.isEmpty()) {
continue;
}
try {
Long deptId = Long.parseLong(trimmed);
result.addAll(getDeptAndChildIds(deptId));
} catch (NumberFormatException e) {
log.warn("解析接单部门ID失败: {}", deptIdText);
}
}
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 {
// 检查积分池是否存在
if (teamScorePoolService.getByDeptId(task.getReceiverDeptId()) != null) {
// 从团队积分池扣除已奖励的积分
teamScorePoolService.deductScore(task.getReceiverDeptId(), task.getScoreReward().intValue());
log.info("已回退主任务积分: deptId={}, score={}", task.getReceiverDeptId(), task.getScoreReward());
// 回退部门月度积分统计
deptMonthlyScoreService.deductEarnedScore(
task.getReceiverDeptId(),
task.getScoreReward(),
task.getCompleteTime()
);
log.info("已回退部门月度积分: deptId={}, score={}", task.getReceiverDeptId(), task.getScoreReward());
} else {
log.warn("部门积分池不存在,跳过主任务积分回退: deptId={}", task.getReceiverDeptId());
}
} 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 {
// 检查积分池是否存在
if (teamScorePoolService.getByDeptId(item.getReceiverDeptId()) != null) {
// 从团队积分池扣除已奖励的积分
teamScorePoolService.deductScore(item.getReceiverDeptId(), item.getTotalScore().intValue());
log.info("已回退子任务积分: itemNo={}, deptId={}, score={}",
item.getItemNo(), item.getReceiverDeptId(), item.getTotalScore());
// 回退部门月度积分统计
deptMonthlyScoreService.deductEarnedScore(
item.getReceiverDeptId(),
item.getTotalScore(),
item.getCompleteTime()
);
} else {
log.warn("部门积分池不存在,跳过子任务积分回退: itemNo={}, deptId={}", item.getItemNo(), item.getReceiverDeptId());
}
} catch (Exception e) {
log.warn("回退子任务积分失败: itemNo={}, error={}", item.getItemNo(), e.getMessage());
}
}
}
// 3. 删除延误扣分记录
try {
int deletedRecords = delayPenaltyRecordMapper.delete(
new LambdaQueryWrapper<com.designhub.entity.DelayPenaltyRecord>()
.eq(com.designhub.entity.DelayPenaltyRecord::getTaskId, taskId)
);
if (deletedRecords > 0) {
log.info("已删除 {} 条延误扣分记录", deletedRecords);
}
} catch (Exception e) {
log.warn("删除延误扣分记录失败: {}", e.getMessage());
}
// 4. 删除所有子任务
int deletedItems = taskItemMapper.delete(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, taskId)
);
log.info("已删除 {} 个子任务", deletedItems);
// 5. 删除主任务
taskMapper.deleteById(taskId);
log.info("已删除主任务: taskNo={}, taskId={}", taskNo, taskId);
}
/**
* 获取用户信息(供Controller调用)
*/
public User getUserById(Long userId) {
return userService.getUserById(userId);
}
/**
* 从DTO创建任务子项(用于一级需求部门任务明细完善)
* 按接单部门分组,与普通流水线任务保持一致
*/
@Transactional(rollbackFor = Exception.class)
public void createTaskItemsFromDTO(Long taskId, List<com.designhub.dto.PipelineTaskDTO.TaskItemDTO> taskItems) {
Task task = taskMapper.selectById(taskId);
if (task == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "任务不存在");
}
log.info("为任务 {} 创建子任务(按接单部门分组),共 {} 个任务类型", taskId, taskItems.size());
// **步骤1:按接单部门和设计师自动分组**
int currentGroupNo = 1;
String lastGroupKey = null; // 格式:deptKey_receiverId
for (int i = 0; i < taskItems.size(); i++) {
com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto = taskItems.get(i);
var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId());
if (demandType == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "任务类型不存在: " + itemDto.getTaskTypeId());
}
// 获取当前任务类型在当前品牌下的接单部门配置
String currentDeptKey = resolveEffectiveDeptKey(demandType, itemDto.getSelectedDeptId(), task.getBrandId());
// 🔥 构建分组键:部门 + 设计师(如果指定了设计师)
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);
}
// 记录当前分组号(临时存储在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 : taskItems) {
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 < taskItems.size(); i++) {
com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto = taskItems.get(i);
int itemGroupNo = itemDto.getSortOrder() / 1000;
// 获取部门键
var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId());
String deptKey = resolveEffectiveDeptKey(demandType, itemDto.getSelectedDeptId(), task.getBrandId());
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(", ")));
// **步骤3:为每个分组创建一个子任务**
int itemIndex = 1;
BigDecimal totalScore = BigDecimal.ZERO;
BigDecimal totalHours = BigDecimal.ZERO;
int totalQuantity = 0;
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 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 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());
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);
}
BigDecimal groupTotalScore = groupTotalScoreDecimal.setScale(2, java.math.RoundingMode.HALF_UP);
// 创建子任务
TaskItem item = new TaskItem();
item.setTaskId(taskId);
// 使用新的编号格式:任务编号-部门序号-部门内子任务序号
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);
// 🔥 根据流水线模式和部门序号设置子任务状态
// 获取主任务的流水线模式(一级需求部门任务默认为sequential)
String pipelineMode = task.getPipelineMode();
if (pipelineMode == null || pipelineMode.isEmpty()) {
pipelineMode = "sequential"; // 默认顺序模式
}
if ("sequential".equals(pipelineMode)) {
// 顺序流水线:第一个部门的所有子任务可接单(部门内并行)
// 只有当第一个部门的所有子任务完成后,才解锁下一个部门的子任务
if (currentDeptIndex == 1) {
item.setStatus("pending");
item.setIsLocked(0);
log.info("子任务 {} 属于第一个部门,设置为待接单状态", item.getItemNo());
} else {
// 其他部门的子任务都是 locked(锁定),等待前一个部门完成后解锁
item.setStatus("locked");
item.setIsLocked(1);
log.info("子任务 {} 属于第{}个部门,设置为锁定状态", item.getItemNo(), currentDeptIndex);
}
} else {
// 并行流水线:全部待接单
item.setStatus("pending");
item.setIsLocked(0);
log.info("子任务 {} 并行模式,设置为待接单状态", item.getItemNo());
}
// 🔥 设置子任务的接单部门
// 优先使用用户选择的部门,否则尝试从任务类型配置获取
if (!groupItems.isEmpty()) {
Long selectedDeptId = groupItems.get(0).getSelectedDeptId();
if (selectedDeptId != null) {
item.setReceiverDeptId(selectedDeptId);
log.info("子任务 {} 设置用户选择的接单部门: {}", item.getItemNo(), selectedDeptId);
} else if (deptKey != null && !deptKey.equals("null") && !deptKey.contains(",")) {
// 任务类型只配置了一个部门,自动设置
try {
Long singleDeptId = Long.parseLong(deptKey.trim());
item.setReceiverDeptId(singleDeptId);
log.info("子任务 {} 自动设置唯一的接单部门: {}", item.getItemNo(), singleDeptId);
} catch (NumberFormatException e) {
log.warn("无法解析部门ID: {}", deptKey);
}
}
}
// 如果指定了设计师
if (!groupItems.isEmpty() && groupItems.get(0).getReceiverId() != null) {
Long receiverId = groupItems.get(0).getReceiverId();
item.setReceiverId(receiverId);
User receiver = userService.getUserById(receiverId);
if (receiver != null) {
item.setReceiverName(receiver.getRealName());
if (item.getReceiverDeptId() == null && receiver.getDeptId() != null) {
item.setReceiverDeptId(receiver.getDeptId());
}
}
}
taskItemMapper.insert(item);
log.info("创建子任务组: itemNo={}, groupNo={}, deptIndex={}, subIndex={}, types={}, score={}",
item.getItemNo(), groupNo, currentDeptIndex, subIndex, typeDetailsList.size(), groupTotalScore);
totalScore = totalScore.add(groupTotalScore);
totalHours = totalHours.add(groupTotalHours);
totalQuantity += groupTotalQuantity;
itemIndex++;
}
// 更新主任务的汇总信息
task.setQuantity(totalQuantity);
task.setEstimatedHours(totalHours);
// 注意:一级任务的积分由一级部门设定,这里不覆盖
// 但如果明细积分超过预估积分,需要警告
if (task.getScore() != null && totalScore.compareTo(task.getScore()) > 0) {
log.warn("任务明细积分总和 {} 超过预估积分 {},请注意", totalScore, task.getScore());
}
taskMapper.updateById(task);
log.info("任务 {} 子任务创建完成,共 {} 个子任务组,总积分 {}", taskId, groupedItems.size(), totalScore);
}
// ==================== 任务流程状态查询 ====================
/**
* 获取任务流程状态(用于任务节点查询功能)
* @param taskNo 任务编号
* @param keyword 任务标题关键字
* @return 任务流程状态信息
*/
public com.designhub.vo.TaskFlowStatusVO getTaskFlowStatus(String taskNo, String keyword) {
// 1. 查询任务
Task task = null;
if (taskNo != null && !taskNo.trim().isEmpty()) {
task = taskMapper.selectOne(
new LambdaQueryWrapper<Task>()
.eq(Task::getTaskNo, taskNo.trim())
.eq(Task::getDeleted, 0)
);
} else if (keyword != null && !keyword.trim().isEmpty()) {
task = taskMapper.selectOne(
new LambdaQueryWrapper<Task>()
.like(Task::getTitle, keyword.trim())
.eq(Task::getDeleted, 0)
.orderByDesc(Task::getId)
.last("LIMIT 1")
);
}
if (task == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "任务不存在");
}
// 2. 构建返回结果
com.designhub.vo.TaskFlowStatusVO vo = new com.designhub.vo.TaskFlowStatusVO();
vo.setTask(buildTaskBasicInfo(task));
vo.setCurrentNode(buildCurrentNodeInfo(task));
vo.setFlowTimeline(buildFlowTimeline(task));
vo.setRelatedUsers(buildRelatedUsers(task));
return vo;
}
/**
* 构建任务基本信息
*/
private com.designhub.vo.TaskFlowStatusVO.TaskBasicInfo buildTaskBasicInfo(Task task) {
com.designhub.vo.TaskFlowStatusVO.TaskBasicInfo info = new com.designhub.vo.TaskFlowStatusVO.TaskBasicInfo();
info.setId(task.getId());
info.setTaskNo(task.getTaskNo());
info.setTitle(task.getTitle());
info.setScore(task.getScore());
info.setIsPrimaryTask(task.getIsPrimaryTask() != null && task.getIsPrimaryTask() == 1);
info.setIsPipeline(task.getIsPipeline() != null && task.getIsPipeline() == 1);
info.setStatus(task.getStatus());
info.setStatusName(getStatusName(task.getStatus()));
info.setCreateTime(task.getCreateTime());
return info;
}
/**
* 构建当前节点信息
*/
private com.designhub.vo.TaskFlowStatusVO.CurrentNodeInfo buildCurrentNodeInfo(Task task) {
com.designhub.vo.TaskFlowStatusVO.CurrentNodeInfo info = new com.designhub.vo.TaskFlowStatusVO.CurrentNodeInfo();
String approvalStatus = task.getApprovalStatus();
String status = task.getStatus();
boolean isPrimaryTask = task.getIsPrimaryTask() != null && task.getIsPrimaryTask() == 1;
info.setStatus(approvalStatus != null ? approvalStatus : status);
info.setStatusName(getApprovalStatusName(approvalStatus, status, isPrimaryTask));
info.setDescription(getApprovalStatusDescription(approvalStatus, status, isPrimaryTask));
// 任务终态:不再使用 currentApprover,避免显示历史审批人导致“当前节点”误导
if ("completed".equals(status) || "cancelled".equals(status)) {
com.designhub.vo.TaskFlowStatusVO.WaitingUser finalOperator = buildFinalOperator(task);
if (finalOperator != null) {
finalOperator.setRoleName("最后操作人");
info.setWaitingFor(finalOperator);
}
return info;
}
// 设置等待操作人
if (task.getCurrentApproverId() != null) {
com.designhub.vo.TaskFlowStatusVO.WaitingUser waitingUser = new com.designhub.vo.TaskFlowStatusVO.WaitingUser();
waitingUser.setUserId(task.getCurrentApproverId());
User approver = userMapper.selectById(task.getCurrentApproverId());
if (approver != null) {
waitingUser.setUserName(approver.getRealName());
waitingUser.setDeptName(approver.getDeptName());
}
String role = task.getCurrentApproverRole();
waitingUser.setRole(role);
waitingUser.setRoleName(getApproverRoleName(role));
info.setWaitingFor(waitingUser);
} else if ("pending".equals(status) && task.getReceiverId() == null) {
// 待接单状态
com.designhub.vo.TaskFlowStatusVO.WaitingUser waitingUser = new com.designhub.vo.TaskFlowStatusVO.WaitingUser();
waitingUser.setRoleName("等待设计师接单");
info.setWaitingFor(waitingUser);
} else if ("in_progress".equals(status)) {
// 进行中状态 - 对于流水线任务,查找当前待处理的子任务
com.designhub.vo.TaskFlowStatusVO.WaitingUser waitingUser = new com.designhub.vo.TaskFlowStatusVO.WaitingUser();
if (task.getIsPipeline() != null && task.getIsPipeline() == 1) {
// 🔥 流水线任务:查找当前待处理的子任务(pending状态且未锁定)
TaskItem currentItem = taskItemMapper.selectOne(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, task.getId())
.eq(TaskItem::getStatus, "pending")
.eq(TaskItem::getIsLocked, 0)
.orderByAsc(TaskItem::getId)
.last("LIMIT 1")
);
if (currentItem != null && currentItem.getReceiverId() != null) {
// 有待处理的子任务,显示该子任务的接单人
waitingUser.setUserId(currentItem.getReceiverId());
User receiver = userMapper.selectById(currentItem.getReceiverId());
if (receiver != null) {
waitingUser.setUserName(receiver.getRealName());
waitingUser.setDeptName(receiver.getDeptName());
}
waitingUser.setRoleName("等待设计师接单");
} else if (currentItem != null) {
// 有待处理子任务但未指定接单人
waitingUser.setRoleName("等待设计师接单");
} else {
// 没有待处理子任务,查找进行中的子任务
TaskItem inProgressItem = taskItemMapper.selectOne(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, task.getId())
.eq(TaskItem::getStatus, "in_progress")
.orderByAsc(TaskItem::getId)
.last("LIMIT 1")
);
if (inProgressItem != null && inProgressItem.getReceiverId() != null) {
waitingUser.setUserId(inProgressItem.getReceiverId());
User receiver = userMapper.selectById(inProgressItem.getReceiverId());
if (receiver != null) {
waitingUser.setUserName(receiver.getRealName());
waitingUser.setDeptName(receiver.getDeptName());
}
waitingUser.setRoleName("设计师正在执行任务");
} else if (task.getReceiverId() != null) {
// 回退到主任务的接单人
waitingUser.setUserId(task.getReceiverId());
User receiver = userMapper.selectById(task.getReceiverId());
if (receiver != null) {
waitingUser.setUserName(receiver.getRealName());
waitingUser.setDeptName(receiver.getDeptName());
}
waitingUser.setRoleName("设计师正在执行任务");
}
}
} else if (task.getReceiverId() != null) {
// 普通任务:使用主任务的接单人
waitingUser.setUserId(task.getReceiverId());
User receiver = userMapper.selectById(task.getReceiverId());
if (receiver != null) {
waitingUser.setUserName(receiver.getRealName());
waitingUser.setDeptName(receiver.getDeptName());
}
waitingUser.setRoleName("设计师正在执行任务");
}
info.setWaitingFor(waitingUser);
}
return info;
}
private com.designhub.vo.TaskFlowStatusVO.WaitingUser buildFinalOperator(Task task) {
com.designhub.vo.TaskFlowStatusVO.WaitingUser waitingUser = new com.designhub.vo.TaskFlowStatusVO.WaitingUser();
Long userId = null;
if (task.getIsPipeline() != null && task.getIsPipeline() == 1) {
TaskItem lastCompletedItem = taskItemMapper.selectOne(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, task.getId())
.eq(TaskItem::getStatus, "completed")
.isNotNull(TaskItem::getReceiverId)
.orderByDesc(TaskItem::getCompleteTime)
.orderByDesc(TaskItem::getUpdateTime)
.orderByDesc(TaskItem::getId)
.last("LIMIT 1")
);
if (lastCompletedItem != null) {
userId = lastCompletedItem.getReceiverId();
}
} else {
userId = task.getReceiverId();
}
if (userId == null) {
return null;
}
waitingUser.setUserId(userId);
User user = userMapper.selectById(userId);
if (user != null) {
waitingUser.setUserName(user.getRealName());
waitingUser.setDeptName(user.getDeptName());
}
return waitingUser;
}
/**
* 构建流程时间线
*/
private java.util.List<com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode> buildFlowTimeline(Task task) {
java.util.List<com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode> timeline = new java.util.ArrayList<>();
boolean isPrimaryTask = task.getIsPrimaryTask() != null && task.getIsPrimaryTask() == 1;
String approvalStatus = task.getApprovalStatus();
String status = task.getStatus();
// 查询审批日志
java.util.List<TaskApprovalLog> logs = taskApprovalService.getApprovalLogs(task.getId());
java.util.Map<String, TaskApprovalLog> logMap = new java.util.HashMap<>();
for (TaskApprovalLog log : logs) {
logMap.put(log.getAction(), log);
}
if (isPrimaryTask) {
// 一级需求任务流程
// 1. 一级需求发起
com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode node1 = new com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode();
node1.setNodeName("一级需求发起");
node1.setStatus("completed");
node1.setTime(task.getCreateTime());
if (task.getPrimaryPublisherId() != null) {
User publisher = userMapper.selectById(task.getPrimaryPublisherId());
if (publisher != null) {
node1.setOperatorName(publisher.getRealName());
node1.setOperatorDept(publisher.getDeptName());
}
}
timeline.add(node1);
// 2. 一级需求审批
com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode node2 = new com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode();
node2.setNodeName("一级需求审批");
TaskApprovalLog primaryLog = logMap.get("approve_primary");
if (primaryLog != null) {
node2.setStatus("completed");
node2.setOperatorName(primaryLog.getApproverName());
node2.setTime(primaryLog.getCreateTime());
node2.setComment(primaryLog.getComment());
// 获取审批人部门
User approver = userMapper.selectById(primaryLog.getApproverId());
if (approver != null) {
node2.setOperatorDept(approver.getDeptName());
}
} else if ("pending_primary_approval".equals(approvalStatus)) {
node2.setStatus("pending");
if (task.getCurrentApproverId() != null) {
User approver = userMapper.selectById(task.getCurrentApproverId());
if (approver != null) {
node2.setOperatorName(approver.getRealName());
node2.setOperatorDept(approver.getDeptName());
}
}
} else {
node2.setStatus("waiting");
}
timeline.add(node2);
// 3. 明细完善
com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode node3 = new com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode();
node3.setNodeName("明细完善");
TaskApprovalLog detailLog = logMap.get("complete_detail");
if (detailLog != null) {
node3.setStatus("completed");
node3.setOperatorName(detailLog.getApproverName());
node3.setTime(detailLog.getCreateTime());
node3.setComment(detailLog.getComment());
User completer = userMapper.selectById(detailLog.getApproverId());
if (completer != null) {
node3.setOperatorDept(completer.getDeptName());
}
} else if ("pending_detail_completion".equals(approvalStatus)) {
node3.setStatus("pending");
if (task.getDetailCompleterId() != null) {
User completer = userMapper.selectById(task.getDetailCompleterId());
if (completer != null) {
node3.setOperatorName(completer.getRealName());
node3.setOperatorDept(completer.getDeptName());
}
}
} else if (primaryLog != null) {
node3.setStatus("waiting");
} else {
node3.setStatus("waiting");
}
timeline.add(node3);
// 4. 接单部门审批(如果是流水线任务)
boolean isPipeline = task.getIsPipeline() != null && task.getIsPipeline() == 1;
if (isPipeline || "pending_receiving_approval".equals(approvalStatus) ||
logMap.containsKey("approve_receiving") || logMap.containsKey("reject_receiving")) {
com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode node4 = new com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode();
node4.setNodeName("接单部门审批");
TaskApprovalLog receivingLog = logMap.get("approve_receiving");
if (receivingLog != null) {
node4.setStatus("completed");
node4.setOperatorName(receivingLog.getApproverName());
node4.setTime(receivingLog.getCreateTime());
node4.setComment(receivingLog.getComment());
User approver = userMapper.selectById(receivingLog.getApproverId());
if (approver != null) {
node4.setOperatorDept(approver.getDeptName());
}
} else if ("pending_receiving_approval".equals(approvalStatus)) {
node4.setStatus("pending");
if (task.getCurrentApproverId() != null) {
User approver = userMapper.selectById(task.getCurrentApproverId());
if (approver != null) {
node4.setOperatorName(approver.getRealName());
node4.setOperatorDept(approver.getDeptName());
}
}
} else {
node4.setStatus("waiting");
}
timeline.add(node4);
}
// 5. 任务发布/执行
com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode node5 = new com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode();
node5.setNodeName("任务执行");
if ("approved".equals(approvalStatus) && ("pending".equals(status) || "in_progress".equals(status) || "completed".equals(status))) {
if ("completed".equals(status)) {
node5.setStatus("completed");
node5.setTime(task.getCompleteTime());
} else if ("in_progress".equals(status)) {
node5.setStatus("pending");
} else {
node5.setStatus("pending");
}
if (task.getReceiverId() != null) {
User receiver = userMapper.selectById(task.getReceiverId());
if (receiver != null) {
node5.setOperatorName(receiver.getRealName());
node5.setOperatorDept(receiver.getDeptName());
}
}
} else {
node5.setStatus("waiting");
}
timeline.add(node5);
} else {
// 普通任务流程
// 1. 任务发起
com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode node1 = new com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode();
node1.setNodeName("任务发起");
node1.setStatus("completed");
node1.setTime(task.getCreateTime());
if (task.getPublisherId() != null) {
User publisher = userMapper.selectById(task.getPublisherId());
if (publisher != null) {
node1.setOperatorName(publisher.getRealName());
node1.setOperatorDept(publisher.getDeptName());
}
}
timeline.add(node1);
// 2. 任务审核
com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode node2 = new com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode();
node2.setNodeName("任务审核");
if ("approved".equals(approvalStatus) || "pending".equals(status) || "in_progress".equals(status) || "completed".equals(status)) {
node2.setStatus("completed");
// 查找审批日志
TaskApprovalLog approveLog = logMap.get("approve");
if (approveLog != null) {
node2.setOperatorName(approveLog.getApproverName());
node2.setTime(approveLog.getCreateTime());
node2.setComment(approveLog.getComment());
User approver = userMapper.selectById(approveLog.getApproverId());
if (approver != null) {
node2.setOperatorDept(approver.getDeptName());
}
}
} else if ("pending_approval".equals(approvalStatus)) {
node2.setStatus("pending");
if (task.getCurrentApproverId() != null) {
User approver = userMapper.selectById(task.getCurrentApproverId());
if (approver != null) {
node2.setOperatorName(approver.getRealName());
node2.setOperatorDept(approver.getDeptName());
}
}
} else {
node2.setStatus("waiting");
}
timeline.add(node2);
// 3. 任务执行
com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode node3 = new com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode();
node3.setNodeName("任务执行");
if ("completed".equals(status)) {
node3.setStatus("completed");
node3.setTime(task.getCompleteTime());
if (task.getReceiverId() != null) {
User receiver = userMapper.selectById(task.getReceiverId());
if (receiver != null) {
node3.setOperatorName(receiver.getRealName());
node3.setOperatorDept(receiver.getDeptName());
}
}
} else if ("in_progress".equals(status)) {
node3.setStatus("pending");
if (task.getReceiverId() != null) {
User receiver = userMapper.selectById(task.getReceiverId());
if (receiver != null) {
node3.setOperatorName(receiver.getRealName());
node3.setOperatorDept(receiver.getDeptName());
}
}
} else if ("pending".equals(status)) {
node3.setStatus("waiting");
} else {
node3.setStatus("waiting");
}
timeline.add(node3);
// 4. 如果是流水线任务,添加每个节点的详细进度
boolean isPipeline = task.getIsPipeline() != null && task.getIsPipeline() == 1;
if (isPipeline && ("in_progress".equals(status) || "completed".equals(status))) {
// 查询所有子任务,按 group_no 分组
java.util.List<TaskItem> taskItems = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, task.getId())
.orderByAsc(TaskItem::getGroupNo)
.orderByAsc(TaskItem::getSortOrder)
);
// 按 group_no 分组统计
java.util.Map<Integer, java.util.List<TaskItem>> groupedItems = taskItems.stream()
.collect(java.util.stream.Collectors.groupingBy(
item -> item.getGroupNo() != null ? item.getGroupNo() : 1
));
// 为每个节点创建时间线项
java.util.List<Integer> sortedGroupNos = groupedItems.keySet().stream()
.sorted()
.collect(java.util.stream.Collectors.toList());
for (Integer groupNo : sortedGroupNos) {
java.util.List<TaskItem> groupItems = groupedItems.get(groupNo);
if (groupItems == null || groupItems.isEmpty()) continue;
// 统计该节点的完成情况
int totalItems = groupItems.size();
int completedItems = 0;
int inProgressItems = 0;
int pendingApprovalItems = 0;
TaskItem firstItem = groupItems.get(0);
String receiverName = null;
java.time.LocalDateTime completeTime = null;
for (TaskItem item : groupItems) {
if ("completed".equals(item.getStatus())) {
completedItems++;
if (completeTime == null || (item.getCompleteTime() != null && item.getCompleteTime().isAfter(completeTime))) {
completeTime = item.getCompleteTime();
}
} else if ("in_progress".equals(item.getStatus())) {
inProgressItems++;
} else if ("pending_approval".equals(item.getStatus())) {
pendingApprovalItems++;
}
if (receiverName == null && item.getReceiverName() != null) {
receiverName = item.getReceiverName();
}
}
// 创建节点时间线
com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode pipelineNode = new com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode();
pipelineNode.setNodeName("节点" + groupNo + "执行");
if (completedItems == totalItems) {
// 所有子任务都完成
pipelineNode.setStatus("completed");
pipelineNode.setTime(completeTime);
pipelineNode.setComment("已完成 " + completedItems + "/" + totalItems + " 项");
} else if (completedItems > 0 || inProgressItems > 0) {
// 有进行中的子任务
pipelineNode.setStatus("pending");
pipelineNode.setComment("进行中 " + (completedItems + inProgressItems) + "/" + totalItems + " 项");
} else if (pendingApprovalItems > 0) {
// 等待审批
pipelineNode.setStatus("pending");
pipelineNode.setComment("待审批 " + pendingApprovalItems + " 项");
} else {
// 等待中
pipelineNode.setStatus("waiting");
pipelineNode.setComment("等待前置节点完成");
}
if (receiverName != null) {
pipelineNode.setOperatorName(receiverName);
}
timeline.add(pipelineNode);
}
}
}
return timeline;
}
/**
* 构建相关人员信息
*/
private com.designhub.vo.TaskFlowStatusVO.RelatedUsers buildRelatedUsers(Task task) {
com.designhub.vo.TaskFlowStatusVO.RelatedUsers users = new com.designhub.vo.TaskFlowStatusVO.RelatedUsers();
// 发起人
Long publisherId = task.getPrimaryPublisherId() != null ? task.getPrimaryPublisherId() : task.getPublisherId();
if (publisherId != null) {
User publisher = userMapper.selectById(publisherId);
if (publisher != null) {
com.designhub.vo.TaskFlowStatusVO.UserInfo info = new com.designhub.vo.TaskFlowStatusVO.UserInfo();
info.setUserId(publisherId);
info.setName(publisher.getRealName());
info.setDept(publisher.getDeptName());
users.setPublisher(info);
}
}
// 明细完善人
if (task.getDetailCompleterId() != null) {
User completer = userMapper.selectById(task.getDetailCompleterId());
if (completer != null) {
com.designhub.vo.TaskFlowStatusVO.UserInfo info = new com.designhub.vo.TaskFlowStatusVO.UserInfo();
info.setUserId(task.getDetailCompleterId());
info.setName(completer.getRealName());
info.setDept(completer.getDeptName());
users.setDetailCompleter(info);
}
}
// 接单人
if (task.getReceiverId() != null) {
User receiver = userMapper.selectById(task.getReceiverId());
if (receiver != null) {
com.designhub.vo.TaskFlowStatusVO.UserInfo info = new com.designhub.vo.TaskFlowStatusVO.UserInfo();
info.setUserId(task.getReceiverId());
info.setName(receiver.getRealName());
info.setDept(receiver.getDeptName());
users.setReceiver(info);
}
}
// 当前审批人
if (task.getCurrentApproverId() != null) {
User approver = userMapper.selectById(task.getCurrentApproverId());
if (approver != null) {
com.designhub.vo.TaskFlowStatusVO.UserInfo info = new com.designhub.vo.TaskFlowStatusVO.UserInfo();
info.setUserId(task.getCurrentApproverId());
info.setName(approver.getRealName());
info.setDept(approver.getDeptName());
users.setCurrentApprover(info);
}
}
return users;
}
/**
* 获取任务执行状态中文名称
*/
private String getStatusName(String status) {
if (status == null) return "未知";
switch (status) {
case "draft": return "草稿";
case "pending_audit": return "待审核";
case "pending": return "待接单";
case "in_progress": return "进行中";
case "completed": return "已完成";
case "cancelled": return "已取消";
default: return status;
}
}
/**
* 获取审批状态中文名称
*/
private String getApprovalStatusName(String approvalStatus, String status, boolean isPrimaryTask) {
if (approvalStatus == null) {
return getStatusName(status);
}
switch (approvalStatus) {
case "draft": return "草稿";
case "pending_primary_approval": return "待一级需求审批";
case "pending_detail_completion": return "待完善明细";
case "pending_demand_approval": return "待需求部门审批";
case "pending_receiving_approval": return "待接单部门审批";
case "pending_approval": return "待审核";
case "approved":
// 已审核通过,显示执行状态
return getStatusName(status);
case "rejected": return "已驳回";
case "rejected_by_demand": return "需求部门驳回";
case "rejected_by_receiving": return "接单部门驳回";
default: return approvalStatus;
}
}
/**
* 获取审批状态描述
*/
private String getApprovalStatusDescription(String approvalStatus, String status, boolean isPrimaryTask) {
if (approvalStatus == null) {
return "";
}
switch (approvalStatus) {
case "draft": return "任务已创建但未提交";
case "pending_primary_approval": return "等待需求部门负责人审核并指定明细完善人";
case "pending_detail_completion": return "等待指定的明细完善人填写任务明细";
case "pending_demand_approval": return "明细完善后等待需求部门负责人审核";
case "pending_receiving_approval": return "等待接单部门负责人审核";
case "pending_approval": return "等待审核通过后发布";
case "approved":
if ("pending".equals(status)) {
return "任务已发布,等待设计师接单";
} else if ("in_progress".equals(status)) {
return "设计师正在执行任务";
} else if ("completed".equals(status)) {
return "任务已完成";
}
return "任务已审核通过";
case "rejected": return "任务被驳回,请修改后重新提交";
case "rejected_by_demand": return "被需求部门负责人驳回";
case "rejected_by_receiving": return "被接单部门负责人驳回";
default: return "";
}
}
/**
* 获取审批人角色中文名称
*/
private String getApproverRoleName(String role) {
if (role == null) return "";
switch (role) {
case "demand_leader": return "需求部门负责人";
case "detail_completer": return "明细完善人";
case "receiving_leader": return "接单部门负责人";
default: return role;
}
}
}