diff --git a/app/Console/Commands/CertificateGenerateWindow.php b/app/Console/Commands/CertificateGenerateWindow.php new file mode 100644 index 000000000..e3b248b50 --- /dev/null +++ b/app/Console/Commands/CertificateGenerateWindow.php @@ -0,0 +1,106 @@ +option('edition'); + $limit = max(1, (int) $this->option('limit')); + $includeFailed = (bool) $this->option('include-failed'); + $typeInput = strtolower(trim((string) $this->option('type'))); + $type = match ($typeInput) { + 'excellence' => 'Excellence', + 'super-organiser', 'superorganiser' => 'SuperOrganiser', + default => null, + }; + + if ($type === null) { + $this->error("Invalid --type value: {$typeInput}. Use 'excellence' or 'super-organiser'."); + return self::FAILURE; + } + + $query = Excellence::query() + ->where('edition', $edition) + ->where('type', $type) + ->where(function ($q) use ($includeFailed) { + $q->whereNull('certificate_url'); + if (! $includeFailed) { + $q->whereNull('certificate_generation_error'); + } + }) + ->with('user') + ->orderBy('id') + ->limit($limit); + + $rows = $query->get(); + if ($rows->isEmpty()) { + $this->info('No pending recipients found for this window.'); + return self::SUCCESS; + } + + $this->info("Generating {$rows->count()} {$type} certificates for edition {$edition}..."); + $bar = $this->output->createProgressBar($rows->count()); + $bar->start(); + + $ok = 0; + $failed = 0; + + foreach ($rows as $excellence) { + $bar->advance(); + $user = $excellence->user; + if (! $user) { + $failed++; + $excellence->update(['certificate_generation_error' => 'User missing.']); + continue; + } + + $name = $excellence->name_for_certificate ?? trim(($user->firstname ?? '') . ' ' . ($user->lastname ?? '')); + $name = $name !== '' ? $name : 'Unknown'; + $certType = $type === 'SuperOrganiser' ? 'super-organiser' : 'excellence'; + $numberOfActivities = $type === 'SuperOrganiser' ? (int) $user->activities($edition) : 0; + + try { + $cert = new CertificateExcellence( + $edition, + $name, + $certType, + $numberOfActivities, + (int) $user->id, + (string) ($user->email ?? '') + ); + $url = $cert->generate(); + $excellence->update([ + 'certificate_url' => $url, + 'certificate_generation_error' => null, + ]); + $ok++; + } catch (\Throwable $e) { + $failed++; + $excellence->update([ + 'certificate_generation_error' => $e->getMessage(), + ]); + } + } + + $bar->finish(); + $this->newLine(2); + $this->info("Window complete. Success: {$ok}, Failed: {$failed}."); + $this->line('Run the same command again to process the next window.'); + + return self::SUCCESS; + } +} diff --git a/app/Console/Commands/CertificateSendWindow.php b/app/Console/Commands/CertificateSendWindow.php new file mode 100644 index 000000000..67d240f48 --- /dev/null +++ b/app/Console/Commands/CertificateSendWindow.php @@ -0,0 +1,98 @@ +option('edition'); + $limit = max(1, (int) $this->option('limit')); + $includeSendFailed = (bool) $this->option('include-send-failed'); + $typeInput = strtolower(trim((string) $this->option('type'))); + $type = match ($typeInput) { + 'excellence' => 'Excellence', + 'super-organiser', 'superorganiser' => 'SuperOrganiser', + default => null, + }; + + if ($type === null) { + $this->error("Invalid --type value: {$typeInput}. Use 'excellence' or 'super-organiser'."); + return self::FAILURE; + } + + $query = Excellence::query() + ->where('edition', $edition) + ->where('type', $type) + ->where(function ($q) use ($includeSendFailed) { + $q->whereNull('notified_at'); + if ($includeSendFailed) { + $q->orWhereNotNull('certificate_sent_error'); + } + }) + ->with('user') + ->orderBy('id') + ->limit($limit); + + $rows = $query->get(); + if ($rows->isEmpty()) { + $this->info('No recipients found for this send window.'); + return self::SUCCESS; + } + + $this->info("Queueing {$rows->count()} {$type} emails for edition {$edition}..."); + $bar = $this->output->createProgressBar($rows->count()); + $bar->start(); + + $queued = 0; + $failed = 0; + + foreach ($rows as $excellence) { + $bar->advance(); + $user = $excellence->user; + if (! $user || ! $user->email) { + $failed++; + $excellence->update(['certificate_sent_error' => 'No user or email']); + continue; + } + + try { + if ($type === 'SuperOrganiser') { + Mail::to($user->email)->queue(new NotifySuperOrganiser($user, $edition, $excellence->certificate_url)); + } else { + Mail::to($user->email)->queue(new NotifyWinner($user, $edition, $excellence->certificate_url)); + } + $excellence->update([ + 'notified_at' => Carbon::now(), + 'certificate_sent_error' => null, + ]); + $queued++; + } catch (\Throwable $e) { + $failed++; + $excellence->update(['certificate_sent_error' => $e->getMessage()]); + } + } + + $bar->finish(); + $this->newLine(2); + $this->info("Send window complete. Queued: {$queued}, Failed: {$failed}."); + $this->line('Run again to process the next send window.'); + + return self::SUCCESS; + } +}