mirror of
https://github.com/kuaifan/dootask.git
synced 2026-08-01 19:05:55 +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 = $task->toArray();
|
||||||
$data['department_readonly'] = UserDepartment::isDepartmentReadonlyProject($departmentView, intval($task->project_id));
|
$taskProject = Project::withTrashed()->find($task->project_id);
|
||||||
$data['project_name'] = $task->project?->name;
|
$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['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');
|
$data['visibility_appointor'] = $task->visibility == 1 ? [0] : ProjectTaskVisibilityUser::whereTaskId($task_id)->pluck('userid');
|
||||||
return Base::retSuccess('success', $data);
|
return Base::retSuccess('success', $data);
|
||||||
}
|
}
|
||||||
@ -3205,8 +3208,27 @@ class ProjectController extends AbstractController
|
|||||||
//
|
//
|
||||||
$task = ProjectTask::userTask($task_id, null, $type !== 'recovery');
|
$task = ProjectTask::userTask($task_id, null, $type !== 'recovery');
|
||||||
//
|
//
|
||||||
$project = Project::userProject($task->project_id);
|
try {
|
||||||
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_REMOVE, $task);
|
$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') {
|
if ($type == 'recovery') {
|
||||||
$task->restoreTask();
|
$task->restoreTask();
|
||||||
|
|||||||
@ -482,7 +482,7 @@ class ProjectTask extends AbstractModel
|
|||||||
if ($parent_id == 0) {
|
if ($parent_id == 0) {
|
||||||
$priorityList = Setting::normalizeTaskPriorityList(Base::setting('priority'));
|
$priorityList = Setting::normalizeTaskPriorityList(Base::setting('priority'));
|
||||||
if ($p_level > 0) {
|
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 {
|
} else {
|
||||||
$matched = Setting::getDefaultTaskPriorityItem($priorityList);
|
$matched = Setting::getDefaultTaskPriorityItem($priorityList);
|
||||||
}
|
}
|
||||||
@ -2246,7 +2246,7 @@ class ProjectTask extends AbstractModel
|
|||||||
$project = Project::userProject($task->project_id);
|
$project = Project::userProject($task->project_id);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
if ($task->owner !== null || $task->permission(4)) {
|
if ($task->owner !== null || $task->permission(4)) {
|
||||||
$project = Project::find($task->project_id);
|
$project = Project::withTrashed()->find($task->project_id);
|
||||||
if (empty($project)) {
|
if (empty($project)) {
|
||||||
throw new ApiException('项目不存在或已被删除', [ 'task_id' => $task_id ], -4002);
|
throw new ApiException('项目不存在或已被删除', [ 'task_id' => $task_id ], -4002);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,10 @@ class LoopTask extends AbstractTask
|
|||||||
ProjectTask::whereBetween('loop_at', [
|
ProjectTask::whereBetween('loop_at', [
|
||||||
Carbon::now()->subMinutes(10),
|
Carbon::now()->subMinutes(10),
|
||||||
Carbon::now()
|
Carbon::now()
|
||||||
])->chunkById(100, function ($list) {
|
])->whereHas('project', function ($query) {
|
||||||
|
// 仅处理未删除、未归档项目的任务(Project 软删除由全局作用域排除)
|
||||||
|
$query->whereNull('archived_at');
|
||||||
|
})->chunkById(100, function ($list) {
|
||||||
/** @var ProjectTask $item */
|
/** @var ProjectTask $item */
|
||||||
foreach ($list as $item) {
|
foreach ($list as $item) {
|
||||||
if ($item->parent_id > 0) {
|
if ($item->parent_id > 0) {
|
||||||
|
|||||||
@ -1040,3 +1040,4 @@ AI 助手
|
|||||||
scene 暂未实现:(*)
|
scene 暂未实现:(*)
|
||||||
upload_id 不能为空
|
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)
|
- 重复任务不能在子任务上独立设置(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>
|
<Alert v-if="taskDetail.department_readonly" class="task-readonly-alert" type="info" show-icon>
|
||||||
{{$L('当前为负责人视角,并参与讨论,但不能编辑任务。')}}
|
{{$L('当前为负责人视角,并参与讨论,但不能编辑任务。')}}
|
||||||
</Alert>
|
</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>
|
<span class="receive-text">{{$L('该任务尚未被领取,点击这里')}}</span>
|
||||||
<EPopover
|
<EPopover
|
||||||
v-model="receiveShow"
|
v-model="receiveShow"
|
||||||
@ -635,6 +639,8 @@ export default {
|
|||||||
ownerData: {},
|
ownerData: {},
|
||||||
ownerLoad: 0,
|
ownerLoad: 0,
|
||||||
|
|
||||||
|
removeLoad: 0,
|
||||||
|
|
||||||
receiveShow: false,
|
receiveShow: false,
|
||||||
|
|
||||||
tagForce: false,
|
tagForce: false,
|
||||||
@ -989,6 +995,10 @@ export default {
|
|||||||
return !!this.taskDetail?.department_readonly;
|
return !!this.taskDetail?.department_readonly;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
projectInvalid() {
|
||||||
|
return this.taskDetail.project_deleted == 1 || this.taskDetail.project_archived == 1;
|
||||||
|
},
|
||||||
|
|
||||||
isSubTask({taskDetail}) {
|
isSubTask({taskDetail}) {
|
||||||
return taskDetail.parent_id > 0
|
return taskDetail.parent_id > 0
|
||||||
},
|
},
|
||||||
@ -1136,6 +1146,30 @@ export default {
|
|||||||
this.receiveShow = true;
|
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) {
|
within24Hours(date) {
|
||||||
return ($A.dayjs(date).unix() - this.keepUnix) < 86400
|
return ($A.dayjs(date).unix() - this.keepUnix) < 86400
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user