diff --git a/app/Console/Commands/MatchmakingApplyDeaUpdateJuly2026.php b/app/Console/Commands/MatchmakingApplyDeaUpdateJuly2026.php new file mode 100644 index 000000000..12be7f49a --- /dev/null +++ b/app/Console/Commands/MatchmakingApplyDeaUpdateJuly2026.php @@ -0,0 +1,130 @@ +option('dry-run'); + + $cityLabs = MatchmakingProfile::query() + ->where(function ($q) { + $q->whereRaw('LOWER(TRIM(COALESCE(organisation_name, \'\'))) = ?', ['citylab']) + ->orWhereRaw('LOWER(TRIM(COALESCE(organisation_name, \'\'))) like ?', ['citylab%']) + ->orWhereRaw('LOWER(TRIM(COALESCE(organisation_name, \'\'))) like ?', ['% citylab%']) + ->orWhereRaw('LOWER(TRIM(COALESCE(email, \'\'))) like ?', ['%@citylab.gr']); + }) + ->where('type', MatchmakingProfile::TYPE_ORGANISATION) + ->get(); + + $this->info('Existing CityLab organisation profiles: '.$cityLabs->count()); + foreach ($cityLabs as $profile) { + $this->line(sprintf( + ' #%d %s <%s> slug=%s', + $profile->id, + $profile->organisation_name, + $profile->email, + $profile->slug + )); + } + + if (! $dryRun) { + foreach ($cityLabs as $profile) { + $profile->delete(); + $this->warn("Removed CityLab organisation #{$profile->id}"); + } + } else { + $this->line('[DRY] Would remove '.$cityLabs->count().' CityLab organisation profile(s)'); + } + + if (! $this->option('skip-import')) { + $indiv = base_path('database/seeders/data/matchmaking/sophia-drakaki-individual.xlsx'); + $org = base_path('database/seeders/data/matchmaking/citylab-organisation-updated.xlsx'); + + foreach ([$indiv, $org] as $path) { + if (! is_file($path)) { + $this->error("Missing import file: {$path}"); + + return self::FAILURE; + } + } + + if ($dryRun) { + $this->line('[DRY] Would import: '.$org); + $this->line('[DRY] Would import: '.$indiv); + } else { + // Organisation first, then individual (same org name must not collide). + Log::info('[MatchmakingApplyDeaUpdateJuly2026] Importing updated CityLab'); + Excel::import(new MatchmakingProfileImport, $org); + $this->info('Imported updated CityLab organisation registration'); + + Log::info('[MatchmakingApplyDeaUpdateJuly2026] Importing Sophia Drakaki'); + Excel::import(new MatchmakingProfileImport, $indiv); + $this->info('Imported Sophia Drakaki individual registration'); + } + } + + $targets = MatchmakingProfile::query() + ->where(function ($q) { + $q->where(function ($inner) { + $inner->where('type', MatchmakingProfile::TYPE_VOLUNTEER) + ->whereRaw('LOWER(TRIM(last_name)) = ?', ['drakaki']); + })->orWhere(function ($inner) { + $inner->where('type', MatchmakingProfile::TYPE_ORGANISATION) + ->whereRaw('LOWER(TRIM(COALESCE(organisation_name, \'\'))) = ?', ['citylab']); + }); + }) + ->get(); + + if ($targets->isEmpty() && ! $dryRun) { + $this->warn('No Sophia/CityLab profiles found after import to sync avatars.'); + } + + foreach ($targets as $profile) { + $resolved = $resolver->resolveForProfile($profile); + $label = $profile->type === MatchmakingProfile::TYPE_VOLUNTEER + ? trim(($profile->first_name ?? '').' '.($profile->last_name ?? '')) + : (string) ($profile->organisation_name ?: ('Profile #'.$profile->id)); + if ($label === '') { + $label = 'Profile #'.$profile->id; + } + + if (empty($resolved)) { + $this->warn("No avatar match for {$label}"); + continue; + } + + if ($profile->avatar === $resolved) { + $this->line("Avatar already set for {$label}: {$resolved}"); + continue; + } + + if ($dryRun) { + $this->line("[DRY] Would set avatar for {$label} -> {$resolved}"); + } else { + $profile->avatar = $resolved; + $profile->save(); + $this->info("Set avatar for {$label} -> {$resolved}"); + } + } + + $this->newLine(); + $this->info('Done. On production, run: php artisan matchmaking:apply-dea-update-2026-07'); + + return self::SUCCESS; + } +} diff --git a/app/Imports/MatchmakingProfileImport.php b/app/Imports/MatchmakingProfileImport.php index 1b97cd063..c0a7dbcae 100644 --- a/app/Imports/MatchmakingProfileImport.php +++ b/app/Imports/MatchmakingProfileImport.php @@ -750,18 +750,12 @@ public function model(array $row): ?Model $trimmedOrgName = trim($organisationName); $lowerOrgName = mb_strtolower($trimmedOrgName); - // First try with type constraint + // Match organisations only against organisation profiles. + // Volunteers often share organisation_name (e.g. CityLab) and must not be overwritten. $existingProfile = MatchmakingProfile::where('type', MatchmakingProfile::TYPE_ORGANISATION) ->whereRaw('LOWER(TRIM(COALESCE(organisation_name, \'\'))) = ?', [$lowerOrgName]) ->first(); - // If not found, try without type constraint (in case type was set incorrectly) - if (!$existingProfile) { - $existingProfile = MatchmakingProfile::whereRaw('LOWER(TRIM(COALESCE(organisation_name, \'\'))) = ?', [$lowerOrgName]) - ->whereNotNull('organisation_name') - ->first(); - } - // Log for debugging if ($existingProfile) { Log::info('[MatchmakingProfileImport] Found existing organisation', [ @@ -784,18 +778,10 @@ public function model(array $row): ?Model $trimmedEmail = trim($email); $lowerEmail = mb_strtolower($trimmedEmail); - // First try with type constraint $existingProfile = MatchmakingProfile::where('type', MatchmakingProfile::TYPE_VOLUNTEER) ->whereRaw('LOWER(TRIM(email)) = ?', [$lowerEmail]) ->first(); - // If not found, try without type constraint (in case type was set incorrectly) - if (!$existingProfile) { - $existingProfile = MatchmakingProfile::whereRaw('LOWER(TRIM(email)) = ?', [$lowerEmail]) - ->whereNotNull('email') - ->first(); - } - // Log for debugging if ($existingProfile) { Log::info('[MatchmakingProfileImport] Found existing volunteer by email', [ @@ -823,13 +809,6 @@ public function model(array $row): ?Model ->whereRaw('LOWER(TRIM(last_name)) = ?', [$last]) ->first(); - // If not found, try without type constraint (in case type was set incorrectly) - if (!$existingProfile) { - $existingProfile = MatchmakingProfile::whereRaw('LOWER(TRIM(first_name)) = ?', [$first]) - ->whereRaw('LOWER(TRIM(last_name)) = ?', [$last]) - ->first(); - } - if ($existingProfile) { Log::info('[MatchmakingProfileImport] Found existing volunteer by name', [ 'id' => $existingProfile->id, diff --git a/database/seeders/data/matchmaking/citylab-organisation-updated.xlsx b/database/seeders/data/matchmaking/citylab-organisation-updated.xlsx new file mode 100644 index 000000000..a78dc8ab2 Binary files /dev/null and b/database/seeders/data/matchmaking/citylab-organisation-updated.xlsx differ diff --git a/database/seeders/data/matchmaking/sophia-drakaki-individual.xlsx b/database/seeders/data/matchmaking/sophia-drakaki-individual.xlsx new file mode 100644 index 000000000..173c20ba0 Binary files /dev/null and b/database/seeders/data/matchmaking/sophia-drakaki-individual.xlsx differ diff --git a/public/images/matchmaking-tool/matchmaking-volunteers/Sophia Drakaki.jpeg b/public/images/matchmaking-tool/matchmaking-volunteers/Sophia Drakaki.jpeg new file mode 100644 index 000000000..682868c5b Binary files /dev/null and b/public/images/matchmaking-tool/matchmaking-volunteers/Sophia Drakaki.jpeg differ