first
This commit is contained in:
180
app/Services/Admin/PusatImportService.php
Normal file
180
app/Services/Admin/PusatImportService.php
Normal file
@@ -0,0 +1,180 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user