2026-03-12 10:26:37 +08:00

660 lines
26 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<String, Object> 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<String, Object> 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<Long> 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<Task> getTaskPool() {
return taskMapper.selectList(
new LambdaQueryWrapper<Task>()
.in(Task::getStatus, "pending", "pool")
.orderByDesc(Task::getCreateTime)
);
}
/**
* 获取所有进行中的任务
*/
public List<Task> getInProgressTasks() {
return taskMapper.selectList(
new LambdaQueryWrapper<Task>()
.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<String, Object> 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<TaskItem>()
.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<String, Object> 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<String, Integer> batchUpdateLowHoursDeadlines() {
Map<String, Integer> result = new HashMap<>();
int taskUpdated = 0;
int itemUpdated = 0;
BigDecimal minHours = BigDecimal.ONE; // 最低1小时
// 1. 更新主任务的截止时间
LambdaQueryWrapper<Task> taskWrapper = new LambdaQueryWrapper<Task>()
.in(Task::getStatus, "pending", "in_progress")
.eq(Task::getDeleted, 0)
.lt(Task::getEstimatedHours, minHours)
.gt(Task::getEstimatedHours, BigDecimal.ZERO)
.isNotNull(Task::getStartTime); // 只更新已接单的任务
List<Task> 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<TaskItem> itemWrapper = new LambdaQueryWrapper<TaskItem>()
.in(TaskItem::getStatus, "pending", "in_progress")
.lt(TaskItem::getEstimatedHours, minHours)
.gt(TaskItem::getEstimatedHours, BigDecimal.ZERO)
.isNotNull(TaskItem::getReceiverId); // 只更新已接单的子任务
List<TaskItem> 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;
}
}