@@ -3,9 +3,11 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\InvestorPath;
|
||||
use App\Services\LegacyContent;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
@@ -21,7 +23,20 @@ class InvestorPathController extends Controller
|
||||
|
||||
public function create(Request $request, InvestorPath $path): JsonResponse|RedirectResponse
|
||||
{
|
||||
$id = $path->create($request->user(), $request->all());
|
||||
$source = $request->input('content_resource_id');
|
||||
if ($source !== null) {
|
||||
abort_unless(filter_var($source, FILTER_VALIDATE_INT), 422);
|
||||
app(LegacyContent::class)->articleForResource((int) $source);
|
||||
}
|
||||
$id = DB::transaction(function () use ($path, $request, $source): 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]);
|
||||
}
|
||||
|
||||
return $id;
|
||||
});
|
||||
|
||||
return $request->expectsJson() ? response()->json(['id' => $id], 201) : redirect('/investimentos/'.$id)->with('success', 'Investimento guardado no seu dossier.');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\InvestorPath;
|
||||
use App\Services\LegacyContent;
|
||||
use App\Services\VirtualDesk;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
class LegacyContentController extends Controller
|
||||
{
|
||||
public function landing(Request $request, LegacyContent $legacy, string $sectorSlug = 'agronegocio'): Response
|
||||
{
|
||||
$filters = $request->validate(['q' => 'nullable|string|max:120']);
|
||||
$articles = $legacy->articlesForGroup($sectorSlug);
|
||||
$root = config('legacy_content.groups.'.$sectorSlug.'.root');
|
||||
$article = $legacy->article($root);
|
||||
$q = $filters['q'] ?? '';
|
||||
$articles = array_values(array_filter($articles, fn ($item) => $q === ''
|
||||
|| str_contains(mb_strtolower(strip_tags($item['html'].' '.$item['title'])), mb_strtolower($q))));
|
||||
$match = $articles !== [];
|
||||
|
||||
return Inertia::render('portal/legacy', ['article' => $article, 'articles' => $articles, 'view' => 'landing', 'query' => $q, 'match' => $match]);
|
||||
}
|
||||
|
||||
public function article(Request $request, LegacyContent $legacy, string $legacySlug = 'agronegocio'): Response
|
||||
{
|
||||
return Inertia::render('portal/legacy', ['article' => $legacy->article($legacySlug), 'view' => 'article', 'contentContext' => $this->context($request)]);
|
||||
}
|
||||
|
||||
public function start(Request $request, LegacyContent $legacy, InvestorPath $path, string $legacySlug = 'agronegocio'): Response
|
||||
{
|
||||
abort_unless($path->available(), 404);
|
||||
$article = $legacy->article($legacySlug);
|
||||
|
||||
return Inertia::render('investor-path/index', ['contexts' => $path->contexts($request->user()), 'contentSource' => $article]);
|
||||
}
|
||||
|
||||
public function attach(Request $request, string $id, LegacyContent $legacy): JsonResponse|RedirectResponse
|
||||
{
|
||||
$legacy->attach($request->user(), $id, $request->all());
|
||||
|
||||
return $request->expectsJson() ? response()->json(['saved' => true]) : redirect('/investimentos/'.$id)->with('success', 'Referência guardada no dossier.');
|
||||
}
|
||||
|
||||
/** @return array{id: string, title: string}|null */
|
||||
private function context(Request $request): ?array
|
||||
{
|
||||
$data = $request->validate(['context' => 'nullable|uuid']);
|
||||
if (! $request->user() || empty($data['context'])) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
$case = app(VirtualDesk::class)->case($request->user(), $data['context'], true);
|
||||
|
||||
return $case->closed_at ? null : ['id' => $case->id, 'title' => $case->title];
|
||||
} catch (HttpException $error) {
|
||||
if (! in_array($error->getStatusCode(), [403, 404], true)) {
|
||||
throw $error;
|
||||
}
|
||||
|
||||
return null; // Consultation stays public; an unauthorized private context is never returned.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use App\Models\Opportunity;
|
||||
use App\Models\Sector;
|
||||
use App\Services\InvestmentGeography;
|
||||
use App\Services\OpportunityCanonicalContract;
|
||||
use App\Services\OpportunityCanonicalResponse;
|
||||
use App\Services\OpportunityManagement;
|
||||
use App\Services\OpportunityManagementInstallation;
|
||||
use App\Services\OpportunityMaturity;
|
||||
@@ -126,6 +127,7 @@ class OpportunityManagementController extends Controller
|
||||
'canonicalOptions' => ($record?->owner_user_id !== null || str_starts_with($request->path(), 'minhas-oportunidades')) ? app(OpportunityCanonicalContract::class)->options($request->user(), $record) : null,
|
||||
'workflowState' => $record ? $access->state($record) : null,
|
||||
'publicationState' => $record?->publicationPresentation(),
|
||||
'canonicalResponse' => $record?->owner_user_id !== null ? app(OpportunityCanonicalResponse::class)->read($request->user(), $record) : null,
|
||||
'canEditDraft' => $record ? $access->canEdit($request->user(), $record) : false,
|
||||
'workflowParticipants' => $record && $record->owner_user_id !== null ? app(OpportunityProfileWorkflow::class)->participants($request->user(), $record) : ['validators' => [], 'publishers' => []],
|
||||
'workflowTasks' => $record && $record->owner_user_id !== null ? app(OpportunityWorkflowTasks::class)->listing($record) : [],
|
||||
@@ -151,6 +153,12 @@ class OpportunityManagementController extends Controller
|
||||
return response()->json(['id' => $record->id, 'etag' => $service->etag($record), 'message' => 'Rascunho guardado no servidor. Não foi submetido, aprovado ou publicado.'], $id ? 200 : 201)->header('ETag', '"'.$service->etag($record).'"');
|
||||
}
|
||||
|
||||
public function canonicalResponse(Request $request, int $id, OpportunityCanonicalResponse $response): JsonResponse
|
||||
{
|
||||
return response()->json($response->read($request->user(), Opportunity::withTrashed()->findOrFail($id)))
|
||||
->header('Cache-Control', 'private, no-store');
|
||||
}
|
||||
|
||||
public function lifecycle(Request $request, int $id, OpportunityManagement $service): JsonResponse
|
||||
{
|
||||
abort_unless($service->installed(), 503);
|
||||
|
||||
@@ -138,13 +138,20 @@ class OpportunityResourceController extends Controller
|
||||
$districts = [];
|
||||
foreach ($locations as $location) {
|
||||
$p = $location->payload;
|
||||
if (empty($p['province_id']) || empty($p['district_id']) || empty($p['role'])) {
|
||||
$issues[] = 'Uma localização tem província, distrito ou papel pendente.';
|
||||
$mozambican = empty($p['country_code']) || $p['country_code'] === 'MZ';
|
||||
if ($mozambican && (empty($p['province_id']) || empty($p['district_id']))) {
|
||||
$issues[] = 'Uma localização em Moçambique tem província ou distrito pendente.';
|
||||
}
|
||||
if (! empty($p['district_id'])) {
|
||||
if (! $mozambican && array_filter(array_intersect_key($p, array_flip(['province_id', 'district_id', 'admin_post_id', 'locality_id'])))) {
|
||||
$issues[] = 'Uma localização noutro país conserva uma seleção territorial de Moçambique incompatível; corrigir explicitamente sem converter o histórico.';
|
||||
}
|
||||
if (empty($p['role'])) {
|
||||
$issues[] = 'Uma localização tem papel pendente.';
|
||||
}
|
||||
if ($mozambican && ! empty($p['district_id'])) {
|
||||
$districts[$p['district_id']] = true;
|
||||
}
|
||||
if (! empty($p['province_id'])) {
|
||||
if ($mozambican && ! empty($p['province_id'])) {
|
||||
$provinces[$p['province_id']] = true;
|
||||
}
|
||||
if (($p['role'] ?? null) === 'other' && empty($p['role_description'])) {
|
||||
@@ -164,7 +171,7 @@ class OpportunityResourceController extends Controller
|
||||
$issues[] = 'Multidistrito/interprovincial exige fundamento operacional de 50–1000 caracteres.';
|
||||
}
|
||||
if ($locations) {
|
||||
$issues[] = 'Modo preparado: '.(count($locations) > 1 ? 'multiple' : 'single').'; âmbito derivado: '.(count($provinces) > 1 ? 'interprovincial' : (count($districts) > 1 ? 'multi_district' : 'district')).'. Sem conversão de contexto em implantação.';
|
||||
$issues[] = 'Modo preparado: '.(count($locations) > 1 ? 'multiple' : 'single').'; âmbito administrativo moçambicano derivado: '.(count($provinces) > 1 ? 'interprovincial' : (count($districts) > 1 ? 'multi_district' : ($districts ? 'district' : 'sem seleção moçambicana'))).'. Sem conversão de contexto em implantação.';
|
||||
}
|
||||
foreach (['images', 'documents'] as $kind) {
|
||||
foreach ($resources[$kind] as $r) {
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Models\Sector;
|
||||
use App\Services\CountryCatalogue;
|
||||
use App\Services\IdempotencyConflict;
|
||||
use App\Services\InterestSubmission;
|
||||
use App\Services\LegacyContent;
|
||||
use App\Services\Malware\MalwareScanner;
|
||||
use App\Services\VerifiedDocumentBinary;
|
||||
use App\Services\VirtualDesk;
|
||||
@@ -366,7 +367,7 @@ class PortalServicesController extends Controller
|
||||
|
||||
public function upload(Request $request): RedirectResponse
|
||||
{
|
||||
$this->validateManagement($request, ['file' => ['required', 'file', 'mimetypes:application/pdf,text/plain,text/csv,image/jpeg,image/png,image/webp,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.oasis.opendocument.text', 'max:10240'],
|
||||
$this->validateManagement($request, ['file' => ['required', 'file', 'mimetypes:application/pdf,text/plain,text/csv,image/jpeg,image/png,image/webp,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.oasis.opendocument.text'.(app(LegacyContent::class)->available() ? ',application/json' : ''), 'max:10240'],
|
||||
'original_source' => ['nullable', 'url:https', 'max:2000'], 'issuer' => ['nullable', 'string', 'max:180']]);
|
||||
$file = $request->file('file');
|
||||
$checksum = hash_file('sha256', $file->getRealPath());
|
||||
@@ -635,7 +636,9 @@ class PortalServicesController extends Controller
|
||||
$data = $this->validateManagement($request, [
|
||||
'document_id' => ['required', 'uuid'], 'title' => ['required', 'string', 'max:200'],
|
||||
'description' => ['nullable', 'string', 'max:4000'], 'category' => ['required', 'string', 'max:80'],
|
||||
'topic' => ['required', 'string', 'max:80'], 'document_type' => ['required', 'in:legislation,procedure,official_form,digital_adaptation,guidance_template,guide,study,reference_data,unknown'],
|
||||
// QA legacy originals may have no theme (e.g. a shared image). Do not invent a sector to disclose an authorized original.
|
||||
'topic' => [app(\App\Services\LegacyContent::class)->available() ? 'nullable' : 'required', 'string', 'max:80'],
|
||||
'document_type' => ['required', 'in:legislation,procedure,official_form,digital_adaptation,guidance_template,guide,study,reference_data,unknown'],
|
||||
'issuer' => ['nullable', 'string', 'max:180'], 'document_number' => ['nullable', 'string', 'max:120'],
|
||||
'document_date' => ['nullable', 'date_format:Y-m-d'], 'source_url' => ['nullable', 'url:https', 'max:2000'],
|
||||
'applicable_from' => ['nullable', 'date_format:Y-m-d'],
|
||||
@@ -656,6 +659,7 @@ class PortalServicesController extends Controller
|
||||
'associations.*.applicability' => ['required', 'in:required,conditional,support,optional_template,evidence'],
|
||||
'associations.*.condition_text' => ['nullable', 'string', 'max:2000'], 'associations.*.basis_reference' => ['nullable', 'string', 'max:2000'],
|
||||
]);
|
||||
$data['topic'] = $data['topic'] ?? '';
|
||||
if (! empty($data['applicable_from']) && ! empty($data['applicable_until']) && $data['applicable_until'] < $data['applicable_from']) {
|
||||
throw $this->managementValidationException(['applicable_until' => 'A data final deve ser igual ou posterior à data inicial.']);
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ class VirtualDeskController extends Controller
|
||||
}
|
||||
}
|
||||
$this->auditRead($request, 'desk_requests', $id);
|
||||
$data = ['countries' => app(CountryCatalogue::class)->all(), 'request' => [...(array) $row, 'payload' => $payload, 'submitted_snapshot' => $desk->decode($row->submitted_snapshot), 'editable_fields' => json_decode($row->editable_fields ?? '[]', true)],
|
||||
$data = ['countries' => app(CountryCatalogue::class)->all(), 'toolPrefill' => $desk->requestToolPrefill($request->user(), $row), 'request' => [...(array) $row, 'payload' => $payload, 'submitted_snapshot' => $desk->decode($row->submitted_snapshot), 'editable_fields' => json_decode($row->editable_fields ?? '[]', true)],
|
||||
'case' => ['id' => $case->id, 'title' => $case->title], 'caseVersion' => $case->version, 'definition' => $definition, 'territoryPaths' => $territoryPaths,
|
||||
'serviceSource' => json_decode(DB::table('desk_service_versions')->where('id', $row->service_version_id)->value('source'), true),
|
||||
'owner' => $desk->owns($request->user(), $case), 'manager' => $request->user()->can('manage-portal'), 'analyst' => (int) $row->analyst_id === $request->user()->id,
|
||||
|
||||
Reference in New Issue
Block a user