validated(); $key = $request->header('Idempotency-Key'); Validator::make(['idempotency_key' => $key], ['idempotency_key' => [$key === null ? 'nullable' : 'required', 'uuid']])->validate(); try { $interestRequest = $submission->receive($validated, $key, function () use ($request, $validated) { $opportunity = ! empty($validated['opportunityReference']) ? Opportunity::published()->where('reference', $validated['opportunityReference'])->first() : null; if (! empty($validated['opportunityReference']) && ! $opportunity) { throw ValidationException::withMessages(['opportunityReference' => 'A oportunidade seleccionada já não está disponível.']); } $business = ! empty($validated['existingBusinessId']) ? ExistingBusiness::published()->whereKey($validated['existingBusinessId'])->first() : null; if (! empty($validated['existingBusinessId']) && ! $business) { throw ValidationException::withMessages(['existingBusinessId' => 'A oportunidade seleccionada já não está disponível.']); } $interest = InterestRequest::create([ 'opportunity_id' => $opportunity?->id, 'existing_business_id' => $business?->id, 'modality' => $business ? $validated['modality'] : null, 'reference' => $this->generateReference(), 'full_name' => trim($validated['fullName']), 'email' => strtolower(trim($validated['email'])), 'phone' => $validated['phone'] ?? null, 'investor_type' => $validated['investorType'], 'country' => $validated['country'], 'metadata' => ['declared_origin_country_code' => $validated['declared_origin_country_code'] ?? null], 'sector_interest' => $validated['sectorInterest'], 'opportunity_reference' => $validated['opportunityReference'] ?? null, 'investment_range' => $validated['investmentRange'] ?? null, 'message' => trim($validated['message']), 'privacy_consent' => (bool) $validated['privacyConsent'], 'consented_at' => now(), 'status' => InterestStatus::Received->value, 'source_page' => Str::limit($request->headers->get('referer', ''), 255, ''), 'ip_address' => $request->ip(), 'user_agent' => $request->userAgent(), ]); if (config('portal.notifications_enabled')) { $connection = config('queue.connections.database.connection'); if (config('queue.connections.database.after_commit') || ($connection && $connection !== config('database.default'))) { throw new \RuntimeException('Portal notifications require the transactional database queue.'); } foreach (array_unique(config('portal.notification_recipients')) as $recipient) { if (filter_var($recipient, FILTER_VALIDATE_EMAIL)) { Queue::connection('database')->push(new NotifyInterestReceived($interest->id, $recipient)); } } } return $interest; }); return response()->json([ 'success' => true, 'message' => 'Manifestação de interesse registada com sucesso.', 'data' => [ 'reference' => $interestRequest->reference, 'status' => 'received', ], ], 201); } catch (IdempotencyConflict) { return response()->json(['success' => false, 'message' => 'A chave de envio já foi usada com dados diferentes.'], 409); } catch (ValidationException $exception) { throw $exception; } catch (QueryException $exception) { if ($exception->getCode() === '23505') { return response()->json(['success' => false, 'message' => 'Já existe uma manifestação activa para esta oportunidade e modalidade.'], 409); } throw $exception; } catch (\Throwable $exception) { Log::error('Erro ao registar manifestação de interesse', [ 'exception_type' => $exception::class, ]); return response()->json([ 'success' => false, 'message' => 'Não foi possível registar o pedido. Tente novamente mais tarde.', ], 500); } } private function generateReference(): string { $year = now()->format('Y'); return 'IR-'.$year.'-'.Str::ulid(); } }