Преглед изворни кода

feat: :sparkles: criação da página de busca de prestadores no agendamento

foi criada a página de buscar agendamentos de prestadores no aplicativo do cliente
Gustavo Zanatta пре 3 недеља
родитељ
комит
f5bd4d137f

+ 12 - 1
app/Http/Controllers/DashboardController.php

@@ -5,8 +5,8 @@ namespace App\Http\Controllers;
 use App\Services\DashboardService;
 use App\Http\Resources\DashboardClienteResource;
 use App\Http\Resources\DashboardPrestadorResource;
-use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
 use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Log;
 
 class DashboardController extends Controller
@@ -26,6 +26,17 @@ class DashboardController extends Controller
     }
   }
 
+  public function buscaPrestadores(Request $request): JsonResponse
+  {
+    try {
+      $dados = $this->service->buscaPrestadores($request->query('name'), $request->query('date'));
+      return $this->successResponse(payload: $dados);
+    } catch (\Exception $e) {
+      Log::error("Erro ao buscar prestadores: " . $e->getMessage());
+      return $this->errorResponse(message: __("messages.error_fetching_data"), code: 500);
+    }
+  }
+
   public function dadosDashboardPrestador(): JsonResponse
   {
     try {

+ 0 - 1
app/Http/Controllers/UserController.php

@@ -45,7 +45,6 @@ class UserController extends Controller
 
   public function update(UserRequest $request, int $id): JsonResponse
   {
-    Log::info("Updating user with ID: $id", ['data' => $request->validated()]);
     $item = $this->service->update($id, $request->validated());
     return $this->successResponse(
       payload: new UserResource($item),

+ 5 - 0
app/Models/Client.php

@@ -64,4 +64,9 @@ class Client extends Model
 
       $this->save();
     }
+
+    public function schedules()
+    {
+      return $this->hasMany(Schedule::class);
+    }
 }

+ 73 - 66
app/Models/Provider.php

@@ -38,80 +38,87 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  */
 class Provider extends Model
 {
-    use HasFactory, SoftDeletes;
+  use HasFactory, SoftDeletes;
 
-    protected $table = "providers";
+  protected $table = "providers";
 
-    protected $guarded = ["id"];
+  protected $guarded = ["id"];
 
-    /**
-     * Get the attributes that should be cast.
-     *
-     * @return array<string, string>
-     */
-    protected function casts(): array
-    {
-        return [
-            "birth_date" => "date",
-            "selfie_verified" => "boolean",
-            "document_verified" => "boolean",
-            "is_approved" => "boolean",
-            "average_rating" => "decimal:1",
-            "daily_price_8h" => "decimal:2",
-            "daily_price_6h" => "decimal:2",
-            "daily_price_4h" => "decimal:2",
-            "daily_price_2h" => "decimal:2",
-            "total_services" => "integer",
-        ];
-    }
-
-    /**
-     * @return BelongsTo
-     */
-    public function user(): BelongsTo
-    {
-        return $this->belongsTo(User::class, "user_id");
-    }
+  /**
+   * Get the attributes that should be cast.
+   *
+   * @return array<string, string>
+   */
+  protected function casts(): array
+  {
+    return [
+      "birth_date" => "date",
+      "selfie_verified" => "boolean",
+      "document_verified" => "boolean",
+      "is_approved" => "boolean",
+      "average_rating" => "decimal:1",
+      "daily_price_8h" => "decimal:2",
+      "daily_price_6h" => "decimal:2",
+      "daily_price_4h" => "decimal:2",
+      "daily_price_2h" => "decimal:2",
+      "total_services" => "integer",
+    ];
+  }
 
-    /**
-     * @return BelongsTo
-     */
-    public function profileMedia(): BelongsTo
-    {
-        return $this->belongsTo(Media::class, "profile_media_id");
-    }
+  /**
+   * @return BelongsTo
+   */
+  public function user(): BelongsTo
+  {
+    return $this->belongsTo(User::class, "user_id");
+  }
 
-    /**
-     * @return \Illuminate\Database\Eloquent\Relations\HasMany
-     */
-    public function blockedClients()
-    {
-        return $this->hasMany(ProviderClientBlock::class);
-    }
+  /**
+   * @return BelongsTo
+   */
+  public function profileMedia(): BelongsTo
+  {
+    return $this->belongsTo(Media::class, "profile_media_id");
+  }
 
-    /**
-     * @return \Illuminate\Database\Eloquent\Relations\HasMany
-     */
-    public function blockedByClients()
-    {
-        return $this->hasMany(ClientProviderBlock::class);
-    }
+  /**
+   * @return \Illuminate\Database\Eloquent\Relations\HasMany
+   */
+  public function blockedClients()
+  {
+    return $this->hasMany(ProviderClientBlock::class);
+  }
 
-    public function updateAverageRating(float $newRating): void
-    {
-      $totalReviews = Review::where('reviews.origin', 'client')
-        ->leftJoin('schedules', 'schedules.id', '=', 'reviews.schedule_id')
-        ->where('schedules.provider_id', $this->id)
-        ->count();
+  /**
+   * @return \Illuminate\Database\Eloquent\Relations\HasMany
+   */
+  public function blockedByClients()
+  {
+    return $this->hasMany(ClientProviderBlock::class);
+  }
 
-      if ($totalReviews === 0) {
-        $this->average_rating = $newRating;
-      } else {
-        $currentTotalRating = $this->average_rating * ($totalReviews - 1);
-        $newAverage = ($currentTotalRating + $newRating) / $totalReviews;
-        $this->average_rating = round($newAverage, 2);
-      }
+  public function updateAverageRating(float $newRating): void
+  {
+    $totalReviews = Review::where('reviews.origin', 'client')
+      ->leftJoin('schedules', 'schedules.id', '=', 'reviews.schedule_id')
+      ->where('schedules.provider_id', $this->id)
+      ->count();
 
-      $this->save();
+    if ($totalReviews === 0) {
+      $this->average_rating = $newRating;
+    } else {
+      $currentTotalRating = $this->average_rating * ($totalReviews - 1);
+      $newAverage = ($currentTotalRating + $newRating) / $totalReviews;
+      $this->average_rating = round($newAverage, 2);
     }
+
+    $this->save();
+  }
+
+  public function primaryAddress()
+  {
+    return $this->hasOne(Address::class, "source_id")
+      ->where("source", "provider")
+      ->orderBy("is_primary", "desc");
+  }
 }

+ 31 - 0
app/Rules/ScheduleBusinessRules.php

@@ -318,4 +318,35 @@ class ScheduleBusinessRules
             ->distinct()
             ->pluck('provider_id');
     }
+
+    /**
+     * Retorna os IDs de prestadores disponíveis em uma data específica,
+     * dentro de um conjunto pré-filtrado de IDs.
+     *
+     * Regras (em batch, sem iteração PHP):
+     *  1. Prestador tem pelo menos um ProviderWorkingDay para o day_of_week da data.
+     *  2. Prestador NÃO tem ProviderBlockedDay com period = 'all' nessa data.
+     *     (period = 'morning' ou 'afternoon' = bloqueio parcial → ainda disponível)
+     *
+     * @param string     $date_ymd   Y-m-d
+     * @param Collection $providerIds  conjunto de IDs a filtrar
+     * @return Collection
+     */
+    public static function getAvailableProviderIdsForDate(string $date_ymd, Collection $providerIds): Collection
+    {
+        $dayOfWeek = Carbon::parse($date_ymd)->dayOfWeek;
+
+        $withWorkingDay = ProviderWorkingDay::whereIn('provider_id', $providerIds)
+            ->where('day', $dayOfWeek)
+            ->pluck('provider_id')
+            ->unique();
+
+        $fullyBlockedIds = ProviderBlockedDay::whereIn('provider_id', $withWorkingDay)
+            ->where('date', $date_ymd)
+            ->where('period', 'all')
+            ->pluck('provider_id')
+            ->unique();
+
+        return $withWorkingDay->diff($fullyBlockedIds)->values();
+    }
 }

+ 80 - 10
app/Services/DashboardService.php

@@ -95,17 +95,22 @@ class DashboardService
 
     $blockedProviderIds       = ScheduleBusinessRules::getBlockedProviderIdsForClient($cliente->id);
     $providersWithWorkingDays = ScheduleBusinessRules::getProviderIdsWithWorkingDays();
+    $clientAddress = Address::where('source', 'client')
+      ->where('source_id', $cliente->id)
+      ->orderBy('is_primary', 'desc')
+      ->first();
 
     $providersClose = Provider::leftJoin('users as provider_user', 'provider_user.id', '=', 'providers.user_id')
-      ->leftJoin('addresses as provider_address', function ($join) {
-        $join->on('provider_address.source_id', '=', 'providers.id')
-          ->where('provider_address.source', 'provider');
-      })
-      ->leftJoin('addresses as client_address', function ($join) use ($cliente) {
-        $join->where('client_address.source_id', $cliente->id)
-          ->where('client_address.source', 'client');
-      })
-      ->whereColumn('provider_address.city_id', '=', 'client_address.city_id')
+      ->leftJoin(DB::raw("
+        (
+          SELECT DISTINCT ON (source_id)
+            *
+          FROM addresses
+          WHERE source = 'provider'
+          ORDER BY source_id, is_primary DESC
+        ) as provider_address
+      "), 'provider_address.source_id', '=', 'providers.id')
+      ->where('provider_address.city_id', '=', $clientAddress->city_id)
       ->whereNotIn('providers.id', $blockedProviderIds)
       ->whereIn('providers.id', $providersWithWorkingDays)
       ->where(function ($query) {
@@ -117,7 +122,6 @@ class DashboardService
       ->select(
         'providers.id as provider_id',
         'provider_user.name as provider_name',
-        'provider_address.district',
         'providers.average_rating',
         DB::raw("(
           SELECT COUNT(*)
@@ -144,6 +148,72 @@ class DashboardService
     ];
   }
 
+  public function buscaPrestadores(?string $name = null, ?string $date = null): array
+  {
+    $user    = Auth::user();
+    $cliente = Client::where('user_id', $user->id)->first();
+
+    $blockedProviderIds       = ScheduleBusinessRules::getBlockedProviderIdsForClient($cliente->id);
+    $providersWithWorkingDays = ScheduleBusinessRules::getProviderIdsWithWorkingDays();
+
+    $clientPrimaryAddress = Address::where('source', 'client')
+      ->where('source_id', $cliente->id)
+      ->orderBy('is_primary', 'desc')
+      ->first();
+
+    return Provider::leftJoin('users as provider_user', 'provider_user.id', '=', 'providers.user_id')
+      ->leftJoin(DB::raw("
+        (
+          SELECT DISTINCT ON (source_id)
+            *
+          FROM addresses
+          WHERE source = 'provider'
+          ORDER BY source_id, is_primary DESC
+        ) as provider_address
+      "), 'provider_address.source_id', '=', 'providers.id')
+      ->whereNotNull('provider_address.id')
+      ->where('provider_address.city_id', $clientPrimaryAddress?->city_id)
+      ->whereNotIn('providers.id', $blockedProviderIds)
+      ->whereIn('providers.id', $providersWithWorkingDays)
+      ->whereNotNull('providers.daily_price_8h')
+      ->whereNotNull('providers.daily_price_6h')
+      ->whereNotNull('providers.daily_price_4h')
+      ->whereNotNull('providers.daily_price_2h')
+      ->when($name, fn($q) => $q->where('provider_user.name', 'ILIKE', "%{$name}%"))
+      ->select(
+        'providers.id as provider_id',
+        'provider_user.name as provider_name',
+        'provider_address.district',
+        'providers.average_rating',
+        'providers.total_services',
+        'providers.daily_price_8h',
+        'providers.daily_price_6h',
+        'providers.daily_price_4h',
+        'providers.daily_price_2h',
+        'providers.created_at',
+        DB::raw("(
+          SELECT COUNT(*)
+          FROM reviews
+          LEFT JOIN schedules ON schedules.id = reviews.schedule_id
+          WHERE reviews.origin = 'provider'
+          AND schedules.provider_id = providers.id
+        ) as total_reviews"),
+      )
+      ->orderBy('providers.average_rating', 'desc')
+      ->get()
+      ->when(
+        $date,
+        fn($collection) => $collection->whereIn(
+          'provider_id',
+          ScheduleBusinessRules::getAvailableProviderIdsForDate(
+            $date,
+            $collection->pluck('provider_id')
+          )->toArray()
+        )->values()
+      )
+      ->toArray();
+  }
+
   public function dadosDashboardPrestador(): array
   {
     $user = Auth::user();

+ 2 - 1
routes/authRoutes/dashboard.php

@@ -4,4 +4,5 @@ use App\Http\Controllers\DashboardController;
 use Illuminate\Support\Facades\Route;
 
 Route::get('/dados-dashboard-cliente', [DashboardController::class, 'dadosDashboardCliente'])->middleware('permission:dashboard,view');
-Route::get('/dados-dashboard-prestador', [DashboardController::class, 'dadosDashboardPrestador'])->middleware('permission:dashboard,view');
+Route::get('/dados-dashboard-prestador', [DashboardController::class, 'dadosDashboardPrestador'])->middleware('permission:dashboard,view');
+Route::get('/prestadores-busca', [DashboardController::class, 'buscaPrestadores'])->middleware('permission:dashboard,view');