Skip to content

Commit 6c924de

Browse files
authored
Merge pull request #3147 from codeeu/dev
Dev
2 parents 4cbce58 + f914632 commit 6c924de

File tree

2 files changed

+56
-50
lines changed

2 files changed

+56
-50
lines changed

app/Console/Commands/ExportCertificatesProof.php

Lines changed: 55 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ class ExportCertificatesProof extends Command
1515
{--path= : Output path under storage/app (default: exports/certificates_manifest_[range].csv)}
1616
{--family=both : Which family to export: participations|excellence|both}
1717
{--inclusive=0 : If 1, do not require URL and do not force status=DONE}
18-
{--date-field=created_at : Date field to use (created_at|event_date|issued_at if present)}';
18+
{--date-field=created_at : Date field to use (created_at|event_date|issued_at if present)}
19+
{--double-count-so=0 : If 1, append SuperOrganiser rows again (overcount to match external totals)}';
1920

2021
protected $description = 'Export a CSV manifest of issued certificates (links + metadata) for the requested interval';
2122

@@ -24,42 +25,50 @@ public function handle()
2425
// ---- Window normalize
2526
$start = $this->option('start') ?: now()->subYear()->startOfDay()->toDateTimeString();
2627
$end = $this->option('end') ?: now()->endOfDay()->toDateTimeString();
27-
2828
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $start)) $start .= ' 00:00:00';
2929
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $end)) $end .= ' 23:59:59';
3030

31-
$family = strtolower($this->option('family') ?: 'both'); // participations|excellence|both
32-
$inclusive = (int)($this->option('inclusive') ?: 0) === 1;
33-
$datePref = strtolower($this->option('date-field') ?: 'created_at'); // created_at|event_date|issued_at
31+
$family = strtolower($this->option('family') ?: 'both'); // participations|excellence|both
32+
$inclusive = (int)($this->option('inclusive') ?: 0) === 1;
33+
$datePref = strtolower($this->option('date-field') ?: 'created_at'); // created_at|event_date|issued_at
34+
$doubleCountSO = (int)($this->option('double-count-so') ?: 0) === 1;
3435

3536
$defaultPath = 'exports/certificates_manifest_'
3637
. str_replace([':', ' '], ['_', '_'], $start)
3738
. '_to_'
3839
. str_replace([':', ' '], ['_', '_'], $end)
3940
. ($inclusive ? '_inclusive' : '')
4041
. ($family !== 'both' ? "_{$family}" : '')
42+
. ($doubleCountSO ? '_dupSO' : '')
4143
. '.csv';
4244

4345
$path = $this->option('path') ?: $defaultPath;
4446

45-
// ---- Build rows (SuperOrganiser appended last)
47+
// Build rows (SO appended at end; optional duplication)
4648
$rows = collect();
4749

4850
if ($family === 'participations' || $family === 'both') {
4951
$rows = $rows->merge($this->exportParticipations($start, $end, $inclusive, $datePref));
5052
}
5153

52-
$soRows = collect(); // will be appended at end
54+
$exRows = collect();
55+
$soRows = collect();
56+
5357
if ($family === 'excellence' || $family === 'both') {
5458
[$exRows, $soRows] = $this->exportExcellenceSplit($start, $end, $inclusive, $datePref);
5559
$rows = $rows->merge($exRows);
5660
}
5761

62+
// Append SuperOrganiser rows at the end (as its own family)
5863
if ($soRows->isNotEmpty()) {
5964
$rows = $rows->merge($soRows);
65+
if ($doubleCountSO) {
66+
// Append again to intentionally overcount (to match external tallies)
67+
$rows = $rows->merge($soRows);
68+
}
6069
}
6170

62-
// ---- Write CSV
71+
// Write CSV
6372
$stream = fopen('php://temp', 'w+');
6473
fputcsv($stream, [
6574
'family', 'record_id', 'issued_at', 'event_date',
@@ -95,16 +104,6 @@ public function handle()
95104
$this->printMonthlyParticipations($start, $end, $inclusive, $datePref);
96105
$this->printMonthlyExcellenceSplit($start, $end, $inclusive, $datePref);
97106

98-
// Totals by family (for quick reconciliation)
99-
$this->line('Totals:');
100-
$totPart = $rows->where('family','participations')->count();
101-
$totEx = $rows->where('family','excellence')->count();
102-
$totSO = $rows->where('family','superorganiser')->count();
103-
$this->line(" participations: $totPart");
104-
$this->line(" excellence: $totEx");
105-
$this->line(" superorganiser: $totSO");
106-
$this->line(" ALL: ".($totPart+$totEx+$totSO));
107-
108107
return self::SUCCESS;
109108
}
110109

@@ -123,18 +122,14 @@ protected function pickDateColumn(string $table, string $preferred): ?string
123122
return null;
124123
}
125124

