election; $settings = $election instanceof Election ? $election->settings : null; if (! $settings instanceof ElectionSetting || ! $settings->is_attendance_active) { throw ValidationException::withMessages([ 'attendance' => 'Modul kehadiran belum diaktifkan untuk pilihanraya ini.', ]); } } public function ensurePpmCanRecord(User $actor, PusatMengundi $pusatMengundi): void { if (! $this->ppmScopeService->assignedPusatIds($actor)->contains((int) $pusatMengundi->id)) { throw ValidationException::withMessages([ 'pusat_mengundi_id' => 'PPM hanya boleh merekod kehadiran untuk Pusat Mengundi sendiri.', ]); } } /** * @param array $records */ public function recordBulk(PusatMengundi $pusatMengundi, User $actor, array $records): void { $this->ensureActive($pusatMengundi); $this->ensurePpmCanRecord($actor, $pusatMengundi); DB::transaction(function () use ($pusatMengundi, $actor, $records): void { $assignments = StaffAssignment::query() ->with(['position']) ->where('pusat_mengundi_id', $pusatMengundi->id) ->where('status', 'active') ->whereIn('id', array_keys($records)) ->get() ->keyBy('id'); foreach ($records as $assignmentId => $record) { /** @var StaffAssignment|null $assignment */ $assignment = $assignments->get((int) $assignmentId); if (! $assignment instanceof StaffAssignment) { continue; } $status = $record['status']; $checkInTime = $status === 'present' ? ($record['check_in_time'] ?? now()) : null; $attendance = Attendance::query()->updateOrCreate( [ 'election_id' => $assignment->election_id, 'staff_assignment_id' => $assignment->id, ], [ 'pusat_mengundi_id' => $assignment->pusat_mengundi_id, 'saluran_mengundi_id' => $assignment->saluran_mengundi_id, 'position_id' => $assignment->position_id, 'status' => $status, 'check_in_time' => $checkInTime, 'recorded_by_user_id' => $actor->id, 'note' => $record['note'] ?? null, ], ); activity('attendance') ->performedOn($attendance) ->causedBy($actor) ->withProperties([ 'staff_assignment_id' => $assignment->id, 'status' => $status, 'check_in_time' => $checkInTime, ]) ->log('attendance_updated_by_ppm'); } }); } }