feat(editorial): preserve public editions and add QA review workflow
This commit is contained in:
@@ -7,6 +7,7 @@ use App\Models\Opportunity;
|
||||
use App\Models\Sector;
|
||||
use App\Services\DashboardWorkQueue;
|
||||
use App\Services\InvestorPath;
|
||||
use App\Services\PortalEditorialWorkflow;
|
||||
use App\Services\PortalProfileAccess;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@@ -28,7 +29,7 @@ class DashboardController extends Controller
|
||||
$contexts = $path->contexts($request->user());
|
||||
$current = $contexts ? $path->view($request->user(), $contexts[0]['id']) : null;
|
||||
|
||||
return Inertia::render('dashboard', ['summary' => null, 'workQueue' => $workQueue, 'investorPath' => $path->available() ? [
|
||||
return Inertia::render('dashboard', ['editorialQueue' => app(PortalEditorialWorkflow::class)->dashboard($request->user()), 'summary' => null, 'workQueue' => $workQueue, 'investorPath' => $path->available() ? [
|
||||
'contexts' => $contexts,
|
||||
'current' => $current ? ['id' => $current['case']['id'], 'next' => $current['next'],
|
||||
'pending' => count(array_filter($current['steps'], fn ($step) => ! in_array($step['state'], ['completed', 'approved', 'dispensed'], true))),
|
||||
@@ -43,7 +44,7 @@ class DashboardController extends Controller
|
||||
$drafts = app(PortalProfileAccess::class)->scope($drafts, $request->user());
|
||||
}
|
||||
|
||||
return Inertia::render('dashboard', ['workQueue' => $workQueue, 'summary' => [
|
||||
return Inertia::render('dashboard', ['editorialQueue' => app(PortalEditorialWorkflow::class)->dashboard($request->user()), 'workQueue' => $workQueue, 'summary' => [
|
||||
'canCreateOpportunity' => app(PortalProfileAccess::class)->legacyWriteAllowed($request->user()),
|
||||
'requestsByStatus' => collect(['received', 'in_review', 'contacted', 'closed'])
|
||||
->mapWithKeys(fn ($status) => [$status => (int) ($counts[$status] ?? 0)]),
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
||||
use App\Services\DocumentManagementAccess;
|
||||
use App\Services\EditorialInstallation;
|
||||
use App\Services\EditorialReading;
|
||||
use App\Services\PortalEditorialWorkflow;
|
||||
use App\Services\PortalProfileAccess;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
@@ -97,11 +98,17 @@ class EditorialArticleController extends Controller
|
||||
app(DocumentManagementAccess::class)->assertEditableResource(request()->user(), $id);
|
||||
$r = DB::table('portal_resources')->where('id', $id)->first();
|
||||
abort_unless($r instanceof \stdClass, 404);
|
||||
$workflow = app(PortalEditorialWorkflow::class);
|
||||
if ($workflow->enabled()) {
|
||||
$workflow->authorize(request()->user(), $id);
|
||||
$r = $workflow->workingResource($r);
|
||||
}
|
||||
$reading = app(EditorialReading::class);
|
||||
$original = $reading->original($r);
|
||||
$content = $reading->content($r, $original);
|
||||
|
||||
return tap(Inertia::render('portal/article-editor', ['resource' => ['id' => $r->id, 'title' => $r->title, 'metadata_version' => $r->metadata_version, 'published' => (bool) $r->published],
|
||||
'workflowEnabled' => $workflow->enabled(), 'workflow' => $workflow->latest($id),
|
||||
'content' => $content, 'source' => $original ? array_intersect_key($original, array_flip(['source_url', 'published_at', 'modified_at', 'legacy_id', 'image_source_url'])) : null, 'images' => $this->publicResources()->whereIn('d.media_type', ['image/png', 'image/jpeg', 'image/webp'])->get(['r.id', 'r.title']),
|
||||
'revisions' => DB::table('portal_resource_revisions')->where('resource_id', $id)->orderByDesc('revision_no')->get(['revision_no', 'change_type', 'created_at'])])->toResponse(request()), static fn (Response $response) => $response->headers->set('Cache-Control', 'private, no-store'));
|
||||
}
|
||||
@@ -114,7 +121,9 @@ class EditorialArticleController extends Controller
|
||||
$r = DB::table('portal_resources')->where('id', $id)->lockForUpdate()->first();
|
||||
abort_unless($r !== null, 404);
|
||||
app(DocumentManagementAccess::class)->assertEditableResource($request->user(), $id);
|
||||
abort_unless((int) $r->metadata_version === (int) $data['metadata_version'], 409, 'O artigo foi alterado noutra sessão. Recarregue antes de editar.');
|
||||
$workflow = app(PortalEditorialWorkflow::class);
|
||||
$current = $workflow->enabled() ? $workflow->workingResource($r) : $r;
|
||||
abort_unless((int) $current->metadata_version === (int) $data['metadata_version'], 409, 'O artigo foi alterado noutra sessão. Recarregue antes de editar.');
|
||||
if (! empty($data['image_resource_id'])) {
|
||||
abort_unless($this->publicResources()->where('r.id', $data['image_resource_id'])->whereIn('d.media_type', ['image/png', 'image/jpeg', 'image/webp'])->exists(), 422, 'Escolha uma imagem pública disponível na biblioteca.');
|
||||
}
|
||||
@@ -122,7 +131,13 @@ class EditorialArticleController extends Controller
|
||||
if ($body === '') {
|
||||
throw ValidationException::withMessages(['body' => 'Escreva o corpo do artigo.']);
|
||||
}
|
||||
$content = $this->edition($r, $data);
|
||||
$workflow = app(PortalEditorialWorkflow::class);
|
||||
$content = $this->edition($workflow->enabled() ? $workflow->workingResource($r) : $r, $data);
|
||||
if ($workflow->enabled()) {
|
||||
$workflow->save($request->user(), $id, (int) $data['metadata_version'], ['editorial_content' => json_encode($content, JSON_THROW_ON_ERROR)]);
|
||||
|
||||
return;
|
||||
}
|
||||
$revision = (int) $r->metadata_version + 1;
|
||||
DB::table('portal_resources')->where('id', $id)->update(['editorial_content' => json_encode($content, JSON_THROW_ON_ERROR), 'published' => false, 'approved_by' => null, 'metadata_version' => $revision, 'last_reviewed_by' => $request->user()->id, 'last_reviewed_at' => now(), 'updated_at' => now()]);
|
||||
$resource = DB::table('portal_resources')->where('id', $id)->first();
|
||||
@@ -165,6 +180,11 @@ class EditorialArticleController extends Controller
|
||||
app(DocumentManagementAccess::class)->assertEditableResource($request->user(), $id);
|
||||
$resource = DB::table('portal_resources')->where('id', $id)->first();
|
||||
abort_unless($resource instanceof \stdClass, 404);
|
||||
$workflow = app(PortalEditorialWorkflow::class);
|
||||
if ($workflow->enabled()) {
|
||||
$workflow->authorize(request()->user(), $id);
|
||||
$resource = $workflow->workingResource($resource);
|
||||
}
|
||||
|
||||
return tap(Inertia::render('portal/article', ['article' => app(EditorialReading::class)->reading($resource, null, true)])
|
||||
->toResponse($request), static function (Response $response): void {
|
||||
@@ -179,6 +199,11 @@ class EditorialArticleController extends Controller
|
||||
app(DocumentManagementAccess::class)->assertEditableResource($request->user(), $id);
|
||||
$resource = DB::table('portal_resources')->where('id', $id)->first();
|
||||
abort_unless($resource instanceof \stdClass, 404);
|
||||
$workflow = app(PortalEditorialWorkflow::class);
|
||||
if ($workflow->enabled()) {
|
||||
$workflow->authorize($request->user(), $id);
|
||||
$resource = $workflow->workingResource($resource);
|
||||
}
|
||||
$data = $this->validateContent($request);
|
||||
abort_unless((int) $resource->metadata_version === (int) $data['metadata_version'], 409, 'A revisão mudou; recupere antes de rever a prévia.');
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Services\ContentManagement;
|
||||
use App\Services\DocumentManagementAccess;
|
||||
use App\Services\PortalEditorialWorkflow;
|
||||
use App\Services\VerifiedDocumentBinary;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Inertia\Inertia;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class PortalEditorialWorkflowController extends Controller
|
||||
{
|
||||
private const KIND = "CASE WHEN e.payload->'resource'->>'legislation' = 'true' OR e.payload->'resource'->>'document_type' = 'legislation' THEN 'legislation' WHEN e.payload->'resource'->>'editorial_content' IS NOT NULL THEN 'article' ELSE 'document' END";
|
||||
|
||||
/** @param array<string,mixed> $data */
|
||||
private function screen(Request $request, string $component, array $data): Response
|
||||
{
|
||||
$response = $request->expectsJson() ? response()->json($data) : Inertia::render($component, $data)->toResponse($request);
|
||||
$response->headers->set('Cache-Control', 'private, no-store');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function index(Request $request, PortalEditorialWorkflow $workflow): Response
|
||||
{
|
||||
abort_unless($workflow->enabled(), 503);
|
||||
$actor = $request->user();
|
||||
$access = app(DocumentManagementAccess::class);
|
||||
$query = $workflow->queue($actor);
|
||||
$filters = array_filter($request->validate(['state' => 'nullable|in:draft,submitted,in_review,approved,published,returned,rejected,archived', 'kind' => 'nullable|in:article,legislation,document']), fn ($value) => $value !== null && $value !== '');
|
||||
if (isset($filters['state'])) {
|
||||
$query->where('e.state', $filters['state']);
|
||||
}
|
||||
if (isset($filters['kind'])) {
|
||||
$query->whereRaw(self::KIND.' = ?', [$filters['kind']]);
|
||||
}
|
||||
$items = $query->orderByDesc('e.updated_at')->paginate(25, ['r.id', DB::raw("e.payload->'resource'->>'title' AS title"), 'r.document_type', 'r.published', 'e.id as edition_id', 'e.revision_no', 'e.state', 'e.legal_required', 'u.name as responsible'])->withQueryString();
|
||||
$opportunities = app(ContentManagement::class)->access($actor)['opportunities'] ?? null;
|
||||
$data = ['items' => $items, 'filters' => $filters, 'notices' => $workflow->notices($actor), 'resourceAccess' => $access->full($actor) || $access->editorial($actor), 'opportunitiesUrl' => $opportunities['index'] ?? null, 'opportunityPending' => $workflow->opportunityPending($actor)];
|
||||
|
||||
return $this->screen($request, 'management/editorial-workflow', $data);
|
||||
}
|
||||
|
||||
public function show(Request $request, int $id, PortalEditorialWorkflow $workflow): Response
|
||||
{
|
||||
$workflow->authorize($request->user(), $id);
|
||||
$resource = DB::table('portal_resources')->where('id', $id)->first();
|
||||
abort_unless($resource !== null, 404);
|
||||
$selected = $request->validate(['edition' => 'nullable|uuid'])['edition'] ?? null;
|
||||
$latest = $workflow->latest($id);
|
||||
$e = $selected ? DB::table('portal_editorial_editions')->where('resource_id', $id)->where('id', $selected)->first() : $latest;
|
||||
abort_if($selected && ! $e, 404);
|
||||
$payload = $e ? $workflow->payload($e) : null;
|
||||
$independent = ! $e || ! $workflow->requiresIndependent($e) || ! in_array($request->user()->id, $payload['contributors'], true);
|
||||
$data = ['resource' => ['id' => $id, 'title' => $resource->title, 'published' => (bool) $resource->published, 'public_edition_no' => $resource->published ? DB::table('portal_editorial_editions')->where('resource_id', $id)->where('state', 'published')->max('revision_no') : null], 'edition' => $e ? [...(array) $e, 'payload' => $payload, 'fresh' => $e->state === 'published' || $workflow->fresh($e)] : null,
|
||||
'current' => $e && $e->id === $latest?->id,
|
||||
'independentRequired' => $e ? $workflow->requiresIndependent($e) : false,
|
||||
'canApprove' => $independent && app(DocumentManagementAccess::class)->full($request->user()),
|
||||
'manager' => app(DocumentManagementAccess::class)->full($request->user()),
|
||||
'reviewers' => User::whereIn('email', config('portal.managers', []))->whereNotNull('email_verified_at')->get(['id', 'name']),
|
||||
'legalReviewer' => in_array($request->user()->id, config('editorial_workflow.legal_reviewers', []), true),
|
||||
'history' => DB::table('audit_events')->where('source_system', 'document-access-v1')->where('action', 'like', 'editorial_%')->where('after_data->resource_id', $id)->orderByDesc('id')->get(['id', 'action', 'actor_user_id', 'after_data', 'occurred_at']),
|
||||
'editions' => DB::table('portal_editorial_editions')->where('resource_id', $id)->orderByDesc('revision_no')->get(['id', 'revision_no', 'state', 'content_hash', 'author_id', 'created_at'])];
|
||||
|
||||
return $this->screen($request, 'management/editorial-edition', $data);
|
||||
}
|
||||
|
||||
public function transition(Request $request, int $id, string $edition, PortalEditorialWorkflow $workflow): Response
|
||||
{
|
||||
$result = $workflow->transition($request->user(), $id, $edition, $request->all());
|
||||
|
||||
return $request->expectsJson() ? response()->json(['edition' => $result])->header('Cache-Control', 'private, no-store') : back()->with('success', 'Operação editorial registada.');
|
||||
}
|
||||
|
||||
public function file(Request $request, int $id, string $edition, string $document, PortalEditorialWorkflow $workflow): Response
|
||||
{
|
||||
$workflow->authorize($request->user(), $id);
|
||||
$e = DB::table('portal_editorial_editions')->where('id', $edition)->where('resource_id', $id)->first();
|
||||
abort_unless($e !== null, 404);
|
||||
$file = null;
|
||||
foreach ($workflow->payload($e)['files'] as $entry) {
|
||||
if ($entry['id'] === $document) {
|
||||
$file = $entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
abort_unless($file !== null, 404);
|
||||
abort_unless($workflow->canReadFile($request->user(), $document), 403);
|
||||
$record = DB::table('documents')->where('id', $document)->first();
|
||||
abort_unless($record && hash_equals(trim($file['sha256']), trim($record->sha256)) && (int) $file['version_no'] === (int) $record->version_no, 409, 'O anexo mudou; esta edição conserva a referência anterior.');
|
||||
|
||||
return $this->binary($record);
|
||||
}
|
||||
|
||||
private function binary(\stdClass $record): Response
|
||||
{
|
||||
$response = app(VerifiedDocumentBinary::class)->respond($record);
|
||||
$response->headers->set('Cache-Control', 'private, no-store');
|
||||
$response->headers->set('X-Robots-Tag', 'noindex, nofollow');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function markRead(Request $request, int $notice, PortalEditorialWorkflow $workflow): Response
|
||||
{
|
||||
abort_unless($workflow->enabled(), 503);
|
||||
$n = DB::table('portal_editorial_notifications')->where('id', $notice)->where('user_id', $request->user()->id)->first();
|
||||
abort_unless($n !== null, 404);
|
||||
abort_unless($workflow->visibleNotice($request->user(), $n), 404);
|
||||
DB::table('portal_editorial_notifications')->where('id', $notice)->where('user_id', $request->user()->id)->whereNull('read_at')->update(['read_at' => now()]);
|
||||
|
||||
return $request->expectsJson() ? response()->json(['read' => true]) : back();
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ use App\Services\InterestSubmission;
|
||||
use App\Services\InvestmentGeography;
|
||||
use App\Services\LegacyContent;
|
||||
use App\Services\Malware\MalwareScanner;
|
||||
use App\Services\PortalEditorialWorkflow;
|
||||
use App\Services\PortalProfileAccess;
|
||||
use App\Services\PublicDocumentPresentation;
|
||||
use App\Services\PublicSearchMetrics;
|
||||
@@ -320,6 +321,9 @@ class PortalServicesController extends Controller
|
||||
$editorial = $access->editorial($request->user());
|
||||
$resources = $editorial ? $this->filterManagedResources($access->editorialResources($request->user()), $managementFilters)->orderByDesc('r.updated_at')
|
||||
->get(['r.*', 'd.file_name', 'd.media_type', 'd.size_bytes', 'd.issuer', 'd.status as file_status', 'd.data_classification', 'd.version_no as file_version', 'd.rights_status as file_rights_status', 'd.malware_scan_status', 'd.malware_failure_reason']) : collect();
|
||||
if (app(PortalEditorialWorkflow::class)->enabled()) {
|
||||
$resources = $resources->map(fn ($r) => app(PortalEditorialWorkflow::class)->workingResource($r));
|
||||
}
|
||||
$links = DB::table('portal_resource_links')->whereIn('resource_id', $resources->pluck('id'))->orderBy('id')->get()->groupBy('resource_id');
|
||||
$revisions = DB::table('portal_resource_revisions')->whereIn('resource_id', $resources->pluck('id'))->orderByDesc('revision_no')->get(['resource_id', 'revision_no', 'change_type', 'actor_user_id', 'created_at'])->groupBy('resource_id');
|
||||
$resources->each(function ($resource) use ($links, $revisions) {
|
||||
@@ -328,7 +332,7 @@ class PortalServicesController extends Controller
|
||||
});
|
||||
|
||||
return Inertia::render('portal/manage', [
|
||||
'managementFilters' => $managementFilters, 'associationOptions' => $this->contentAssociations($request),
|
||||
'editorialWorkflowEnabled' => app(PortalEditorialWorkflow::class)->enabled(), 'managementFilters' => $managementFilters, 'associationOptions' => $this->contentAssociations($request),
|
||||
'documentAccessOnly' => ! $editorial, 'editorialScopeOnly' => $editorial, 'resources' => $resources, 'partnerships' => [], 'territories' => [],
|
||||
'requirementCatalogue' => [], 'requirementAssociationsEnabled' => false,
|
||||
'disclosureAuthorization' => null, 'incompleteCount' => $resources->filter(fn ($r) => ! $r->description || ! $r->issuer || ! $r->source_url || $r->source_confirmation === 'unconfirmed' || $r->legal_status === 'unknown' || $r->rights_status === 'unconfirmed')->count(), 'possibleDuplicates' => $resources->whereNotNull('duplicate_of_resource_id')->count(),
|
||||
@@ -353,12 +357,15 @@ class PortalServicesController extends Controller
|
||||
$resources->each(fn ($resource) => $resource->links = $links->get($resource->id, collect())->values());
|
||||
$revisions = DB::table('portal_resource_revisions')->whereIn('resource_id', $resources->pluck('id'))->orderByDesc('revision_no')->get(['resource_id', 'revision_no', 'change_type', 'actor_user_id', 'created_at'])->groupBy('resource_id');
|
||||
$resources->each(fn ($resource) => $resource->revisions = $revisions->get($resource->id, collect())->values());
|
||||
if (app(PortalEditorialWorkflow::class)->enabled()) {
|
||||
$resources = $resources->map(fn ($r) => app(PortalEditorialWorkflow::class)->workingResource($r));
|
||||
}
|
||||
|
||||
$malwareHealth = $scanner->health();
|
||||
$malwareHealth['signatures_at'] = $malwareHealth['signatures_at']?->format(DATE_ATOM);
|
||||
|
||||
return Inertia::render('portal/manage', ['resources' => $resources,
|
||||
'managementFilters' => $managementFilters, 'associationOptions' => $this->contentAssociations($request),
|
||||
'editorialWorkflowEnabled' => app(PortalEditorialWorkflow::class)->enabled(), 'managementFilters' => $managementFilters, 'associationOptions' => $this->contentAssociations($request),
|
||||
'requirementCatalogue' => $this->requirementEvidenceEnabled()
|
||||
? collect(is_array($catalogue = config('document-requirements.catalogue', [])) ? $catalogue : [])->map(fn ($item, $key) => ['key' => $key, ...$item])->values()
|
||||
: collect(),
|
||||
@@ -474,9 +481,15 @@ class PortalServicesController extends Controller
|
||||
$duplicate = $identity ? DB::table('portal_resources')->where('document_identity_key', $identity)->value('id') : null;
|
||||
$fields = $this->resourceFields($data, $request->user()->id, $identity, $duplicate, true);
|
||||
$this->assertPublicClassificationEligible($data, $document);
|
||||
$workflow = app(PortalEditorialWorkflow::class);
|
||||
$id = DB::table('portal_resources')->insertGetId([...$fields, 'approved_by' => null, 'published' => false, 'created_at' => now(), 'updated_at' => now()]);
|
||||
$this->syncResourceLinks($id, $data['associations'] ?? []);
|
||||
$this->recordResourceRevision($id, 'created', $request->user()->id, 1);
|
||||
if ($workflow->enabled()) {
|
||||
$workflow->save($request->user(), $id, 1, ['_document' => ['issuer' => ($data['issuer'] ?? null) ?: $document->issuer, 'issued_on' => ($data['document_date'] ?? null) ?: $document->issued_on, 'rights_status' => $data['rights_status'], 'data_classification' => $data['data_classification'] ?? $document->data_classification]]);
|
||||
|
||||
return $id;
|
||||
}
|
||||
DB::table('documents')->where('id', $document->id)->update(['issuer' => ($data['issuer'] ?? null) ?: $document->issuer,
|
||||
'issued_on' => ($data['document_date'] ?? null) ?: $document->issued_on, 'rights_status' => $data['rights_status'],
|
||||
'data_classification' => $data['data_classification'] ?? $document->data_classification, 'updated_at' => now()]);
|
||||
@@ -496,7 +509,9 @@ class PortalServicesController extends Controller
|
||||
$resource = DB::table('portal_resources')->where('id', $id)->lockForUpdate()->first();
|
||||
abort_unless($resource !== null, 404);
|
||||
app(DocumentManagementAccess::class)->assertEditableResource($request->user(), $id);
|
||||
abort_if((int) $resource->metadata_version !== $expectedVersion, 409, 'O documento foi alterado por outra sessão. Recarregue a revisão antes de editar.');
|
||||
$workflow = app(PortalEditorialWorkflow::class);
|
||||
$current = $workflow->enabled() ? $workflow->workingResource($resource) : $resource;
|
||||
abort_if((int) $current->metadata_version !== $expectedVersion, 409, 'O documento foi alterado por outra sessão. Recarregue a revisão antes de editar.');
|
||||
$document = DB::table('documents')->where('id', $data['document_id'])->where('status', '!=', 'deleted')->whereNull('deleted_at')->lockForUpdate()->first();
|
||||
abort_unless($document !== null, 422, 'Seleccione um documento existente e não eliminado.');
|
||||
abort_unless(app(DocumentManagementAccess::class)->documents($request->user())->where('id', $document->id)->exists(), 403);
|
||||
@@ -507,6 +522,12 @@ class PortalServicesController extends Controller
|
||||
$duplicate = $identity ? DB::table('portal_resources')->where('document_identity_key', $identity)->where('id', '!=', $id)->value('id') : null;
|
||||
$fields = $this->resourceFields($data, $request->user()->id, $identity, $duplicate, false);
|
||||
$this->assertPublicClassificationEligible($data, $document);
|
||||
$workflow = app(PortalEditorialWorkflow::class);
|
||||
if ($workflow->enabled()) {
|
||||
$workflow->save($request->user(), $id, $expectedVersion, [...$fields, '_document' => ['issuer' => ($data['issuer'] ?? null) ?: $document->issuer, 'issued_on' => ($data['document_date'] ?? null) ?: $document->issued_on, 'rights_status' => $data['rights_status'], 'data_classification' => $data['data_classification'] ?? $document->data_classification]], $data['associations'] ?? []);
|
||||
|
||||
return;
|
||||
}
|
||||
$retainPublication = (bool) $resource->published && $data['rights_status'] === 'authorized'
|
||||
&& $data['source_confirmation'] !== 'unconfirmed' && ! empty($data['source_url'])
|
||||
&& (! ($data['legislation'] || $data['document_type'] === 'legislation') || $data['source_confirmation'] === 'official_confirmed')
|
||||
@@ -523,7 +544,7 @@ class PortalServicesController extends Controller
|
||||
$this->audit($request, 'resource_metadata_updated', 'portal_resources', $id);
|
||||
});
|
||||
|
||||
return to_route('services.manage')->with('success', 'Metadados e relações actualizados; o estado de publicação foi preservado.');
|
||||
return to_route('services.manage')->with('success', 'Edição guardada; confira o circuito editorial quando estiver activo. A versão pública é preservada nesse circuito.');
|
||||
}
|
||||
|
||||
public function moderate(Request $request, string $type, int $id): RedirectResponse
|
||||
@@ -537,6 +558,12 @@ class PortalServicesController extends Controller
|
||||
return redirect('/parcerias/'.$id)->with('success', 'Reveja a proposta e registe uma decisão fundamentada na ficha da Bolsa.');
|
||||
}
|
||||
$data = $this->validateManagement($request, ['published' => ['required', 'boolean']]);
|
||||
if ($type === 'resources' && app(PortalEditorialWorkflow::class)->enabled()) {
|
||||
app(PortalEditorialWorkflow::class)->authorize($request->user(), $id, true);
|
||||
}
|
||||
if ($type === 'resources' && $data['published'] && app(PortalEditorialWorkflow::class)->enabled()) {
|
||||
abort(409, 'Use a aprovação e publicação explícitas da edição no circuito editorial.');
|
||||
}
|
||||
$table = $type === 'resources' ? 'portal_resources' : 'portal_partnerships';
|
||||
DB::transaction(function () use ($request, $data, $table, $type, $id) {
|
||||
abort_unless(DB::table($table)->where('id', $id)->lockForUpdate()->first() !== null, 404);
|
||||
@@ -582,6 +609,7 @@ class PortalServicesController extends Controller
|
||||
|
||||
public function applyDocumentDisclosureAuthorization(Request $request): RedirectResponse
|
||||
{
|
||||
abort_if(app(PortalEditorialWorkflow::class)->enabled(), 409, 'A divulgação de novas edições requer aprovação e publicação no circuito editorial.');
|
||||
$event = DB::table('audit_events')->where('source_system', 'user-instruction')
|
||||
->where('external_id', 'document-repository-disclosure-authorization-2026-10-06-v1')
|
||||
->where('action', 'public_disclosure_authorized')->first();
|
||||
|
||||
Reference in New Issue
Block a user