isDueAt($now, $timeUtc)) { return self::SUCCESS; } if (! class_exists(\App\Jobs\EntraGroupSyncJob::class)) { $this->warn('EntraGroupSyncJob is not available; skipping scheduled directory group sync dispatch.'); return self::SUCCESS; } $tenantIdentifiers = array_values(array_filter(array_map('strval', array_merge( (array) $this->option('tenant'), (array) config('directory_groups.schedule.tenants', []), )))); $tenants = $this->resolveTenants($tenantIdentifiers); $selectionKey = 'groups-v1:all'; $slotKey = $now->format('YmdHi').'Z'; $created = 0; $skipped = 0; foreach ($tenants as $tenant) { $inserted = DB::table('entra_group_sync_runs')->insertOrIgnore([ 'tenant_id' => $tenant->getKey(), 'selection_key' => $selectionKey, 'slot_key' => $slotKey, 'status' => 'pending', 'initiator_user_id' => null, 'created_at' => $now, 'updated_at' => $now, ]); if ($inserted === 1) { $created++; dispatch(new \App\Jobs\EntraGroupSyncJob( tenantId: $tenant->getKey(), selectionKey: $selectionKey, slotKey: $slotKey, )); } else { $skipped++; } } $this->info(sprintf( 'Scanned %d tenant(s), created %d run(s), skipped %d duplicate run(s).', $tenants->count(), $created, $skipped, )); return self::SUCCESS; } /** * @param array $tenantIdentifiers */ private function resolveTenants(array $tenantIdentifiers): \Illuminate\Support\Collection { $query = Tenant::activeQuery(); if ($tenantIdentifiers !== []) { $query->where(function ($subQuery) use ($tenantIdentifiers) { foreach ($tenantIdentifiers as $identifier) { if (ctype_digit($identifier)) { $subQuery->orWhereKey((int) $identifier); continue; } $subQuery->orWhere('tenant_id', $identifier) ->orWhere('external_id', $identifier); } }); } return $query->get(); } private function isDueAt(CarbonImmutable $now, string $timeUtc): bool { if (! preg_match('/^(?[01]\\d|2[0-3]):(?[0-5]\\d)$/', $timeUtc, $matches)) { return false; } return (int) $matches['hour'] === (int) $now->format('H') && (int) $matches['minute'] === (int) $now->format('i'); } }