Files
Investidor-V2/app/Http/Controllers/InvestmentDiscoveryController.php
T

197 lines
11 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\Arr;
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(false, $filters),
'corridorGroups' => ($filters['view'] ?? '') === 'corridors' ? app(\App\Services\OpportunityCorridorExplorer::class)->groups($filters) : [],
'territorySelection' => app(InvestmentGeography::class)->selection($filters),
'guides' => app(\App\Services\SectorInvestment::class)->guides(\App\Models\Sector::where('slug', $filters['sector'] ?? '')->where('is_active', true)->whereNotNull('published_at')->first()),
'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);
app(InvestmentGeography::class)->constrainDossiersToTerritories($query, $ids);
}
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');
}
if (! empty($filters['geo_location_id'])) {
app(InvestmentGeography::class)->constrainDossiersToTerritories($query, app(InvestmentGeography::class)->descendants($filters['geo_location_id']));
}
foreach (['association_type', 'association_target', 'association_state', 'association_purpose', 'authority_id', 'chain_id', 'subsector_id', 'category', 'publication_state', 'zee_id', 'zfi_id', 'park_id', 'education', 'facets', 'sector', 'province', 'district', 'min_amount', 'max_amount', 'currency', 'before', 'maturity_level', 'preparation'] as $field) {
if (isset($filters[$field]) && $filters[$field] !== '' && $filters[$field] !== false && $filters[$field] !== []) {
return $query->whereRaw('false')->get();
}
}
if (! empty($filters['q'])) {
$query->where(fn ($q) => $q->where('title', 'ilike', '%'.$filters['q'].'%')->orWhereRaw('content::text 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(Request $request, InvestmentCatalogue $catalogue): JsonResponse
{
return response()->json($catalogue->references(false, $catalogue->filters($request)));
}
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'], 'from_dossier' => ['nullable', 'uuid']]);
$dossierOrigin = isset($data['from_dossier']) ? InvestmentPortfolioController::visible()->whereKey($data['from_dossier'])->firstOrFail() : null;
if ($dossierOrigin) {
$linked = in_array($record->id, app(\App\Services\CorridorAgribusinessOpportunities::class)->portfolioOpportunityIds($dossierOrigin), true);
foreach (['projects' => 'project_id', 'clusters' => 'cluster_id', 'corridors' => 'corridor_id'] as $relation => $foreign) {
if ($dossierOrigin->$foreign && DB::table('opportunity_'.$relation)->where('opportunity_id', $record->id)->where($foreign, $dossierOrigin->$foreign)->exists()) {
$linked = true;
}
}
abort_unless($linked, 404);
}
$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);
}
$returnFilters = $request->query();
unset($returnFilters['zone_id'], $returnFilters['from_dossier']);
return Inertia::render('investment/detail', ['complements' => app(\App\Services\InvestmentComplements::class)->options('opportunity:'.$record->id), 'record' => $record, 'context' => $catalogue->context($record->id), 'zone_origin' => $origin,
'guides' => app(\App\Services\SectorInvestment::class)->guides($record->sector),
'dossierOrigin' => $dossierOrigin ? ['id' => $dossierOrigin->id, 'title' => $dossierOrigin->title] : null,
'returnUrl' => $returnFilters === [] ? '/oportunidades' : '/oportunidades?'.http_build_query($returnFilters)]);
}
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
{
$filters = $catalogue->filters($request);
$context = $this->territorial($filters)->map(fn ($d) => ['id' => 'dossier:'.$d->id, 'title' => $d->title, 'kind' => $d->cluster_id ? 'Cluster' : ($d->corridor_id ? 'Corredor' : 'Projeto âncora'), 'url' => '/carteira/'.$d->id]);
$opportunityOnlyFilters = array_filter(Arr::only($filters, ['association_type', 'association_target', 'association_state', 'association_purpose', 'authority_id', 'chain_id', 'subsector_id', 'category', 'publication_state', 'education', 'sector', 'facets', 'project_id', 'cluster_id', 'corridor_id', 'min_amount', 'max_amount', 'currency', 'before', 'maturity_level', 'preparation']), fn ($value) => $value !== null && $value !== '' && $value !== false && $value !== []);
if ($opportunityOnlyFilters === [] && ! in_array($filters['dimension'] ?? null, ['cluster', 'corridor'], true)) {
$zoneFilters = $filters;
if (in_array($filters['dimension'] ?? null, ['zee', 'zfi'], true)) {
$zoneFilters['category'] = $filters['dimension'];
}
$zones = app(InvestmentZones::class)->search($zoneFilters);
foreach (['zee_id', 'zfi_id', 'park_id'] as $key) {
if (! empty($filters[$key])) {
$zones->where('id', $filters[$key]);
}
}
$context = $context->concat($zones->orderBy('name')->limit(30)->get(['id', 'name', 'category'])->map(fn ($z) => ['id' => 'zone:'.$z->id, 'title' => $z->name, 'kind' => ['zee' => 'ZEE', 'zfi' => 'ZFI', 'industrial_park' => 'Parque industrial'][$z->category] ?? $z->category, 'url' => '/zonas/'.$z->id]));
}
return response()->json([...$catalogue->map($filters), 'territorial_context' => $context->values(), 'context_limit_per_collection' => 30]);
}
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']);
}
}