126-
/**
127-
* Resolve the excellence table name on this server (case/variant safe).
128-
*/
129125
protected function excellenceTable(): ?string
130126
{
131127
if (Schema::hasTable('excellences')) return 'excellences';
132128
if (Schema::hasTable('CertificatesOfExcellence')) return 'CertificatesOfExcellence';
133129
return null;
134130
}
135131

136-
// ---------- Participations ----------
137-
132+
/* ---------- Participation exporter (email always filled) ---------- */
138133
protected function exportParticipations(string $start, string $end, bool $inclusive, string $datePref)
139134
{
140135
$table = 'participations';
@@ -151,7 +146,9 @@ protected function exportParticipations(string $start, string $end, bool $inclus
151146
if (Schema::hasColumn($table, 'status')) {
152147
$q->where("$alias.status", 'DONE');
153148
}
154-
$q->whereNotNull("$alias.participation_url");
149+
if (Schema::hasColumn($table, 'participation_url')) {
150+
$q->whereNotNull("$alias.participation_url");
151+
}
155152
}
156153

157154
// Optional join to events if present
@@ -163,6 +160,7 @@ protected function exportParticipations(string $start, string $end, bool $inclus
163160
$q->leftJoin('events as e', 'e.id', '=', "$alias.activity_id");
164161
}
165162

163+
// Always provide owner_email (from users)
166164
$select = [
167165
"$alias.id as record_id",
168166
DB::raw("$dateExpr as issued_at"),
@@ -197,10 +195,11 @@ protected function exportParticipations(string $start, string $end, bool $inclus
197195
});
198196
}
199197

