first commit

This commit is contained in:
Your Name
2026-09-08 11:40:15 +08:00
commit a5353f7eb5
9568 changed files with 1646214 additions and 0 deletions
@@ -0,0 +1,997 @@
<?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
{
return self::findCenterRootDeptIdsByNameKeyword('二中心');
}
/**
* 名称含「一中心」的部门 id。
*
* @return list<int>
*/
private static function findYiCenterRootDeptIds(): array
{
return self::findCenterRootDeptIdsByNameKeyword('一中心');
}
/**
* @return list<int>
*/
private static function findCenterRootDeptIdsByNameKeyword(string $keyword): 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, $keyword) !== 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);
}
/**
* 名称含「一中心」的部门及其全部下级 id(map)。
*
* @return array<int, true>
*/
public static function getYiCenterSubtreeDeptIdSet(): array
{
$yiRoots = self::findYiCenterRootDeptIds();
$subtreeIds = self::unionErCenterSubtreeDeptIds($yiRoots);
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;
}
}