feat: 需求部门我发起的任务列表增加撤回编辑功能,撤回时同步返还部门积分

This commit is contained in:
Your Name 2026-01-19 17:51:53 +08:00
parent 681e22fec3
commit f72a06ed22
4 changed files with 201 additions and 0 deletions

View File

@ -476,6 +476,24 @@ public class TaskController {
return Result.success("任务已驳回");
}
@Operation(summary = "撤回任务", description = "发起人撤回未被抢单的任务进行编辑,撤回后任务变为草稿状态")
@PostMapping("/{id}/withdraw")
public Result<String> withdrawTask(
@PathVariable Long id,
@RequestHeader("Authorization") String authHeader) {
log.info("撤回任务: taskId={}", id);
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
try {
taskService.withdrawTask(id, userId);
return Result.success("任务已撤回,可以进行编辑");
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
}
@Operation(summary = "根据任务编号删除任务", description = "管理员根据任务编号删除任务及其所有子任务(仅管理员可用)")
@DeleteMapping("/by-task-no/{taskNo}")
public Result<String> deleteTaskByTaskNo(

View File

@ -1,6 +1,7 @@
package com.designhub.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.designhub.common.ResultCode;
@ -2134,6 +2135,93 @@ public class TaskService {
log.info("任务 {} 评分成功: {} 星", taskId, rating);
}
/**
* 撤回任务(用于编辑后重新发布)
* 只有任务发起人可以撤回,且任务必须未被抢单
*/
@Transactional(rollbackFor = Exception.class)
public void withdrawTask(Long taskId, Long userId) {
log.info("用户 {} 撤回任务 {} 进行编辑", userId, taskId);
Task task = getTaskById(taskId);
// 验证权限(必须是任务发布人)
if (!userId.equals(task.getPublisherId())) {
throw new BusinessException(ResultCode.FORBIDDEN, "只有任务发布人可以撤回任务");
}
// 验证任务状态
// 可以撤回的状态:pending(待接单)、pending_demand_approval(待需求部门审核)、pending_receiving_approval(待接单部门审核)
String status = task.getStatus();
String approvalStatus = task.getApprovalStatus();
boolean canWithdraw = false;
boolean needRefundScore = false; // 是否需要返还积分
// 待接单状态可以撤回(已审核通过,需要返还积分)
if ("pending".equals(status)) {
canWithdraw = true;
needRefundScore = true; // pending状态说明已审核通过,积分已扣除,需要返还
}
// 待审核状态也可以撤回(未审核通过,不需要返还积分)
if (approvalStatus != null && (approvalStatus.contains("pending"))) {
canWithdraw = true;
// 待审核状态积分还未扣除,不需要返还
}
if (!canWithdraw) {
throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "只有待接单或待审核的任务可以撤回");
}
// 检查任务是否已被抢单(receiverId 不为空说明已被抢单)
if (task.getReceiverId() != null) {
throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "任务已被接单,无法撤回");
}
// 检查流水线任务的子任务是否有被抢单的
if (task.getIsPipeline() != null && task.getIsPipeline() == 1) {
List<TaskItem> items = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, taskId)
.isNotNull(TaskItem::getReceiverId)
);
if (!items.isEmpty()) {
throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "部分子任务已被接单,无法撤回");
}
}
// 返还积分(只有pending状态的任务需要返还,因为审核通过后才会扣除积分)
if (needRefundScore && task.getPublisherDeptId() != null && task.getScore() != null) {
int scoreToRefund = task.getScore().intValue();
teamScorePoolService.addScore(task.getPublisherDeptId(), scoreToRefund);
log.info("任务撤回,返还部门积分: taskId={}, deptId={}, score={}",
taskId, task.getPublisherDeptId(), scoreToRefund);
}
// 更新任务状态为草稿
task.setStatus("draft");
task.setApprovalStatus(null); // 清除审批状态
task.setCurrentApproverId(null);
task.setCurrentApproverRole(null);
task.setAuditStatus("draft"); // 重置审核状态
task.setUpdateTime(LocalDateTime.now());
taskMapper.updateById(task);
// 如果是流水线任务,也需要重置子任务状态
if (task.getIsPipeline() != null && task.getIsPipeline() == 1) {
taskItemMapper.update(null,
new LambdaUpdateWrapper<TaskItem>()
.eq(TaskItem::getTaskId, taskId)
.set(TaskItem::getStatus, "pending")
.set(TaskItem::getUpdateTime, LocalDateTime.now())
);
}
log.info("任务 {} 已撤回为草稿状态,可以进行编辑", taskId);
}
/**
* 取消任务
*/

