104 lines
5.5 KiB
PHP
104 lines
5.5 KiB
PHP
<?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]);
|
|
}
|
|
}
|