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_2025.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 { // Ambil informasi permohonan $nomorRegis = $this->getNomorRegistrasiPermohonan($nomorJaminan, $branchCache); if (!$nomorRegis) { Log::warning("Nomor registrasi tidak ditemukan untuk nomor jaminan: {$nomorJaminan}"); $errorCount++; $errorDebitureIds[] = $nomorJaminan; continue; } // Cek apakah sudah ada data $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 baris pertama untuk created_at/updated_at $firstRow = reset($groupRows); // Bangun JSON lengkap $dataFormJson = $this->buildFullDataForm($groupRows); $jenisJaminan = $this->checkJenisJaminan($groupRows[0]['mig_name'] ?? ''); if (!$dataFormJson) { Log::warning("Data form kosong untuk nomor jaminan: {$nomorJaminan}"); $errorCount++; $errorDebitureIds[] = $nomorJaminan; continue; } // Simpan ke database hanya sekali per grup Inspeksi::create([ 'name' => $jenisJaminan, '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'] ?? null), 'updated_at' => $this->parseTimestamp($firstRow['updated_at'] ?? null), '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) { if (empty($rows)) { return null; } $jenisJaminan = $rows[0]['mig_name'] ?? null; if (!$jenisJaminan) { return null; } try { $jsonResult = $this->checkBuildJson($jenisJaminan, $rows); // Validasi apakah hasil JSON valid json_decode($jsonResult); return $jsonResult; } catch (\Exception $e) { Log::error("Gagal build JSON untuk jenis jaminan {$jenisJaminan}: " . $e->getMessage()); return null; } } 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 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' => [], 'saran' => [] ]; 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 'Lain - lain': $fakta['informasi_dokument'][] = $keterangan; break; case 'CATATAN YANG PERLU DIPERHATIKAN': $fakta['keterangan'][] = $keterangan; break; case 'Catatan': $fakta['keterangan'][] = $keterangan; break; case 'Saran': $fakta['saran'][] = $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]); } public function checkJenisJaminan($input) { $input = trim($input); switch ($input) { case 'Tanah dan Bangunan eks KPR-BPPN': return 'tanah, bangunan, lingkungan, fakta, informasi'; case 'Pabrik': return 'tanah, bangunan, lingkungan, fakta, informasi'; case 'Kapal Laut': return 'kapal-laut'; case 'Tanah dan/atau Bangunan': return 'tanah, bangunan'; case 'Kendaraan Bermotor': return 'kendaraan-'; case 'Mesin-mesin dan Peralatan': return 'mesin'; case 'Barang Dagangan/FEO': return 'barang-dagangan'; case 'Pesawat Udara': return 'pesawat-udara'; case 'Alat Berat': return 'alat-berat'; case 'Deposito': return 'deposito'; case 'Rekening Giro / Tabungan': return 'rekening-giro-tabungan'; case 'Emas': return 'emas'; case 'Jaminan Pribadi / Personal Guarantee': return 'jaminan-pribadi'; case 'Jaminan Perusahaan / Corporate Guarantee': return 'jaminan-perusahaan'; case 'Resi Gudang': return 'resi-gudang'; case 'Surat Berharga dan Saham': return 'surat-berharga-saham'; case 'Tanah dan Bangunan (KerjaSama)': return 'tanah-bangunan-kerja-sama'; case 'Tanah Kavling (Kerjasama)': return 'tanah-kavling'; case 'Persediaan Barang / Barang Dagangan': return 'persediaan-barang'; case 'Apartemen': return 'apartemen'; case 'Tagihan / Piutang Dagang': return 'tagihan-piutang'; case 'Tanah dan/atau Bangunan KPR - SEDERHANA': return 'tanah-bangunan-kpr-sederhana'; default: return 'lainnya'; } } public function checkBuildJson($input, $rows = []) { $input = trim($input); // Pastikan rows selalu array if (!is_array($rows)) { $rows = [$rows]; } switch ($input) { case 'Tanah dan Bangunan eks KPR-BPPN': case 'Pabrik': case 'Tanah dan/atau Bangunan': case 'Apartemen': case 'Tanah dan/atau Bangunan KPR - SEDERHANA': $assetJson = json_decode($this->checkAsset($rows)[0] ?? '', true); $tanahJson = json_decode(buildInspeksiTanah($rows)[0] ?? '', true); $bangunanJson = json_decode(buildInspeksiBangunan($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); case 'Kapal Laut': $kapal = createInspeksiKapal($rows); return json_encode($kapal, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); case 'Kendaraan Bermotor': $kendaraan = createInspeksiKendaraan($rows); return json_encode($kendaraan, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); case 'Mesin-mesin dan Peralatan': $mesin = createInspeksiMesin($rows); return json_encode($mesin, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); case 'Pesawat Udara': $pesawat = createInspeksiPesawat($rows); return json_encode($pesawat, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); case 'Alat Berat': $alatBerat = createInspeksiAlatBerat($rows); return json_encode($alatBerat, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); case 'Barang Dagangan/FEO': case 'Deposito': case 'Rekening Giro / Tabungan': case 'Emas': case 'Jaminan Pribadi / Personal Guarantee': case 'Jaminan Perusahaan / Corporate Guarantee': case 'Resi Gudang': case 'Surat Berharga dan Saham': case 'Persediaan Barang / Barang Dagangan': case 'Tagihan / Piutang Dagang': return json_encode([ 'status' => 'pending', 'message' => 'Fungsi untuk "' . $input . '" belum diimplementasikan.', 'data' => $rows ]); case 'Tanah dan Bangunan (KerjaSama)': case 'Tanah Kavling (Kerjasama)': $pesawat = buildRapJsonData($rows); return json_encode($pesawat, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); default: return json_encode([ 'status' => 'unknown', 'message' => 'Jenis aset tidak dikenali: ' . $input, 'data' => $rows ]); } } }