166 lines
9.3 KiB
PHP
166 lines
9.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\ClusterProject;
|
|
use App\Models\DevelopmentCorridor;
|
|
use App\Models\EconomicCluster;
|
|
use App\Models\InvestmentGap;
|
|
use App\Models\Project;
|
|
use App\Models\ValueChain;
|
|
use App\Services\InvestmentGeography;
|
|
use App\Services\PortalAudit;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Validation\Rule;
|
|
use Inertia\Inertia;
|
|
use Inertia\Response;
|
|
|
|
class InvestmentStructureController extends Controller
|
|
{
|
|
public function index(Request $request, string $structure): Response
|
|
{
|
|
$filters = $request->validate(['q' => ['nullable', 'string', 'max:150']]);
|
|
$model = $this->model($structure);
|
|
$query = $model::query();
|
|
if ($term = $filters['q'] ?? null) {
|
|
$query->where(function ($q) use ($term): void {
|
|
$q->whereLike('name', "%{$term}%")->orWhereLike('code', "%{$term}%");
|
|
});
|
|
}
|
|
|
|
return Inertia::render('structures/index', ['structure' => $structure, 'filters' => $filters,
|
|
'records' => $query->orderBy('name')->orderBy('id')->paginate(20)->withQueryString()]);
|
|
}
|
|
|
|
public function edit(Request $request, string $structure, ?string $id = null): Response
|
|
{
|
|
$model = $this->model($structure);
|
|
$record = $id ? $model::findOrFail($id) : null;
|
|
|
|
return Inertia::render('structures/editor', ['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() : [],
|
|
'chainLinks' => $structure === 'clusters' && $id ? DB::table('value_chain_links')
|
|
->join('value_chains as origins', 'origins.id', '=', 'value_chain_links.from_value_chain_id')
|
|
->join('value_chains as destinations', 'destinations.id', '=', 'value_chain_links.to_value_chain_id')
|
|
->where('origins.cluster_id', $id)->where('destinations.cluster_id', $id)
|
|
->whereNull('origins.deleted_at')->whereNull('destinations.deleted_at')->whereNull('value_chain_links.deleted_at')
|
|
->orderBy('origins.code')->orderBy('destinations.code')
|
|
->get(['value_chain_links.id', 'origins.name as origin', 'destinations.name as destination', 'link_type']) : [],
|
|
'competitiveness' => $structure === 'clusters' && $id ? app(\App\Services\ClusterCompetitiveness::class)->management($id) : null,
|
|
'viewerId' => $request->user()->id,
|
|
'memberships' => $structure === 'clusters' && $id ? DB::table('cluster_projects')
|
|
->join('projects', 'projects.id', '=', 'cluster_projects.project_id')
|
|
->where('cluster_id', $id)->whereNull('cluster_projects.deleted_at')->whereNull('projects.deleted_at')
|
|
->orderBy('projects.name')->get(['cluster_projects.id', 'cluster_projects.project_id', 'cluster_projects.role', 'cluster_projects.association_type', 'cluster_projects.source_reference', 'cluster_projects.evidence_reference', 'cluster_projects.valid_from', 'cluster_projects.valid_until', 'cluster_projects.validation_status', 'cluster_projects.version_no', 'cluster_projects.editor_id', 'projects.name']) : [],
|
|
]);
|
|
}
|
|
|
|
public function save(Request $request, string $structure, ?string $id = null): RedirectResponse
|
|
{
|
|
$isProject = $structure === 'projects';
|
|
$isCluster = $structure === 'clusters';
|
|
$isCorridor = $structure === 'corridors';
|
|
$model = $this->model($structure);
|
|
$record = $id ? $model::findOrFail($id) : new $model;
|
|
$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 ($isProject || $isCorridor) {
|
|
$rules['description'] = ['nullable', 'string', 'max:10000'];
|
|
}
|
|
if ($isProject) {
|
|
$rules += [
|
|
'budget_amount' => ['nullable', 'required_with:budget_currency', 'regex:/^\d{1,18}(\.\d{1,2})?$/'],
|
|
'budget_currency' => ['nullable', 'required_with:budget_amount', 'regex:/^[A-Z]{3}$/'],
|
|
'start_date' => ['nullable', 'date_format:Y-m-d'],
|
|
'end_date' => ['nullable', 'date_format:Y-m-d', ...($request->filled('start_date') ? ['after_or_equal:start_date'] : [])]];
|
|
} elseif ($isCluster) {
|
|
$rules['sector'] = ['required', 'string', 'max:200'];
|
|
}
|
|
if (! $id) {
|
|
foreach (InvestmentGeography::FIELDS as $field => $type) {
|
|
$rules[$field] = ['nullable', 'uuid'];
|
|
}
|
|
}
|
|
$data = $request->validate($rules);
|
|
if (! $id && $request->has('province_id')) {
|
|
$path = app(InvestmentGeography::class)->selection($data, true);
|
|
$data = array_diff_key($data, InvestmentGeography::FIELDS);
|
|
$data['primary_territory_id'] = $path === [] ? null : $path[array_key_last($path)]->id;
|
|
$data['territory_version'] = $path === [] ? 0 : 1;
|
|
}
|
|
PortalAudit::save($record, $data, $request->user()->id);
|
|
|
|
return redirect('/management/'.$structure.'/'.$record->getKey().'/edit')->with('success', 'Registo guardado.');
|
|
}
|
|
|
|
/** @return class-string<Project|EconomicCluster|DevelopmentCorridor> */
|
|
private function model(string $structure): string
|
|
{
|
|
return match ($structure) {
|
|
'projects' => Project::class,
|
|
'clusters' => EconomicCluster::class,
|
|
'corridors' => DevelopmentCorridor::class,
|
|
default => abort(404),
|
|
};
|
|
}
|
|
|
|
public function linkProject(Request $request, EconomicCluster $cluster): RedirectResponse
|
|
{
|
|
$data = $request->validate(['project_id' => ['required', 'uuid', Rule::exists('projects', 'id')->whereNull('deleted_at')],
|
|
'version_no' => ['required', 'integer', 'min:0'],
|
|
'role' => ['required', Rule::in(['anchor', 'complementary', 'supporting'])],
|
|
'association_type' => ['required', Rule::in(['official', 'influence', 'proximity', 'potential', 'historical'])],
|
|
'source_reference' => ['required', 'string', 'min:10', 'max:2000'],
|
|
'evidence_reference' => ['required', 'string', 'min:3', 'max:2000'],
|
|
'valid_from' => ['required', 'date_format:Y-m-d'],
|
|
'valid_until' => ['nullable', 'date_format:Y-m-d', 'after_or_equal:valid_from']]);
|
|
DB::transaction(function () use ($cluster, $data, $request) {
|
|
EconomicCluster::whereKey($cluster->id)->lockForUpdate()->firstOrFail();
|
|
$link = ClusterProject::where('cluster_id', $cluster->id)->where('project_id', $data['project_id'])->whereNull('deleted_at')->lockForUpdate()->first();
|
|
if ($link) {
|
|
abort_if((int) $link->version_no !== (int) $data['version_no'], 409, 'Versao da associacao alterada.');
|
|
$changed = false;
|
|
foreach (['role', 'association_type', 'source_reference', 'evidence_reference', 'valid_from', 'valid_until'] as $field) {
|
|
$current = $link->getAttribute($field);
|
|
$current = $current instanceof \DateTimeInterface ? $current->format('Y-m-d') : $current;
|
|
if (($current ?: null) !== (($data[$field] ?? null) ?: null)) {
|
|
$changed = true;
|
|
break;
|
|
}
|
|
}
|
|
if (! $changed) {
|
|
return;
|
|
}
|
|
} else {
|
|
abort_if((int) $data['version_no'] !== 0, 409, 'A associacao foi alterada. Actualize a pagina.');
|
|
$link = new ClusterProject;
|
|
}
|
|
unset($data['version_no']);
|
|
$nextVersion = $link->exists ? ((int) $link->version_no + 1) : 1;
|
|
PortalAudit::save($link, [...$data, 'cluster_id' => $cluster->id, 'validation_status' => 'pending',
|
|
'editor_id' => $request->user()->id, 'validator_id' => null, 'version_no' => $nextVersion], $request->user()->id);
|
|
});
|
|
|
|
return back()->with('success', 'Associação ao projecto guardada.');
|
|
}
|
|
|
|
public function saveChain(Request $request, EconomicCluster $cluster, ?ValueChain $chain = null): RedirectResponse
|
|
{
|
|
abort_if($chain && $chain->cluster_id !== $cluster->id, 404);
|
|
$data = $request->validate(['code' => ['required', 'string', 'max:100',
|
|
Rule::unique('value_chains', 'code')->where('cluster_id', $cluster->id)->whereNull('deleted_at')->ignore($chain?->id)],
|
|
'name' => ['required', 'string', 'max:200'],
|
|
'stage' => ['required', Rule::in(['input', 'production', 'processing', 'logistics', 'market'])]]);
|
|
PortalAudit::save($chain ?? new ValueChain, [...$data, 'cluster_id' => $cluster->id], $request->user()->id);
|
|
|
|
return back()->with('success', 'Etapa da cadeia de valor guardada.');
|
|
}
|
|
}
|