['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(); } /** * @return array */ public function context(string $slug): array { $sector = $this->sector($slug); return [...$sector->only(['id', 'name', 'slug']), 'href' => SectorOrganization::href($sector->slug)]; } /** Public detail shared by the catalogue and the individual sector page. * @return array */ public function page(Sector $sector): array { return [ 'sector' => $sector->only(['id', 'name', 'slug', 'description', 'short_description']), 'sectorEntry' => $this->publicView($sector), 'opportunities' => SectorOrganization::opportunities($sector)->latest('published_at') ->limit(12)->get(['id', 'title', 'slug', 'province', 'summary']), ]; } /** * @return array */ public function publicView(Sector $sector): array { $creation = app(ContentManagement::class)->access(request()->user())['opportunities']['create'] ?? null; $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() ? ($creation ? $creation.'?sector_id='.$sector->id : '/perfil/promotor') : null, 'register_label' => $creation ? 'Cadastrar oportunidade — escolher modalidade' : 'Pedir acesso para cadastrar oportunidades', '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. * @return array */ 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[] = 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; } /** * @param array $input */ 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']]); }); } }