Files
jobifygarden/app/Models/Job.php
T
2026-04-04 20:37:35 +03:00

264 lines
6.7 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;
class Job extends Model
{
use SoftDeletes;
protected static function boot()
{
parent::boot();
static::creating(function ($job) {
if (empty($job->slug)) {
$job->slug = static::generateUniqueSlug($job->title, $job->company);
}
});
static::updating(function ($job) {
if ($job->isDirty('title') && empty($job->slug)) {
$job->slug = static::generateUniqueSlug($job->title, $job->company);
}
});
}
protected $fillable = [
'external_id',
'slug',
'title',
'company',
'description',
'location',
'salary_min',
'salary_max',
'salary_currency',
'job_type',
'job_source_id',
'source_url',
'apply_url',
'posted_at',
'expires_at',
'remote_allowed',
'is_curated',
'experience_level',
'job_category_id',
'job_location_id',
'fingerprint',
];
protected $casts = [
'posted_at' => 'datetime',
'expires_at' => 'datetime',
'remote_allowed' => 'boolean',
'is_curated' => 'boolean',
'salary_min' => 'decimal:2',
'salary_max' => 'decimal:2',
];
/**
* Get the source that provides this job.
*/
public function source(): BelongsTo
{
return $this->belongsTo(JobSource::class, 'job_source_id');
}
/**
* Get the category that this job belongs to.
*/
public function category(): BelongsTo
{
return $this->belongsTo(JobCategory::class, 'job_category_id');
}
/**
* Get the location of this job.
*/
public function jobLocation(): BelongsTo
{
return $this->belongsTo(JobLocation::class, 'job_location_id');
}
/**
* Get the users who saved this job.
*/
public function savedByUsers(): BelongsToMany
{
return $this->belongsToMany(User::class, 'user_saved_jobs')
->withTimestamps();
}
/**
* Get the tags for this job.
*/
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class);
}
/**
* Scope a query to only include active jobs.
*/
public function scopeActive($query)
{
return $query->where(function ($q) {
$q->whereNull('expires_at')
->orWhere('expires_at', '>', now());
});
}
/**
* Scope a query to only include uncurated jobs.
*/
public function scopeUncurated($query)
{
return $query->where('is_curated', false);
}
/**
* Scope a query to only include remote jobs.
*/
public function scopeRemote($query)
{
return $query->where('remote_allowed', true);
}
/**
* Scope a query to filter by job type.
*/
public function scopeOfType($query, string $type)
{
return $query->where('job_type', $type);
}
/**
* Scope a query to filter by experience level.
*/
public function scopeExperienceLevel($query, string $level)
{
return $query->where('experience_level', $level);
}
/**
* Scope a query to search by keywords in title, company, and description.
*/
public function scopeSearchKeywords($query, string $keywords)
{
return $query->where(function ($q) use ($keywords) {
$q->where('title', 'LIKE', "%{$keywords}%")
->orWhere('company', 'LIKE', "%{$keywords}%")
->orWhere('description', 'LIKE', "%{$keywords}%");
});
}
/**
* Scope a query to filter by category.
*/
public function scopeInCategory($query, int $categoryId)
{
return $query->where('job_category_id', $categoryId);
}
/**
* Scope a query to filter by location.
*/
public function scopeInLocation($query, string $location)
{
return $query->where(function ($q) use ($location) {
$q->where('location', 'LIKE', "%{$location}%")
->orWhereHas('jobLocation', function ($q2) use ($location) {
$q2->where('city', 'LIKE', "%{$location}%")
->orWhere('state', 'LIKE', "%{$location}%")
->orWhere('country', 'LIKE', "%{$location}%");
});
});
}
/**
* Scope a query to filter by salary range.
*/
public function scopeWithinSalaryRange($query, ?float $minSalary = null, ?float $maxSalary = null)
{
if ($minSalary) {
$query->where(function ($q) use ($minSalary) {
$q->where('salary_max', '>=', $minSalary)
->orWhereNull('salary_max');
});
}
if ($maxSalary) {
$query->where(function ($q) use ($maxSalary) {
$q->where('salary_min', '<=', $maxSalary)
->orWhereNull('salary_min');
});
}
return $query;
}
/**
* Scope a query to filter by posted date.
*/
public function scopePostedWithin($query, int $days)
{
return $query->where(function ($q) use ($days) {
$q->where('posted_at', '>=', now()->subDays($days))
->orWhere(function ($q2) use ($days) {
$q2->whereNull('posted_at')
->where('created_at', '>=', now()->subDays($days));
});
});
}
/**
* Scope a query to filter by company.
*/
public function scopeByCompany($query, string $company)
{
return $query->where('company', 'LIKE', "%{$company}%");
}
/**
* Generate a unique slug for the job.
*/
public static function generateUniqueSlug(string $title, ?string $company = null): string
{
// Create base slug from title
$baseSlug = Str::slug($title);
// Add company to slug if provided for better uniqueness
if ($company) {
$baseSlug .= '-' . Str::slug(Str::limit($company, 30, ''));
}
// Limit slug length to avoid database issues
$baseSlug = Str::limit($baseSlug, 200, '');
$slug = $baseSlug;
$counter = 1;
// Check if slug exists and append counter if needed
while (static::withTrashed()->where('slug', $slug)->exists()) {
$slug = $baseSlug . '-' . $counter;
$counter++;
}
return $slug;
}
/**
* Get the route key for the model.
*/
public function getRouteKeyName(): string
{
return 'slug';
}
}