Files
Verde-Web/app/Console/Commands/PsgcImport.php
admin c8404564fe feat(backend): complete Modules 2 + 3 (geo + user management)
Module 2 — Geographic Data: PSGC tables (regions/provinces/cities/
barangays) with native geometry(polygon|point, 4326) columns; cascading
dropdown endpoints; ST_Contains-based GPS resolution; service-area CRUD
with barangay attach/detach; SamplePsgcSeeder + psgc:import command.

Module 3 — User Management: 5 role-specific profile tables (driver/
helper/scanner/store_partner with verification_status + rejection
reason); profile auto-created on register; admin user CRUD with
filters/pagination, suspend/activate, profile approve/reject;
self-service /me + /me/profile with role-aware validation that blocks
self-approval and resets rejected profiles to pending on resubmit.

69 feature tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:59:56 +08:00

126 lines
4.6 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\Barangay;
use App\Models\CityMunicipality;
use App\Models\Province;
use App\Models\Region;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use MatanYadaev\EloquentSpatial\Objects\Point;
use MatanYadaev\EloquentSpatial\Objects\Polygon;
use Throwable;
class PsgcImport extends Command
{
protected $signature = 'psgc:import {file : Path to PSGC JSON file}';
protected $description = 'Import PSGC hierarchy (regions/provinces/cities/barangays) from JSON';
public function handle(): int
{
$path = $this->argument('file');
if (! is_file($path)) {
$this->error("File not found: {$path}");
return self::FAILURE;
}
$payload = json_decode((string) file_get_contents($path), true);
if (! is_array($payload)) {
$this->error('Invalid JSON');
return self::FAILURE;
}
$counts = ['regions' => 0, 'provinces' => 0, 'cities' => 0, 'barangays' => 0];
try {
DB::transaction(function () use ($payload, &$counts) {
foreach ($payload['regions'] ?? [] as $r) {
Region::updateOrCreate(
['psgc_code' => $r['psgc_code']],
[
'code' => $r['code'],
'name' => $r['name'],
'island_group' => $r['island_group'] ?? null,
],
);
$counts['regions']++;
}
foreach ($payload['provinces'] ?? [] as $p) {
$region = Region::where('psgc_code', $p['region_psgc'])->firstOrFail();
Province::updateOrCreate(
['psgc_code' => $p['psgc_code']],
[
'code' => $p['code'],
'name' => $p['name'],
'region_id' => $region->id,
'income_classification' => $p['income_classification'] ?? null,
],
);
$counts['provinces']++;
}
foreach ($payload['cities_municipalities'] ?? [] as $c) {
$province = Province::where('psgc_code', $c['province_psgc'])->firstOrFail();
CityMunicipality::updateOrCreate(
['psgc_code' => $c['psgc_code']],
[
'code' => $c['code'],
'name' => $c['name'],
'province_id' => $province->id,
'type' => $c['type'] ?? 'municipality',
'is_capital' => (bool) ($c['is_capital'] ?? false),
'classification' => $c['classification'] ?? null,
],
);
$counts['cities']++;
}
foreach ($payload['barangays'] ?? [] as $b) {
$city = CityMunicipality::where('psgc_code', $b['city_psgc'])->firstOrFail();
$boundary = isset($b['boundary_geojson'])
? Polygon::fromJson(json_encode($b['boundary_geojson']))
: null;
$centroid = isset($b['centroid'])
? new Point((float) $b['centroid']['lat'], (float) $b['centroid']['lng'], 4326)
: null;
Barangay::updateOrCreate(
['psgc_code' => $b['psgc_code']],
[
'code' => $b['code'],
'name' => $b['name'],
'city_municipality_id' => $city->id,
'urban_rural' => $b['urban_rural'] ?? Barangay::URBAN_RURAL_UNKNOWN,
'population' => $b['population'] ?? null,
'boundary' => $boundary,
'centroid' => $centroid,
],
);
$counts['barangays']++;
}
});
} catch (Throwable $e) {
$this->error('Import failed: '.$e->getMessage());
return self::FAILURE;
}
$this->info(sprintf(
'Imported %d regions, %d provinces, %d cities, %d barangays',
$counts['regions'],
$counts['provinces'],
$counts['cities'],
$counts['barangays'],
));
return self::SUCCESS;
}
}