Files
prn2026/app/Services/Admin/PusatImportService.php
2026-06-03 08:51:22 +08:00

181 lines
6.5 KiB
PHP

<?php
namespace App\Services\Admin;
use App\Models\Dun;
use App\Models\Election;
use App\Models\Position;
use App\Models\PositionQuota;
use App\Models\PusatMengundi;
use App\Models\SaluranMengundi;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use OpenSpout\Reader\XLSX\Reader;
class PusatImportService
{
// Column indexes (0-based) in the XLSX
private const COL_NAMA_PUSAT = 0;
private const COL_NAMA_PPM = 1;
private const COL_SALURAN = 2;
// 3 KTM, 4 POLIS, 5 KP — formula columns, computed from saluran count
private const COL_PPM_COUNT = 6;
// 7 KPDP — computed from saluran count
private const COL_PAPM = 8;
private const COL_JKM = 9;
private const COL_KKM = 10;
// 11 TAMBAHAN — computed from saluran count
private const COL_PPM_PHONE = 12;
/**
* @return array{imported: int, salurans: int, errors: list<string>}
*/
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<list<mixed>>
*/
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;
}
}