info('Starting migration: reports → reports...'); $legacyConn = DB::connection('mysql_second'); $query = $legacyConn->table('reports')->whereNull('deleted_at'); $totalCount = $query->count(); if ($totalCount === 0) { $this->warn('No reports found in legacy database.'); return; } $this->info("Found {$totalCount} legacy report record(s)."); // Pre-cache report IDs that already have proof media (skip re-importing files) $this->migratedIds = DB::table('media') ->where('model_type', Report::class) ->where('collection_name', 'reports') ->pluck('model_id') ->flip() ->all(); // Build a lookup: legacy media_orders.id → new cooperations.id // via: media_orders.name (lowercased) → cooperations.title (lowercased) $legacyOrders = $legacyConn->table('media_orders') ->whereNull('deleted_at') ->select('id', 'name') ->get() ->keyBy('id'); $cooperationMap = DB::table('cooperations') ->select('id', 'title') ->get() ->keyBy(fn ($c) => strtolower(trim($c->title))); foreach ($legacyOrders as $order) { $key = strtolower(trim($order->name)); if (isset($cooperationMap[$key])) { $this->mediaOrderToCooperation[$order->id] = $cooperationMap[$key]->id; } } $query->orderBy('id')->chunk(50, function ($chunk) { foreach ($chunk as $legacy) { $this->processReport($legacy); } }); $this->newLine(); $this->info('reports migration completed successfully!'); } /** * Process a single legacy report record. */ private function processReport(object $legacy): void { $idText = "[#{$legacy->id}]"; $this->output->write("{$idText} Processing: ".Str::limit($legacy->title ?? '', 40).'... '); try { // Resolve media_task_assignment_id // Chain: legacy.media_order → cooperation_id → task_assignment → media_task_assignment (by partner_media_id) $cooperationId = $this->mediaOrderToCooperation[$legacy->media_order] ?? null; if (! $cooperationId) { $this->output->writeln('SKIP (cooperation not found for media_order #'.$legacy->media_order.')'); return; } $taskAssignment = DB::table('task_assignments') ->where('cooperation_id', $cooperationId) ->select('id') ->first(); if (! $taskAssignment) { $this->output->writeln('SKIP (task_assignment not found for cooperation #'.$cooperationId.')'); return; } $mediaTaskAssignment = DB::table('media_task_assignment') ->where('task_assignment_id', $taskAssignment->id) ->where('partner_media_id', $legacy->media) ->select('id') ->first(); if (! $mediaTaskAssignment) { $this->output->writeln('SKIP (media_task_assignment not found for media #'.$legacy->media.')'); return; } [$cleanTitle, $cleanLink] = $this->extractLinkData($legacy); $existing = DB::table('reports') ->where('media_task_assignment_id', $mediaTaskAssignment->id) ->where('publication_date', $legacy->broadcast) ->where('link', $cleanLink) ->first(); if ($existing) { $proofMigrated = $this->migrateProofIfNeeded($existing->id, $legacy); $this->output->writeln($proofMigrated ? 'SKIP + IMG' : 'SKIP'); return; } DB::table('reports')->insert([ 'media_task_assignment_id' => $mediaTaskAssignment->id, 'title' => $cleanTitle, 'publication_date' => $legacy->broadcast, 'link' => $cleanLink, 'description' => '', 'status' => $this->mapApprovalStatus((string) $legacy->status), 'created_at' => $legacy->created_at ?? now(), 'updated_at' => $legacy->updated_at ?? now(), ]); $newReportId = (int) DB::getPdo()->lastInsertId(); $proofMigrated = $this->migrateProofIfNeeded($newReportId, $legacy); $this->output->writeln($proofMigrated ? 'DONE + IMG' : 'DONE'); } catch (\Exception $e) { $this->output->writeln('FAILED: '.$e->getMessage().''); } } /** * Register proof media for a report when not yet migrated. */ private function migrateProofIfNeeded(int $reportId, object $legacy): bool { if (isset($this->migratedIds[$reportId])) { return false; } $report = Report::find($reportId); if (! $report) { return false; } return $this->migrateProof($report, $legacy); } /** * Migrate the proof image metadata (file stays on S3 legacy path). */ private function migrateProof(Report $report, object $legacy): bool { $legacyPath = trim($legacy->proof ?? ''); if (empty($legacyPath) || $legacyPath === '-' || $legacyPath === 'no-image.png') { return false; } $sourcePath = 'old-data/reports/'.$legacyPath; $this->importProofFile($report, $sourcePath, $legacy); $this->migratedIds[$report->id] = true; return true; } /** * Import a single proof image to the Report media library. */ private function importProofFile(Report $report, string $sourcePath, object $legacy): void { $fileName = basename($sourcePath); $legacyDir = dirname($sourcePath); DB::table('media')->insert([ 'model_type' => Report::class, 'model_id' => $report->id, 'uuid' => (string) Str::uuid(), 'collection_name' => 'reports', 'name' => pathinfo($fileName, PATHINFO_FILENAME), 'file_name' => $fileName, 'mime_type' => $this->guessMime($fileName), 'disk' => 's3', 'conversions_disk' => 's3', 'size' => 0, 'manipulations' => '[]', 'custom_properties' => json_encode([ 'feature' => 'reports', 'date' => Carbon::parse($legacy->created_at ?? now())->toDateString(), 'doc_type' => 'proof', 'legacy_dir' => $legacyDir, ]), 'generated_conversions' => '[]', 'responsive_images' => '[]', 'order_column' => 1, 'created_at' => now(), 'updated_at' => now(), ]); } private function guessMime(string $fileName): string { return match (strtolower(pathinfo($fileName, PATHINFO_EXTENSION))) { 'pdf' => 'application/pdf', 'jpg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'doc' => 'application/msword', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', default => 'application/octet-stream', }; } /** * Ekstrak title & link bersih dari legacy report. * * @return array{0: string, 1: string} [title, link] */ private function extractLinkData(object $legacyReport): array { $rawTitle = trim($legacyReport->title ?? ''); $rawLink = trim($legacyReport->link ?? ''); $titleIsUrl = (bool) preg_match('#^https?://#i', $rawTitle); preg_match('#https?://\S+#', $rawLink, $urlMatches); $extractedUrl = rtrim($urlMatches[0] ?? '', '.,;)'); if ($titleIsUrl) { $cleanLink = $rawTitle; $lines = array_values(array_filter( array_map('trim', preg_split('/[\r\n]+/', $rawLink)) )); $candidateTitle = ''; foreach ($lines as $line) { if (! preg_match('#^https?://#i', $line)) { $candidateTitle = $line; break; } } $cleanTitle = $candidateTitle !== '' ? mb_substr($candidateTitle, 0, 255) : mb_substr($rawTitle, 0, 255); } else { $cleanLink = $extractedUrl ?: mb_substr($rawLink, 0, 2048); $cleanTitle = mb_substr($rawTitle, 0, 255); } return [$cleanTitle, $cleanLink]; } /** * Map legacy reports.status integer to ApprovalStatus enum value. * Legacy: 0 = menunggu, 1 = menerima, 2 = menolak */ private function mapApprovalStatus(string $legacyStatus): int { return match ($legacyStatus) { '0' => ApprovalStatus::PENDING->value, '1' => ApprovalStatus::ACCEPTED->value, '2' => ApprovalStatus::REJECTED->value, default => ApprovalStatus::PENDING->value, }; } }