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:
@@ -12,18 +12,18 @@ class ActivityLog extends Model
|
||||
protected $fillable = ['action', 'model_type', 'model_id', 'user_id', 'changes', 'created_at'];
|
||||
|
||||
protected $casts = [
|
||||
'changes' => 'array',
|
||||
'changes' => 'array',
|
||||
'created_at' => 'datetime',
|
||||
];
|
||||
|
||||
public static function record(string $action, Model $model, array $changes = []): void
|
||||
{
|
||||
static::create([
|
||||
'action' => $action,
|
||||
'action' => $action,
|
||||
'model_type' => class_basename($model),
|
||||
'model_id' => $model->getKey(),
|
||||
'user_id' => Auth::id(),
|
||||
'changes' => empty($changes) ? null : $changes,
|
||||
'model_id' => $model->getKey(),
|
||||
'user_id' => Auth::id(),
|
||||
'changes' => empty($changes) ? null : $changes,
|
||||
'created_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -35,7 +34,7 @@ class Company extends Model
|
||||
public function projects()
|
||||
{
|
||||
return $this->belongsToMany(Project::class, 'company_project')
|
||||
->withPivot('role_in_project')
|
||||
->withTimestamps();
|
||||
->withPivot('role_in_project')
|
||||
->withTimestamps();
|
||||
}
|
||||
}
|
||||
|
||||
+28
-26
@@ -2,14 +2,15 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\LogsActivity;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use App\Traits\LogsActivity;
|
||||
|
||||
class Feature extends Model
|
||||
{
|
||||
use SoftDeletes, LogsActivity;
|
||||
use LogsActivity, SoftDeletes;
|
||||
|
||||
const STATUSES = ['planned', 'started', 'in_progress', 'completed', 'verified'];
|
||||
|
||||
@@ -22,15 +23,15 @@ class Feature extends Model
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'geometry' => 'array',
|
||||
'properties' => 'array',
|
||||
'is_active' => 'boolean',
|
||||
'planned_start' => 'date',
|
||||
'planned_end' => 'date',
|
||||
'actual_start' => 'date',
|
||||
'actual_end' => 'date',
|
||||
'baseline_start' => 'date',
|
||||
'baseline_end' => 'date',
|
||||
'geometry' => 'array',
|
||||
'properties' => 'array',
|
||||
'is_active' => 'boolean',
|
||||
'planned_start' => 'date',
|
||||
'planned_end' => 'date',
|
||||
'actual_start' => 'date',
|
||||
'actual_end' => 'date',
|
||||
'baseline_start' => 'date',
|
||||
'baseline_end' => 'date',
|
||||
];
|
||||
|
||||
public function featureType()
|
||||
@@ -80,13 +81,13 @@ class Feature extends Model
|
||||
|
||||
public function getStatusColorAttribute(): string
|
||||
{
|
||||
return match($this->status) {
|
||||
'planned' => '#6b7280',
|
||||
'started' => '#3b82f6',
|
||||
return match ($this->status) {
|
||||
'planned' => '#6b7280',
|
||||
'started' => '#3b82f6',
|
||||
'in_progress' => '#f59e0b',
|
||||
'completed' => '#10b981',
|
||||
'verified' => '#8b5cf6',
|
||||
default => '#6b7280',
|
||||
'completed' => '#10b981',
|
||||
'verified' => '#8b5cf6',
|
||||
default => '#6b7280',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -99,7 +100,7 @@ class Feature extends Model
|
||||
return null;
|
||||
}
|
||||
$end = $this->actual_end ?? now()->toDateString();
|
||||
|
||||
|
||||
return $this->planned_end->diffInDays($end, false);
|
||||
}
|
||||
|
||||
@@ -109,28 +110,28 @@ class Feature extends Model
|
||||
return null;
|
||||
}
|
||||
$start = $this->actual_start ?? now()->toDateString();
|
||||
|
||||
|
||||
return $this->planned_start->diffInDays($start, false);
|
||||
}
|
||||
|
||||
public function getPlannedProgressAtAttribute(?\Carbon\Carbon $date = null): float
|
||||
public function getPlannedProgressAtAttribute(?Carbon $date = null): float
|
||||
{
|
||||
$date = $date ?? now();
|
||||
|
||||
|
||||
if (! $this->planned_start || ! $this->planned_end) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if ($date <= $this->planned_start) {
|
||||
return 0;
|
||||
}
|
||||
if ($date >= $this->planned_end) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
|
||||
$totalDays = $this->planned_start->diffInDays($this->planned_end);
|
||||
$elapsedDays = $this->planned_start->diffInDays($date);
|
||||
|
||||
|
||||
return round(($elapsedDays / $totalDays) * 100, 2);
|
||||
}
|
||||
|
||||
@@ -138,11 +139,11 @@ class Feature extends Model
|
||||
{
|
||||
$pv = $this->planned_progress_at;
|
||||
$ev = $this->progress;
|
||||
|
||||
|
||||
if ($pv <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return round($ev / $pv, 2);
|
||||
}
|
||||
|
||||
@@ -152,6 +153,7 @@ class Feature extends Model
|
||||
if ($spi === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $spi >= 0.95;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\LogsActivity;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use App\Traits\LogsActivity;
|
||||
|
||||
class Inspection extends Model
|
||||
{
|
||||
use SoftDeletes, LogsActivity;
|
||||
use LogsActivity, SoftDeletes;
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
@@ -19,7 +19,8 @@ class Inspection extends Model
|
||||
}
|
||||
|
||||
const STATUSES = ['pending', 'in_progress', 'completed', 'approved', 'rejected'];
|
||||
const RESULTS = ['pass', 'fail', 'conditional'];
|
||||
|
||||
const RESULTS = ['pass', 'fail', 'conditional'];
|
||||
|
||||
protected $fillable = [
|
||||
'project_id', 'layer_id', 'feature_id', 'template_id', 'user_id',
|
||||
@@ -28,7 +29,7 @@ class Inspection extends Model
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'data' => 'array',
|
||||
'data' => 'array',
|
||||
'completed_at' => 'datetime',
|
||||
];
|
||||
|
||||
@@ -72,7 +73,18 @@ class Inspection extends Model
|
||||
return $this->hasMany(Issue::class);
|
||||
}
|
||||
|
||||
public function scopePending($q) { return $q->where('status', 'pending'); }
|
||||
public function scopeCompleted($q) { return $q->where('status', 'completed'); }
|
||||
public function scopeRejected($q) { return $q->where('status', 'rejected'); }
|
||||
public function scopePending($q)
|
||||
{
|
||||
return $q->where('status', 'pending');
|
||||
}
|
||||
|
||||
public function scopeCompleted($q)
|
||||
{
|
||||
return $q->where('status', 'completed');
|
||||
}
|
||||
|
||||
public function scopeRejected($q)
|
||||
{
|
||||
return $q->where('status', 'rejected');
|
||||
}
|
||||
}
|
||||
|
||||
+74
-33
@@ -2,17 +2,19 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\LogsActivity;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use App\Traits\LogsActivity;
|
||||
|
||||
class Issue extends Model
|
||||
{
|
||||
use SoftDeletes, LogsActivity;
|
||||
use LogsActivity, SoftDeletes;
|
||||
|
||||
const STATUSES = ['open', 'in_review', 'resolved', 'closed'];
|
||||
|
||||
const STATUSES = ['open', 'in_review', 'resolved', 'closed'];
|
||||
const PRIORITIES = ['low', 'medium', 'high', 'critical'];
|
||||
const TYPES = ['defect', 'safety', 'quality', 'documentation', 'other'];
|
||||
|
||||
const TYPES = ['defect', 'safety', 'quality', 'documentation', 'other'];
|
||||
|
||||
protected $fillable = [
|
||||
'project_id', 'feature_id', 'inspection_id',
|
||||
@@ -23,17 +25,55 @@ class Issue extends Model
|
||||
|
||||
protected $casts = ['resolved_at' => 'datetime'];
|
||||
|
||||
public function project() { return $this->belongsTo(Project::class); }
|
||||
public function feature() { return $this->belongsTo(Feature::class); }
|
||||
public function inspection() { return $this->belongsTo(Inspection::class); }
|
||||
public function reporter() { return $this->belongsTo(User::class, 'reported_by'); }
|
||||
public function assignee() { return $this->belongsTo(User::class, 'assigned_to'); }
|
||||
public function media() { return $this->morphMany(Media::class, 'mediable'); }
|
||||
public function tasks() { return $this->hasMany(IssueTask::class)->orderBy('order')->orderBy('id'); }
|
||||
public function comments() { return $this->hasMany(IssueComment::class)->orderBy('created_at'); }
|
||||
public function project()
|
||||
{
|
||||
return $this->belongsTo(Project::class);
|
||||
}
|
||||
|
||||
public function scopeOpen($q) { return $q->where('status', 'open'); }
|
||||
public function scopeCritical($q) { return $q->where('priority', 'critical'); }
|
||||
public function feature()
|
||||
{
|
||||
return $this->belongsTo(Feature::class);
|
||||
}
|
||||
|
||||
public function inspection()
|
||||
{
|
||||
return $this->belongsTo(Inspection::class);
|
||||
}
|
||||
|
||||
public function reporter()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'reported_by');
|
||||
}
|
||||
|
||||
public function assignee()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'assigned_to');
|
||||
}
|
||||
|
||||
public function media()
|
||||
{
|
||||
return $this->morphMany(Media::class, 'mediable');
|
||||
}
|
||||
|
||||
public function tasks()
|
||||
{
|
||||
return $this->hasMany(IssueTask::class)->orderBy('order')->orderBy('id');
|
||||
}
|
||||
|
||||
public function comments()
|
||||
{
|
||||
return $this->hasMany(IssueComment::class)->orderBy('created_at');
|
||||
}
|
||||
|
||||
public function scopeOpen($q)
|
||||
{
|
||||
return $q->where('status', 'open');
|
||||
}
|
||||
|
||||
public function scopeCritical($q)
|
||||
{
|
||||
return $q->where('priority', 'critical');
|
||||
}
|
||||
|
||||
/** Resolution progress derived from the checklist: done tasks / total. */
|
||||
public function getProgressAttribute(): int
|
||||
@@ -42,6 +82,7 @@ class Issue extends Model
|
||||
if ($total === 0) {
|
||||
return in_array($this->status, ['resolved', 'closed'], true) ? 100 : 0;
|
||||
}
|
||||
|
||||
return (int) round($this->tasks->where('is_done', true)->count() / $total * 100);
|
||||
}
|
||||
|
||||
@@ -53,23 +94,23 @@ class Issue extends Model
|
||||
|
||||
public function getPriorityColorAttribute(): string
|
||||
{
|
||||
return match($this->priority) {
|
||||
'low' => '#6b7280',
|
||||
'medium' => '#f59e0b',
|
||||
'high' => '#ef4444',
|
||||
return match ($this->priority) {
|
||||
'low' => '#6b7280',
|
||||
'medium' => '#f59e0b',
|
||||
'high' => '#ef4444',
|
||||
'critical' => '#7c3aed',
|
||||
default => '#6b7280',
|
||||
default => '#6b7280',
|
||||
};
|
||||
}
|
||||
|
||||
public function getStatusColorAttribute(): string
|
||||
{
|
||||
return match($this->status) {
|
||||
'open' => '#ef4444',
|
||||
return match ($this->status) {
|
||||
'open' => '#ef4444',
|
||||
'in_review' => '#f59e0b',
|
||||
'resolved' => '#10b981',
|
||||
'closed' => '#6b7280',
|
||||
default => '#6b7280',
|
||||
'resolved' => '#10b981',
|
||||
'closed' => '#6b7280',
|
||||
default => '#6b7280',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -77,11 +118,11 @@ class Issue extends Model
|
||||
public static function typeLabels(): array
|
||||
{
|
||||
return [
|
||||
'defect' => 'Defecto',
|
||||
'safety' => 'Seguridad',
|
||||
'quality' => 'Calidad',
|
||||
'defect' => 'Defecto',
|
||||
'safety' => 'Seguridad',
|
||||
'quality' => 'Calidad',
|
||||
'documentation' => 'Documentación',
|
||||
'other' => 'Otro',
|
||||
'other' => 'Otro',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -92,12 +133,12 @@ class Issue extends Model
|
||||
|
||||
public function getTypeColorAttribute(): string
|
||||
{
|
||||
return match($this->type) {
|
||||
'defect' => '#ef4444',
|
||||
'safety' => '#f97316',
|
||||
'quality' => '#0ea5e9',
|
||||
return match ($this->type) {
|
||||
'defect' => '#ef4444',
|
||||
'safety' => '#f97316',
|
||||
'quality' => '#0ea5e9',
|
||||
'documentation' => '#8b5cf6',
|
||||
default => '#6b7280',
|
||||
default => '#6b7280',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,18 @@ class IssueComment extends Model
|
||||
'uuid', 'client_updated_at',
|
||||
];
|
||||
|
||||
public function issue() { return $this->belongsTo(Issue::class); }
|
||||
public function user() { return $this->belongsTo(User::class); }
|
||||
public function media() { return $this->morphMany(Media::class, 'mediable'); }
|
||||
public function issue()
|
||||
{
|
||||
return $this->belongsTo(Issue::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function media()
|
||||
{
|
||||
return $this->morphMany(Media::class, 'mediable');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ class IssueTask extends Model
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_done' => 'boolean',
|
||||
'done_at' => 'datetime',
|
||||
'due_date' => 'date',
|
||||
'is_done' => 'boolean',
|
||||
'done_at' => 'datetime',
|
||||
'due_date' => 'date',
|
||||
'overdue_notified_at' => 'datetime',
|
||||
];
|
||||
|
||||
@@ -26,14 +26,29 @@ class IssueTask extends Model
|
||||
public function scopeOverdue($q)
|
||||
{
|
||||
return $q->where('is_done', false)
|
||||
->whereNotNull('due_date')
|
||||
->whereDate('due_date', '<', now()->toDateString());
|
||||
->whereNotNull('due_date')
|
||||
->whereDate('due_date', '<', now()->toDateString());
|
||||
}
|
||||
|
||||
public function issue() { return $this->belongsTo(Issue::class); }
|
||||
public function assignee() { return $this->belongsTo(User::class, 'assigned_to'); }
|
||||
public function completer() { return $this->belongsTo(User::class, 'done_by'); }
|
||||
public function media() { return $this->morphMany(Media::class, 'mediable'); }
|
||||
public function issue()
|
||||
{
|
||||
return $this->belongsTo(Issue::class);
|
||||
}
|
||||
|
||||
public function assignee()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'assigned_to');
|
||||
}
|
||||
|
||||
public function completer()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'done_by');
|
||||
}
|
||||
|
||||
public function media()
|
||||
{
|
||||
return $this->morphMany(Media::class, 'mediable');
|
||||
}
|
||||
|
||||
/** Overdue = has a due date in the past and not yet done. */
|
||||
public function getIsOverdueAttribute(): bool
|
||||
|
||||
@@ -5,13 +5,12 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
|
||||
class Layer extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'project_id', 'phase_id', 'name', 'color', 'geojson_data', 'original_file', 'uploaded_by'
|
||||
'project_id', 'phase_id', 'name', 'color', 'geojson_data', 'original_file', 'uploaded_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -32,6 +31,7 @@ class Layer extends Model
|
||||
{
|
||||
return $this->belongsTo(User::class, 'uploaded_by');
|
||||
}
|
||||
|
||||
public function features()
|
||||
{
|
||||
return $this->hasMany(Feature::class);
|
||||
@@ -46,4 +46,4 @@ class Layer extends Model
|
||||
{
|
||||
return $this->morphMany(Media::class, 'mediable');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-5
@@ -48,10 +48,17 @@ class Media extends Model
|
||||
public function getFormattedSizeAttribute()
|
||||
{
|
||||
$bytes = $this->file_size;
|
||||
if ($bytes >= 1073741824) return round($bytes / 1073741824, 2) . ' GB';
|
||||
if ($bytes >= 1048576) return round($bytes / 1048576, 1) . ' MB';
|
||||
if ($bytes >= 1024) return round($bytes / 1024) . ' KB';
|
||||
return $bytes . ' B';
|
||||
if ($bytes >= 1073741824) {
|
||||
return round($bytes / 1073741824, 2).' GB';
|
||||
}
|
||||
if ($bytes >= 1048576) {
|
||||
return round($bytes / 1048576, 1).' MB';
|
||||
}
|
||||
if ($bytes >= 1024) {
|
||||
return round($bytes / 1024).' KB';
|
||||
}
|
||||
|
||||
return $bytes.' B';
|
||||
}
|
||||
|
||||
// Scopes
|
||||
@@ -72,4 +79,4 @@ class Media extends Model
|
||||
Storage::delete($media->file_path);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,4 +12,4 @@ class PendingSync extends Model
|
||||
'payload' => 'array',
|
||||
'synced_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
+11
-9
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
@@ -79,7 +80,7 @@ class Phase extends Model
|
||||
return null;
|
||||
}
|
||||
$end = $this->actual_end ?? now()->toDateString();
|
||||
|
||||
|
||||
return $this->planned_end->diffInDays($end, false);
|
||||
}
|
||||
|
||||
@@ -92,31 +93,31 @@ class Phase extends Model
|
||||
return null;
|
||||
}
|
||||
$start = $this->actual_start ?? now()->toDateString();
|
||||
|
||||
|
||||
return $this->planned_start->diffInDays($start, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Planned progress at a given date (linear interpolation)
|
||||
*/
|
||||
public function getPlannedProgressAtAttribute(?\Carbon\Carbon $date = null): float
|
||||
public function getPlannedProgressAtAttribute(?Carbon $date = null): float
|
||||
{
|
||||
$date = $date ?? now();
|
||||
|
||||
|
||||
if (! $this->planned_start || ! $this->planned_end) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if ($date <= $this->planned_start) {
|
||||
return 0;
|
||||
}
|
||||
if ($date >= $this->planned_end) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
|
||||
$totalDays = $this->planned_start->diffInDays($this->planned_end);
|
||||
$elapsedDays = $this->planned_start->diffInDays($date);
|
||||
|
||||
|
||||
return round(($elapsedDays / $totalDays) * 100, 2);
|
||||
}
|
||||
|
||||
@@ -127,11 +128,11 @@ class Phase extends Model
|
||||
{
|
||||
$pv = $this->planned_progress_at; // Planned Value
|
||||
$ev = $this->progress_percent; // Earned Value (current progress)
|
||||
|
||||
|
||||
if ($pv <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return round($ev / $pv, 2);
|
||||
}
|
||||
|
||||
@@ -144,6 +145,7 @@ class Phase extends Model
|
||||
if ($spi === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $spi >= 0.95;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ class ProgressSnapshot extends Model
|
||||
if ($id) {
|
||||
$query->where('trackable_id', $id);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
@@ -76,4 +77,4 @@ class ProgressSnapshot extends Model
|
||||
->get(['snapshot_date', 'progress', 'method'])
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||
class ProgressUpdate extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'uuid', 'phase_id', 'user_id', 'progress_percent', 'comment', 'location', 'client_updated_at'
|
||||
'uuid', 'phase_id', 'user_id', 'progress_percent', 'comment', 'location', 'client_updated_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -24,4 +24,4 @@ class ProgressUpdate extends Model
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user