Skip to content

Commit a553249

Browse files
authored
Merge pull request #3434 from codeeu/dev
Dev
2 parents 6e6cf2e + 84c5484 commit a553249

3 files changed

Lines changed: 111 additions & 45 deletions

File tree

app/Console/Commands/CertificateGenerateWindow.php

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,48 @@ class CertificateGenerateWindow extends Command
1010
{
1111
protected $signature = 'certificate:generate-window
1212
{--edition=2025 : Target edition year}
13-
{--type=excellence : excellence|super-organiser}
14-
{--limit=500 : Max recipients to process in this run}
13+
{--type=all : excellence|super-organiser|all}
14+
{--limit=500 : Max recipients to process per type in this run}
1515
{--include-failed : Include rows with previous generation errors}';
1616

17-
protected $description = 'Generate certificates in controlled windows (e.g. 500 at a time)';
17+
protected $description = 'Generate certificates in controlled windows (e.g. 500 at a time); use --type=all for both certs';
1818

1919
public function handle(): int
2020
{
2121
$edition = (int) $this->option('edition');
2222
$limit = max(1, (int) $this->option('limit'));
2323
$includeFailed = (bool) $this->option('include-failed');
2424
$typeInput = strtolower(trim((string) $this->option('type')));
25-
$type = match ($typeInput) {
26-
'excellence' => 'Excellence',
27-
'super-organiser', 'superorganiser' => 'SuperOrganiser',
28-
default => null,
29-
};
30-
31-
if ($type === null) {
32-
$this->error("Invalid --type value: {$typeInput}. Use 'excellence' or 'super-organiser'.");
25+
$types = $this->resolveTypes($typeInput);
26+
if ($types === null) {
27+
$this->error("Invalid --type value: {$typeInput}. Use 'excellence', 'super-organiser', or 'all'.");
3328
return self::FAILURE;
3429
}
3530

31+
$totalOk = 0;
32+
$totalFailed = 0;
33+
$any = false;
34+
35+
foreach ($types as $type) {
36+
$result = $this->generateWindowForType($edition, $type, $limit, $includeFailed);
37+
$totalOk += $result['ok'];
38+
$totalFailed += $result['failed'];
39+
if ($result['processed'] > 0) {
40+
$any = true;
41+
}
42+
}
43+
44+
$this->newLine();
45+
$this->info("Generate window(s) complete. Total success: {$totalOk}, Total failed: {$totalFailed}.");
46+
if ($any) {
47+
$this->line('Run the same command again to process the next batch.');
48+
}
49+
return self::SUCCESS;
50+
}
51+
52+
/** @return array{processed: int, ok: int, failed: int} */
53+
private function generateWindowForType(int $edition, string $type, int $limit, bool $includeFailed): array
54+
{
3655
$query = Excellence::query()
3756
->where('edition', $edition)
3857
->where('type', $type)
@@ -48,12 +67,13 @@ public function handle(): int
4867

4968
$rows = $query->get();
5069
if ($rows->isEmpty()) {
51-
$this->info('No pending recipients found for this window.');
52-
return self::SUCCESS;
70+
$this->line(" [{$type}] No pending recipients for this window.");
71+
return ['processed' => 0, 'ok' => 0, 'failed' => 0];
5372
}
5473

55-
$this->info("Generating {$rows->count()} {$type} certificates for edition {$edition}...");
74+
$this->info("[{$type}] Generating {$rows->count()} certificates (edition {$edition})...");
5675
$bar = $this->output->createProgressBar($rows->count());
76+
$bar->setFormat(" %current%/%max% [%bar%] %percent:3s%%");
5777
$bar->start();
5878

5979
$ok = 0;
@@ -97,10 +117,19 @@ public function handle(): int
97117
}
98118

99119
$bar->finish();
100-
$this->newLine(2);
101-
$this->info("Window complete. Success: {$ok}, Failed: {$failed}.");
102-
$this->line('Run the same command again to process the next window.');
120+
$this->newLine();
121+
$this->line(" [{$type}] Done. Success: {$ok}, Failed: {$failed}.");
122+
return ['processed' => $rows->count(), 'ok' => $ok, 'failed' => $failed];
123+
}
103124

104-
return self::SUCCESS;
125+
/** @return array<string>|null */
126+
private function resolveTypes(string $typeInput): ?array
127+
{
128+
return match ($typeInput) {
129+
'all' => ['Excellence', 'SuperOrganiser'],
130+
'excellence' => ['Excellence'],
131+
'super-organiser', 'superorganiser' => ['SuperOrganiser'],
132+
default => null,
133+
};
105134
}
106135
}

