option('chunk')); $onlyNull = (bool) $this->option('only-null'); $dryRun = (bool) $this->option('dry-run'); if ($dryRun) { $this->warn('[DRY RUN] No changes will be written.'); } $query = DB::table('artworks') ->select(['id', 'user_id', 'created_at', 'published_at']) ->whereNotNull('published_at') ->orderBy('id'); if ($onlyNull) { $query->whereNull('created_at'); } $processed = 0; $updated = 0; $unchanged = 0; $affectedUserIds = []; $query->chunkById($chunk, function (Collection $rows) use (&$processed, &$updated, &$unchanged, &$affectedUserIds, $dryRun): void { foreach ($rows as $row) { $processed++; $publishedAt = $this->normalizeTimestamp($row->published_at ?? null); $createdAt = $this->normalizeTimestamp($row->created_at ?? null); if ($publishedAt === null) { $unchanged++; continue; } if ($createdAt === $publishedAt) { $unchanged++; continue; } if ($dryRun) { $this->line(sprintf( '[dry] Would update artwork id=%d created_at %s => %s', (int) $row->id, $createdAt ?? '', $publishedAt )); $updated++; continue; } DB::table('artworks') ->where('id', (int) $row->id) ->update([ 'created_at' => $publishedAt, 'updated_at' => now()->toDateTimeString(), ]); $affectedUserIds[(int) $row->user_id] = true; $updated++; $this->line(sprintf('[update] artwork id=%d created_at => %s', (int) $row->id, $publishedAt)); } }, 'id'); if (! $dryRun) { foreach (array_keys($affectedUserIds) as $userId) { $this->userStats->recomputeUser((int) $userId); } } $this->info(sprintf('Finished. processed=%d updated=%d unchanged=%d', $processed, $updated, $unchanged)); return self::SUCCESS; } private function normalizeTimestamp(mixed $value): ?string { if ($value === null || $value === '') { return null; } try { return Carbon::parse((string) $value)->toDateTimeString(); } catch (\Throwable) { return null; } } }