- Menata LpjDatabaseSeeder untuk orkestrasi batch, aktifkan MigrationGambarInspeksiSeeder . - Migrasi domain MIG → LPJ lengkap dengan parseTimestamp , initializeErrorLog , logError . - Tambah seeders MIG eksternal & tim penilai; normalisasi mapping checkTujuanPenilaian . - Perluasan master: JFK009–JFK014, TP0007–TP00010, hubungan pemilik, KJPP; TeamsSeeder via SQL. - MasterDataSurveyorSeeder eksekusi SQL referensi (20+ tabel) via DB::unprepared . - Tambah puluhan SQL referensi (jenis, kondisi, sarana, posisi, spek, dll). - Normalisasi data inspeksi (duplikasi key dinamis), serialisasi JSON rapi. - Logging seragam ke app log + CSV error untuk audit trail.
506 lines
18 KiB
PHP
506 lines
18 KiB
PHP
<?php
|
|
|
|
namespace Modules\Lpj\Database\Seeders;
|
|
|
|
use Illuminate\Database\Seeder;
|
|
use Modules\Lpj\Models\DokumenJaminan;
|
|
use Modules\Lpj\Models\Permohonan;
|
|
use Modules\Lpj\Models\Penilai;
|
|
use Modules\Lpj\Models\Laporan;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class MigrationPenilaiSeeder extends Seeder
|
|
{
|
|
/**
|
|
* Run the database seeds.
|
|
*/
|
|
protected $errorLogFile = __DIR__ . '/csv/penilaian/penilai.fix.latest_error.csv';
|
|
public function run()
|
|
{
|
|
$this->initializeErrorLog();
|
|
// Path ke file csv
|
|
$filePath = realpath(__DIR__ . '/csv/penilaian/penilai.fix.latest_20251011.csv');
|
|
|
|
if (!$filePath) {
|
|
Log::error('File csv tidak ditemukan: ' . __DIR__ . '/csv/penilaian/penilai.fix.latest_20251011.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 = [];
|
|
}
|
|
}
|
|
// info_harga_laporan_202505021522.csv
|
|
// print_r($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 &$userData, int &$errorCount, array &$errorDebitureIds, int $totalData, int $batchCount, int $currentRow)
|
|
{
|
|
// Kelompokkan berdasarkan mig_nomor_lpj
|
|
$groupedRows = $this->groupRowsByLpj($rows);
|
|
|
|
foreach ($groupedRows as $nomorLpj => $groupRows) {
|
|
try {
|
|
// Ambil informasi permohonan dan dokument_id
|
|
$nomorRegis = $this->getNomorRegistrasiPermohonan($nomorLpj, $branchCache);
|
|
if (!$nomorRegis) {
|
|
Log::warning("Nomor registrasi tidak ditemukan untuk nomor LPJ: {$nomorLpj}");
|
|
$errorCount++;
|
|
$errorDebitureIds[] = $nomorLpj;
|
|
//continue;
|
|
}
|
|
|
|
// Dapatkan type_penilai (misal: standar, sederhana)
|
|
$firstRow = reset($groupRows);
|
|
|
|
$typePenilai = $this->checkTypePenilai($firstRow['mig_kode_jenis_laporan'] ?? '');
|
|
|
|
if (!$typePenilai) {
|
|
Log::warning("Tidak ada jenis laporan valid untuk nomor LPJ: {$nomorLpj}");
|
|
$errorCount++;
|
|
$errorDebitureIds[] = $nomorLpj;
|
|
//continue;
|
|
}
|
|
|
|
// Bangun JSON type_penilai
|
|
$penilaiJson = $this->cekJenisPenilai($groupRows);
|
|
|
|
// Simpan ke tabel Penilai
|
|
|
|
// print_r(json_decode($penilaiJson, true));
|
|
|
|
// Mapping field JSON berdasarkan tipe penilaian
|
|
$fillableFieldMap = [
|
|
'memo' => 'memo',
|
|
'standar' => 'lpj',
|
|
'sederhana' => 'lpj',
|
|
'call_report' => 'call_report',
|
|
'rap' => 'rap',
|
|
'resume' => 'resume'
|
|
];
|
|
|
|
$fieldToUpdate = $fillableFieldMap[$typePenilai] ?? null;
|
|
|
|
if (!$fieldToUpdate) {
|
|
Log::warning("Field tidak dikenali untuk tipe: {$typePenilai}");
|
|
$errorCount++;
|
|
$errorDebitureIds[] = $nomorLpj;
|
|
//continue;
|
|
}
|
|
|
|
// NO: 001/241917/LPJ/PJ-2251/VII/24
|
|
// 001 => kode cabang
|
|
// 241917 => nomor lpj
|
|
// LPJ => jenis laporan
|
|
// PJ-2251 => nomor registrasi ambil nilai akhirnnya 242251
|
|
// VII => bulan
|
|
// 24 => tahun
|
|
|
|
// Generate nomor_laporan
|
|
$mig_permohonan = json_decode($nomorRegis['mig_permohonan'],true) ?? '';
|
|
$tanggal = $mig_permohonan['mig_mst_lpj_tgl_laporan'] ?? $mig_permohonan['mig_mst_lpj_tgl_oto'] ?? $firstRow['mig_created_at'];
|
|
|
|
//$tanggal = $firstRow['mig_created_at'];
|
|
$nomorD = $nomorRegis['nomor_registrasi'];
|
|
$nomorRegistrasi = "PJ-{$nomorD}";
|
|
$nomorLaporan = $this->generateNomorLaporan($typePenilai, $nomorLpj, $nomorRegistrasi, $tanggal, $nomorRegis['id']);
|
|
// Simpan atau update ke tabel Penilai
|
|
$penilaiLP = Penilai::updateOrCreate(
|
|
[
|
|
'permohonan_id' => $nomorRegis['id'],
|
|
'dokument_id' => $nomorRegis['dokument_id']
|
|
],
|
|
[
|
|
'type' => $typePenilai,
|
|
$fieldToUpdate => $penilaiJson,
|
|
'type_penilai' => $typePenilai,
|
|
'created_at' => $this->parseTimestamp($tanggal),
|
|
'updated_at' => $this->parseTimestamp($tanggal),
|
|
]
|
|
);
|
|
|
|
// Simpan ke tabel Laporan
|
|
Laporan::updateOrCreate(
|
|
[
|
|
'permohonan_id' => $penilaiLP->permohonan_id,
|
|
'dokumen_jaminan_id' => $penilaiLP->dokument_id
|
|
],
|
|
[
|
|
'nomor_laporan' => $nomorLaporan,
|
|
'created_at' => $this->parseTimestamp($tanggal),
|
|
'updated_at' => $this->parseTimestamp($tanggal)
|
|
]
|
|
);
|
|
|
|
|
|
$this->command->info("Berhasil simpan data penilai untuk nomor LPJ: {$nomorLpj}");
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error("Error pada nomor LPJ {$nomorLpj}: " . $e->getMessage());
|
|
$errorCount++;
|
|
$errorDebitureIds[] = $nomorLpj;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private function getNomorRegistrasiPermohonan($nomorLpj, array &$cache)
|
|
{
|
|
if (isset($cache[$nomorLpj])) {
|
|
return $cache[$nomorLpj];
|
|
}
|
|
|
|
$permohonan = Permohonan::where('nomor_lpj', $nomorLpj)->first();
|
|
|
|
if (!$permohonan) {
|
|
$cache[$nomorLpj] = null;
|
|
return null;
|
|
}
|
|
|
|
$dokumenJaminan = DokumenJaminan::where('permohonan_id', $permohonan->id)->first();
|
|
|
|
$result = [
|
|
'id' => $permohonan->id,
|
|
'dokument_id' => $dokumenJaminan ? $dokumenJaminan->id : null,
|
|
'nomor_registrasi' => $permohonan->nomor_registrasi,
|
|
'mig_permohonan' => $permohonan->mig_permohonan
|
|
];
|
|
|
|
$cache[$nomorLpj] = $result;
|
|
|
|
return $result;
|
|
}
|
|
private function groupRowsByLpj(array $rows): array
|
|
{
|
|
$grouped = [];
|
|
|
|
foreach ($rows as $row) {
|
|
$nomorLpj = $row['mig_nomor_lpj'] ?? null;
|
|
|
|
if (!empty($nomorLpj)) {
|
|
$grouped[$nomorLpj][] = $row;
|
|
}
|
|
}
|
|
|
|
return $grouped;
|
|
}
|
|
|
|
private function checkTypePenilai($type)
|
|
{
|
|
$data = [
|
|
'MAK' => 'memo',
|
|
'STD' => 'standar',
|
|
'SPL' => 'sederhana',
|
|
'RHP' => 'call-report',
|
|
'RAP' => 'rap',
|
|
'PRG' => 'resume',
|
|
];
|
|
|
|
return $data[$type];
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function cekJenisPenilai(array $groupRows)
|
|
{
|
|
// Urutkan grup berdasarkan mig_urutan_seq
|
|
usort($groupRows, function ($a, $b) {
|
|
return ($a['mig_urutan_seq'] ?? 999) <=> ($b['mig_urutan_seq'] ?? 999);
|
|
});
|
|
|
|
// Inisialisasi struktur JSON
|
|
$penilaiJson = [
|
|
'luas_bangunan' => null,
|
|
'luas_tanah' => null,
|
|
'nilai_tanah_1' => null,
|
|
'nilai_tanah_2' => null,
|
|
'sarana_pelengkap_penilai' => null,
|
|
'nilai_sarana_pelengkap_1' => null,
|
|
'nilai_sarana_pelengkap_2' => null,
|
|
'total_nilai_pasar_wajar' => null,
|
|
'likuidasi' => null,
|
|
'likuidasi_nilai_1' => null,
|
|
'likuidasi_nilai_2' => null,
|
|
'asuransi_luas_bangunan' => null,
|
|
'asuransi_nilai_1' => null,
|
|
'asuransi_nilai_2' => "0",
|
|
'npw_tambahan' => []
|
|
];
|
|
|
|
// Ambil mainRow (urutan pertama)
|
|
$mainRow = null;
|
|
foreach ($groupRows as $row) {
|
|
if (($row['mig_urutan_seq'] ?? '') == 1) {
|
|
$mainRow = $row;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Jika tidak ada mainRow, ambil baris pertama sebagai fallback
|
|
if (!$mainRow && !empty($groupRows[0])) {
|
|
$mainRow = $groupRows[0];
|
|
}
|
|
|
|
if ($mainRow) {
|
|
$penilaiJson['total_nilai_pasar_wajar'] = $mainRow['mig_nilai_total_nilai_pasar'] ?? null;
|
|
$penilaiJson['likuidasi'] = $mainRow['mig_nilai_likudasi'] ?? null;
|
|
|
|
// Hitung likuidasi nilai 2 jika ada total dan persen likuidasi
|
|
$totalPasarWajar = (int)str_replace('.', '', $mainRow['mig_nilai_total_nilai_pasar'] ?? 0);
|
|
$persenLikuidasi = (int)($mainRow['mig_nilai_likudasi'] ?? 0);
|
|
$penilaiJson['likuidasi_nilai_1'] = $mainRow['mig_nilai_total_nilai_pasar'] ?? null;
|
|
$penilaiJson['likuidasi_nilai_2'] = number_format(
|
|
$totalPasarWajar * ($persenLikuidasi / 100),
|
|
0,
|
|
'',
|
|
''
|
|
);
|
|
|
|
// Isi data utama hanya untuk urutan 1
|
|
$penilaiJson['luas_tanah'] = $mainRow['mig_nilai_satuan'] ?? null;
|
|
$penilaiJson['nilai_tanah_1'] = $mainRow['mig_harga_satuan'] ?? null;
|
|
$penilaiJson['nilai_tanah_2'] = number_format(
|
|
(int)str_replace('.', '', $mainRow['mig_nilai_satuan'] ?? 0) *
|
|
(int)str_replace('.', '', $mainRow['mig_harga_satuan'] ?? 0),
|
|
0,
|
|
'',
|
|
''
|
|
);
|
|
}
|
|
|
|
// Proses tambahan (urutan > 1)
|
|
foreach ($groupRows as $row) {
|
|
// Hanya proses jika mig_urutan_seq ada
|
|
$urutan = $row['mig_urutan_seq'] ?? '';
|
|
if (empty($urutan)) {
|
|
continue;
|
|
}
|
|
|
|
// Tambahkan ke npw_tambahan
|
|
$penilaiJson['npw_tambahan'][] = [
|
|
'name' => $row['mig_keterangan'] ?? 'Luas Bangunan Tambahan',
|
|
'luas' => $row['mig_nilai_satuan'] ?? null,
|
|
'nilai_1' => $row['mig_harga_satuan'] ?? null,
|
|
'nilai_2' => number_format(
|
|
(int)str_replace('.', '', $row['mig_nilai_satuan'] ?? 0) *
|
|
(int)str_replace('.', '', $row['mig_harga_satuan'] ?? 0),
|
|
0,
|
|
'',
|
|
''
|
|
)
|
|
];
|
|
}
|
|
|
|
// Kosongkan npw_tambahan jika kosong
|
|
if (empty($penilaiJson['npw_tambahan'])) {
|
|
$penilaiJson['npw_tambahan'] = [];
|
|
}
|
|
|
|
return json_encode($penilaiJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
|
}
|
|
|
|
private function convertToRoman($month)
|
|
{
|
|
$roman = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII'];
|
|
return $month >= 1 && $month <= 12 ? $roman[$month - 1] : '';
|
|
}
|
|
|
|
private function generateNomorLaporan($typePenilai, $nomorLpj, $nomorRegistrasi, $tanggal)
|
|
{
|
|
// Mapping type_penilai ke singkatan laporan
|
|
$laporanMap = [
|
|
'memo' => 'MEMO',
|
|
'standar' => 'LPJ',
|
|
'sederhana' => 'LPJ',
|
|
'call_report' => 'CALL',
|
|
'rap' => 'RAP',
|
|
'resume' => 'RESUME'
|
|
];
|
|
|
|
// Dapatkan tahun dan bulan dari tanggal
|
|
$tanggal_ = $this->parseTimestamp($tanggal);
|
|
$date = \Carbon\Carbon::parse($tanggal_);
|
|
$kodeCabang = '001'; // bisa diambil dari user atau parameter lain jika dinamis
|
|
$tahun = substr($date->year, -2); // 2024 → 24
|
|
$bulan = $this->convertToRoman($date->month); // 7 → VII
|
|
|
|
// Format akhir nomor registrasi (PJ-XXX)
|
|
$nomorDebiturAkhir = substr($nomorRegistrasi, -4); // PJ-2251 → 2251
|
|
|
|
return sprintf(
|
|
"%s/%s/%s/%s/%s/%s",
|
|
$kodeCabang,
|
|
$nomorLpj,
|
|
$laporanMap[$typePenilai] ?? strtoupper($typePenilai),
|
|
"PJ-" . $nomorDebiturAkhir,
|
|
$bulan,
|
|
$tahun
|
|
);
|
|
}
|
|
|
|
private function parseTimestamp(?string $timestamp): ?string
|
|
{
|
|
if (!$timestamp) {
|
|
return null;
|
|
}
|
|
|
|
// Trim whitespace dan normalize
|
|
$timestamp = trim($timestamp);
|
|
|
|
// Log untuk debugging
|
|
Log::info('Mencoba parsing timestamp: "' . $timestamp . '"');
|
|
|
|
// Parsing dengan DateTime native PHP untuk lebih robust
|
|
try {
|
|
// Pattern untuk format d/m/Y H:i:s
|
|
if (preg_match('/^(\d{1,2})\/(\d{1,2})\/(\d{4})\s+(\d{1,2}):(\d{1,2}):(\d{1,2})$/', $timestamp, $matches)) {
|
|
$day = (int) $matches[1];
|
|
$month = (int) $matches[2];
|
|
$year = (int) $matches[3];
|
|
$hour = (int) $matches[4];
|
|
$minute = (int) $matches[5];
|
|
$second = (int) $matches[6];
|
|
|
|
// Validasi nilai
|
|
if ($day >= 1 && $day <= 31 && $month >= 1 && $month <= 12 && $year >= 1900 && $year <= 2100 &&
|
|
$hour >= 0 && $hour <= 23 && $minute >= 0 && $minute <= 59 && $second >= 0 && $second <= 59) {
|
|
|
|
// Buat DateTime object langsung
|
|
$dateTime = new \DateTime();
|
|
$dateTime->setDate($year, $month, $day);
|
|
$dateTime->setTime($hour, $minute, $second);
|
|
|
|
$result = $dateTime->format('Y-m-d H:i:s');
|
|
Log::info('Berhasil parsing dengan DateTime: ' . $timestamp . ' -> ' . $result);
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
// Pattern untuk format d/m/Y tanpa waktu
|
|
if (preg_match('/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/', $timestamp, $matches)) {
|
|
$day = (int) $matches[1];
|
|
$month = (int) $matches[2];
|
|
$year = (int) $matches[3];
|
|
|
|
// Validasi nilai
|
|
if ($day >= 1 && $day <= 31 && $month >= 1 && $month <= 12 && $year >= 1900 && $year <= 2100) {
|
|
|
|
// Buat DateTime object langsung
|
|
$dateTime = new \DateTime();
|
|
$dateTime->setDate($year, $month, $day);
|
|
$dateTime->setTime(0, 0, 0);
|
|
|
|
$result = $dateTime->format('Y-m-d H:i:s');
|
|
Log::info('Berhasil parsing tanpa waktu dengan DateTime: ' . $timestamp . ' -> ' . $result);
|
|
return $result;
|
|
}
|
|
}
|
|
} catch (\Exception $e) {
|
|
Log::error('Gagal parsing dengan DateTime: ' . $timestamp . '. Error: ' . $e->getMessage());
|
|
}
|
|
|
|
// Fallback ke format Carbon standar untuk format lainnya
|
|
$formats = [
|
|
'Y-m-d H:i:s',
|
|
'Y-m-d',
|
|
'd-m-Y H:i:s',
|
|
'd-m-Y',
|
|
'j-n-Y H:i:s',
|
|
'j-n-Y',
|
|
];
|
|
|
|
foreach ($formats as $format) {
|
|
try {
|
|
$carbon = \Carbon\Carbon::createFromFormat($format, $timestamp);
|
|
|
|
if ($carbon && $carbon->format($format) === $timestamp) {
|
|
// Jika format tidak mengandung waktu, set ke awal hari
|
|
if (!str_contains($format, 'H:i:s')) {
|
|
$carbon = $carbon->startOfDay();
|
|
}
|
|
Log::info('Berhasil parsing dengan format ' . $format . ': ' . $timestamp . ' -> ' . $carbon->toDateTimeString());
|
|
return $carbon->toDateTimeString();
|
|
}
|
|
} catch (\Exception $e) {
|
|
// Lanjut ke format berikutnya
|
|
continue;
|
|
}
|
|
}
|
|
|
|
Log::error('Tidak dapat memparsing timestamp dengan format apapun: "' . $timestamp . '"');
|
|
return null;
|
|
}
|
|
|
|
private function initializeErrorLog()
|
|
{
|
|
$file = $this->errorLogFile;
|
|
|
|
if (file_exists($file)) {
|
|
unlink($file); // Hapus file lama
|
|
}
|
|
|
|
$handle = fopen($file, 'w');
|
|
fputcsv($handle, ['mig_kd_debitur_seq', 'Error']);
|
|
fclose($handle);
|
|
}
|
|
|
|
private function logError(string $kode, string $message)
|
|
{
|
|
Log::error("Error migrasi dokumen jaminan [$kode]: $message");
|
|
|
|
$handle = fopen($this->errorLogFile, 'a');
|
|
fputcsv($handle, [$kode, $message]);
|
|
fclose($handle);
|
|
}
|
|
}
|