Files
Investidor-V2/app/Services/SectorInvestment.php

108 lines
6.2 KiB
PHP

<?php
namespace App\Services;
use App\Http\Controllers\InvestmentPortfolioController;
use App\Models\Opportunity;
use App\Models\Sector;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Symfony\Component\HttpKernel\Exception\HttpException;
/** Entry adapter over existing sector, opportunity, portfolio and private dossier records. */
final class SectorInvestment
{
// Editorial bridges, not renamed masters or automatic opportunity classifications.
public const CONTENT = [
'mineracao' => ['mineracao', 'energia-mineracao'], 'energia-especifica' => ['energia', 'energia-mineracao'],
'agronegocio' => ['agronegocio'], 'turismo' => ['turismo'], 'infraestruturas' => ['infraestruturas'],
'tecnologias-de-informacao' => ['tech-fintech'], 'mercado-financeiro' => ['tech-fintech'],
'fintech' => ['tech-fintech'], 'imobiliario' => ['imobiliario'],
'bens-de-consumo-e-franquias' => ['bens-de-consumo-franquias'],
];
public function sector(string $slug): Sector
{
return Sector::where('slug', $slug)->where('is_active', true)->firstOrFail();
}
public function context(string $slug): array
{
$sector = $this->sector($slug);
return [...$sector->only(['id', 'name', 'slug']), 'href' => SectorOrganization::href($sector->slug)];
}
public function publicView(Sector $sector): array
{
$query = SectorOrganization::opportunities($sector);
$articles = $this->guides($sector);
$relations = [];
foreach (['projects' => 'project_id', 'clusters' => 'cluster_id', 'corridors' => 'corridor_id'] as $table => $foreign) {
$ids = DB::table('opportunity_'.$table)->whereIn('opportunity_id', (clone $query)->select('id'))->pluck($foreign);
foreach (InvestmentPortfolioController::visible()->whereIn($foreign, $ids)->orderBy('title')->get() as $dossier) {
$relations[] = ['id' => $dossier->id, 'title' => $dossier->title, 'href' => '/carteira/'.$dossier->id, 'kind' => $table,
'basis' => 'Associação cadastrada entre uma oportunidade pública deste setor e esta ficha publicada.', 'source' => $dossier->source];
}
}
$locations = [];
foreach ((clone $query)->orderBy('id')->get(['id', 'title', 'slug']) as $opportunity) {
foreach (app(InvestmentRelations::class)->opportunityLocations($opportunity->id) as $location) {
$locations[] = ['opportunity_title' => $opportunity->title, 'href' => '/oportunidades/'.$opportunity->slug, 'location' => $location];
}
}
return ['articles' => $articles, 'relations' => collect($relations)->unique('id')->values()->all(), 'locations' => $locations,
'start_href' => app(InvestorPath::class)->available() ? '/investimentos/a-partir-do-setor/'.$sector->slug : null,
'start_notice' => app(InvestorPath::class)->available() ? null : 'O percurso para guardar a ideia está temporariamente indisponível. A consulta e o apoio continuam disponíveis.',
'register_href' => app(PortalProfileAccess::class)->available() ? '/minhas-oportunidades/create?sector_id='.$sector->id : null,
'register_notice' => app(PortalProfileAccess::class)->available() ? null : 'O cadastro por perfis está temporariamente indisponível; pode preparar a ideia e contactar o apoio.',
'specialization_href' => in_array($sector->slug, ['tecnologias-de-informacao', 'mercado-financeiro'], true) ? SectorOrganization::href('fintech') : null,
'content_notice' => $sector->slug === 'mercado-financeiro' ? 'O artigo geral de Mercado Financeiro permanece vazio e não foi publicado. A preparação de negócios neste setor está disponível.' : null];
}
/** Published guides for an explicit sector; never inferred from proximity. */
public function guides(?Sector $sector): array
{
if (! $sector) return [];
$articles = [];
if (app(LegacyContent::class)->available()) {
foreach (self::CONTENT[$sector->slug] ?? [] as $slug) {
try {
$a = app(LegacyContent::class)->article($slug, false);
$articles[] = \Illuminate\Support\Arr::only($a, ['title', 'href', 'image', 'sha256', 'summary', 'author', 'opportunities_href', 'opportunities_label', 'has_opportunities']);
} catch (HttpException $e) {
if ($e->getStatusCode() !== 404) {
throw $e;
}
}
}
}
return $articles;
}
public function attach(User $actor, string $id, array $input): void
{
$data = Validator::make($input, ['version' => 'required|integer|min:1', 'sector_slug' => 'required|string|max:150'])->validate();
$context = $this->context($data['sector_slug']);
abort_unless(app(InvestorPath::class)->available(), 404);
DB::transaction(function () use ($actor, $id, $data, $context) {
$desk = app(VirtualDesk::class);
$case = $desk->case($actor, $id, true, true);
abort_if($case->closed_at, 409, 'O dossier está encerrado.');
abort_unless(DB::table('investor_journeys')->where('id', $case->journey_id)->whereNotNull('path_definition_id')->whereNull('deleted_at')->exists(), 409, 'Este dossier conserva outro roteiro.');
$payload = $desk->decode($case->payload);
foreach ($payload['investor_path']['sector_contexts'] ?? [] as $existing) {
if ($existing['id'] === $context['id']) {
return;
}
}
abort_unless((int) $case->version === $data['version'], 409, 'O dossier foi alterado; recupere a versão atual.');
$payload['investor_path']['sector_contexts'][] = [...$context, 'actor_id' => $actor->id, 'saved_at' => now()->toIso8601String()];
DB::table('desk_cases')->where('id', $id)->update(['payload' => $desk->encode($payload), 'version' => $case->version + 1, 'updated_at' => now()]);
$desk->event($desk->case($actor, $id), $actor, 'sector_context_referenced', ['sector_id' => $context['id'], 'sector_slug' => $context['slug']]);
});
}
}