app/Console/Commands/CertificateSendWindow.php

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,48 @@ class CertificateSendWindow extends Command
1313
{
1414
protected $signature = 'certificate:send-window
1515
{--edition=2025 : Target edition year}
16-
{--type=excellence : excellence|super-organiser}
17-
{--limit=500 : Max recipients to send in this run}
16+
{--type=all : excellence|super-organiser|all}
17+
{--limit=500 : Max recipients to send per type in this run}
1818
{--include-send-failed : Include rows that previously failed sending}';
1919

20-
protected $description = 'Send certificate emails in controlled windows (e.g. 500 at a time)';
20+
protected $description = 'Send certificate emails in controlled windows (e.g. 500 at a time); use --type=all for both certs';
2121

2222
public function handle(): int
2323
{
2424
$edition = (int) $this->option('edition');
2525
$limit = max(1, (int) $this->option('limit'));
2626
$includeSendFailed = (bool) $this->option('include-send-failed');
2727
$typeInput = strtolower(trim((string) $this->option('type')));
28-
$type = match ($typeInput) {
29-
'excellence' => 'Excellence',
30-
'super-organiser', 'superorganiser' => 'SuperOrganiser',
31-
default => null,
32-
};
33-
34-
if ($type === null) {
35-
$this->error("Invalid --type value: {$typeInput}. Use 'excellence' or 'super-organiser'.");
28+
$types = $this->resolveTypes($typeInput);
29+
if ($types === null) {
30+
$this->error("Invalid --type value: {$typeInput}. Use 'excellence', 'super-organiser', or 'all'.");
3631
return self::FAILURE;
3732
}
3833

34+
$totalQueued = 0;
35+
$totalFailed = 0;
36+
$any = false;
37+
38+
foreach ($types as $type) {
39+
$result = $this->sendWindowForType($edition, $type, $limit, $includeSendFailed);
40+
$totalQueued += $result['queued'];
41+
$totalFailed += $result['failed'];
42+
if ($result['processed'] > 0) {
43+
$any = true;
44+
}
45+
}
46+
47+
$this->newLine();
48+
$this->info("Send window(s) complete. Total queued: {$totalQueued}, Total failed: {$totalFailed}.");
49+
if ($any) {
50+
$this->line('Run the same command again to process the next batch (or ensure queue worker is running: php artisan queue:work).');
51+
}
52+
return self::SUCCESS;
53+
}
54+
55+
/** @return array{processed: int, queued: int, failed: int} */
56+
private function sendWindowForType(int $edition, string $type, int $limit, bool $includeSendFailed): array
57+
{
3958
$query = Excellence::query()
4059
->where('edition', $edition)
4160
->where('type', $type)
@@ -51,12 +70,13 @@ public function handle(): int
5170

5271
$rows = $query->get();
5372
if ($rows->isEmpty()) {
54-
$this->info('No recipients found for this send window.');
55-
return self::SUCCESS;
73+
$this->line(" [{$type}] No recipients left for this window.");
74+
return ['processed' => 0, 'queued' => 0, 'failed' => 0];
5675
}
5776

58-
$this->info("Queueing {$rows->count()} {$type} emails for edition {$edition}...");
77+
$this->info("[{$type}] Queueing {$rows->count()} emails (edition {$edition})...");
5978
$bar = $this->output->createProgressBar($rows->count());
79+
$bar->setFormat(" %current%/%max% [%bar%] %percent:3s%%");
6080
$bar->start();
6181

6282
$queued = 0;
@@ -89,10 +109,19 @@ public function handle(): int
89109
}
90110

91111
$bar->finish();
92-
$this->newLine(2);
93-
$this->info("Send window complete. Queued: {$queued}, Failed: {$failed}.");
94-
$this->line('Run again to process the next send window.');
112+
$this->newLine();
113+
$this->line(" [{$type}] Done. Queued: {$queued}, Failed: {$failed}.");
114+
return ['processed' => $rows->count(), 'queued' => $queued, 'failed' => $failed];
115+
}
95116

96-
return self::SUCCESS;
117+
/** @return array<string>|null */
118+
private function resolveTypes(string $typeInput): ?array
119+
{
120+
return match ($typeInput) {
121+
'all' => ['Excellence', 'SuperOrganiser'],
122+
'excellence' => ['Excellence'],
123+
'super-organiser', 'superorganiser' => ['SuperOrganiser'],
124+
default => null,
125+
};
97126
}
98127
}