View File

@ -671,6 +671,16 @@ export const confirmTaskProductFilled = (taskId: number) => {
}
// ==================== 任务撤回 ====================
/**
* 撤回任务(用于编辑后重新发布)
* 只有任务发起人可以撤回,且任务必须未被抢单
*/
export const withdrawTask = (taskId: number) => {
return request.post<any>(`/tasks/${taskId}/withdraw`)
}
// ==================== 管理员任务管理 ====================
/**

View File

@ -1452,6 +1452,19 @@
</a-button>
</div>
<!-- 撤回编辑按钮(待接单或待审核且未被抢单的任务) -->
<div v-if="canWithdrawTask(task)" class="task-actions" style="margin-top: 16px; padding-top: 16px; border-top: 1px solid rgba(49, 240, 139, 0.2);">
<a-button
type="default"
size="small"
@click.stop="handleWithdrawTask(task)"
:loading="withdrawingTaskId === task.id"
style="width: 100%;">
<Icon name="rotate-ccw" size="sm" style="margin-right: 4px;" />
撤回编辑
</a-button>
</div>
<!-- 被驳回或草稿任务的操作按钮 -->
<div v-if="['rejected', 'draft'].includes(task.status) || task.approvalStatus?.includes('rejected')" class="task-actions" style="margin-top: 16px; padding-top: 16px; border-top: 1px solid rgba(49, 240, 139, 0.2);">
<a-button
@ -2284,6 +2297,7 @@ const userDeptId = ref<number | null>(null)
const userDeptName = ref<string>('')
const userDeptType = ref<string>('')
const exportLoading = ref(false) // 导出加载状态
const withdrawingTaskId = ref<number | null>(null) // 正在撤回的任务ID
// 获取当前有效的部门类型(根据选择的视角)
const effectiveDeptType = computed(() => {
@ -5033,6 +5047,77 @@ const handleDownloadPrevAssets = (task: TaskOrSubTask) => {
}
}
// 判断任务是否可以撤回
const canWithdrawTask = (task: Task) => {
// 已被驳回或草稿状态的任务不显示撤回按钮(它们有自己的编辑按钮)
if (['rejected', 'draft'].includes(task.status) || task.approvalStatus?.includes('rejected')) {
return false
}
// 已完成、已取消的任务不能撤回
if (['completed', 'cancelled'].includes(task.status)) {
return false
}
// 已被抢单的任务不能撤回(receiverId 不为空)
if (task.receiverId) {
return false
}
// 进行中的任务不能撤回
if (task.status === 'in_progress') {
return false
}
// 待接单或待审核的任务可以撤回
if (task.status === 'pending') {
return true
}
// 审批状态中包含 pending 的可以撤回
if (task.approvalStatus?.includes('pending')) {
return true
}
return false
}
// 撤回任务
const handleWithdrawTask = async (task: Task) => {
if (!task.id) return
Modal.confirm({
title: '撤回任务',
content: h('div', [
h('p', `确认撤回任务【${task.title}】?`),
h('p', { style: 'color: #999; font-size: 13px; margin-top: 8px;' }, '撤回后任务将变为草稿状态,您可以进行编辑后重新发布。')
]),
okText: '确认撤回',
cancelText: '取消',
async onOk() {
try {
withdrawingTaskId.value = task.id!
await taskApi.withdrawTask(task.id!)
message.success('任务已撤回,正在跳转到编辑页面...')
// 撤回成功后跳转到编辑页面
router.push({
path: '/tasks/create',
query: {
editId: task.id,
mode: 'edit'
}
})
} catch (error: any) {
console.error('撤回任务失败:', error)
message.error(error.response?.data?.message || error.message || '撤回任务失败')
} finally {
withdrawingTaskId.value = null
}
}
})
}
const handleEditRejectedTask = (task: Task) => {
// 跳转到创建任务页面,并传入任务ID作为编辑标识
router.push({