Files
zyt/server/app/adminapi/logic/stats/PersonalStatsScopeTrait.php
T
2026-05-22 16:45:28 +08:00

232 lines
7.6 KiB
PHP

<?php
declare(strict_types=1);
namespace app\adminapi\logic\stats;
use app\adminapi\logic\dept\DeptLogic;
use app\common\model\auth\AdminDept;
use app\common\model\dept\Dept;
use app\common\model\stats\PersonalAccountCost;
use app\common\model\stats\PersonalYeji;
use app\common\service\DataScope\DataScopeService;
use think\facade\Config;
trait PersonalStatsScopeTrait
{
protected static function resolvePrimaryDeptId(int $adminId): int
{
if ($adminId <= 0) {
return 0;
}
$deptId = AdminDept::where('admin_id', $adminId)->value('dept_id');
return (int) ($deptId ?: 0);
}
/**
* @param array<int, int> $creatorIds
* @return array{0: array<int, int>, 1: array<int, string>, 2: array<int, string>}
* [adminId => deptId, deptId => name, deptId => "祖/父/当前"]
*/
protected static function loadAdminDeptMap(array $creatorIds): array
{
$creatorIds = array_values(array_unique(array_filter(array_map('intval', $creatorIds))));
if ($creatorIds === []) {
return [[], [], []];
}
$rows = AdminDept::whereIn('admin_id', $creatorIds)
->field('admin_id, dept_id')
->select()
->toArray();
$adminToDeptId = [];
foreach ($rows as $row) {
$adminId = (int) ($row['admin_id'] ?? 0);
$deptId = (int) ($row['dept_id'] ?? 0);
if ($adminId <= 0 || $deptId <= 0) {
continue;
}
if (!isset($adminToDeptId[$adminId])) {
$adminToDeptId[$adminId] = $deptId;
}
}
if ($adminToDeptId === []) {
return [[], [], []];
}
$allDeptRows = Dept::field('id, pid, name')->select()->toArray();
$deptIndex = [];
foreach ($allDeptRows as $row) {
$id = (int) ($row['id'] ?? 0);
if ($id <= 0) {
continue;
}
$deptIndex[$id] = [
'pid' => (int) ($row['pid'] ?? 0),
'name' => (string) ($row['name'] ?? ''),
];
}
$deptNameMap = [];
$deptPathMap = [];
foreach (array_unique(array_values($adminToDeptId)) as $deptId) {
$deptId = (int) $deptId;
if ($deptId <= 0 || !isset($deptIndex[$deptId])) {
continue;
}
$deptNameMap[$deptId] = $deptIndex[$deptId]['name'];
$deptPathMap[$deptId] = self::resolveDeptPath($deptId, $deptIndex);
}
return [$adminToDeptId, $deptNameMap, $deptPathMap];
}
/**
* 从根节点到当前部门的完整链路(用 / 分隔)。
*
* @param array<int, array{pid: int, name: string}> $deptIndex
*/
private static function resolveDeptPath(int $deptId, array $deptIndex): string
{
$names = [];
$guard = 0;
$cursor = $deptId;
while ($cursor > 0 && isset($deptIndex[$cursor]) && $guard++ < 32) {
$node = $deptIndex[$cursor];
$name = trim($node['name']);
if ($name !== '') {
array_unshift($names, $name);
}
$cursor = $node['pid'];
}
return implode(' / ', $names);
}
/**
* @param array<int, array<string, mixed>> $rows
* @return array<int, array<string, mixed>>
*/
protected static function attachDeptInfoToRows(array $rows): array
{
if ($rows === []) {
return $rows;
}
$creatorIds = array_map(static fn (array $row): int => (int) ($row['creator_id'] ?? 0), $rows);
[$adminToDeptId, $deptNameMap, $deptPathMap] = self::loadAdminDeptMap($creatorIds);
foreach ($rows as &$row) {
$creatorId = (int) ($row['creator_id'] ?? 0);
$deptId = $adminToDeptId[$creatorId] ?? 0;
$row['dept_id'] = $deptId;
$row['dept_name'] = $deptId > 0 ? (string) ($deptNameMap[$deptId] ?? '') : '';
$row['dept_path'] = $deptId > 0 ? (string) ($deptPathMap[$deptId] ?? '') : '';
}
unset($row);
return $rows;
}
/**
* 根据 dept_id(包含其子部门)收窄可见 admin id 集合。
* 与 visibleAdminIds 取交集。
*
* @param array<int>|null $visibleAdminIds null 表示不限
* @return array<int>|null 返回 null 表示外部条件无需附加;返回 [] 表示无可见 admin
*/
protected static function intersectVisibleByDept(?array $visibleAdminIds, int $deptId): ?array
{
if ($deptId <= 0) {
return $visibleAdminIds;
}
$deptIds = DeptLogic::getSelfAndDescendantIds($deptId);
if ($deptIds === []) {
$deptIds = [$deptId];
}
$adminIds = AdminDept::whereIn('dept_id', $deptIds)->column('admin_id');
$adminIds = array_values(array_unique(array_filter(array_map('intval', $adminIds), static fn (int $v): bool => $v > 0)));
if ($visibleAdminIds === null) {
return $adminIds;
}
return array_values(array_intersect($visibleAdminIds, $adminIds));
}
protected static function normalizeMediaSource(string $mediaSource): string
{
return trim($mediaSource);
}
/**
* 与 project.self_input_stats_view_all_roles 一致:超管或白名单角色可见全部录入人数据。
*/
protected static function canViewAllSelfInputStats(array $adminInfo): bool
{
if ((int) ($adminInfo['root'] ?? 0) === 1) {
return true;
}
$allow = Config::get('project.self_input_stats_view_all_roles', []);
$allow = array_map('intval', is_array($allow) ? $allow : []);
if ($allow === []) {
return false;
}
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
return count(array_intersect($myRoles, $allow)) > 0;
}
/**
* @return array<int>|null null=全部录入人;[]=无可见;int[]=可见 creator_id 集合
*/
protected static function getVisibleCreatorIds(int $adminId, array $adminInfo): ?array
{
if (self::canViewAllSelfInputStats($adminInfo)) {
return null;
}
return DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
}
protected static function assertRecordVisible(int $adminId, array $adminInfo, int $creatorId): bool
{
$visibleIds = self::getVisibleCreatorIds($adminId, $adminInfo);
if ($visibleIds === null) {
return true;
}
return in_array($creatorId, $visibleIds, true);
}
/**
* 同一录入人 + 同一天 + 同一渠道唯一(不同录入人可同日同渠道各录一条)。
*/
protected static function isYejiDuplicate(int $creatorId, string $yejiDate, string $mediaSource, int $excludeId = 0): bool
{
$query = PersonalYeji::where('creator_id', $creatorId)
->where('yeji_date', $yejiDate)
->where('media_source', $mediaSource);
if ($excludeId > 0) {
$query->where('id', '<>', $excludeId);
}
return $query->count() > 0;
}
/**
* 同一录入人 + 同一天 + 同一渠道唯一(不同录入人可同日同渠道各录一条)。
*/
protected static function isCostDuplicate(int $creatorId, string $costDate, string $mediaSource, int $excludeId = 0): bool
{
$query = PersonalAccountCost::where('creator_id', $creatorId)
->where('cost_date', $costDate)
->where('media_source', $mediaSource);
if ($excludeId > 0) {
$query->where('id', '<>', $excludeId);
}
return $query->count() > 0;
}
}