249 lines
6.3 KiB
PHP
249 lines
6.3 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace Modules\RestaurantDelivery\Models;
|
||
|
|
|
||
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||
|
|
use Illuminate\Database\Eloquent\Model;
|
||
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||
|
|
use Modules\RestaurantDelivery\Traits\HasRestaurant;
|
||
|
|
use Modules\RestaurantDelivery\Traits\HasUuid;
|
||
|
|
|
||
|
|
class DeliveryRating extends Model
|
||
|
|
{
|
||
|
|
use HasFactory, HasRestaurant, HasUuid, SoftDeletes;
|
||
|
|
|
||
|
|
public const TABLE_NAME = 'restaurant_delivery_ratings';
|
||
|
|
|
||
|
|
protected $table = self::TABLE_NAME;
|
||
|
|
|
||
|
|
protected $fillable = [
|
||
|
|
'delivery_id',
|
||
|
|
'rider_id',
|
||
|
|
'customer_id',
|
||
|
|
'restaurant_id',
|
||
|
|
'overall_rating',
|
||
|
|
'speed_rating',
|
||
|
|
'communication_rating',
|
||
|
|
'food_condition_rating',
|
||
|
|
'professionalism_rating',
|
||
|
|
'review',
|
||
|
|
'is_anonymous',
|
||
|
|
'tags',
|
||
|
|
'is_restaurant_rating',
|
||
|
|
'is_approved',
|
||
|
|
'is_featured',
|
||
|
|
'moderation_status',
|
||
|
|
'moderation_notes',
|
||
|
|
'rider_response',
|
||
|
|
'rider_responded_at',
|
||
|
|
'helpful_count',
|
||
|
|
'not_helpful_count',
|
||
|
|
];
|
||
|
|
|
||
|
|
protected $casts = [
|
||
|
|
'overall_rating' => 'integer',
|
||
|
|
'speed_rating' => 'integer',
|
||
|
|
'communication_rating' => 'integer',
|
||
|
|
'food_condition_rating' => 'integer',
|
||
|
|
'professionalism_rating' => 'integer',
|
||
|
|
'is_anonymous' => 'boolean',
|
||
|
|
'is_restaurant_rating' => 'boolean',
|
||
|
|
'is_approved' => 'boolean',
|
||
|
|
'is_featured' => 'boolean',
|
||
|
|
'tags' => 'array',
|
||
|
|
'rider_responded_at' => 'datetime',
|
||
|
|
];
|
||
|
|
|
||
|
|
protected $appends = [
|
||
|
|
'average_category_rating',
|
||
|
|
'star_display',
|
||
|
|
];
|
||
|
|
|
||
|
|
/*
|
||
|
|
|--------------------------------------------------------------------------
|
||
|
|
| Relationships
|
||
|
|
|--------------------------------------------------------------------------
|
||
|
|
*/
|
||
|
|
|
||
|
|
public function delivery(): BelongsTo
|
||
|
|
{
|
||
|
|
return $this->belongsTo(Delivery::class, 'delivery_id');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function rider(): BelongsTo
|
||
|
|
{
|
||
|
|
return $this->belongsTo(Rider::class, 'rider_id');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function customer(): BelongsTo
|
||
|
|
{
|
||
|
|
return $this->belongsTo(config('auth.providers.users.model'), 'customer_id');
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
|--------------------------------------------------------------------------
|
||
|
|
| Accessors
|
||
|
|
|--------------------------------------------------------------------------
|
||
|
|
*/
|
||
|
|
|
||
|
|
public function getAverageCategoryRatingAttribute(): ?float
|
||
|
|
{
|
||
|
|
$ratings = array_filter([
|
||
|
|
$this->speed_rating,
|
||
|
|
$this->communication_rating,
|
||
|
|
$this->food_condition_rating,
|
||
|
|
$this->professionalism_rating,
|
||
|
|
]);
|
||
|
|
|
||
|
|
if (empty($ratings)) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return round(array_sum($ratings) / count($ratings), 2);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getStarDisplayAttribute(): string
|
||
|
|
{
|
||
|
|
$filled = str_repeat('★', $this->overall_rating);
|
||
|
|
$empty = str_repeat('☆', 5 - $this->overall_rating);
|
||
|
|
|
||
|
|
return $filled.$empty;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
|--------------------------------------------------------------------------
|
||
|
|
| Scopes
|
||
|
|
|--------------------------------------------------------------------------
|
||
|
|
*/
|
||
|
|
|
||
|
|
public function scopeApproved($query)
|
||
|
|
{
|
||
|
|
return $query->where('is_approved', true);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function scopeFeatured($query)
|
||
|
|
{
|
||
|
|
return $query->where('is_featured', true);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function scopeFromCustomers($query)
|
||
|
|
{
|
||
|
|
return $query->where('is_restaurant_rating', false);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function scopeFromRestaurants($query)
|
||
|
|
{
|
||
|
|
return $query->where('is_restaurant_rating', true);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function scopeWithReview($query)
|
||
|
|
{
|
||
|
|
return $query->whereNotNull('review');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function scopeHighRated($query, int $minRating = 4)
|
||
|
|
{
|
||
|
|
return $query->where('overall_rating', '>=', $minRating);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function scopeLowRated($query, int $maxRating = 2)
|
||
|
|
{
|
||
|
|
return $query->where('overall_rating', '<=', $maxRating);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function scopeRecent($query, int $days = 30)
|
||
|
|
{
|
||
|
|
return $query->where('created_at', '>=', now()->subDays($days));
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
|--------------------------------------------------------------------------
|
||
|
|
| Methods
|
||
|
|
|--------------------------------------------------------------------------
|
||
|
|
*/
|
||
|
|
|
||
|
|
public function approve(): void
|
||
|
|
{
|
||
|
|
$this->update([
|
||
|
|
'is_approved' => true,
|
||
|
|
'moderation_status' => 'approved',
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function reject(string $reason): void
|
||
|
|
{
|
||
|
|
$this->update([
|
||
|
|
'is_approved' => false,
|
||
|
|
'moderation_status' => 'rejected',
|
||
|
|
'moderation_notes' => $reason,
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function feature(): void
|
||
|
|
{
|
||
|
|
$this->update(['is_featured' => true]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function unfeature(): void
|
||
|
|
{
|
||
|
|
$this->update(['is_featured' => false]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function addRiderResponse(string $response): void
|
||
|
|
{
|
||
|
|
$this->update([
|
||
|
|
'rider_response' => $response,
|
||
|
|
'rider_responded_at' => now(),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function markHelpful(): void
|
||
|
|
{
|
||
|
|
$this->increment('helpful_count');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function markNotHelpful(): void
|
||
|
|
{
|
||
|
|
$this->increment('not_helpful_count');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getHelpfulnessScore(): float
|
||
|
|
{
|
||
|
|
$total = $this->helpful_count + $this->not_helpful_count;
|
||
|
|
|
||
|
|
if ($total === 0) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return round(($this->helpful_count / $total) * 100, 2);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function hasTag(string $tag): bool
|
||
|
|
{
|
||
|
|
return in_array($tag, $this->tags ?? []);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getCategoryRatings(): array
|
||
|
|
{
|
||
|
|
return array_filter([
|
||
|
|
'speed' => $this->speed_rating,
|
||
|
|
'communication' => $this->communication_rating,
|
||
|
|
'food_condition' => $this->food_condition_rating,
|
||
|
|
'professionalism' => $this->professionalism_rating,
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function getPositiveTags(): array
|
||
|
|
{
|
||
|
|
return ['friendly', 'fast', 'careful', 'professional', 'polite', 'on_time'];
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function getNegativeTags(): array
|
||
|
|
{
|
||
|
|
return ['late', 'rude', 'careless', 'unprofessional', 'no_communication'];
|
||
|
|
}
|
||
|
|
}
|