0, 'duplicates' => 0, 'failed' => 0, 'errors' => []]; $csv = Reader::createFromPath($file->getRealPath(), 'r'); $csv->setHeaderOffset(0); // Strip UTF-8 BOM if present (Excel-exported CSV) $csv->setOutputBOM(''); try { $csv->addStreamFilter('convert.iconv.UTF-8/UTF-8'); } catch (\Throwable) {} foreach ($csv->getRecords() as $rowNum => $row) { $row = array_map('trim', $row); // Normalise header keys (lowercase, strip BOM) $row = array_combine( array_map(fn($k) => strtolower(preg_replace('/[\x{FEFF}\s]/u', '', $k)), array_keys($row)), array_values($row) ); $data = [ 'name' => $row['name'] ?? $row['nama'] ?? '', 'no_kp' => preg_replace('/[^0-9]/', '', $row['no_kp'] ?? $row['nokp'] ?? $row['ic'] ?? ''), 'email' => $row['email'] ?? $row['emel'] ?? null, 'phone' => $row['phone'] ?? $row['telefon'] ?? $row['phone'] ?? null, 'agency' => $row['agency'] ?? $row['agensi'] ?? $row['jabatan'] ?? null, ]; // Validate row $validator = Validator::make($data, [ 'name' => ['required', 'string', 'max:255'], 'no_kp' => ['required', 'digits:12'], 'email' => ['nullable', 'email', 'max:255'], 'phone' => ['nullable', 'string', 'max:20'], ]); if ($validator->fails()) { $result['failed']++; $result['errors'][] = 'Baris ' . ($rowNum + 2) . ': ' . implode(', ', $validator->errors()->all()); continue; } try { DB::transaction(function () use ($program, $data, $defaultSession, &$result) { // Find or create participant by no_kp $participant = Participant::firstOrCreate( ['no_kp' => $data['no_kp']], [ 'name' => $data['name'], 'email' => $data['email'] ?: null, 'phone' => $data['phone'] ?: null, 'agency' => $data['agency'] ?: null, 'participant_type' => 'staff', ] ); // Check duplicate in this program $exists = $program->programParticipants() ->where('participant_id', $participant->id) ->exists(); if ($exists) { $result['duplicates']++; return; } $program->programParticipants()->create([ 'participant_id' => $participant->id, 'registration_source' => 'import', 'is_pre_registered' => true, 'pre_registered_session' => $defaultSession, 'status' => 'registered', 'registered_at' => now(), ]); $result['success']++; }); } catch (\Throwable $e) { $result['failed']++; $result['errors'][] = 'Baris ' . ($rowNum + 2) . ': Ralat sistem — ' . $e->getMessage(); } } return $result; } }