补充以下节点的一级发起人通知: 1. 子任务被接单 - 同时通知一级发起人 2. 子任务进度更新 - 同时通知一级发起人 3. 流水线任务全部完成 - 同时通知一级发起人 4. 子任务组审核通过并指派 - 通知一级发起人 5. 需求部门审批通过 - 通知一级发起人审核进度 6. 需求部门审批驳回 - 同时通知一级发起人 7. 接单部门审批通过 - 通知一级发起人任务已发布 8. 接单部门审批驳回 - 同时通知一级发起人 确保一级需求发起人能够全程跟踪任务状态变化
1654 lines
83 KiB
Java
1654 lines
83 KiB
Java
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 com.designhub.mapper.DemandTypeMapper;
|
||
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.HashSet;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import java.util.Set;
|
||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||
|
||
/**
|
||
* 任务子项服务类(流水线任务)
|
||
*/
|
||
@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;
|
||
private final DemandTypeMapper demandTypeMapper;
|
||
private final com.designhub.mapper.UserMapper userMapper;
|
||
private final ObjectMapper objectMapper;
|
||
private final com.designhub.util.WorkHoursCalculator workHoursCalculator;
|
||
|
||
/**
|
||
* 获取可接单的子任务列表
|
||
*/
|
||
public IPage<TaskItem> getAvailableItems(Integer page, Integer pageSize, Long taskTypeId, String keyword) {
|
||
return getAvailableItems(page, pageSize, taskTypeId, keyword, null);
|
||
}
|
||
|
||
/**
|
||
* 获取可接单的子任务列表(支持按接单部门筛选)
|
||
* @param filterDeptId 筛选可接单部门ID(可选)
|
||
*/
|
||
public IPage<TaskItem> getAvailableItems(Integer page, Integer pageSize, Long taskTypeId, String keyword, Long filterDeptId) {
|
||
Page<TaskItem> pageParam = new Page<>(page, pageSize);
|
||
|
||
// 如果没有指定筛选部门,直接查询
|
||
if (filterDeptId == null) {
|
||
return taskItemMapper.selectAvailableItems(pageParam, taskTypeId, keyword);
|
||
}
|
||
|
||
// 🔥 获取筛选部门及其所有子部门的ID列表
|
||
Set<Long> filterDeptAndChildIds = getDeptAndChildIds(filterDeptId);
|
||
log.info("筛选部门ID: {}, 包含子部门: {}", filterDeptId, filterDeptAndChildIds);
|
||
|
||
// 获取该部门可接单的任务类型ID列表
|
||
List<com.designhub.entity.DemandType> allDemandTypes = demandTypeMapper.selectList(null);
|
||
Map<Long, com.designhub.entity.DemandType> demandTypeMap = allDemandTypes.stream()
|
||
.collect(java.util.stream.Collectors.toMap(com.designhub.entity.DemandType::getId, dt -> dt));
|
||
|
||
List<Long> filterDeptAllowedTypeIds = allDemandTypes.stream()
|
||
.filter(dt -> {
|
||
String allowedDepts = dt.getAllowedDeptIds();
|
||
if ((allowedDepts == null || allowedDepts.isEmpty()) && dt.getParentId() != null) {
|
||
com.designhub.entity.DemandType parentType = demandTypeMap.get(dt.getParentId());
|
||
if (parentType != null) {
|
||
allowedDepts = parentType.getAllowedDeptIds();
|
||
}
|
||
}
|
||
// 只筛选明确指定了该部门(或其子部门)的任务类型
|
||
if (allowedDepts == null || allowedDepts.isEmpty()) {
|
||
return false;
|
||
}
|
||
String[] deptIds = allowedDepts.split(",");
|
||
for (String deptIdStr : deptIds) {
|
||
try {
|
||
Long deptId = Long.parseLong(deptIdStr.trim());
|
||
// 🔥 检查是否匹配筛选部门或其子部门
|
||
if (filterDeptAndChildIds.contains(deptId)) {
|
||
return true;
|
||
}
|
||
} catch (NumberFormatException e) {
|
||
// 忽略无效的部门ID
|
||
}
|
||
}
|
||
return false;
|
||
})
|
||
.map(com.designhub.entity.DemandType::getId)
|
||
.collect(java.util.stream.Collectors.toList());
|
||
|
||
log.info("匹配的任务类型ID: {}", filterDeptAllowedTypeIds);
|
||
|
||
// 如果没有找到匹配的任务类型,返回空结果
|
||
if (filterDeptAllowedTypeIds.isEmpty()) {
|
||
return new Page<>(page, pageSize);
|
||
}
|
||
|
||
// 查询并筛选
|
||
return taskItemMapper.selectAvailableItemsByTaskTypes(pageParam, taskTypeId, keyword, filterDeptAllowedTypeIds);
|
||
}
|
||
|
||
/**
|
||
* 获取指定部门及其所有子部门的ID集合
|
||
*/
|
||
private Set<Long> getDeptAndChildIds(Long deptId) {
|
||
Set<Long> result = new HashSet<>();
|
||
result.add(deptId);
|
||
|
||
// 获取所有部门
|
||
List<com.designhub.entity.Department> allDepts = departmentMapper.selectList(null);
|
||
|
||
// 递归获取子部门
|
||
addChildDeptIds(result, deptId, allDepts);
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 递归添加子部门ID
|
||
*/
|
||
private void addChildDeptIds(Set<Long> result, Long parentId, List<com.designhub.entity.Department> allDepts) {
|
||
for (com.designhub.entity.Department dept : allDepts) {
|
||
if (parentId.equals(dept.getParentId())) {
|
||
result.add(dept.getId());
|
||
// 递归获取子部门的子部门
|
||
addChildDeptIds(result, dept.getId(), allDepts);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取进行中的子任务列表
|
||
* @param receiverId 接单人ID,null表示不按接单人过滤
|
||
* @param deptId 部门ID,用于部门负责人查看本部门所有任务
|
||
* @param keyword 搜索关键词
|
||
*/
|
||
public IPage<TaskItem> getInProgressItems(Integer page, Integer pageSize, Long receiverId, Long deptId, String keyword) {
|
||
Page<TaskItem> pageParam = new Page<>(page, pageSize);
|
||
return taskItemMapper.selectInProgressItems(pageParam, receiverId, deptId, keyword);
|
||
}
|
||
|
||
/**
|
||
* 获取进行中的子任务列表(向后兼容)
|
||
*/
|
||
public IPage<TaskItem> getInProgressItems(Integer page, Integer pageSize, Long receiverId, Long deptId) {
|
||
return getInProgressItems(page, pageSize, receiverId, deptId, null);
|
||
}
|
||
|
||
/**
|
||
* 获取进行中的子任务列表(向后兼容)
|
||
*/
|
||
public IPage<TaskItem> getInProgressItems(Integer page, Integer pageSize, Long receiverId) {
|
||
return getInProgressItems(page, pageSize, receiverId, null, null);
|
||
}
|
||
|
||
/**
|
||
* 获取进行中的子任务列表(部门负责人:按多个部门ID过滤)
|
||
* @param deptIds 部门ID列表
|
||
* @param keyword 搜索关键词
|
||
*/
|
||
public IPage<TaskItem> getInProgressItemsByDeptIds(Integer page, Integer pageSize, List<Long> deptIds, String keyword) {
|
||
Page<TaskItem> pageParam = new Page<>(page, pageSize);
|
||
return taskItemMapper.selectInProgressItemsByDeptIds(pageParam, deptIds, keyword);
|
||
}
|
||
|
||
/**
|
||
* 获取已完成的子任务列表
|
||
* @param receiverId 接单人ID,null表示不按接单人过滤
|
||
* @param deptId 部门ID,用于部门负责人查看本部门所有任务
|
||
* @param keyword 搜索关键词
|
||
*/
|
||
public IPage<TaskItem> getCompletedItems(Integer page, Integer pageSize, Long receiverId, Long deptId, String keyword) {
|
||
Page<TaskItem> pageParam = new Page<>(page, pageSize);
|
||
return taskItemMapper.selectCompletedItems(pageParam, receiverId, deptId, keyword);
|
||
}
|
||
|
||
/**
|
||
* 获取已完成的子任务列表(向后兼容)
|
||
*/
|
||
public IPage<TaskItem> getCompletedItems(Integer page, Integer pageSize, Long receiverId, Long deptId) {
|
||
return getCompletedItems(page, pageSize, receiverId, deptId, null);
|
||
}
|
||
|
||
/**
|
||
* 获取已完成的子任务列表(向后兼容)
|
||
*/
|
||
public IPage<TaskItem> getCompletedItems(Integer page, Integer pageSize, Long receiverId) {
|
||
return getCompletedItems(page, pageSize, receiverId, null, null);
|
||
}
|
||
|
||
/**
|
||
* 获取已完成的子任务列表(部门负责人:按多个部门ID过滤)
|
||
* @param deptIds 部门ID列表
|
||
* @param keyword 搜索关键词
|
||
*/
|
||
public IPage<TaskItem> getCompletedItemsByDeptIds(Integer page, Integer pageSize, List<Long> deptIds, String keyword) {
|
||
Page<TaskItem> pageParam = new Page<>(page, pageSize);
|
||
return taskItemMapper.selectCompletedItemsByDeptIds(pageParam, deptIds, keyword);
|
||
}
|
||
|
||
/**
|
||
* 根据任务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(",");
|
||
Set<Long> allowedDeptIdSet = new HashSet<>();
|
||
for (String allowedId : allowedIds) {
|
||
try {
|
||
allowedDeptIdSet.add(Long.parseLong(allowedId.trim()));
|
||
} catch (NumberFormatException e) {
|
||
log.warn("解析允许部门ID失败: {}", allowedId);
|
||
}
|
||
}
|
||
|
||
// 获取用户部门及其所有父部门ID
|
||
boolean isAllowed = false;
|
||
Long currentDeptId = user.getDeptId();
|
||
while (currentDeptId != null) {
|
||
if (allowedDeptIdSet.contains(currentDeptId)) {
|
||
isAllowed = true;
|
||
break;
|
||
}
|
||
// 向上查找父部门
|
||
com.designhub.entity.Department currentDept = departmentMapper.selectById(currentDeptId);
|
||
if (currentDept != null && currentDept.getParentId() != null) {
|
||
currentDeptId = currentDept.getParentId();
|
||
} else {
|
||
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, "该子任务已被锁定,请等待前序任务完成");
|
||
}
|
||
|
||
// 🔥 检查是否是指定任务:如果子任务已预先指定了接收人
|
||
boolean isAssignedItem = false;
|
||
if (item.getReceiverId() != null) {
|
||
// 检查主任务是否是指定模式
|
||
Task mainTaskForCheck = taskMapper.selectById(item.getTaskId());
|
||
boolean isAssignedMode = mainTaskForCheck != null && "assigned".equals(mainTaskForCheck.getReleaseType());
|
||
|
||
if (isAssignedMode) {
|
||
// 指定任务模式:只有被指定的设计师才能接单
|
||
if (!item.getReceiverId().equals(userId)) {
|
||
throw new BusinessException(ResultCode.FORBIDDEN, "该子任务已指定给其他设计师");
|
||
}
|
||
// 被指定的设计师可以接单,继续执行
|
||
isAssignedItem = true;
|
||
log.info("指定任务模式:设计师 {} 是被指定的接收人,允许接单", userId);
|
||
} else {
|
||
// 非指定任务模式:已有接收人则不能再接单
|
||
throw new BusinessException(ResultCode.TASK_ALREADY_TAKEN, "该子任务已被接单");
|
||
}
|
||
}
|
||
|
||
// 计算deadline
|
||
LocalDateTime now = LocalDateTime.now();
|
||
LocalDateTime deadline = null;
|
||
if (item.getEstimatedHours() != null && item.getEstimatedHours().compareTo(java.math.BigDecimal.ZERO) > 0) {
|
||
deadline = workHoursCalculator.calculateDeadline(now, item.getEstimatedHours());
|
||
log.info("子任务deadline已计算:从接单时间 {} 开始,预估 {} 小时,截止时间 {}",
|
||
now, item.getEstimatedHours(), deadline);
|
||
}
|
||
|
||
// 🔥 根据是否是指定任务使用不同的抢单方法
|
||
int result;
|
||
if (isAssignedItem) {
|
||
// 指定任务:使用专用方法,只更新状态和deadline
|
||
result = taskItemMapper.grabAssignedItem(itemId, userId, deadline);
|
||
} else {
|
||
// 普通任务:乐观锁抢单(同时设置接单部门ID和deadline)
|
||
result = taskItemMapper.grabItem(itemId, userId, user.getRealName(), user.getDeptId(), deadline);
|
||
}
|
||
|
||
if (result == 0) {
|
||
throw new BusinessException(ResultCode.TASK_ALREADY_TAKEN, "抢单失败,该子任务可能已被他人接单");
|
||
}
|
||
|
||
log.info("设计师 {} 成功抢单子任务 {} (指定任务: {})", userId, itemId, isAssignedItem);
|
||
|
||
// 更新主任务状态
|
||
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);
|
||
}
|
||
|
||
// 🔥 一级需求任务:同时通知一级发起人
|
||
if (mainTask.getPrimaryPublisherId() != null
|
||
&& !mainTask.getPrimaryPublisherId().equals(mainTask.getPublisherId())) {
|
||
Notification notification2 = new Notification();
|
||
notification2.setReceiverId(mainTask.getPrimaryPublisherId());
|
||
notification2.setNotificationType("task");
|
||
notification2.setTitle("子任务已被接单");
|
||
notification2.setContent(user.getRealName() + " 接单了子任务:" + item.getItemNo());
|
||
notification2.setRelatedId(itemId);
|
||
notification2.setRelatedType("task_item");
|
||
notification2.setPriority(1);
|
||
notification2.setPushDingtalk(1);
|
||
notificationService.sendNotification(notification2);
|
||
log.info("已通知一级发起人 {} 子任务被接单", mainTask.getPrimaryPublisherId());
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新子任务进度
|
||
*/
|
||
@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之间");
|
||
}
|
||
|
||
// 记录更新前的进度
|
||
Integer oldProgress = item.getProgress() != null ? item.getProgress() : 0;
|
||
|
||
// 更新进度
|
||
item.setProgress(progress);
|
||
item.setUpdateTime(LocalDateTime.now());
|
||
taskItemMapper.updateById(item);
|
||
|
||
log.info("子任务 {} 进度已更新为 {}%", itemId, progress);
|
||
|
||
// 🔥 同步更新主任务的整体进度
|
||
updateMainTaskProgress(item.getTaskId());
|
||
|
||
// 获取主任务信息,通知需求者(发布者)
|
||
try {
|
||
Task mainTask = taskMapper.selectById(item.getTaskId());
|
||
if (mainTask != null && mainTask.getPublisherId() != null) {
|
||
// 获取设计师姓名
|
||
String designerName = item.getReceiverName();
|
||
if (designerName == null && item.getReceiverId() != null) {
|
||
com.designhub.entity.User designer = userService.getUserById(item.getReceiverId());
|
||
if (designer != null) {
|
||
designerName = designer.getRealName() != null ? designer.getRealName() : designer.getUsername();
|
||
}
|
||
}
|
||
|
||
// 构建通知内容
|
||
StringBuilder content = new StringBuilder();
|
||
content.append("### 任务进度更新\n\n");
|
||
content.append("**任务名称**:").append(mainTask.getTitle()).append(" \n");
|
||
content.append("**子任务编号**:").append(item.getItemNo()).append(" \n");
|
||
content.append("**设计师**:").append(designerName != null ? designerName : "未知").append(" \n");
|
||
content.append("**进度变化**:").append(oldProgress).append("% → ").append(progress).append("% \n");
|
||
|
||
if (progress == 100) {
|
||
content.append("\n🎉 子任务进度已达100%,请关注完结状态。");
|
||
}
|
||
|
||
// 发送通知给需求者(发布者)
|
||
notificationService.sendNotification(
|
||
mainTask.getPublisherId(),
|
||
"任务进度已更新",
|
||
content.toString(),
|
||
"PROGRESS_UPDATE",
|
||
"task_item",
|
||
itemId
|
||
);
|
||
|
||
log.info("已通知需求者 {} 任务进度更新: 子任务={}, 进度={}%",
|
||
mainTask.getPublisherId(), item.getItemNo(), progress);
|
||
|
||
// 🔥 一级需求任务:同时通知一级发起人
|
||
if (mainTask.getPrimaryPublisherId() != null
|
||
&& !mainTask.getPrimaryPublisherId().equals(mainTask.getPublisherId())) {
|
||
notificationService.sendNotification(
|
||
mainTask.getPrimaryPublisherId(),
|
||
"任务进度已更新",
|
||
content.toString(),
|
||
"PROGRESS_UPDATE",
|
||
"task_item",
|
||
itemId
|
||
);
|
||
log.info("已通知一级发起人 {} 任务进度更新: 子任务={}, 进度={}%",
|
||
mainTask.getPrimaryPublisherId(), item.getItemNo(), progress);
|
||
}
|
||
}
|
||
} catch (Exception e) {
|
||
// 通知失败不影响主流程
|
||
log.error("发送进度更新通知给需求者失败: itemId={}", itemId, e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 完结子任务(关键方法:包含自动解锁逻辑)
|
||
* @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,不允许完结任务
|
||
throw new BusinessException(ResultCode.ERROR,
|
||
"请先在系统设置中启用并配置第三方素材API后才能完结任务");
|
||
}
|
||
|
||
// 调用第三方素材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验证失败: " + e.getMessage());
|
||
}
|
||
|
||
if (downloadUrl == null || downloadUrl.trim().isEmpty()) {
|
||
// 如果第三方API未返回下载链接,提示用户去上传素材
|
||
throw new BusinessException(ResultCode.ERROR,
|
||
"素材未提交,请先前往 3D.YYDD.AI 提交任务素材后再完结任务");
|
||
}
|
||
|
||
log.info("第三方素材API返回下载链接: {}", downloadUrl);
|
||
|
||
// 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. 🔥 关键步骤:检查是否是顺序流水线,基于部门解锁下一个部门
|
||
// 新逻辑:同一部门内的所有子任务并行,部门间串行
|
||
Task mainTask = taskMapper.selectById(item.getTaskId());
|
||
if (mainTask != null && "sequential".equals(mainTask.getPipelineMode())) {
|
||
// 从子任务编号解析部门序号(格式:TASK...-部门序号-部门内序号)
|
||
int currentDeptIndex = parseDeptIndexFromItemNo(item.getItemNo());
|
||
log.info("当前子任务 {} 属于第 {} 个部门", item.getItemNo(), currentDeptIndex);
|
||
|
||
// 获取当前部门的所有子任务(通过编号匹配)
|
||
List<TaskItem> allItems = taskItemMapper.selectList(
|
||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TaskItem>()
|
||
.eq(TaskItem::getTaskId, item.getTaskId())
|
||
);
|
||
|
||
// 筛选出同一部门的子任务
|
||
List<TaskItem> currentDeptItems = allItems.stream()
|
||
.filter(i -> parseDeptIndexFromItemNo(i.getItemNo()) == currentDeptIndex)
|
||
.collect(java.util.stream.Collectors.toList());
|
||
|
||
// 检查当前部门的所有子任务是否都已完成
|
||
boolean allDeptCompleted = currentDeptItems.stream()
|
||
.allMatch(i -> "completed".equals(i.getStatus()));
|
||
|
||
log.info("当前部门共 {} 个子任务,已完成: {}", currentDeptItems.size(),
|
||
currentDeptItems.stream().filter(i -> "completed".equals(i.getStatus())).count());
|
||
|
||
if (allDeptCompleted) {
|
||
log.info("✅ 第 {} 个部门的所有子任务已完成,准备解锁下一个部门", currentDeptIndex);
|
||
|
||
// 🔥 收集当前部门所有子任务的下载链接,传递给下一个部门
|
||
StringBuilder prevDownloadUrls = new StringBuilder();
|
||
for (TaskItem deptItem : currentDeptItems) {
|
||
if (deptItem.getDownloadUrl() != null && !deptItem.getDownloadUrl().isEmpty()) {
|
||
if (prevDownloadUrls.length() > 0) {
|
||
prevDownloadUrls.append(",");
|
||
}
|
||
prevDownloadUrls.append(deptItem.getDownloadUrl());
|
||
}
|
||
// 同时继承前面环节的下载链接
|
||
if (deptItem.getPrevDownloadUrl() != null && !deptItem.getPrevDownloadUrl().isEmpty()) {
|
||
if (prevDownloadUrls.length() > 0) {
|
||
prevDownloadUrls.append(",");
|
||
}
|
||
prevDownloadUrls.append(deptItem.getPrevDownloadUrl());
|
||
}
|
||
}
|
||
String prevDownloadUrlStr = prevDownloadUrls.toString();
|
||
log.info("前置部门素材下载链接: {}", prevDownloadUrlStr);
|
||
|
||
// 找出下一个部门的所有子任务(状态为locked)
|
||
int nextDeptIndex = currentDeptIndex + 1;
|
||
List<TaskItem> nextDeptItems = allItems.stream()
|
||
.filter(i -> parseDeptIndexFromItemNo(i.getItemNo()) == nextDeptIndex)
|
||
.filter(i -> "locked".equals(i.getStatus()))
|
||
.collect(java.util.stream.Collectors.toList());
|
||
|
||
if (!nextDeptItems.isEmpty()) {
|
||
// 批量解锁下一个部门的所有子任务,状态设为待审核,并传递前置环节的下载链接
|
||
for (TaskItem nextItem : nextDeptItems) {
|
||
nextItem.setStatus("pending_approval"); // 待接单部门负责人审核
|
||
nextItem.setIsLocked(0);
|
||
nextItem.setPrevDownloadUrl(prevDownloadUrlStr); // 🔥 传递前置环节的下载链接
|
||
nextItem.setUpdateTime(LocalDateTime.now());
|
||
taskItemMapper.updateById(nextItem);
|
||
|
||
log.info("✅ 解锁子任务: {} (部门序号: {}, ID: {}),待接单部门负责人审核,已传递前置素材链接",
|
||
nextItem.getItemNo(), nextDeptIndex, nextItem.getId());
|
||
}
|
||
|
||
result.put("unlockedDeptIndex", nextDeptIndex);
|
||
result.put("unlockedCount", nextDeptItems.size());
|
||
|
||
// 发送通知给接单部门负责人
|
||
// 从子任务的任务类型配置中获取allowed_dept_ids
|
||
Set<Long> notifiedLeaderIds = new HashSet<>();
|
||
for (TaskItem nextItem : nextDeptItems) {
|
||
Long targetDeptId = getTargetDeptIdFromTaskType(nextItem.getTaskTypeId());
|
||
if (targetDeptId != null) {
|
||
com.designhub.entity.Department receiverDept = departmentMapper.selectById(targetDeptId);
|
||
if (receiverDept != null && receiverDept.getLeaderId() != null
|
||
&& !notifiedLeaderIds.contains(receiverDept.getLeaderId())) {
|
||
notifiedLeaderIds.add(receiverDept.getLeaderId());
|
||
|
||
Notification notification = new Notification();
|
||
notification.setReceiverId(receiverDept.getLeaderId());
|
||
notification.setNotificationType("task_approval");
|
||
notification.setTitle("新子任务组待审核");
|
||
notification.setContent("任务《" + mainTask.getTitle() + "》第 " + nextDeptIndex + " 个部门共 " +
|
||
nextDeptItems.size() + " 个子任务已解锁,请审核并分配!");
|
||
notification.setRelatedId(item.getTaskId());
|
||
notification.setRelatedType("task");
|
||
notification.setPriority(2); // 重要
|
||
notification.setPushDingtalk(1); // 推送到钉钉
|
||
notificationService.sendNotification(notification);
|
||
|
||
log.info("已通知接单部门负责人(部门: {}, 负责人ID: {})审核第 {} 个部门子任务",
|
||
receiverDept.getDeptName(), receiverDept.getLeaderId(), nextDeptIndex);
|
||
}
|
||
} else {
|
||
log.warn("子任务 {} 的任务类型未配置接单部门", nextItem.getItemNo());
|
||
}
|
||
}
|
||
|
||
log.info("第 {} 个部门 {} 个子任务已解锁,等待接单部门负责人审核,已通知 {} 个部门负责人",
|
||
nextDeptIndex, nextDeptItems.size(), notifiedLeaderIds.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.setProgress(100); // 🔥 设置主任务进度为100%
|
||
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);
|
||
}
|
||
|
||
// 🔥 一级需求任务:同时通知一级发起人
|
||
if (mainTask.getPrimaryPublisherId() != null
|
||
&& !mainTask.getPrimaryPublisherId().equals(mainTask.getPublisherId())) {
|
||
Notification notification2 = new Notification();
|
||
notification2.setReceiverId(mainTask.getPrimaryPublisherId());
|
||
notification2.setNotificationType("task");
|
||
notification2.setTitle("流水线任务已完成");
|
||
notification2.setContent("您发起的任务《" + mainTask.getTitle() + "》所有子任务已完成!");
|
||
notification2.setRelatedId(mainTask.getId());
|
||
notification2.setRelatedType("task");
|
||
notification2.setPriority(1);
|
||
notification2.setPushDingtalk(1);
|
||
notificationService.sendNotification(notification2);
|
||
log.info("已通知一级发起人 {} 任务全部完成", mainTask.getPrimaryPublisherId());
|
||
}
|
||
|
||
result.put("mainTaskCompleted", true);
|
||
} else {
|
||
// 🔥 子任务完成但主任务未全部完成,更新主任务整体进度
|
||
updateMainTaskProgress(item.getTaskId());
|
||
|
||
result.put("mainTaskCompleted", false);
|
||
result.put("progress", completedCount + "/" + totalCount);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 🔥 同步更新主任务的整体进度
|
||
* 根据所有子任务的进度计算主任务的平均进度
|
||
*/
|
||
private void updateMainTaskProgress(Long taskId) {
|
||
try {
|
||
// 查询该任务的所有子任务
|
||
List<TaskItem> allItems = taskItemMapper.selectList(
|
||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TaskItem>()
|
||
.eq(TaskItem::getTaskId, taskId)
|
||
);
|
||
|
||
if (allItems.isEmpty()) {
|
||
return;
|
||
}
|
||
|
||
// 计算所有子任务的平均进度
|
||
int totalProgress = 0;
|
||
for (TaskItem item : allItems) {
|
||
totalProgress += (item.getProgress() != null ? item.getProgress() : 0);
|
||
}
|
||
int avgProgress = totalProgress / allItems.size();
|
||
|
||
// 更新主任务进度
|
||
Task mainTask = taskMapper.selectById(taskId);
|
||
if (mainTask != null) {
|
||
mainTask.setProgress(avgProgress);
|
||
mainTask.setUpdateTime(LocalDateTime.now());
|
||
taskMapper.updateById(mainTask);
|
||
log.info("主任务 {} 进度已更新为 {}%(基于 {} 个子任务的平均进度)",
|
||
taskId, avgProgress, allItems.size());
|
||
}
|
||
} catch (Exception e) {
|
||
log.error("更新主任务进度失败: taskId={}", taskId, e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 驳回子任务
|
||
*/
|
||
@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) {
|
||
// 获取该组的子任务,检查审核人是否是对应部门的负责人
|
||
List<TaskItem> checkItems = taskItemMapper.selectList(
|
||
new LambdaQueryWrapper<TaskItem>()
|
||
.eq(TaskItem::getTaskId, taskId)
|
||
.eq(TaskItem::getGroupNo, groupNo)
|
||
.eq(TaskItem::getStatus, "pending_approval")
|
||
.last("LIMIT 1")
|
||
);
|
||
|
||
if (checkItems.isEmpty()) {
|
||
throw new BusinessException(ResultCode.PARAM_ERROR, "该组没有待审核的子任务");
|
||
}
|
||
|
||
// 从子任务的任务类型配置获取接单部门
|
||
TaskItem firstItem = checkItems.get(0);
|
||
Long targetDeptId = getTargetDeptIdFromTaskType(firstItem.getTaskTypeId());
|
||
|
||
boolean hasPermission = false;
|
||
|
||
// 检查任务类型配置的接单部门
|
||
if (targetDeptId != null) {
|
||
com.designhub.entity.Department receiverDept = departmentMapper.selectById(targetDeptId);
|
||
if (receiverDept != null && approverId.equals(receiverDept.getLeaderId())) {
|
||
hasPermission = true;
|
||
}
|
||
}
|
||
|
||
// 兼容:也检查主任务的 receiver_dept_id
|
||
if (!hasPermission && mainTask.getReceiverDeptId() != null) {
|
||
com.designhub.entity.Department receiverDept = departmentMapper.selectById(mainTask.getReceiverDeptId());
|
||
if (receiverDept != null && approverId.equals(receiverDept.getLeaderId())) {
|
||
hasPermission = true;
|
||
}
|
||
}
|
||
|
||
if (!hasPermission) {
|
||
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) {
|
||
// 检查第一个子任务是否已有预先指定的接收人
|
||
TaskItem firstItem = groupItems.get(0);
|
||
if (firstItem.getReceiverId() != null) {
|
||
assignedReceiverId = firstItem.getReceiverId();
|
||
log.info("子任务组已预先指定接收人: receiverId={}, 自动使用", assignedReceiverId);
|
||
}
|
||
}
|
||
|
||
// 5. 如果指定了接收人(包括预先指定的),验证接收人并直接分配
|
||
if (assignedReceiverId != null) {
|
||
com.designhub.entity.User receiver = userService.getUserById(assignedReceiverId);
|
||
if (receiver == null) {
|
||
throw new BusinessException(ResultCode.PARAM_ERROR, "指定的接收人不存在");
|
||
}
|
||
|
||
LocalDateTime now = LocalDateTime.now();
|
||
|
||
// 批量分配给指定设计师
|
||
for (TaskItem item : groupItems) {
|
||
item.setStatus("in_progress");
|
||
item.setReceiverId(assignedReceiverId);
|
||
item.setReceiverDeptId(receiver.getDeptId()); // 设置接单部门ID
|
||
item.setUpdateTime(now);
|
||
// 计算并设置deadline(使用工作时间计算)
|
||
if (item.getEstimatedHours() != null && item.getEstimatedHours().compareTo(java.math.BigDecimal.ZERO) > 0) {
|
||
LocalDateTime deadline = workHoursCalculator.calculateDeadline(now, item.getEstimatedHours());
|
||
item.setDeadline(deadline);
|
||
log.info("子任务组审核通过,子任务 {} 设置deadline: {}", item.getId(), deadline);
|
||
}
|
||
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);
|
||
|
||
// 🔥 一级需求任务:通知一级发起人子任务已指派
|
||
if (mainTask.getPrimaryPublisherId() != null) {
|
||
com.designhub.entity.User designer = userService.getUserById(assignedReceiverId);
|
||
String designerName = designer != null ? designer.getRealName() : "未知";
|
||
Notification notification2 = new Notification();
|
||
notification2.setReceiverId(mainTask.getPrimaryPublisherId());
|
||
notification2.setNotificationType("task");
|
||
notification2.setTitle("子任务已指派");
|
||
notification2.setContent("任务《" + mainTask.getTitle() + "》的第 " + groupNo +
|
||
" 组已指派给设计师 " + designerName);
|
||
notification2.setRelatedId(taskId);
|
||
notification2.setRelatedType("task");
|
||
notification2.setPriority(1);
|
||
notification2.setPushDingtalk(1);
|
||
notificationService.sendNotification(notification2);
|
||
log.info("已通知一级发起人 {} 子任务组已指派", mainTask.getPrimaryPublisherId());
|
||
}
|
||
|
||
// 发送钉钉群通知并@指定的设计师
|
||
try {
|
||
java.math.BigDecimal totalScore = groupItems.stream()
|
||
.map(item -> item.getTotalScore() != null ? item.getTotalScore() : java.math.BigDecimal.ZERO)
|
||
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
|
||
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("**任务名称**:").append(mainTask.getTitle()).append(" \n");
|
||
content.append("**任务编号**:").append(mainTask.getTaskNo()).append(" \n");
|
||
|
||
// 任务详情
|
||
if (mainTask.getBrandName() != null && !mainTask.getBrandName().isEmpty()) {
|
||
content.append("**品牌**:").append(mainTask.getBrandName()).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");
|
||
}
|
||
if (mainTask.getTaskSubType() != null && !mainTask.getTaskSubType().isEmpty()) {
|
||
content.append("**任务类型**:").append(mainTask.getTaskSubType()).append(" \n");
|
||
}
|
||
|
||
content.append(" \n");
|
||
|
||
// 任务明细(与第一轮格式完全一致)
|
||
if (!groupItems.isEmpty()) {
|
||
content.append("**任务明细**: \n");
|
||
for (TaskItem item : groupItems) {
|
||
// 通过任务类型ID获取接单部门名称
|
||
String deptName = "未指定";
|
||
if (item.getTaskTypeId() != null) {
|
||
try {
|
||
com.designhub.entity.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());
|
||
com.designhub.entity.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")
|
||
java.util.List<java.util.Map<String, Object>> typeDetails =
|
||
objectMapper.readValue(item.getTypeDetails(), java.util.List.class);
|
||
for (java.util.Map<String, Object> 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());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
content.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("点击下方按钮查看详情");
|
||
|
||
log.info("准备调用钉钉机器人发送消息,title={}", title);
|
||
boolean success = dingTalkService.sendRobotMessage(title, content.toString());
|
||
if (success) {
|
||
log.info("第{}组子任务钉钉群通知发送成功", groupNo);
|
||
} else {
|
||
log.warn("第{}组子任务钉钉群通知发送失败(返回false)", groupNo);
|
||
}
|
||
} catch (Exception e) {
|
||
log.error("发送钉钉群通知异常:taskId={}, groupNo={}, error={}", taskId, groupNo, e.getMessage(), e);
|
||
// 通知发送失败不影响主流程
|
||
}
|
||
}
|
||
|
||
// 🔥 更新主任务状态为已审核通过
|
||
mainTask.setApprovalStatus("approved");
|
||
mainTask.setStatus("pending");
|
||
mainTask.setReceivingApprovalTime(LocalDateTime.now());
|
||
mainTask.setCurrentApproverId(null);
|
||
mainTask.setCurrentApproverRole(null);
|
||
mainTask.setUpdateTime(LocalDateTime.now());
|
||
taskMapper.updateById(mainTask);
|
||
log.info("主任务 {} 接单部门审核通过,状态更新为 approved/pending", taskId);
|
||
}
|
||
|
||
/**
|
||
* 驳回子任务组(接单部门负责人审核不通过)
|
||
* 逻辑:驳回该组所有子任务,并将主任务及其所有子任务设为驳回状态
|
||
*/
|
||
@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) {
|
||
boolean hasPermission = false;
|
||
|
||
if (mainTask.getReceiverDeptId() != null) {
|
||
// 如果任务已指定接单部门,检查用户是否是该部门的负责人
|
||
com.designhub.entity.Department receiverDept = departmentMapper.selectById(mainTask.getReceiverDeptId());
|
||
if (receiverDept != null && rejectorId.equals(receiverDept.getLeaderId())) {
|
||
hasPermission = true;
|
||
log.info("用户 {} 是任务接单部门 {} 的负责人", rejectorId, mainTask.getReceiverDeptId());
|
||
}
|
||
} else {
|
||
// 如果任务未指定接单部门,检查用户是否是任何接单部门的负责人
|
||
List<com.designhub.entity.Department> receivingDepts = departmentMapper.selectList(
|
||
new LambdaQueryWrapper<com.designhub.entity.Department>()
|
||
.eq(com.designhub.entity.Department::getLeaderId, rejectorId)
|
||
.eq(com.designhub.entity.Department::getDeptType, "receiving")
|
||
.eq(com.designhub.entity.Department::getStatus, 1)
|
||
.eq(com.designhub.entity.Department::getDeleted, 0)
|
||
);
|
||
if (!receivingDepts.isEmpty()) {
|
||
hasPermission = true;
|
||
log.info("用户 {} 是接单部门负责人(管理的接单部门:{}),允许驳回未指定接单部门的任务",
|
||
rejectorId, receivingDepts.stream().map(com.designhub.entity.Department::getDeptName).collect(java.util.stream.Collectors.toList()));
|
||
}
|
||
}
|
||
|
||
if (!hasPermission) {
|
||
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) {
|
||
log.info("===== getPendingApprovalGroups 开始 =====");
|
||
log.info("查询参数 receiverDeptId: {}", receiverDeptId);
|
||
|
||
// 查询所有有待审核子任务组的主任务
|
||
List<TaskItem> pendingItems = taskItemMapper.selectList(
|
||
new LambdaQueryWrapper<TaskItem>()
|
||
.eq(TaskItem::getStatus, "pending_approval")
|
||
);
|
||
|
||
log.info("找到 {} 个 pending_approval 状态的子任务", pendingItems.size());
|
||
|
||
// 按主任务和组号分组
|
||
Map<String, Map<String, Object>> groupMap = new HashMap<>();
|
||
for (TaskItem item : pendingItems) {
|
||
log.info("处理子任务 ID: {}, item_no: {}, task_type_id: {}",
|
||
item.getId(), item.getItemNo(), item.getTaskTypeId());
|
||
|
||
Task mainTask = taskMapper.selectById(item.getTaskId());
|
||
if (mainTask == null) {
|
||
log.warn("子任务 {} 的主任务不存在,跳过", item.getId());
|
||
continue;
|
||
}
|
||
|
||
// 判断是否有权限查看该子任务
|
||
boolean hasPermission = false;
|
||
if (receiverDeptId == null) {
|
||
// 管理员可以看到所有任务
|
||
hasPermission = true;
|
||
log.info("管理员模式,有权限");
|
||
} else {
|
||
// 部门负责人:检查子任务的任务类型配置的 allowed_dept_ids 是否包含该部门
|
||
Long targetDeptId = getTargetDeptIdFromTaskType(item.getTaskTypeId());
|
||
log.info("子任务 {} 的目标部门ID: {}, 查询的部门ID: {}",
|
||
item.getId(), targetDeptId, receiverDeptId);
|
||
|
||
if (targetDeptId != null && targetDeptId.equals(receiverDeptId)) {
|
||
hasPermission = true;
|
||
log.info("匹配成功(通过任务类型配置)");
|
||
}
|
||
// 兼容:也检查主任务的 receiver_dept_id
|
||
if (!hasPermission && mainTask.getReceiverDeptId() != null
|
||
&& mainTask.getReceiverDeptId().equals(receiverDeptId)) {
|
||
hasPermission = true;
|
||
log.info("匹配成功(通过主任务接单部门)");
|
||
}
|
||
}
|
||
|
||
log.info("子任务 {} 权限检查结果: {}", item.getId(), hasPermission);
|
||
|
||
if (hasPermission) {
|
||
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());
|
||
groupInfo.put("itemCount", 1);
|
||
groupInfo.put("createTime", item.getCreateTime());
|
||
|
||
// 添加主任务的完整信息
|
||
groupInfo.put("brandId", mainTask.getBrandId());
|
||
groupInfo.put("brandName", mainTask.getBrandName());
|
||
groupInfo.put("categoryId", mainTask.getCategoryId());
|
||
groupInfo.put("categoryName", mainTask.getCategoryName());
|
||
groupInfo.put("platformId", mainTask.getPlatformId());
|
||
groupInfo.put("platformName", mainTask.getPlatformName());
|
||
groupInfo.put("spuInfo", mainTask.getSpuInfo());
|
||
groupInfo.put("quantity", mainTask.getQuantity());
|
||
groupInfo.put("previewImage", mainTask.getPreviewImage());
|
||
groupInfo.put("attachmentUrl", mainTask.getAttachmentUrl());
|
||
groupInfo.put("remark", mainTask.getRemark());
|
||
// 使用子任务自己的typeDetails(子任务创建时已经保存了明细)
|
||
groupInfo.put("typeDetails", item.getTypeDetails());
|
||
|
||
// 一级需求部门发起人信息(对于一级需求部门任务)
|
||
groupInfo.put("primaryPublisherId", mainTask.getPrimaryPublisherId());
|
||
groupInfo.put("primaryPublisherDeptId", mainTask.getPrimaryPublisherDeptId());
|
||
if (mainTask.getPrimaryPublisherId() != null) {
|
||
com.designhub.entity.User primaryPublisher = userMapper.selectById(mainTask.getPrimaryPublisherId());
|
||
if (primaryPublisher != null) {
|
||
groupInfo.put("primaryPublisherName", primaryPublisher.getRealName());
|
||
}
|
||
}
|
||
if (mainTask.getPrimaryPublisherDeptId() != null) {
|
||
com.designhub.entity.Department primaryDept = departmentMapper.selectById(mainTask.getPrimaryPublisherDeptId());
|
||
if (primaryDept != null) {
|
||
groupInfo.put("primaryPublisherDeptName", primaryDept.getDeptName());
|
||
}
|
||
}
|
||
|
||
// 是否是一级需求部门任务
|
||
groupInfo.put("isPrimaryTask", mainTask.getIsPrimaryTask());
|
||
|
||
// 明细完善人信息(需求部门成员)
|
||
groupInfo.put("detailCompleterId", mainTask.getDetailCompleterId());
|
||
if (mainTask.getDetailCompleterId() != null) {
|
||
com.designhub.entity.User detailCompleter = userMapper.selectById(mainTask.getDetailCompleterId());
|
||
if (detailCompleter != null) {
|
||
groupInfo.put("detailCompleterName", detailCompleter.getRealName());
|
||
}
|
||
}
|
||
|
||
// 发起人信息:对于一级需求任务,发起人显示为明细完善人;否则显示原始发起人
|
||
if (mainTask.getIsPrimaryTask() != null && mainTask.getIsPrimaryTask() == 1
|
||
&& mainTask.getDetailCompleterId() != null) {
|
||
// 一级需求任务:发起人 = 明细完善人
|
||
groupInfo.put("publisherId", mainTask.getDetailCompleterId());
|
||
com.designhub.entity.User detailCompleter = userMapper.selectById(mainTask.getDetailCompleterId());
|
||
if (detailCompleter != null) {
|
||
groupInfo.put("publisherName", detailCompleter.getRealName());
|
||
groupInfo.put("publisherDeptName", detailCompleter.getDeptName());
|
||
}
|
||
} else {
|
||
// 普通任务:发起人 = 原始发起人
|
||
groupInfo.put("publisherId", mainTask.getPublisherId());
|
||
if (mainTask.getPublisherId() != null) {
|
||
com.designhub.entity.User publisher = userMapper.selectById(mainTask.getPublisherId());
|
||
if (publisher != null) {
|
||
groupInfo.put("publisherName", publisher.getRealName());
|
||
groupInfo.put("publisherDeptName", publisher.getDeptName());
|
||
}
|
||
}
|
||
}
|
||
|
||
// 主任务的任务类型信息
|
||
groupInfo.put("taskTypeId", mainTask.getTaskTypeId());
|
||
// 使用taskSubType显示一级分类(新品/优化/改图等)
|
||
String taskSubType = mainTask.getTaskSubType();
|
||
if (taskSubType != null && !taskSubType.isEmpty()) {
|
||
// 转换为中文显示
|
||
String taskTypeName = switch (taskSubType) {
|
||
case "new" -> "新品";
|
||
case "optimize" -> "优化";
|
||
case "redesign" -> "改图";
|
||
case "other" -> "其他";
|
||
default -> taskSubType;
|
||
};
|
||
groupInfo.put("taskTypeName", taskTypeName);
|
||
groupInfo.put("taskSubType", taskSubType);
|
||
} else if (mainTask.getTaskTypeId() != null) {
|
||
// 兼容:如果没有taskSubType,使用taskTypeId查询
|
||
com.designhub.entity.DemandType taskType = demandTypeMapper.selectById(mainTask.getTaskTypeId());
|
||
if (taskType != null) {
|
||
groupInfo.put("taskTypeName", taskType.getName());
|
||
}
|
||
}
|
||
// 子任务的具体任务类型ID(用于审核流程)
|
||
groupInfo.put("subTaskTypeId", item.getTaskTypeId());
|
||
|
||
// 添加审核人信息(目标部门负责人)
|
||
Long targetDeptId = getTargetDeptIdFromTaskType(item.getTaskTypeId());
|
||
if (targetDeptId != null) {
|
||
com.designhub.entity.Department targetDept = departmentMapper.selectById(targetDeptId);
|
||
if (targetDept != null && targetDept.getLeaderId() != null) {
|
||
com.designhub.entity.User leader = userMapper.selectById(targetDept.getLeaderId());
|
||
if (leader != null) {
|
||
groupInfo.put("currentApproverName", leader.getRealName());
|
||
groupInfo.put("currentApproverRole", "receiving_leader");
|
||
groupInfo.put("approvalStatus", "pending_receiving_approval");
|
||
}
|
||
}
|
||
}
|
||
|
||
// 计算子任务组总积分(BigDecimal类型)
|
||
groupInfo.put("totalScore", item.getTotalScore() != null ? item.getTotalScore() : java.math.BigDecimal.ZERO);
|
||
|
||
// 获取前置子任务的素材下载链接(流水线继承)
|
||
if (item.getGroupNo() != null && item.getGroupNo() > 1) {
|
||
// 查找前一组已完成的子任务的下载链接
|
||
List<TaskItem> prevGroupItems = taskItemMapper.selectList(
|
||
new LambdaQueryWrapper<TaskItem>()
|
||
.eq(TaskItem::getTaskId, item.getTaskId())
|
||
.eq(TaskItem::getGroupNo, item.getGroupNo() - 1)
|
||
.eq(TaskItem::getStatus, "completed")
|
||
);
|
||
if (!prevGroupItems.isEmpty()) {
|
||
// 收集所有前置子任务的下载链接
|
||
List<String> prevDownloadUrls = prevGroupItems.stream()
|
||
.filter(pi -> pi.getDownloadUrl() != null && !pi.getDownloadUrl().isEmpty())
|
||
.map(TaskItem::getDownloadUrl)
|
||
.collect(java.util.stream.Collectors.toList());
|
||
if (!prevDownloadUrls.isEmpty()) {
|
||
groupInfo.put("prevDownloadUrls", prevDownloadUrls);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 存储子任务列表,用于显示详情
|
||
List<Map<String, Object>> itemsList = new java.util.ArrayList<>();
|
||
Map<String, Object> itemInfo = new HashMap<>();
|
||
itemInfo.put("id", item.getId());
|
||
itemInfo.put("itemNo", item.getItemNo());
|
||
itemInfo.put("taskTypeId", item.getTaskTypeId());
|
||
itemInfo.put("score", item.getTotalScore());
|
||
itemsList.add(itemInfo);
|
||
groupInfo.put("items", itemsList);
|
||
|
||
groupMap.put(key, groupInfo);
|
||
} else {
|
||
Map<String, Object> groupInfo = groupMap.get(key);
|
||
groupInfo.put("itemCount", (Integer) groupInfo.get("itemCount") + 1);
|
||
// 累加积分(BigDecimal类型)
|
||
java.math.BigDecimal currentScore = (java.math.BigDecimal) groupInfo.get("totalScore");
|
||
java.math.BigDecimal itemScore = item.getTotalScore() != null ? item.getTotalScore() : java.math.BigDecimal.ZERO;
|
||
groupInfo.put("totalScore", currentScore.add(itemScore));
|
||
|
||
// 添加到子任务列表
|
||
@SuppressWarnings("unchecked")
|
||
List<Map<String, Object>> itemsList = (List<Map<String, Object>>) groupInfo.get("items");
|
||
Map<String, Object> itemInfo = new HashMap<>();
|
||
itemInfo.put("id", item.getId());
|
||
itemInfo.put("itemNo", item.getItemNo());
|
||
itemInfo.put("taskTypeId", item.getTaskTypeId());
|
||
itemInfo.put("score", item.getTotalScore());
|
||
itemsList.add(itemInfo);
|
||
}
|
||
}
|
||
}
|
||
|
||
return new java.util.ArrayList<>(groupMap.values());
|
||
}
|
||
|
||
/**
|
||
* 从子任务编号解析部门序号
|
||
* 编号格式:任务编号-部门序号-部门内序号,例如 TASK20260114-01-02
|
||
* @param itemNo 子任务编号
|
||
* @return 部门序号,解析失败返回1
|
||
*/
|
||
private int parseDeptIndexFromItemNo(String itemNo) {
|
||
if (itemNo == null || itemNo.isEmpty()) {
|
||
return 1;
|
||
}
|
||
|
||
String[] parts = itemNo.split("-");
|
||
if (parts.length >= 3) {
|
||
try {
|
||
// 新格式:TASK...-部门序号-部门内序号
|
||
// 倒数第二段是部门序号
|
||
return Integer.parseInt(parts[parts.length - 2]);
|
||
} catch (NumberFormatException e) {
|
||
log.warn("解析子任务编号部门序号失败: {}", itemNo);
|
||
}
|
||
} else if (parts.length == 2) {
|
||
// 旧格式:TASK...-序号,认为是第一个部门
|
||
return 1;
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
/**
|
||
* 从任务类型配置中获取目标接单部门ID
|
||
* 如果当前类型未配置,则递归向上查找父级类型的配置
|
||
*/
|
||
private Long getTargetDeptIdFromTaskType(Long taskTypeId) {
|
||
log.debug("getTargetDeptIdFromTaskType 调用, taskTypeId: {}", taskTypeId);
|
||
|
||
if (taskTypeId == null) {
|
||
log.debug("taskTypeId 为空,返回 null");
|
||
return null;
|
||
}
|
||
|
||
com.designhub.entity.DemandType taskType = demandTypeMapper.selectById(taskTypeId);
|
||
if (taskType == null) {
|
||
log.warn("任务类型 {} 不存在", taskTypeId);
|
||
return null;
|
||
}
|
||
|
||
log.debug("任务类型: id={}, name={}, parentId={}, allowedDeptIds={}",
|
||
taskType.getId(), taskType.getName(), taskType.getParentId(), taskType.getAllowedDeptIds());
|
||
|
||
// 如果当前类型配置了allowed_dept_ids,返回第一个部门ID
|
||
String allowedDeptIds = taskType.getAllowedDeptIds();
|
||
if (allowedDeptIds != null && !allowedDeptIds.isEmpty()) {
|
||
try {
|
||
String[] deptIds = allowedDeptIds.split(",");
|
||
if (deptIds.length > 0) {
|
||
Long result = Long.parseLong(deptIds[0].trim());
|
||
log.debug("找到 allowed_dept_ids: {}, 返回第一个: {}", allowedDeptIds, result);
|
||
return result;
|
||
}
|
||
} catch (NumberFormatException e) {
|
||
log.warn("解析allowed_dept_ids失败: {}", allowedDeptIds, e);
|
||
}
|
||
}
|
||
|
||
// 如果当前类型未配置,递归查找父级
|
||
if (taskType.getParentId() != null) {
|
||
log.debug("当前类型未配置 allowed_dept_ids, 递归查找父级: {}", taskType.getParentId());
|
||
return getTargetDeptIdFromTaskType(taskType.getParentId());
|
||
}
|
||
|
||
log.debug("未找到配置的部门,返回 null");
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 格式化工时显示(基于工作时间配置)
|
||
*/
|
||
private String formatEstimatedHours(java.math.BigDecimal hours) {
|
||
if (hours == null) {
|
||
return "未设置";
|
||
}
|
||
String formatted = workHoursCalculator.formatWorkHours(hours);
|
||
// WorkHoursCalculator 返回 "--" 表示无效,这里保持 "未设置" 的语义
|
||
return "--".equals(formatted) ? "未设置" : formatted;
|
||
}
|
||
}
|