certificates() ->with('participant') ->latest() ->paginate(50); $stats = [ 'total' => $program->certificates()->count(), 'generated' => $program->certificates()->whereIn('status', ['generated', 'emailed', 'downloaded'])->count(), 'pending' => $program->certificates()->where('status', 'pending')->count(), 'failed' => $program->certificates()->where('status', 'failed')->count(), 'emailed' => $program->certificates()->where('status', 'emailed')->count(), ]; return view('admin.programs.certificates.index', compact('program', 'certificates', 'stats')); } public function generateAll(Request $request, Program $program): RedirectResponse { $template = $program->certificateTemplate; if (! $template) { return back()->with('error', 'Template sijil belum ditetapkan untuk program ini.'); } // Find all attended participants without certificates $existingIds = $program->certificates()->pluck('participant_id')->toArray(); $attended = $program->attendances() ->whereNotIn('participant_id', $existingIds) ->get(); if ($attended->isEmpty() && $program->certificates()->count() === 0) { return back()->with('error', 'Tiada peserta yang hadir untuk dijana sijil.'); } $sequence = $program->certificates()->count(); $created = 0; foreach ($attended as $attendance) { $sequence++; $cert = Certificate::firstOrCreate( ['program_id' => $program->id, 'participant_id' => $attendance->participant_id], [ 'certificate_template_id' => $template->id, 'certificate_no' => $this->buildCertNo($program, $sequence), 'status' => 'pending', ] ); if ($cert->wasRecentlyCreated || $cert->status === 'failed') { $cert->update(['certificate_template_id' => $template->id, 'status' => 'pending']); GenerateCertificateJob::dispatch($cert); $created++; } } // Re-queue failed certificates $failed = $program->certificates()->where('status', 'failed')->get(); foreach ($failed as $cert) { $cert->update(['status' => 'pending', 'error_message' => null]); GenerateCertificateJob::dispatch($cert); $created++; } return back()->with('success', "Penjanaan sijil telah diantri untuk {$created} peserta."); } public function emailAll(Program $program): RedirectResponse { $toEmail = $program->certificates() ->whereIn('status', ['generated']) ->whereNull('emailed_at') ->count(); if ($toEmail === 0) { return back()->with('error', 'Tiada sijil yang sedia untuk dihantar emel.'); } // Dispatch email blast job — implemented in Fasa 8 \App\Jobs\SendCertificateEmailJob::dispatchBatch($program); return back()->with('success', "Penghantaran emel sijil dijadualkan untuk {$toEmail} peserta."); } private function buildCertNo(Program $program, int $seq): string { $year = now()->format('Y'); $prefix = strtoupper(substr(preg_replace('/[^A-Za-z]/', '', $program->title), 0, 4)); return sprintf('%s/%s/%04d', $prefix ?: 'ECT', $year, $seq); } }