mirror of
https://github.com/kuaifan/dootask.git
synced 2026-05-24 01:14:06 +00:00
feat(manage): 部门负责人视角支持项目级可见开关并尊重任务可见性
新增项目级"负责人视角"开关(projects.department_owner_view,默认开启), 项目负责人可关闭,关闭后该项目及其群聊对部门负责人视角隐藏。同时将负责人 只读视角调整为尊重任务可见性:仅"全员可见"任务可被查看/进入任务群,指定 成员可见的任务仅对被指定成员开放。 - 新增 projects.department_owner_view 字段(migration) - ProjectController::update 支持读写该开关 - UserDepartment::ownerViewContext 过滤已关闭项目,并合并为单次 JOIN 查询 - ProjectTask::findForDepartmentView / task__one / tasks 列表尊重任务可见性 - WebSocketDialog::checkDialog 任务群按可见性放行 - 前端项目设置新增开关(仅系统开启该功能时显示) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0863e5529a
commit
7f7a82b4b8
@ -311,6 +311,7 @@ class ProjectController extends AbstractController
|
||||
* @apiParam {Number} [archive_days] 自动归档天数
|
||||
* @apiParam {String} [ai_auto_analyze] AI自动分析(open|close)
|
||||
* @apiParam {String} [task_template_share] 共享模板(open|close)
|
||||
* @apiParam {String} [department_owner_view] 部门负责人视角可见(open|close)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@ -327,6 +328,7 @@ class ProjectController extends AbstractController
|
||||
$archive_days = intval(Request::input('archive_days'));
|
||||
$ai_auto_analyze = Request::input('ai_auto_analyze');
|
||||
$task_template_share = Request::input('task_template_share');
|
||||
$department_owner_view = Request::input('department_owner_view');
|
||||
if (mb_strlen($name) < 2) {
|
||||
return Base::retError('项目名称不可以少于2个字');
|
||||
} elseif (mb_strlen($name) > 32) {
|
||||
@ -342,7 +344,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
//
|
||||
$project = Project::userProject($project_id, true, true);
|
||||
AbstractModel::transaction(function () use ($archive_days, $archive_method, $ai_auto_analyze, $task_template_share, $desc, $name, $project) {
|
||||
AbstractModel::transaction(function () use ($archive_days, $archive_method, $ai_auto_analyze, $task_template_share, $department_owner_view, $desc, $name, $project) {
|
||||
if ($project->name != $name) {
|
||||
$project->addLog("修改项目名称", [
|
||||
'change' => [$project->name, $name]
|
||||
@ -380,6 +382,12 @@ class ProjectController extends AbstractController
|
||||
]);
|
||||
$project->task_template_share = $task_template_share;
|
||||
}
|
||||
if (in_array($department_owner_view, ['open', 'close']) && $project->department_owner_view != $department_owner_view) {
|
||||
$project->addLog("修改负责人视角可见", [
|
||||
'change' => [$project->department_owner_view, $department_owner_view]
|
||||
]);
|
||||
$project->department_owner_view = $department_owner_view;
|
||||
}
|
||||
$project->save();
|
||||
});
|
||||
$project->pushMsg('update');
|
||||
@ -1406,15 +1414,12 @@ class ProjectController extends AbstractController
|
||||
$query->on('project_sub_task_visibility_users.task_id', '=', 'project_tasks.parent_id');
|
||||
$query->where('project_sub_task_visibility_users.userid', $userid);
|
||||
});
|
||||
$builder->where(function ($query) use ($userid, $departmentView) {
|
||||
$builder->where(function ($query) use ($userid) {
|
||||
$query->where("project_tasks.visibility", 1);
|
||||
$query->orWhere("project_users.userid", $userid);
|
||||
$query->orWhere("project_task_users.userid", $userid);
|
||||
$query->orWhere("project_task_visibility_users.userid", $userid);
|
||||
$query->orWhere("project_sub_task_visibility_users.userid", $userid);
|
||||
if ($departmentView['enabled']) {
|
||||
$query->orWhereIn('project_tasks.project_id', $departmentView['project_ids']);
|
||||
}
|
||||
});
|
||||
// 优化子查询汇总
|
||||
$builder->leftJoinSub(function ($query) {
|
||||
@ -2048,7 +2053,7 @@ class ProjectController extends AbstractController
|
||||
$projectOwnerids = ProjectUser::whereProjectId($task->project_id)
|
||||
->whereIn('owner', [ProjectUser::OWNER_PRIMARY, ProjectUser::OWNER_DEPUTY])
|
||||
->pluck('userid')->map(fn($v) => (int)$v)->toArray(); // 项目负责人(含项目管理员)
|
||||
if (!UserDepartment::isDepartmentReadonlyProject($departmentView, intval($task->project_id)) && $task->visibility != 1 && !in_array($user->userid, $projectOwnerids)) {
|
||||
if ($task->visibility != 1 && !in_array($user->userid, $projectOwnerids)) {
|
||||
$taskUserids = ProjectTaskUser::whereTaskId($task_id)->pluck('userid')->toArray(); //任务负责人、协助人
|
||||
$subTaskUserids = ProjectTaskUser::whereTaskPid($task_id)->pluck('userid')->toArray(); //子任务负责人、协助人
|
||||
$visibleUserids = ProjectTaskVisibilityUser::whereTaskId($task_id)->pluck('userid')->toArray(); //可见人
|
||||
|
||||
@ -24,6 +24,7 @@ use Request;
|
||||
* @property int|null $archive_days 自动归档天数
|
||||
* @property string|null $ai_auto_analyze AI自动分析
|
||||
* @property string|null $task_template_share 共享模板开关
|
||||
* @property string|null $department_owner_view 部门负责人视角可见开关
|
||||
* @property string|null $user_simple 成员总数|1,2,3
|
||||
* @property int|null $dialog_id 聊天会话ID
|
||||
* @property \Illuminate\Support\Carbon|null $archived_at 归档时间
|
||||
|
||||
@ -2278,7 +2278,8 @@ class ProjectTask extends AbstractModel
|
||||
$builder->withTrashed();
|
||||
}
|
||||
$task = $builder->first();
|
||||
if (!empty($task) && UserDepartment::isDepartmentReadonlyProject($departmentView, intval($task->project_id))) {
|
||||
// 仅"全员可见"(visibility=1)的任务走负责人只读视角;指定成员可见的任务交由 userTask 按可见性校验
|
||||
if (!empty($task) && intval($task->visibility) === 1 && UserDepartment::isDepartmentReadonlyProject($departmentView, intval($task->project_id))) {
|
||||
if ($archived === true && $task->archived_at != null) {
|
||||
throw new ApiException('任务已归档', ['task_id' => $task_id]);
|
||||
}
|
||||
|
||||
@ -567,10 +567,17 @@ class UserDepartment extends AbstractModel
|
||||
if (empty($memberUserids)) {
|
||||
return $empty;
|
||||
}
|
||||
$projectIds = ProjectUser::whereIn('userid', $memberUserids)
|
||||
->pluck('project_id')
|
||||
// 项目可单独关闭"部门负责人视角可见",关闭后对负责人隐藏(含项目和任务群聊)
|
||||
$projectIds = ProjectUser::whereIn('project_users.userid', $memberUserids)
|
||||
->join('projects', 'projects.id', '=', 'project_users.project_id')
|
||||
->whereNull('projects.deleted_at')
|
||||
->where(function ($query) {
|
||||
$query->where('projects.department_owner_view', '<>', 'close')
|
||||
->orWhereNull('projects.department_owner_view');
|
||||
})
|
||||
->distinct()
|
||||
->pluck('projects.id')
|
||||
->map(fn($v) => intval($v))
|
||||
->unique()
|
||||
->values()
|
||||
->toArray();
|
||||
$ownProjectIds = ProjectUser::whereUserid($user->userid)
|
||||
|
||||
@ -904,15 +904,19 @@ class WebSocketDialog extends AbstractModel
|
||||
case 'project':
|
||||
case 'task':
|
||||
// 项目群、任务群对话校验是否在项目内
|
||||
$taskVisibility = 1; // 项目群不涉及任务可见性,按可见处理
|
||||
if ($dialog->group_type === 'project') {
|
||||
$projectId = intval(Project::whereDialogId($dialog->id)->value('id'));
|
||||
} else {
|
||||
$projectId = intval(ProjectTask::whereDialogId($dialog->id)->value('project_id'));
|
||||
$taskRow = ProjectTask::select(['project_id', 'visibility'])->whereDialogId($dialog->id)->first();
|
||||
$projectId = intval($taskRow?->project_id);
|
||||
$taskVisibility = intval($taskRow?->visibility);
|
||||
}
|
||||
if ($projectId > 0 && ProjectUser::whereProjectId($projectId)->whereUserid($userid)->exists()) {
|
||||
return $dialog;
|
||||
}
|
||||
if ($projectId > 0 && $checkOwner === false) {
|
||||
// 部门负责人只读视角:项目群放行;任务群仅"全员可见"任务放行,指定成员可见任务不放行
|
||||
if ($projectId > 0 && $checkOwner === false && ($dialog->group_type === 'project' || $taskVisibility === 1)) {
|
||||
$departmentView = UserDepartment::ownerViewContext(User::auth(), true);
|
||||
if (UserDepartment::isDepartmentReadonlyProject($departmentView, $projectId)) {
|
||||
return $dialog;
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddDepartmentOwnerViewToProjectsTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::table('projects', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('projects', 'department_owner_view')) {
|
||||
$table->string('department_owner_view', 20)->default('open')->after('task_template_share')->comment('部门负责人视角可见开关');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::table('projects', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('projects', 'department_owner_view')) {
|
||||
$table->dropColumn('department_owner_view');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -994,3 +994,4 @@ LDAP 用户缺少邮箱属性,请联系管理员配置
|
||||
该用户不存在
|
||||
无权操作此模板
|
||||
修改共享模板
|
||||
修改负责人视角可见
|
||||
|
||||
@ -2392,3 +2392,6 @@ AI任务分析
|
||||
开启后,添加任务时可使用其他项目共享的任务模板。
|
||||
关闭后,添加任务时仅加载本项目模板,不显示其他项目共享模板。
|
||||
根据系统设置的自动归档规则执行
|
||||
负责人视角
|
||||
开启后,部门负责人可只读查看本项目及其全员可见任务。
|
||||
关闭后,本项目及其群聊对部门负责人视角隐藏。
|
||||
|
||||
@ -452,6 +452,14 @@
|
||||
<div v-if="settingData.task_template_share === 'open'" class="form-tip">{{$L('开启后,添加任务时可使用其他项目共享的任务模板。')}}</div>
|
||||
<div v-else class="form-tip">{{$L('关闭后,添加任务时仅加载本项目模板,不显示其他项目共享模板。')}}</div>
|
||||
</FormItem>
|
||||
<FormItem v-if="systemConfig.department_owner_project_view === 'open'" :label="$L('负责人视角')" prop="department_owner_view">
|
||||
<RadioGroup v-model="settingData.department_owner_view">
|
||||
<Radio label="open">{{$L('开启')}}</Radio>
|
||||
<Radio label="close">{{$L('关闭')}}</Radio>
|
||||
</RadioGroup>
|
||||
<div v-if="settingData.department_owner_view === 'open'" class="form-tip">{{$L('开启后,部门负责人可只读查看本项目及其全员可见任务。')}}</div>
|
||||
<div v-else class="form-tip">{{$L('关闭后,本项目及其群聊对部门负责人视角隐藏。')}}</div>
|
||||
</FormItem>
|
||||
</Form>
|
||||
<div slot="footer" class="adaption">
|
||||
<Button type="default" @click="settingShow=false">{{$L('取消')}}</Button>
|
||||
@ -1665,7 +1673,8 @@ export default {
|
||||
archive_method: this.projectData.archive_method,
|
||||
archive_days: this.projectData.archive_days,
|
||||
ai_auto_analyze: this.projectData.ai_auto_analyze || 'open',
|
||||
task_template_share: this.projectData.task_template_share || 'open'
|
||||
task_template_share: this.projectData.task_template_share || 'open',
|
||||
department_owner_view: this.projectData.department_owner_view || 'open'
|
||||
});
|
||||
this.settingShow = true;
|
||||
this.$nextTick(() => {
|
||||
|
||||
@ -117,7 +117,7 @@
|
||||
</div>
|
||||
<Scrollbar ref="scroller" class="scroller" :touch-content-blur="false">
|
||||
<Alert v-if="taskDetail.department_readonly" class="task-readonly-alert" type="info" show-icon>
|
||||
{{$L('当前为负责人视角:你可查看任务内容、动态和附件,并参与讨论,但不能编辑任务。')}}
|
||||
{{$L('当前为负责人 ,并参与讨论,但不能编辑任务。')}}
|
||||
</Alert>
|
||||
<Alert v-if="!isDepartmentReadonly && taskDetail.task_user !== undefined && getOwner.length === 0" class="receive-box" type="warning">
|
||||
<span class="receive-text">{{$L('该任务尚未被领取,点击这里')}}</span>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user