package com.designhub.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.designhub.common.ResultCode; import com.designhub.entity.Department; import com.designhub.entity.Task; import com.designhub.entity.TaskTransferLog; import com.designhub.entity.User; import com.designhub.exception.BusinessException; import com.designhub.mapper.TaskTransferLogMapper; import com.designhub.mapper.TaskMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import java.math.BigDecimal; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.HashMap; import java.util.List; import java.util.Map; import com.designhub.entity.TaskItem; import com.designhub.mapper.TaskItemMapper; import com.designhub.util.WorkHoursCalculator; /** * 管理员任务调配Service */ @Slf4j @Service @RequiredArgsConstructor public class AdminTaskService { private final TaskMapper taskMapper; private final TaskItemMapper taskItemMapper; private final NotificationService notificationService; private final UserService userService; private final DepartmentService departmentService; private final RoleService roleService; private final WorkHoursCalculator workHoursCalculator; private final TaskTransferLogMapper taskTransferLogMapper; private final TaskUnlockService taskUnlockService; /** * 应急解锁排队任务到大厅(支持主任务编号/子任务编号) */ @Transactional(rollbackFor = Exception.class) public Map emergencyUnlockQueuedTaskToHall(String taskNoOrItemNo, Long adminId, String reason) { if (!isAdminUser(adminId)) { throw new BusinessException(ResultCode.FORBIDDEN, "只有管理员可以解锁排队任务"); } if (!StringUtils.hasText(taskNoOrItemNo)) { throw new BusinessException(ResultCode.PARAM_ERROR, "任务编号不能为空"); } Map unlockResult = taskUnlockService.emergencyUnlockToHall(taskNoOrItemNo.trim()); log.info("管理员应急解锁到大厅: adminId={}, key={}, reason={}, result={}", adminId, taskNoOrItemNo, StringUtils.hasText(reason) ? reason.trim() : "", unlockResult ); return unlockResult; } /** * 管理员分配任务 */ @Transactional(rollbackFor = Exception.class) public Task assignTask(Long taskId, Long designerId, Long adminId) { // 验证管理员权限 if (!isAdminUser(adminId)) { throw new BusinessException(ResultCode.FORBIDDEN, "只有管理员可以分配任务"); } // 验证设计师 User designer = userService.getUserById(designerId); if (designer == null) { throw new BusinessException(ResultCode.PARAM_ERROR, "设计师不存在"); } // 验证设计师部门类型(必须是接单部门) if (designer.getDeptId() != null) { Department dept = departmentService.getDepartmentById(designer.getDeptId()); if (dept == null || !"receiving".equals(dept.getDeptType())) { throw new BusinessException(ResultCode.FORBIDDEN, "只能分配给接单部门的设计师"); } } Task task = taskMapper.selectById(taskId); if (task == null) { throw new BusinessException(ResultCode.TASK_NOT_EXIST); } if (!"pending".equals(task.getStatus()) && !"recruitment".equals(task.getReleaseType())) { throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "只能分配待接单的任务"); } // 验证任务需求类型是否允许该部门接单 if (task.getDemandTypeId() != null && designer.getDeptId() != null) { Department dept = departmentService.getDepartmentById(designer.getDeptId()); if (dept != null && dept.getAllowedDemandTypes() != null && !dept.getAllowedDemandTypes().isEmpty()) { String[] allowedTypes = dept.getAllowedDemandTypes().split(","); boolean allowed = false; for (String typeId : allowedTypes) { if (task.getDemandTypeId().toString().equals(typeId.trim())) { allowed = true; break; } } if (!allowed) { throw new BusinessException(ResultCode.FORBIDDEN, "该设计师所在部门不支持此需求类型"); } } } // 记录原接收人(如果有) Long originalReceiverId = task.getReceiverId(); // 分配任务 task.setReceiverId(designerId); task.setReceiverDeptId(designer.getDeptId()); task.setStatus("in_progress"); task.setStartTime(LocalDateTime.now()); task.setActualStartTime(LocalDateTime.now()); // 重新计算截止时间 if (task.getEstimatedHours() != null) { int hours = task.getEstimatedHours().intValue(); if (hours > 0) { task.setDeadline(LocalDateTime.now().plusHours(hours)); } } else if (task.getEstimatedDuration() != null) { task.setDeadline(LocalDateTime.now().plusHours(task.getEstimatedDuration())); } task.setUpdateTime(LocalDateTime.now()); taskMapper.updateById(task); // 记录操作日志 log.info("管理员分配任务: taskId={}, designerId={}, adminId={}", taskId, designerId, adminId); // 发送通知给设计师 notificationService.sendTaskNotification( designerId, taskId, "📋 新任务分配", "管理员为您分配了新任务:" + task.getTitle() ); // 如果有原接收人,通知他任务已被重新分配 if (originalReceiverId != null && !originalReceiverId.equals(designerId)) { notificationService.sendTaskNotification( originalReceiverId, taskId, "⚠️ 任务重新分配", "您的任务已被管理员重新分配" ); } return task; } /** * 管理员转移任务 */ @Transactional(rollbackFor = Exception.class) public Task transferTask(Long taskId, Long newDesignerId, Long adminId, String reason) { // 验证管理员权限 if (!isAdminUser(adminId)) { throw new BusinessException(ResultCode.FORBIDDEN, "只有管理员可以转移任务"); } // 验证新设计师 User newDesigner = userService.getUserById(newDesignerId); if (newDesigner == null) { throw new BusinessException(ResultCode.PARAM_ERROR, "新设计师不存在"); } // 验证新设计师部门类型 if (newDesigner.getDeptId() != null) { Department dept = departmentService.getDepartmentById(newDesigner.getDeptId()); if (dept == null || !"receiving".equals(dept.getDeptType())) { throw new BusinessException(ResultCode.FORBIDDEN, "只能转移给接单部门的设计师"); } } Task task = taskMapper.selectById(taskId); if (task == null) { throw new BusinessException(ResultCode.TASK_NOT_EXIST); } if (!"in_progress".equals(task.getStatus()) && !"review".equals(task.getStatus())) { throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "只能转移进行中或待审核的任务"); } Long originalDesignerId = task.getReceiverId(); if (originalDesignerId == null) { throw new BusinessException("任务没有接收人"); } if (originalDesignerId.equals(newDesignerId)) { throw new BusinessException("新接收人与原接收人相同"); } // 计算已用时长 LocalDateTime startTime = task.getActualStartTime(); if (startTime != null) { long hoursUsed = ChronoUnit.HOURS.between(startTime, LocalDateTime.now()); // 检查是否已延误 LocalDateTime originalDeadline = task.getDeadline(); if (originalDeadline != null && LocalDateTime.now().isAfter(originalDeadline)) { // 原设计师已延误,记录延误信息 long delayHours = ChronoUnit.HOURS.between(originalDeadline, LocalDateTime.now()); log.warn("任务转移时发现延误: taskId={}, originalDesignerId={}, delayHours={}", taskId, originalDesignerId, delayHours); // TODO: 记录延误扣分信息 // TODO: 更新原设计师的延误统计 } } // 转移任务 task.setReceiverId(newDesignerId); task.setReceiverDeptId(newDesigner.getDeptId()); task.setActualStartTime(LocalDateTime.now()); // 重新开始计时 // 如果任务状态是 review,转回 in_progress if ("review".equals(task.getStatus())) { task.setStatus("in_progress"); } // 重新计算截止时间 if (task.getEstimatedHours() != null) { int hours = task.getEstimatedHours().intValue(); if (hours > 0) { task.setDeadline(LocalDateTime.now().plusHours(hours)); } } else if (task.getEstimatedDuration() != null) { task.setDeadline(LocalDateTime.now().plusHours(task.getEstimatedDuration())); } task.setUpdateTime(LocalDateTime.now()); // 在备注中记录转移信息 String transferNote = String.format("[%s] 管理员转移任务:从设计师%d转移到设计师%d,原因:%s", LocalDateTime.now().toString(), originalDesignerId, newDesignerId, reason); String existingRemark = task.getRemark(); task.setRemark(existingRemark != null ? existingRemark + "\n" + transferNote : transferNote); taskMapper.updateById(task); log.info("管理员转移任务: taskId={}, from={}, to={}, adminId={}, reason={}", taskId, originalDesignerId, newDesignerId, adminId, reason); // 通知新设计师 notificationService.sendTaskNotification( newDesignerId, taskId, "📋 新任务转移", "管理员为您转移了任务:" + task.getTitle() + ",原因:" + reason ); // 通知原设计师 notificationService.sendTaskNotification( originalDesignerId, taskId, "⚠️ 任务已转移", "您的任务已被管理员转移,原因:" + reason ); return task; } /** * 批量分配任务 */ @Transactional(rollbackFor = Exception.class) public int batchAssignTasks(List taskIds, Long designerId, Long adminId) { int successCount = 0; for (Long taskId : taskIds) { try { assignTask(taskId, designerId, adminId); successCount++; } catch (Exception e) { log.error("批量分配任务失败: taskId={}, error={}", taskId, e.getMessage()); } } log.info("批量分配任务完成: total={}, success={}, adminId={}", taskIds.size(), successCount, adminId); return successCount; } /** * 调整任务优先级 */ @Transactional(rollbackFor = Exception.class) public Task adjustTaskPriority(Long taskId, Integer newPriority, Long adminId) { Task task = taskMapper.selectById(taskId); if (task == null) { throw new BusinessException("任务不存在"); } Integer oldPriority = task.getPriority(); task.setPriority(newPriority); task.setUpdateTime(LocalDateTime.now()); taskMapper.updateById(task); log.info("管理员调整任务优先级: taskId={}, old={}, new={}, adminId={}", taskId, oldPriority, newPriority, adminId); // 如果任务有接收人,发送通知 if (task.getReceiverId() != null) { String priorityText = getPriorityText(newPriority); notificationService.sendTaskNotification( task.getReceiverId(), taskId, "⚡ 任务优先级调整", "任务优先级已调整为:" + priorityText ); } return task; } /** * 获取任务池列表(待分配的任务) */ public List getTaskPool() { return taskMapper.selectList( new LambdaQueryWrapper() .in(Task::getStatus, "pending", "pool") .orderByDesc(Task::getCreateTime) ); } /** * 获取所有进行中的任务 */ public List getInProgressTasks() { return taskMapper.selectList( new LambdaQueryWrapper() .eq(Task::getStatus, "in_progress") .orderByAsc(Task::getDeadline) ); } /** * 强制完成任务(管理员操作) */ @Transactional(rollbackFor = Exception.class) public Task forceCompleteTask(Long taskId, Long adminId, String reason) { Task task = taskMapper.selectById(taskId); if (task == null) { throw new BusinessException("任务不存在"); } task.setStatus("completed"); task.setActualEndTime(LocalDateTime.now()); task.setUpdateTime(LocalDateTime.now()); String completeNote = String.format("[%s] 管理员强制完成,原因:%s", LocalDateTime.now().toString(), reason); String existingRemark = task.getRemark(); task.setRemark(existingRemark != null ? existingRemark + "\n" + completeNote : completeNote); taskMapper.updateById(task); log.info("管理员强制完成任务: taskId={}, adminId={}, reason={}", taskId, adminId, reason); // 通知相关人员 if (task.getReceiverId() != null) { notificationService.sendTaskNotification( task.getReceiverId(), taskId, "✅ 任务已完成", "管理员已将您的任务标记为完成,原因:" + reason ); } return task; } /** * 取消任务(管理员操作) */ @Transactional(rollbackFor = Exception.class) public Task cancelTask(Long taskId, Long adminId, String reason) { Task task = taskMapper.selectById(taskId); if (task == null) { throw new BusinessException("任务不存在"); } task.setStatus("cancelled"); task.setUpdateTime(LocalDateTime.now()); String cancelNote = String.format("[%s] 管理员取消任务,原因:%s", LocalDateTime.now().toString(), reason); String existingRemark = task.getRemark(); task.setRemark(existingRemark != null ? existingRemark + "\n" + cancelNote : cancelNote); taskMapper.updateById(task); log.info("管理员取消任务: taskId={}, adminId={}, reason={}", taskId, adminId, reason); // 通知相关人员 if (task.getReceiverId() != null) { notificationService.sendTaskNotification( task.getReceiverId(), taskId, "❌ 任务已取消", "管理员已取消该任务,原因:" + reason ); } if (task.getPublisherId() != null) { notificationService.sendTaskNotification( task.getPublisherId(), taskId, "❌ 任务已取消", "您发布的任务已被取消,原因:" + reason ); } return task; } /** * 管理员将子任务直接切换到指定接单部门可接单 */ @Transactional(rollbackFor = Exception.class) public Map redirectTaskItemToDept(String itemNo, Long targetDeptId, Long adminId, String reason) { if (!isAdminUser(adminId)) { throw new BusinessException(ResultCode.FORBIDDEN, "只有管理员可以操作任务节点调拨"); } if (!StringUtils.hasText(itemNo)) { throw new BusinessException(ResultCode.PARAM_ERROR, "子任务编号不能为空"); } if (targetDeptId == null) { throw new BusinessException(ResultCode.PARAM_ERROR, "目标部门不能为空"); } TaskItem item = taskItemMapper.selectOne( new LambdaQueryWrapper() .eq(TaskItem::getItemNo, itemNo.trim()) .last("LIMIT 1") ); if (item == null) { throw new BusinessException(ResultCode.NOT_FOUND, "子任务不存在"); } Task task = taskMapper.selectById(item.getTaskId()); if (task == null || task.getDeleted() != null && task.getDeleted() == 1) { throw new BusinessException(ResultCode.NOT_FOUND, "主任务不存在"); } Department targetDept = departmentService.getDepartmentById(targetDeptId); if (targetDept == null) { throw new BusinessException(ResultCode.PARAM_ERROR, "目标部门不存在"); } if (targetDept.getStatus() == null || targetDept.getStatus() != 1) { throw new BusinessException(ResultCode.PARAM_ERROR, "目标部门已禁用"); } if (!"receiving".equals(targetDept.getDeptType())) { throw new BusinessException(ResultCode.PARAM_ERROR, "目标部门必须是接单部门"); } if ("completed".equals(item.getStatus())) { throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "已完成子任务不允许调拨"); } if ("in_progress".equals(item.getStatus())) { throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "进行中子任务请先转移接单人"); } Long oldDeptId = item.getReceiverDeptId(); Long oldReceiverId = item.getReceiverId(); LocalDateTime oldDeadline = item.getDeadline(); String oldStatus = item.getStatus(); item.setStatus("pending"); item.setReceiverDeptId(targetDeptId); item.setReceiverId(null); item.setReceiverName(null); item.setClaimTime(null); item.setDeadline(null); item.setIsLocked(0); item.setUpdateTime(LocalDateTime.now()); taskItemMapper.updateById(item); // 排队记录重建到新部门,确保可见且可抢 taskUnlockService.deleteUnlockStatus(item.getId()); if (taskUnlockService.isUnlockEnabled() && !"assigned".equals(task.getReleaseType())) { taskUnlockService.onPipelineUnlock(item, targetDeptId); } String transferReason = StringUtils.hasText(reason) ? reason.trim() : "管理员手动调拨子任务到指定接单部门"; TaskTransferLog transferLog = new TaskTransferLog(); transferLog.setTaskId(task.getId()); transferLog.setTransferType("transfer"); transferLog.setFromUserId(oldReceiverId); transferLog.setToUserId(null); transferLog.setOperatorId(adminId); transferLog.setOldDeadline(oldDeadline); transferLog.setNewDeadline(null); transferLog.setOldPriority(task.getPriority()); transferLog.setNewPriority(task.getPriority()); transferLog.setReason(transferReason); transferLog.setRemark(String.format("节点调拨:%s(%s)->%s(%s), itemNo=%s, oldStatus=%s", oldDeptId == null ? "-" : oldDeptId, resolveDeptName(oldDeptId), targetDeptId, targetDept.getDeptName(), item.getItemNo(), oldStatus )); transferLog.setCreateTime(LocalDateTime.now()); taskTransferLogMapper.insert(transferLog); Map result = new HashMap<>(); result.put("taskId", task.getId()); result.put("taskNo", task.getTaskNo()); result.put("itemId", item.getId()); result.put("itemNo", item.getItemNo()); result.put("oldStatus", oldStatus); result.put("newStatus", item.getStatus()); result.put("oldDeptId", oldDeptId); result.put("oldDeptName", resolveDeptName(oldDeptId)); result.put("newDeptId", targetDeptId); result.put("newDeptName", targetDept.getDeptName()); return result; } private String resolveDeptName(Long deptId) { if (deptId == null) { return ""; } Department department = departmentService.getDepartmentById(deptId); return department != null ? department.getDeptName() : ""; } /** * 获取优先级文本 */ private String getPriorityText(Integer priority) { if (priority == null) return "未设置"; return switch (priority) { case 0 -> "低"; case 1 -> "普通"; case 2 -> "高"; case 3 -> "紧急"; default -> String.valueOf(priority); }; } /** * 检查用户是否是管理员(ADMIN或SUPER_ADMIN) */ private boolean isAdminUser(Long userId) { try { 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; } } /** * 批量更新低时效任务的截止时间(应用最低1小时时效) * @return 更新结果统计 */ @Transactional(rollbackFor = Exception.class) public Map batchUpdateLowHoursDeadlines() { Map result = new HashMap<>(); int taskUpdated = 0; int itemUpdated = 0; BigDecimal minHours = BigDecimal.ONE; // 最低1小时 // 1. 更新主任务的截止时间 LambdaQueryWrapper taskWrapper = new LambdaQueryWrapper() .in(Task::getStatus, "pending", "in_progress") .eq(Task::getDeleted, 0) .lt(Task::getEstimatedHours, minHours) .gt(Task::getEstimatedHours, BigDecimal.ZERO) .isNotNull(Task::getStartTime); // 只更新已接单的任务 List lowHoursTasks = taskMapper.selectList(taskWrapper); log.info("找到 {} 个低时效主任务需要更新", lowHoursTasks.size()); for (Task task : lowHoursTasks) { try { // 使用接单时间计算新的截止时间(应用最低1小时) LocalDateTime newDeadline = workHoursCalculator.calculateDeadline( task.getStartTime(), minHours ); if (newDeadline != null && !newDeadline.equals(task.getDeadline())) { log.info("更新任务截止时间: taskNo={}, 原时效={}小时, 原截止={}, 新截止={}", task.getTaskNo(), task.getEstimatedHours(), task.getDeadline(), newDeadline); task.setDeadline(newDeadline); task.setUpdateTime(LocalDateTime.now()); taskMapper.updateById(task); taskUpdated++; } } catch (Exception e) { log.error("更新任务截止时间失败: taskId={}, error={}", task.getId(), e.getMessage()); } } // 2. 更新子任务(TaskItem)的截止时间 LambdaQueryWrapper itemWrapper = new LambdaQueryWrapper() .in(TaskItem::getStatus, "pending", "in_progress") .lt(TaskItem::getEstimatedHours, minHours) .gt(TaskItem::getEstimatedHours, BigDecimal.ZERO) .isNotNull(TaskItem::getReceiverId); // 只更新已接单的子任务 List lowHoursItems = taskItemMapper.selectList(itemWrapper); log.info("找到 {} 个低时效子任务需要更新", lowHoursItems.size()); for (TaskItem item : lowHoursItems) { try { // 子任务使用 update_time 作为接单时间(如果没有专门的接单时间字段) LocalDateTime startTime = item.getUpdateTime(); if (startTime == null) { startTime = item.getCreateTime(); } LocalDateTime newDeadline = workHoursCalculator.calculateDeadline( startTime, minHours ); if (newDeadline != null && !newDeadline.equals(item.getDeadline())) { log.info("更新子任务截止时间: itemNo={}, 原时效={}小时, 原截止={}, 新截止={}", item.getItemNo(), item.getEstimatedHours(), item.getDeadline(), newDeadline); item.setDeadline(newDeadline); item.setUpdateTime(LocalDateTime.now()); taskItemMapper.updateById(item); itemUpdated++; } } catch (Exception e) { log.error("更新子任务截止时间失败: itemId={}, error={}", item.getId(), e.getMessage()); } } result.put("tasksFound", lowHoursTasks.size()); result.put("tasksUpdated", taskUpdated); result.put("itemsFound", lowHoursItems.size()); result.put("itemsUpdated", itemUpdated); log.info("批量更新低时效任务截止时间完成: {}", result); return result; } }