diff --git a/app/Services/Events/EventMailCalendarSyncService.php b/app/Services/Events/EventMailCalendarSyncService.php index 3e41dfe..91dcf82 100644 --- a/app/Services/Events/EventMailCalendarSyncService.php +++ b/app/Services/Events/EventMailCalendarSyncService.php @@ -9,6 +9,7 @@ use App\Services\Integrations\MailCalendarClient; use Carbon\CarbonInterface; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Str; class EventMailCalendarSyncService { @@ -91,7 +92,9 @@ class EventMailCalendarSyncService } } - if ($format === 'hybrid' && filled($content['starts_at'] ?? null)) { + if ($entries === [] && filled($content['starts_at'] ?? null)) { + $entries[] = $this->mainEventEntry($event, $content); + } elseif ($format === 'hybrid' && filled($content['starts_at'] ?? null)) { $entries[] = $this->mainEventEntry($event, $content, ' (in person)'); } @@ -114,7 +117,7 @@ class EventMailCalendarSyncService { $sessionId = trim((string) ($session['id'] ?? '')); if ($sessionId === '') { - return null; + $sessionId = 'vs-'.Str::lower(Str::random(12)); } $starts = $this->parseDate($session['scheduled_at'] ?? $content['starts_at'] ?? null); diff --git a/app/Services/Qr/QrPayloadValidator.php b/app/Services/Qr/QrPayloadValidator.php index 361cad5..7a4f53e 100644 --- a/app/Services/Qr/QrPayloadValidator.php +++ b/app/Services/Qr/QrPayloadValidator.php @@ -4,6 +4,7 @@ namespace App\Services\Qr; use App\Models\QrCode; use App\Support\Qr\QrDateFormatter; +use Illuminate\Support\Str; use App\Support\Qr\QrTypeCatalog; use Illuminate\Http\UploadedFile; use RuntimeException; @@ -336,6 +337,10 @@ class QrPayloadValidator } $id = trim((string) ($session['id'] ?? '')); + if ($id === '') { + $id = 'vs-'.Str::lower(Str::random(12)); + } + $roomType = in_array($session['meet_room_type'] ?? '', ['town_hall', 'webinar'], true) ? (string) $session['meet_room_type'] : 'town_hall'; @@ -349,7 +354,7 @@ class QrPayloadValidator } $normalized[] = [ - 'id' => $id !== '' ? mb_substr($id, 0, 64) : null, + 'id' => mb_substr($id, 0, 64), 'title' => mb_substr($title, 0, 120), 'meet_room_type' => $roomType, 'scheduled_at' => QrDateFormatter::normalize((string) ($session['scheduled_at'] ?? '')), diff --git a/tests/Feature/EventMailCalendarSyncTest.php b/tests/Feature/EventMailCalendarSyncTest.php index eacdd8c..fa2a436 100644 --- a/tests/Feature/EventMailCalendarSyncTest.php +++ b/tests/Feature/EventMailCalendarSyncTest.php @@ -84,6 +84,33 @@ class EventMailCalendarSyncTest extends TestCase )->count()); } + public function test_virtual_session_without_id_still_syncs_to_mail_calendar(): void + { + $owner = User::factory()->create(['public_id' => 'usr-cal-4', 'email' => 'host@example.com']); + $event = $this->makeEvent($owner, [ + 'format' => 'virtual', + 'name' => 'NextGen Summit 2026', + 'starts_at' => '2026-08-26', + 'ends_at' => '2026-08-27', + 'virtual_sessions' => [[ + 'id' => null, + 'title' => 'NextGen Summit', + 'scheduled_at' => '2026-08-26', + 'duration_minutes' => 180, + ]], + ]); + + app(EventMailCalendarSyncService::class)->sync($event, $owner); + + Http::assertSent(function ($request) use ($event) { + return str_contains($request->url(), '/calendar/events') + && str_starts_with((string) $request['external_ref'], 'events:'.$event->id.':session:vs-') + && $request['title'] === 'NextGen Summit'; + }); + + $this->assertNotEmpty(data_get($event->fresh()->payload, 'calendar_sync.items')); + } + public function test_uses_linked_mailbox_when_available(): void { Http::fake([