app/Console/Commands/CertificateStats.php

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class CertificateStats extends Command
1212
{
1313
protected $signature = 'certificate:stats
1414
{--edition=2025 : Target edition year}
15-
{--type=excellence : excellence|super-organiser}
15+
{--type=all : excellence|super-organiser|all}
1616
{--sample=10 : Number of sample user IDs to show for missing/extra}';
1717

1818
protected $description = 'Show diagnostic counts for certificate backend recipients and processing status';
@@ -23,12 +23,20 @@ public function handle(): int
2323
$typeOption = (string) $this->option('type');
2424
$sampleSize = max(0, (int) $this->option('sample'));
2525

26-
$normalizedType = $this->normalizeType($typeOption);
27-
if ($normalizedType === null) {
28-
$this->error("Invalid --type value: {$typeOption}. Use 'excellence' or 'super-organiser'.");
26+
$types = $this->resolveTypes($typeOption);
27+
if ($types === null) {
28+
$this->error("Invalid --type value: {$typeOption}. Use 'excellence', 'super-organiser', or 'all'.");
2929
return self::FAILURE;
3030
}
3131

32+
foreach ($types as $normalizedType) {
33+
$this->outputStatsForType($edition, $normalizedType, $sampleSize);
34+
}
35+
return self::SUCCESS;
36+
}
37+
38+
private function outputStatsForType(int $edition, string $normalizedType, int $sampleSize): void
39+
{
3240
$eligibleUserIds = $this->eligibleUserIds($edition, $normalizedType);
3341
$eligibleUserIds = array_values(array_unique(array_filter(array_map('intval', $eligibleUserIds))));
3442

@@ -91,16 +99,16 @@ public function handle(): int
9199
$this->line('Sample missing user IDs: ' . $this->sampleCsv($missingUserIds, $sampleSize));
92100
$this->line('Sample extra user IDs: ' . $this->sampleCsv($extraUserIds, $sampleSize));
93101
}
94-
95-
return self::SUCCESS;
96102
}
97103

98-
private function normalizeType(string $typeOption): ?string
104+
/** @return array<string>|null */
105+
private function resolveTypes(string $typeOption): ?array
99106
{
100107
$slug = strtolower(trim($typeOption));
101108
return match ($slug) {
102-
'excellence' => 'Excellence',
103-
'super-organiser', 'superorganiser' => 'SuperOrganiser',
109+
'all' => ['Excellence', 'SuperOrganiser'],
110+
'excellence' => ['Excellence'],
111+
'super-organiser', 'superorganiser' => ['SuperOrganiser'],
104112
default => null,
105113
};
106114
}

0 commit comments

Comments
 (0)