Files
lgp-admin-plus-api/app/Service/DepartmentService.php

204 lines
6.4 KiB
PHP
Raw Permalink Normal View History

<?php
namespace App\Service;
use App\BaseApp\BaseService;
use App\Models\AdminModel;
use App\Models\business\DepartmentModel;
use Exception;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
/**
* 部门服务
*
* 老后台的 department/option 返回的是扁平列表,前端 BasicTree 拿到后其实渲染不出层级,
* pid 白存了。这里 option 直接返回真正的树,账号列表左侧的部门树才能用。
*/
class DepartmentService extends BaseService
{
public function __construct()
{
parent::__construct();
$this->model = DepartmentModel::class;
$this->selectField = ['id', 'name', 'desc', 'status', 'pid', 'color', 'created_at', 'updated_at'];
$this->queryField = ['name' => 'like', 'status' => '=', 'pid' => '='];
$this->orderBy = ['name' => 'id', 'sort' => 'asc'];
}
/**
* 分页列表,附带每个部门的账号数
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function list(): array
{
$result = $this->getPageList();
$ids = array_column($result['items'] ?? [], 'id');
$counts = $this->countAdminByDepartment($ids);
foreach ($result['items'] as &$item) {
$item['admin_count'] = $counts[$item['id']] ?? 0;
}
unset($item);
return $result;
}
/**
* 部门树(账号列表左侧筛选树、部门表单的上级选择都用这个)
*/
public function option(): array
{
$rows = DepartmentModel::where('deleted_at', 0)
->orderBy('id')
->get(['id', 'name', 'pid', 'status', 'color'])
->toArray();
return $this->utils->tree($rows);
}
/**
* 部门树下拉(部门表单选上级用)。带 $isSelect 时首位补「顶级部门」
*/
public function getTreeOption(bool $isSelect = false): array
{
$rows = DepartmentModel::where('deleted_at', 0)
->orderBy('id')
->get(['id', 'name', 'pid'])
->toArray();
$result = $this->utils->tree($rows);
if ($isSelect) {
array_unshift($result, ['id' => 0, 'name' => '顶级部门', 'pid' => 0]);
}
return $result;
}
/**
* @throws Exception
*/
public function detail($id): mixed
{
return $this->getDetail($id);
}
/**
* 新增部门
*/
public function create($params): mixed
{
$this->assertNameUnique((string) ($params['name'] ?? ''), (int) ($params['pid'] ?? 0));
return $this->insert($params);
}
/**
* 编辑部门
* @throws Exception
*/
public function update($id, $params): mixed
{
$id = (int) $id;
if (array_key_exists('name', $params)) {
$this->assertNameUnique((string) $params['name'], (int) ($params['pid'] ?? 0), $id);
}
// 上级不能指向自己或自己的子孙,否则树会成环、递归建树直接栈溢出
if (array_key_exists('pid', $params)) {
$pid = (int) $params['pid'];
if ($pid === $id) {
$this->utils->errorThrow('上级部门不能是自己');
}
if ($pid > 0 && in_array($pid, $this->descendantIds($id), true)) {
$this->utils->errorThrow('上级部门不能是自己的下级');
}
}
return $this->save($id, $params);
}
/**
* 删除部门:有子部门或仍有账号在用时拒绝,避免留下悬空引用
* @throws Exception
*/
public function delete($id): mixed
{
$ids = array_values(array_filter(array_map('intval', is_array($id) ? $id : [$id])));
if (empty($ids)) {
$this->utils->errorThrow('参数错误');
}
$hasChild = DepartmentModel::whereIn('pid', $ids)->where('deleted_at', 0)->exists();
if ($hasChild) {
$this->utils->errorThrow('存在下级部门,请先删除下级');
}
$inUse = AdminModel::whereIn('department_id', $ids)->where('deleted_at', 0)->exists();
if ($inUse) {
$this->utils->errorThrow('仍有账号属于该部门,请先调整账号所属部门');
}
return $this->del($ids);
}
/**
* 停用/启用部门
* @throws Exception
*/
public function status($id, $status): mixed
{
return $this->save((int) $id, ['status' => (int) $status]);
}
/**
* 同一父级下部门名不允许重复,否则树上出现两个同名节点没法分辨
*/
private function assertNameUnique(string $name, int $pid, int $exceptId = 0): void
{
$name = trim($name);
if ($name === '') {
$this->utils->errorThrow('部门名称不能为空');
}
$exists = DepartmentModel::where('name', $name)
->where('pid', $pid)
->where('deleted_at', 0)
->when($exceptId > 0, fn ($q) => $q->where('id', '<>', $exceptId))
->exists();
if ($exists) {
$this->utils->errorThrow('同级下已存在同名部门');
}
}
/**
* 取某部门的全部子孙 id用于成环校验
*/
private function descendantIds(int $id): array
{
$all = DepartmentModel::where('deleted_at', 0)->get(['id', 'pid']);
$childMap = [];
foreach ($all as $row) {
$childMap[(int) $row->pid][] = (int) $row->id;
}
$result = [];
$stack = $childMap[$id] ?? [];
while (!empty($stack)) {
$current = array_pop($stack);
if (in_array($current, $result, true)) {
continue;
}
$result[] = $current;
foreach ($childMap[$current] ?? [] as $child) {
$stack[] = $child;
}
}
return $result;
}
/**
* 部门账号数admin mysql 连接、department business 连接,跨连接不能 join分两次查
*/
private function countAdminByDepartment(array $departmentIds): array
{
if (empty($departmentIds)) {
return [];
}
return AdminModel::whereIn('department_id', $departmentIds)
->where('deleted_at', 0)
->groupBy('department_id')
->selectRaw('department_id, COUNT(*) AS c')
->pluck('c', 'department_id')
->all();
}
}