feat(todo): 待办设置权限放开系统管理员(任意群可设/取消他人待办)

- checkTodoOwnerPermission 最高优先级放行系统管理员,覆盖无群主的全员群等场景
- 同步设/取消待办、到点提醒接口报错文案与系统设置描述
- 补充管理员放行测试(user/project/全员群)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
kuaifan 2026-06-01 15:02:03 +00:00
parent 0a6e944a9a
commit daca384822
7 changed files with 42 additions and 6 deletions

View File

@ -2647,7 +2647,7 @@ class DialogController extends AbstractController
if (Base::settingFind('system', 'todo_set_permission') === 'close') {
$others = array_diff($userids, [$user->userid]);
if ($others && !$dialog->checkTodoOwnerPermission($user->userid)) {
return Base::retError('仅群主、项目/任务负责人可设置或取消他人待办');
return Base::retError('仅群主、项目/任务负责人或系统管理员可设置或取消他人待办');
}
}
//

View File

@ -723,6 +723,10 @@ class WebSocketDialog extends AbstractModel
if ($userid <= 0) {
return false;
}
// 系统管理员:可管理任意会话的他人待办(与管理员全局管理能力一致,覆盖无群主的全员群等)
if (User::find($userid)?->isAdmin()) {
return true;
}
// 群主 / 群管理员
if ($this->isOwner($userid)) {
return true;

View File

@ -428,7 +428,7 @@ class WebSocketDialogMsg extends AbstractModel
$affected = array_unique(array_merge($cancel, $setup)); // 本次真正影响到的用户
$others = array_diff($affected, [$sender]); // 排除"自己"
if ($others && !$dialog->checkTodoOwnerPermission($sender)) {
return Base::retError('仅群主、项目/任务负责人可设置或取消他人待办');
return Base::retError('仅群主、项目/任务负责人或系统管理员可设置或取消他人待办');
}
}
//

View File

@ -976,7 +976,7 @@ LDAP 用户缺少邮箱属性,请联系管理员配置
负责人不能任命为项目管理员
普通成员不能移出群主或群管理员
只有群主、群管理员或邀请人可以移出成员
仅群主、项目/任务负责人可设置或取消他人待办
仅群主、项目/任务负责人或系统管理员可设置或取消他人待办
请选择文件
仅支持 xls/xlsx/csv 文件
文件中没有可导入的数据

View File

@ -2395,7 +2395,7 @@ AI任务分析
部门管理员同步失败
待办设置权限
允许:所有成员可设置/取消他人待办。
禁止:仅本人、群主(含群管理员)、项目负责人(含项目管理员)、任务负责人可设置/取消待办。
禁止:仅本人、系统管理员、群主(含群管理员)、项目负责人(含项目管理员)、任务负责人可设置/取消待办。
批量导入用户
请按模板填写后上传,列顺序:邮箱、昵称、初始密码、职位(选填)单次最多导入500条。

View File

@ -213,7 +213,7 @@
<Radio label="close">{{$L('禁止')}}</Radio>
</RadioGroup>
<div v-if="formDatum.todo_set_permission == 'open'" class="form-tip">{{$L('允许所有成员可设置/取消他人待办')}}</div>
<div v-else class="form-tip">{{$L('禁止:仅本人、群主(含群管理员)、项目负责人(含项目管理员)、任务负责人可设置/取消待办。')}}</div>
<div v-else class="form-tip">{{$L('禁止:仅本人、系统管理员、群主(含群管理员)、项目负责人(含项目管理员)、任务负责人可设置/取消待办。')}}</div>
</FormItem>
<FormItem :label="$L('视频转换')" prop="convertVideo">
<RadioGroup v-model="formDatum.convert_video">

View File

@ -35,6 +35,15 @@ class TodoSetPermissionTest extends TestCase
return $user;
}
/** 系统管理员identity 含 admin */
private function makeAdmin(string $email): User
{
$user = $this->makeUser($email);
$user->identity = Base::arrayImplode(['admin']);
$user->save();
return $user->fresh();
}
/** 普通群group_type=user设置群主与群管理员 */
private function makeUserGroup(int $ownerUserid, array $members, array $deputyUserids = []): WebSocketDialog
{
@ -137,6 +146,29 @@ class TodoSetPermissionTest extends TestCase
$this->assertFalse($taskDialog->checkTodoOwnerPermission($member->userid), '普通成员应拒绝');
}
public function test_admin_allowed_in_any_group()
{
$admin = $this->makeAdmin('t_admin@test.local');
$owner = $this->makeUser('t_admin_o@test.local');
$member = $this->makeUser('t_admin_m@test.local');
// 普通群:管理员既非群主也非成员,仍放行;普通成员仍拒绝
$userGroup = $this->makeUserGroup($owner->userid, [$member->userid]);
$this->assertTrue($userGroup->checkTodoOwnerPermission($admin->userid), '管理员在普通群应放行');
$this->assertFalse($userGroup->checkTodoOwnerPermission($member->userid), '普通成员仍应拒绝');
// 项目群:管理员非项目负责人,仍放行
$project = $this->makeProjectWithDialog($owner->userid, [$member->userid]);
$pdialog = WebSocketDialog::find($project->dialog_id);
$this->assertTrue($pdialog->checkTodoOwnerPermission($admin->userid), '管理员在项目群应放行');
// 全员群(无群主):管理员放行,普通成员拒绝
$allGroup = WebSocketDialog::createGroup('Test_all', [$owner->userid, $member->userid], 'all')->fresh();
$this->assertSame(0, (int)$allGroup->owner_id, '全员群应无群主');
$this->assertTrue($allGroup->checkTodoOwnerPermission($admin->userid), '管理员在全员群应放行');
$this->assertFalse($allGroup->checkTodoOwnerPermission($member->userid), '全员群普通成员应拒绝');
}
/**
* 镜像 WebSocketDialogMsg::toggleTodoMsg 内的权限闸门决策。
* @param string $switch 开关值 open|close
@ -234,7 +266,7 @@ class TodoSetPermissionTest extends TestCase
$this->assertTrue(Base::isError($res), '被拦截路径应返回错误响应');
$this->assertSame(0, $res['ret']);
$this->assertStringContainsString('仅群主、项目/任务负责人可设置或取消他人待办', $res['msg']);
$this->assertStringContainsString('仅群主、项目/任务负责人或系统管理员可设置或取消他人待办', $res['msg']);
// 被拦截后不应写入任何待办记录
$this->assertSame(0, WebSocketDialogMsgTodo::whereMsgId($msg->id)->count());