created_from === self::CREATED_FROM; } /** @return array{supplier_name:string,supplier_code:string,dingtalk_group_id:int,dingtalk_group_name:string}|null */ public function resolve(string $userId, string $unionId = ''): ?array { $userId = trim($userId); $unionId = trim($unionId); if ($userId === '') { return null; } $query = $this->queries->forSource('operations_replica'); $employees = $query->select( 'SELECT TOP (2) userid, unionid, name, active, dept_id_list FROM rpt.hr_employee WHERE userid = ?', [$userId], ); if (count($employees) !== 1 || ! (bool) ($employees[0]['active'] ?? false)) { return null; } $employee = $employees[0]; $employeeUnionId = trim((string) ($employee['unionid'] ?? '')); if ($employeeUnionId === '' || ($unionId !== '' && ! hash_equals($employeeUnionId, $unionId))) { return null; } $departmentIds = $this->departmentIds($employee['dept_id_list'] ?? null); if ($departmentIds === [] || count($departmentIds) > 64) { return null; } $placeholders = implode(',', array_fill(0, count($departmentIds), '?')); $ancestors = $query->select( 'WITH lineage AS ( SELECT Id, parent_id, name, 0 AS depth FROM rpt.hr_groups WHERE Id IN ('.$placeholders.') UNION ALL SELECT parent.Id, parent.parent_id, parent.name, child.depth + 1 FROM rpt.hr_groups parent JOIN lineage child ON parent.Id = child.parent_id WHERE child.depth < 20 ) SELECT TOP (256) Id, parent_id, name, depth FROM lineage', $departmentIds, ); if ($ancestors === [] || count($ancestors) >= 256 || array_filter($ancestors, static fn (array $row): bool => (int) ($row['depth'] ?? 0) >= 20)) { return null; } $groupsByName = []; foreach ($ancestors as $ancestor) { $name = trim((string) ($ancestor['name'] ?? '')); if ($name !== '') { $groupsByName[$name][] = $ancestor; } } if ($groupsByName === []) { return null; } $supplierRows = $this->suppliers->suppliersByNames(array_keys($groupsByName)); $matchedSupplierNames = []; foreach ($supplierRows as $supplier) { $name = trim((string) ($supplier->scmname ?? '')); if ($name !== '' && isset($groupsByName[$name])) { $matchedSupplierNames[$name] = true; } } if (count($matchedSupplierNames) !== 1) { return null; } $supplierName = (string) array_key_first($matchedSupplierNames); $matchingRows = array_values(array_filter( $supplierRows, static fn (object $supplier): bool => trim((string) ($supplier->scmname ?? '')) === $supplierName, )); if (count($matchingRows) !== 1) { return null; } usort($groupsByName[$supplierName], static fn (array $left, array $right): int => (int) ($right['depth'] ?? 0) <=> (int) ($left['depth'] ?? 0)); $group = $groupsByName[$supplierName][0]; return [ 'supplier_name' => $supplierName, 'supplier_code' => trim((string) ($matchingRows[0]->scmcode ?? '')), 'dingtalk_group_id' => (int) ($group['Id'] ?? 0), 'dingtalk_group_name' => $supplierName, ]; } public function bind(User $user, int $companyId, array $resolved): SupplierPortalIdentity { return SupplierPortalIdentity::query()->updateOrCreate( ['user_id' => $user->id], [ 'company_id' => $companyId, 'supplier_code' => $resolved['supplier_code'] ?? null, 'supplier_name' => $resolved['supplier_name'], 'dingtalk_group_id' => (int) $resolved['dingtalk_group_id'], 'dingtalk_group_name' => $resolved['dingtalk_group_name'], 'last_verified_at' => now(), ], ); } public function mappingFor(User $user): ?SupplierPortalIdentity { if (! self::isSupplierUser($user)) { return null; } return SupplierPortalIdentity::query() ->where('user_id', $user->id) ->where('company_id', company_id()) ->first(); } public function supplierFor(User $user): ?string { $mapping = $this->mappingFor($user); $name = trim((string) ($mapping?->supplier_name ?? '')); return $name !== '' ? $name : null; } public function revalidate(User $user): ?SupplierPortalIdentity { if (! self::isSupplierUser($user)) { return null; } $dingTalk = DingTalkIdentity::query()->where('user_id', $user->id)->first(); $company = $user->withoutEvents(fn () => $user->companies()->enabled()->first()); if (! $dingTalk || ! $company) { return null; } $resolved = $this->resolve( (string) $dingTalk->dingtalk_user_id, (string) $dingTalk->union_id, ); return $resolved ? $this->bind($user, (int) $company->id, $resolved) : null; } private function departmentIds(mixed $value): array { $value = trim((string) $value); if ($value === '') { return []; } $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( static fn (mixed $id): string => is_scalar($id) ? trim((string) $id) : '', is_array($parts) ? $parts : [], ), static fn (string $id): bool => ctype_digit($id) && (int) $id > 0)); } }