$row) { // Log setiap baris yang diproses Log::info('Processing Bucok import row', [ 'row_number' => $rowIndex + 5, // +5 karena mulai dari baris 5 'row_data' => $row->toArray() ]); // Konversi row ke array dengan indeks numerik $rowArray = $row->toArray(); // Skip baris kosong if (empty(array_filter($rowArray))) { continue; } // Validasi data baris $mappedData = $this->mapRowToBucok($rowArray, $rowIndex + 5); // Update atau create berdasarkan nomor_tiket if (!empty($mappedData['nomor_tiket'])) { // Update atau create berdasarkan nomor_tiket $bucok = Bucok::updateOrCreate( ['nomor_tiket' => $mappedData['nomor_tiket']], // Kondisi pencarian array_merge($mappedData, ['updated_by' => auth()->id()]) // Data yang akan diupdate/create ); // Log dan tracking apakah data di-update atau di-create if ($bucok->wasRecentlyCreated) { $this->createdCount++; Log::info('Bucok created successfully', [ 'row_number' => $rowIndex + 5, 'nomor_tiket' => $mappedData['nomor_tiket'], 'action' => 'created' ]); } else { $this->updatedCount++; Log::info('Bucok updated successfully', [ 'row_number' => $rowIndex + 5, 'nomor_tiket' => $mappedData['nomor_tiket'], 'action' => 'updated' ]); } } $this->importedCount++; } DB::commit(); // Log summary Log::info('Bucok import completed', [ 'imported' => $this->importedCount, 'skipped' => $this->skippedCount, 'total_errors' => count($this->errors) ]); } catch (Exception $e) { DB::rollback(); Log::error('Bucok import failed', ['error' => $e->getMessage()]); throw $e; } } /** * Mapping data Excel berdasarkan indeks kolom ke struktur model Bucok * Kolom dimulai dari indeks 0 (A=0, B=1, C=2, dst.) * * @param array $row * @param int $rowNumber * @return array */ private function mapRowToBucok(array $row, int $rowNumber): array { return [ 'no' => $row[0] ?? null, // Kolom A 'tanggal' => !empty($row[1]) ? $this->parseDate($row[1]) : null, // Kolom B 'bulan' => $row[2] ?? null, // Kolom C 'tahun' => $row[3] ?? null, // Kolom D 'tanggal_penuh' => !empty($row[4]) ? $this->parseDate($row[4]) : null, // Kolom E 'nomor_categ' => $row[5] ?? null, // Kolom F 'coa_summary' => $row[6] ?? null, // Kolom G 'nomor_coa' => $row[7] ?? null, // Kolom H 'nama_coa' => $row[8] ?? null, // Kolom I 'nomor_tiket' => $row[9] ?? null, // Kolom J - Auto-generate jika kosong 'deskripsi' => $row[10] ?? null, // Kolom K 'nominal' => $this->parseNumeric($row[11] ?? 0), // Kolom L 'penyelesaian' => $row[12] ?? 'Belum Selesai', // Kolom M 'umur_aging' => $this->parseNumeric($row[13] ?? 0), // Kolom N 'cost_center' => $row[14] ?? null, // Kolom O 'nama_sub_direktorat' => $row[15] ?? null, // Kolom P 'nama_direktorat_cabang' => $row[16] ?? null, // Kolom Q 'tanggal_penyelesaian' => !empty($row[17]) ? $this->parseDate($row[17]) : null, // Kolom R 'nominal_penyelesaian' => $this->parseNumeric($row[18] ?? 0), // Kolom S 'nominal_berjalan' => $this->parseNumeric($row[19] ?? 0), // Kolom T 'amortisasi_berjalan' => $this->parseNumeric($row[20] ?? 0), // Kolom U 'sistem_berjalan' => $this->parseNumeric($row[21] ?? 0), // Kolom V 'lainnya_berjalan' => $this->parseNumeric($row[22] ?? 0), // Kolom W 'nominal_gantung' => $this->parseNumeric($row[23] ?? 0), // Kolom X 'aset_gantung' => $this->parseNumeric($row[24] ?? 0), // Kolom Y 'keterangan_gantung' => $row[25] ?? null, // Kolom Z 'lainnya_satu' => $row[26] ?? null, // Kolom AA 'lainnya_dua' => $row[27] ?? null, // Kolom AB 'created_by' => auth()->id(), 'updated_by' => auth()->id() ]; } /** * Parse tanggal dari berbagai format * * @param mixed $dateValue * @return Carbon|null */ private function parseDate($dateValue) { if (empty($dateValue)) { return null; } try { // Jika berupa angka Excel date serial if (is_numeric($dateValue)) { return Carbon::createFromFormat('Y-m-d', \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($dateValue)->format('Y-m-d')); } // Jika berupa string tanggal return Carbon::parse($dateValue); } catch (Exception $e) { Log::warning('Failed to parse date', ['value' => $dateValue, 'error' => $e->getMessage()]); return null; } } /** * Parse nilai numerik dari berbagai format * * @param mixed $numericValue * @return float */ private function parseNumeric($numericValue): float { if (empty($numericValue)) { return 0; } // Hapus karakter non-numerik kecuali titik dan koma $cleaned = preg_replace('/[^0-9.,\-]/', '', $numericValue); // Ganti koma dengan titik untuk decimal $cleaned = str_replace(',', '.', $cleaned); return (float) $cleaned; } /** * Validasi data yang sudah dimapping * * @param array $data * @return \Illuminate\Validation\Validator */ private function validateMappedData(array $data) { return Validator::make($data, [ 'no' => 'nullable|integer', 'tanggal' => 'nullable|date', 'bulan' => 'nullable|integer|between:1,12', 'tahun' => 'nullable|integer|min:2000|max:2099', 'tanggal_penuh' => 'nullable|date', 'nomor_categ' => 'nullable|string|max:50', 'coa_summary' => 'nullable|string|max:255', 'nomor_coa' => 'nullable|string|max:50', 'nama_coa' => 'nullable|string|max:255', 'nomor_tiket' => 'nullable|string|max:50', 'deskripsi' => 'nullable|string', 'nominal' => 'nullable|numeric|min:0', 'penyelesaian' => 'nullable|in:Selesai,Belum Selesai,Dalam Proses', 'umur_aging' => 'nullable|integer|min:0', 'cost_center' => 'nullable|string|max:100', 'nama_sub_direktorat' => 'nullable|string|max:255', 'nama_direktorat_cabang' => 'nullable|string|max:255', 'tanggal_penyelesaian' => 'nullable|date', 'nominal_penyelesaian' => 'nullable|numeric|min:0', 'nominal_berjalan' => 'nullable|numeric|min:0', 'amortisasi_berjalan' => 'nullable|numeric|min:0', 'sistem_berjalan' => 'nullable|numeric|min:0', 'lainnya_berjalan' => 'nullable|numeric|min:0', 'nominal_gantung' => 'nullable|numeric|min:0', 'aset_gantung' => 'nullable|numeric|min:0', 'keterangan_gantung' => 'nullable|string', 'lainnya_satu' => 'nullable|string', 'lainnya_dua' => 'nullable|string' ]); } /** * Aturan validasi untuk seluruh file Excel (tidak digunakan karena tanpa header) * * @return array */ public function rules(): array { return []; } /** * Ukuran batch untuk insert * * @return int */ public function batchSize(): int { return 100; } /** * Ukuran chunk untuk membaca file * * @return int */ public function chunkSize(): int { return 100; } /** * Mendapatkan jumlah data yang berhasil diimpor * * @return int */ public function getImportedCount(): int { return $this->importedCount; } /** * Mendapatkan jumlah data yang berhasil dibuat * * @return int */ public function getCreatedCount(): int { return $this->createdCount; } /** * Mendapatkan jumlah data yang berhasil diupdate * * @return int */ public function getUpdatedCount(): int { return $this->updatedCount; } /** * Mendapatkan statistik lengkap import * * @return array */ public function getImportStatistics(): array { return [ 'total_processed' => $this->importedCount, 'created' => $this->createdCount, 'updated' => $this->updatedCount, 'skipped' => $this->skippedCount, 'errors' => count($this->errors) ]; } /** * Mendapatkan jumlah data yang dilewati * * @return int */ public function getSkippedCount(): int { return $this->skippedCount; } /** * Mendapatkan daftar error yang terjadi * * @return array */ public function getErrors(): array { return $this->errors; } }