5515 lines
263 KiB
Java
5515 lines
263 KiB
Java
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;
|
||
}
|
||
}
|
||
}
|