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; /** * 创建任务 */ @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); } } // 计算总积分和工时:取第一个类型的单价 Long firstTypeId = Long.parseLong(typeIds[0].trim()); var firstType = configService.getDemandTypeById(firstTypeId); int totalScore = firstType.getUnitScore().multiply(BigDecimal.valueOf(task.getQuantity())).intValue(); BigDecimal totalHours = firstType.getEstimatedHours() != null ? firstType.getEstimatedHours().multiply(BigDecimal.valueOf(task.getQuantity())) : BigDecimal.ZERO; task.setScore(totalScore); task.setEstimatedHours(totalHours); // 兼容性:同时保存第一个类型到task_type_id task.setTaskTypeId(firstTypeId); log.info("创建多类型任务: typeIds={}, score={}", task.getTaskTypeIds(), totalScore); } // 兼容单类型模式 else if (task.getTaskTypeId() != null) { var demandType = configService.getDemandTypeById(task.getTaskTypeId()); if (demandType == null) { throw new BusinessException(ResultCode.PARAM_ERROR, "任务类型不存在"); } // 计算总积分和工时 int totalScore = demandType.getUnitScore().multiply(BigDecimal.valueOf(task.getQuantity())).intValue(); BigDecimal totalHours = demandType.getEstimatedHours() != null ? demandType.getEstimatedHours().multiply(BigDecimal.valueOf(task.getQuantity())) : BigDecimal.ZERO; task.setScore(totalScore); task.setEstimatedHours(totalHours); // 自动填充taskTypeIds字段 task.setTaskTypeIds(String.valueOf(task.getTaskTypeId())); log.info("创建单类型任务: typeId={}, score={}", task.getTaskTypeId(), totalScore); } else { throw new BusinessException(ResultCode.PARAM_ERROR, "请选择任务类型"); } } // 检查部门积分是否充足(管理员跳过积分检查) boolean isAdminPublisher = task.getPublisherId() != null && isAdminUser(task.getPublisherId()); if (!isAdminPublisher && task.getPublisherDeptId() != null && task.getScore() != null) { if (!teamScorePoolService.checkBalance(task.getPublisherDeptId(), task.getScore())) { throw new BusinessException(ResultCode.INSUFFICIENT_SCORE, "部门积分余额不足"); } // 检查周期消耗限制 if (!teamScorePoolService.checkPeriodLimit(task.getPublisherDeptId(), task.getScore())) { 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, "流水线任务至少需要一个子任务"); } // 计算总积分和工时 int totalScore = 0; double totalHours = 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()); } int itemScore = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity())).intValue(); double itemHours = demandType.getEstimatedHours() != null ? demandType.getEstimatedHours().multiply(BigDecimal.valueOf(itemDto.getQuantity())).doubleValue() : 0; totalScore += itemScore; totalHours += itemHours; } // 检查部门积分是否充足(管理员跳过积分检查) if (!isAdmin && user.getDeptId() != null) { if (!teamScorePoolService.checkBalance(user.getDeptId(), totalScore)) { throw new BusinessException(ResultCode.INSUFFICIENT_SCORE, "部门积分余额不足"); } if (!teamScorePoolService.checkPeriodLimit(user.getDeptId(), totalScore)) { 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(totalScore); task.setEstimatedHours(BigDecimal.valueOf(totalHours)); 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 lastDeptKey = null; 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); } } } // 判断是否需要创建新分组 boolean needNewGroup = false; if (i > 0) { // 如果当前或上一个的部门配置为 null,则必须分组 if (currentDeptKey == null || lastDeptKey == null) { needNewGroup = true; } // 如果两者都不为 null,则比较是否相同 else if (!currentDeptKey.equals(lastDeptKey)) { needNewGroup = true; } } if (needNewGroup) { currentGroupNo++; log.info("检测到接单部门变化,创建新分组: groupNo={}, 前一部门={}, 当前部门={}", currentGroupNo, lastDeptKey, currentDeptKey); } // 记录当前分组号(临时存储在sortOrder字段,稍后会恢复) itemDto.setSortOrder(currentGroupNo * 1000 + i); // 高位存group_no,低位存原始顺序 lastDeptKey = currentDeptKey; } log.info("分组完成,共 {} 个子任务组", currentGroupNo); // **步骤2:按分组汇总,每个分组创建一个子任务** // 先按分组号收集任务类型 Map> groupedItems = new java.util.LinkedHashMap<>(); Map groupFirstTaskTypeId = new java.util.HashMap<>(); // 每组第一个任务类型ID for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : dto.getItems()) { int itemGroupNo = itemDto.getSortOrder() / 1000; groupedItems.computeIfAbsent(itemGroupNo, k -> new java.util.ArrayList<>()).add(itemDto); // 记录每组第一个任务类型ID if (!groupFirstTaskTypeId.containsKey(itemGroupNo)) { groupFirstTaskTypeId.put(itemGroupNo, itemDto.getTaskTypeId()); } } // 为每个分组创建一个子任务 int itemIndex = 1; for (Map.Entry> entry : groupedItems.entrySet()) { int groupNo = entry.getKey(); List groupItems = entry.getValue(); // 汇总该分组的积分、工时、数量 int groupTotalScore = 0; BigDecimal groupTotalHours = BigDecimal.ZERO; int groupTotalQuantity = 0; StringBuilder taskTypeIds = new StringBuilder(); // 构建任务类型明细 JSON 数组 java.util.List> typeDetailsList = new java.util.ArrayList<>(); for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : groupItems) { var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId()); int itemScore = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity())).intValue(); BigDecimal itemHours = demandType.getEstimatedHours() != null ? demandType.getEstimatedHours().multiply(BigDecimal.valueOf(itemDto.getQuantity())) : BigDecimal.ZERO; groupTotalScore += itemScore; groupTotalHours = groupTotalHours.add(itemHours); groupTotalQuantity += itemDto.getQuantity(); if (taskTypeIds.length() > 0) { taskTypeIds.append(","); } taskTypeIds.append(itemDto.getTaskTypeId()); // 添加任务类型明细 java.util.Map detail = new java.util.LinkedHashMap<>(); detail.put("typeId", itemDto.getTaskTypeId()); detail.put("typeName", demandType.getName()); detail.put("quantity", itemDto.getQuantity()); detail.put("unitScore", demandType.getUnitScore()); detail.put("totalScore", itemScore); typeDetailsList.add(detail); } // 创建子任务 com.designhub.entity.TaskItem item = new com.designhub.entity.TaskItem(); item.setTaskId(task.getId()); item.setItemNo(generateTaskItemNo(task.getTaskNo(), itemIndex)); item.setGroupNo(groupNo); item.setTaskTypeId(groupFirstTaskTypeId.get(groupNo)); // 使用第一个任务类型ID // 保存任务类型明细 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); item.setUnitScore(groupTotalScore / groupTotalQuantity); // 平均单价 item.setTotalScore(groupTotalScore); item.setEstimatedHours(groupTotalHours); item.setSortOrder(itemIndex); item.setProgress(0); // 如果是assigned模式,设置子任务的接收人(从该组的第一个任务项获取) if ("assigned".equals(dto.getReleaseType()) && !groupItems.isEmpty()) { Long itemReceiverId = groupItems.get(0).getReceiverId(); if (itemReceiverId != null) { item.setReceiverId(itemReceiverId); User itemReceiver = userService.getUserById(itemReceiverId); if (itemReceiver != null && itemReceiver.getDeptId() != null) { item.setReceiverDeptId(itemReceiver.getDeptId()); } log.info("子任务组 {} 已预先指定设计师: {}", groupNo, itemReceiverId); } } // 关键:根据流水线模式和分组设置初始状态 if ("sequential".equals(dto.getPipelineMode())) { if (groupNo == 1) { // 顺序流水线:第一组待接单 item.setStatus("pending"); item.setIsLocked(0); item.setPrevItemId(null); } else { // 其他组的子任务都是 locked(锁定) item.setStatus("locked"); item.setIsLocked(1); } } else { // 并行流水线:全部待接单 item.setStatus("pending"); item.setIsLocked(0); } taskItemMapper.insert(item); log.info("子任务创建成功,ID: {}, 编号: {}, 组号: {}, 积分: {}, 工时: {}, 状态: {}", item.getId(), item.getItemNo(), item.getGroupNo(), groupTotalScore, groupTotalHours, item.getStatus()); itemIndex++; } log.info("流水线任务创建完成,主任务ID: {}, 子任务数: {}, 分组数: {}", task.getId(), groupedItems.size(), currentGroupNo); // 【两级审核流程】发送审核通知给部门负责人 sendApprovalNotification(task); return task.getId(); } /** * 生成子任务编号 */ private String generateTaskItemNo(String taskNo, int itemIndex) { return taskNo + "-" + String.format("%02d", itemIndex); } /** * 生成任务编号 * 格式: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().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 getTaskList(Integer page, Integer pageSize, String status, Long taskTypeId) { Page pageParam = new Page<>(page, pageSize); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); // 根据状态筛选 if (StringUtils.hasText(status)) { wrapper.eq(Task::getStatus, status); } // 根据任务类型筛选 if (taskTypeId != null) { wrapper.eq(Task::getTaskTypeId, taskTypeId); } // 按更新时间倒序 wrapper.orderByDesc(Task::getUpdateTime); return taskMapper.selectPage(pageParam, wrapper); } /** * 获取所有待审核任务(处于审批流程中的任务) * 包括:pending_demand_approval 和 pending_receiving_approval 状态的任务 */ public IPage getAllPendingApprovalTasks(Integer page, Integer pageSize, Long taskTypeId) { Page pageParam = new Page<>(page, pageSize); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); // 查询处于审批流程中的任务 wrapper.in(Task::getApprovalStatus, "pending_demand_approval", "pending_receiving_approval"); // 根据任务类型筛选 if (taskTypeId != null) { wrapper.eq(Task::getTaskTypeId, taskTypeId); } // 未删除的任务 wrapper.eq(Task::getDeleted, 0); // 按创建时间倒序 wrapper.orderByDesc(Task::getCreateTime); IPage 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 items = taskItemMapper.selectByTaskId(task.getId()); if (items != null && !items.isEmpty()) { // 聚合所有子任务的 typeDetails java.util.List> 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> itemDetails = objectMapper.readValue( item.getTypeDetails(), new com.fasterxml.jackson.core.type.TypeReference>>() {} ); 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 getCompletedTasks(Integer page, Integer pageSize, Long userId, String keyword, String sortBy) { // 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 pageParam = new Page<>(page, pageSize); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); // 状态必须是已完成 wrapper.eq(Task::getStatus, "completed"); // 3. 根据角色筛选数据范围 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 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()); } } } return result; } /** * 获取任务大厅(根据用户部门筛选可接单的任务) */ public IPage 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 pageParam = new Page<>(page, pageSize); // 获取当前用户部门可以接单的任务类型ID列表(支持三级分类继承二级分类的配置) List allowedTaskTypeIds = null; if (userDeptId != null) { List allDemandTypes = demandTypeMapper.selectList(null); // 构建ID到DemandType的映射,用于快速查找父级 Map 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 getMyPublishedTasks(Integer page, Integer pageSize, Long userId, String status) { Page pageParam = new Page<>(page, pageSize); IPage taskPage = taskMapper.selectMyPublishedTasks(pageParam, userId, status); // 填充子任务统计信息 for (Task task : taskPage.getRecords()) { fillSubTaskStats(task); } return taskPage; } /** * 获取我接收的任务 */ public IPage getMyReceivedTasks(Integer page, Integer pageSize, Long userId, String status) { Page pageParam = new Page<>(page, pageSize); IPage 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 subTasks = taskItemMapper.selectList( new LambdaQueryWrapper() .eq(TaskItem::getTaskId, task.getId()) .orderByAsc(TaskItem::getSortOrder) ); // 汇总所有子任务的交付路径 if (subTasks != null && !subTasks.isEmpty()) { List 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); // 设置子任务列表(包含处理人信息) task.setSubTasks(subTasks); // 如果主任务没有receiverName,从子任务中获取第一个有接单人的 if (task.getReceiverName() == null || task.getReceiverName().isEmpty()) { subTasks.stream() .filter(item -> item.getReceiverName() != null && !item.getReceiverName().isEmpty()) .findFirst() .ifPresent(item -> task.setReceiverName(item.getReceiverName() + "等")); } } } 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) { Task existTask = getTaskById(id); // TODO: 验证权限 task.setId(id); taskMapper.updateById(task); } /** * 抢单 */ @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) { int hours = task.getEstimatedHours().intValue(); if (hours > 0) { deadline = now.plusHours(hours); log.info("任务deadline已重新计算:从接单时间 {} 开始,预估 {} 小时,截止时间 {}", now, hours, 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()); // 如果有延误扣分,从积分池中扣除 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()); } // 发送通知给接单设计师 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 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) { // 检查积分是否充足 if (!teamScorePoolService.checkBalance(task.getPublisherDeptId(), task.getScore())) { throw new BusinessException("部门积分余额不足,无法通过审核"); } // 扣除积分 teamScorePoolService.consumeScore(task.getPublisherDeptId(), task.getScore()); 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 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 pageParam = new Page<>(page, pageSize); return taskMapper.selectLeaderTaskPage(pageParam, user.getDeptId(), auditStatus); } /** * 获取接单部门负责人的接单明细(接单部门负责人视图) */ public IPage 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 pageParam = new Page<>(page, pageSize); return taskMapper.selectDeptReceivingTaskPage(pageParam, user.getDeptId(), status); } /** * 获取工作台统计数据 */ public DashboardStatsVO getDashboardStats(Long userId, String month) { try { // 获取用户角色,判断是否为管理员 List roles = roleService.getRolesByUserId(userId); boolean isAdmin = roles.stream() .anyMatch(role -> "ADMIN".equals(role.getRoleCode()) || "SUPER_ADMIN".equals(role.getRoleCode())); log.info("获取工作台统计数据: userId={}, isAdmin={}, month={}", userId, isAdmin, month); // 解析月份,如果不传则使用当前月份 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); // 进行中的任务数 LambdaQueryWrapper inProgressQuery = new LambdaQueryWrapper() .eq(Task::getStatus, "in_progress"); if (!isAdmin) { inProgressQuery.eq(Task::getReceiverId, userId); } Long inProgressCount = taskMapper.selectCount(inProgressQuery); // 已完成的任务数 LambdaQueryWrapper completedQuery = new LambdaQueryWrapper() .eq(Task::getStatus, "completed"); if (!isAdmin) { completedQuery.eq(Task::getReceiverId, userId); } Long completedCount = taskMapper.selectCount(completedQuery); // 待审核的任务数 LambdaQueryWrapper reviewQuery = new LambdaQueryWrapper() .eq(Task::getStatus, "review"); if (!isAdmin) { reviewQuery.eq(Task::getReceiverId, userId); } Long reviewCount = taskMapper.selectCount(reviewQuery); // 今日开始的任务数 LambdaQueryWrapper todayStartedQuery = new LambdaQueryWrapper() .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 todayCompletedQuery = new LambdaQueryWrapper() .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 weekCompletedQuery = new LambdaQueryWrapper() .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 lastWeekCompletedQuery = new LambdaQueryWrapper() .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 availableQuery = new LambdaQueryWrapper() .eq(Task::getStatus, "pending"); // 可接任务对所有人都是全局的,不需要过滤用户 Long availableCount = taskMapper.selectCount(availableQuery); // 当日新增任务数 LambdaQueryWrapper todayNewQuery = new LambdaQueryWrapper() .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); // 获取总积分 LambdaQueryWrapper scoreQuery = new LambdaQueryWrapper() .eq(Task::getStatus, "completed") .select(Task::getScoreReward); if (!isAdmin) { scoreQuery.eq(Task::getReceiverId, userId); } List completedTasks = taskMapper.selectList(scoreQuery); int totalScore = completedTasks.stream() .filter(t -> t.getScoreReward() != null) .mapToInt(t -> t.getScoreReward().intValue()) .sum(); // 获取当前用户当月发布的任务数 LambdaQueryWrapper publishedQuery = new LambdaQueryWrapper() .eq(Task::getPublisherId, userId) .ge(Task::getCreateTime, monthStart) .le(Task::getCreateTime, monthEnd) .eq(Task::getDeleted, 0); Long publishedCount = taskMapper.selectCount(publishedQuery); 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 getMyInProgressTasks(Long userId) { return taskMapper.selectList( new LambdaQueryWrapper() .eq(Task::getReceiverId, userId) .eq(Task::getStatus, "in_progress") .orderByDesc(Task::getStartTime) ); } /** * 批量更新任务进度 */ @Transactional(rollbackFor = Exception.class) public void batchUpdateProgress(Long userId, List> updates) { for (java.util.Map 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) { // 计算预计完成时间(接单时间 + 预估工时) long estimatedMinutes = task.getEstimatedHours().multiply(BigDecimal.valueOf(60)).longValue(); LocalDateTime expectedCompleteTime = task.getStartTime().plusMinutes(estimatedMinutes); // 判断是否延误 if (completeTime.isAfter(expectedCompleteTime)) { isDelayed = true; task.setIsDelayed(1); // 计算延误小时数 long delayMinutes = java.time.Duration.between(expectedCompleteTime, completeTime).toMinutes(); delayHours = BigDecimal.valueOf(delayMinutes).divide(BigDecimal.valueOf(60), 2, java.math.RoundingMode.HALF_UP); task.setDelayDays(delayHours.divide(BigDecimal.valueOf(24), 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 = BigDecimal.valueOf(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 taskItems = taskItemMapper.selectList( new LambdaQueryWrapper() .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); } } /** * 格式化工时(小时转天+小时,8小时=1天) */ private String formatHours(BigDecimal hours) { if (hours == null) return "--"; int totalHours = hours.intValue(); int days = totalHours / 8; int remainingHours = totalHours % 8; if (days > 0 && remainingHours > 0) { return days + "天" + remainingHours + "小时"; } else if (days > 0) { return days + "天"; } else { return totalHours + "小时"; } } /** * 计算任务总积分(用于审核时修改) */ public int calculateTotalScore(List taskTypes) { int totalScore = 0; for (TaskUpdateDTO.TaskTypeUpdateDTO taskType : taskTypes) { DemandType demandType = configService.getDemandTypeById(taskType.getTaskTypeId()); if (demandType != null && demandType.getUnitScore() != null) { totalScore += demandType.getUnitScore().multiply(BigDecimal.valueOf(taskType.getQuantity())).intValue(); } } return totalScore; } /** * 创建流水线任务的子任务项(用于审核通过后拆分) */ public void createPipelineTaskItems(Task mainTask, com.designhub.dto.PipelineTaskDTO dto) { // 委托给已有的createPipelineTask逻辑中的子任务创建部分 // 这里简化处理,实际应该提取createPipelineTask中的子任务创建逻辑 log.info("开始拆分流水线子任务:taskId={}, mode={}", mainTask.getId(), dto.getPipelineMode()); // TODO: 实现流水线子任务拆分逻辑 // 可以参考createPipelineTask方法中的实现 } }