feat(editorial): stage portfolio base data and territory for atomic publication
This commit is contained in:
@@ -11,6 +11,7 @@ use App\Models\ValueChain;
|
||||
use App\Services\ClusterCompetitiveness;
|
||||
use App\Services\InvestmentGeography;
|
||||
use App\Services\PortalAudit;
|
||||
use App\Services\PortalEditorialWorkflow;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -48,8 +49,12 @@ class InvestmentStructureController extends Controller
|
||||
{
|
||||
$model = $this->model($structure);
|
||||
$record = $id ? $model::findOrFail($id) : null;
|
||||
$editorialDossier = $id ? app(PortalEditorialWorkflow::class)->structureDossier($structure, $id) : null;
|
||||
if ($record && $editorialDossier) {
|
||||
$record = app(PortalEditorialWorkflow::class)->workingStructure($record, $editorialDossier);
|
||||
}
|
||||
|
||||
return Inertia::render('structures/editor', ['structure' => $structure, 'record' => $record,
|
||||
return Inertia::render('structures/editor', ['editorialDossier' => $editorialDossier?->id, 'structure' => $structure, 'record' => $record,
|
||||
'projects' => $structure === 'clusters' && $id ? Project::orderBy('name')->get(['id', 'name', 'code']) : [],
|
||||
'chains' => $structure === 'clusters' && $id ? ValueChain::where('cluster_id', $id)->orderBy('code')->get() : [],
|
||||
'gaps' => $structure === 'clusters' && $id ? InvestmentGap::where('cluster_id', $id)->orderBy('created_at')->get() : [],
|
||||
@@ -76,11 +81,15 @@ class InvestmentStructureController extends Controller
|
||||
$isCorridor = $structure === 'corridors';
|
||||
$model = $this->model($structure);
|
||||
$record = $id ? $model::findOrFail($id) : new $model;
|
||||
$editorialDossier = $id ? app(PortalEditorialWorkflow::class)->structureDossier($structure, $id, true) : null;
|
||||
$rules = [
|
||||
'code' => ['required', 'string', 'max:100', Rule::unique($record->getTable(), 'code')->whereNull('deleted_at')->ignore($record->getKey())],
|
||||
'name' => ['required', 'string', 'max:200'],
|
||||
'status' => ['required', Rule::in($isProject ? ['draft', 'planned', 'active', 'suspended', 'completed', 'cancelled'] : ($isCluster ? ['planned', 'active', 'inactive'] : ['proposed', 'active', 'closed']))],
|
||||
];
|
||||
if ($editorialDossier) {
|
||||
$rules['editorial_version'] = ['required', 'integer', 'min:1'];
|
||||
}
|
||||
if ($isProject || $isCorridor) {
|
||||
$rules['description'] = ['nullable', 'string', 'max:10000'];
|
||||
}
|
||||
@@ -105,6 +114,11 @@ class InvestmentStructureController extends Controller
|
||||
$data['primary_territory_id'] = $path === [] ? null : $path[array_key_last($path)]->id;
|
||||
$data['territory_version'] = $path === [] ? 0 : 1;
|
||||
}
|
||||
if ($editorialDossier) {
|
||||
app(PortalEditorialWorkflow::class)->saveStructure($request->user(), $record, $editorialDossier, (int) $data['editorial_version'], $data);
|
||||
|
||||
return redirect('/management/'.$structure.'/'.$record->getKey().'/edit')->with('success', 'Dados-base guardados numa edição privada da carteira. A publicação anterior está preservada.');
|
||||
}
|
||||
PortalAudit::save($record, $data, $request->user()->id);
|
||||
|
||||
return redirect('/management/'.$structure.'/'.$record->getKey().'/edit')->with('success', 'Registo guardado.');
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\DevelopmentCorridor;
|
||||
use App\Models\EconomicCluster;
|
||||
use App\Models\InvestmentDossier;
|
||||
use App\Models\Opportunity;
|
||||
use App\Models\Project;
|
||||
use App\Services\InvestmentZones;
|
||||
use App\Services\PortalEditorialWorkflow;
|
||||
use App\Services\PortalProfileAccess;
|
||||
use App\Services\TerritoryGeometry;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -82,6 +87,12 @@ class InvestmentTerritoryController extends Controller
|
||||
$table = self::TABLES[$kind] ?? abort(404);
|
||||
$record = DB::table($table)->where('id', $id)->whereNull('deleted_at')->first();
|
||||
abort_unless($record !== null, 404);
|
||||
if ($dossier = app(PortalEditorialWorkflow::class)->structureDossier($kind, $id)) {
|
||||
$model = ['projects' => Project::class, 'clusters' => EconomicCluster::class, 'corridors' => DevelopmentCorridor::class][$kind] ?? abort(404);
|
||||
$working = app(PortalEditorialWorkflow::class)->workingStructure($model::findOrFail($id), $dossier);
|
||||
$record->primary_territory_id = $working->getAttribute('primary_territory_id');
|
||||
$record->territory_version = $working->getAttribute('territory_version');
|
||||
}
|
||||
$path = [];
|
||||
$current = $record->primary_territory_id;
|
||||
for ($i = 0; $current && $i < 5; $i++) {
|
||||
@@ -93,7 +104,7 @@ class InvestmentTerritoryController extends Controller
|
||||
$current = $location->parent_id;
|
||||
}
|
||||
|
||||
return response()->json(['version' => $record->territory_version, 'path' => $path]);
|
||||
return response()->json(['version' => $record->territory_version, 'path' => $path])->header('Cache-Control', 'private, no-store');
|
||||
}
|
||||
|
||||
public function save(Request $request, string $kind, string $id): JsonResponse
|
||||
@@ -103,11 +114,20 @@ class InvestmentTerritoryController extends Controller
|
||||
abort_unless($request->user()->two_factor_confirmed_at && $request->user()->two_factor_secret, 403);
|
||||
}
|
||||
$data = $request->validate(['version' => ['required', 'integer', 'min:0'], 'path' => ['present', 'array', 'max:4'], 'path.*' => ['required', 'uuid', 'distinct']]);
|
||||
$version = DB::transaction(function () use ($request, $table, $id, $data) {
|
||||
$dossier = app(PortalEditorialWorkflow::class)->structureDossier($kind, $id, true);
|
||||
$version = DB::transaction(function () use ($request, $table, $id, $data, $dossier, $kind) {
|
||||
if ($dossier) {
|
||||
$dossier = InvestmentDossier::whereKey($dossier->id)->lockForUpdate()->firstOrFail();
|
||||
}
|
||||
DB::select('SELECT pg_advisory_xact_lock(280926, 150000)');
|
||||
$record = DB::table($table)->where('id', $id)->whereNull('deleted_at')->lockForUpdate()->first();
|
||||
abort_unless($record !== null, 404);
|
||||
abort_unless((int) $record->territory_version === $data['version'], 409, 'Localização alterada. Recarregue a ficha.');
|
||||
$effectiveVersion = (int) $record->territory_version;
|
||||
if ($dossier) {
|
||||
$model = ['projects' => Project::class, 'clusters' => EconomicCluster::class, 'corridors' => DevelopmentCorridor::class][$kind] ?? abort(404);
|
||||
$effectiveVersion = (int) app(PortalEditorialWorkflow::class)->workingStructure($model::findOrFail($id), $dossier)->getAttribute('territory_version');
|
||||
}
|
||||
abort_unless($effectiveVersion === $data['version'], 409, 'Localização alterada. Recarregue a ficha.');
|
||||
$parent = null;
|
||||
$names = [];
|
||||
foreach (array_values($data['path']) as $index => $locationId) {
|
||||
@@ -119,6 +139,12 @@ class InvestmentTerritoryController extends Controller
|
||||
$names[] = $location->name;
|
||||
}
|
||||
$version = $data['version'] + 1;
|
||||
if ($dossier) {
|
||||
$workflow = app(PortalEditorialWorkflow::class);
|
||||
$workflow->saveStructure($request->user(), $model::findOrFail($id), $dossier, (int) $workflow->workingPortfolio($dossier)->getAttribute('version_no'), ['primary_territory_id' => $parent, 'territory_version' => $version]);
|
||||
|
||||
return $version;
|
||||
}
|
||||
$changes = ['primary_territory_id' => $parent, 'territory_version' => $version, 'updated_at' => now()];
|
||||
if (in_array($table, ['opportunities', 'investment_zones'], true)) {
|
||||
$changes['province'] = $names[0] ?? null;
|
||||
@@ -136,6 +162,6 @@ class InvestmentTerritoryController extends Controller
|
||||
return $version;
|
||||
});
|
||||
|
||||
return response()->json(['version' => $version]);
|
||||
return response()->json(['version' => $version, 'pending_publication' => $dossier !== null]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user