varias alteracoes
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Opportunity;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class InvestmentCoordinatesController extends Controller
|
||||
{
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
Opportunity::findOrFail($id);
|
||||
$point = DB::table('geo_locations as g')->join('geo_geometries as s', 's.geo_location_id', '=', 'g.id')
|
||||
->where('g.source_system', 'investment-site-editor')->where('g.external_id', (string) $id)->whereNull('g.deleted_at')->whereNull('s.deleted_at')
|
||||
->first(['s.version_no', 's.properties', DB::raw('ST_X(s.geom) as longitude'), DB::raw('ST_Y(s.geom) as latitude')]);
|
||||
|
||||
return response()->json(['version' => $point->version_no ?? 0, 'latitude' => $point?->latitude, 'longitude' => $point?->longitude,
|
||||
'source_reference' => $point ? (json_decode($point->properties, true)['source_reference'] ?? '') : '']);
|
||||
}
|
||||
|
||||
public function save(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$data = $request->validate(['version' => ['required', 'integer', 'min:0'], 'latitude' => ['required', 'numeric', 'between:-90,90'],
|
||||
'longitude' => ['required', 'numeric', 'between:-180,180'], 'source_reference' => ['required', 'string', 'min:10', 'max:2000'],
|
||||
'confirmed_site' => ['accepted']]);
|
||||
$version = DB::transaction(function () use ($data, $id, $request) {
|
||||
DB::select('SELECT pg_advisory_xact_lock(280926, 150000)');
|
||||
$opportunity = Opportunity::whereKey($id)->lockForUpdate()->firstOrFail();
|
||||
$location = DB::table('geo_locations')->where('source_system', 'investment-site-editor')->where('external_id', (string) $id)->first();
|
||||
abort_if($location && $location->deleted_at, 409, 'Local arquivado; requer reconciliação.');
|
||||
abort_if($location && $location->data_classification !== 'public', 409, 'Local retirado de divulgação; requer revisão da classificação.');
|
||||
$geometry = $location ? DB::table('geo_geometries')->where('geo_location_id', $location->id)->where('geometry_type', 'point')->first() : null;
|
||||
abort_if($geometry && $geometry->deleted_at, 409, 'Geometria arquivada; requer reconciliação.');
|
||||
abort_if($geometry && $geometry->data_classification !== 'public', 409, 'Geometria retirada de divulgação; requer revisão da classificação.');
|
||||
abort_unless((int) ($geometry->version_no ?? 0) === $data['version'], 409, 'Coordenadas alteradas. Recarregue.');
|
||||
$locationId = $location->id ?? (string) Str::uuid();
|
||||
if (! $location) {
|
||||
DB::table('geo_locations')->insert(['id' => $locationId, 'source_system' => 'investment-site-editor', 'external_id' => (string) $id,
|
||||
'name' => $opportunity->title, 'location_type' => 'site', 'code' => 'SITE-OPP-'.$id, 'data_classification' => 'public', 'parent_id' => $opportunity->primary_territory_id]);
|
||||
}
|
||||
$version = $data['version'] + 1;
|
||||
$geometryId = $geometry->id ?? (string) Str::uuid();
|
||||
DB::statement("INSERT INTO geo_geometries(id,geo_location_id,geometry_type,geom,data_classification,version_no,properties)
|
||||
VALUES(?,?,'point',ST_SetSRID(ST_MakePoint(?,?),4326),'public',?,?::jsonb)
|
||||
ON CONFLICT(id) DO UPDATE SET geom=EXCLUDED.geom,version_no=EXCLUDED.version_no,properties=EXCLUDED.properties,updated_at=now()",
|
||||
[$geometryId, $locationId, $data['longitude'], $data['latitude'], $version, json_encode(['source_reference' => $data['source_reference'], 'accuracy' => 'site_confirmed_by_editor'], JSON_THROW_ON_ERROR)]);
|
||||
DB::table('opportunity_locations')->updateOrInsert(['opportunity_id' => $id, 'geo_location_id' => $locationId], []);
|
||||
DB::table('audit_events')->insert(['source_system' => 'investment-coordinates', 'entity_type' => 'geo_geometries', 'entity_id' => $geometryId,
|
||||
'actor_user_id' => $request->user()->id, 'action' => 'site_coordinates_saved',
|
||||
'before_data' => $geometry ? json_encode(['version' => $geometry->version_no, 'geometry_hex' => $geometry->geom], JSON_THROW_ON_ERROR) : null,
|
||||
'after_data' => json_encode(['opportunity_id' => $id, 'version' => $version, 'latitude' => $data['latitude'], 'longitude' => $data['longitude'], 'source_sha256' => hash('sha256', $data['source_reference'])], JSON_THROW_ON_ERROR)]);
|
||||
|
||||
return $version;
|
||||
});
|
||||
|
||||
return response()->json(['version' => $version]);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
||||
use App\Models\InvestmentDossier;
|
||||
use App\Models\Opportunity;
|
||||
use App\Services\InvestmentCatalogue;
|
||||
use App\Services\InvestmentGeography;
|
||||
use App\Services\InvestmentZones;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -24,6 +25,7 @@ class InvestmentDiscoveryController extends Controller
|
||||
}
|
||||
|
||||
return Inertia::render('investment/catalogue', ['filters' => $filters, 'references' => $catalogue->references(),
|
||||
'territorySelection' => app(InvestmentGeography::class)->selection($filters),
|
||||
'results' => $catalogue->query($filters)->paginate(12)->withQueryString(),
|
||||
'favourites' => $request->user() ? DB::table('opportunity_favourites')->where('user_id', $request->user()->id)->pluck('opportunity_id') : [],
|
||||
'territorial' => $this->territorial($filters)]);
|
||||
@@ -36,6 +38,15 @@ class InvestmentDiscoveryController extends Controller
|
||||
private function territorial(array $filters): Collection
|
||||
{
|
||||
$query = InvestmentPortfolioController::visible();
|
||||
$path = app(InvestmentGeography::class)->selection($filters);
|
||||
if ($path !== []) {
|
||||
$ids = app(InvestmentGeography::class)->descendants($path[array_key_last($path)]->id);
|
||||
$query->where(function ($query) use ($ids) {
|
||||
foreach (['project_id' => 'projects', 'cluster_id' => 'economic_clusters', 'corridor_id' => 'development_corridors'] as $foreign => $table) {
|
||||
$query->orWhereIn($foreign, DB::table($table)->whereNull('deleted_at')->whereIn('primary_territory_id', $ids)->select('id'));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (in_array($filters['dimension'] ?? null, ['zee', 'zfi'], true)) {
|
||||
return $query->whereRaw('false')->get();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ 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;
|
||||
@@ -74,7 +75,19 @@ class InvestmentStructureController extends Controller
|
||||
} else {
|
||||
$rules['sector'] = ['required', 'string', 'max:200'];
|
||||
}
|
||||
PortalAudit::save($record, $request->validate($rules), $request->user()->id);
|
||||
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);
|
||||
$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.');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\InvestmentZones;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class InvestmentTerritoryController extends Controller
|
||||
{
|
||||
private const TABLES = ['opportunities' => 'opportunities', 'projects' => 'projects', 'clusters' => 'economic_clusters', 'corridors' => 'development_corridors', 'zones' => 'investment_zones'];
|
||||
|
||||
public function subjects(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->validate(['kind' => ['required', 'in:'.implode(',', array_keys(self::TABLES))], 'q' => ['nullable', 'string', 'max:100'], 'page' => ['nullable', 'integer', 'min:1']]);
|
||||
$column = $data['kind'] === 'opportunities' ? 'title' : 'name';
|
||||
$query = DB::table(self::TABLES[$data['kind']])->whereNull('deleted_at');
|
||||
if (! empty($data['q'])) {
|
||||
$query->whereRaw($column.' ILIKE ?', ['%'.addcslashes($data['q'], '%_\\').'%']);
|
||||
}
|
||||
|
||||
return response()->json($query->orderBy($column)->orderBy('id')->paginate(50, ['id', $column.' as name']));
|
||||
}
|
||||
|
||||
public function options(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->validate(['type' => ['required', 'in:province,district,admin_post,locality'], 'parent_id' => ['nullable', 'uuid'], 'q' => ['nullable', 'string', 'max:100'], 'page' => ['nullable', 'integer', 'min:1']]);
|
||||
$query = DB::table('geo_locations')->whereNull('deleted_at')->where('location_type', $data['type'])
|
||||
->whereIn('data_classification', ['public', 'internal']);
|
||||
if ($data['type'] !== 'province') {
|
||||
$query->where('parent_id', $data['parent_id'] ?? '00000000-0000-0000-0000-000000000000');
|
||||
}
|
||||
if (! empty($data['q'])) {
|
||||
$query->whereRaw('name ILIKE ?', ['%'.addcslashes($data['q'], '%_\\').'%']);
|
||||
}
|
||||
|
||||
return response()->json($query->orderBy('name')->orderBy('id')->paginate(100, ['id', 'name', 'parent_id', 'location_type']));
|
||||
}
|
||||
|
||||
public function show(string $kind, string $id): JsonResponse
|
||||
{
|
||||
$table = self::TABLES[$kind] ?? abort(404);
|
||||
$record = DB::table($table)->where('id', $id)->whereNull('deleted_at')->first();
|
||||
abort_unless($record !== null, 404);
|
||||
$path = [];
|
||||
$current = $record->primary_territory_id;
|
||||
for ($i = 0; $current && $i < 5; $i++) {
|
||||
$location = DB::table('geo_locations')->where('id', $current)->first(['id', 'name', 'parent_id', 'location_type', 'deleted_at']);
|
||||
if (! $location) {
|
||||
break;
|
||||
}
|
||||
$path[] = $location;
|
||||
$current = $location->parent_id;
|
||||
}
|
||||
|
||||
return response()->json(['version' => $record->territory_version, 'path' => $path]);
|
||||
}
|
||||
|
||||
public function save(Request $request, string $kind, string $id): JsonResponse
|
||||
{
|
||||
$table = self::TABLES[$kind] ?? abort(404);
|
||||
if ($kind === 'zones') {
|
||||
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) {
|
||||
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.');
|
||||
$parent = null;
|
||||
$names = [];
|
||||
foreach (array_values($data['path']) as $index => $locationId) {
|
||||
$location = DB::table('geo_locations')->where('id', $locationId)->whereNull('deleted_at')->whereIn('data_classification', ['public', 'internal'])->first();
|
||||
if (! $location || $location->location_type !== ['province', 'district', 'admin_post', 'locality'][$index] || ($index > 0 && $location->parent_id !== $parent)) {
|
||||
throw ValidationException::withMessages(['path' => 'Seleccione uma hierarquia territorial coerente e activa.']);
|
||||
}
|
||||
$parent = $locationId;
|
||||
$names[] = $location->name;
|
||||
}
|
||||
$version = $data['version'] + 1;
|
||||
$changes = ['primary_territory_id' => $parent, 'territory_version' => $version, 'updated_at' => now()];
|
||||
if (in_array($table, ['opportunities', 'investment_zones'], true)) {
|
||||
$changes['province'] = $names[0] ?? null;
|
||||
$changes['district'] = $names[1] ?? null;
|
||||
}
|
||||
if ($table === 'investment_zones') {
|
||||
app(InvestmentZones::class)->save($id, [...$changes, 'version_no' => $record->version_no], $request->user()->id);
|
||||
} else {
|
||||
DB::table($table)->where('id', $id)->update($changes);
|
||||
}
|
||||
DB::table('audit_events')->insert(['source_system' => 'investment-territory', 'entity_type' => $table,
|
||||
'actor_user_id' => $request->user()->id, 'action' => 'primary_territory_saved',
|
||||
'after_data' => json_encode(['record_id' => $id, 'territory_id' => $parent, 'version' => $version], JSON_THROW_ON_ERROR)]);
|
||||
|
||||
return $version;
|
||||
});
|
||||
|
||||
return response()->json(['version' => $version]);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use App\Models\InvestmentDossier;
|
||||
use App\Models\InvestmentZone;
|
||||
use App\Models\Opportunity;
|
||||
use App\Services\InvestmentCatalogue;
|
||||
use App\Services\InvestmentGeography;
|
||||
use App\Services\InvestmentZoneAccess;
|
||||
use App\Services\InvestmentZones;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -49,13 +50,18 @@ class InvestmentZoneController extends Controller
|
||||
|
||||
public function index(Request $request, InvestmentZones $zones): Response|JsonResponse
|
||||
{
|
||||
$filters = $request->validate(['category' => ['nullable', 'in:zee,zfi,industrial_park'], 'q' => ['nullable', 'string', 'max:120'], 'page' => ['nullable', 'integer', 'min:1']]);
|
||||
$filters = $request->validate(['category' => ['nullable', 'in:zee,zfi,industrial_park'], 'q' => ['nullable', 'string', 'max:120'], 'page' => ['nullable', 'integer', 'min:1'],
|
||||
'province_id' => ['nullable', 'uuid'], 'district_id' => ['nullable', 'uuid'], 'admin_post_id' => ['nullable', 'uuid'], 'locality_id' => ['nullable', 'uuid']]);
|
||||
$management = $request->is('management/*', 'api/v2/investment/admin/*');
|
||||
$query = $management ? InvestmentZone::query() : $zones->visible();
|
||||
$path = app(InvestmentGeography::class)->selection($filters);
|
||||
if ($path !== []) {
|
||||
$query->whereIn('primary_territory_id', app(InvestmentGeography::class)->descendants($path[array_key_last($path)]->id));
|
||||
}
|
||||
$rows = $query->when($filters['category'] ?? null, fn ($q, $v) => $q->where('category', $v))
|
||||
->when($filters['q'] ?? null, fn ($q, $v) => $q->where(fn ($q) => $q->where('name', 'ilike', '%'.$v.'%')->orWhere('code', 'ilike', '%'.$v.'%')))
|
||||
->orderBy('name')->orderBy('id')->paginate(12, $management ? ['*'] : ['id', 'code', 'name', 'name_en', 'category', 'province', 'district', 'description', 'description_en', 'validation_status', 'updated_at'])->withQueryString();
|
||||
$props = ['records' => $rows, 'filters' => $filters, 'management' => $management];
|
||||
$props = ['records' => $rows, 'filters' => $filters, 'management' => $management, 'territorySelection' => $path];
|
||||
|
||||
return $request->is('api/*') ? response()->json($props) : Inertia::render('investment/zones', $props);
|
||||
}
|
||||
@@ -70,6 +76,15 @@ class InvestmentZoneController extends Controller
|
||||
'operator_id' => ['nullable', 'uuid', Rule::exists('organizations', 'id')->whereNull('deleted_at')],
|
||||
'legal_source_id' => ['nullable', 'uuid', 'exists:investment_legal_sources,id']]);
|
||||
|
||||
if (! $id && $request->has('province_id')) {
|
||||
$geo = $request->validate(['province_id' => ['nullable', 'uuid'], 'district_id' => ['nullable', 'uuid'], 'admin_post_id' => ['nullable', 'uuid'], 'locality_id' => ['nullable', 'uuid']]);
|
||||
$path = app(InvestmentGeography::class)->selection($geo);
|
||||
$data['primary_territory_id'] = $path === [] ? null : $path[array_key_last($path)]->id;
|
||||
$data['territory_version'] = $path === [] ? 0 : 1;
|
||||
$data['province'] = $path[0]->name ?? null;
|
||||
$data['district'] = $path[1]->name ?? null;
|
||||
}
|
||||
|
||||
return response()->json($zones->save($id, $data, $request->user()->id), $id ? 200 : 201);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Models\InterestRequest;
|
||||
use App\Models\InvestmentSubmission;
|
||||
use App\Models\Opportunity;
|
||||
use App\Models\Sector;
|
||||
use App\Services\InvestmentGeography;
|
||||
use App\Services\InvestmentWorkspace;
|
||||
use App\Services\PortalAudit;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -177,7 +178,20 @@ class PortalManagementController extends Controller
|
||||
'status' => ['required', Rule::in(['draft', 'published', 'archived'])],
|
||||
'is_featured' => ['required', 'boolean'],
|
||||
];
|
||||
if ($resource === 'opportunities' && ! $id) {
|
||||
foreach (InvestmentGeography::FIELDS as $field => $type) {
|
||||
$rules[$field] = ['nullable', 'uuid'];
|
||||
}
|
||||
}
|
||||
$data = $request->validate($rules);
|
||||
if ($resource === 'opportunities' && ! $id && $request->has('province_id')) {
|
||||
$path = app(InvestmentGeography::class)->selection($data);
|
||||
$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;
|
||||
$data['province'] = $path[0]->name ?? null;
|
||||
$data['district'] = $path[1]->name ?? null;
|
||||
}
|
||||
if ($resource === 'opportunities' && $data['status'] === 'published' && $record->exists
|
||||
&& Schema::hasTable('education_investment_proposals')
|
||||
&& DB::table('education_investment_proposals')->where('opportunity_id', $record->id)->exists()) {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\InvestmentGeography;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PublicGeographyController extends Controller
|
||||
{
|
||||
public function options(Request $request, InvestmentGeography $geography): JsonResponse
|
||||
{
|
||||
$data = $request->validate(['type' => ['required', 'in:province,district,admin_post,locality'], 'parent_id' => ['nullable', 'uuid'], 'q' => ['nullable', 'string', 'max:100'], 'page' => ['nullable', 'integer', 'min:1']]);
|
||||
$query = $geography->publicReferences()->where('location_type', $data['type']);
|
||||
if ($data['type'] !== 'province') {
|
||||
$parentType = ['district' => 'province', 'admin_post' => 'district', 'locality' => 'admin_post'][$data['type']];
|
||||
$parent = $geography->publicReferences()->where('id', $data['parent_id'] ?? '00000000-0000-0000-0000-000000000000')->where('location_type', $parentType)->value('id');
|
||||
$query->where('parent_id', $parent ?? '00000000-0000-0000-0000-000000000000');
|
||||
}
|
||||
if (! empty($data['q'])) {
|
||||
$query->whereRaw('name ILIKE ?', ['%'.addcslashes($data['q'], '%_\\').'%']);
|
||||
}
|
||||
|
||||
return response()->json($query->orderBy('name')->orderBy('id')->paginate(100, ['id', 'name', 'parent_id', 'location_type']));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user