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)); } }