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