logFile = storage_path('logs/mark-incomplete-profiles.log'); } public function handle(): int { $dryRun = (bool) $this->option('dry-run'); $config = timebank_config('profile_incomplete'); if (empty($config)) { $this->warn('No profile_incomplete configuration; nothing to do.'); return self::SUCCESS; } $relations = array_values(array_filter( $config['check_relations'] ?? [], fn($relation) => is_string($relation) && $relation !== '' )); $marked = 0; $cleared = 0; foreach (self::TYPES as $type) { if (!method_exists($type, 'hasIncompleteProfile')) { continue; } // Only relations the type actually defines — a white-label config // may name one that does not apply to every profile type. $usable = array_values(array_filter( $relations, fn($relation) => method_exists($type, $relation) )); $type::query() ->notRemoved() ->with($usable) ->chunkById(500, function ($profiles) use (&$marked, &$cleared, $dryRun) { foreach ($profiles as $profile) { $isIncomplete = $profile->hasIncompleteProfile($profile); $wasIncomplete = !empty($profile->incomplete_at); if ($isIncomplete === $wasIncomplete) { continue; } if ($isIncomplete) { $marked++; } else { $cleared++; } if (!$dryRun) { // Keep the original timestamp if it is already set: // this records when the profile became incomplete, // not when the command last looked at it. $profile->incomplete_at = $isIncomplete ? now() : null; $profile->saveQuietly(); } } }); } $summary = $dryRun ? "Dry run: {$marked} would be marked incomplete, {$cleared} would be cleared" : "Marked {$marked} profiles incomplete, cleared {$cleared}"; $this->info($summary); $this->logMessage($summary); return self::SUCCESS; } private function logMessage(string $message): void { try { file_put_contents( $this->logFile, '[' . now()->toDateTimeString() . '] ' . $message . PHP_EOL, FILE_APPEND ); } catch (\Throwable $e) { Log::warning('MarkIncompleteProfiles: could not write log file', ['error' => $e->getMessage()]); } } }