- 新增sendTaskAssignedNotification方法 - 流水线任务审核通过后,遍历第一个部门的所有子任务 - 给每个子任务的设计师都发送钉钉通知 - 每个设计师收到的通知只包含自己负责的任务明细
947 lines
46 KiB
Java
947 lines
46 KiB
Java
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.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());
|
||
} 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)
|
||
);
|
||
|
||
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());
|
||
|
||
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")
|
||
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);
|
||
|
||
// 通知需求部门负责人
|
||
notificationService.sendNotification(
|
||
demandDept.getLeaderId(),
|
||
"任务已被驳回",
|
||
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);
|
||
}
|
||
|
||
/**
|
||
* 获取待审核任务列表
|
||
* 管理员可以看到所有待审核任务
|
||
*/
|
||
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());
|
||
}
|
||
|
||
// 重新计算积分
|
||
int totalScore = taskService.calculateTotalScore(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)
|
||
);
|
||
|
||
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());
|
||
|
||
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")
|
||
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);
|
||
}
|
||
}
|
||
}
|