From d39624c93d02bdbcb2ec1440f72b622fa86ab7a6 Mon Sep 17 00:00:00 2001 From: bernardhanna Date: Wed, 4 Jun 2025 11:04:57 +0100 Subject: [PATCH] Improve GenericEventsImport and add example import command + test file --- app/Console/Commands/excel/Generic.php | 2 +- app/Imports/GenericEventsImport.php | 223 ++++++++++++++----------- resources/excel/example.xlsx | Bin 0 -> 5558 bytes 3 files changed, 129 insertions(+), 96 deletions(-) create mode 100644 resources/excel/example.xlsx diff --git a/app/Console/Commands/excel/Generic.php b/app/Console/Commands/excel/Generic.php index 68c0276bf..48279386a 100644 --- a/app/Console/Commands/excel/Generic.php +++ b/app/Console/Commands/excel/Generic.php @@ -42,7 +42,7 @@ public function handle(): void Excel::import( new GenericEventsImport(), - 'pauline-2023.xlsx', + 'example.xlsx', 'excel' ); } diff --git a/app/Imports/GenericEventsImport.php b/app/Imports/GenericEventsImport.php index 72d5403be..b969839ea 100644 --- a/app/Imports/GenericEventsImport.php +++ b/app/Imports/GenericEventsImport.php @@ -4,117 +4,150 @@ use App\Event; use App\User; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Log; -use Illuminate\Support\Str; use Maatwebsite\Excel\Concerns\ToModel; use Maatwebsite\Excel\Concerns\WithCustomValueBinder; use Maatwebsite\Excel\Concerns\WithHeadingRow; +use PhpOffice\PhpSpreadsheet\Cell\DefaultValueBinder; use PhpOffice\PhpSpreadsheet\Shared\Date; class GenericEventsImport extends BaseEventsImport implements ToModel, WithCustomValueBinder, WithHeadingRow { - public function parseDate($date) + public function parseDate($value) { - return Date::excelToDateTimeObject($date); - } + // Handle Excel dates or normal string dates + if (is_numeric($value)) { + return Date::excelToDateTimeObject($value)->format('Y-m-d H:i:s'); + } - public function loadUser($email) - { - return User::firstOrCreate( - [ - 'email' => $email, - ], - [ - 'firstname' => '', - 'lastname' => '', - 'username' => '', - 'password' => bcrypt(Str::random()), - ] - ); + try { + return Carbon::parse($value)->format('Y-m-d H:i:s'); + } catch (\Exception $e) { + Log::warning("Invalid date: {$value}"); + return null; + } } + public function model(array $row): ?Model { - - $event = new Event([ - 'status' => 'APPROVED', - 'title' => $row['activity_title'], - 'slug' => str_slug($row['activity_title']), - 'organizer' => $row['name_of_organisation'], - 'description' => $row['description'], - 'organizer_type' => $row['type_of_organisation'], - 'activity_type' => $row['activity_type'], - 'location' => $row['address'], - 'event_url' => $row['organiser_website'], - 'user_email' => '', - 'creator_id' => $this->loadUser($row['contact_email'])->id, - 'contact_person' => $row['contact_email'], - 'country_iso' => $row['country'], - 'picture' => $row['image_path'], - 'pub_date' => now(), - 'created' => now(), - 'updated' => now(), - 'codeweek_for_all_participation_code' => 'cw23-CodeWeekNL', - 'start_date' => $this->parseDate($row['start_date']), - 'end_date' => $this->parseDate($row['end_date']), - 'geoposition' => $row['latitude'].','.$row['longitude'], - 'longitude' => $row['longitude'], - 'latitude' => $row['latitude'], - 'language' => strtolower($row['language']), - 'mass_added_for' => 'Excel', - 'recurring_event' => isset($row['recurring_event']) - ? $this->validateSingleChoice($row['recurring_event'], Event::RECURRING_EVENTS) - : null, - - 'males_count' => isset($row['males_count']) ? (int) $row['males_count'] : null, - 'females_count' => isset($row['females_count']) ? (int) $row['females_count'] : null, - 'other_count' => isset($row['other_count']) ? (int) $row['other_count'] : null, - - 'is_extracurricular_event' => isset($row['is_extracurricular_event']) - ? $this->parseBool($row['is_extracurricular_event']) - : false, - - 'is_standard_school_curriculum' => isset($row['is_standard_school_curriculum']) - ? $this->parseBool($row['is_standard_school_curriculum']) - : false, - - 'is_use_resource' => isset($row['is_use_resource']) - ? $this->parseBool($row['is_use_resource']) - : false, - - 'activity_format' => isset($row['activity_format']) - ? $this->validateMultiChoice($row['activity_format'], Event::ACTIVITY_FORMATS) - : [], - - 'ages' => isset($row['ages']) - ? $this->validateMultiChoice($row['ages'], Event::AGES) - : [], - - 'duration' => isset($row['duration']) - ? $this->validateSingleChoice($row['duration'], Event::DURATIONS) - : null, - - 'recurring_type' => isset($row['recurring_type']) - ? $this->validateSingleChoice($row['recurring_type'], Event::RECURRING_TYPES) - : null, - ]); - - $event->save(); - - if ($row['audience_comma_separated_ids']) { - $event - ->audiences() - ->attach(explode(',', $row['audience_comma_separated_ids'])); - } - if ($row['theme_comma_separated_ids']) { - $event - ->themes() - ->attach(explode(',', $row['theme_comma_separated_ids'])); + Log::info('Importing row:', $row); + + // Validate required fields + if ( + empty($row['activity_title']) || + empty($row['name_of_organisation']) || + empty($row['description']) || + empty($row['type_of_organisation']) || + empty($row['activity_type']) || + empty($row['country']) || + empty($row['start_date']) || + empty($row['end_date']) + ) { + Log::error('Missing required fields in row, skipping.'); + return null; } - Log::info($event->slug); + // Resolve creator_id + $creatorId = null; + if (!empty($row['creator_id'])) { + if (is_numeric($row['creator_id'])) { + $creatorId = (int) $row['creator_id']; + } else { + $creatorId = User::where('email', trim($row['creator_id']))->value('id'); + } + } - return $event; + try { + $event = new Event([ + 'status' => 'APPROVED', + 'title' => trim($row['activity_title']), + 'slug' => str_slug(trim($row['activity_title'])), + 'organizer' => trim($row['name_of_organisation']), + 'description' => trim($row['description']), + 'organizer_type' => trim($row['type_of_organisation']), + 'activity_type' => trim($row['activity_type']), + 'location' => !empty($row['address']) ? trim($row['address']) : 'online', + 'event_url' => !empty($row['organiser_website']) ? trim($row['organiser_website']) : '', + 'contact_person' => !empty($row['contact_email']) ? trim($row['contact_email']) : '', + 'user_email' => !empty($row['contact_email']) ? trim($row['contact_email']) : '', + 'creator_id' => $creatorId, + 'country_iso' => strtoupper(trim($row['country'])), + 'picture' => !empty($row['image_path']) ? trim($row['image_path']) : '', + 'pub_date' => now(), + 'created' => now(), + 'updated' => now(), + 'codeweek_for_all_participation_code' => '', // You can make this configurable + 'start_date' => $this->parseDate($row['start_date']), + 'end_date' => $this->parseDate($row['end_date']), + 'geoposition' => (!empty($row['latitude']) && !empty($row['longitude'])) ? $row['latitude'] . ',' . $row['longitude'] : '', + 'longitude' => !empty($row['longitude']) ? trim($row['longitude']) : '', + 'latitude' => !empty($row['latitude']) ? trim($row['latitude']) : '', + 'language' => !empty($row['language']) ? strtolower(explode('_', trim($row['language']))[0]) : 'en', + 'mass_added_for' => 'Excel', + 'recurring_event' => isset($row['recurring_event']) + ? $this->validateSingleChoice($row['recurring_event'], Event::RECURRING_EVENTS) + : null, + + 'males_count' => isset($row['males_count']) ? (int) $row['males_count'] : null, + 'females_count' => isset($row['females_count']) ? (int) $row['females_count'] : null, + 'other_count' => isset($row['other_count']) ? (int) $row['other_count'] : null, + + 'is_extracurricular_event' => isset($row['is_extracurricular_event']) + ? $this->parseBool($row['is_extracurricular_event']) + : false, + + 'is_standard_school_curriculum' => isset($row['is_standard_school_curriculum']) + ? $this->parseBool($row['is_standard_school_curriculum']) + : false, + + 'is_use_resource' => isset($row['is_use_resource']) + ? $this->parseBool($row['is_use_resource']) + : false, + + 'activity_format' => isset($row['activity_format']) + ? $this->validateMultiChoice($row['activity_format'], Event::ACTIVITY_FORMATS) + : [], + + 'ages' => isset($row['ages']) + ? $this->validateMultiChoice($row['ages'], Event::AGES) + : [], + + 'duration' => isset($row['duration']) + ? $this->validateSingleChoice($row['duration'], Event::DURATIONS) + : null, + + 'recurring_type' => isset($row['recurring_type']) + ? $this->validateSingleChoice($row['recurring_type'], Event::RECURRING_TYPES) + : null, + ]); + + $event->save(); + + // Audiences + if (!empty($row['audience_comma_separated_ids'])) { + $audiences = array_unique(array_map('trim', explode(',', $row['audience_comma_separated_ids']))); + $audiences = array_filter($audiences, fn ($id) => is_numeric($id) && $id > 0 && $id <= 100); + if (!empty($audiences)) { + $event->audiences()->attach($audiences); + } + } + + // Themes + if (!empty($row['theme_comma_separated_ids'])) { + $themes = array_unique(array_map('trim', explode(',', $row['theme_comma_separated_ids']))); + $themes = array_filter($themes, fn ($id) => is_numeric($id) && $id > 0 && $id <= 100); + if (!empty($themes)) { + $event->themes()->attach($themes); + } + } + + return $event; + } catch (\Exception $e) { + Log::error('Event import failed: ' . $e->getMessage()); + return null; + } } -} +} \ No newline at end of file diff --git a/resources/excel/example.xlsx b/resources/excel/example.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..dd6c1cabecee3fb65b0450827f128af3e977fc52 GIT binary patch literal 5558 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 KmqPR(tNsVjp(WG+ literal 0 HcmV?d00001