diff --git a/backend/src/main/java/com/designhub/controller/TaskItemController.java b/backend/src/main/java/com/designhub/controller/TaskItemController.java index 9a28174..a6cfe03 100644 --- a/backend/src/main/java/com/designhub/controller/TaskItemController.java +++ b/backend/src/main/java/com/designhub/controller/TaskItemController.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.designhub.common.Result; import com.designhub.entity.TaskItem; import com.designhub.service.TaskItemService; +import com.designhub.mapper.UserMapper; import com.designhub.util.JwtUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -24,6 +25,7 @@ import java.util.Map; public class TaskItemController { private final TaskItemService taskItemService; + private final UserMapper userMapper; private final JwtUtil jwtUtil; /** @@ -171,4 +173,50 @@ public class TaskItemController { Map progress = taskItemService.getTaskProgress(taskId); return Result.success(progress); } + + /** + * 审核子任务组(接单部门负责人) + */ + @Operation(summary = "审核子任务组") + @PostMapping("/groups/approve") + public Result approveTaskItemGroup( + @RequestBody Map request, + @RequestHeader("Authorization") String token) { + + String actualToken = token.replace("Bearer ", ""); + Long approverId = jwtUtil.getUserIdFromToken(actualToken); + + Long taskId = Long.valueOf(request.get("taskId").toString()); + Integer groupNo = Integer.valueOf(request.get("groupNo").toString()); + Long assignedReceiverId = request.get("assignedReceiverId") != null ? + Long.valueOf(request.get("assignedReceiverId").toString()) : null; + + taskItemService.approveTaskItemGroup(taskId, groupNo, approverId, assignedReceiverId); + return Result.success(assignedReceiverId != null ? "子任务组已指派" : "子任务组已发布到大厅"); + } + + /** + * 获取待审核的子任务组列表 + */ + @Operation(summary = "获取待审核的子任务组列表") + @GetMapping("/groups/pending-approval") + public Result>> getPendingApprovalGroups( + @Parameter(description = "部门ID") @RequestParam(required = false) Long deptId, + @RequestHeader("Authorization") String token) { + + String actualToken = token.replace("Bearer ", ""); + Long userId = jwtUtil.getUserIdFromToken(actualToken); + + // 如果没有提供部门ID,从用户信息中获取 + if (deptId == null) { + com.designhub.entity.User user = userMapper.selectById(userId); + if (user == null || user.getDeptId() == null) { + return Result.success(new java.util.ArrayList<>()); + } + deptId = user.getDeptId(); + } + + List> groups = taskItemService.getPendingApprovalGroups(deptId); + return Result.success(groups); + } } diff --git a/backend/src/main/java/com/designhub/service/TaskItemService.java b/backend/src/main/java/com/designhub/service/TaskItemService.java index 62efa35..d2b1bd7 100644 --- a/backend/src/main/java/com/designhub/service/TaskItemService.java +++ b/backend/src/main/java/com/designhub/service/TaskItemService.java @@ -10,6 +10,7 @@ import com.designhub.entity.Notification; import com.designhub.exception.BusinessException; import com.designhub.mapper.TaskItemMapper; import com.designhub.mapper.TaskMapper; +import com.designhub.mapper.DepartmentMapper; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -29,6 +30,7 @@ public class TaskItemService { private final TaskItemMapper taskItemMapper; private final TaskMapper taskMapper; + private final DepartmentMapper departmentMapper; private final UserService userService; private final NotificationService notificationService; @@ -250,31 +252,41 @@ public class TaskItemService { ); if (!nextGroupItems.isEmpty()) { - // 批量解锁下一组的所有子任务 + // 批量解锁下一组的所有子任务,状态设为待审核 for (TaskItem nextItem : nextGroupItems) { - nextItem.setStatus("pending"); + nextItem.setStatus("pending_approval"); // 待接单部门负责人审核 nextItem.setIsLocked(0); nextItem.setUpdateTime(LocalDateTime.now()); taskItemMapper.updateById(nextItem); - log.info("✅ 解锁子任务: {} (组号: {}, ID: {})", + log.info("✅ 解锁子任务: {} (组号: {}, ID: {}),待接单部门负责人审核", nextItem.getItemNo(), nextGroupNo, nextItem.getId()); } result.put("unlockedGroupNo", nextGroupNo); result.put("unlockedCount", nextGroupItems.size()); - // 发送通知:新的子任务组已开放 - Notification notification = new Notification(); - notification.setNotificationType("task"); - notification.setTitle("新子任务组已开放"); - notification.setContent("第 " + nextGroupNo + " 组共 " + nextGroupItems.size() + " 个子任务已解锁,可以接单了!"); - notification.setRelatedId(item.getTaskId()); - notification.setRelatedType("task"); - notification.setPriority(1); // 普通 - // 可以通知所有接单部门的成员(这里暂时省略,可以扩展) + // 发送通知给接单部门负责人 + if (mainTask.getReceiverDeptId() != null) { + com.designhub.entity.Department receiverDept = departmentMapper.selectById(mainTask.getReceiverDeptId()); + if (receiverDept != null && receiverDept.getLeaderId() != null) { + Notification notification = new Notification(); + notification.setReceiverId(receiverDept.getLeaderId()); + notification.setNotificationType("task_approval"); + notification.setTitle("新子任务组待审核"); + notification.setContent("任务《" + mainTask.getTitle() + "》第 " + nextGroupNo + " 组共 " + + nextGroupItems.size() + " 个子任务已解锁,请审核并分配!"); + notification.setRelatedId(item.getTaskId()); + notification.setRelatedType("task"); + notification.setPriority(2); // 重要 + notificationService.sendNotification(notification); + + log.info("已通知接单部门负责人(ID: {})审核第 {} 组子任务", + receiverDept.getLeaderId(), nextGroupNo); + } + } - log.info("第 {} 组 {} 个子任务已进入可接任务池", nextGroupNo, nextGroupItems.size()); + log.info("第 {} 组 {} 个子任务已解锁,等待接单部门负责人审核", nextGroupNo, nextGroupItems.size()); } else { log.info("没有更多子任务组需要解锁"); } @@ -362,4 +374,119 @@ public class TaskItemService { return progress; } + + /** + * 审核子任务组(接单部门负责人审核) + * @param taskId 主任务ID + * @param groupNo 组号 + * @param approverId 审核人ID + * @param assignedReceiverId 指定的接收人ID(可选) + */ + @Transactional(rollbackFor = Exception.class) + public void approveTaskItemGroup(Long taskId, Integer groupNo, Long approverId, Long assignedReceiverId) { + log.info("接单部门负责人 {} 审核任务 {} 的第 {} 组子任务", approverId, taskId, groupNo); + + // 1. 验证主任务 + Task mainTask = taskMapper.selectById(taskId); + if (mainTask == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "主任务不存在"); + } + + // 2. 验证审核人权限(必须是接单部门负责人) + if (mainTask.getReceiverDeptId() == null) { + throw new BusinessException(ResultCode.FORBIDDEN, "任务未指定接单部门"); + } + com.designhub.entity.Department receiverDept = departmentMapper.selectById(mainTask.getReceiverDeptId()); + if (receiverDept == null || !approverId.equals(receiverDept.getLeaderId())) { + throw new BusinessException(ResultCode.FORBIDDEN, "只有接单部门负责人可以审核子任务组"); + } + + // 3. 获取该组的所有待审核子任务 + List groupItems = taskItemMapper.selectList( + new LambdaQueryWrapper() + .eq(TaskItem::getTaskId, taskId) + .eq(TaskItem::getGroupNo, groupNo) + .eq(TaskItem::getStatus, "pending_approval") + ); + + if (groupItems.isEmpty()) { + throw new BusinessException(ResultCode.PARAM_ERROR, "该组没有待审核的子任务"); + } + + // 4. 如果指定了接收人,验证接收人并直接分配 + if (assignedReceiverId != null) { + com.designhub.entity.User receiver = userService.getUserById(assignedReceiverId); + if (receiver == null) { + throw new BusinessException(ResultCode.PARAM_ERROR, "指定的接收人不存在"); + } + + // 批量分配给指定设计师 + for (TaskItem item : groupItems) { + item.setStatus("in_progress"); + item.setReceiverId(assignedReceiverId); + item.setStartTime(LocalDateTime.now()); + item.setUpdateTime(LocalDateTime.now()); + taskItemMapper.updateById(item); + } + + // 发送通知给指定设计师 + Notification notification = new Notification(); + notification.setReceiverId(assignedReceiverId); + notification.setNotificationType("task"); + notification.setTitle("子任务已指派"); + notification.setContent("负责人为您指派了任务《" + mainTask.getTitle() + "》的第 " + groupNo + + " 组共 " + groupItems.size() + " 个子任务"); + notification.setRelatedId(taskId); + notification.setRelatedType("task"); + notification.setPriority(2); + notificationService.sendNotification(notification); + + log.info("第 {} 组 {} 个子任务已指派给设计师 {}", groupNo, groupItems.size(), receiver.getRealName()); + } else { + // 未指定接收人,发布到任务大厅 + for (TaskItem item : groupItems) { + item.setStatus("pending"); + item.setUpdateTime(LocalDateTime.now()); + taskItemMapper.updateById(item); + } + + log.info("第 {} 组 {} 个子任务已发布到任务大厅", groupNo, groupItems.size()); + } + } + + /** + * 获取待审核的子任务组列表(按主任务分组) + * @param receiverDeptId 接单部门ID + */ + public List> getPendingApprovalGroups(Long receiverDeptId) { + // 查询该部门所有有待审核子任务组的主任务 + List pendingItems = taskItemMapper.selectList( + new LambdaQueryWrapper() + .eq(TaskItem::getStatus, "pending_approval") + ); + + // 按主任务和组号分组 + Map> groupMap = new HashMap<>(); + for (TaskItem item : pendingItems) { + Task mainTask = taskMapper.selectById(item.getTaskId()); + if (mainTask != null && receiverDeptId.equals(mainTask.getReceiverDeptId())) { + String key = item.getTaskId() + "-" + item.getGroupNo(); + if (!groupMap.containsKey(key)) { + Map groupInfo = new HashMap<>(); + groupInfo.put("taskId", item.getTaskId()); + groupInfo.put("taskTitle", mainTask.getTitle()); + groupInfo.put("taskNo", mainTask.getTaskNo()); + groupInfo.put("groupNo", item.getGroupNo()); + groupInfo.put("itemCount", 1); + groupInfo.put("createTime", item.getCreateTime()); + groupMap.put(key, groupInfo); + } else { + Map groupInfo = groupMap.get(key); + groupInfo.put("itemCount", (Integer) groupInfo.get("itemCount") + 1); + } + } + } + + return new java.util.ArrayList<>(groupMap.values()); + } } diff --git a/frontend/src/api/task.ts b/frontend/src/api/task.ts index 85e6375..63cdd91 100644 --- a/frontend/src/api/task.ts +++ b/frontend/src/api/task.ts @@ -444,6 +444,26 @@ export const getTaskItemsProgress = (taskId: number) => { return request.get(`/tasks/items/task/${taskId}/progress`) } +/** + * 审核子任务组 + */ +export const approveTaskItemGroup = (data: { + taskId: number + groupNo: number + assignedReceiverId?: number +}) => { + return request.post('/tasks/items/groups/approve', data) +} + +/** + * 获取待审核的子任务组列表 + */ +export const getPendingApprovalGroups = (deptId?: number) => { + return request.get('/tasks/items/groups/pending-approval', { + params: deptId ? { deptId } : {} + }) +} + // ==================== 两级审核API ==================== /** diff --git a/frontend/src/views/CreateTask.vue b/frontend/src/views/CreateTask.vue index 9957a60..132ff3e 100644 --- a/frontend/src/views/CreateTask.vue +++ b/frontend/src/views/CreateTask.vue @@ -293,6 +293,8 @@ + +