CaiWuXiTong/app/Services/Auth/FinanceRoster.php

136 lines
5.7 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\Services\Auth;
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQueryFactory;
/** Single-person, current HR authorization lookup; no HR rows or stale grants are cached. */
class FinanceRoster
{
public static function isDesignatedSuperAdmin(string $userId): bool
{
return $userId !== '' && in_array($userId, config('dingtalk.super_admin_user_ids', []), true);
}
public static function isLimitedLoginUser(string $userId): bool
{
return $userId !== '' && in_array($userId, config('dingtalk.limited_login_user_ids', []), true);
}
public function __construct(private readonly UpstreamReadOnlyQueryFactory $queries)
{
}
public function findEligible(string $userId): ?array
{
if (trim($userId) === '') {
return null;
}
$query = $this->queries->forSource('operations_replica');
$flowers = $query->select(
'SELECT TOP (2) Id, name, flowerName, deptId, status, empStatus, leaveDate
FROM rpt.hr_flowerName_book WHERE flowerNameId = ? AND leaveDate IS NULL
ORDER BY Id DESC', [$userId]
);
if (count($flowers) !== 1) {
return null;
}
$flower = $flowers[0];
if ($this->isResigned($flower['status'] ?? null) || $this->isResigned($flower['empStatus'] ?? null)) {
return null;
}
if ($query->first('SELECT TOP (1) userid FROM rpt.hr_employee_dimission WHERE userid = ?', [$userId])) {
return null;
}
$employees = $query->select('SELECT TOP (2) active, dept_id_list FROM rpt.hr_employee WHERE userid = ?', [$userId]);
if (count($employees) > 1 || (isset($employees[0]['active']) && ! (bool) $employees[0]['active'])) {
return null;
}
$registrations = $query->select(
'SELECT TOP (20) [sys01-employeeStatus] AS employment_status, [sys00-deptIds] AS department_ids,
[sys00-mainDeptId] AS main_department_id FROM rpt.hr_employee_register WHERE userid = ?', [$userId]
);
if (count($registrations) === 20) {
return null;
}
$departmentIds = $this->departmentIds($flower['deptId'] ?? null);
foreach ($employees as $employee) {
$departmentIds = array_merge($departmentIds, $this->departmentIds($employee['dept_id_list'] ?? null));
}
foreach ($registrations as $registration) {
if ($this->isResigned($registration['employment_status'] ?? null)) {
return null;
}
$departmentIds = array_merge($departmentIds,
$this->departmentIds($registration['department_ids'] ?? null),
$this->departmentIds($registration['main_department_id'] ?? null));
}
$departmentIds = array_values(array_unique($departmentIds));
if ($departmentIds === [] || count($departmentIds) > 64) {
return null;
}
$placeholders = implode(',', array_fill(0, count($departmentIds), '?'));
$ancestors = $query->select(
'WITH lineage AS (
SELECT Id, parent_id, 0 AS depth FROM rpt.hr_groups WHERE Id IN ('.$placeholders.')
UNION ALL
SELECT parent.Id, parent.parent_id, child.depth + 1
FROM rpt.hr_groups parent JOIN lineage child ON parent.Id = child.parent_id
WHERE child.depth < 20
) SELECT TOP (2048) Id, parent_id, depth FROM lineage', $departmentIds
);
if (count($ancestors) === 2048 || array_filter($ancestors, fn ($row) => (int) $row['depth'] >= 20)) {
return null;
}
$allowed = array_map('strval', config('dingtalk.finance_department_ids', []));
// 财务中心(或指定超级管理员/受限登录名单)拥有完整权限;
// 其它在职员工返回 restricted,只能访问风险管控下的两个页面。
$inFinance = self::isDesignatedSuperAdmin($userId)
|| self::isLimitedLoginUser($userId)
|| (bool) array_intersect($allowed, array_map('strval', array_column($ancestors, 'Id')));
return [
'roster_id' => (string) $flower['Id'],
'display_name' => trim((string) ($flower['flowerName'] ?: $flower['name'])),
'department_ids' => $departmentIds,
'restricted' => ! $inFinance,
];
}
/** Minimal, bounded directory search for workflow selection; no HR dataset is cached. */
public function search(string $term): array
{
if (trim($term) === '') {
return [];
}
$like = '%' . str_replace(['!', '%', '_', '['], ['!!', '!%', '!_', '!['], trim($term)) . '%';
return $this->queries->forSource('operations_replica')->select(
"SELECT TOP (12) flowerNameId, name, flowerName FROM rpt.hr_flowerName_book
WHERE leaveDate IS NULL AND flowerNameId IS NOT NULL
AND (name LIKE ? ESCAPE '!' OR flowerName LIKE ? ESCAPE '!') ORDER BY Id DESC", [$like, $like]
);
}
private function isResigned(mixed $value): bool
{
$status = trim((string) $value);
return $status === '5' || preg_match('/离职|停用|退休/u', $status) === 1;
}
private function departmentIds(mixed $value): array
{
$value = trim((string) $value);
$parts = json_decode($value, true);
if (! is_array($parts)) {
$parts = preg_split('/[\[\]\/>,,;;||\s]+/u', $value, -1, PREG_SPLIT_NO_EMPTY);
}
return array_values(array_filter(array_map(
fn ($id) => is_scalar($id) ? trim((string) $id) : '', $parts
), fn ($id) => ctype_digit($id) && (int) $id > 0));
}
}