904 lines
43 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.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.designhub.common.ResultCode;
import com.designhub.entity.Task;
import com.designhub.entity.TaskItem;
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;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 任务子项服务类(流水线任务)
*/
@Service
@RequiredArgsConstructor
public class TaskItemService {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TaskItemService.class);
private final TaskItemMapper taskItemMapper;
private final TaskMapper taskMapper;
private final DepartmentMapper departmentMapper;
private final UserService userService;
private final NotificationService notificationService;
private final DingTalkService dingTalkService;
private final ConfigService configService;
private final ExternalAssetService externalAssetService;
private final SystemConfigService systemConfigService;
/**
* 获取可接单的子任务列表
*/
public IPage<TaskItem> getAvailableItems(Integer page, Integer pageSize, Long taskTypeId, String keyword) {
Page<TaskItem> pageParam = new Page<>(page, pageSize);
return taskItemMapper.selectAvailableItems(pageParam, taskTypeId, keyword);
}
/**
* 获取进行中的子任务列表
*/
public IPage<TaskItem> getInProgressItems(Integer page, Integer pageSize, Long receiverId) {
Page<TaskItem> pageParam = new Page<>(page, pageSize);
return taskItemMapper.selectInProgressItems(pageParam, receiverId);
}
/**
* 获取已完成的子任务列表
*/
public IPage<TaskItem> getCompletedItems(Integer page, Integer pageSize, Long receiverId) {
Page<TaskItem> pageParam = new Page<>(page, pageSize);
return taskItemMapper.selectCompletedItems(pageParam, receiverId);
}
/**
* 根据任务ID获取所有子任务
*/
public List<TaskItem> getItemsByTaskId(Long taskId) {
return taskItemMapper.selectByTaskId(taskId);
}
/**
* 获取子任务详情
*/
public TaskItem getItemById(Long itemId) {
TaskItem item = taskItemMapper.selectById(itemId);
if (item == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "子任务不存在");
}
// 加载主任务信息
Task mainTask = taskMapper.selectById(item.getTaskId());
if (mainTask != null) {
item.setMainTaskTitle(mainTask.getTitle());
item.setPipelineMode(mainTask.getPipelineMode());
item.setBrandName(mainTask.getBrandName());
item.setCategoryName(mainTask.getCategoryName());
item.setPlatformName(mainTask.getPlatformName());
item.setPublisherName(mainTask.getPublisherName());
item.setSpuInfo(mainTask.getSpuInfo());
item.setTaskSubType(mainTask.getTaskSubType());
item.setAttachmentUrl(mainTask.getAttachmentUrl());
}
return item;
}
/**
* 抢单子任务
*/
@Transactional(rollbackFor = Exception.class)
public void grabItem(Long itemId, Long userId) {
log.info("设计师 {} 抢单子任务 {}", userId, itemId);
// 获取用户信息
var user = userService.getUserById(userId);
if (user == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "用户不存在");
}
// 获取子任务信息
TaskItem item = taskItemMapper.selectById(itemId);
if (item == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "子任务不存在");
}
// 检查是否是管理员(管理员可以抢单,绕过所有限制)
boolean isAdmin = user.getIsAdmin() != null && user.getIsAdmin() == 1;
// 非管理员需要进行权限验证
if (!isAdmin) {
// 验证用户部门权限
if (user.getDeptId() == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "您未分配部门,无法接单");
}
com.designhub.entity.Department department = departmentMapper.selectById(user.getDeptId());
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 (item.getTaskTypeId() != null) {
com.designhub.entity.DemandType demandType = configService.getDemandTypeById(item.getTaskTypeId());
if (demandType != null) {
// 获取允许的部门ID(支持继承)
String allowedDeptIds = demandType.getAllowedDeptIds();
if ((allowedDeptIds == null || allowedDeptIds.isEmpty()) && demandType.getParentId() != null) {
// 三级分类没有配置,尝试获取父级(二级分类)的配置
var parentType = configService.getDemandTypeById(demandType.getParentId());
if (parentType != null) {
allowedDeptIds = parentType.getAllowedDeptIds();
}
}
if (allowedDeptIds != null && !allowedDeptIds.isEmpty()) {
// 检查用户部门是否在允许列表中
String[] allowedIds = allowedDeptIds.split(",");
boolean isAllowed = false;
for (String allowedId : allowedIds) {
if (allowedId.trim().equals(String.valueOf(user.getDeptId()))) {
isAllowed = true;
break;
}
}
if (!isAllowed) {
throw new BusinessException(ResultCode.FORBIDDEN, "您的部门无权接收此类型的任务");
}
}
}
}
} else {
log.info("管理员 {} 抢单子任务 {}", user.getRealName(), itemId);
}
// 验证状态
if (!"pending".equals(item.getStatus())) {
throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "该子任务不可接单");
}
if (item.getIsLocked() != null && item.getIsLocked() == 1) {
throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "该子任务已被锁定,请等待前序任务完成");
}
if (item.getReceiverId() != null) {
throw new BusinessException(ResultCode.TASK_ALREADY_TAKEN, "该子任务已被接单");
}
// 乐观锁抢单
int result = taskItemMapper.grabItem(itemId, userId, user.getRealName());
if (result == 0) {
throw new BusinessException(ResultCode.TASK_ALREADY_TAKEN, "抢单失败,该子任务可能已被他人接单");
}
log.info("设计师 {} 成功抢单子任务 {}", userId, itemId);
// 更新主任务状态
Task mainTask = taskMapper.selectById(item.getTaskId());
if (mainTask != null) {
// 如果主任务还是pending状态,更新为in_progress
if ("pending".equals(mainTask.getStatus())) {
mainTask.setStatus("in_progress");
mainTask.setStartTime(LocalDateTime.now());
mainTask.setActualStartTime(LocalDateTime.now());
taskMapper.updateById(mainTask);
log.info("主任务 {} 状态已更新为 in_progress", mainTask.getId());
}
// 发送通知给任务发布者
if (mainTask.getPublisherId() != null) {
Notification notification = new Notification();
notification.setReceiverId(mainTask.getPublisherId());
notification.setNotificationType("task");
notification.setTitle("子任务已被接单");
notification.setContent(user.getRealName() + " 接单了子任务:" + item.getItemNo());
notification.setRelatedId(itemId);
notification.setRelatedType("task_item");
notification.setPriority(1); // 普通
notification.setPushDingtalk(1); // 推送到钉钉
notificationService.sendNotification(notification);
}
}
}
/**
* 更新子任务进度
*/
@Transactional(rollbackFor = Exception.class)
public void updateProgress(Long itemId, Long userId, Integer progress) {
log.info("设计师 {} 更新子任务 {} 进度为 {}%", userId, itemId, progress);
TaskItem item = taskItemMapper.selectById(itemId);
if (item == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "子任务不存在");
}
// 验证权限
if (!userId.equals(item.getReceiverId())) {
throw new BusinessException(ResultCode.FORBIDDEN, "无权更新该子任务");
}
// 验证状态
if (!"in_progress".equals(item.getStatus())) {
throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "只能更新进行中的子任务");
}
// 验证进度值
if (progress < 0 || progress > 100) {
throw new BusinessException(ResultCode.PARAM_ERROR, "进度值必须在0-100之间");
}
// 更新进度
item.setProgress(progress);
item.setUpdateTime(LocalDateTime.now());
taskItemMapper.updateById(item);
log.info("子任务 {} 进度已更新为 {}%", itemId, progress);
}
/**
* 完结子任务(关键方法:包含自动解锁逻辑)
* @param itemId 子任务ID
* @param userId 用户ID
* @param deliveryPath 交付路径(可选,如果启用第三方API则自动获取)
*/
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> completeItem(Long itemId, Long userId, String deliveryPath) {
log.info("设计师 {} 完结子任务 {}", userId, itemId);
// 1. 获取子任务信息
TaskItem item = taskItemMapper.selectById(itemId);
if (item == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "子任务不存在");
}
// 2. 验证权限
if (!userId.equals(item.getReceiverId())) {
throw new BusinessException(ResultCode.FORBIDDEN, "无权完结该子任务");
}
// 3. 验证状态
if (!"in_progress".equals(item.getStatus())) {
throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "只能完结进行中的子任务");
}
// 4. 检查是否启用第三方素材API验证
String downloadUrl = null;
boolean externalApiEnabled = "true".equalsIgnoreCase(
systemConfigService.getConfigValue("external_asset_api_enabled", "false"));
if (externalApiEnabled) {
// 调用第三方素材API验证任务完成状态
String taskNo = item.getItemNo(); // 使用子任务编号作为任务号
try {
downloadUrl = externalAssetService.checkTaskCompletion(taskNo);
} catch (Exception e) {
log.warn("调用第三方素材API失败: {}", e.getMessage());
// API调用失败时,提示用户
throw new BusinessException(ResultCode.ERROR,
"素材API验证失败,请稍后重试或联系管理员");
}
if (downloadUrl == null) {
// 如果第三方API未返回下载链接,提示用户去上传素材
throw new BusinessException(ResultCode.ERROR,
"素材未提交,请先前往 3D.YYDD.AI 提交任务素材后再完结任务");
}
log.info("第三方素材API返回下载链接: {}", downloadUrl);
} else {
// 未启用第三方API,允许直接完结任务
log.info("第三方素材API未启用,直接完结任务: itemId={}", itemId);
// 如果有传入deliveryPath,则使用它
if (deliveryPath != null && !deliveryPath.trim().isEmpty()) {
downloadUrl = deliveryPath;
}
}
// 5. 更新子任务状态
item.setStatus("completed");
item.setProgress(100);
item.setDeliveryPath(downloadUrl != null ? downloadUrl : deliveryPath);
item.setDownloadUrl(downloadUrl);
item.setCompleteTime(LocalDateTime.now());
item.setUpdateTime(LocalDateTime.now());
taskItemMapper.updateById(item);
log.info("子任务 {} 已完成", itemId);
// 5. 设计师立即获得积分(如果需要,可以调用积分服务)
// scoreService.addScore(userId, item.getTotalScore());
Map<String, Object> result = new HashMap<>();
result.put("completedItem", item);
// 6. 🔥 关键步骤:检查是否是顺序流水线,基于group_no解锁下一组
Task mainTask = taskMapper.selectById(item.getTaskId());
if (mainTask != null && "sequential".equals(mainTask.getPipelineMode())) {
// 获取当前子任务的分组号
Integer currentGroupNo = item.getGroupNo();
if (currentGroupNo == null) {
currentGroupNo = 1; // 兼容旧数据
}
// 检查当前组的所有子任务是否都已完成
List<TaskItem> currentGroupItems = taskItemMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, item.getTaskId())
.eq(TaskItem::getGroupNo, currentGroupNo)
);
boolean allGroupCompleted = currentGroupItems.stream()
.allMatch(i -> "completed".equals(i.getStatus()));
if (allGroupCompleted) {
log.info("✅ 第 {} 组所有子任务已完成,准备解锁下一组", currentGroupNo);
// 🔥 收集当前组所有子任务的下载链接,传递给下一组
StringBuilder prevDownloadUrls = new StringBuilder();
for (TaskItem groupItem : currentGroupItems) {
if (groupItem.getDownloadUrl() != null && !groupItem.getDownloadUrl().isEmpty()) {
if (prevDownloadUrls.length() > 0) {
prevDownloadUrls.append(",");
}
prevDownloadUrls.append(groupItem.getDownloadUrl());
}
// 同时继承前面环节的下载链接
if (groupItem.getPrevDownloadUrl() != null && !groupItem.getPrevDownloadUrl().isEmpty()) {
if (prevDownloadUrls.length() > 0) {
prevDownloadUrls.append(",");
}
prevDownloadUrls.append(groupItem.getPrevDownloadUrl());
}
}
String prevDownloadUrlStr = prevDownloadUrls.toString();
log.info("前置环节素材下载链接: {}", prevDownloadUrlStr);
// 解锁下一组的所有子任务
Integer nextGroupNo = currentGroupNo + 1;
List<TaskItem> nextGroupItems = taskItemMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, item.getTaskId())
.eq(TaskItem::getGroupNo, nextGroupNo)
.eq(TaskItem::getStatus, "locked")
);
if (!nextGroupItems.isEmpty()) {
// 批量解锁下一组的所有子任务,状态设为待审核,并传递前置环节的下载链接
for (TaskItem nextItem : nextGroupItems) {
nextItem.setStatus("pending_approval"); // 待接单部门负责人审核
nextItem.setIsLocked(0);
nextItem.setPrevDownloadUrl(prevDownloadUrlStr); // 🔥 传递前置环节的下载链接
nextItem.setUpdateTime(LocalDateTime.now());
taskItemMapper.updateById(nextItem);
log.info("✅ 解锁子任务: {} (组号: {}, ID: {}),待接单部门负责人审核,已传递前置素材链接",
nextItem.getItemNo(), nextGroupNo, nextItem.getId());
}
result.put("unlockedGroupNo", nextGroupNo);
result.put("unlockedCount", nextGroupItems.size());
// 发送通知给接单部门负责人
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); // 重要
notification.setPushDingtalk(1); // 推送到钉钉
notificationService.sendNotification(notification);
log.info("已通知接单部门负责人(ID: {})审核第 {} 组子任务",
receiverDept.getLeaderId(), nextGroupNo);
}
}
log.info("第 {} 组 {} 个子任务已解锁,等待接单部门负责人审核", nextGroupNo, nextGroupItems.size());
} else {
log.info("没有更多子任务组需要解锁");
}
} else {
log.info("当前组还有子任务未完成,暂不解锁下一组");
}
}
// 7. 检查主任务是否所有子任务都已完成
int completedCount = taskItemMapper.countCompletedItems(item.getTaskId());
int totalCount = taskItemMapper.countTotalItems(item.getTaskId());
if (completedCount == totalCount) {
// 🔥 获取最后一组子任务的下载链接,保存到主任务
Integer maxGroupNo = taskItemMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, item.getTaskId())
.orderByDesc(TaskItem::getGroupNo)
.last("LIMIT 1")
).stream().findFirst().map(TaskItem::getGroupNo).orElse(1);
List<TaskItem> lastGroupItems = taskItemMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, item.getTaskId())
.eq(TaskItem::getGroupNo, maxGroupNo)
);
// 收集最后一组的下载链接
StringBuilder finalDownloadUrl = new StringBuilder();
for (TaskItem lastItem : lastGroupItems) {
if (lastItem.getDownloadUrl() != null && !lastItem.getDownloadUrl().isEmpty()) {
if (finalDownloadUrl.length() > 0) {
finalDownloadUrl.append(",");
}
finalDownloadUrl.append(lastItem.getDownloadUrl());
}
}
// 所有子任务完成,更新主任务状态
mainTask.setStatus("completed");
mainTask.setCompleteTime(LocalDateTime.now());
mainTask.setUpdateTime(LocalDateTime.now());
// 🔥 保存最后一组的下载链接到主任务
if (finalDownloadUrl.length() > 0) {
mainTask.setDeliveryPath(finalDownloadUrl.toString());
log.info("主任务最终素材下载链接: {}", finalDownloadUrl);
}
taskMapper.updateById(mainTask);
log.info("🎉 主任务 {} 所有子任务已完成,主任务标记为完成", mainTask.getId());
// 发送通知给发布者
if (mainTask.getPublisherId() != null) {
Notification notification = new Notification();
notification.setReceiverId(mainTask.getPublisherId());
notification.setNotificationType("task");
notification.setTitle("流水线任务已完成");
notification.setContent("您的任务《" + mainTask.getTitle() + "》所有子任务已完成!");
notification.setRelatedId(mainTask.getId());
notification.setRelatedType("task");
notification.setPriority(1); // 普通
notification.setPushDingtalk(1); // 推送到钉钉
notificationService.sendNotification(notification);
}
result.put("mainTaskCompleted", true);
} else {
result.put("mainTaskCompleted", false);
result.put("progress", completedCount + "/" + totalCount);
}
return result;
}
/**
* 驳回子任务
*/
@Transactional(rollbackFor = Exception.class)
public void rejectItem(Long itemId, String reason) {
TaskItem item = taskItemMapper.selectById(itemId);
if (item == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "子任务不存在");
}
item.setStatus("rejected");
item.setUpdateTime(LocalDateTime.now());
taskItemMapper.updateById(item);
// 发送通知给接单人
if (item.getReceiverId() != null) {
Notification notification = new Notification();
notification.setReceiverId(item.getReceiverId());
notification.setNotificationType("task");
notification.setTitle("子任务被驳回");
notification.setContent("您的子任务 " + item.getItemNo() + " 被驳回。原因:" + reason);
notification.setRelatedId(itemId);
notification.setRelatedType("task_item");
notification.setPriority(1); // 普通
notification.setPushDingtalk(1); // 推送到钉钉
notificationService.sendNotification(notification);
}
log.info("子任务 {} 已驳回", itemId);
}
/**
* 获取主任务的进度统计
*/
public Map<String, Object> getTaskProgress(Long taskId) {
int completedCount = taskItemMapper.countCompletedItems(taskId);
int totalCount = taskItemMapper.countTotalItems(taskId);
Map<String, Object> progress = new HashMap<>();
progress.put("completedCount", completedCount);
progress.put("totalCount", totalCount);
progress.put("percentage", totalCount > 0 ? (completedCount * 100 / totalCount) : 0);
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. 验证审核人权限(必须是接单部门负责人或管理员)
com.designhub.entity.User approver = userService.getUserById(approverId);
boolean isAdmin = approver != null && Integer.valueOf(1).equals(approver.getIsAdmin());
if (!isAdmin) {
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, "只有接单部门负责人可以审核子任务组");
}
} else {
log.info("管理员 {} 代审核子任务组", approver.getRealName());
}
// 3. 获取该组的所有待审核子任务
List<TaskItem> groupItems = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.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.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);
notification.setPushDingtalk(1); // 推送到钉钉
notificationService.sendNotification(notification);
// 发送钉钉群通知并@指定的设计师
try {
int totalScore = groupItems.stream().mapToInt(item -> item.getTotalScore() != null ? item.getTotalScore() : 0).sum();
String title = "子任务组已指派";
StringBuilder content = new StringBuilder();
content.append("-------------------------------- \n");
content.append(" 【 子 任 务 组 已 指 派 】 \n");
content.append("-------------------------------- \n\n");
content.append("主任务:").append(mainTask.getTitle()).append(" \n");
content.append("任务编号:").append(mainTask.getTaskNo()).append(" \n");
content.append("任务组:第 ").append(groupNo).append(" 组 \n");
content.append("子任务数量:").append(groupItems.size()).append(" 个 \n");
content.append("总积分:").append(totalScore).append(" 分 \n");
content.append("指派给:").append(receiver.getRealName()).append(" \n");
if (mainTask.getPublisherName() != null && !mainTask.getPublisherName().isEmpty()) {
content.append("发起人:").append(mainTask.getPublisherName()).append(" \n");
}
content.append(" \n");
content.append("-------------------------------- \n");
content.append("请尽快处理!qd.yydd.ai \n");
content.append("--------------------------------");
// @指定的设计师
java.util.List<String> atMobiles = receiver.getPhone() != null ?
java.util.Arrays.asList(receiver.getPhone()) : null;
dingTalkService.sendRobotMessage(title, content.toString(), atMobiles);
log.info("子任务组指派群通知已发送,@手机号: {}", receiver.getPhone());
} catch (Exception e) {
log.error("发送子任务组指派群通知失败", e);
}
log.info("第 {} 组 {} 个子任务已指派给设计师 {}", groupNo, groupItems.size(), receiver.getRealName());
} else {
// 未指定接收人,发布到任务大厅
for (TaskItem item : groupItems) {
item.setStatus("pending");
item.setUpdateTime(LocalDateTime.now());
taskItemMapper.updateById(item);
}
// 发送钉钉群机器人通知
// 注意:钉钉Markdown换行需要两个空格+换行符
try {
String title = "新子任务组发布";
StringBuilder content = new StringBuilder();
content.append("-------------------------------- \n");
content.append(" 【 新 子 任 务 组 发 布 】 \n");
content.append("-------------------------------- \n\n");
// 主任务信息
content.append("主任务:").append(mainTask.getTitle()).append(" \n");
content.append("任务编号:").append(mainTask.getTaskNo()).append(" \n");
content.append("任务组:第 ").append(groupNo).append(" 组 \n");
content.append("子任务数量:").append(groupItems.size()).append(" 个 \n");
// 计算总积分
int totalScore = groupItems.stream().mapToInt(item -> item.getTotalScore() != null ? item.getTotalScore() : 0).sum();
content.append("总积分:").append(totalScore).append(" 分 \n");
// 任务需求类型
if (mainTask.getTaskTypeIds() != null && !mainTask.getTaskTypeIds().isEmpty()) {
StringBuilder typeNames = new StringBuilder();
String[] typeIds = mainTask.getTaskTypeIds().split(",");
for (String typeIdStr : typeIds) {
try {
Long typeId = Long.parseLong(typeIdStr.trim());
com.designhub.entity.DemandType demandType = configService.getDemandTypeById(typeId);
if (demandType != null) {
if (typeNames.length() > 0) {
typeNames.append("、");
}
typeNames.append(demandType.getName());
}
} catch (NumberFormatException e) {
log.warn("解析任务类型ID失败:{}", typeIdStr);
}
}
if (typeNames.length() > 0) {
content.append("需求类型:").append(typeNames.toString()).append(" \n");
}
}
content.append(" \n");
// 任务详情
if (mainTask.getBrandName() != null && !mainTask.getBrandName().isEmpty()) {
content.append("品牌:").append(mainTask.getBrandName()).append(" \n");
}
if (mainTask.getCategoryName() != null && !mainTask.getCategoryName().isEmpty()) {
content.append("类目:").append(mainTask.getCategoryName()).append(" \n");
}
if (mainTask.getPlatformName() != null && !mainTask.getPlatformName().isEmpty()) {
content.append("平台:").append(mainTask.getPlatformName()).append(" \n");
}
if (mainTask.getSpuInfo() != null && !mainTask.getSpuInfo().isEmpty()) {
content.append("SPU:").append(mainTask.getSpuInfo()).append(" \n");
}
content.append(" \n");
// 列出子任务详情
content.append("子任务列表: \n");
for (TaskItem item : groupItems) {
content.append(" - ").append(item.getItemNo())
.append("(").append(item.getTotalScore() != null ? item.getTotalScore() : 0).append("分) \n");
}
// 发起人信息
if (mainTask.getPublisherName() != null && !mainTask.getPublisherName().isEmpty()) {
content.append("发起人:").append(mainTask.getPublisherName()).append(" \n");
}
if (mainTask.getPublisherDeptName() != null && !mainTask.getPublisherDeptName().isEmpty()) {
content.append("发起部门:").append(mainTask.getPublisherDeptName()).append(" \n");
}
content.append(" \n");
content.append("-------------------------------- \n");
content.append("前往抢单:qd.yydd.ai \n");
content.append("--------------------------------");
dingTalkService.sendRobotMessage(title, content.toString());
log.info("第 {} 组 {} 个子任务已发布到任务大厅,已发送钉钉群通知", groupNo, groupItems.size());
} catch (Exception e) {
log.error("发送钉钉群通知失败:taskId={}, groupNo={}", taskId, groupNo, e);
// 通知发送失败不影响主流程
}
}
}
/**
* 驳回子任务组(接单部门负责人审核不通过)
* 逻辑:驳回该组所有子任务,并将主任务及其所有子任务设为驳回状态
*/
@Transactional(rollbackFor = Exception.class)
public void rejectTaskGroup(Long taskId, Integer groupNo, Long rejectorId, String reason) {
log.info("接单部门负责人 {} 驳回任务 {} 的第 {} 组子任务,原因:{}", rejectorId, taskId, groupNo, reason);
// 1. 验证主任务
Task mainTask = taskMapper.selectById(taskId);
if (mainTask == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "主任务不存在");
}
// 2. 验证审核人权限(必须是接单部门负责人或管理员)
com.designhub.entity.User rejector = userService.getUserById(rejectorId);
boolean isAdmin = rejector != null && Integer.valueOf(1).equals(rejector.getIsAdmin());
if (!isAdmin) {
if (mainTask.getReceiverDeptId() == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "任务未指定接单部门");
}
com.designhub.entity.Department receiverDept = departmentMapper.selectById(mainTask.getReceiverDeptId());
if (receiverDept == null || !rejectorId.equals(receiverDept.getLeaderId())) {
throw new BusinessException(ResultCode.FORBIDDEN, "只有接单部门负责人可以驳回子任务组");
}
} else {
log.info("管理员 {} 代驳回子任务组", rejector.getRealName());
}
// 3. 更新主任务状态为 rejected
mainTask.setStatus("rejected");
mainTask.setRejectReason(reason);
mainTask.setUpdateTime(LocalDateTime.now());
taskMapper.updateById(mainTask);
// 4. 更新该任务下所有子任务状态为 rejected
taskItemMapper.update(null,
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<TaskItem>()
.eq(TaskItem::getTaskId, taskId)
.set(TaskItem::getStatus, "rejected")
.set(TaskItem::getUpdateTime, LocalDateTime.now())
);
log.info("任务 {} 及其所有子任务已被驳回", taskId);
// 5. 发送通知给任务发布者
if (mainTask.getPublisherId() != null) {
Notification notification = new Notification();
notification.setReceiverId(mainTask.getPublisherId());
notification.setNotificationType("task_rejected");
notification.setTitle("任务被驳回");
notification.setContent("您的任务《" + mainTask.getTitle() + "》在第 " + groupNo + " 组子任务审核时被驳回。原因:" + reason + "。请重新编辑并提交。");
notification.setRelatedId(taskId);
notification.setRelatedType("task");
notification.setPriority(2); // 重要
notification.setPushDingtalk(1); // 推送到钉钉
notificationService.sendNotification(notification);
}
// 6. 查找该任务组中有处理人的子任务,发送群通知@处理人
try {
List<TaskItem> groupItems = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, taskId)
.eq(TaskItem::getGroupNo, groupNo)
);
// 收集所有处理人的手机号
java.util.Set<String> atMobiles = new java.util.HashSet<>();
java.util.Set<String> receiverNames = new java.util.HashSet<>();
for (TaskItem item : groupItems) {
if (item.getReceiverId() != null) {
com.designhub.entity.User receiver = userService.getUserById(item.getReceiverId());
if (receiver != null) {
if (receiver.getPhone() != null) {
atMobiles.add(receiver.getPhone());
}
if (receiver.getRealName() != null) {
receiverNames.add(receiver.getRealName());
}
}
}
}
// 如果有处理人,发送群通知
if (!receiverNames.isEmpty()) {
String title = "子任务组已驳回";
StringBuilder content = new StringBuilder();
content.append("-------------------------------- \n");
content.append(" 【 子 任 务 组 已 驳 回 】 \n");
content.append("-------------------------------- \n\n");
content.append("主任务:").append(mainTask.getTitle()).append(" \n");
content.append("任务编号:").append(mainTask.getTaskNo()).append(" \n");
content.append("任务组:第 ").append(groupNo).append(" 组 \n");
content.append("原处理人:").append(String.join("、", receiverNames)).append(" \n");
content.append("驳回原因:").append(reason).append(" \n");
content.append(" \n");
content.append("--------------------------------");
dingTalkService.sendRobotMessage(title, content.toString(),
atMobiles.isEmpty() ? null : new java.util.ArrayList<>(atMobiles));
}
} catch (Exception e) {
log.error("发送子任务组驳回群通知失败", e);
}
}
/**
* 获取待审核的子任务组列表(按主任务分组)
* @param receiverDeptId 接单部门ID,null表示查询所有(管理员)
*/
public List<Map<String, Object>> getPendingApprovalGroups(Long receiverDeptId) {
// 查询所有有待审核子任务组的主任务
List<TaskItem> pendingItems = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getStatus, "pending_approval")
);
// 按主任务和组号分组
Map<String, Map<String, Object>> groupMap = new HashMap<>();
for (TaskItem item : pendingItems) {
Task mainTask = taskMapper.selectById(item.getTaskId());
// receiverDeptId为null表示管理员,可以看到所有任务
if (mainTask != null && (receiverDeptId == null || receiverDeptId.equals(mainTask.getReceiverDeptId()))) {
String key = item.getTaskId() + "-" + item.getGroupNo();
if (!groupMap.containsKey(key)) {
Map<String, Object> 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("groupId", item.getGroupNo()); // 前端使用groupId作为唯一标识
groupInfo.put("itemCount", 1);
groupInfo.put("createTime", item.getCreateTime());
groupMap.put(key, groupInfo);
} else {
Map<String, Object> groupInfo = groupMap.get(key);
groupInfo.put("itemCount", (Integer) groupInfo.get("itemCount") + 1);
}
}
}
return new java.util.ArrayList<>(groupMap.values());
}
}