Files
2026-05-22 11:15:59 +08:00

966 lines
31 KiB
PHP
Executable File
Raw Permalink 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
// +----------------------------------------------------------------------
// | likeadmin快速开发前后端分离管理后台(PHP版)
// +----------------------------------------------------------------------
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
// | 开源版本可自由商用,可去除界面版权logo
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
// | github下载:https://github.com/likeshop-github/likeadmin
// | 访问官网:https://www.likeadmin.cn
// | likeadmin团队 版权所有 拥有最终解释权
// +----------------------------------------------------------------------
// | author: likeadminTeam
// +----------------------------------------------------------------------
namespace app\adminapi\logic\dept;
use app\adminapi\logic\stats\YejiStatsLogic;
use app\common\logic\BaseLogic;
use app\common\model\auth\Admin;
use app\common\model\auth\AdminDept;
use app\common\model\dept\Dept;
use app\common\service\DataScope\DataScopeService;
use think\facade\Db;
/**
* 部门管理逻辑
* Class DeptLogic
* @package app\adminapi\logic\dept
*/
class DeptLogic extends BaseLogic
{
/**
* @notes 部门列表
* @param $params
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author 段誉
* @date 2022/5/30 15:44
*/
public static function lists($params)
{
$where = [];
if (!empty($params['name'])) {
$where[] = ['name', 'like', '%' . $params['name'] . '%'];
}
if (isset($params['status']) && $params['status'] != '') {
$where[] = ['status', '=', $params['status']];
}
$lists = Dept::where($where)
->append(['status_desc'])
->order(['sort' => 'desc', 'id' => 'desc'])
->select()
->toArray();
$totalsWithSubtree = self::deptAdminCountTotalsWithSubtree();
foreach ($lists as &$row) {
$row['admin_count'] = $totalsWithSubtree[(int) $row['id']] ?? 0;
}
unset($row);
$pid = 0;
if (!empty($lists)) {
$pid = min(array_column($lists, 'pid'));
}
$tree = self::getTree($lists, $pid);
return $tree;
}
/**
* 每个部门人数(直属 + 所有下级部门),基于全表部门树汇总;管理员来自 admin_dept,排除已删除账号
*
* @return array<int, int> dept_id => count
*/
private static function deptAdminCountTotalsWithSubtree(): array
{
$treeRows = Dept::field(['id', 'pid'])->select()->toArray();
if ($treeRows === []) {
return [];
}
$allIds = array_map('intval', array_column($treeRows, 'id'));
$directMap = self::fetchDirectAdminCountMap($allIds);
$childrenByPid = [];
foreach ($treeRows as $r) {
$pid = (int) $r['pid'];
$id = (int) $r['id'];
if (!isset($childrenByPid[$pid])) {
$childrenByPid[$pid] = [];
}
$childrenByPid[$pid][] = $id;
}
$memo = [];
$dfs = function (int $id) use (&$dfs, $childrenByPid, $directMap, &$memo): int {
if (array_key_exists($id, $memo)) {
return $memo[$id];
}
$sum = $directMap[$id] ?? 0;
foreach ($childrenByPid[$id] ?? [] as $cid) {
$sum += $dfs((int) $cid);
}
$memo[$id] = $sum;
return $sum;
};
$out = [];
foreach ($allIds as $id) {
$out[$id] = $dfs($id);
}
return $out;
}
/**
* 各部门直属管理员人数
*
* @param array<int, int> $deptIds
* @return array<int, int>
*/
private static function fetchDirectAdminCountMap(array $deptIds): array
{
if ($deptIds === []) {
return [];
}
$adminTable = (new Admin())->getTable();
$rows = AdminDept::alias('ad')
->join($adminTable . ' a', 'a.id = ad.admin_id')
->whereNull('a.delete_time')
->whereIn('ad.dept_id', $deptIds)
->field('ad.dept_id, COUNT(*) AS admin_count')
->group('ad.dept_id')
->select()
->toArray();
$map = [];
foreach ($rows as $r) {
$map[(int) $r['dept_id']] = (int) $r['admin_count'];
}
return $map;
}
/**
* 名称含「二中心」的部门 id(与业绩看板二中心规则一致)。
*
* @return list<int>
*/
private static function findErCenterRootDeptIds(): array
{
$rows = Dept::whereNull('delete_time')
->field(['id', 'name'])
->select()
->toArray();
$out = [];
foreach ($rows as $r) {
$name = (string) ($r['name'] ?? '');
if ($name !== '' && mb_strpos($name, '二中心') !== false) {
$out[] = (int) $r['id'];
}
}
return array_values(array_unique($out));
}
/**
* @param list<int> $rootIds
*
* @return list<int>
*/
private static function unionErCenterSubtreeDeptIds(array $rootIds): array
{
$set = [];
foreach ($rootIds as $rid) {
$rid = (int) $rid;
if ($rid <= 0) {
continue;
}
foreach (self::getSelfAndDescendantIds($rid) as $id) {
$set[(int) $id] = true;
}
}
return array_keys($set);
}
/**
* 名称含「二中心」的部门及其全部下级 id(map),与业绩看板判定一致。
*
* @return array<int, true>
*/
public static function getErCenterSubtreeDeptIdSet(): array
{
$erRoots = self::findErCenterRootDeptIds();
$subtreeIds = self::unionErCenterSubtreeDeptIds($erRoots);
return array_fill_keys($subtreeIds, true);
}
/**
* 二中心复诊统计用的业务订单行(与 rollup 同源 SQL)。
*
* @return list<array{diagnosis_id: int, create_time: int, id: int, assistant_id: int}>
*/
private static function fetchErCenterRevisitCandidateOrders(?int $orderStartTs = null, ?int $orderEndTs = null): array
{
$q = Db::name('tcm_prescription_order')
->alias('o')
->join('tcm_diagnosis dg', 'dg.id = o.diagnosis_id AND dg.delete_time IS NULL', 'INNER')
->whereNull('o.delete_time')
->where('o.diagnosis_id', '>', 0)
->where('dg.assistant_id', '>', 0);
YejiStatsLogic::applyPrescriptionOrderNotCancelledForPerformanceQuery($q, 'o');
if ($orderStartTs !== null && $orderEndTs !== null) {
$q->where('o.create_time', 'between', [$orderStartTs, $orderEndTs]);
}
return $q
->field(['o.diagnosis_id', 'o.create_time', 'o.id', 'dg.assistant_id', 'o.creator_id'])
->order(['o.diagnosis_id' => 'asc', 'o.create_time' => 'asc', 'o.id' => 'asc'])
->select()
->toArray();
}
/**
* 二中心子树:各部门复诊 rollup(按订单创建人 creator_id 归属)。
* 候选订单仍要求 dg.assistant_id > 0(保持「er-center 案例 = 有医助的诊单」的定义),
* 但**复诊归属**改为订单创建人——leaf 部门归属取 creator_id 在二中心子树内的 canonical 部门,
* 并产出 by_creator_slots 供医助排行榜复诊列复用,与诊金 / 接诊诊单口径完全一致。
*
* @param list<int> $subtreeDeptIds
*
* @return array{
* totals: array<int, int>,
* slots: array<int, array<int, int>>,
* by_creator_slots: array<int, array<int, int>>
* }
*/
private static function computeErCenterRevisitFullPack(
array $subtreeDeptIds,
?int $orderStartTs = null,
?int $orderEndTs = null,
?array $visibleCreatorIds = null
): array {
$emptySlots = [];
$emptyTotals = [];
foreach ($subtreeDeptIds as $did) {
$did = (int) $did;
$emptySlots[$did] = [];
$emptyTotals[$did] = 0;
}
if ($subtreeDeptIds === []) {
return ['totals' => [], 'slots' => [], 'by_creator_slots' => []];
}
$subtreeSet = array_fill_keys($subtreeDeptIds, true);
$orderRows = self::fetchErCenterRevisitCandidateOrders($orderStartTs, $orderEndTs);
if ($orderRows === []) {
return ['totals' => $emptyTotals, 'slots' => $emptySlots, 'by_creator_slots' => []];
}
$byPatient = [];
foreach ($orderRows as $r) {
$pid = (int) ($r['diagnosis_id'] ?? 0);
if ($pid <= 0) {
continue;
}
$byPatient[$pid][] = $r;
}
$creatorNeed = [];
foreach ($byPatient as $orders) {
$n = \count($orders);
for ($i = 1; $i < $n; $i++) {
$cid = (int) ($orders[$i]['creator_id'] ?? 0);
if ($cid > 0) {
$creatorNeed[$cid] = true;
}
}
}
$creatorIds = array_keys($creatorNeed);
/** 复用 buildAssistantCanonicalDeptInSubtree —— 该方法实际是 admin → 子树内 canonical 部门,与 admin 角色无关 */
$canonicalDept = self::buildAssistantCanonicalDeptInSubtree($creatorIds, $subtreeSet);
$creatorFlip = null;
if ($visibleCreatorIds !== null && $visibleCreatorIds !== []) {
$creatorFlip = array_flip(array_values(array_unique(array_filter(
array_map('intval', $visibleCreatorIds),
static fn (int $id): bool => $id > 0
))));
}
/** @var array<int, array<int, int>> $leafByDeptSlot dept_id => [ slot => cnt ]slot≥2 */
$leafByDeptSlot = [];
/** @var array<int, array<int, int>> $byCreatorSlot 复诊按订单创建人聚合(与诊金/接诊诊单口径一致) */
$byCreatorSlot = [];
foreach ($byPatient as $orders) {
$n = \count($orders);
for ($i = 1; $i < $n; $i++) {
$slot = $i + 1;
$cid = (int) ($orders[$i]['creator_id'] ?? 0);
if ($creatorFlip !== null && ($cid <= 0 || !isset($creatorFlip[$cid]))) {
continue;
}
$deptId = $canonicalDept[$cid] ?? null;
if ($deptId === null || !isset($subtreeSet[$deptId])) {
continue;
}
if (!isset($leafByDeptSlot[$deptId])) {
$leafByDeptSlot[$deptId] = [];
}
$leafByDeptSlot[$deptId][$slot] = ($leafByDeptSlot[$deptId][$slot] ?? 0) + 1;
if ($cid > 0) {
if (!isset($byCreatorSlot[$cid])) {
$byCreatorSlot[$cid] = [];
}
$byCreatorSlot[$cid][$slot] = ($byCreatorSlot[$cid][$slot] ?? 0) + 1;
}
}
}
$childrenByPid = self::buildAllDeptChildrenByPid();
$rollupSlots = self::rollupRevisitSlotsBySubtree($subtreeDeptIds, $subtreeSet, $childrenByPid, $leafByDeptSlot);
$rollupTotals = [];
foreach ($subtreeDeptIds as $id) {
$id = (int) $id;
$rollupTotals[$id] = array_sum($rollupSlots[$id] ?? []);
}
foreach ($byCreatorSlot as $cid => $sm) {
if ($sm !== []) {
ksort($byCreatorSlot[$cid], SORT_NUMERIC);
}
}
return [
'totals' => $rollupTotals,
'slots' => $rollupSlots,
'by_creator_slots' => $byCreatorSlot,
];
}
/**
* 二中心子树:各部门复诊合计与分项(第 2 笔订单=复诊2…),供业绩看板等复用。
* $orderStartTs/$orderEndTs 均非 null 时仅统计该 create_time 窗口内业务单(与看板区间一致);均为 null 时不限时间(慎用,数据量大)。
*
* @return array{totals: array<int, int>, slots: array<int, array<int, int>>}
*/
public static function getErCenterRevisitRollupIndexedByDept(
?int $orderStartTs = null,
?int $orderEndTs = null,
?array $visibleCreatorIds = null
): array {
$erRoots = self::findErCenterRootDeptIds();
$subtreeIds = self::unionErCenterSubtreeDeptIds($erRoots);
if ($subtreeIds === []) {
return ['totals' => [], 'slots' => []];
}
$pack = self::computeErCenterRevisitFullPack($subtreeIds, $orderStartTs, $orderEndTs, $visibleCreatorIds);
return ['totals' => $pack['totals'], 'slots' => $pack['slots']];
}
/**
* 二中心子树复诊:按订单创建人 creator_id 聚合(与部门 rollup 同源,与诊金/接诊诊单口径一致)。
*
* @return array{totals: array<int, int>, slots: array<int, array<int, int>>}
*/
public static function getErCenterRevisitCountsByCreator(
?int $orderStartTs = null,
?int $orderEndTs = null,
?array $visibleCreatorIds = null
): array {
$erRoots = self::findErCenterRootDeptIds();
$subtreeIds = self::unionErCenterSubtreeDeptIds($erRoots);
if ($subtreeIds === []) {
return ['totals' => [], 'slots' => []];
}
$pack = self::computeErCenterRevisitFullPack($subtreeIds, $orderStartTs, $orderEndTs, $visibleCreatorIds);
$by = $pack['by_creator_slots'];
$totals = [];
$slots = [];
foreach ($by as $cid => $slotMap) {
$cid = (int) $cid;
$totals[$cid] = (int) array_sum($slotMap);
$slots[$cid] = $slotMap;
}
return ['totals' => $totals, 'slots' => $slots];
}
/**
* 二中心子树复诊:列出某「订单创建人」在区间内、复诊序列(slot≥2)命中的业务订单 id(用于看板侧栏按复诊笔数下钻)。
* $revisitSlot = 0 表示累计全部复诊(第 2 笔起);≥2 时仅该分项。
*
* @return list<int>
*/
public static function listErCenterRevisitOrderIdsForCreator(
int $creatorId,
int $revisitSlot,
int $orderStartTs,
int $orderEndTs
): array {
if ($creatorId <= 0 || $orderStartTs <= 0 || $orderEndTs < $orderStartTs) {
return [];
}
if ($revisitSlot < 0 || $revisitSlot === 1) {
return [];
}
$orderRows = self::fetchErCenterRevisitCandidateOrders($orderStartTs, $orderEndTs);
if ($orderRows === []) {
return [];
}
$byPatient = [];
foreach ($orderRows as $r) {
$pid = (int) ($r['diagnosis_id'] ?? 0);
if ($pid <= 0) {
continue;
}
$byPatient[$pid][] = $r;
}
$ids = [];
foreach ($byPatient as $orders) {
$n = \count($orders);
for ($i = 1; $i < $n; $i++) {
$slot = $i + 1;
if ($revisitSlot > 0 && $slot !== $revisitSlot) {
continue;
}
$cid = (int) ($orders[$i]['creator_id'] ?? 0);
if ($cid !== $creatorId) {
continue;
}
$oid = (int) ($orders[$i]['id'] ?? 0);
if ($oid > 0) {
$ids[$oid] = true;
}
}
}
return array_keys($ids);
}
/**
* 某展示部门行(组织架构上含下级 rollup)内,二中心复诊按**订单创建人**拆解笔数。
* 与看板行内「复诊」列口径一致:订单序列第 2 笔起;创建人 canonical 部门须落在二中心子树且在该部门行的子树内。
*
* @return array{rows: list<array{admin_id: int, name: string, order_count: int}>}
*/
public static function getErCenterRevisitAssistantBreakdownForDept(
int $rootDeptId,
int $revisitSlot,
int $orderStartTs,
int $orderEndTs,
?array $visibleCreatorIds = null
): array {
if ($rootDeptId <= 0 || $orderStartTs <= 0 || $orderEndTs < $orderStartTs) {
return ['rows' => []];
}
if ($revisitSlot < 0 || $revisitSlot === 1) {
return ['rows' => []];
}
$erSubtreeSet = self::getErCenterSubtreeDeptIdSet();
if ($erSubtreeSet === []) {
return ['rows' => []];
}
$descIds = self::getSelfAndDescendantIds($rootDeptId);
$descFlip = [];
foreach ($descIds as $did) {
$did = (int) $did;
if ($did > 0) {
$descFlip[$did] = true;
}
}
$orderRows = self::fetchErCenterRevisitCandidateOrders($orderStartTs, $orderEndTs);
if ($orderRows === []) {
return ['rows' => []];
}
$byPatient = [];
foreach ($orderRows as $r) {
$pid = (int) ($r['diagnosis_id'] ?? 0);
if ($pid <= 0) {
continue;
}
$byPatient[$pid][] = $r;
}
$creatorNeed = [];
foreach ($byPatient as $orders) {
$n = \count($orders);
for ($i = 1; $i < $n; $i++) {
$cid = (int) ($orders[$i]['creator_id'] ?? 0);
if ($cid > 0) {
$creatorNeed[$cid] = true;
}
}
}
$canonicalDept = self::buildAssistantCanonicalDeptInSubtree(array_keys($creatorNeed), $erSubtreeSet);
$creatorFlip = null;
if ($visibleCreatorIds !== null && $visibleCreatorIds !== []) {
$creatorFlip = array_flip(array_values(array_unique(array_filter(
array_map('intval', $visibleCreatorIds),
static fn (int $id): bool => $id > 0
))));
}
/** @var array<int, int> $counts */
$counts = [];
foreach ($byPatient as $orders) {
$n = \count($orders);
for ($i = 1; $i < $n; $i++) {
$slot = $i + 1;
if ($revisitSlot > 0 && $slot !== $revisitSlot) {
continue;
}
$cid = (int) ($orders[$i]['creator_id'] ?? 0);
if ($cid <= 0) {
continue;
}
if ($creatorFlip !== null && !isset($creatorFlip[$cid])) {
continue;
}
$canon = $canonicalDept[$cid] ?? null;
if ($canon === null || !isset($erSubtreeSet[$canon]) || !isset($descFlip[$canon])) {
continue;
}
$counts[$cid] = ($counts[$cid] ?? 0) + 1;
}
}
if ($counts === []) {
return ['rows' => []];
}
$ids = array_keys($counts);
$nameRows = [];
if ($ids !== []) {
$nameRows = Db::name('admin')
->whereIn('id', $ids)
->whereNull('delete_time')
->column('name', 'id');
}
$rows = [];
foreach ($counts as $cid => $cnt) {
$rows[] = [
'admin_id' => (int) $cid,
'name' => (string) ($nameRows[$cid] ?? ('#' . $cid)),
'order_count' => (int) $cnt,
];
}
usort($rows, static function (array $a, array $b): int {
if ($a['order_count'] !== $b['order_count']) {
return $b['order_count'] <=> $a['order_count'];
}
return strcmp((string) $a['name'], (string) $b['name']);
});
return ['rows' => $rows];
}
/**
* @return array<int, list<int>>
*/
private static function buildAllDeptChildrenByPid(): array
{
$rows = Dept::field(['id', 'pid'])->select()->toArray();
$out = [];
foreach ($rows as $r) {
$pid = (int) ($r['pid'] ?? 0);
$id = (int) ($r['id'] ?? 0);
if ($id <= 0) {
continue;
}
if (!isset($out[$pid])) {
$out[$pid] = [];
}
$out[$pid][] = $id;
}
return $out;
}
/**
* @param array<int, array<int, int>> $leafByDeptSlot
*
* @return array<int, array<int, int>> dept_id => [ slot => count ],已含下级汇总
*/
private static function rollupRevisitSlotsBySubtree(
array $subtreeDeptIds,
array $subtreeSet,
array $childrenByPid,
array $leafByDeptSlot
): array {
$memo = [];
$dfs = static function (int $id) use (&$dfs, $childrenByPid, $leafByDeptSlot, $subtreeSet, &$memo): array {
if (isset($memo[$id])) {
return $memo[$id];
}
$acc = [];
if (isset($leafByDeptSlot[$id])) {
foreach ($leafByDeptSlot[$id] as $slot => $cnt) {
$acc[(int) $slot] = (int) $cnt;
}
}
foreach ($childrenByPid[$id] ?? [] as $cid) {
$cid = (int) $cid;
if (!isset($subtreeSet[$cid])) {
continue;
}
$childAcc = $dfs($cid);
foreach ($childAcc as $slot => $cnt) {
$acc[$slot] = ($acc[$slot] ?? 0) + $cnt;
}
}
if ($acc !== []) {
ksort($acc, SORT_NUMERIC);
}
$memo[$id] = $acc;
return $acc;
};
$out = [];
foreach ($subtreeDeptIds as $id) {
$id = (int) $id;
$out[$id] = $dfs($id);
}
return $out;
}
/**
* @param list<int> $assistantIds
* @param array<int, true> $subtreeSet
*
* @return array<int, int> admin_id => dept_id
*/
private static function buildAssistantCanonicalDeptInSubtree(array $assistantIds, array $subtreeSet): array
{
$ids = array_values(array_unique(array_filter(array_map('intval', $assistantIds), static function (int $v): bool {
return $v > 0;
})));
if ($ids === []) {
return [];
}
$subtreeIdList = array_keys($subtreeSet);
$rows = AdminDept::whereIn('admin_id', $ids)
->whereIn('dept_id', $subtreeIdList)
->field(['admin_id', 'dept_id'])
->select()
->toArray();
$candidates = [];
foreach ($rows as $r) {
$a = (int) ($r['admin_id'] ?? 0);
$d = (int) ($r['dept_id'] ?? 0);
if ($a > 0 && $d > 0) {
$candidates[$a][] = $d;
}
}
$depthMap = self::buildDeptIdDepthMap();
$out = [];
foreach ($candidates as $aid => $depts) {
$best = null;
$bestDepth = -1;
foreach ($depts as $d) {
$depth = (int) ($depthMap[$d] ?? 0);
if (
$best === null
|| $depth > $bestDepth
|| ($depth === $bestDepth && $d < (int) $best)
) {
$bestDepth = $depth;
$best = $d;
}
}
if ($best !== null) {
$out[$aid] = (int) $best;
}
}
return $out;
}
/**
* @return array<int, int> dept_id => 从根层深度 0 起的层级
*/
private static function buildDeptIdDepthMap(): array
{
$rows = Dept::field(['id', 'pid'])->select()->toArray();
$pidById = [];
foreach ($rows as $r) {
$pidById[(int) $r['id']] = (int) $r['pid'];
}
$depth = [];
$getDepth = static function (int $id) use (&$getDepth, &$depth, $pidById): int {
if (isset($depth[$id])) {
return $depth[$id];
}
$p = (int) ($pidById[$id] ?? 0);
$depth[$id] = $p > 0 ? $getDepth($p) + 1 : 0;
return $depth[$id];
};
foreach (array_keys($pidById) as $id) {
$getDepth((int) $id);
}
return $depth;
}
/**
* @notes 列表树状结构
* @param $array
* @param int $pid
* @param int $level
* @return array
* @author 段誉
* @date 2022/5/30 15:44
*/
public static function getTree($array, $pid = 0, $level = 0)
{
$list = [];
foreach ($array as $key => $item) {
if ($item['pid'] == $pid) {
$item['level'] = $level;
$item['children'] = self::getTree($array, $item['id'], $level + 1);
$list[] = $item;
}
}
return $list;
}
/**
* @notes 上级部门
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author 段誉
* @date 2022/5/26 18:36
*/
public static function leaderDept()
{
$lists = Dept::field(['id', 'name'])->where(['status' => 1])
->order(['sort' => 'desc', 'id' => 'desc'])
->select()
->toArray();
return $lists;
}
/**
* @notes 添加部门
* @param array $params
* @author 段誉
* @date 2022/5/25 18:20
*/
public static function add(array $params)
{
Dept::create([
'pid' => $params['pid'],
'name' => $params['name'],
'leader' => $params['leader'] ?? '',
'mobile' => $params['mobile'] ?? '',
'status' => $params['status'],
'sort' => $params['sort'] ?? 0
]);
}
/**
* @notes 编辑部门
* @param array $params
* @return bool
* @author 段誉
* @date 2022/5/25 18:39
*/
public static function edit(array $params): bool
{
try {
$pid = $params['pid'];
$oldDeptData = Dept::findOrEmpty($params['id']);
if ($oldDeptData['pid'] == 0) {
$pid = 0;
}
Dept::update([
'id' => $params['id'],
'pid' => $pid,
'name' => $params['name'],
'leader' => $params['leader'] ?? '',
'mobile' => $params['mobile'] ?? '',
'status' => $params['status'],
'sort' => $params['sort'] ?? 0
]);
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 删除部门
* @param array $params
* @author 段誉
* @date 2022/5/25 18:40
*/
public static function delete(array $params)
{
Dept::destroy($params['id']);
}
/**
* @notes 获取部门详情
* @param $params
* @return array
* @author 段誉
* @date 2022/5/25 18:40
*/
public static function detail($params): array
{
return Dept::findOrEmpty($params['id'])->toArray();
}
/**
* @notes 部门数据
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author 段誉
* @date 2022/10/13 10:19
*/
public static function getAllData()
{
// 与业绩看板等统计口径一致:含全部未软删部门(不再仅限 status=启用),
// 避免外链 assistant_dept_id 在树下拉中不存在导致 TreeSelect 初始化异常。
$data = Dept::order(['sort' => 'desc', 'id' => 'desc'])
->select()
->toArray();
if ($data === []) {
return [];
}
$pid = min(array_column($data, 'pid'));
return self::getTree($data, $pid);
}
/**
* 部门树(与 getAllData 同结构),按当前管理员角色数据权限收窄可选节点;
* 保留必选祖先节点以便树形展示(与业绩看板 deptOptions 的 allowed 集合一致)。
*
* @param array<string, mixed> $adminInfo BaseAdminController::$adminInfo 形态(须含 root、role_id 等)
*
* @return array<int, array<string, mixed>>
*/
public static function getAllDataScoped(int $adminId, array $adminInfo): array
{
$full = self::getAllData();
if ($full === []) {
return [];
}
if ($adminId <= 0 || !DataScopeService::isEnabled()) {
return $full;
}
$allowed = DataScopeService::getAllowedDeptIdSet($adminId, $adminInfo);
if ($allowed === null) {
return $full;
}
if ($allowed === []) {
return [];
}
return self::filterDeptTreeByAllowedIds($full, $allowed);
}
/**
* @param array<int, array<string, mixed>> $nodes
* @param array<int, true> $allowedIdMap
*
* @return array<int, array<string, mixed>>
*/
private static function filterDeptTreeByAllowedIds(array $nodes, array $allowedIdMap): array
{
$out = [];
foreach ($nodes as $node) {
$id = (int) ($node['id'] ?? 0);
$rawChildren = $node['children'] ?? [];
$children = \is_array($rawChildren) && $rawChildren !== []
? self::filterDeptTreeByAllowedIds($rawChildren, $allowedIdMap)
: [];
$inAllowed = isset($allowedIdMap[$id]);
if ($inAllowed || $children !== []) {
$row = $node;
$row['children'] = $children;
$out[] = $row;
}
}
return $out;
}
/**
* 指定部门及其全部下级部门 id(含自身)。筛选时选父级可匹配子级下成员(admin_dept.dept_id)。
*
* @return array<int>
*/
public static function getSelfAndDescendantIds(int $rootDeptId): array
{
if ($rootDeptId <= 0) {
return [];
}
$rows = Dept::field(['id', 'pid'])->select()->toArray();
if ($rows === []) {
return [$rootDeptId];
}
$validIds = [];
foreach ($rows as $r) {
$id = (int) ($r['id'] ?? 0);
if ($id > 0) {
$validIds[$id] = true;
}
}
if (!isset($validIds[$rootDeptId])) {
return [$rootDeptId];
}
$childrenByPid = [];
foreach ($rows as $r) {
$pid = (int) ($r['pid'] ?? 0);
$id = (int) ($r['id'] ?? 0);
if ($id <= 0) {
continue;
}
if (!isset($childrenByPid[$pid])) {
$childrenByPid[$pid] = [];
}
$childrenByPid[$pid][] = $id;
}
$out = [];
$queue = [$rootDeptId];
$seen = [];
while ($queue !== []) {
$id = array_shift($queue);
if (isset($seen[$id])) {
continue;
}
$seen[$id] = true;
$out[] = $id;
foreach ($childrenByPid[$id] ?? [] as $cid) {
$cid = (int) $cid;
if ($cid > 0 && !isset($seen[$cid])) {
$queue[] = $cid;
}
}
}
return $out;
}
}