181 lines
6.5 KiB
PHP
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;
|
|
}
|
|
}
|