mirror of
https://github.com/kuaifan/dootask.git
synced 2026-07-23 06:28:30 +00:00
fix(task): 已删除/已归档项目不再生成周期任务,残留任务详情可打开可删除
- LoopTask 周期生成仅处理未删除、未归档项目的任务,从源头杜绝残留繁殖 - userTask 兜底改用 withTrashed,任务相关成员可打开已删项目下的任务详情 - task/one 返回 project_deleted/project_archived 标记,详情顶部横幅提示并提供删除按钮 - task/remove 对已删除/已归档项目放行删除(限项目负责人、任务负责人/协助人/创建人) - 顺手修复 addTask 优先级匹配 reset() 传临时值的 notice 隐患,改用 collect()->first() - 同步 ai-kb(recurring/delete-restore)与国际化文案
This commit is contained in:
parent
746a0bf9a1
commit
e72758f313
@ -2271,9 +2271,12 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
//
|
||||
$data = $task->toArray();
|
||||
$data['department_readonly'] = UserDepartment::isDepartmentReadonlyProject($departmentView, intval($task->project_id));
|
||||
$data['project_name'] = $task->project?->name;
|
||||
$taskProject = Project::withTrashed()->find($task->project_id);
|
||||
$data['project_name'] = $taskProject?->name;
|
||||
$data['project_deleted'] = (!$taskProject || $taskProject->trashed()) ? 1 : 0;
|
||||
$data['project_archived'] = $taskProject?->archived_at ? 1 : 0;
|
||||
$data['column_name'] = $task->projectColumn?->name;
|
||||
$data['department_readonly'] = UserDepartment::isDepartmentReadonlyProject($departmentView, intval($task->project_id));
|
||||
$data['visibility_appointor'] = $task->visibility == 1 ? [0] : ProjectTaskVisibilityUser::whereTaskId($task_id)->pluck('userid');
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
@ -3205,8 +3208,27 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
$task = ProjectTask::userTask($task_id, null, $type !== 'recovery');
|
||||
//
|
||||
$project = Project::userProject($task->project_id);
|
||||
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_REMOVE, $task);
|
||||
try {
|
||||
$project = Project::userProject($task->project_id);
|
||||
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_REMOVE, $task);
|
||||
} catch (\Throwable $e) {
|
||||
if ($type == 'recovery') {
|
||||
throw $e;
|
||||
}
|
||||
// 项目已删除/已归档时放行删除操作(限:管理员、原项目负责人、任务负责人/协助人/创建人)
|
||||
$project = Project::withTrashed()->find($task->project_id);
|
||||
$projectInvalid = !$project || $project->trashed() || $project->archived_at;
|
||||
if (!$projectInvalid) {
|
||||
throw $e;
|
||||
}
|
||||
$isProjectOwner = ProjectUser::whereProjectId($task->project_id)
|
||||
->whereUserid(Doo::userId())
|
||||
->whereIn('owner', [ProjectUser::OWNER_PRIMARY, ProjectUser::OWNER_DEPUTY])
|
||||
->exists();
|
||||
if (!$isProjectOwner && !$task->permission(3)) {
|
||||
throw new ApiException('仅项目负责人或任务相关成员删除');
|
||||
}
|
||||
}
|
||||
//
|
||||
if ($type == 'recovery') {
|
||||
$task->restoreTask();
|
||||
|
||||
@ -482,7 +482,7 @@ class ProjectTask extends AbstractModel
|
||||
if ($parent_id == 0) {
|
||||
$priorityList = Setting::normalizeTaskPriorityList(Base::setting('priority'));
|
||||
if ($p_level > 0) {
|
||||
$matched = reset(array_filter($priorityList, fn($item) => intval($item['priority']) === $p_level)) ?: null;
|
||||
$matched = collect($priorityList)->first(fn($item) => intval($item['priority']) === $p_level);
|
||||
} else {
|
||||
$matched = Setting::getDefaultTaskPriorityItem($priorityList);
|
||||
}
|
||||
@ -2246,7 +2246,7 @@ class ProjectTask extends AbstractModel
|
||||
$project = Project::userProject($task->project_id);
|
||||
} catch (\Throwable $e) {
|
||||
if ($task->owner !== null || $task->permission(4)) {
|
||||
$project = Project::find($task->project_id);
|
||||
$project = Project::withTrashed()->find($task->project_id);
|
||||
if (empty($project)) {
|
||||
throw new ApiException('项目不存在或已被删除', [ 'task_id' => $task_id ], -4002);
|
||||
}
|
||||
|
||||
@ -23,7 +23,10 @@ class LoopTask extends AbstractTask
|
||||
ProjectTask::whereBetween('loop_at', [
|
||||
Carbon::now()->subMinutes(10),
|
||||
Carbon::now()
|
||||
])->chunkById(100, function ($list) {
|
||||
])->whereHas('project', function ($query) {
|
||||
// 仅处理未删除、未归档项目的任务(Project 软删除由全局作用域排除)
|
||||
$query->whereNull('archived_at');
|
||||
})->chunkById(100, function ($list) {
|
||||
/** @var ProjectTask $item */
|
||||
foreach ($list as $item) {
|
||||
if ($item->parent_id > 0) {
|
||||
|
||||
@ -1040,3 +1040,4 @@ AI 助手
|
||||
scene 暂未实现:(*)
|
||||
upload_id 不能为空
|
||||
合并繁忙,请稍后再试
|
||||
仅项目负责人或任务相关成员删除
|
||||
|
||||
@ -2525,3 +2525,5 @@ AI任务分析
|
||||
选择要使用的授权
|
||||
当前设备使用中
|
||||
确定授权
|
||||
当前任务所属项目已被删除
|
||||
当前任务所属项目已归档
|
||||
|
||||
@ -19,7 +19,8 @@ negative:
|
||||
- 删除后默认仅在回收站近期记录中保留,超过保留窗口会被定时清理
|
||||
- 父任务删除会级联软删所有子任务
|
||||
- 已删除任务不能编辑、不能完成、不能加子任务,必须先恢复
|
||||
last_verified: v1.7.90
|
||||
- 所属项目已删除或已归档的任务不能恢复,只能删除
|
||||
last_verified: v1.8.64
|
||||
---
|
||||
|
||||
# 删除任务与回收站恢复
|
||||
@ -30,6 +31,7 @@ DooTask 任务使用软删除(`deleted_at` + `deleted_userid`),删除并
|
||||
## 入口
|
||||
- 桌面端:任务详情页 → 「⋯」 → 「删除」
|
||||
- 桌面端列表视图:右键任务行 → 「删除」
|
||||
- 所属项目已被删除或已归档的任务:任务详情页顶部横幅 → 「删除任务」按钮(限管理员、原项目负责人、任务负责人/协助人/创建人)
|
||||
- 恢复入口:项目设置 → 「回收站」或全局「我删除的任务」
|
||||
|
||||
## 删除操作步骤
|
||||
|
||||
@ -19,7 +19,8 @@ negative:
|
||||
- 重复任务不能在子任务上独立设置(subtask 不支持 loop)
|
||||
- 重复任务的下一份在「上一份被标记完成时」生成,不是按日历预生成
|
||||
- 修改循环周期不会回填已生成的实例
|
||||
last_verified: v1.7.90
|
||||
- 所属项目已被删除或已归档时,不再自动生成新的周期任务
|
||||
last_verified: v1.8.64
|
||||
---
|
||||
|
||||
# 重复 / 循环任务
|
||||
@ -51,3 +52,4 @@ DooTask 任务支持循环重复(字段 `loop` + `loop_at`),常用于「
|
||||
- 不能为同一任务并行启用多个周期
|
||||
- 循环不复制附件
|
||||
- 子任务不能独立设置循环
|
||||
- 所属项目已被删除或已归档的任务不再自动生成新周期任务;这类残留任务可在任务详情顶部横幅点「删除任务」清理
|
||||
|
||||
@ -115,7 +115,11 @@
|
||||
<Alert v-if="taskDetail.department_readonly" class="task-readonly-alert" type="info" show-icon>
|
||||
{{$L('当前为负责人视角,并参与讨论,但不能编辑任务。')}}
|
||||
</Alert>
|
||||
<Alert v-if="!isDepartmentReadonly && taskDetail.task_user !== undefined && getOwner.length === 0" class="receive-box" type="warning">
|
||||
<Alert v-if="projectInvalid" class="receive-box" type="error">
|
||||
<span class="receive-text">{{taskDetail.project_deleted ? $L('当前任务所属项目已被删除') : $L('当前任务所属项目已归档')}}</span>
|
||||
<Button v-if="!isDepartmentReadonly" class="receive-button" :loading="removeLoad > 0" size="small" type="error" @click="deleteInvalidTask">{{$L('删除任务')}}</Button>
|
||||
</Alert>
|
||||
<Alert v-if="!projectInvalid && !isDepartmentReadonly && taskDetail.task_user !== undefined && getOwner.length === 0" class="receive-box" type="warning">
|
||||
<span class="receive-text">{{$L('该任务尚未被领取,点击这里')}}</span>
|
||||
<EPopover
|
||||
v-model="receiveShow"
|
||||
@ -635,6 +639,8 @@ export default {
|
||||
ownerData: {},
|
||||
ownerLoad: 0,
|
||||
|
||||
removeLoad: 0,
|
||||
|
||||
receiveShow: false,
|
||||
|
||||
tagForce: false,
|
||||
@ -989,6 +995,10 @@ export default {
|
||||
return !!this.taskDetail?.department_readonly;
|
||||
},
|
||||
|
||||
projectInvalid() {
|
||||
return this.taskDetail.project_deleted == 1 || this.taskDetail.project_archived == 1;
|
||||
},
|
||||
|
||||
isSubTask({taskDetail}) {
|
||||
return taskDetail.parent_id > 0
|
||||
},
|
||||
@ -1136,6 +1146,30 @@ export default {
|
||||
this.receiveShow = true;
|
||||
},
|
||||
|
||||
deleteInvalidTask() {
|
||||
const typeTask = this.taskDetail.parent_id > 0 ? '子任务' : '任务';
|
||||
$A.modalConfirm({
|
||||
title: '删除' + typeTask,
|
||||
content: '你确定要删除' + typeTask + '【' + this.taskDetail.name + '】吗?',
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
if (this.removeLoad > 0) {
|
||||
return;
|
||||
}
|
||||
this.removeLoad++;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.$store.dispatch("removeTask", {task_id: this.taskDetail.id}).then(({msg}) => {
|
||||
resolve(msg);
|
||||
}).catch(({msg}) => {
|
||||
reject(msg);
|
||||
}).finally(() => {
|
||||
this.removeLoad--;
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
within24Hours(date) {
|
||||
return ($A.dayjs(date).unix() - this.keepUnix) < 86400
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user