package com.designhub.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.designhub.dto.PipelineTaskDTO; import com.designhub.dto.TaskApprovalDTO; import com.designhub.dto.TaskUpdateDTO; import com.designhub.entity.*; import com.designhub.exception.BusinessException; import com.designhub.mapper.*; import com.designhub.common.ResultCode; import com.designhub.entity.Brand; import com.designhub.entity.Category; import com.designhub.entity.Platform; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; /** * 任务审核服务 */ @Service @RequiredArgsConstructor @Slf4j public class TaskApprovalService { private final TaskMapper taskMapper; private final TaskItemMapper taskItemMapper; private final TaskApprovalLogMapper approvalLogMapper; private final DepartmentMapper departmentMapper; private final UserMapper userMapper; private final BrandMapper brandMapper; private final CategoryMapper categoryMapper; private final PlatformMapper platformMapper; private final NotificationService notificationService; private final TeamScorePoolService teamScorePoolService; private final TaskService taskService; private final ConfigService configService; private final ObjectMapper objectMapper; private final DingTalkService dingTalkService; private final com.designhub.util.WorkHoursCalculator workHoursCalculator; /** * 需求部门负责人审核通过 */ @Transactional(rollbackFor = Exception.class) public void approveDemandLevel(Long taskId, Long approverId, TaskApprovalDTO dto) { // 1. 验证任务和权限 Task task = getTaskForApproval(taskId, "pending_demand_approval"); validateApprover(task, approverId, "demand_leader"); // 2. 应用任务修改(如果有) if (dto.getTaskUpdates() != null) { applyTaskUpdates(task, dto.getTaskUpdates()); logApproval(taskId, approverId, 1, "modify", dto.getComment()); } // 3. 所有任务都需要接单部门负责人审核(无论是否预先指定了设计师) String releaseType = task.getReleaseType(); // 确定接单部门负责人 Long receivingLeaderId = determineReceivingLeader(task); if (receivingLeaderId == null) { throw new BusinessException(ResultCode.PARAM_ERROR, "无法确定接单部门负责人,请在系统设置中为接单部门配置负责人"); } // 更新任务状态为待接单部门审核 task.setApprovalStatus("pending_receiving_approval"); task.setDemandApprovalTime(LocalDateTime.now()); task.setCurrentApproverId(receivingLeaderId); task.setCurrentApproverRole("receiving_leader"); taskMapper.updateById(task); // 记录审核日志 logApproval(taskId, approverId, 1, "approve", dto.getComment()); // 发送通知给接单部门负责人 User receivingLeader = userMapper.selectById(receivingLeaderId); if (receivingLeader != null) { // 根据是否预先指定了设计师,调整通知内容 String notificationContent; if ("assigned".equals(releaseType) && task.getReceiverId() != null) { User preAssignedDesigner = userMapper.selectById(task.getReceiverId()); String designerName = preAssignedDesigner != null ? preAssignedDesigner.getRealName() : "未知"; notificationContent = String.format("任务【%s】需要您审核确认(已预先指定设计师:%s)", task.getTitle(), designerName); } else { notificationContent = String.format("任务【%s】需要您审核并分配设计师", task.getTitle()); } notificationService.sendNotification( receivingLeaderId, "新任务待审核", notificationContent, "task_approval", taskId ); } log.info("需求部门负责人审核通过,任务ID:{},审核人:{},发布类型:{},等待接单部门负责人审核", taskId, approverId, releaseType); } /** * 需求部门负责人驳回 */ @Transactional(rollbackFor = Exception.class) public void rejectByDemandLeader(Long taskId, Long approverId, String reason) { Task task = getTaskForApproval(taskId, "pending_demand_approval"); validateApprover(task, approverId, "demand_leader"); task.setApprovalStatus("rejected_by_demand"); task.setStatus("draft"); task.setRejectReason(reason); // 保存驳回原因 task.setCurrentApproverId(null); task.setCurrentApproverRole(null); taskMapper.updateById(task); logApproval(taskId, approverId, 1, "reject", reason); // 通知发起人 notificationService.sendNotification( task.getPublisherId(), "任务已被驳回", String.format("您的任务【%s】被需求部门负责人驳回,原因:%s", task.getTitle(), reason), "task_reject", taskId ); // 如果任务有指定的处理人,发送群通知告知驳回 if (task.getReceiverId() != null) { try { com.designhub.entity.User receiver = userMapper.selectById(task.getReceiverId()); if (receiver != null) { String title = "任务已驳回"; StringBuilder content = new StringBuilder(); content.append("-------------------------------- \n"); content.append(" 【 任 务 已 驳 回 】 \n"); content.append("-------------------------------- \n\n"); content.append("任务名称:").append(task.getTitle()).append(" \n"); content.append("任务编号:").append(task.getTaskNo()).append(" \n"); content.append("原处理人:").append(receiver.getRealName()).append(" \n"); content.append("驳回原因:").append(reason).append(" \n"); content.append(" \n"); content.append("--------------------------------"); java.util.List atMobiles = receiver.getPhone() != null ? java.util.Arrays.asList(receiver.getPhone()) : null; dingTalkService.sendRobotMessage(title, content.toString(), atMobiles); } } catch (Exception e) { log.error("发送任务驳回群通知失败", e); } } log.info("需求部门负责人驳回任务,任务ID:{},审核人:{}", taskId, approverId); } /** * 接单部门负责人审核通过 * @param taskId 任务ID * @param approverId 审核人ID * @param dto 审核数据(包含备注和任务修改) * @param assignedReceiverId 指定的接收人ID(可选,如果任务已预先指定设计师则忽略此参数) */ @Transactional(rollbackFor = Exception.class) public void approveReceivingLevel(Long taskId, Long approverId, TaskApprovalDTO dto, Long assignedReceiverId) { Task task = getTaskForApproval(taskId, "pending_receiving_approval"); validateApprover(task, approverId, "receiving_leader"); // 应用任务修改(如果有) if (dto.getTaskUpdates() != null) { applyTaskUpdates(task, dto.getTaskUpdates()); logApproval(taskId, approverId, 2, "modify", dto.getComment()); } // 检查任务是否已预先指定了设计师 boolean isPreAssigned = "assigned".equals(task.getReleaseType()) && task.getReceiverId() != null; Long finalReceiverId = null; if (isPreAssigned) { // 任务已预先指定设计师,不允许修改 if (assignedReceiverId != null && !assignedReceiverId.equals(task.getReceiverId())) { log.warn("任务【{}】已预先指定设计师,忽略接单部门负责人的指定操作", task.getTitle()); } finalReceiverId = task.getReceiverId(); log.info("任务【{}】使用预先指定的设计师: {}", task.getTitle(), finalReceiverId); } else if (assignedReceiverId != null) { // 接单部门负责人新指定设计师 com.designhub.entity.User receiver = userMapper.selectById(assignedReceiverId); if (receiver == null) { throw new BusinessException(ResultCode.PARAM_ERROR, "指定的接收人不存在"); } task.setReceiverId(assignedReceiverId); task.setReceiverDeptId(receiver.getDeptId()); task.setReleaseType("assigned"); // 设置为指定模式 finalReceiverId = assignedReceiverId; log.info("任务【{}】已指定给设计师【{}】", task.getTitle(), receiver.getRealName()); } // 判断是否为流水线任务 if (task.getIsPipeline() != null && task.getIsPipeline() == 1) { // 流水线任务:先保存主任务的receiverId(如果已设置),确保updatePipelineSubTasksForAssigned能读取到 if (finalReceiverId != null) { task.setStatus("pending"); // 临时状态,splitIntoPipelineSubTasks会更新 taskMapper.updateById(task); log.info("流水线任务主任务receiverId已保存:taskId={}, receiverId={}", task.getId(), finalReceiverId); } // 拆分子任务 splitIntoPipelineSubTasks(task); } else { // 普通任务:根据是否有接收人设置状态 if (finalReceiverId != null) { // 如果接收人部门ID还没设置,需要设置 if (task.getReceiverDeptId() == null) { com.designhub.entity.User receiver = userMapper.selectById(finalReceiverId); if (receiver != null) { task.setReceiverDeptId(receiver.getDeptId()); } } task.setStatus("in_progress"); // 指定接单直接进入进行中状态 LocalDateTime now = LocalDateTime.now(); task.setStartTime(now); task.setActualStartTime(now); // 重新计算deadline:从接单时间开始计算(使用工作时间计算) if (task.getEstimatedHours() != null && task.getEstimatedHours().compareTo(java.math.BigDecimal.ZERO) > 0) { task.setDeadline(workHoursCalculator.calculateDeadline(now, task.getEstimatedHours())); log.info("任务deadline已重新计算:从接单时间 {} 开始,预估 {} 小时,截止时间 {}", now, task.getEstimatedHours(), task.getDeadline()); } } else { task.setStatus("pending"); // 未指定则发布到大厅 } } // 更新审核状态 task.setApprovalStatus("approved"); task.setReceivingApprovalTime(LocalDateTime.now()); task.setCurrentApproverId(null); task.setCurrentApproverRole(null); taskMapper.updateById(task); // 扣除部门积分 try { teamScorePoolService.deductScore(task.getPublisherDeptId(), task.getScore()); } catch (Exception e) { log.error("扣除部门积分失败", e); throw new BusinessException(ResultCode.ERROR, "扣除部门积分失败:" + e.getMessage()); } // 记录审核日志 String logComment = dto.getComment(); if (finalReceiverId != null) { com.designhub.entity.User receiver = userMapper.selectById(finalReceiverId); String assignNote = isPreAssigned ? "(预先指定)" : ""; logComment = (logComment != null ? logComment + ";" : "") + "已指定给设计师" + assignNote + ":" + (receiver != null ? receiver.getRealName() : finalReceiverId); } logApproval(taskId, approverId, 2, "approve", logComment); // 发送钉钉通知 // 对于流水线任务,需要给第一个部门的所有子任务设计师发送通知 java.util.Set notifiedReceiverIds = new java.util.HashSet<>(); if (task.getIsPipeline() != null && task.getIsPipeline() == 1) { // 流水线任务:查询第一个部门的所有子任务,给每个设计师发送通知 List firstDeptItems = taskItemMapper.selectList( new LambdaQueryWrapper() .eq(TaskItem::getTaskId, task.getId()) .eq(TaskItem::getStatus, "in_progress") .isNotNull(TaskItem::getReceiverId) ); for (TaskItem item : firstDeptItems) { if (item.getReceiverId() != null && !notifiedReceiverIds.contains(item.getReceiverId())) { notifiedReceiverIds.add(item.getReceiverId()); sendTaskAssignedNotification(task, item.getReceiverId(), isPreAssigned); } } log.info("流水线任务【{}】已通知 {} 个设计师", task.getTitle(), notifiedReceiverIds.size()); } else if (finalReceiverId != null) { // 普通任务:只通知一个设计师 sendTaskAssignedNotification(task, finalReceiverId, isPreAssigned); notifiedReceiverIds.add(finalReceiverId); log.info("普通任务【{}】已通知设计师: {}", task.getTitle(), finalReceiverId); } // 如果没有指定设计师(发布到大厅),发送群通知 if (notifiedReceiverIds.isEmpty()) { log.info("任务【{}】已发布到任务大厅,准备发送钉钉群通知", task.getTitle()); // 任务发布到大厅,发送钉钉群机器人通知 // 注意:钉钉Markdown换行需要两个空格+换行符 try { String title = "新任务发布"; StringBuilder content = new StringBuilder(); // 基本信息 content.append("**任务名称**:").append(task.getTitle()).append(" \n"); content.append("**任务编号**:").append(task.getTaskNo()).append(" \n"); // 任务详情 if (task.getBrandName() != null && !task.getBrandName().isEmpty()) { content.append("**品牌**:").append(task.getBrandName()).append(" \n"); } if (task.getPlatformName() != null && !task.getPlatformName().isEmpty()) { content.append("**平台**:").append(task.getPlatformName()).append(" \n"); } if (task.getSpuInfo() != null && !task.getSpuInfo().isEmpty()) { content.append("**SPU**:").append(task.getSpuInfo()).append(" \n"); } if (task.getTaskSubType() != null && !task.getTaskSubType().isEmpty()) { content.append("**任务类型**:").append(task.getTaskSubType()).append(" \n"); } content.append(" \n"); // 查询子任务获取任务类型明细 List taskItems = taskItemMapper.selectList( new LambdaQueryWrapper() .eq(TaskItem::getTaskId, task.getId()) .orderByAsc(TaskItem::getGroupNo) ); if (!taskItems.isEmpty()) { content.append("**任务明细**: \n"); for (TaskItem item : taskItems) { // 通过任务类型ID获取接单部门名称 String deptName = "未指定"; if (item.getTaskTypeId() != null) { try { DemandType demandType = configService.getDemandTypeById(item.getTaskTypeId()); if (demandType != null && demandType.getAllowedDeptIds() != null && !demandType.getAllowedDeptIds().isEmpty()) { String[] deptIds = demandType.getAllowedDeptIds().split(","); Long firstDeptId = Long.parseLong(deptIds[0].trim()); Department dept = departmentMapper.selectById(firstDeptId); if (dept != null) { deptName = dept.getDeptName(); } } } catch (Exception e) { log.warn("获取部门名称失败: {}", e.getMessage()); } } // 格式化工时显示 String hoursDisplay = formatEstimatedHours(item.getEstimatedHours()); content.append(" \n"); content.append("▸ **").append(deptName).append("** | ") .append(item.getTotalScore()).append("分 | ") .append(hoursDisplay).append(" \n"); // 解析并显示任务类型明细 if (item.getTypeDetails() != null && !item.getTypeDetails().isEmpty()) { try { @SuppressWarnings("unchecked") List> typeDetails = objectMapper.readValue(item.getTypeDetails(), List.class); for (java.util.Map detail : typeDetails) { String typeName = String.valueOf(detail.get("typeName")); Object qtyObj = detail.get("quantity"); Object scoreObj = detail.get("totalScore"); int qty = qtyObj instanceof Number ? ((Number) qtyObj).intValue() : 1; String score = scoreObj != null ? String.valueOf(scoreObj) : "0"; content.append("  └ ").append(typeName) .append(" ×").append(qty) .append(" = ").append(score).append("分 \n"); } } catch (Exception e) { log.warn("解析任务类型明细失败: {}", e.getMessage()); } } } } else { // 没有子任务时显示总积分和工时 content.append("**任务积分**:").append(task.getScore()).append(" 分 \n"); if (task.getEstimatedHours() != null) { content.append("**预估工时**:").append(formatEstimatedHours(task.getEstimatedHours())).append(" \n"); } } content.append(" \n"); // 发起人信息 if (task.getPublisherName() != null && !task.getPublisherName().isEmpty()) { content.append("**发起人**:").append(task.getPublisherName()).append(" \n"); } if (task.getPublisherDeptName() != null && !task.getPublisherDeptName().isEmpty()) { content.append("**发起部门**:").append(task.getPublisherDeptName()).append(" \n"); } content.append(" \n"); content.append("点击下方按钮查看详情"); log.info("准备调用钉钉机器人发送消息,title={}", title); boolean success = dingTalkService.sendRobotMessage(title, content.toString()); if (success) { log.info("任务【{}】钉钉群通知发送成功", task.getTitle()); } else { log.warn("任务【{}】钉钉群通知发送失败(返回false)", task.getTitle()); } } catch (Exception e) { log.error("发送钉钉群通知异常:taskId={}, error={}", taskId, e.getMessage(), e); // 通知发送失败不影响主流程 } } log.info("接单部门负责人审核通过,任务ID:{},审核人:{},指定接收人:{}", taskId, approverId, assignedReceiverId); } /** * 接单部门负责人审核通过(重载方法,保持向后兼容) */ @Transactional(rollbackFor = Exception.class) public void approveReceivingLevel(Long taskId, Long approverId, TaskApprovalDTO dto) { approveReceivingLevel(taskId, approverId, dto, null); } /** * 接单部门负责人驳回 */ @Transactional(rollbackFor = Exception.class) public void rejectByReceivingLeader(Long taskId, Long approverId, String reason) { Task task = getTaskForApproval(taskId, "pending_receiving_approval"); validateApprover(task, approverId, "receiving_leader"); // 退回给需求部门负责人 Department demandDept = departmentMapper.selectById(task.getPublisherDeptId()); if (demandDept == null || demandDept.getLeaderId() == null) { throw new BusinessException(ResultCode.PARAM_ERROR, "需求部门负责人不存在"); } task.setApprovalStatus("rejected_by_receiving"); task.setStatus("draft"); task.setRejectReason(reason); // 保存驳回原因 task.setCurrentApproverId(demandDept.getLeaderId()); task.setCurrentApproverRole("demand_leader"); taskMapper.updateById(task); logApproval(taskId, approverId, 2, "reject", reason); // 通知需求部门负责人 notificationService.sendNotification( demandDept.getLeaderId(), "任务已被驳回", String.format("任务【%s】被接单部门负责人驳回,原因:%s", task.getTitle(), reason), "task_reject", taskId ); // 如果任务有指定的处理人,发送群通知告知驳回 if (task.getReceiverId() != null) { try { com.designhub.entity.User receiver = userMapper.selectById(task.getReceiverId()); if (receiver != null) { String title = "任务已驳回"; StringBuilder content = new StringBuilder(); content.append("-------------------------------- \n"); content.append(" 【 任 务 已 驳 回 】 \n"); content.append("-------------------------------- \n\n"); content.append("任务名称:").append(task.getTitle()).append(" \n"); content.append("任务编号:").append(task.getTaskNo()).append(" \n"); content.append("原处理人:").append(receiver.getRealName()).append(" \n"); content.append("驳回原因:").append(reason).append(" \n"); content.append(" \n"); content.append("--------------------------------"); java.util.List atMobiles = receiver.getPhone() != null ? java.util.Arrays.asList(receiver.getPhone()) : null; dingTalkService.sendRobotMessage(title, content.toString(), atMobiles); } } catch (Exception e) { log.error("发送任务驳回群通知失败", e); } } log.info("接单部门负责人驳回任务,任务ID:{},审核人:{}", taskId, approverId); } /** * 获取待审核任务列表 * 管理员可以看到所有待审核任务 */ public List getPendingApprovalTasks(Long userId) { // 检查用户是否是管理员 User user = userMapper.selectById(userId); boolean isAdmin = user != null && Integer.valueOf(1).equals(user.getIsAdmin()); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.in(Task::getApprovalStatus, "pending_demand_approval", "pending_receiving_approval"); // 非管理员只能看到分配给自己的待审核任务 if (!isAdmin) { wrapper.eq(Task::getCurrentApproverId, userId); } wrapper.orderByDesc(Task::getCreateTime); List tasks = taskMapper.selectList(wrapper); // 填充关联信息 for (Task task : tasks) { // 填充发起人姓名 if (task.getPublisherId() != null) { User publisher = userMapper.selectById(task.getPublisherId()); if (publisher != null) { task.setPublisherName(publisher.getUsername()); } } // 填充发起部门名称 if (task.getPublisherDeptId() != null) { Department dept = departmentMapper.selectById(task.getPublisherDeptId()); if (dept != null) { task.setPublisherDeptName(dept.getDeptName()); } } // 填充品牌名称 if (task.getBrandId() != null) { Brand brand = brandMapper.selectById(task.getBrandId()); if (brand != null) { // Brand实体类中name字段映射到brand_name,使用getName() task.setBrandName(brand.getName()); } else { log.warn("任务 {} 品牌ID {} 不存在", task.getId(), task.getBrandId()); } } // 填充类目名称 if (task.getCategoryId() != null) { Category category = categoryMapper.selectById(task.getCategoryId()); if (category != null) { // Category实体类中name字段映射到category_name,使用getName() task.setCategoryName(category.getName()); } else { log.warn("任务 {} 类目ID {} 不存在", task.getId(), task.getCategoryId()); } } // 填充平台名称 if (task.getPlatformId() != null) { Platform platform = platformMapper.selectById(task.getPlatformId()); if (platform != null) { // Platform实体类中name字段映射到platform_name,使用getName() task.setPlatformName(platform.getName()); } else { log.warn("任务 {} 平台ID {} 不存在", task.getId(), task.getPlatformId()); } } // 填充当前审核人姓名 if (task.getCurrentApproverId() != null) { User approver = userMapper.selectById(task.getCurrentApproverId()); if (approver != null) { task.setCurrentApproverName(approver.getRealName() != null ? approver.getRealName() : approver.getUsername()); } } } return tasks; } /** * 获取任务并验证审核状态 */ private Task getTaskForApproval(Long taskId, String expectedStatus) { Task task = taskMapper.selectById(taskId); if (task == null) { throw new BusinessException(ResultCode.NOT_FOUND, "任务不存在"); } if (!expectedStatus.equals(task.getApprovalStatus())) { throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "任务当前状态不允许此操作,当前状态:" + task.getApprovalStatus()); } return task; } /** * 验证审核人权限 * 管理员可以代审核所有任务 */ private void validateApprover(Task task, Long approverId, String expectedRole) { // 检查是否是管理员,管理员可以代审核所有任务 User approver = userMapper.selectById(approverId); if (approver != null && Integer.valueOf(1).equals(approver.getIsAdmin())) { log.info("管理员 {} 代审核任务 {}", approver.getRealName(), task.getId()); return; // 管理员绕过权限验证 } if (!approverId.equals(task.getCurrentApproverId())) { throw new BusinessException(ResultCode.FORBIDDEN, "您无权审核此任务"); } if (!expectedRole.equals(task.getCurrentApproverRole())) { throw new BusinessException(ResultCode.FORBIDDEN, "审核角色不匹配"); } } /** * 确定接单部门负责人(支持三级分类继承二级分类的配置) */ private Long determineReceivingLeader(Task task) { // 根据任务类型的allowedDeptIds确定接单部门 // 简化版本:取第一个任务类型的第一个允许部门 String taskTypeIds = task.getTaskTypeIds(); if (taskTypeIds == null || taskTypeIds.isEmpty()) { return null; } String[] typeIdArray = taskTypeIds.split(","); for (String typeIdStr : typeIdArray) { try { Long typeId = Long.parseLong(typeIdStr.trim()); DemandType demandType = configService.getDemandTypeById(typeId); if (demandType != null) { // 获取允许的部门ID(支持继承) String allowedDeptIds = demandType.getAllowedDeptIds(); if ((allowedDeptIds == null || allowedDeptIds.isEmpty()) && demandType.getParentId() != null) { // 三级分类没有配置,尝试获取父级(二级分类)的配置 DemandType parentType = configService.getDemandTypeById(demandType.getParentId()); if (parentType != null) { allowedDeptIds = parentType.getAllowedDeptIds(); } } if (allowedDeptIds != null && !allowedDeptIds.isEmpty()) { String[] deptIds = allowedDeptIds.split(","); if (deptIds.length > 0) { Long deptId = Long.parseLong(deptIds[0].trim()); Department dept = departmentMapper.selectById(deptId); if (dept != null && dept.getLeaderId() != null) { return dept.getLeaderId(); } } } } } catch (Exception e) { log.error("解析任务类型或部门ID失败", e); } } return null; } /** * 应用任务更新 */ private void applyTaskUpdates(Task task, TaskUpdateDTO updates) { if (updates.getTitle() != null) { task.setTitle(updates.getTitle()); } if (updates.getTaskSubType() != null) { task.setTaskSubType(updates.getTaskSubType()); } if (updates.getBrandId() != null) { task.setBrandId(updates.getBrandId()); } if (updates.getPlatformId() != null) { task.setPlatformId(updates.getPlatformId()); } if (updates.getCategoryId() != null) { task.setCategoryId(updates.getCategoryId()); } if (updates.getSpuInfo() != null) { task.setSpuInfo(updates.getSpuInfo()); } if (updates.getRemark() != null) { task.setRemark(updates.getRemark()); } if (updates.getAttachmentUrl() != null) { task.setAttachmentUrl(updates.getAttachmentUrl()); } // 如果修改了任务类型和数量,需要重新计算积分 if (updates.getTaskTypes() != null && !updates.getTaskTypes().isEmpty()) { String taskTypeIds = updates.getTaskTypes().stream() .map(t -> String.valueOf(t.getTaskTypeId())) .collect(Collectors.joining(",")); task.setTaskTypeIds(taskTypeIds); // 如果只有一个任务类型,更新quantity字段 if (updates.getTaskTypes().size() == 1) { task.setQuantity(updates.getTaskTypes().get(0).getQuantity()); } // 重新计算积分 int totalScore = taskService.calculateTotalScore(updates.getTaskTypes()); task.setScore(totalScore); } } /** * 拆分为流水线子任务 */ private void splitIntoPipelineSubTasks(Task task) { try { // 构建流水线任务DTO PipelineTaskDTO dto = new PipelineTaskDTO(); dto.setTitle(task.getTitle()); dto.setTaskSubType(task.getTaskSubType()); dto.setBrandId(task.getBrandId()); dto.setPlatformId(task.getPlatformId()); dto.setCategoryId(task.getCategoryId()); dto.setSpuInfo(task.getSpuInfo()); dto.setRemark(task.getRemark()); dto.setAttachmentUrl(task.getAttachmentUrl()); dto.setPipelineMode(task.getPipelineMode()); dto.setReleaseType(task.getReleaseType()); // 传递发布类型 // 解析任务类型 String[] taskTypeIds = task.getTaskTypeIds().split(","); List items = new java.util.ArrayList<>(); // 简化版本:所有任务类型使用相同的数量(task.quantity) int quantity = task.getQuantity() != null ? task.getQuantity() : 1; for (String taskTypeIdStr : taskTypeIds) { PipelineTaskDTO.TaskItemDTO item = new PipelineTaskDTO.TaskItemDTO(); item.setTaskTypeId(Long.parseLong(taskTypeIdStr.trim())); item.setQuantity(quantity); items.add(item); } dto.setItems(items); // 调用TaskService创建流水线子任务 taskService.createPipelineTaskItems(task, dto); // 根据发布类型设置主任务状态 if ("recruitment".equals(task.getReleaseType())) { // recruitment模式:发布到任务大厅 task.setStatus("pending"); } else if ("assigned".equals(task.getReleaseType()) && task.getReceiverId() != null) { // assigned模式:如果指定了接收人,主任务直接进入进行中状态 task.setStatus("in_progress"); LocalDateTime now = LocalDateTime.now(); task.setStartTime(now); task.setActualStartTime(now); // 重新计算deadline:从接单时间开始计算(使用工作时间计算) if (task.getEstimatedHours() != null && task.getEstimatedHours().compareTo(java.math.BigDecimal.ZERO) > 0) { task.setDeadline(workHoursCalculator.calculateDeadline(now, task.getEstimatedHours())); } // 更新子任务状态为in_progress(第一组或全部,取决于流水线模式) taskService.updatePipelineSubTasksForAssigned(task.getId(), dto.getPipelineMode()); log.info("指派流水线任务已设置为进行中:taskId={}, receiverId={}", task.getId(), task.getReceiverId()); } else { // default模式或其他模式:流水线任务的主任务状态设置为pending // 子任务会根据is_locked字段控制可见性 task.setStatus("pending"); } } catch (Exception e) { log.error("拆分流水线任务失败", e); throw new BusinessException(ResultCode.ERROR, "拆分流水线任务失败:" + e.getMessage()); } } /** * 记录审核日志 */ private void logApproval(Long taskId, Long approverId, Integer level, String action, String comment) { try { User approver = userMapper.selectById(approverId); TaskApprovalLog log = new TaskApprovalLog(); log.setTaskId(taskId); log.setApprovalLevel(level); log.setApproverId(approverId); log.setApproverName(approver != null ? approver.getRealName() : null); log.setAction(action); log.setComment(comment); log.setCreateTime(LocalDateTime.now()); approvalLogMapper.insert(log); } catch (Exception e) { this.log.error("记录审核日志失败", e); } } /** * 格式化预估工时显示(基于工作时间配置) */ private String formatEstimatedHours(java.math.BigDecimal hours) { if (hours == null) { return "未设置"; } String formatted = workHoursCalculator.formatWorkHours(hours); // WorkHoursCalculator 返回 "--" 表示无效,这里保持 "未设置" 的语义 return "--".equals(formatted) ? "未设置" : formatted; } /** * 发送任务指派通知给设计师 * @param task 任务 * @param receiverId 接收人ID * @param isPreAssigned 是否预先指定 */ private void sendTaskAssignedNotification(Task task, Long receiverId, boolean isPreAssigned) { try { com.designhub.entity.User receiver = userMapper.selectById(receiverId); if (receiver == null) { log.warn("接收人不存在: {}", receiverId); return; } String receiverName = receiver.getRealName(); String receiverPhone = receiver.getPhone(); // 构建通知内容 StringBuilder content = new StringBuilder(); // 基本信息 content.append("**任务名称**:").append(task.getTitle()).append(" \n"); content.append("**任务编号**:").append(task.getTaskNo()).append(" \n"); // 任务详情 if (task.getBrandName() != null && !task.getBrandName().isEmpty()) { content.append("**品牌**:").append(task.getBrandName()).append(" \n"); } if (task.getPlatformName() != null && !task.getPlatformName().isEmpty()) { content.append("**平台**:").append(task.getPlatformName()).append(" \n"); } if (task.getSpuInfo() != null && !task.getSpuInfo().isEmpty()) { content.append("**SPU**:").append(task.getSpuInfo()).append(" \n"); } if (task.getTaskSubType() != null && !task.getTaskSubType().isEmpty()) { content.append("**任务类型**:").append(task.getTaskSubType()).append(" \n"); } content.append(" \n"); // 查询该设计师负责的子任务 List receiverItems = taskItemMapper.selectList( new LambdaQueryWrapper() .eq(TaskItem::getTaskId, task.getId()) .eq(TaskItem::getReceiverId, receiverId) .orderByAsc(TaskItem::getGroupNo) ); if (!receiverItems.isEmpty()) { content.append("**您负责的任务明细**: \n"); for (TaskItem item : receiverItems) { // 通过任务类型ID获取接单部门名称 String deptName = "未指定"; if (item.getTaskTypeId() != null) { try { DemandType demandType = configService.getDemandTypeById(item.getTaskTypeId()); if (demandType != null && demandType.getAllowedDeptIds() != null && !demandType.getAllowedDeptIds().isEmpty()) { String[] deptIds = demandType.getAllowedDeptIds().split(","); Long firstDeptId = Long.parseLong(deptIds[0].trim()); Department dept = departmentMapper.selectById(firstDeptId); if (dept != null) { deptName = dept.getDeptName(); } } } catch (Exception e) { log.warn("获取部门名称失败: {}", e.getMessage()); } } // 格式化工时显示 String hoursDisplay = formatEstimatedHours(item.getEstimatedHours()); content.append(" \n"); content.append("▸ **").append(deptName).append("** | ") .append(item.getTotalScore()).append("分 | ") .append(hoursDisplay).append(" \n"); // 解析并显示任务类型明细 if (item.getTypeDetails() != null && !item.getTypeDetails().isEmpty()) { try { @SuppressWarnings("unchecked") List> typeDetails = objectMapper.readValue(item.getTypeDetails(), List.class); for (java.util.Map detail : typeDetails) { String typeName = String.valueOf(detail.get("typeName")); Object qtyObj = detail.get("quantity"); Object scoreObj = detail.get("totalScore"); int qty = qtyObj instanceof Number ? ((Number) qtyObj).intValue() : 1; String score = scoreObj != null ? String.valueOf(scoreObj) : "0"; content.append("  └ ").append(typeName) .append(" ×").append(qty) .append(" = ").append(score).append("分 \n"); } } catch (Exception e) { log.warn("解析任务类型明细失败: {}", e.getMessage()); } } } } else { // 没有子任务时显示总积分和工时 content.append("**任务积分**:").append(task.getScore()).append(" 分 \n"); if (task.getEstimatedHours() != null) { content.append("**预估工时**:").append(formatEstimatedHours(task.getEstimatedHours())).append(" \n"); } } content.append(" \n"); // 发起人信息 if (task.getPublisherName() != null && !task.getPublisherName().isEmpty()) { content.append("**发起人**:").append(task.getPublisherName()).append(" \n"); } if (task.getPublisherDeptName() != null && !task.getPublisherDeptName().isEmpty()) { content.append("**发起部门**:").append(task.getPublisherDeptName()).append(" \n"); } // 指派信息 content.append("**指派给**:").append(receiverName).append(" \n"); content.append(" \n"); content.append("点击下方按钮查看详情"); // 发送个人钉钉通知给设计师 Notification notification = new Notification(); notification.setReceiverId(receiverId); notification.setTitle("任务已指派"); notification.setContent(content.toString()); notification.setNotificationType("TASK_ASSIGNED"); notification.setRelatedType("task"); notification.setRelatedId(task.getId()); notification.setPushDingtalk(1); // 推送到钉钉 notificationService.sendNotification(notification); log.info("任务指派个人通知已发送给设计师: userId={}, name={}", receiverId, receiverName); // 只有非预先指定的任务才发送群通知(预先指定的任务只发送个人通知) if (!isPreAssigned) { java.util.List atMobiles = receiverPhone != null ? java.util.Arrays.asList(receiverPhone) : null; dingTalkService.sendRobotMessage("任务已指派", content.toString(), atMobiles); log.info("任务指派群通知已发送,@手机号: {}", receiverPhone); } else { log.info("任务【{}】为预先指定任务,只发送个人通知,不发送群通知", task.getTitle()); } } catch (Exception e) { log.error("发送任务指派通知失败: receiverId={}", receiverId, e); } } }