qiangdanxitong/backend/src/main/java/com/designhub/service/TaskApprovalService.java

979 lines
47 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

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

package com.designhub.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.designhub.dto.PipelineTaskDTO;
import com.designhub.dto.TaskApprovalDTO;
import com.designhub.dto.TaskUpdateDTO;
import com.designhub.entity.*;
import com.designhub.exception.BusinessException;
import com.designhub.mapper.*;
import com.designhub.common.ResultCode;
import com.designhub.entity.Brand;
import com.designhub.entity.Category;
import com.designhub.entity.Platform;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;
/**
* 任务审核服务
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class TaskApprovalService {
private final TaskMapper taskMapper;
private final TaskItemMapper taskItemMapper;
private final TaskApprovalLogMapper approvalLogMapper;
private final DepartmentMapper departmentMapper;
private final UserMapper userMapper;
private final BrandMapper brandMapper;
private final CategoryMapper categoryMapper;
private final PlatformMapper platformMapper;
private final NotificationService notificationService;
private final TeamScorePoolService teamScorePoolService;
private final TaskService taskService;
private final ConfigService configService;
private final ObjectMapper objectMapper;
private final DingTalkService dingTalkService;
private final com.designhub.util.WorkHoursCalculator workHoursCalculator;
/**
* 需求部门负责人审核通过
*/
@Transactional(rollbackFor = Exception.class)
public void approveDemandLevel(Long taskId, Long approverId, TaskApprovalDTO dto) {
// 1. 验证任务和权限
Task task = getTaskForApproval(taskId, "pending_demand_approval");
validateApprover(task, approverId, "demand_leader");
// 2. 应用任务修改(如果有)
if (dto.getTaskUpdates() != null) {
applyTaskUpdates(task, dto.getTaskUpdates());
logApproval(taskId, approverId, 1, "modify", dto.getComment());
}
// 3. 所有任务都需要接单部门负责人审核(无论是否预先指定了设计师)
String releaseType = task.getReleaseType();
// 确定接单部门负责人
Long receivingLeaderId = determineReceivingLeader(task);
if (receivingLeaderId == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "无法确定接单部门负责人,请在系统设置中为接单部门配置负责人");
}
// 更新任务状态为待接单部门审核
task.setApprovalStatus("pending_receiving_approval");
task.setDemandApprovalTime(LocalDateTime.now());
task.setCurrentApproverId(receivingLeaderId);
task.setCurrentApproverRole("receiving_leader");
taskMapper.updateById(task);
// 记录审核日志
logApproval(taskId, approverId, 1, "approve", dto.getComment());
// 发送通知给接单部门负责人
User receivingLeader = userMapper.selectById(receivingLeaderId);
if (receivingLeader != null) {
// 根据是否预先指定了设计师,调整通知内容
String notificationContent;
if ("assigned".equals(releaseType) && task.getReceiverId() != null) {
User preAssignedDesigner = userMapper.selectById(task.getReceiverId());
String designerName = preAssignedDesigner != null ? preAssignedDesigner.getRealName() : "未知";
notificationContent = String.format("任务【%s】需要您审核确认(已预先指定设计师:%s)", task.getTitle(), designerName);
} else {
notificationContent = String.format("任务【%s】需要您审核并分配设计师", task.getTitle());
}
notificationService.sendNotification(
receivingLeaderId,
"新任务待审核",
notificationContent,
"task_approval",
taskId
);
}
log.info("需求部门负责人审核通过,任务ID:{},审核人:{},发布类型:{},等待接单部门负责人审核", taskId, approverId, releaseType);
}
/**
* 需求部门负责人驳回
*/
@Transactional(rollbackFor = Exception.class)
public void rejectByDemandLeader(Long taskId, Long approverId, String reason) {
Task task = getTaskForApproval(taskId, "pending_demand_approval");
validateApprover(task, approverId, "demand_leader");
task.setApprovalStatus("rejected_by_demand");
task.setStatus("draft");
task.setRejectReason(reason); // 保存驳回原因
task.setCurrentApproverId(null);
task.setCurrentApproverRole(null);
taskMapper.updateById(task);
logApproval(taskId, approverId, 1, "reject", reason);
// 通知发起人
notificationService.sendNotification(
task.getPublisherId(),
"任务已被驳回",
String.format("您的任务【%s】被需求部门负责人驳回,原因:%s", task.getTitle(), reason),
"task_reject",
taskId
);
// 如果任务有指定的处理人,发送群通知告知驳回
if (task.getReceiverId() != null) {
try {
com.designhub.entity.User receiver = userMapper.selectById(task.getReceiverId());
if (receiver != null) {
String title = "任务已驳回";
StringBuilder content = new StringBuilder();
content.append("-------------------------------- \n");
content.append(" 【 任 务 已 驳 回 】 \n");
content.append("-------------------------------- \n\n");
content.append("任务名称:").append(task.getTitle()).append(" \n");
content.append("任务编号:").append(task.getTaskNo()).append(" \n");
content.append("原处理人:").append(receiver.getRealName()).append(" \n");
content.append("驳回原因:").append(reason).append(" \n");
content.append(" \n");
content.append("--------------------------------");
java.util.List<String> atMobiles = receiver.getPhone() != null ?
java.util.Arrays.asList(receiver.getPhone()) : null;
dingTalkService.sendRobotMessage(title, content.toString(), atMobiles);
}
} catch (Exception e) {
log.error("发送任务驳回群通知失败", e);
}
}
log.info("需求部门负责人驳回任务,任务ID:{},审核人:{}", taskId, approverId);
}
/**
* 接单部门负责人审核通过
* @param taskId 任务ID
* @param approverId 审核人ID
* @param dto 审核数据(包含备注和任务修改)
* @param assignedReceiverId 指定的接收人ID(可选,如果任务已预先指定设计师则忽略此参数)
*/
@Transactional(rollbackFor = Exception.class)
public void approveReceivingLevel(Long taskId, Long approverId, TaskApprovalDTO dto, Long assignedReceiverId) {
Task task = getTaskForApproval(taskId, "pending_receiving_approval");
validateApprover(task, approverId, "receiving_leader");
// 应用任务修改(如果有)
if (dto.getTaskUpdates() != null) {
applyTaskUpdates(task, dto.getTaskUpdates());
logApproval(taskId, approverId, 2, "modify", dto.getComment());
}
// 检查任务是否已预先指定了设计师
boolean isPreAssigned = "assigned".equals(task.getReleaseType()) && task.getReceiverId() != null;
Long finalReceiverId = null;
if (isPreAssigned) {
// 任务已预先指定设计师,不允许修改
if (assignedReceiverId != null && !assignedReceiverId.equals(task.getReceiverId())) {
log.warn("任务【{}】已预先指定设计师,忽略接单部门负责人的指定操作", task.getTitle());
}
finalReceiverId = task.getReceiverId();
log.info("任务【{}】使用预先指定的设计师: {}", task.getTitle(), finalReceiverId);
} else if (assignedReceiverId != null) {
// 接单部门负责人新指定设计师
com.designhub.entity.User receiver = userMapper.selectById(assignedReceiverId);
if (receiver == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "指定的接收人不存在");
}
task.setReceiverId(assignedReceiverId);
task.setReceiverDeptId(receiver.getDeptId());
task.setReleaseType("assigned"); // 设置为指定模式
finalReceiverId = assignedReceiverId;
log.info("任务【{}】已指定给设计师【{}】", task.getTitle(), receiver.getRealName());
}
// 判断是否为流水线任务
if (task.getIsPipeline() != null && task.getIsPipeline() == 1) {
// 流水线任务:先保存主任务的receiverId(如果已设置),确保updatePipelineSubTasksForAssigned能读取到
if (finalReceiverId != null) {
task.setStatus("pending"); // 临时状态,splitIntoPipelineSubTasks会更新
taskMapper.updateById(task);
log.info("流水线任务主任务receiverId已保存:taskId={}, receiverId={}", task.getId(), finalReceiverId);
}
// 拆分子任务
splitIntoPipelineSubTasks(task);
} else {
// 普通任务:根据是否有接收人设置状态
if (finalReceiverId != null) {
// 如果接收人部门ID还没设置,需要设置
if (task.getReceiverDeptId() == null) {
com.designhub.entity.User receiver = userMapper.selectById(finalReceiverId);
if (receiver != null) {
task.setReceiverDeptId(receiver.getDeptId());
}
}
task.setStatus("in_progress"); // 指定接单直接进入进行中状态
LocalDateTime now = LocalDateTime.now();
task.setStartTime(now);
task.setActualStartTime(now);
// 重新计算deadline:从接单时间开始计算(使用工作时间计算)
if (task.getEstimatedHours() != null && task.getEstimatedHours().compareTo(java.math.BigDecimal.ZERO) > 0) {
task.setDeadline(workHoursCalculator.calculateDeadline(now, task.getEstimatedHours()));
log.info("任务deadline已重新计算:从接单时间 {} 开始,预估 {} 小时,截止时间 {}",
now, task.getEstimatedHours(), task.getDeadline());
}
} else {
task.setStatus("pending"); // 未指定则发布到大厅
}
}
// 更新审核状态
task.setApprovalStatus("approved");
task.setReceivingApprovalTime(LocalDateTime.now());
task.setCurrentApproverId(null);
task.setCurrentApproverRole(null);
taskMapper.updateById(task);
// 扣除部门积分
try {
teamScorePoolService.deductScore(task.getPublisherDeptId(), task.getScore() != null ? task.getScore().intValue() : 0);
} catch (Exception e) {
log.error("扣除部门积分失败", e);
throw new BusinessException(ResultCode.ERROR, "扣除部门积分失败:" + e.getMessage());
}
// 记录审核日志
String logComment = dto.getComment();
if (finalReceiverId != null) {
com.designhub.entity.User receiver = userMapper.selectById(finalReceiverId);
String assignNote = isPreAssigned ? "(预先指定)" : "";
logComment = (logComment != null ? logComment + ";" : "") +
"已指定给设计师" + assignNote + ":" + (receiver != null ? receiver.getRealName() : finalReceiverId);
}
logApproval(taskId, approverId, 2, "approve", logComment);
// 发送钉钉通知
// 对于流水线任务,需要给第一个部门的所有子任务设计师发送通知
java.util.Set<Long> notifiedReceiverIds = new java.util.HashSet<>();
if (task.getIsPipeline() != null && task.getIsPipeline() == 1) {
// 流水线任务:查询第一个部门的所有子任务,给每个设计师发送通知
List<TaskItem> firstDeptItems = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, task.getId())
.eq(TaskItem::getStatus, "in_progress")
.isNotNull(TaskItem::getReceiverId)
);
for (TaskItem item : firstDeptItems) {
if (item.getReceiverId() != null && !notifiedReceiverIds.contains(item.getReceiverId())) {
notifiedReceiverIds.add(item.getReceiverId());
sendTaskAssignedNotification(task, item.getReceiverId(), isPreAssigned);
}
}
log.info("流水线任务【{}】已通知 {} 个设计师", task.getTitle(), notifiedReceiverIds.size());
} else if (finalReceiverId != null) {
// 普通任务:只通知一个设计师
sendTaskAssignedNotification(task, finalReceiverId, isPreAssigned);
notifiedReceiverIds.add(finalReceiverId);
log.info("普通任务【{}】已通知设计师: {}", task.getTitle(), finalReceiverId);
}
// 如果没有指定设计师(发布到大厅),发送群通知
if (notifiedReceiverIds.isEmpty()) {
log.info("任务【{}】已发布到任务大厅,准备发送钉钉群通知", task.getTitle());
// 任务发布到大厅,发送钉钉群机器人通知
// 注意:钉钉Markdown换行需要两个空格+换行符
try {
String title = "新任务发布";
StringBuilder content = new StringBuilder();
// 基本信息
content.append("**任务名称**:").append(task.getTitle()).append(" \n");
content.append("**任务编号**:").append(task.getTaskNo()).append(" \n");
// 任务详情
if (task.getBrandName() != null && !task.getBrandName().isEmpty()) {
content.append("**品牌**:").append(task.getBrandName()).append(" \n");
}
if (task.getPlatformName() != null && !task.getPlatformName().isEmpty()) {
content.append("**平台**:").append(task.getPlatformName()).append(" \n");
}
if (task.getSpuInfo() != null && !task.getSpuInfo().isEmpty()) {
content.append("**SPU**:").append(task.getSpuInfo()).append(" \n");
}
if (task.getTaskSubType() != null && !task.getTaskSubType().isEmpty()) {
content.append("**任务类型**:").append(task.getTaskSubType()).append(" \n");
}
content.append(" \n");
// 查询子任务获取任务类型明细
List<TaskItem> taskItems = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, task.getId())
.orderByAsc(TaskItem::getGroupNo)
);
// 时间格式化器:MM月dd日 HH:mm
DateTimeFormatter groupDeadlineFormatter = DateTimeFormatter.ofPattern("MM月dd日 HH:mm");
if (!taskItems.isEmpty()) {
content.append("**任务明细**: \n");
for (TaskItem item : taskItems) {
// 通过任务类型ID获取接单部门名称
String deptName = "未指定";
if (item.getTaskTypeId() != null) {
try {
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());
Department dept = departmentMapper.selectById(firstDeptId);
if (dept != null) {
deptName = dept.getDeptName();
}
}
} catch (Exception e) {
log.warn("获取部门名称失败: {}", e.getMessage());
}
}
// 格式化工时显示
String hoursDisplay = formatEstimatedHours(item.getEstimatedHours());
// 格式化截止时间
String groupDeadlineDisplay = "";
if (item.getDeadline() != null) {
groupDeadlineDisplay = " | 截止: " + item.getDeadline().format(groupDeadlineFormatter);
}
content.append(" \n");
content.append("▸ **").append(deptName).append("** | ")
.append(item.getTotalScore()).append("分 | ")
.append(hoursDisplay).append(groupDeadlineDisplay).append(" \n");
// 解析并显示任务类型明细
if (item.getTypeDetails() != null && !item.getTypeDetails().isEmpty()) {
try {
@SuppressWarnings("unchecked")
List<java.util.Map<String, Object>> typeDetails =
objectMapper.readValue(item.getTypeDetails(), 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());
}
}
}
} else {
// 没有子任务时显示总积分和工时
content.append("**任务积分**:").append(task.getScore()).append(" 分 \n");
if (task.getEstimatedHours() != null) {
content.append("**预估工时**:").append(formatEstimatedHours(task.getEstimatedHours())).append(" \n");
}
}
content.append(" \n");
// 发起人信息
if (task.getPublisherName() != null && !task.getPublisherName().isEmpty()) {
content.append("**发起人**:").append(task.getPublisherName()).append(" \n");
}
if (task.getPublisherDeptName() != null && !task.getPublisherDeptName().isEmpty()) {
content.append("**发起部门**:").append(task.getPublisherDeptName()).append(" \n");
}
content.append(" \n");
content.append("点击下方按钮查看详情");
log.info("准备调用钉钉机器人发送消息,title={}", title);
boolean success = dingTalkService.sendRobotMessage(title, content.toString());
if (success) {
log.info("任务【{}】钉钉群通知发送成功", task.getTitle());
} else {
log.warn("任务【{}】钉钉群通知发送失败(返回false)", task.getTitle());
}
} catch (Exception e) {
log.error("发送钉钉群通知异常:taskId={}, error={}", taskId, e.getMessage(), e);
// 通知发送失败不影响主流程
}
}
log.info("接单部门负责人审核通过,任务ID:{},审核人:{},指定接收人:{}",
taskId, approverId, assignedReceiverId);
}
/**
* 接单部门负责人审核通过(重载方法,保持向后兼容)
*/
@Transactional(rollbackFor = Exception.class)
public void approveReceivingLevel(Long taskId, Long approverId, TaskApprovalDTO dto) {
approveReceivingLevel(taskId, approverId, dto, null);
}
/**
* 接单部门负责人驳回
*/
@Transactional(rollbackFor = Exception.class)
public void rejectByReceivingLeader(Long taskId, Long approverId, String reason) {
Task task = getTaskForApproval(taskId, "pending_receiving_approval");
validateApprover(task, approverId, "receiving_leader");
// 退回给需求部门负责人
Department demandDept = departmentMapper.selectById(task.getPublisherDeptId());
if (demandDept == null || demandDept.getLeaderId() == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "需求部门负责人不存在");
}
task.setApprovalStatus("rejected_by_receiving");
task.setStatus("draft");
task.setRejectReason(reason); // 保存驳回原因
task.setCurrentApproverId(demandDept.getLeaderId());
task.setCurrentApproverRole("demand_leader");
taskMapper.updateById(task);
logApproval(taskId, approverId, 2, "reject", reason);
// 1. 通知任务发起人
notificationService.sendNotification(
task.getPublisherId(),
"任务已被驳回",
String.format("您的任务【%s】被接单部门负责人驳回,原因:%s,请修改后重新提交", task.getTitle(), reason),
"task_reject",
taskId
);
log.info("已通知任务发起人: publisherId={}", task.getPublisherId());
// 2. 如果需求部门负责人不是发起人,也通知需求部门负责人
if (!demandDept.getLeaderId().equals(task.getPublisherId())) {
notificationService.sendNotification(
demandDept.getLeaderId(),
"任务已被驳回",
String.format("任务【%s】被接单部门负责人驳回,原因:%s", task.getTitle(), reason),
"task_reject",
taskId
);
log.info("已通知需求部门负责人: leaderId={}", demandDept.getLeaderId());
}
// 3. 如果任务有指定的处理人,发送群通知告知驳回
if (task.getReceiverId() != null) {
try {
com.designhub.entity.User receiver = userMapper.selectById(task.getReceiverId());
if (receiver != null) {
String title = "任务已驳回";
StringBuilder content = new StringBuilder();
content.append("-------------------------------- \n");
content.append(" 【 任 务 已 驳 回 】 \n");
content.append("-------------------------------- \n\n");
content.append("任务名称:").append(task.getTitle()).append(" \n");
content.append("任务编号:").append(task.getTaskNo()).append(" \n");
content.append("原处理人:").append(receiver.getRealName()).append(" \n");
content.append("驳回原因:").append(reason).append(" \n");
content.append(" \n");
content.append("--------------------------------");
java.util.List<String> atMobiles = receiver.getPhone() != null ?
java.util.Arrays.asList(receiver.getPhone()) : null;
dingTalkService.sendRobotMessage(title, content.toString(), atMobiles);
}
} catch (Exception e) {
log.error("发送任务驳回群通知失败", e);
}
}
log.info("接单部门负责人驳回任务,任务ID:{},审核人:{}", taskId, approverId);
}
/**
* 获取待审核任务列表
* 管理员可以看到所有待审核任务
*/
public List<Task> getPendingApprovalTasks(Long userId) {
// 检查用户是否是管理员
User user = userMapper.selectById(userId);
boolean isAdmin = user != null && Integer.valueOf(1).equals(user.getIsAdmin());
LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
wrapper.in(Task::getApprovalStatus, "pending_demand_approval", "pending_receiving_approval");
// 非管理员只能看到分配给自己的待审核任务
if (!isAdmin) {
wrapper.eq(Task::getCurrentApproverId, userId);
}
wrapper.orderByDesc(Task::getCreateTime);
List<Task> tasks = taskMapper.selectList(wrapper);
// 填充关联信息
for (Task task : tasks) {
// 填充发起人姓名
if (task.getPublisherId() != null) {
User publisher = userMapper.selectById(task.getPublisherId());
if (publisher != null) {
task.setPublisherName(publisher.getUsername());
}
}
// 填充发起部门名称
if (task.getPublisherDeptId() != null) {
Department dept = departmentMapper.selectById(task.getPublisherDeptId());
if (dept != null) {
task.setPublisherDeptName(dept.getDeptName());
}
}
// 填充品牌名称
if (task.getBrandId() != null) {
Brand brand = brandMapper.selectById(task.getBrandId());
if (brand != null) {
// Brand实体类中name字段映射到brand_name,使用getName()
task.setBrandName(brand.getName());
} else {
log.warn("任务 {} 品牌ID {} 不存在", task.getId(), task.getBrandId());
}
}
// 填充类目名称
if (task.getCategoryId() != null) {
Category category = categoryMapper.selectById(task.getCategoryId());
if (category != null) {
// Category实体类中name字段映射到category_name,使用getName()
task.setCategoryName(category.getName());
} else {
log.warn("任务 {} 类目ID {} 不存在", task.getId(), task.getCategoryId());
}
}
// 填充平台名称
if (task.getPlatformId() != null) {
Platform platform = platformMapper.selectById(task.getPlatformId());
if (platform != null) {
// Platform实体类中name字段映射到platform_name,使用getName()
task.setPlatformName(platform.getName());
} else {
log.warn("任务 {} 平台ID {} 不存在", task.getId(), task.getPlatformId());
}
}
// 填充当前审核人姓名
if (task.getCurrentApproverId() != null) {
User approver = userMapper.selectById(task.getCurrentApproverId());
if (approver != null) {
task.setCurrentApproverName(approver.getRealName() != null ? approver.getRealName() : approver.getUsername());
}
}
}
return tasks;
}
/**
* 获取任务并验证审核状态
*/
private Task getTaskForApproval(Long taskId, String expectedStatus) {
Task task = taskMapper.selectById(taskId);
if (task == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "任务不存在");
}
if (!expectedStatus.equals(task.getApprovalStatus())) {
throw new BusinessException(ResultCode.TASK_STATUS_ERROR,
"任务当前状态不允许此操作,当前状态:" + task.getApprovalStatus());
}
return task;
}
/**
* 验证审核人权限
* 管理员可以代审核所有任务
*/
private void validateApprover(Task task, Long approverId, String expectedRole) {
// 检查是否是管理员,管理员可以代审核所有任务
User approver = userMapper.selectById(approverId);
if (approver != null && Integer.valueOf(1).equals(approver.getIsAdmin())) {
log.info("管理员 {} 代审核任务 {}", approver.getRealName(), task.getId());
return; // 管理员绕过权限验证
}
if (!approverId.equals(task.getCurrentApproverId())) {
throw new BusinessException(ResultCode.FORBIDDEN, "您无权审核此任务");
}
if (!expectedRole.equals(task.getCurrentApproverRole())) {
throw new BusinessException(ResultCode.FORBIDDEN, "审核角色不匹配");
}
}
/**
* 确定接单部门负责人(支持三级分类继承二级分类的配置)
*/
private Long determineReceivingLeader(Task task) {
// 根据任务类型的allowedDeptIds确定接单部门
// 简化版本:取第一个任务类型的第一个允许部门
String taskTypeIds = task.getTaskTypeIds();
if (taskTypeIds == null || taskTypeIds.isEmpty()) {
return null;
}
String[] typeIdArray = taskTypeIds.split(",");
for (String typeIdStr : typeIdArray) {
try {
Long typeId = Long.parseLong(typeIdStr.trim());
DemandType demandType = configService.getDemandTypeById(typeId);
if (demandType != null) {
// 获取允许的部门ID(支持继承)
String allowedDeptIds = demandType.getAllowedDeptIds();
if ((allowedDeptIds == null || allowedDeptIds.isEmpty()) && demandType.getParentId() != null) {
// 三级分类没有配置,尝试获取父级(二级分类)的配置
DemandType parentType = configService.getDemandTypeById(demandType.getParentId());
if (parentType != null) {
allowedDeptIds = parentType.getAllowedDeptIds();
}
}
if (allowedDeptIds != null && !allowedDeptIds.isEmpty()) {
String[] deptIds = allowedDeptIds.split(",");
if (deptIds.length > 0) {
Long deptId = Long.parseLong(deptIds[0].trim());
Department dept = departmentMapper.selectById(deptId);
if (dept != null && dept.getLeaderId() != null) {
return dept.getLeaderId();
}
}
}
}
} catch (Exception e) {
log.error("解析任务类型或部门ID失败", e);
}
}
return null;
}
/**
* 应用任务更新
*/
private void applyTaskUpdates(Task task, TaskUpdateDTO updates) {
if (updates.getTitle() != null) {
task.setTitle(updates.getTitle());
}
if (updates.getTaskSubType() != null) {
task.setTaskSubType(updates.getTaskSubType());
}
if (updates.getBrandId() != null) {
task.setBrandId(updates.getBrandId());
}
if (updates.getPlatformId() != null) {
task.setPlatformId(updates.getPlatformId());
}
if (updates.getCategoryId() != null) {
task.setCategoryId(updates.getCategoryId());
}
if (updates.getSpuInfo() != null) {
task.setSpuInfo(updates.getSpuInfo());
}
if (updates.getRemark() != null) {
task.setRemark(updates.getRemark());
}
if (updates.getAttachmentUrl() != null) {
task.setAttachmentUrl(updates.getAttachmentUrl());
}
// 如果修改了任务类型和数量,需要重新计算积分
if (updates.getTaskTypes() != null && !updates.getTaskTypes().isEmpty()) {
String taskTypeIds = updates.getTaskTypes().stream()
.map(t -> String.valueOf(t.getTaskTypeId()))
.collect(Collectors.joining(","));
task.setTaskTypeIds(taskTypeIds);
// 如果只有一个任务类型,更新quantity字段
if (updates.getTaskTypes().size() == 1) {
task.setQuantity(updates.getTaskTypes().get(0).getQuantity());
}
// 重新计算积分
java.math.BigDecimal totalScore = taskService.calculateTotalScoreDecimal(updates.getTaskTypes());
task.setScore(totalScore);
}
}
/**
* 拆分为流水线子任务
*/
private void splitIntoPipelineSubTasks(Task task) {
try {
// 构建流水线任务DTO
PipelineTaskDTO dto = new PipelineTaskDTO();
dto.setTitle(task.getTitle());
dto.setTaskSubType(task.getTaskSubType());
dto.setBrandId(task.getBrandId());
dto.setPlatformId(task.getPlatformId());
dto.setCategoryId(task.getCategoryId());
dto.setSpuInfo(task.getSpuInfo());
dto.setRemark(task.getRemark());
dto.setAttachmentUrl(task.getAttachmentUrl());
dto.setPipelineMode(task.getPipelineMode());
dto.setReleaseType(task.getReleaseType()); // 传递发布类型
// 解析任务类型
String[] taskTypeIds = task.getTaskTypeIds().split(",");
List<PipelineTaskDTO.TaskItemDTO> items = new java.util.ArrayList<>();
// 简化版本:所有任务类型使用相同的数量(task.quantity)
int quantity = task.getQuantity() != null ? task.getQuantity() : 1;
for (String taskTypeIdStr : taskTypeIds) {
PipelineTaskDTO.TaskItemDTO item = new PipelineTaskDTO.TaskItemDTO();
item.setTaskTypeId(Long.parseLong(taskTypeIdStr.trim()));
item.setQuantity(quantity);
items.add(item);
}
dto.setItems(items);
// 调用TaskService创建流水线子任务
taskService.createPipelineTaskItems(task, dto);
// 根据发布类型设置主任务状态
if ("recruitment".equals(task.getReleaseType())) {
// recruitment模式:发布到任务大厅
task.setStatus("pending");
} else if ("assigned".equals(task.getReleaseType()) && task.getReceiverId() != null) {
// assigned模式:如果指定了接收人,主任务直接进入进行中状态
task.setStatus("in_progress");
LocalDateTime now = LocalDateTime.now();
task.setStartTime(now);
task.setActualStartTime(now);
// 重新计算deadline:从接单时间开始计算(使用工作时间计算)
if (task.getEstimatedHours() != null && task.getEstimatedHours().compareTo(java.math.BigDecimal.ZERO) > 0) {
task.setDeadline(workHoursCalculator.calculateDeadline(now, task.getEstimatedHours()));
}
// 更新子任务状态为in_progress(第一组或全部,取决于流水线模式)
taskService.updatePipelineSubTasksForAssigned(task.getId(), dto.getPipelineMode());
log.info("指派流水线任务已设置为进行中:taskId={}, receiverId={}", task.getId(), task.getReceiverId());
} else {
// default模式或其他模式:流水线任务的主任务状态设置为pending
// 子任务会根据is_locked字段控制可见性
task.setStatus("pending");
}
} catch (Exception e) {
log.error("拆分流水线任务失败", e);
throw new BusinessException(ResultCode.ERROR, "拆分流水线任务失败:" + e.getMessage());
}
}
/**
* 记录审核日志
*/
private void logApproval(Long taskId, Long approverId, Integer level, String action, String comment) {
try {
User approver = userMapper.selectById(approverId);
TaskApprovalLog log = new TaskApprovalLog();
log.setTaskId(taskId);
log.setApprovalLevel(level);
log.setApproverId(approverId);
log.setApproverName(approver != null ? approver.getRealName() : null);
log.setAction(action);
log.setComment(comment);
log.setCreateTime(LocalDateTime.now());
approvalLogMapper.insert(log);
} catch (Exception e) {
this.log.error("记录审核日志失败", e);
}
}
/**
* 格式化预估工时显示(基于工作时间配置)
*/
private String formatEstimatedHours(java.math.BigDecimal hours) {
if (hours == null) {
return "未设置";
}
String formatted = workHoursCalculator.formatWorkHours(hours);
// WorkHoursCalculator 返回 "--" 表示无效,这里保持 "未设置" 的语义
return "--".equals(formatted) ? "未设置" : formatted;
}
/**
* 发送任务指派通知给设计师
* @param task 任务
* @param receiverId 接收人ID
* @param isPreAssigned 是否预先指定
*/
private void sendTaskAssignedNotification(Task task, Long receiverId, boolean isPreAssigned) {
try {
com.designhub.entity.User receiver = userMapper.selectById(receiverId);
if (receiver == null) {
log.warn("接收人不存在: {}", receiverId);
return;
}
String receiverName = receiver.getRealName();
String receiverPhone = receiver.getPhone();
// 构建通知内容
StringBuilder content = new StringBuilder();
// 基本信息
content.append("**任务名称**:").append(task.getTitle()).append(" \n");
content.append("**任务编号**:").append(task.getTaskNo()).append(" \n");
// 任务详情
if (task.getBrandName() != null && !task.getBrandName().isEmpty()) {
content.append("**品牌**:").append(task.getBrandName()).append(" \n");
}
if (task.getPlatformName() != null && !task.getPlatformName().isEmpty()) {
content.append("**平台**:").append(task.getPlatformName()).append(" \n");
}
if (task.getSpuInfo() != null && !task.getSpuInfo().isEmpty()) {
content.append("**SPU**:").append(task.getSpuInfo()).append(" \n");
}
if (task.getTaskSubType() != null && !task.getTaskSubType().isEmpty()) {
content.append("**任务类型**:").append(task.getTaskSubType()).append(" \n");
}
content.append(" \n");
// 查询该设计师负责的子任务
List<TaskItem> receiverItems = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, task.getId())
.eq(TaskItem::getReceiverId, receiverId)
.orderByAsc(TaskItem::getGroupNo)
);
// 时间格式化器:MM月dd日 HH:mm
DateTimeFormatter deadlineFormatter = DateTimeFormatter.ofPattern("MM月dd日 HH:mm");
if (!receiverItems.isEmpty()) {
content.append("**您负责的任务明细**: \n");
for (TaskItem item : receiverItems) {
// 通过任务类型ID获取接单部门名称
String deptName = "未指定";
if (item.getTaskTypeId() != null) {
try {
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());
Department dept = departmentMapper.selectById(firstDeptId);
if (dept != null) {
deptName = dept.getDeptName();
}
}
} catch (Exception e) {
log.warn("获取部门名称失败: {}", e.getMessage());
}
}
// 格式化工时显示
String hoursDisplay = formatEstimatedHours(item.getEstimatedHours());
// 格式化截止时间
String deadlineDisplay = "";
if (item.getDeadline() != null) {
deadlineDisplay = " | 截止: " + item.getDeadline().format(deadlineFormatter);
}
content.append(" \n");
content.append("▸ **").append(deptName).append("** | ")
.append(item.getTotalScore()).append("分 | ")
.append(hoursDisplay).append(deadlineDisplay).append(" \n");
// 解析并显示任务类型明细
if (item.getTypeDetails() != null && !item.getTypeDetails().isEmpty()) {
try {
@SuppressWarnings("unchecked")
List<java.util.Map<String, Object>> typeDetails =
objectMapper.readValue(item.getTypeDetails(), 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());
}
}
}
} else {
// 没有子任务时显示总积分和工时
content.append("**任务积分**:").append(task.getScore()).append(" 分 \n");
if (task.getEstimatedHours() != null) {
content.append("**预估工时**:").append(formatEstimatedHours(task.getEstimatedHours())).append(" \n");
}
}
content.append(" \n");
// 发起人信息
if (task.getPublisherName() != null && !task.getPublisherName().isEmpty()) {
content.append("**发起人**:").append(task.getPublisherName()).append(" \n");
}
if (task.getPublisherDeptName() != null && !task.getPublisherDeptName().isEmpty()) {
content.append("**发起部门**:").append(task.getPublisherDeptName()).append(" \n");
}
// 指派信息
content.append("**指派给**:").append(receiverName).append(" \n");
content.append(" \n");
content.append("点击下方按钮查看详情");
// 发送个人钉钉通知给设计师
Notification notification = new Notification();
notification.setReceiverId(receiverId);
notification.setTitle("任务已指派");
notification.setContent(content.toString());
notification.setNotificationType("TASK_ASSIGNED");
notification.setRelatedType("task");
notification.setRelatedId(task.getId());
notification.setPushDingtalk(1); // 推送到钉钉
notificationService.sendNotification(notification);
log.info("任务指派个人通知已发送给设计师: userId={}, name={}", receiverId, receiverName);
// 只有非预先指定的任务才发送群通知(预先指定的任务只发送个人通知)
if (!isPreAssigned) {
java.util.List<String> atMobiles = receiverPhone != null ?
java.util.Arrays.asList(receiverPhone) : null;
dingTalkService.sendRobotMessage("任务已指派", content.toString(), atMobiles);
log.info("任务指派群通知已发送,@手机号: {}", receiverPhone);
} else {
log.info("任务【{}】为预先指定任务,只发送个人通知,不发送群通知", task.getTitle());
}
} catch (Exception e) {
log.error("发送任务指派通知失败: receiverId={}", receiverId, e);
}
}
}