firstOrFail(); $sesi->load('dun'); if (! $sesi->is_active) { return view('checkin.closed', compact('sesi')); } return view('checkin.form', compact('sesi')); } /** * Sahkan no KP dengan SPPM dan rekod kehadiran. */ public function submit(Request $request, string $token) { $sesi = SesiTaklimat::where('qr_token', $token)->firstOrFail(); $sesi->load('dun'); if (! $sesi->is_active) { return view('checkin.closed', compact('sesi')); } $request->validate( ['no_kp' => ['required', 'string', 'max:20']], ['no_kp.required' => 'Sila masukkan nombor kad pengenalan anda.'], ); $noKp = preg_replace('/[^0-9A-Za-z]/', '', $request->input('no_kp')); // Semak dalam SPPM: no_kp padan + status 'assigned' + DUN yang betul $petugas = PetugasPermohonan::assignedUntukDun($sesi->dun_id) ->whereRaw("REPLACE(REPLACE(no_kp, '-', ''), ' ', '') = ?", [$noKp]) ->with(['assignments' => fn ($q) => $q->orderBy('urutan')->with(['pusatMengundi', 'saluran', 'jawatan'])]) ->first(); if (! $petugas) { return back()->withErrors([ 'no_kp' => 'Maaf, rekod anda tidak ditemui sebagai petugas bertugas (assigned) untuk ' . ($sesi->dun->nama ?? 'DUN ini') . '. Sila semak nombor kad pengenalan atau rujuk kaunter pendaftaran.', ])->onlyInput('no_kp'); } // Pernah hadir di sesi taklimat LAIN? Taklimat ulangan hanya untuk // petugas yang belum pernah hadir langsung — tak perlu rekod semula. $rekodSesiLain = KehadiranTaklimat::where('permohonan_id', $petugas->id) ->where('sesi_taklimat_id', '!=', $sesi->id) ->with('sesi.dun') ->orderBy('hadir_at') ->first(); if ($rekodSesiLain) { return view('checkin.pernah-hadir', [ 'sesi' => $sesi, 'petugas' => $petugas, 'kehadiranSebelumIni' => $rekodSesiLain, 'sesiSebelumIni' => $rekodSesiLain->sesi, ]); } // Jika sudah daftar dalam sesi ini, papar semula slip sedia ada $kehadiran = KehadiranTaklimat::where('sesi_taklimat_id', $sesi->id) ->where('permohonan_id', $petugas->id) ->first(); $sudahDaftar = (bool) $kehadiran; if (! $kehadiran) { $kehadiran = $this->rekodKehadiran($sesi, $petugas, $noKp); } return view('checkin.slip', [ 'sesi' => $sesi, 'petugas' => $petugas, 'kehadiran' => $kehadiran, 'penempatan' => $petugas->butiranPenempatan(), 'sudahDaftar' => $sudahDaftar, ]); } /** * Jana nombor siri seterusnya dan simpan kehadiran secara atomik. */ private function rekodKehadiran(SesiTaklimat $sesi, PetugasPermohonan $petugas, string $noKp): KehadiranTaklimat { $attempts = 0; while (true) { try { return DB::transaction(function () use ($sesi, $petugas, $noKp) { $nomborSeterusnya = (int) KehadiranTaklimat::where('sesi_taklimat_id', $sesi->id) ->lockForUpdate() ->max('nombor_siri') + 1; return KehadiranTaklimat::create([ 'sesi_taklimat_id' => $sesi->id, 'permohonan_id' => $petugas->id, 'no_kp' => $noKp, 'nombor_siri' => $nomborSeterusnya, 'hadir_at' => now(), ]); }); } catch (UniqueConstraintViolationException $e) { // Hantar berganda: rekod petugas ini mungkin sudah wujud $sediaAda = KehadiranTaklimat::where('sesi_taklimat_id', $sesi->id) ->where('permohonan_id', $petugas->id) ->first(); if ($sediaAda) { return $sediaAda; } // Perlumbaan nombor siri — cuba semula sehingga 3 kali if (++$attempts >= 3) { throw $e; } } } } }