initializeErrorLog(); // Path ke file csv $filePath = realpath(__DIR__ . '/csv/inspeksi/mig_inspeksi_2025.csv'); if (!$filePath) { Log::error('File csv tidak ditemukan: ' . __DIR__ . '/csv/inspeksi/mig_inspeksi_2022.csv'); $this->command->error('File csv tidak ditemukan.'); return; } if (($handle = fopen($filePath, 'r')) === false) { Log::error('Gagal membuka file CSV: ' . $filePath); $this->command->error('Gagal membuka file CSV.'); return; } $header = fgetcsv($handle, 0, '~'); $rows = []; $batchSize = 500; $userData = []; $branchCache = []; // <-- Gunakan sebagai cache $totalData = 0; while (($data = fgetcsv($handle, 0, '~')) !== false) { $totalData++; } rewind($handle); fgetcsv($handle, 0, '~'); // Skip header $batchCount = 0; $currentRow = 0; $errorCount = 0; $errorDebitureIds = []; while (($data = fgetcsv($handle, 0, '~')) !== false) { if (count($data) != count($header)) { Log::warning('Baris CSV memiliki jumlah kolom yang tidak sesuai: ' . json_encode($data)); continue; } $rows[] = array_combine($header, $data); $currentRow++; if (count($rows) >= $batchSize) { $batchCount++; $this->command->info("Memproses batch ke-{$batchCount} ({$currentRow}/{$totalData})"); $this->processBatch($rows, $branchCache, $userData, $errorCount, $errorDebitureIds, $totalData, $batchCount, $currentRow); $rows = []; } } if (!empty($rows)) { $batchCount++; $this->command->info("Memproses batch ke-{$batchCount} ({$currentRow}/{$totalData})"); $this->processBatch($rows, $branchCache, $userData, $errorCount, $errorDebitureIds, $totalData, $batchCount, $currentRow); } fclose($handle); $this->command->info("Data debiture berhasil dimigrasikan. Total data: {$totalData}, Total batch: {$batchCount}"); } private function processBatch( array $rows, array &$branchCache, array &$debitureCache, int &$errorCount, array &$errorDebitureIds, int $totalData, int $batchCount, int $currentRow ) { // Kelompokkan baris berdasarkan mig_nomor_jaminan $groupedData = $this->groupRowsByJaminan($rows); foreach ($groupedData as $nomorJaminan => $groupRows) { // try { // Cek apakah sudah ada di inspeksi // Ambil informasi permohonan $nomorRegis = $this->getNomorRegistrasiPermohonan($nomorJaminan, $branchCache); if (!$nomorRegis) { Log::warning("Nomor registrasi tidak ditemukan untuk nomor jaminan: {$nomorJaminan}"); $errorCount++; $errorDebitureIds[] = $nomorJaminan; continue; } $existingRecord = Inspeksi::where('permohonan_id', $nomorRegis['id']) ->where('dokument_id', $nomorRegis['dokument_id']) ->whereNotNull('data_form') ->first(); if ($existingRecord) { $this->command->info("Data untuk nomor jaminan {$nomorJaminan} sudah ada. Lewati..."); continue; } // Ambil created_at / updated_at dari baris pertama $firstRow = reset($groupRows); // Bangun JSON lengkap $dataFormJson = $this->buildFullDataForm($groupRows); // Simpan ke database hanya sekali per grup Inspeksi::create([ 'name' => 'tanah,bangunan,lingkungan,fakta,informasi', 'data_form' => $dataFormJson, 'foto_form' => null, 'data_pembanding' => null, 'permohonan_id' => $nomorRegis['id'] ?? null, 'dokument_id' => $nomorRegis['dokument_id'] ?? null, 'created_at' => $this->parseTimestamp($firstRow['created_at']), 'updated_at' => $this->parseTimestamp($firstRow['updated_at']), 'nomor_lpj' => $nomorJaminan, 'processed_at' => now(), 'is_mig' => 1 ]); $this->command->info("Berhasil simpan data inspeksi untuk nomor jaminan: {$nomorJaminan}"); // } catch (\Exception $e) { // Log::error("Error pada nomor jaminan {$nomorJaminan}: " . $e->getMessage()); // $errorCount++; // $errorDebitureIds[] = $nomorJaminan; // continue; // } } } private function groupRowsByJaminan(array $rows): array { $grouped = []; foreach ($rows as $row) { $nomorJaminan = $row['mig_nomor_jaminan'] ?? null; if (!empty($nomorJaminan)) { $grouped[$nomorJaminan][] = $row; } } return $grouped; } private function getNomorRegistrasiPermohonan($nomor_jaminan_id, array &$cache) { // Cek apakah sudah ada di cache if (isset($cache[$nomor_jaminan_id])) { return $cache[$nomor_jaminan_id]; } // Cari di tabel Permohonan berdasarkan nomor registrasi $permohonan = Permohonan::where('nomor_registrasi', $nomor_jaminan_id)->first(); if (!$permohonan) { // Tidak ditemukan $cache[$nomor_jaminan_id] = null; return null; } // Cari dokument jaminan terkait $dokumenJaminan = DokumenJaminan::where('permohonan_id', $permohonan->id)->first(); // Simpan hasil ke cache $result = [ 'id' => $permohonan->id, 'dokument_id' => $dokumenJaminan ? $dokumenJaminan->id : null ]; $cache[$nomor_jaminan_id] = $result; return $result; } private function buildFullDataForm(array $rows) { $assetJson = json_decode($this->checkAsset($rows)[0] ?? '', true); $tanahJson = json_decode($this->checkTanah($rows)[0] ?? '', true); $bangunanJson = json_decode($this->checkBangunan($rows)[0] ?? '', true); $lingkunganJson = json_decode($this->checkLingkungan($rows)[0] ?? '', true); $nomorLpj = $rows[0]['mig_nomor_lpj'] ?? null; $kesimpulanRows = $this->loadKesimpulanByNomorLpj($nomorLpj); $faktaJson = []; if (!empty($kesimpulanRows)) { $faktaResult = $this->checkKesimpulan($kesimpulanRows); $faktaJson = json_decode($faktaResult[0] ?? '', true)['fakta'] ?? []; } return json_encode([ 'asset' => $assetJson['asset'] ?? [], 'tanah' => $tanahJson['tanah'] ?? [], 'bangunan' => $bangunanJson['bangunan'] ?? [], 'lingkungan' => $lingkunganJson['lingkungan'] ?? [], 'fakta' => $faktaJson ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); } private function loadKesimpulanByNomorLpj($nomorLpj) { $filePath = realpath(__DIR__ . '/csv/inspeksi/mig_inspeksi_kesimpulan_2025.csv'); if (!$filePath || !$nomorLpj) { return []; } if (($handle = fopen($filePath, 'r')) === false) { Log::error("Gagal membuka file CSV kesimpulan: " . $filePath); return []; } $header = fgetcsv($handle, 0, '~'); $result = []; while (($data = fgetcsv($handle, 0, '~')) !== false) { if (count($data) != count($header)) { continue; } $row = array_combine($header, $data); if ($row['mig_nomor_lpj'] === $nomorLpj) { $result[] = $row; } } // print_r($result); fclose($handle); return $result; } private function checkTanah($rows) { $dataToInsertJson = []; $tanah = [ 'luas_tanah' => ['sesuai' => null], 'hadap_mata_angin' => ['sesuai' => null], 'bentuk_tanah' => ['bentuk_tanah' => [], 'lainnya' => null], 'kontur_tanah' => [], 'ketinggian_tanah' => ['ketinggian' => [], 'lebih_tinggi' => null, 'lebih_rendah' => null], 'kontur_jalan' => null, 'ketinggian_jalan' => [], 'posisi_kavling' => ['posisi_kavling' => [], 'lainnya' => null], 'tusuk_sate' => 'no', 'lockland' => 'no', 'kondisi_fisik_tanah' => ['kondisi_fisik_tanah' => [], 'lainnya' => null] ]; foreach ($rows as $row) { $name = trim($row['name']); $keySesuaiORTidak = !empty($row['mig_name_keterangan_lain']) ? 'sesuai' : 'tidak sesuai'; $value = !empty($row['mig_name_keterangan_lain']) ? $row['mig_name_keterangan_lain'] : $row['mig_name_keterangan']; switch ($name) { case 'Luas Tanah': $tanah['luas_tanah'][$keySesuaiORTidak] = $value; break; case 'Permukaan dengan Jalan': $tanah['hadap_mata_angin'][$keySesuaiORTidak] = $value; break; case 'Bentuk Tanah': if ($value === 'Lainnya') { $tanah['bentuk_tanah']['bentuk_tanah'][] = $value; $tanah['bentuk_tanah']['lainnya'] = $row['mig_name_keterangan_lain']; } else { $tanah['bentuk_tanah']['bentuk_tanah'][] = $value; } break; case 'Kontur Tanah': $tanah['kontur_tanah'][] = $value; break; case 'Peruntukan Tanah': $tanah['ketinggian_tanah']['ketinggian'][] = $value; break; case 'Fisik Tanah': $tanah['kondisi_fisik_tanah']['kondisi_fisik_tanah'][] = $value; break; } } // Isi default jika kosong if (empty($tanah['luas_tanah']['sesuai'])) { $tanah['luas_tanah'] = ['sesuai' => null]; } if (empty($tanah['hadap_mata_angin']['sesuai'])) { $tanah['hadap_mata_angin'] = ['sesuai' => null]; } // Masukkan ke array JSON $dataToInsertJson[] = json_encode([ 'tanah' => $tanah ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); return $dataToInsertJson; } private function checkBangunan($rows) { $dataToInsertJson = []; // Inisialisasi struktur bangunan $bangunan = [ 'luas_tanah_bagunan' => ['sesuai' => null], 'jenis_bangunan' => [], 'kondisi_bangunan' => [], 'sifat_bangunan' => [], 'sifat_bangunan_input' => [null, null, null], 'spesifikasi_bangunan' => [ [ 'bagunan' => 'Bangunan 1', 'spek_kategori_bangunan' => [ 'Pondasi' => ['0' => null, '1' => null, 'lainnya' => null], 'Struktur' => ['0' => null, '1' => null, 'lainnya' => null], 'Rangka Atap' => ['0' => null, 'lainnya' => null], 'Tutup Atap' => ['0' => null, 'lainnya' => null], 'Plafond' => ['lainnya' => null], 'Dinding' => ['0' => null, '1' => null, 'lainnya' => null], 'Pelapis Dinding' => ['0' => null, 'lainnya' => null], 'Pintu Jendela' => ['0' => null, '1' => null, '2' => null, '3' => null, 'lainnya' => null], 'Lantai' => ['0' => null, '1' => null, 'lainnya' => null], ] ] ], 'sarana_pelengkap' => [], 'sarana_pelengkap_input' => [null, null, null, null, null, null, null, null, null] ]; foreach ($rows as $row) { $name = trim($row['name']); $keySesuaiORTidak = !empty($row['mig_name_keterangan_lain']) ? 'sesuai' : 'tidak sesuai'; $value = !empty($row['mig_name_keterangan_lain']) ? $row['mig_name_keterangan_lain'] : $row['mig_name_keterangan']; switch ($name) { case 'Luas Bangunan': $bangunan['luas_tanah_bagunan'][$keySesuaiORTidak] = $value; break; case 'Jenis Bangunan': if ($value === 'Lainnya') { $bangunan['jenis_bangunan'][] = $value; } else { $bangunan['jenis_bangunan'][] = $value; } break; case 'Kondisi Bangunan': $bangunan['kondisi_bangunan'][] = $value; break; case 'Sifat Bangunan': $bangunan['sifat_bangunan'][] = $value; break; case 'Lantai': $bangunan['spesifikasi_bangunan'][0]['spek_kategori_bangunan']['Lantai']['0'] = $value; break; case 'Dinding': $bangunan['spesifikasi_bangunan'][0]['spek_kategori_bangunan']['Dinding']['0'] = $value; break; case 'Langit-langit / Plafon': $bangunan['spesifikasi_bangunan'][0]['spek_kategori_bangunan']['Plafond']['lainnya'] = $value; break; case 'Kusen-kusen': $bangunan['spesifikasi_bangunan'][0]['spek_kategori_bangunan']['Pintu Jendela']['0'] = $value; break; case 'Penutup Atap': $bangunan['spesifikasi_bangunan'][0]['spek_kategori_bangunan']['Tutup Atap']['0'] = $value; break; case 'Partisi': $bangunan['spesifikasi_bangunan'][0]['spek_kategori_bangunan']['Dinding']['1'] = $value; break; } } // Isi default jika kosong if (empty($bangunan['luas_tanah_bagunan']['sesuai'])) { $bangunan['luas_tanah_bagunan'] = ['sesuai' => null]; } // Masukkan ke array JSON $dataToInsertJson[] = json_encode([ 'bangunan' => $bangunan ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); return $dataToInsertJson; } private function checkLingkungan($rows) { $dataToInsertJson = []; // Inisialisasi struktur lingkungan $lingkungan = [ 'jarak_jalan_utama' => null, 'jalan_linkungan' => null, 'jarak_cbd_point' => null, 'nama_cbd_point' => null, 'lebar_perkerasan_jalan' => null, 'perkerasan_jalan' => ['perkerasan_jalan' => [], 'lainnya' => null], 'lalu_lintas' => null, 'gol_mas_sekitar' => [], 'tingkat_keramaian' => [], 'terletak_diarea' => ['terletak_diarea' => [], 'lainnya' => null], 'disekitar_lokasi' => null, 'kondisi_bagunan_disekitar_lokasi' => null, 'sifat_bagunan_disekitar_lokasi' => null, 'dekat_makam' => 'tidak', 'jarak_makam' => null, 'nama_makam' => null, 'dekat_tps' => 'tidak', 'jarak_tps' => null, 'nama_tps' => null, 'dekat_lainnya' => null, 'merupakan_daerah' => [], 'fasilitas_dekat_object' => [], 'fasilitas_dekat_object_input' => [ 'Tempat Ibadah' => null, 'Rumah Sakit' => null, 'Sekolah' => null, 'Kantor Pemerintahan' => null, 'Stasiun Kereta' => null, 'Terminal Bus' => null, 'Bandara' => null, 'Pos Polisi' => null, 'Lainnya' => null ] ]; foreach ($rows as $row) { $name = trim($row['name']); $value = !empty($row['mig_name_keterangan_lain']) ? $row['mig_name_keterangan_lain'] : $row['mig_name_keterangan']; switch ($name) { case 'Lebar jalan dimuka lokasi': $lingkungan['lebar_perkerasan_jalan'] = $row['mig_name_keterangan_lain']; break; case 'Lapisan perkerasan jalan dari': if ($value === 'Lainnya') { $lingkungan['perkerasan_jalan']['perkerasan_jalan'][] = $value; $lingkungan['perkerasan_jalan']['lainnya'] = $row['mig_name_keterangan_lain']; } else { $lingkungan['perkerasan_jalan']['perkerasan_jalan'][] = $value; } break; case 'Lalulintas didepan lokasi': $lingkungan['lalu_lintas'] = $value; break; case 'Golongan Masyarakat sekitar': $lingkungan['gol_mas_sekitar'][] = $value; break; case 'Dengan kondisi': $lingkungan['tingkat_keramaian'][] = $value; break; case 'Terletak didaerah': if ($value === 'Lainnya') { $lingkungan['terletak_diarea']['terletak_diarea'][] = $value; $lingkungan['terletak_diarea']['lainnya'] = $row['mig_name_keterangan_lain']; } else { $lingkungan['terletak_diarea']['terletak_diarea'][] = $value; } break; case 'Disekitar lokasi': $lingkungan['disekitar_lokasi'] = $value === 'Telah ada bangunan' ? 'sesuai' : 'tidak sesuai'; break; case 'Merupakan daerah': $lingkungan['merupakan_daerah'][] = $value; break; case 'Fasilitas umum dekat lokasi': $lingkungan['fasilitas_dekat_object'][] = $value; if (isset($lingkungan['fasilitas_dekat_object_input'][$value])) { $lingkungan['fasilitas_dekat_object_input'][$value] = $value; } elseif ($value === 'Pasar / Swalayan') { $lingkungan['fasilitas_dekat_object_input']['Lainnya'] = 'pasar'; } break; } } // Isi default jika kosong if (empty($lingkungan['fasilitas_dekat_object'])) { $lingkungan['fasilitas_dekat_object'] = []; } // Masukkan ke array JSON $dataToInsertJson[] = json_encode([ 'lingkungan' => $lingkungan ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); return $dataToInsertJson; } private function checkAsset(array $rows) { $dataToInsertJson = []; // Ambil baris pertama saja $firstRow = reset($rows); if (!$firstRow) { return []; } // Inisialisasi variabel dari baris pertama $hubCadeb = null; $hubPenghuni = null; $jenisAsset = null; // Ambil data utama dari baris pertama $pihakBank = $firstRow['mig_pihak_bank'] ?? null; $debiturPerwakilan = $firstRow['mig_debiture_perwakilan'] ?? null; $address = $firstRow['address'] ?? null; // Kode wilayah $provinceCache = []; // cache untuk helper $cityCache = []; $districtCache = []; $subdistrictCache = []; $proviceCode = $this->getProvinceCode($firstRow['mig_village_name'] ?? '', $provinceCache); $cityCode = $this->getCityCode($firstRow['mig_city_name'] ?? '', $cityCache); $districtCode = $this->getDistrictCode($firstRow['mig_district_name'] ?? '', $districtCache); $subdistrict = $this->getSubdistrictCode($firstRow['mig_province_name'] ?? '', $subdistrictCache, $districtCache); // Proses hubungan pemilik/penghuni & jenis asset dari seluruh rows foreach ($rows as $row) { $name = trim($row['name'] ?? ''); $keySesuaiORTidak = !empty($row['mig_name_keterangan_lain']) ? 'sesuai' : 'tidak sesuai'; $value = !empty($row['mig_name_keterangan_lain']) ? $row['mig_name_keterangan_lain'] : $row['mig_name_keterangan'] ?? null; switch ($name) { case 'Hubungan Pemilik Jaminan dengan Debitur': $hubCadeb = [ $keySesuaiORTidak => $value ]; break; case 'Hubungan Penghuni Jaminan dengan Debitur': $hubPenghuni = [ $keySesuaiORTidak => $value ]; break; case 'Jenis Bangunan': $jenisAsset = [ $keySesuaiORTidak => $value ]; break; } } // Isi default jika kosong if (empty($hubCadeb)) $hubCadeb = ["tidak sesuai" => null]; if (empty($hubPenghuni)) $hubPenghuni = ["tidak sesuai" => null]; if (empty($jenisAsset)) $jenisAsset = ["sesuai" => null]; // Bangun struktur JSON $asset = [ "debitur_perwakilan" => $debiturPerwakilan, "jenis_asset" => $jenisAsset, "alamat" => [ "sesuai" => [ "address" => $address, "village_code" => $subdistrict['code'], "district_code" => $districtCode, "city_code" => $cityCode, "province_code" => $proviceCode ] ], "hub_cadeb" => $hubCadeb, "hub_cadeb_penghuni" => $hubPenghuni, "pihak_bank" => $pihakBank, "kordinat_lng" => null, "kordinat_lat" => null ]; // Masukkan ke array JSON $dataToInsertJson[] = json_encode([ "asset" => $asset ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); return $dataToInsertJson; } private function checkKesimpulan($rows) { $dataToInsertJson = []; $fakta = [ 'fakta_positif' => [], 'fakta_negatif' => [], 'rute_menuju' => null, 'batas_batas' => ["Utara", "Timur", "Selatan", "Barat"], 'batas_batas_input' => [ "Utara" => null, "Timur Laut" => null, "Timur" => null, "Tenggara" => null, "Selatan" => null, "Barat Daya" => null, "Barat" => null, "Barat Laut" => null, "Utara Timur Laut" => null, "Timur Timur Laut" => null, "Timur Tenggara" => null, "Selatan Tenggara" => null, "Selatan Barat Daya" => null, "Barat Barat Daya" => null, "Barat Barat Laut" => null, "Utara Barat Laut" => null ], 'kondisi_lingkungan' => [], 'kondisi_lain_bangunan' => [], 'informasi_dokument' => [], 'peruntukan' => null, 'kdb' => null, 'kdh' => null, 'gsb' => null, 'max_lantai' => null, 'klb' => null, 'gss' => null, 'pelebaran_jalan' => null, 'nama_petugas' => null, 'keterangan' => [] ]; foreach ($rows as $row) { $jenis = trim($row['mig_keterangan']); // misal: Fakta Positif, Fakta Negatif, dll $keterangan = trim($row['mig_kesimpulan']); switch ($jenis) { case 'Faktor Positif': $fakta['fakta_positif'][] = $keterangan; break; case 'Faktor Negatif': $fakta['fakta_negatif'][] = $keterangan; break; case 'Rute Menuju': $fakta['rute_menuju'] = $keterangan; break; case 'Kondisi Lingkungan': $fakta['kondisi_lingkungan'][] = $keterangan; break; case 'Kondisi Bangunan': $fakta['kondisi_lain_bangunan'][] = $keterangan; break; case 'Informasi Dokumen': $fakta['informasi_dokument'][] = $keterangan; break; case 'Lain - lain': $fakta['keterangan'][] = $keterangan; break; } } $dataToInsertJson[] = json_encode([ 'fakta' => $fakta ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); return $dataToInsertJson; } private function parseTimestamp(?string $timestamp): ?string { try { if ($timestamp) { // Cek jika format hanya tanggal (Y-m-d) if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $timestamp)) { return \Carbon\Carbon::createFromFormat('Y-m-d', $timestamp) ->startOfDay() ->toDateTimeString(); } // Format lengkap (Y-m-d H:i:s) return \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $timestamp)->toDateTimeString(); } return null; } catch (\Exception $e) { Log::error('Gagal memparsing timestamp: ' . $timestamp . '. Pesan: ' . $e->getMessage()); return null; } } private function getProvinceCode(string $name, array &$cache): ?string { $normalizedName = strtolower($name); if (isset($cache[$normalizedName])) { return $cache[$normalizedName]; } $province = Province::whereRaw('LOWER(name) = ?', [strtolower($name)])->first(); if ($province) { $cache[$normalizedName] = $province->code; return $province->code; } return null; } private function getCityCode(string $name, array &$cache): ?string { $normalizedName = strtolower($name); if (isset($cache[$normalizedName])) { return $cache[$normalizedName]; } $city = City::whereRaw('LOWER(name) = ?', [strtolower($name)])->first(); if ($city) { $cache[$normalizedName] = $city->code; return $city->code; } return null; } private function getDistrictCode(string $name, array &$cache): ?string { $normalizedName = strtolower($name); if (isset($cache[$normalizedName])) { return $cache[$normalizedName]; } $district = District::whereRaw('LOWER(name) = ?', [strtolower($name)])->first(); if ($district) { $cache[$normalizedName] = $district->code; return $district->code; } return null; } private function getSubdistrictCode(string $name, array &$cache, array &$districtCache): ?array { $normalizedName = strtolower($name); // Pastikan cache menyimpan array, bukan hanya kode if (isset($cache[$normalizedName])) { return $cache[$normalizedName]; } // Ambil subdistrict dari database $subdistrict = Village::whereRaw('LOWER(name) = ?', [$normalizedName])->first(); // Jika ditemukan, simpan ke dalam cache sebagai array lengkap if ($subdistrict) { $cache[$normalizedName] = [ 'code' => $subdistrict->code, 'postal_code' => $subdistrict->postal_code ]; return $cache[$normalizedName]; } // Jika tidak ditemukan, kembalikan null return [ 'code' => null, 'postal_code' => null ]; } private function initializeErrorLog() { $file = $this->errorLogFile; // Hapus file lama jika ada if (file_exists($file)) { unlink($file); } // Buat file baru dengan header $handle = fopen($file, 'w'); fputcsv($handle, ['mig_mst_jaminan_nomor_jaminan', 'Error']); fclose($handle); } private function logError( $nomorJaminan, string $message) { Log::error("Error migrasi permohonan [$nomorJaminan]: $message"); // Tulis ke file error $handle = fopen($this->errorLogFile, 'a'); fputcsv($handle, [$nomorJaminan, $message]); } }