chore: cleanup dead code + format with Pint
- Remove unused FeaturesController (empty stubs, no routes) - Remove ConvertSpatialFile CLI command (unused; service used in LayerManager) - Remove MigrateGeojsonToFeatures CLI command (one-shot migration, not referenced) - Remove .claude/worktrees/ (11 old agent worktrees from June) - Apply Laravel Pint formatting across 219 files (style only, no functional changes) Tests: 101 passing (319 assertions) API routes: unchanged (8 routes intact)
This commit is contained in:
@@ -8,16 +8,15 @@ use App\Models\Inspection;
|
||||
use App\Models\Issue;
|
||||
use App\Models\Media;
|
||||
use App\Models\Phase;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProgressSnapshot;
|
||||
use App\Models\Project;
|
||||
use App\Models\Task;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ReportGenerator
|
||||
{
|
||||
protected Project $project;
|
||||
|
||||
protected ReportFilters $filters;
|
||||
|
||||
public function __construct(Project $project, ReportFilters $filters)
|
||||
@@ -82,7 +81,7 @@ class ReportGenerator
|
||||
->with(['layers.features'])
|
||||
->get();
|
||||
|
||||
$allFeatures = $phases->flatMap(fn($p) => $p->layers->flatMap(fn($l) => $l->features));
|
||||
$allFeatures = $phases->flatMap(fn ($p) => $p->layers->flatMap(fn ($l) => $l->features));
|
||||
$completedFeatures = $allFeatures->where('status', 'completed')->count();
|
||||
|
||||
$inspectionsQuery = Inspection::where('project_id', $this->project->id);
|
||||
@@ -136,8 +135,8 @@ class ReportGenerator
|
||||
->get();
|
||||
|
||||
return $phases->map(function ($phase) {
|
||||
$phaseFeatures = $phase->layers->flatMap(fn($l) => $l->features);
|
||||
|
||||
$phaseFeatures = $phase->layers->flatMap(fn ($l) => $l->features);
|
||||
|
||||
return [
|
||||
'id' => $phase->id,
|
||||
'name' => $phase->name,
|
||||
@@ -158,7 +157,7 @@ class ReportGenerator
|
||||
'is_on_track' => $phase->is_on_track,
|
||||
'features_count' => $phaseFeatures->count(),
|
||||
'completed_features' => $phaseFeatures->where('status', 'completed')->count(),
|
||||
'layers' => $phase->layers->map(fn($l) => [
|
||||
'layers' => $phase->layers->map(fn ($l) => [
|
||||
'id' => $l->id,
|
||||
'name' => $l->name,
|
||||
'features_count' => $l->features->count(),
|
||||
@@ -169,7 +168,7 @@ class ReportGenerator
|
||||
|
||||
protected function buildFeaturesData(): array
|
||||
{
|
||||
$query = Feature::whereHas('layer.phase', fn($q) => $q->where('project_id', $this->project->id))
|
||||
$query = Feature::whereHas('layer.phase', fn ($q) => $q->where('project_id', $this->project->id))
|
||||
->with(['layer.phase', 'template', 'inspections', 'issues']);
|
||||
|
||||
// Apply date filter on feature created_at if date range provided
|
||||
@@ -179,7 +178,7 @@ class ReportGenerator
|
||||
|
||||
return $features->map(function ($feature) {
|
||||
$lastInspection = $feature->inspections->sortByDesc('created_at')->first();
|
||||
|
||||
|
||||
return [
|
||||
'id' => $feature->id,
|
||||
'name' => $feature->name,
|
||||
@@ -227,7 +226,7 @@ class ReportGenerator
|
||||
'date' => $inspection->created_at->format('d/m/Y H:i'),
|
||||
'status' => $inspection->status,
|
||||
'result' => $inspection->result,
|
||||
'result_label' => match($inspection->result) {
|
||||
'result_label' => match ($inspection->result) {
|
||||
'pass' => 'Aprobada',
|
||||
'fail' => 'Fallida',
|
||||
'conditional' => 'Condicional',
|
||||
@@ -256,7 +255,7 @@ class ReportGenerator
|
||||
'feature' => $issue->feature?->name ?? '—',
|
||||
'phase' => $issue->feature?->layer?->phase?->name ?? '—',
|
||||
'priority' => $issue->priority,
|
||||
'priority_label' => match($issue->priority) {
|
||||
'priority_label' => match ($issue->priority) {
|
||||
'low' => 'Baja',
|
||||
'medium' => 'Media',
|
||||
'high' => 'Alta',
|
||||
@@ -264,7 +263,7 @@ class ReportGenerator
|
||||
default => ucfirst($issue->priority ?? ''),
|
||||
},
|
||||
'status' => $issue->status,
|
||||
'status_label' => match($issue->status) {
|
||||
'status_label' => match ($issue->status) {
|
||||
'open' => 'Abierta',
|
||||
'in_review' => 'En revisión',
|
||||
'closed' => 'Cerrada',
|
||||
@@ -308,7 +307,7 @@ class ReportGenerator
|
||||
'actual_hours' => $task->actual_hours,
|
||||
'progress' => $task->progress,
|
||||
'is_overdue' => $task->is_overdue,
|
||||
'subtasks' => $task->subtasks->map(fn($st) => [
|
||||
'subtasks' => $task->subtasks->map(fn ($st) => [
|
||||
'id' => $st->id,
|
||||
'title' => $st->title,
|
||||
'status' => $st->status,
|
||||
@@ -341,8 +340,10 @@ class ReportGenerator
|
||||
// Filter by project in PHP (polymorphic complexity)
|
||||
$filtered = $media->filter(function ($m) {
|
||||
$mediable = $m->mediable;
|
||||
if (!$mediable) return false;
|
||||
|
||||
if (! $mediable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($mediable instanceof Phase) {
|
||||
return $mediable->project_id === $this->project->id;
|
||||
}
|
||||
@@ -358,6 +359,7 @@ class ReportGenerator
|
||||
if ($mediable instanceof Task) {
|
||||
return $mediable->project_id === $this->project->id;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -398,9 +400,9 @@ class ReportGenerator
|
||||
'spi' => $phase->spi,
|
||||
'is_on_track' => $phase->is_on_track,
|
||||
];
|
||||
})->filter(fn($p) => $p['planned_start'] || $p['planned_end'])->toArray();
|
||||
})->filter(fn ($p) => $p['planned_start'] || $p['planned_end'])->toArray();
|
||||
|
||||
$featureDeviations = Feature::whereHas('layer.phase', fn($q) => $q->where('project_id', $this->project->id))
|
||||
$featureDeviations = Feature::whereHas('layer.phase', fn ($q) => $q->where('project_id', $this->project->id))
|
||||
->whereNotNull('planned_end')
|
||||
->with(['layer.phase'])
|
||||
->get()
|
||||
@@ -428,12 +430,12 @@ class ReportGenerator
|
||||
'phases' => $phaseDeviations,
|
||||
'features' => $featureDeviations,
|
||||
'summary' => [
|
||||
'phases_delayed' => count(array_filter($phaseDeviations, fn($p) => ($p['end_deviation'] ?? 0) > 0)),
|
||||
'phases_early' => count(array_filter($phaseDeviations, fn($p) => ($p['end_deviation'] ?? 0) < 0)),
|
||||
'phases_on_time' => count(array_filter($phaseDeviations, fn($p) => ($p['end_deviation'] ?? 0) === 0)),
|
||||
'features_delayed' => count(array_filter($featureDeviations, fn($f) => ($f['end_deviation'] ?? 0) > 0)),
|
||||
'features_early' => count(array_filter($featureDeviations, fn($f) => ($f['end_deviation'] ?? 0) < 0)),
|
||||
'features_on_time' => count(array_filter($featureDeviations, fn($f) => ($f['end_deviation'] ?? 0) === 0)),
|
||||
'phases_delayed' => count(array_filter($phaseDeviations, fn ($p) => ($p['end_deviation'] ?? 0) > 0)),
|
||||
'phases_early' => count(array_filter($phaseDeviations, fn ($p) => ($p['end_deviation'] ?? 0) < 0)),
|
||||
'phases_on_time' => count(array_filter($phaseDeviations, fn ($p) => ($p['end_deviation'] ?? 0) === 0)),
|
||||
'features_delayed' => count(array_filter($featureDeviations, fn ($f) => ($f['end_deviation'] ?? 0) > 0)),
|
||||
'features_early' => count(array_filter($featureDeviations, fn ($f) => ($f['end_deviation'] ?? 0) < 0)),
|
||||
'features_on_time' => count(array_filter($featureDeviations, fn ($f) => ($f['end_deviation'] ?? 0) === 0)),
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -442,7 +444,7 @@ class ReportGenerator
|
||||
{
|
||||
// Get snapshots for this project's phases and features
|
||||
$phaseIds = $this->project->phases->pluck('id')->toArray();
|
||||
$featureIds = Feature::whereHas('layer.phase', fn($q) => $q->whereIn('phase_id', $phaseIds))
|
||||
$featureIds = Feature::whereHas('layer.phase', fn ($q) => $q->whereIn('phase_id', $phaseIds))
|
||||
->pluck('id')->toArray();
|
||||
$taskIds = Task::where('project_id', $this->project->id)->pluck('id')->toArray();
|
||||
|
||||
@@ -475,7 +477,7 @@ class ReportGenerator
|
||||
|
||||
foreach ($grouped as $date => $snaps) {
|
||||
$dates[] = Carbon::parse($date)->format('d/m/Y');
|
||||
|
||||
|
||||
$phaseSnaps = $snaps->where('trackable_type', Phase::class);
|
||||
$featureSnaps = $snaps->where('trackable_type', Feature::class);
|
||||
$taskSnaps = $snaps->where('trackable_type', Task::class);
|
||||
@@ -523,7 +525,7 @@ class ReportGenerator
|
||||
|
||||
protected function getFeatureStatusLabel(string $status): string
|
||||
{
|
||||
return match($status) {
|
||||
return match ($status) {
|
||||
'planned' => 'Planificado',
|
||||
'started' => 'Iniciado',
|
||||
'in_progress' => 'En progreso',
|
||||
@@ -541,6 +543,7 @@ class ReportGenerator
|
||||
$bytes /= 1024;
|
||||
$i++;
|
||||
}
|
||||
return round($bytes, 1) . ' ' . $units[$i];
|
||||
|
||||
return round($bytes, 1).' '.$units[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,14 +15,16 @@ class SpatialFileConverter
|
||||
|
||||
$geojson = match ($ext) {
|
||||
'geojson' => self::parseGeoJson($path),
|
||||
'kml' => self::kmlToGeoJson($path),
|
||||
'kmz' => self::kmzToGeoJson($path),
|
||||
'shp' => self::shapefileToGeoJson($path),
|
||||
'zip' => self::handleZip($path),
|
||||
default => null,
|
||||
'kml' => self::kmlToGeoJson($path),
|
||||
'kmz' => self::kmzToGeoJson($path),
|
||||
'shp' => self::shapefileToGeoJson($path),
|
||||
'zip' => self::handleZip($path),
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (!$geojson) return null;
|
||||
if (! $geojson) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::postProcess($geojson);
|
||||
}
|
||||
@@ -37,15 +39,19 @@ class SpatialFileConverter
|
||||
|
||||
foreach ($geojson['features'] ?? [] as $feature) {
|
||||
|
||||
if (!isset($feature['geometry'])) continue;
|
||||
if (! isset($feature['geometry'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$geometry = self::cleanGeometry($feature['geometry']);
|
||||
if (!$geometry) continue;
|
||||
if (! $geometry) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$features[] = [
|
||||
'type' => 'Feature',
|
||||
'geometry' => $geometry,
|
||||
'properties' => self::normalizeProperties($feature['properties'] ?? [])
|
||||
'properties' => self::normalizeProperties($feature['properties'] ?? []),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -53,7 +59,7 @@ class SpatialFileConverter
|
||||
'type' => 'FeatureCollection',
|
||||
'features' => $features,
|
||||
'bbox' => self::calculateBBox($features),
|
||||
'centroid' => self::calculateCentroid($features)
|
||||
'centroid' => self::calculateCentroid($features),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -75,13 +81,16 @@ class SpatialFileConverter
|
||||
|
||||
private static function cleanGeometry(array $geom): ?array
|
||||
{
|
||||
if (!isset($geom['type'], $geom['coordinates'])) return null;
|
||||
if (! isset($geom['type'], $geom['coordinates'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($geom['type'] === 'Polygon') {
|
||||
$geom['coordinates'] = array_map(function ($ring) {
|
||||
if ($ring[0] !== end($ring)) {
|
||||
$ring[] = $ring[0];
|
||||
}
|
||||
|
||||
return $ring;
|
||||
}, $geom['coordinates']);
|
||||
}
|
||||
@@ -101,7 +110,9 @@ class SpatialFileConverter
|
||||
$coords = array_merge($coords, self::flattenCoords($f['geometry']['coordinates']));
|
||||
}
|
||||
|
||||
if (empty($coords)) return null;
|
||||
if (empty($coords)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$lons = array_column($coords, 0);
|
||||
$lats = array_column($coords, 1);
|
||||
@@ -110,7 +121,7 @@ class SpatialFileConverter
|
||||
min($lons),
|
||||
min($lats),
|
||||
max($lons),
|
||||
max($lats)
|
||||
max($lats),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -126,7 +137,9 @@ class SpatialFileConverter
|
||||
$coords = array_merge($coords, self::flattenCoords($f['geometry']['coordinates']));
|
||||
}
|
||||
|
||||
if (empty($coords)) return null;
|
||||
if (empty($coords)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$x = array_sum(array_column($coords, 0)) / count($coords);
|
||||
$y = array_sum(array_column($coords, 1)) / count($coords);
|
||||
@@ -139,10 +152,13 @@ class SpatialFileConverter
|
||||
$result = [];
|
||||
|
||||
$iterator = function ($c) use (&$result, &$iterator) {
|
||||
if (!is_array($c)) return;
|
||||
if (! is_array($c)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($c[0]) && isset($c[1]) && is_numeric($c[0])) {
|
||||
$result[] = [$c[0], $c[1]];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -163,6 +179,7 @@ class SpatialFileConverter
|
||||
private static function parseGeoJson($path): ?array
|
||||
{
|
||||
$data = json_decode(file_get_contents($path), true);
|
||||
|
||||
return json_last_error() === JSON_ERROR_NONE ? $data : null;
|
||||
}
|
||||
|
||||
@@ -175,18 +192,24 @@ class SpatialFileConverter
|
||||
libxml_use_internal_errors(true);
|
||||
$xml = simplexml_load_file($path);
|
||||
|
||||
if (!$xml) return null;
|
||||
if (! $xml) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Namespace-agnostic: usamos local-name() en el XPath para aceptar KMLs
|
||||
// con cualquier xmlns (opengis 2.2, earth.google 2.1/2.0, o sin xmlns).
|
||||
$placemarks = $xml->xpath('//*[local-name()="Placemark"]');
|
||||
if ($placemarks === false) $placemarks = [];
|
||||
if ($placemarks === false) {
|
||||
$placemarks = [];
|
||||
}
|
||||
|
||||
$features = [];
|
||||
|
||||
foreach ($placemarks as $pm) {
|
||||
$geom = self::parseKmlGeometry($pm);
|
||||
if (!$geom) continue;
|
||||
if (! $geom) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$features[] = [
|
||||
'type' => 'Feature',
|
||||
@@ -204,12 +227,15 @@ class SpatialFileConverter
|
||||
/** Descomprime un KMZ y parsea el .kml interno. */
|
||||
private static function kmzToGeoJson(string $path): ?array
|
||||
{
|
||||
$zip = new \ZipArchive();
|
||||
if ($zip->open($path) !== true) return null;
|
||||
$zip = new \ZipArchive;
|
||||
if ($zip->open($path) !== true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tmp = sys_get_temp_dir() . '/kmz_' . uniqid();
|
||||
$tmp = sys_get_temp_dir().'/kmz_'.uniqid();
|
||||
if (! @mkdir($tmp, 0777, true) && ! is_dir($tmp)) {
|
||||
$zip->close();
|
||||
|
||||
return null;
|
||||
}
|
||||
$zip->extractTo($tmp);
|
||||
@@ -228,15 +254,20 @@ class SpatialFileConverter
|
||||
|
||||
// Limpieza
|
||||
self::rrmdir($tmp);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private static function rrmdir(string $dir): void
|
||||
{
|
||||
if (! is_dir($dir)) return;
|
||||
if (! is_dir($dir)) {
|
||||
return;
|
||||
}
|
||||
foreach (scandir($dir) ?: [] as $item) {
|
||||
if ($item === '.' || $item === '..') continue;
|
||||
$p = $dir . DIRECTORY_SEPARATOR . $item;
|
||||
if ($item === '.' || $item === '..') {
|
||||
continue;
|
||||
}
|
||||
$p = $dir.DIRECTORY_SEPARATOR.$item;
|
||||
is_dir($p) ? self::rrmdir($p) : @unlink($p);
|
||||
}
|
||||
@rmdir($dir);
|
||||
@@ -245,38 +276,45 @@ class SpatialFileConverter
|
||||
/** Devuelve el primer hijo cuyo local-name coincida (sin depender del prefijo). */
|
||||
private static function kmlChild(\SimpleXMLElement $node, string $name): \SimpleXMLElement|string
|
||||
{
|
||||
$matches = $node->xpath('./*[local-name()="' . $name . '"]');
|
||||
$matches = $node->xpath('./*[local-name()="'.$name.'"]');
|
||||
|
||||
return $matches ? $matches[0] : '';
|
||||
}
|
||||
|
||||
private static function parseKmlGeometry($pm): ?array
|
||||
{
|
||||
// Todos los accesos van por xpath local-name para tolerar cualquier xmlns.
|
||||
$find = fn (string $tag) => $pm->xpath('./*[local-name()="' . $tag . '"]');
|
||||
$findDeep = fn (\SimpleXMLElement $n, string $tag) => $n->xpath('.//*[local-name()="' . $tag . '"]');
|
||||
$find = fn (string $tag) => $pm->xpath('./*[local-name()="'.$tag.'"]');
|
||||
$findDeep = fn (\SimpleXMLElement $n, string $tag) => $n->xpath('.//*[local-name()="'.$tag.'"]');
|
||||
|
||||
if ($multi = $find('MultiGeometry')) {
|
||||
$geoms = [];
|
||||
foreach ($multi[0]->children() as $g) {
|
||||
$parsed = self::parseKmlGeometry($g);
|
||||
if ($parsed) $geoms[] = $parsed;
|
||||
if ($parsed) {
|
||||
$geoms[] = $parsed;
|
||||
}
|
||||
}
|
||||
|
||||
return ['type' => 'GeometryCollection', 'geometries' => $geoms];
|
||||
}
|
||||
|
||||
if ($point = $find('Point')) {
|
||||
$coords = self::parseKmlCoords((string) ($findDeep($point[0], 'coordinates')[0] ?? ''));
|
||||
|
||||
return $coords ? ['type' => 'Point', 'coordinates' => $coords[0]] : null;
|
||||
}
|
||||
|
||||
if ($line = $find('LineString')) {
|
||||
$coords = self::parseKmlCoords((string) ($findDeep($line[0], 'coordinates')[0] ?? ''));
|
||||
|
||||
return $coords ? ['type' => 'LineString', 'coordinates' => $coords] : null;
|
||||
}
|
||||
|
||||
if ($poly = $find('Polygon')) {
|
||||
$outer = $findDeep($poly[0], 'coordinates')[0] ?? '';
|
||||
$coords = self::parseKmlCoords((string) $outer);
|
||||
|
||||
return $coords ? ['type' => 'Polygon', 'coordinates' => [$coords]] : null;
|
||||
}
|
||||
|
||||
@@ -289,9 +327,10 @@ class SpatialFileConverter
|
||||
foreach (preg_split('/\s+/', trim($text)) as $pair) {
|
||||
$p = explode(',', $pair);
|
||||
if (count($p) >= 2) {
|
||||
$coords[] = [(float)$p[0], (float)$p[1]];
|
||||
$coords[] = [(float) $p[0], (float) $p[1]];
|
||||
}
|
||||
}
|
||||
|
||||
return $coords;
|
||||
}
|
||||
|
||||
@@ -307,16 +346,20 @@ class SpatialFileConverter
|
||||
$features = [];
|
||||
|
||||
while ($record = $reader->fetchRecord()) {
|
||||
if ($record->isDeleted()) continue;
|
||||
if ($record->isDeleted()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$geom = json_decode($record->getGeometry()->toGeoJSON(), true);
|
||||
|
||||
if (!$geom) continue;
|
||||
if (! $geom) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$features[] = [
|
||||
'type' => 'Feature',
|
||||
'geometry' => $geom,
|
||||
'properties' => $record->getDataArray()
|
||||
'properties' => $record->getDataArray(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -324,6 +367,7 @@ class SpatialFileConverter
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error($e->getMessage());
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -334,11 +378,13 @@ class SpatialFileConverter
|
||||
|
||||
private static function handleZip($zipPath): ?array
|
||||
{
|
||||
$zip = new \ZipArchive();
|
||||
$zip = new \ZipArchive;
|
||||
|
||||
if ($zip->open($zipPath) !== true) return null;
|
||||
if ($zip->open($zipPath) !== true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dir = sys_get_temp_dir() . '/geo_' . uniqid();
|
||||
$dir = sys_get_temp_dir().'/geo_'.uniqid();
|
||||
mkdir($dir);
|
||||
|
||||
$zip->extractTo($dir);
|
||||
@@ -347,7 +393,7 @@ class SpatialFileConverter
|
||||
$result = null;
|
||||
|
||||
foreach (scandir($dir) as $file) {
|
||||
$full = $dir . '/' . $file;
|
||||
$full = $dir.'/'.$file;
|
||||
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
|
||||
|
||||
if ($ext === 'shp') {
|
||||
@@ -373,4 +419,4 @@ class SpatialFileConverter
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user