versao compilada 1
tests / ci (push) Canceled after 0s

This commit is contained in:
2026-10-16 09:17:28 +02:00
parent c13f250b87
commit 6a76aac80f
901 changed files with 253428 additions and 252 deletions
@@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers;
use App\Services\ExistingBusinessClassification;
use App\Services\Mne\BusinessRepository;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class ExistingBusinessClassificationController extends Controller
{
public function show(Request $request, string $id, ExistingBusinessClassification $service): Response|JsonResponse
{
$props = ['supplement' => $service->privateView($request->user(), app(BusinessRepository::class)->find($id))];
return $request->expectsJson() ? response()->json($props) : Inertia::render('investment/business-classification', $props);
}
public function act(Request $request, string $id, ExistingBusinessClassification $service): JsonResponse
{
$business = $service->act($request->user(), $id, $request->all());
return response()->json(['supplement' => $service->privateView($request->user(), $business)]);
}
}
@@ -53,7 +53,7 @@ class InvestmentDiscoveryController extends Controller
if (! empty($filters['geo_location_id'])) {
app(InvestmentGeography::class)->constrainDossiersToTerritories($query, app(InvestmentGeography::class)->descendants($filters['geo_location_id']));
}
foreach (['zee_id', 'zfi_id', 'park_id', 'education', 'facets', 'sector', 'province', 'district', 'min_amount', 'max_amount', 'currency', 'before', 'maturity_level'] as $field) {
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'] as $field) {
if (isset($filters[$field]) && $filters[$field] !== '' && $filters[$field] !== false && $filters[$field] !== []) {
return $query->whereRaw('false')->get();
}
@@ -131,7 +131,7 @@ class InvestmentDiscoveryController extends Controller
{
$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, ['education', 'sector', 'facets', 'project_id', 'cluster_id', 'corridor_id', 'min_amount', 'max_amount', 'currency', 'before', 'maturity_level']), fn ($value) => $value !== null && $value !== '' && $value !== false && $value !== []);
$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']), 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)) {
@@ -2,8 +2,10 @@
namespace App\Http\Controllers;
use App\Services\FintechInvestment;
use App\Services\InvestorPath;
use App\Services\LegacyContent;
use App\Services\SectorInvestment;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@@ -21,21 +23,45 @@ class InvestorPathController extends Controller
return $request->expectsJson() ? response()->json($data) : Inertia::render('investor-path/index', $data);
}
public function sectorStart(Request $request, string $sectorSlug, InvestorPath $path): JsonResponse|Response
{
abort_unless($path->available(), 404);
$data = ['contexts' => $path->contexts($request->user()), 'sectorSource' => app(SectorInvestment::class)->context($sectorSlug)];
return $request->expectsJson() ? response()->json($data) : Inertia::render('investor-path/index', $data);
}
public function sectorAttach(Request $request, string $id): JsonResponse|RedirectResponse
{
app(SectorInvestment::class)->attach($request->user(), $id, $request->all());
return $request->expectsJson() ? response()->json(['ok' => true]) : redirect('/investimentos/'.$id)->with('success', 'Setor associado ao mesmo dossier.');
}
public function create(Request $request, InvestorPath $path): JsonResponse|RedirectResponse
{
$sector = $request->validate(['sector_slug' => 'nullable|string|max:150'])['sector_slug'] ?? null;
if ($sector !== null) {
app(SectorInvestment::class)->context($sector);
}
$source = $request->input('content_resource_id');
if ($source !== null) {
$resourceId = filter_var($source, FILTER_VALIDATE_INT);
abort_unless(is_int($resourceId) && $resourceId !== 0, 422);
app(LegacyContent::class)->articleForResource($resourceId);
}
$id = DB::transaction(function () use ($path, $request, $source): string {
$id = DB::transaction(function () use ($path, $request, $source, $sector): string {
$id = $path->create($request->user(), $request->all());
if ($source !== null) {
$case = $path->view($request->user(), $id)['case'];
app(LegacyContent::class)->attach($request->user(), $id, ['version' => $case['version'], 'resource_id' => (int) $source]);
}
if ($sector !== null) {
$case = $path->view($request->user(), $id)['case'];
app(SectorInvestment::class)->attach($request->user(), $id, ['version' => $case['version'], 'sector_slug' => $sector]);
}
return $id;
});
@@ -80,6 +106,14 @@ class InvestorPathController extends Controller
return $request->expectsJson() ? response()->json(['id' => $task], 201) : back()->with('success', 'Tarefa assumida no acompanhamento interno.');
}
public function fintech(Request $request, string $id, InvestorPath $path): JsonResponse|RedirectResponse
{
abort_unless($path->available(), 404);
app(FintechInvestment::class)->save($request->user(), $id, $request->all());
return $this->saved($request, 'Caracterização Fintech guardada no mesmo dossier; pendências e histórico preservados.');
}
private function saved(Request $request, string $message): JsonResponse|RedirectResponse
{
return $request->expectsJson() ? response()->json(['message' => $message]) : back()->with('success', $message);
@@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers;
use App\Models\Opportunity;
use App\Services\OpportunityClassification;
use App\Services\OpportunityManagement;
use App\Services\PortalProfileAccess;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class OpportunityClassificationController extends Controller
{
public function index(Request $request, int $id, OpportunityClassification $service): JsonResponse
{
$record = Opportunity::findOrFail($id);
app(PortalProfileAccess::class)->authorize($request->user(), 'view', $record);
return response()->json(['version' => $service::VERSION, 'etag' => app(OpportunityManagement::class)->etag($record), 'rows' => $service->listing($request->user(), $record), 'duplicates' => $service->duplicates($request->user(), $record)])->header('Cache-Control', 'private, no-store');
}
public function review(Request $request, int $id, OpportunityClassification $service): JsonResponse
{
$record = $service->review($request->user(), Opportunity::findOrFail($id), $request->all());
return response()->json(['etag' => app(OpportunityManagement::class)->etag($record), 'rows' => $service->listing($request->user(), $record)])->header('Cache-Control', 'private, no-store');
}
}
@@ -136,6 +136,11 @@ class OpportunityManagementController extends Controller
'profileMode' => $record ? $record->owner_user_id !== null : str_starts_with($request->path(), 'minhas-oportunidades'),
'profileActions' => $record && $record->owner_user_id !== null ? app(OpportunityProfileWorkflow::class)->actions($request->user(), $record) : [], 'profileContext' => $record?->workflow_context, 'profileOrganization' => $record?->organization_id]);
$values = $record ? $service->values($record) : [];
if (! $record && $request->filled('sector_id')) {
$selection = $request->validate(['sector_id' => ['required', 'integer', Rule::exists('sectors', 'id')->where('is_active', true)->whereNull('deleted_at')]]);
$values['sector_id'] = (int) $selection['sector_id'];
$values['canonical']['sector_id'] = (int) $selection['sector_id'];
}
Inertia::share('publicationPreview', app(OpportunityCanonicalContract::class)->publicProjection($values['canonical'] ?? []));
$classification = $id ? DB::table('audit_events')->where('source_system', 'opportunity-origin-review')->where('after_data->opportunity_id', $id)->orderByDesc('occurred_at')->orderByDesc('id')->value('after_data') : null;
@@ -123,12 +123,21 @@ class PortalProfileController extends Controller
return app(OpportunityManagementController::class)->index($request, app(OpportunityManagement::class));
}
public function edit(Request $request, PortalProfileAccess $access, ?int $id = null): Response
public function edit(Request $request, PortalProfileAccess $access, ?int $id = null): Response|\Symfony\Component\HttpFoundation\Response
{
if ($id) {
$access->authorize($request->user(), str_ends_with($request->path(), '/view') ? 'view' : 'edit', Opportunity::withTrashed()->findOrFail($id));
} else {
abort_unless($access->navigation($request->user())['contexts'] ?? [], 403);
if (! ($access->navigation($request->user())['contexts'] ?? [])) {
$reason = $access->has($request->user(), 'auditor')
? 'O papel de Auditor permite consulta e impede a criação de oportunidades.'
: 'Esta conta não dispõe de um contexto ativo de Promotor para uma organização e modalidade autorizadas. Confirme a vigência, o âmbito e os tipos atribuídos na gestão de perfis. A elevação excecional de Superutilizador não substitui a autorização de Promotor.';
if ($request->expectsJson()) {
abort(403, $reason);
}
return response()->view('profiles.creation-unavailable', ['reason' => $reason], 403);
}
}
return app(OpportunityManagementController::class)->edit($request, app(OpportunityManagement::class), $id);
@@ -6,6 +6,8 @@ use App\Http\Resources\PublicBusinessOpportunityResource;
use App\Models\ExistingBusiness;
use App\Services\CountryCatalogue;
use App\Services\ExistingBusinessDrafts;
use App\Services\ExistingBusinessClassification;
use App\Services\OpportunityClassification;
use App\Services\InvestmentGeography;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
@@ -19,16 +21,10 @@ class PublicBusinessOpportunityController extends Controller
{
public function index(Request $request): Response|JsonResponse
{
$filters = $request->validate([
'q' => ['nullable', 'string', 'max:120'],
'modality' => ['nullable', Rule::in(ExistingBusinessDrafts::MODALITIES)],
'sector' => ['nullable', 'integer', 'min:1'],
'per_page' => ['nullable', 'integer', 'min:1', 'max:50'],
'province_id' => ['nullable', 'uuid'], 'district_id' => ['nullable', 'uuid'],
'admin_post_id' => ['nullable', 'uuid'], 'locality_id' => ['nullable', 'uuid'],
]);
$filters = $this->filters($request);
$query = $this->publishedQuery();
app(ExistingBusinessClassification::class)->constrain($query, $filters);
$path = app(InvestmentGeography::class)->selection($filters);
if ($path !== []) {
$territoryIds = app(InvestmentGeography::class)->descendants($path[array_key_last($path)]->id);
@@ -53,12 +49,27 @@ class PublicBusinessOpportunityController extends Controller
$query->where('existing_businesses.modality', $filters['modality']);
}
$results = PublicBusinessOpportunityResource::collection($query->paginate($filters['per_page'] ?? 12)->withQueryString());
$results = PublicBusinessOpportunityResource::collection($query->paginate($filters['per_page'] ?? 12)->appends($filters));
if ($request->expectsJson()) {
return $results->response();
}
return Inertia::render('investment/business-marketplace', ['results' => $results, 'filters' => $filters, 'territorySelection' => $path]);
return Inertia::render('investment/business-marketplace', ['results' => $results, 'filters' => $filters, 'territorySelection' => $path,
'classificationOptions' => app(OpportunityClassification::class)->options(),
'authorities' => DB::table('authorities')->where('status', 'published')->orderBy('label_pt_mz')->get(['id', 'label_pt_mz'])]);
}
private function filters(Request $request): array
{
return $request->validate([
'q' => ['nullable', 'string', 'max:120'],
'modality' => ['nullable', Rule::in(ExistingBusinessDrafts::MODALITIES)],
'sector' => ['nullable', 'integer', 'min:1'],
'per_page' => ['nullable', 'integer', 'min:1', 'max:50'],
'province_id' => ['nullable', 'uuid'], 'district_id' => ['nullable', 'uuid'],
'admin_post_id' => ['nullable', 'uuid'], 'locality_id' => ['nullable', 'uuid'],
...app(ExistingBusinessClassification::class)->filterRules(),
]);
}
public function show(Request $request, string $slug): Response|JsonResponse
@@ -69,7 +80,7 @@ class PublicBusinessOpportunityController extends Controller
return $resource->response();
}
$returnFilters = $request->query();
$returnFilters = $this->filters($request);
return Inertia::render('investment/business-opportunity', ['record' => $resource->resolve($request),
'returnUrl' => $returnFilters === [] ? '/negocios' : '/negocios?'.http_build_query($returnFilters),
@@ -80,7 +91,7 @@ class PublicBusinessOpportunityController extends Controller
{
$record = $this->publishedQuery()->where('existing_businesses.public_slug', $slug)->firstOrFail();
$returnFilters = $request->query();
$returnFilters = $this->filters($request);
return Inertia::render('investment/business-interest', [
'record' => (new PublicBusinessOpportunityResource($record))->resolve($request),
@@ -109,6 +120,7 @@ class PublicBusinessOpportunityController extends Controller
'existing_businesses.activity_code', 'existing_businesses.stage', 'existing_businesses.objective',
'existing_businesses.currency', 'existing_businesses.amount_min', 'existing_businesses.amount_max',
'existing_businesses.available_percentage', 'existing_businesses.published_at', 'existing_businesses.sector_id',
'existing_businesses.owner_user_id', 'existing_businesses.organization_id', 'existing_businesses.version_no', 'existing_businesses.expires_at', 'existing_businesses.status', 'existing_businesses.modality',
'public_location.province', 'public_location.district', 'public_location.locality']);
}
}
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use App\Models\Opportunity;
use App\Models\Sector;
use App\Services\CountryCatalogue;
use App\Services\SectorInvestment;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
@@ -23,6 +24,7 @@ class PublicPortalController extends Controller
$sector = Sector::where('slug', $slug)->where('is_active', true)->firstOrFail();
return Inertia::render('portal/services', ['section' => 'sector', 'sector' => $sector->only(['name', 'slug', 'description', 'short_description']),
'sectorEntry' => array_key_exists($sector->slug, SectorInvestment::CONTENT) ? app(SectorInvestment::class)->publicView($sector) : null,
'opportunities' => $sector->opportunities()->published()->latest('published_at')->limit(12)->get(['title', 'slug', 'province', 'summary'])]);
}