} */ public function import(Election $election, Dun $dun, UploadedFile $file): array { $rows = $this->parseXlsx($file->getRealPath()); $errors = []; $imported = 0; $saluranTotal = 0; DB::transaction(function () use ($election, $dun, $rows, &$imported, &$saluranTotal, &$errors): void { // Replace: delete all existing pusat for this DUN (cascade to saluran + quotas). PusatMengundi::query() ->where('dun_id', $dun->id) ->each(function (PusatMengundi $pusat): void { $pusat->saluranMengundis()->delete(); $pusat->positionQuotas()->delete(); $pusat->delete(); }); $positions = Position::query()->get()->keyBy('code'); foreach ($rows as $index => $row) { $rowNum = $index + 2; // +2: 1-based + skip header $namaPusat = trim((string) ($row[self::COL_NAMA_PUSAT] ?? '')); if ($namaPusat === '') { continue; // skip empty rows } $saluranCount = (int) ($row[self::COL_SALURAN] ?? 0); if ($saluranCount < 1) { $errors[] = "Baris {$rowNum}: Bilangan saluran mesti sekurang-kurangnya 1 untuk '{$namaPusat}'."; continue; } $code = sprintf('PM%03d', $imported + 1); $pusat = PusatMengundi::query()->create([ 'election_id' => $election->id, 'dun_id' => $dun->id, 'public_uuid' => (string) Str::uuid(), 'code' => $code, 'name' => $namaPusat, 'address' => null, 'ppm_name' => trim((string) ($row[self::COL_NAMA_PPM] ?? '')), 'ppm_phone' => trim((string) ($row[self::COL_PPM_PHONE] ?? '')), 'registration_url' => null, 'qr_code_path' => null, ]); // Generate QR code for this pusat app(PusatQrCodeService::class)->generate($pusat); // Create numbered salurans $salurans = []; for ($n = 1; $n <= $saluranCount; $n++) { $salurans[] = SaluranMengundi::query()->create([ 'election_id' => $election->id, 'pusat_mengundi_id' => $pusat->id, 'number' => (string) $n, 'name' => null, 'voter_count' => null, ]); } $saluranTotal += $saluranCount; // Pusat-level quotas $pusatQuotas = [ 'PPM' => (int) ($row[self::COL_PPM_COUNT] ?? 1), 'PAPM' => (int) ($row[self::COL_PAPM] ?? 0), 'JKM' => (int) ($row[self::COL_JKM] ?? 1), 'KKM' => (int) ($row[self::COL_KKM] ?? 2), ]; foreach ($pusatQuotas as $code => $quota) { $position = $positions->get($code); if ($position) { PositionQuota::query()->create([ 'election_id' => $election->id, 'pusat_mengundi_id' => $pusat->id, 'saluran_mengundi_id' => null, 'position_id' => $position->id, 'quota' => $quota, ]); } } // Saluran-level quotas (standard per saluran) $saluranQuotaCodes = [ 'KTM' => 1, 'POLIS' => 1, 'KP' => 4, 'KPDP' => 1, 'CALON_TAMBAHAN' => 1, ]; foreach ($salurans as $saluran) { foreach ($saluranQuotaCodes as $posCode => $quota) { $position = $positions->get($posCode); if ($position) { PositionQuota::query()->create([ 'election_id' => $election->id, 'pusat_mengundi_id' => $pusat->id, 'saluran_mengundi_id' => $saluran->id, 'position_id' => $position->id, 'quota' => $quota, ]); } } } $imported++; } }); return [ 'imported' => $imported, 'salurans' => $saluranTotal, 'errors' => $errors, ]; } /** * @return list> */ private function parseXlsx(string $path): array { $reader = new Reader(); $reader->open($path); $rows = []; $isHeader = true; foreach ($reader->getSheetIterator() as $sheet) { foreach ($sheet->getRowIterator() as $row) { if ($isHeader) { $isHeader = false; continue; } $rows[] = $row->toArray(); } break; // only first sheet } $reader->close(); return $rows; } }