currentWorkspace(); $user = $request->user(); $workspaceUser = $user->currentWorkspaceUser(); $declarations = Declaration::where('workspace_id', $workspace->id) ->forUser($user, $workspaceUser) ->where('status', DeclarationStatus::EnAttenteClient) ->whereIn('id', $request->validated('declaration_ids')) ->with('client') ->get() ->filter(fn (Declaration $d) => $d->client !== null); if ($declarations->isEmpty()) { return back()->with('flash', [ 'type' => 'warning', 'message' => 'Aucune déclaration éligible trouvée.', ]); } // DB transaction for invitation creation/update, collect mail data for queuing after commit $mailJobs = DB::transaction(function () use ($declarations) { $jobs = []; foreach ($declarations as $declaration) { $clientEmail = $declaration->client->contact_email; $invitation = $declaration->invitations() ->whereNull('used_at') ->latest() ->first(); if ($invitation && $invitation->isValid()) { if ($invitation->email !== $clientEmail) { $invitation->update(['email' => $clientEmail]); $invitation->refresh(); } } elseif ($invitation && ! $invitation->isValid()) { $invitation->update([ 'email' => $clientEmail, 'expires_at' => now()->addDays(30), ]); $invitation->refresh(); } else { $invitation = $declaration->invitations()->create([ 'email' => $clientEmail, 'expires_at' => now()->addDays(30), ]); } $body = 'Nous vous invitons à déposer les documents complémentaires pour votre déclaration "' . $declaration->title . '".'; $jobs[] = [ 'email' => $declaration->client->contact_email, 'mailable' => new DeclarationFileRequestMail($declaration, $invitation, $body), ]; } return $jobs; }); // Queue emails outside transaction (Redis is not transactional with MySQL) foreach ($mailJobs as $job) { Mail::to($job['email'])->queue($job['mailable']); } activity() ->performedOn($workspace) ->causedBy($user) ->withProperties([ 'count' => $declarations->count(), 'declaration_ids' => $declarations->pluck('id')->all(), ]) ->log('bulk_client_notification'); // Invalidate notification caches for workspace users $workspace->users->each(function ($wsUser) use ($workspace) { Cache::forget("user:{$wsUser->id}:workspace:{$workspace->id}:unread_notifications"); }); return back()->with('flash', [ 'type' => 'success', 'message' => $declarations->count() . ' notifications envoyées', ]); } }