dootask/app/Models/UserDepartment.php

457 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Models;
use App\Exceptions\ApiException;
use Cache;
/**
* App\Models\UserDepartment
*
* @property int $id
* @property string|null $name 部门名称
* @property int|null $dialog_id 聊天会话ID
* @property int|null $parent_id 上级部门
* @property int|null $owner_userid 部门负责人
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelHidden()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel change($array)
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel getKeyValue()
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment query()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel remove()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel saveOrIgnore()
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereOwnerUserid($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereParentId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereUpdatedAt($value)
* @mixin \Eloquent
*/
class UserDepartment extends AbstractModel
{
protected $appends = [
'deputy_userids',
];
/**
* 获取所有父级部门
* @return array
*/
public function parents()
{
$parents = [];
$parent = $this;
while ($parent) {
$parents[] = $parent;
$parent = $parent->parent_id ? self::find($parent->parent_id) : null;
}
return $parents;
}
/**
* 部门管理员 userid 列表
* @return array
*/
public function getDeputyUseridsAttribute(): array
{
if (empty($this->id)) {
return [];
}
return \DB::table('user_department_owners')
->where('department_id', $this->id)
->pluck('userid')
->map(fn($v) => (int)$v)
->toArray();
}
/**
* 是否部门负责人(与 owner_userid 一致)
*/
public function isPrimaryOwner($userid): bool
{
if (empty($this->id) || $userid <= 0) {
return false;
}
return (int)$this->owner_userid === (int)$userid;
}
/**
* 是否部门管理员(在 user_department_owners 表里)
*/
public function isDeputyOwner($userid): bool
{
if (empty($this->id) || $userid <= 0) {
return false;
}
return \DB::table('user_department_owners')
->where('department_id', $this->id)
->where('userid', $userid)
->exists();
}
/**
* 是否负责人(含部门管理员)
*/
public function isOwner($userid): bool
{
return $this->isPrimaryOwner($userid) || $this->isDeputyOwner($userid);
}
/**
* 保存部门
* @param $data
* @param $dialogUseid
*/
public function saveDepartment($data = [], $dialogUseid = 0) {
AbstractModel::transaction(function () use ($dialogUseid, $data) {
$oldUser = null;
$newUser = null;
if ($data['owner_userid'] !== $this->owner_userid) {
$oldUser = User::find($this->owner_userid);
$newUser = User::find($data['owner_userid']);
}
$this->updateInstance($data);
//
// 防御:新负责人若残留在 user_department_owners 中(如曾是该部门管理员),清理掉
// 否则后续 delDeputy / 罢免接口会把当前部门负责人误移出部门
if ($this->id && (int)$this->owner_userid > 0) {
\DB::table('user_department_owners')
->where('department_id', $this->id)
->where('userid', (int)$this->owner_userid)
->delete();
}
//
if ($this->dialog_id > 0) {
// 已有群
$dialog = WebSocketDialog::find($this->dialog_id);
if ($dialog) {
$oldOwnerId = (int)$dialog->owner_id;
$dialog->name = $this->name;
$dialog->owner_id = $this->owner_userid;
if ($dialog->save()) {
$dialog->joinGroup($this->owner_userid, 0, true);
// 同步 role原负责人 role=0、新负责人 role=1部门管理员 role=2 保留不动)
if ($oldOwnerId > 0 && $oldOwnerId !== (int)$this->owner_userid) {
WebSocketDialogUser::where('dialog_id', $dialog->id)
->where('userid', $oldOwnerId)
->update(['role' => 0]);
}
WebSocketDialogUser::where('dialog_id', $dialog->id)
->where('userid', $this->owner_userid)
->update(['role' => 1]);
$dialog->pushMsg("groupUpdate", [
'id' => $dialog->id,
'name' => $dialog->name,
'owner_id' => $dialog->owner_id,
'deputy_ids' => $dialog->fresh()->deputy_ids,
]);
}
}
} elseif ($dialogUseid > 0) {
// 使用现有群
$dialog = WebSocketDialog::whereType('group')->whereGroupType('user')->find($dialogUseid);
if (empty($dialog)) {
throw new ApiException("选择现有聊天群不存在");
}
$oldOwnerId = (int)$dialog->owner_id;
$dialog->name = $this->name;
$dialog->owner_id = $this->owner_userid;
$dialog->group_type = 'department';
if ($dialog->save()) {
$dialog->joinGroup($this->owner_userid, 0, true);
// 同步 role原负责人 role=0、新负责人 role=1、原部门管理员 role=0
// 原部门管理员清零:避免 dialog_users.role=2 与 user_department_owners 不一致
// (部门管理员关系不带过来,须通过 addDeputy 显式重新任命)
if ($oldOwnerId > 0 && $oldOwnerId !== (int)$this->owner_userid) {
WebSocketDialogUser::where('dialog_id', $dialog->id)
->where('userid', $oldOwnerId)
->update(['role' => 0]);
}
WebSocketDialogUser::where('dialog_id', $dialog->id)
->where('userid', '!=', $this->owner_userid)
->where('role', 2)
->update(['role' => 0]);
WebSocketDialogUser::where('dialog_id', $dialog->id)
->where('userid', $this->owner_userid)
->update(['role' => 1]);
$dialog->pushMsg("groupUpdate", [
'id' => $dialog->id,
'name' => $dialog->name,
'owner_id' => $dialog->owner_id,
'group_type' => $dialog->group_type,
'deputy_ids' => $dialog->fresh()->deputy_ids,
]);
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'notice', [
'notice' => User::nickname() . " 将此群改为部门群"
], User::userid(), true, true);
}
$this->dialog_id = $dialog->id;
} else {
// 创建群
$dialog = WebSocketDialog::createGroup($this->name, [$this->owner_userid], 'department', $this->owner_userid);
if (empty($dialog)) {
throw new ApiException("创建群组失败");
}
$this->dialog_id = $dialog->id;
}
$this->save();
//
if ($oldUser) {
$oldUser->department = array_diff($oldUser->department, [$this->id]);
$oldUser->department = "," . implode(",", $oldUser->department) . ",";
$oldUser->save();
// 原主从 users.department 移除后也要退出部门群(保持成员关系=群关系一致)
// checkDelete=false业务流程跳过 owner_id/important 校验
if ($this->dialog_id > 0) {
$dialog = WebSocketDialog::find($this->dialog_id);
$dialog?->exitGroup($oldUser->userid, 'remove', false, true);
}
}
if ($newUser) {
$newUser->department = array_diff($newUser->department, [$this->id]);
$newUser->department = array_merge($newUser->department, [$this->id]);
$newUser->department = "," . implode(",", $newUser->department) . ",";
$newUser->save();
}
});
}
/**
* 任命部门管理员
* - 部门管理员自动加入 users.department成为部门成员与负责人对齐
* - 部门管理员自动加入部门群 + 设 role=2
* - 幂等(已是部门管理员不报错)
*
* @param int $userid
* @return void
* @throws ApiException
*/
public function addDeputy($userid)
{
if ($userid <= 0) {
throw new ApiException('请选择有效的成员');
}
$user = User::whereUserid($userid)->first();
if (!$user) {
throw new ApiException('该用户不存在');
}
if ((int)$this->owner_userid === (int)$userid) {
throw new ApiException('不能将部门负责人任命为部门管理员');
}
AbstractModel::transaction(function () use ($userid, $user) {
// 写部门管理员表unique key 自动幂等)
\DB::table('user_department_owners')->insertOrIgnore([
'department_id' => $this->id,
'userid' => $userid,
]);
// 加入 users.department成为部门成员与负责人对齐
$userDeptIds = $user->department; // accessor 返回数组
if (!in_array($this->id, $userDeptIds)) {
$userDeptIds = array_merge($userDeptIds, [$this->id]);
$user->department = "," . implode(",", $userDeptIds) . ",";
$user->save();
}
// 加部门管理员入部门群 + 设 role=2 + important=true
if ($this->dialog_id > 0) {
$dialog = WebSocketDialog::find($this->dialog_id);
if ($dialog) {
// joinGroup($userid, $inviter, $important=null, $pushMsg=true)
// important=true部门管理员成员关系不可被普通群操作打散
$dialog->joinGroup($userid, 0, true, true);
WebSocketDialogUser::where('dialog_id', $dialog->id)
->where('userid', $userid)
->update(['role' => 2]);
$dialog->pushMsg('groupUpdate', [
'id' => $dialog->id,
'deputy_ids' => $dialog->fresh()->deputy_ids,
]);
}
}
});
}
/**
* 罢免部门管理员
* - 删部门管理员表记录
* - 从 users.department 移除该部门 ID与负责人"离开部门"对齐)
* - 退出部门群(成员关系=群关系一致)
* - 幂等
*
* @param int $userid
* @return void
*/
public function delDeputy($userid)
{
if ($userid <= 0) {
return;
}
// 防御当前部门负责人不能被罢免saveDepartment 应已清理残留,此处兜底)
// 仅清理 user_department_owners 中的悬挂记录,绝不联动移除其部门成员关系/部门群成员
if ((int)$this->owner_userid === (int)$userid) {
\DB::table('user_department_owners')
->where('department_id', $this->id)
->where('userid', $userid)
->delete();
return;
}
AbstractModel::transaction(function () use ($userid) {
$deleted = \DB::table('user_department_owners')
->where('department_id', $this->id)
->where('userid', $userid)
->delete();
if ($deleted > 0) {
// 从 users.department 移除该部门 ID
$user = User::whereUserid($userid)->first();
if ($user) {
$userDeptIds = $user->department;
if (in_array($this->id, $userDeptIds)) {
$userDeptIds = array_diff($userDeptIds, [$this->id]);
$user->department = "," . implode(",", $userDeptIds) . ",";
$user->save();
}
}
// 退出部门群exitGroup 会清除 dialog_users 记录role 随之消失)
if ($this->dialog_id > 0) {
$dialog = WebSocketDialog::find($this->dialog_id);
if ($dialog) {
// checkDelete=false业务流程跳过 owner_id/important 校验
$dialog->exitGroup($userid, 'remove', false, true);
$dialog->pushMsg('groupUpdate', [
'id' => $dialog->id,
'deputy_ids' => $dialog->fresh()->deputy_ids,
]);
}
}
}
});
}
/**
* 删除部门
* @return void
*/
public function deleteDepartment() {
// 删除子部门
$list = self::whereParentId($this->id)->get();
foreach ($list as $item) {
$item->deleteDepartment();
}
// 移出成员
User::where("department", "like", "%,{$this->id},%")->chunk(100, function($items) {
/** @var User $user */
foreach ($items as $user) {
$user->department = array_diff($user->department, [$this->id]);
$user->department = "," . implode(",", $user->department) . ",";
$user->save();
}
});
// 解散群组
$dialog = WebSocketDialog::find($this->dialog_id);
$dialog?->deleteDialog();
// 清理部门管理员记录(防悬挂)
\DB::table('user_department_owners')->where('department_id', $this->id)->delete();
//
$this->delete();
}
/**
* 移交部门身份
* @param $originalUserid
* @param $newUserid
* @return void
*/
public static function transfer($originalUserid, $newUserid)
{
// 部门负责人转让(保持现有逻辑)
self::whereOwnerUserid($originalUserid)->chunkById(100, function ($list) use ($originalUserid, $newUserid) {
/** @var self $item */
foreach ($list as $item) {
$item->saveDepartment([
'owner_userid' => $newUserid,
]);
}
});
// 部门管理员离职清理(新增):直接删除离职用户的所有部门管理员记录
// 不需要清群 role —— UserTransfer::exitDialog 会把人踢出所有群role 随成员关系一起消失
\DB::table('user_department_owners')
->where('userid', $originalUserid)
->delete();
}
/**
* 递归获取所有子部门ID
* @param int $departmentId
* @return array
*/
public static function getAllSubDepartmentIds($departmentId)
{
$subIds = [];
$directSubs = self::whereParentId($departmentId)->pluck('id')->toArray();
foreach ($directSubs as $subId) {
$subIds[] = $subId;
// 递归获取子部门的子部门
$subSubIds = self::getAllSubDepartmentIds($subId);
$subIds = array_merge($subIds, $subSubIds);
}
return array_unique($subIds);
}
/**
* 获取部门基本信息缓存时间1小时
* @param int|array $ids
* @return \Illuminate\Support\Collection|static|null
*/
public static function getDepartmentsByIds($ids)
{
$ids = is_array($ids) ? $ids : [$ids];
$departments = collect();
$uncachedIds = [];
foreach ($ids as $id) {
$cacheKey = "department_info_{$id}";
$department = Cache::get($cacheKey);
if ($department) {
$departments->push($department);
} else {
$uncachedIds[] = $id;
}
}
if (!empty($uncachedIds)) {
$dbDepartments = self::select(['id', 'name', 'parent_id', 'owner_userid'])->whereIn('id', $uncachedIds)->get();
foreach ($dbDepartments as $department) {
$cacheKey = "department_info_{$department->id}";
Cache::put($cacheKey, $department, 60 * 60); // 1小时
$departments->push($department);
}
}
// 保持返回顺序与传入ids一致
$departments = $departments->keyBy('id');
$result = collect();
foreach ($ids as $id) {
if ($departments->has($id)) {
$result->push($departments->get($id));
}
}
return is_array($ids) ? $result : $result->first();
}
}