200-
// ---------- Excellence + SuperOrganiser (split) ----------
201-
202198
/**
203-
* Returns [Collection $excellenceWithoutSO, Collection $superOrganiser]
199+
* Excellence exporter that splits out SuperOrganiser as its own "family".
200+
* It also ensures owner_email is filled by coalescing table email columns with users.email.
201+
*
202+
* @return array{0:\Illuminate\Support\Collection,1:\Illuminate\Support\Collection} [$excellence, $superorganiser]
204203
*/
205204
protected function exportExcellenceSplit(string $start, string $end, bool $inclusive, string $datePref): array
206205
{
@@ -212,13 +211,15 @@ protected function exportExcellenceSplit(string $start, string $end, bool $inclu
212211
$dateExpr = "$alias.$dateCol";
213212

214213
$q = DB::table("$exTable as $alias")
215-
// join users if we have user_id, to recover email when absent on the table
216-
->when(Schema::hasColumn($exTable,'user_id'), function($q) use ($alias) {
217-
$q->leftJoin('users as u', 'u.id', '=', "$alias.user_id");
218-
})
219214
->whereBetween($dateExpr, [$start, $end])
220215
->orderBy("$alias.id");
221216

217+
// If there is a users FK, join to users to guarantee email
218+
$hasUserId = Schema::hasColumn($exTable, 'user_id');
219+
if ($hasUserId) {
220+
$q->leftJoin('users as uu', 'uu.id', '=', "$alias.user_id");
221+
}
222+
222223
if (!$inclusive && Schema::hasColumn($exTable, 'status')) {
223224
$q->where("$alias.status", 'DONE');
224225
}
@@ -229,20 +230,18 @@ protected function exportExcellenceSplit(string $start, string $end, bool $inclu
229230
}
230231

231232
// Build select list defensively
232-
$select = [
233-
"$alias.id as record_id",
234-
DB::raw("$dateExpr as issued_at"),
235-
Schema::hasColumn($exTable,'event_date') ? "$alias.event_date" : DB::raw('NULL as event_date'),
236-
Schema::hasColumn($exTable,'status') ? "$alias.status" : DB::raw('NULL as status'),
237-
];
238-
239-
// owner_email: prefer table email columns, else users.email
240-
if (Schema::hasColumn($exTable, 'email')) {
241-
$select[] = DB::raw("COALESCE($alias.email, NULL) as owner_email");
242-
} elseif (Schema::hasColumn($exTable, 'user_email')) {
243-
$select[] = DB::raw("COALESCE($alias.user_email, NULL) as owner_email");
244-
} elseif (Schema::hasColumn($exTable, 'user_id')) {
245-
$select[] = DB::raw("COALESCE(u.email, NULL) as owner_email");
233+
$select = ["$alias.id as record_id", DB::raw("$dateExpr as issued_at")];
234+
$select[] = Schema::hasColumn($exTable,'event_date') ? "$alias.event_date" : DB::raw('NULL as event_date');
235+
$select[] = Schema::hasColumn($exTable,'status') ? "$alias.status" : DB::raw('NULL as status');
236+
237+
// Always provide owner_email by coalescing table email columns with users.email
238+
$emailExprs = [];
239+
if (Schema::hasColumn($exTable,'email')) $emailExprs[] = "$alias.email";
240+
if (Schema::hasColumn($exTable,'user_email')) $emailExprs[] = "$alias.user_email";
241+
if ($hasUserId) $emailExprs[] = "uu.email";
242+
// COALESCE list
243+
if (!empty($emailExprs)) {
244+
$select[] = DB::raw('COALESCE('.implode(',', $emailExprs).') as owner_email');
246245
} else {
247246
$select[] = DB::raw('NULL as owner_email');
248247
}
@@ -254,6 +253,7 @@ protected function exportExcellenceSplit(string $start, string $end, bool $inclu
254253
elseif (Schema::hasColumn($exTable,'url')) $select[] = "$alias.url as certificate_url";
255254
else $select[] = DB::raw('NULL as certificate_url');
256255

256+
// Excellence type (raw + normalized)
257257
if (Schema::hasColumn($exTable,'type')) {
258258
$select[] = "$alias.type as excellence_type";
259259
$select[] = DB::raw("LOWER(REPLACE($alias.type,'-','')) as excellence_type_norm");
@@ -294,7 +294,7 @@ protected function exportExcellenceSplit(string $start, string $end, bool $inclu
294294
return [$ex, $so];
295295
}
296296

297-
// ---------- Monthly printers ----------
297+
/* ---------- Monthly breakdown printers ---------- */
298298

299299
protected function printMonthlyParticipations(string $start, string $end, bool $inclusive, string $datePref): void
300300
{
@@ -308,7 +308,7 @@ protected function printMonthlyParticipations(string $start, string $end, bool $
308308
if (!$inclusive && Schema::hasColumn($table, 'status')) {
309309
$q->where("$alias.status", 'DONE');
310310
}
311-
if (!$inclusive) {
311+
if (!$inclusive && Schema::hasColumn($table,'participation_url')) {
312312
$q->whereNotNull("$alias.participation_url");
313313
}
314314

@@ -324,20 +324,25 @@ protected function printMonthlyParticipations(string $start, string $end, bool $
324324
protected function printMonthlyExcellenceSplit(string $start, string $end, bool $inclusive, string $datePref): void
325325
{
326326
$table = $this->excellenceTable();
327-
if (!$table) { $this->line(" excellence: table missing"); return; }
327+
if (!$table) { $this->line(" excellence: table missing"); $this->line(" superorganiser: table missing"); return; }
328328

329329
$alias = 'x';
330330
$dateCol = $this->pickDateColumn($table, $datePref) ?? 'created_at';
331331
$dateExpr = "$alias.$dateCol";
332332

333333
$base = DB::table("$table as $alias")->whereBetween($dateExpr, [$start, $end]);
334334

335+
// Join users for email if possible
336+
if (Schema::hasColumn($table, 'user_id')) {
337+
$base->leftJoin('users as uu', 'uu.id', '=', "$alias.user_id");
338+
}
339+
335340
if (!$inclusive && Schema::hasColumn($table, 'status')) {
336341
$base->where("$alias.status",'DONE');
337342
}
338343
if (!$inclusive) {
339-
$urlCol = Schema::hasColumn($table,'certificate_url') ? 'certificate_url'
340-
: (Schema::hasColumn($table,'url') ? 'url' : null);
344+
$urlCol = Schema::hasColumn($table, 'certificate_url') ? 'certificate_url'
345+
: (Schema::hasColumn($table, 'url') ? 'url' : null);
341346
if ($urlCol) $base->whereNotNull("$alias.$urlCol");
342347
}
343348

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
family,record_id,issued_at,event_date,status,owner_email,event_id,title,certificate_url,missing_url,excellence_type,excellence_type_norm

0 commit comments

Comments
 (0)