feat: 为任务卡片添加子任务进度统计功能

新增功能:
1. Task实体新增字段:
   - subTaskTotal: 子任务总数
   - subTaskCompleted: 已完成子任务数
   - subTaskInProgress: 进行中子任务数
   - subTaskPending: 待接单子任务数
   - subTasks: 子任务列表(包含处理人信息)

2. TaskService新增方法:
   - fillSubTaskStats(): 填充流水线任务的子任务统计信息
   - 自动在getMyPublishedTasks和getMyReceivedTasks中调用
   - 统计各状态子任务数量
   - 自动填充处理人信息

3. 同时修复流水线任务主任务状态同步问题:
   - 设计师接单第一个子任务时,主任务状态更新为in_progress
   - 确保需求方能正确看到任务进度

影响范围:
- 需求方查看"我发起的"任务时,可以看到子任务进度
- 设计师查看接收的任务时,可以看到子任务详情
- 为后续前端显示提供数据支持
This commit is contained in:
Your Name 2026-01-03 11:32:16 +08:00
parent ef7e39cb00
commit 371cfd5b2a
6 changed files with 137 additions and 21 deletions

View File

@ -317,6 +317,36 @@ public class Task implements Serializable {
@TableField(exist = false)
private String auditorName;
/**
* 子任务总数(非数据库字段,流水线任务专用)
*/
@TableField(exist = false)
private Integer subTaskTotal;
/**
* 已完成子任务数(非数据库字段,流水线任务专用)
*/
@TableField(exist = false)
private Integer subTaskCompleted;
/**
* 进行中子任务数(非数据库字段,流水线任务专用)
*/
@TableField(exist = false)
private Integer subTaskInProgress;
/**
* 待接单子任务数(非数据库字段,流水线任务专用)
*/
@TableField(exist = false)
private Integer subTaskPending;
/**
* 子任务列表(非数据库字段,流水线任务专用)
*/
@TableField(exist = false)
private java.util.List<TaskItem> subTasks;
/**
* 当前审核人姓名(查询时返回,不映射到数据库)
*/
@ -513,5 +543,20 @@ public class Task implements Serializable {
public String getCurrentApproverName() { return currentApproverName; }
public void setCurrentApproverName(String currentApproverName) { this.currentApproverName = currentApproverName; }
public Integer getSubTaskTotal() { return subTaskTotal; }
public void setSubTaskTotal(Integer subTaskTotal) { this.subTaskTotal = subTaskTotal; }
public Integer getSubTaskCompleted() { return subTaskCompleted; }
public void setSubTaskCompleted(Integer subTaskCompleted) { this.subTaskCompleted = subTaskCompleted; }
public Integer getSubTaskInProgress() { return subTaskInProgress; }
public void setSubTaskInProgress(Integer subTaskInProgress) { this.subTaskInProgress = subTaskInProgress; }
public Integer getSubTaskPending() { return subTaskPending; }
public void setSubTaskPending(Integer subTaskPending) { this.subTaskPending = subTaskPending; }
public java.util.List<TaskItem> getSubTasks() { return subTasks; }
public void setSubTasks(java.util.List<TaskItem> subTasks) { this.subTasks = subTasks; }
}

View File

@ -105,7 +105,7 @@ public class DepartmentService {
@Transactional(rollbackFor = Exception.class)
public DepartmentOverviewVO createDepartment(DepartmentCreateRequest request) {
validateDeptType(request.getDeptType());
// 验证负责人ID是否有效
if (request.getLeaderId() != null && request.getLeaderId() > 0) {
User leader = userMapper.selectById(request.getLeaderId());

View File

@ -261,8 +261,8 @@ public class TaskApprovalService {
// TODO: 通知指定的设计师
log.info("任务【{}】已指定给设计师", task.getTitle());
} else {
// TODO: 通知任务大厅(所有接单部门成员)
log.info("任务【{}】已发布到任务大厅", task.getTitle());
// TODO: 通知任务大厅(所有接单部门成员)
log.info("任务【{}】已发布到任务大厅", task.getTitle());
}
log.info("接单部门负责人审核通过,任务ID:{},审核人:{},指定接收人:{}",

View File

@ -131,19 +131,31 @@ public class TaskItemService {
log.info("设计师 {} 成功抢单子任务 {}", userId, itemId);
// 发送通知给任务发布者
// 更新主任务状态
Task mainTask = taskMapper.selectById(item.getTaskId());
if (mainTask != null && 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 != 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);
}
}
}

View File

@ -618,7 +618,14 @@ public class TaskService {
*/
public IPage<Task> getMyPublishedTasks(Integer page, Integer pageSize, Long userId, String status) {
Page<Task> pageParam = new Page<>(page, pageSize);
return taskMapper.selectMyPublishedTasks(pageParam, userId, status);
IPage<Task> taskPage = taskMapper.selectMyPublishedTasks(pageParam, userId, status);
// 填充子任务统计信息
for (Task task : taskPage.getRecords()) {
fillSubTaskStats(task);
}
return taskPage;
}
/**
@ -626,7 +633,59 @@ public class TaskService {
*/
public IPage<Task> getMyReceivedTasks(Integer page, Integer pageSize, Long userId, String status) {
Page<Task> pageParam = new Page<>(page, pageSize);
return taskMapper.selectMyReceivedTasks(pageParam, userId, status);
IPage<Task> taskPage = taskMapper.selectMyReceivedTasks(pageParam, userId, status);
// 填充子任务统计信息
for (Task task : taskPage.getRecords()) {
fillSubTaskStats(task);
}
return taskPage;
}
/**
* 填充流水线任务的子任务统计信息
*/
private void fillSubTaskStats(Task task) {
// 只有流水线任务才需要填充子任务信息
if (task.getIsPipeline() == null || task.getIsPipeline() != 1) {
return;
}
try {
// 查询所有子任务
List<TaskItem> subTasks = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, task.getId())
.orderByAsc(TaskItem::getSortOrder)
);
if (subTasks != null && !subTasks.isEmpty()) {
task.setSubTaskTotal(subTasks.size());
// 统计各状态子任务数量
long completedCount = subTasks.stream().filter(item -> "completed".equals(item.getStatus())).count();
long inProgressCount = subTasks.stream().filter(item -> "in_progress".equals(item.getStatus())).count();
long pendingCount = subTasks.stream().filter(item -> "pending".equals(item.getStatus())).count();
task.setSubTaskCompleted((int) completedCount);
task.setSubTaskInProgress((int) inProgressCount);
task.setSubTaskPending((int) pendingCount);
// 设置子任务列表(包含处理人信息)
task.setSubTasks(subTasks);
// 如果主任务没有receiverName,从子任务中获取第一个有接单人的
if (task.getReceiverName() == null || task.getReceiverName().isEmpty()) {
subTasks.stream()
.filter(item -> item.getReceiverName() != null && !item.getReceiverName().isEmpty())
.findFirst()
.ifPresent(item -> task.setReceiverName(item.getReceiverName() + "等"));
}
}
} catch (Exception e) {
log.error("填充子任务统计信息失败:taskId={}", task.getId(), e);
}
}
/**

View File

@ -168,22 +168,22 @@ const getIconName = (notification: Notification) => {
// 匹配系统通知
if (type === 'system') {
return 'info'
return 'info'
}
// 匹配任务相关通知(task, task_approval, task_review, task_completed, task_rejected等)
if (type.includes('task')) {
return 'document'
return 'document'
}
// 匹配积分通知
if (type.includes('score')) {
return 'trophy'
return 'trophy'
}
// 匹配评审通知
if (type === 'review' || type.includes('review')) {
return 'star'
return 'star'
}
// 默认铃铛图标