Files
Investidor-V2/app/Http/Controllers/InvestmentDiscoveryController.php
T
2026-09-30 12:59:28 +02:00

162 lines
7.7 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\InvestmentDossier;
use App\Models\Opportunity;
use App\Services\InvestmentCatalogue;
use App\Services\InvestmentGeography;
use App\Services\InvestmentZones;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
use Inertia\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
class InvestmentDiscoveryController extends Controller
{
public function index(Request $request, InvestmentCatalogue $catalogue): Response
{
$filters = $catalogue->filters($request);
if ($request->routeIs('investment.education')) {
$filters['education'] = true;
}
return Inertia::render('investment/catalogue', ['filters' => $filters, 'catalogueUrl' => $request->routeIs('investment.education') ? '/educacao' : '/oportunidades', 'references' => $catalogue->references(),
'territorySelection' => app(InvestmentGeography::class)->selection($filters),
'results' => $catalogue->query($filters)->paginate(12)->withQueryString(),
'favourites' => $request->user() ? DB::table('opportunity_favourites')->where('user_id', $request->user()->id)->pluck('opportunity_id') : [],
'territorial' => $this->territorial($filters)]);
}
/**
* @param array<string,mixed> $filters
* @return Collection<int,InvestmentDossier>
*/
private function territorial(array $filters): Collection
{
$query = InvestmentPortfolioController::visible();
$path = app(InvestmentGeography::class)->selection($filters);
if ($path !== []) {
$ids = app(InvestmentGeography::class)->descendants($path[array_key_last($path)]->id);
$query->where(function ($query) use ($ids) {
foreach (['project_id' => 'projects', 'cluster_id' => 'economic_clusters', 'corridor_id' => 'development_corridors'] as $foreign => $table) {
$query->orWhereIn($foreign, DB::table($table)->whereNull('deleted_at')->whereIn('primary_territory_id', $ids)->select('id'));
}
});
}
if (in_array($filters['dimension'] ?? null, ['zee', 'zfi'], true)) {
return $query->whereRaw('false')->get();
}
if (in_array($filters['dimension'] ?? null, ['cluster', 'corridor'], true)) {
$query->whereNotNull($filters['dimension'].'_id');
}
foreach (['zee_id', 'zfi_id', 'park_id', 'education', 'facets', 'sector', 'province', 'district', 'geo_location_id', 'min_amount', 'max_amount', 'currency', 'before', 'maturity_level'] as $field) {
if (isset($filters[$field]) && $filters[$field] !== '' && $filters[$field] !== false && $filters[$field] !== []) {
return $query->whereRaw('false')->get();
}
}
if (! empty($filters['q'])) {
$query->where('title', 'ilike', '%'.$filters['q'].'%');
}
foreach (['cluster_id', 'corridor_id', 'project_id'] as $field) {
if (! empty($filters[$field])) {
$query->where($field, $filters[$field]);
}
}
return $query->orderBy('title')->limit(30)->get();
}
public function search(Request $request, InvestmentCatalogue $catalogue): JsonResponse
{
return response()->json($catalogue->query($catalogue->filters($request))->paginate($request->integer('per_page', 12))->withQueryString());
}
public function references(InvestmentCatalogue $catalogue): JsonResponse
{
return response()->json($catalogue->references());
}
public function detail(Request $request, string $slug, InvestmentCatalogue $catalogue): Response
{
$record = Opportunity::published()->with('sector:id,name,slug')->where('slug', $slug)->firstOrFail();
$data = $request->validate(['zone_id' => ['nullable', 'uuid']]);
$origin = null;
if (! empty($data['zone_id'])) {
$origin = app(InvestmentZones::class)->links()->join('investment_item_opportunities as m', 'm.item_id', '=', 'zl.item_id')
->join('investment_zones as z', 'z.id', '=', 'zl.zone_id')->where('zl.zone_id', $data['zone_id'])->where('m.opportunity_id', $record->id)->first(['z.id', 'z.name']);
abort_unless($origin !== null, 404);
}
return Inertia::render('investment/detail', ['record' => $record, 'context' => $catalogue->context($record->id), 'zone_origin' => $origin]);
}
public function detailApi(string $slug, InvestmentCatalogue $catalogue): JsonResponse
{
$record = Opportunity::published()->with('sector:id,name,slug')->where('slug', $slug)->firstOrFail();
return response()->json(['record' => $record, 'context' => $catalogue->context($record->id)]);
}
public function suggest(Request $request, InvestmentCatalogue $catalogue): JsonResponse
{
$filters = $catalogue->filters($request);
if (mb_strlen(trim($filters['q'] ?? '')) < 2) {
return response()->json(['opportunities' => [], 'territorial' => []]);
}
return response()->json(['opportunities' => $catalogue->query($filters)->limit(5)->get(['id', 'title', 'slug']), 'territorial' => $this->territorial($filters)->take(5)->values()]);
}
public function map(Request $request, InvestmentCatalogue $catalogue): JsonResponse
{
return response()->json($catalogue->map($catalogue->filters($request)));
}
public function compare(Request $request, InvestmentCatalogue $catalogue): JsonResponse
{
$data = $request->validate(['ids' => ['required', 'array', 'min:1', 'max:4'], 'ids.*' => ['required', 'integer', 'distinct']]);
$records = Opportunity::published()->with('sector:id,name,slug')->whereIn('id', $data['ids'])->get();
abort_unless($records->count() === count($data['ids']), 404);
return response()->json(['records' => $records->map(fn ($r) => ['record' => $r, 'context' => $catalogue->context($r->id)])]);
}
public function favourite(Request $request, int $id): JsonResponse
{
Opportunity::published()->findOrFail($id);
$data = $request->validate(['saved' => ['required', 'boolean']]);
$key = ['user_id' => $request->user()->id, 'opportunity_id' => $id];
if ($data['saved']) {
DB::table('opportunity_favourites')->insertOrIgnore([...$key, 'created_at' => now(), 'updated_at' => now()]);
} else {
DB::table('opportunity_favourites')->where($key)->delete();
}
return response()->json(['saved' => $data['saved']]);
}
public function export(Request $request, InvestmentCatalogue $catalogue): StreamedResponse
{
$records = $catalogue->query($catalogue->filters($request))->limit(1000)->get();
return response()->streamDownload(function () use ($records) {
$stream = fopen('php://output', 'w');
if ($stream === false) {
return;
}
fwrite($stream, "\xEF\xBB\xBF");
fputcsv($stream, ['Referência', 'Título', 'Sector', 'Província', 'Montante', 'Moeda', 'Maturidade', 'URL'], ';', '"', '');
foreach ($records as $r) {
$fields = [$r->reference, $r->title, $r->sector?->name, $r->province, $r->estimated_investment_amount, $r->estimated_investment_currency, $r->maturity_label, url('/oportunidades/'.$r->slug)];
fputcsv($stream, array_map(fn ($v) => preg_match('/^[\s]*[=+@-]/u', (string) $v) ? "'".$v : $v, $fields), ';', '"', '');
}
fclose($stream);
}, 'oportunidades.csv', ['Content-Type' => 'text/csv; charset=UTF-8', 'X-Export-Limit' => '1000']);
}
}