|null whmcs client id => local customer id */ private static ?array $map = null; /** @var array|null whmcs client ids that exist in tblclients */ private static ?array $known = null; public function __construct(private SyncReport $report) { } public function resolve(int $whmcsClientId): ?int { self::$map ??= $this->build(); return self::$map[$whmcsClientId] ?? null; } public function resolveFor(string $entity, string|int $ref, int $whmcsClientId): ?int { $customerId = $this->resolve($whmcsClientId); if ($customerId === null) { $exists = isset(self::$known[$whmcsClientId]); $this->report->note( $entity, (string) $ref, $whmcsClientId, $exists ? 'khach_chua_co_tren_portal' : 'khach_khong_ton_tai_trong_whmcs', $exists ? "Khách WHMCS #{$whmcsClientId} chưa có tài khoản portal khớp uuid/email — bỏ qua {$entity}." : "Khách WHMCS #{$whmcsClientId} không còn trong tblclients (đã xoá) — bỏ qua {$entity}.", ); } return $customerId; } public function resolveDomainOwner(string $domain, int $whmcsClientId): ?int { $customerId = $this->resolve($whmcsClientId); if ($customerId !== null) { return $customerId; } $name = mb_strtolower(trim($domain)); $whmcs = DB::connection('whmcs'); $holderClientId = $whmcs->table('tbldomains as d') ->join('tblclients as c', 'c.id', '=', 'd.userid') ->whereRaw('LOWER(TRIM(d.domain)) = ?', [$name]) ->orderByRaw("d.status = 'Active' DESC") ->orderByDesc('d.id') ->value('d.userid'); if ($holderClientId && ($customerId = $this->resolve((int) $holderClientId)) !== null) { return $this->traced($name, $whmcsClientId, $customerId, 'domain_cung_ten_khach_khac', "Tên miền cùng tên đang do khách WHMCS #{$holderClientId} giữ."); } $linkedCustomerId = DB::table('dns_domain_user as l') ->join('dns_manager_domain as d', 'd.id', '=', 'l.domain_id') ->whereRaw('LOWER(d.domain_name) = ?', [$name]) ->orderBy('l.id') ->value('l.user_id'); if ($linkedCustomerId) { return $this->traced($name, $whmcsClientId, (int) $linkedCustomerId, 'domain_da_gan_khach_trong_dns', 'Tên miền đã gắn khách trong DNS Manager của portal.'); } $contactEmails = $whmcs->table('tblcontacts')->where('userid', $whmcsClientId)->pluck('email')->map(fn ($email) => strtolower((string) $email))->filter()->all(); if ($contactEmails !== [] && ($customerId = DB::table('customers')->whereIn(DB::raw('LOWER(email)'), $contactEmails)->value('id'))) { return $this->traced($name, $whmcsClientId, (int) $customerId, 'domain_khop_email_lien_he', 'Khớp theo email liên hệ của khách đã xoá.'); } $this->report->note( 'domain', $name, $whmcsClientId, 'khach_khong_ton_tai_trong_whmcs', "Khách WHMCS #{$whmcsClientId} đã xoá, không suy ra được chủ. Email liên hệ còn lại: " . ($contactEmails ? implode(', ', $contactEmails) : 'không có'), ); return null; } private function traced(string $domain, int $whmcsClientId, int $customerId, string $issue, string $detail): int { $this->report->note('domain', $domain, $whmcsClientId, $issue, "Khách WHMCS #{$whmcsClientId} đã xoá; chủ suy ra → khách portal #{$customerId}. {$detail}"); return $customerId; } public static function flush(): void { self::$map = null; self::$known = null; } private function build(): array { $clients = DB::connection('whmcs')->table('tblclients')->get(['id', 'uuid', 'email']); $byUuid = DB::table('customers')->pluck('id', 'uuid'); $byEmail = DB::table('customers')->whereNotNull('email')->pluck('id', DB::raw('LOWER(email)')); $map = []; self::$known = []; foreach ($clients as $client) { self::$known[$client->id] = true; $customerId = $byUuid[$client->uuid] ?? $byEmail[strtolower((string) $client->email)] ?? null; if ($customerId !== null) { $map[$client->id] = $customerId; } } return $map; } }