schneespur/app/Models/Job.php
Michael ee3dbba6cc Initial release v1.0.0
Schneespur — Open-source winter service documentation software (PWA + Admin).
GPS tracking via OwnTracks, weather data, photo evidence, and legally
compliant service records for winter maintenance operators.

License: AGPL-3.0-or-later
2026-05-17 13:33:51 +00:00

153 lines
3.5 KiB
PHP

<?php
namespace App\Models;
use App\Enums\JobType;
use Database\Factories\JobFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Support\Carbon;
class Job extends Model
{
/** @use HasFactory<JobFactory> */
use HasFactory;
protected $table = 'service_jobs';
protected $fillable = [
'work_shift_id',
'customer_id',
'customer_object_id',
'user_id',
'vehicle_id',
'type',
'started_at',
'ended_at',
'notes',
'is_manual',
];
protected function casts(): array
{
return [
'type' => JobType::class,
'started_at' => 'datetime',
'ended_at' => 'datetime',
'is_manual' => 'boolean',
];
}
public function workShift(): BelongsTo
{
return $this->belongsTo(WorkShift::class);
}
public function customerObject(): BelongsTo
{
return $this->belongsTo(CustomerObject::class);
}
public function customer(): BelongsTo
{
return $this->belongsTo(Customer::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function vehicle(): BelongsTo
{
return $this->belongsTo(Vehicle::class);
}
public function gpsPoints(): HasMany
{
return $this->hasMany(GpsPoint::class);
}
public function weatherSnapshots(): HasMany
{
return $this->hasMany(WeatherSnapshot::class);
}
public function jobPhotos(): HasMany
{
return $this->hasMany(JobPhoto::class);
}
public function notificationLogs(): MorphMany
{
return $this->morphMany(NotificationLog::class, 'notifiable');
}
public function alertDismissals(): HasMany
{
return $this->hasMany(AlertDismissal::class);
}
public function audits(): HasMany
{
return $this->hasMany(JobAudit::class);
}
public function isCompleted(): bool
{
return $this->ended_at !== null;
}
public function isInGracePeriod(): bool
{
return $this->isCompleted() && $this->ended_at->addHours(24)->isFuture();
}
public function isLocked(): bool
{
return $this->isCompleted() && ! $this->isInGracePeriod();
}
public function isGpsLocked(): bool
{
return $this->isCompleted();
}
public function graceDeadline(): ?Carbon
{
return $this->ended_at?->addHours(24);
}
public function localStartedAt(): Carbon
{
return $this->started_at->copy()->setTimezone(config('app.display_timezone'));
}
public function localEndedAt(): ?Carbon
{
return $this->ended_at?->copy()->setTimezone(config('app.display_timezone'));
}
public function durationFormatted(): string
{
if (! $this->ended_at) {
return '';
}
$diff = $this->started_at->diff($this->ended_at);
$hours = $diff->h + ($diff->days * 24);
if ($hours > 0 && $diff->i > 0) {
return __('job.duration_hours_minutes', ['hours' => $hours, 'minutes' => $diff->i]);
}
if ($hours > 0) {
return trans_choice('job.duration_hours', $hours, ['hours' => $hours]);
}
return __('job.duration_minutes', ['minutes' => $diff->i ?: 1]);
}
}