CaiWuXiTong/app/Utilities/SidebarPermissions.php
renshen 87eaf48c2c 供应商对账:应付款项下新增对账页面(财务端数据 / 厂家端数据) (#364)
新增供应商对账目录:财务端实时上传情况与行级差异(条码+单价匹配,四类口径),厂家端严格按模板上传(表头不一致/必填缺失/非数字/串厂一律失败);复用终版存储与流式读取。
2026-10-17 08:49:17 +08:00

265 lines
10 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\Utilities;
use App\Models\Auth\User;
use App\Models\Auth\UserAccessGroup;
use App\Services\Auth\FinanceRoster;
class SidebarPermissions
{
private const LEGACY_PAGE_KEYS = [
'refunds/small-payment-work-orders' => 'financial-operations/small-payment-work-orders',
];
private const FINAL_SECTION_KEYS = [
'supplier-payables' => 'supplier-payables-final',
'courier-payables' => 'courier-payables-final',
];
public static function settings(): array
{
$value = setting('system.sidebar_permissions', []);
if (is_string($value)) {
$value = json_decode($value, true) ?: [];
}
return is_array($value) ? $value : [];
}
public static function subjectKey(User $user): string
{
$userKey = 'user_' . $user->id;
$settings = self::settings();
if (in_array($userKey, $settings['configured'] ?? [], true)) {
return $userKey;
}
return 'role_' . SystemAccess::roleName($user);
}
public static function allowedKeys(User $user): ?array
{
// Limited identities cannot inherit full navigation from roles, groups or user overrides.
if (self::hasLimitedDingTalkAccess($user)) {
return config('dingtalk.limited_login_page_keys', []);
}
if (SystemAccess::isSuperAdmin($user)) {
return null;
}
$settings = self::settings();
$userSubject = 'user_' . $user->id;
if (in_array($userSubject, $settings['configured'] ?? [], true)) {
return self::normalizeAllowedKeys((array) ($settings['subjects'][$userSubject] ?? []));
}
$groups = UserAccessGroup::forUser($user);
if ($groups->isNotEmpty()) {
return self::normalizeAllowedKeys(
$groups->flatMap(fn ($group) => $group->page_permissions ?? [])->all()
);
}
$subject = 'role_' . SystemAccess::roleName($user);
if (! in_array($subject, $settings['configured'] ?? [], true)) {
return null;
}
return self::normalizeAllowedKeys((array) ($settings['subjects'][$subject] ?? []));
}
public static function hasLimitedDingTalkAccess(User $user): bool
{
return $user->created_from === 'dingtalk_roster_limited'
|| (config('dingtalk.limited_login_user_ids', []) !== []
&& FinanceRoster::isLimitedLoginUser((string) $user->dingtalkIdentity?->dingtalk_user_id));
}
public static function navigationFor(User $user): array
{
$navigation = BusinessNavigation::all();
$allowed = self::allowedKeys($user);
if ($allowed === null) {
return $navigation;
}
$navigation = array_values(array_filter(array_map(function (array $section) use ($allowed) {
$sectionKey = $section['slug'];
if (in_array($sectionKey, $allowed, true)) {
return $section;
}
$pages = array_values(array_filter(array_map(function (array $page) use ($allowed, $sectionKey) {
$pageKey = $sectionKey . '/' . $page['slug'];
if (in_array($pageKey, $allowed, true)) {
// 若有 children,保留 children 的精确权限过滤
if (! empty($page['children'])) {
$page['children'] = array_values(array_filter($page['children'], function (array $child) use ($allowed, $sectionKey) {
return in_array($sectionKey . '/' . $child['slug'], $allowed, true);
}));
}
return $page;
}
// 子页面可能在 allowed 列表中(独立 URL),父级分组也允许展开
if (! empty($page['children'])) {
$visibleChildren = array_values(array_filter($page['children'], function (array $child) use ($allowed, $sectionKey) {
return in_array($sectionKey . '/' . $child['slug'], $allowed, true);
}));
if (! empty($visibleChildren)) {
$page['children'] = $visibleChildren;
return $page;
}
}
return null;
}, $section['pages'] ?? [])));
if (! empty($pages)) {
$section['pages'] = $pages;
return $section;
}
return null;
}, $navigation)));
// 新增的公司成本安排、税金预测表与主体经营报表共用公司经营报表权限。
// 对已经保存过精确权限的账号兼容旧配置,避免新增页面被旧权限集合隐藏。
foreach ($navigation as &$section) {
if (($section['slug'] ?? null) !== 'company-management') {
continue;
}
$hasSubjectReport = collect($section['pages'] ?? [])->contains('slug', 'subject-operating-report');
if ($hasSubjectReport) {
$configuredPages = collect(BusinessNavigation::all())
->firstWhere('slug', 'company-management')['pages'] ?? [];
foreach (['company-cost-arrangement', 'tax-forecast'] as $sharedReportSlug) {
$alreadyVisible = collect($section['pages'] ?? [])->contains('slug', $sharedReportSlug);
if ($alreadyVisible) {
continue;
}
$sharedReportPage = collect($configuredPages)->firstWhere('slug', $sharedReportSlug);
if ($sharedReportPage) {
$section['pages'][] = $sharedReportPage;
}
}
}
}
unset($section);
// 「财务经营风险管控」新增「预览统计 / 流程统计」子页面:按页面键保存过精确权限的
// 账号只会有一条或两条,会看不到新页面;这里按「能看店铺违规预警 就能看新增页」
// 兼容旧配置(同属财务经营风险管控权限)。
foreach ($navigation as &$riskSection) {
if (($riskSection['slug'] ?? null) !== 'financial-risk-control') {
continue;
}
$pageSlugs = array_column($riskSection['pages'] ?? [], 'slug');
if (! in_array('store-violation-alert', $pageSlugs, true)) {
continue;
}
$configuredPages = collect(BusinessNavigation::all())
->firstWhere('slug', 'financial-risk-control')['pages'] ?? [];
foreach ($configuredPages as $configuredPage) {
$configuredSlug = (string) ($configuredPage['slug'] ?? '');
if (! in_array($configuredSlug, ['preview-statistics', 'process-statistics'], true)) {
continue;
}
if (! in_array($configuredSlug, $pageSlugs, true)) {
$riskSection['pages'][] = $configuredPage;
}
}
}
unset($riskSection);
// 「种菜明细表 / 聚水潭订单标签明细」原先挂在「账单流水数据」下,后来移入新的一级菜单
// 「种菜数据汇总」。按页面键保存过精确权限的账号(访问组/角色)里不会有新的 planting-data,
// 会因此看不到这两个被移动的页面;这里按「能看账单流水数据 就能看这两个页面」兼容旧配置。
// 「供应商对账」与「供应商应付(终版)」同属应付款项,先按同样的权限跟随授权
// (厂家账号后续按单页面单独配置)。
$visibleSlugs = array_column($navigation, 'slug');
if (! in_array('supplier-reconciliation', $visibleSlugs, true) && in_array('supplier-payables-final', $visibleSlugs, true)) {
$reconciliationSection = collect(BusinessNavigation::all())->firstWhere('slug', 'supplier-reconciliation');
if (! empty($reconciliationSection)) {
$index = array_search('supplier-payables-final', $visibleSlugs, true);
array_splice($navigation, $index + 1, 0, [$reconciliationSection]);
}
}
$slugs = array_column($navigation, 'slug');
if (! in_array('planting-data', $slugs, true) && in_array('billing-transactions', $slugs, true)) {
$plantingSection = collect(BusinessNavigation::all())->firstWhere('slug', 'planting-data');
if (! empty($plantingSection)) {
$billingIndex = array_search('billing-transactions', $slugs, true);
array_splice($navigation, $billingIndex + 1, 0, [$plantingSection]);
}
}
return $navigation;
}
public static function allKeys(): array
{
$keys = [];
foreach (BusinessNavigation::all() as $section) {
$keys[] = $section['slug'];
foreach ($section['pages'] ?? [] as $page) {
$keys[] = $section['slug'] . '/' . $page['slug'];
foreach ($page['children'] ?? [] as $child) {
$keys[] = $section['slug'] . '/' . $child['slug'];
}
}
}
return $keys;
}
public static function normalizeAllowedKeys(array $keys): array
{
$normalized = [];
foreach ($keys as $key) {
$key = (string) $key;
$normalizedKey = self::LEGACY_PAGE_KEYS[$key] ?? $key;
$normalized[] = $normalizedKey;
foreach (self::FINAL_SECTION_KEYS as $sectionKey => $finalSectionKey) {
if ($normalizedKey === $sectionKey) {
$normalized[] = $finalSectionKey;
} elseif (str_starts_with($normalizedKey, $sectionKey . '/')) {
// 「终版」目录已是独立目录,按同样的子页面粒度跟着授权。
$normalized[] = $finalSectionKey . substr($normalizedKey, strlen($sectionKey));
}
}
if ($key === 'refunds') {
$normalized[] = self::LEGACY_PAGE_KEYS['refunds/small-payment-work-orders'];
}
}
return array_values(array_unique($normalized));
}
}