6
0

14 Commits 31db4522b3 ... 91f27d9994

Autor SHA1 Nachricht Datum
  kayo henrique 91f27d9994 feat: :sparkles: feat(agendamento-sob-medida) ajustado validação de preço por periodo vor 1 Woche
  Gustavo Zanatta ac5a5771ec Merge remote-tracking branch 'origin/feature/diariaapp-kay-agendamentos-sob-medida-apps' into development vor 2 Wochen
  Gustavo Zanatta 6aa2d37b32 ajustes merge vor 2 Wochen
  zntt 5dfb4f6265 Merge branch 'feature/diariaapp-gus-agendamentos-apps' of Softpar/sfp_api_laravel_diarista into development vor 2 Wochen
  Gustavo Zanatta b2d4e998e7 Merge branch 'development' into feature/diariaapp-gus-agendamentos-apps vor 2 Wochen
  Gustavo Zanatta 01df534426 comentario regra de negocio vor 2 Wochen
  Gustavo Zanatta 302b628ea3 feat: :sparkles: feat (agendamentos) fluxo cancelamento de um agendamento vor 2 Wochen
  Gustavo Zanatta 31ee0218c5 fix: :bug: fix (agendamento) corrigir exibição de informações e filtro de status vor 2 Wochen
  Gustavo Zanatta 0e0430709d feat: :sparkles: feat (agendamentos) inclusao do bloco todayservices na dashboard do prestador vor 3 Wochen
  Gustavo Zanatta c0e971cd49 feat: :sparkles: feat (agendamento) fluxo de aceite e recusa de um agendamento pelo prestador vor 3 Wochen
  Gustavo Zanatta 8893c8a903 feat: :sparkles: feat (agendamentos default) fluxo de gerar agendamentos default vor 3 Wochen
  Gustavo Zanatta b1d306db49 fix corrige migrate original para ser conforme o padrao necessario vor 3 Wochen
  zntt ac0c2ca108 Merge branch 'fix/diariaapp-gus-ajustes-layout' of Softpar/sfp_api_laravel_diarista into development vor 3 Wochen
  Gustavo Zanatta 99e890a914 fix: :bug: correcoes layout dashboard cliente vor 3 Wochen

+ 0 - 11
app/Http/Controllers/DashboardController.php

@@ -26,17 +26,6 @@ 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 {

+ 19 - 1
app/Http/Controllers/ScheduleController.php

@@ -6,7 +6,7 @@ use App\Http\Requests\ScheduleRequest;
 use App\Http\Resources\ScheduleResource;
 use App\Services\ScheduleService;
 use Illuminate\Http\JsonResponse;
-use Illuminate\Support\Facades\Request;
+use Illuminate\Http\Request;
 
 class ScheduleController extends Controller
 {
@@ -102,4 +102,22 @@ class ScheduleController extends Controller
       return $this->errorResponse($e->getMessage(), 422);
     }
   }
+
+  public function cancelWithReason(string $id, Request $request): JsonResponse
+  {
+    try {
+      $validated = $request->validate([
+        'cancel_text' => 'required|string|min:5|max:1000',
+      ]);
+
+      $schedule = $this->scheduleService->cancelWithReason((int) $id, $validated['cancel_text']);
+
+      return $this->successResponse(
+        payload: new ScheduleResource($schedule),
+        message: __("messages.updated"),
+      );
+    } catch (\Exception $e) {
+      return $this->errorResponse($e->getMessage(), 422);
+    }
+  }
 }

+ 24 - 0
app/Http/Controllers/SearchController.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Services\SearchService;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Log;
+
+class SearchController extends Controller
+{
+  public function __construct(private readonly SearchService $service) {}
+
+  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);
+    }
+  }
+}

+ 2 - 0
app/Http/Requests/ScheduleRequest.php

@@ -26,6 +26,8 @@ class ScheduleRequest extends FormRequest
             'schedules.*.start_time' => 'required|date_format:H:i',
             'schedules.*.end_time' => 'required|date_format:H:i|after:schedules.*.start_time',
             'schedules.*.total_amount' => 'required|numeric|min:0',
+            'schedules.*.offers_meal' => 'nullable|boolean',
+            'offers_meal' => 'sometimes|nullable|boolean',
             'period_type' => 'sometimes|required|in:2,4,6,8',
             'schedule_type' => 'sometimes|in:default,custom',
             'start_time' => 'sometimes|required|date_format:H:i',

+ 1 - 0
app/Http/Resources/DashboardClienteResource.php

@@ -17,6 +17,7 @@ class DashboardClienteResource extends JsonResource
     return [
       'headerBar' => $this['headerBar'],
       'summaryInfos' => $this['summaryInfos'],
+      'pendingSchedules' => $this['pendingSchedules'],
       'nextSchedules' => $this['nextSchedules'],
       'lastDoneSchedules' => $this['lastDoneSchedules'],
       'favoriteProviders' => $this['favoriteProviders'],

+ 1 - 0
app/Http/Resources/DashboardPrestadorResource.php

@@ -18,6 +18,7 @@ class DashboardPrestadorResource extends JsonResource
       'headerBar' => $this['headerBar'],
       'summaryInfos' => $this['summaryInfos'],
       'priceSuggested' => $this['priceSuggested'],
+      'todayServices' => $this['todayServices'],
       'solicitations' => $this['solicitations'],
       'nextSchedules' => $this['nextSchedules'],
       'opportunities' => $this['opportunities'],

+ 1 - 0
app/Http/Resources/ScheduleResource.php

@@ -28,6 +28,7 @@ class ScheduleResource extends JsonResource
             'schedule_type' => $this->schedule_type,
             'start_time' => $this->start_time,
             'end_time' => $this->end_time,
+            'offers_meal' => $this->offers_meal,
             'status' => $this->status,
             'total_amount' => $this->total_amount,
             'code' => $this->code,

+ 22 - 1
app/Models/Provider.php

@@ -5,6 +5,8 @@ namespace App\Models;
 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\HasOne;
 use Illuminate\Database\Eloquent\SoftDeletes;
 
 /**
@@ -82,7 +84,26 @@ class Provider extends Model
   }
 
   /**
-   * @return \Illuminate\Database\Eloquent\Relations\HasMany
+   * @return HasMany
+     */
+    public function addresses(): HasMany
+    {
+        return $this->hasMany(Address::class, 'source_id')
+            ->where('source', 'provider');
+    }
+
+    // /**
+    //  * @return HasOne
+    //  */
+    // public function primaryAddress(): HasOne
+    // {
+    //     return $this->hasOne(Address::class, 'source_id')
+    //         ->where('source', 'provider')
+    //         ->where('is_primary', true);
+    // }
+
+    /**
+     * @return HasMany
    */
   public function blockedClients()
   {

+ 4 - 0
app/Models/Schedule.php

@@ -23,12 +23,16 @@ class Schedule extends Model
         'total_amount',
         'code',
         'code_verified',
+        'offers_meal',
+        'cancel_text',
+        'cancelled_by',
     ];
 
     protected $casts = [
         'date' => 'date',
         'code_verified' => 'boolean',
         'total_amount' => 'decimal:2',
+        'offers_meal' => 'boolean',
     ];
 
     public function client()

+ 15 - 1
app/Rules/ScheduleBusinessRules.php

@@ -109,6 +109,7 @@ class ScheduleBusinessRules
       return true;
     }
 
+    // apenas para custom_schedules
     public static function validatePricePeriod($provider_id, $min_price, $max_price, $period_type)
     {
       if ($min_price < 0 || $max_price < 0) {
@@ -120,25 +121,38 @@ class ScheduleBusinessRules
       }
 
       $provider = Provider::find($provider_id);
+      $min_price_proportional = 0 ;
+      $max_price_proportional = 0; 
+
       $provider_price_period = 0;
       switch ($period_type):
         case '2': //2 horas
           $provider_price_period = $provider->daily_price_2h;
+          $min_price_proportional = $min_price * 0.30;
+          $max_price_proportional = $max_price * 0.30;
           break;
         case '4': //4 horas
           $provider_price_period = $provider->daily_price_4h;
+          $min_price_proportional = $min_price * 0.55;
+          $max_price_proportional = $max_price * 0.55;
           break;
         case '6': //6 horas
           $provider_price_period = $provider->daily_price_6h;
+          $min_price_proportional = $min_price * 0.85;
+          $max_price_proportional = $max_price * 0.85;
+
           break;
         case '8': //8 horas
           $provider_price_period = $provider->daily_price_8h;
+          $min_price_proportional = $min_price;
+          $max_price_proportional = $max_price;
+
           break;
         default:
           throw new \Exception(__('validation.custom.schedule.invalid_period_type'));
         endswitch;
 
-      if ($provider_price_period < $min_price || $provider_price_period > $max_price) {
+      if ($provider_price_period < $min_price_proportional || $provider_price_period > $max_price_proportional) {
         throw new \Exception(__('validation.custom.schedule.price_not_in_range'));
       }
 

+ 95 - 101
app/Services/DashboardService.php

@@ -64,16 +64,23 @@ class DashboardService
         'custom_schedules.address_type as custom_address_type',
       )
       ->orderBy('schedules.date', 'asc')
+      ->limit(5)
       ->get();
 
     $lastDoneSchedules = Schedule::where('schedules.client_id', $cliente->id)
       ->where('schedules.status', 'finished')
       ->leftJoin('providers', 'providers.id', '=', 'schedules.provider_id')
       ->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')
+          ->orderBy('provider_address.is_primary', 'desc');
+      })
       ->select(
         'schedules.id',
         'schedules.provider_id',
         'provider_user.name as provider_name',
+        'provider_address.district as provider_district',
       )
       ->orderBy('schedules.date', 'desc')
       ->limit(5)
@@ -82,6 +89,11 @@ class DashboardService
     $favoriteProviders = ClientFavoriteProvider::where('client_favorite_providers.client_id', $cliente->id)
       ->leftJoin('providers', 'providers.id', '=', 'client_favorite_providers.provider_id')
       ->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')
+          ->orderBy('provider_address.is_primary', 'desc');
+      })
       ->select(
         'providers.id as provider_id',
         'provider_user.name as provider_name',
@@ -90,14 +102,17 @@ class DashboardService
         'providers.daily_price_6h',
         'providers.daily_price_4h',
         'providers.daily_price_2h',
+        'provider_address.district as provider_district',
       )
+      ->orderBy('client_favorite_providers.created_at', 'desc')
+      ->limit(5)
       ->get();
 
     $blockedProviderIds       = ScheduleBusinessRules::getBlockedProviderIdsForClient($cliente->id);
     $providersWithWorkingDays = ScheduleBusinessRules::getProviderIdsWithWorkingDays();
-    $clientAddress = Address::where('source', 'client')
+    $clientPrimaryAddress = Address::where('source', 'client')
       ->where('source_id', $cliente->id)
-      ->orderBy('is_primary', 'desc')
+      ->where('is_primary', true)
       ->first();
 
     $providersClose = Provider::leftJoin('users as provider_user', 'provider_user.id', '=', 'providers.user_id')
@@ -107,22 +122,26 @@ class DashboardService
             *
           FROM addresses
           WHERE source = 'provider'
+          AND deleted_at IS NULL
           ORDER BY source_id, is_primary DESC
         ) as provider_address
       "), 'provider_address.source_id', '=', 'providers.id')
-      ->where('provider_address.city_id', '=', $clientAddress->city_id)
+      ->whereNotNull('provider_address.id')
+      ->where('provider_address.city_id', $clientPrimaryAddress?->city_id)
       ->whereNotIn('providers.id', $blockedProviderIds)
       ->whereIn('providers.id', $providersWithWorkingDays)
-      ->where(function ($query) {
-        $query->whereNotNull('providers.daily_price_8h')
-          ->orWhereNotNull('providers.daily_price_6h')
-          ->orWhereNotNull('providers.daily_price_4h')
-          ->orWhereNotNull('providers.daily_price_2h');
-      })
+      ->whereNull('providers.deleted_at')
+
       ->select(
         'providers.id as provider_id',
         'provider_user.name as provider_name',
+        'provider_address.id as address_id',
         'providers.average_rating',
+        'providers.total_services',
+        'providers.daily_price_8h',
+        'providers.daily_price_6h',
+        'providers.daily_price_4h',
+        'providers.daily_price_2h',
         DB::raw("(
           SELECT COUNT(*)
           FROM reviews
@@ -130,17 +149,41 @@ class DashboardService
           WHERE reviews.origin = 'provider'
           AND schedules.provider_id = providers.id
         ) as total_reviews"),
-        'providers.total_services',
-        'providers.daily_price_8h',
-        'providers.daily_price_6h',
-        'providers.daily_price_4h',
-        'providers.daily_price_2h',
       )
       ->get();
 
+    $pendingSchedules = Schedule::with('address:district,address,number,source_id,source,id,address_type')
+      ->where('schedules.client_id', $cliente->id)
+      ->whereIn('schedules.status', ['pending', 'accepted'])
+      ->where('schedules.schedule_type', 'default')
+      ->leftJoin('providers', 'providers.id', '=', 'schedules.provider_id')
+      ->leftJoin('users as provider_user', 'provider_user.id', '=', 'providers.user_id')
+      ->select(
+        'schedules.id',
+        'schedules.provider_id',
+        'provider_user.name as provider_name',
+        'schedules.date',
+        DB::raw("TO_CHAR(schedules.date, 'DD \"de\" TMMonth \"de\" YYYY') as formatted_date"),
+        'schedules.start_time',
+        'schedules.end_time',
+        'schedules.period_type',
+        'schedules.address_id',
+        'schedules.status',
+        'schedules.total_amount',
+        DB::raw("(SELECT district FROM addresses WHERE source = 'provider' AND source_id = schedules.provider_id and deleted_at is null ORDER BY is_primary DESC LIMIT 1) as provider_district"),
+        DB::raw("CASE
+          WHEN (now() - schedules.created_at) < INTERVAL '1 hour' THEN CONCAT(ROUND(EXTRACT(EPOCH FROM (now() - schedules.created_at)) / 60), 'min')
+          WHEN (now() - schedules.created_at) < INTERVAL '1 day' THEN CONCAT(ROUND(EXTRACT(EPOCH FROM (now() - schedules.created_at)) / 3600), 'h')
+          ELSE CONCAT(ROUND(EXTRACT(EPOCH FROM (now() - schedules.created_at)) / 86400), 'd')
+        END as time_since_request"),
+      )
+      ->orderBy('schedules.date', 'asc')
+      ->get();
+
     return [
       'headerBar'        => $headerBar,
       'summaryInfos'     => $summaryInfos,
+      'pendingSchedules' => $pendingSchedules,
       'nextSchedules'    => $nextSchedules,
       'lastDoneSchedules' => $lastDoneSchedules,
       'favoriteProviders' => $favoriteProviders,
@@ -148,72 +191,6 @@ 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();
@@ -249,6 +226,7 @@ class DashboardService
       ->where('schedules.status', 'pending')
       ->leftJoin('clients', 'clients.id', '=', 'schedules.client_id')
       ->leftJoin('users as client_user', 'client_user.id', '=', 'clients.user_id')
+      ->leftJoin('custom_schedules', 'custom_schedules.schedule_id', '=', 'schedules.id')
       ->select(
         'schedules.id',
         'client_user.name as client_name',
@@ -261,6 +239,8 @@ class DashboardService
         'schedules.period_type',
         'schedules.schedule_type',
         'schedules.address_id',
+        'schedules.status',
+        'custom_schedules.offers_meal',
         DB::raw("CASE
           WHEN (now() - schedules.created_at) < INTERVAL '1 day' THEN CONCAT(ROUND(EXTRACT(EPOCH FROM (now() - schedules.created_at)) / 3600), ' hours ago')
           ELSE CONCAT(ROUND(EXTRACT(EPOCH FROM (now() - schedules.created_at)) / 86400), ' days ago')
@@ -269,6 +249,31 @@ class DashboardService
       ->orderBy('schedules.date', 'asc')
       ->get();
 
+    $todayServices = Schedule::with('address:district,address,number,source_id,source,id')
+      ->where('schedules.provider_id', $provider->id)
+      ->whereIn('schedules.status', ['accepted', 'paid', 'started'])
+      ->whereDate('schedules.date', now()->toDateString())
+      ->leftJoin('clients', 'clients.id', '=', 'schedules.client_id')
+      ->leftJoin('users as client_user', 'client_user.id', '=', 'clients.user_id')
+      ->leftJoin('custom_schedules', 'custom_schedules.schedule_id', '=', 'schedules.id')
+      ->select(
+        'schedules.id',
+        'client_user.name as client_name',
+        'schedules.date',
+        'schedules.start_time',
+        'schedules.end_time',
+        'schedules.total_amount',
+        'schedules.period_type',
+        'schedules.address_id',
+        'schedules.schedule_type',
+        'schedules.status',
+        'schedules.code_verified',
+        'schedules.status',
+        'custom_schedules.offers_meal',
+      )
+      ->orderBy('schedules.start_time', 'asc')
+      ->get();
+
     $nextSchedules = Schedule::with('address:district,address,number,source_id,source,id')
       ->where('schedules.provider_id', $provider->id)
       ->whereIn('schedules.status', ['accepted', 'paid'])
@@ -285,6 +290,7 @@ class DashboardService
         'schedules.period_type',
         'schedules.address_id',
         'schedules.schedule_type',
+        'schedules.status',
         'custom_schedules.offers_meal',
       )
       ->orderBy('schedules.date', 'asc')
@@ -306,36 +312,24 @@ class DashboardService
         'schedules.period_type',
         'schedules.schedule_type',
         'schedules.address_id',
-        'custom_schedules.address_type'
+        'custom_schedules.address_type',
+        DB::raw("CASE
+          WHEN schedules.period_type = '2' THEN {$provider->daily_price_2h}
+          WHEN schedules.period_type = '4' THEN {$provider->daily_price_4h}
+          WHEN schedules.period_type = '6' THEN {$provider->daily_price_6h}
+          WHEN schedules.period_type = '8' THEN {$provider->daily_price_8h}
+          ELSE 0
+        END as total_amount"),
       )
       ->orderBy('schedules.date', 'asc')
       ->get();
 
-    $opportunities = $opportunities->map(function ($item) use ($provider) {
-      $price = 0;
-      switch ($item->period_type) {
-        case 2:
-          $price = $provider->daily_price_2h;
-          break;
-        case 4:
-          $price = $provider->daily_price_4h;
-          break;
-        case 6:
-          $price = $provider->daily_price_6h;
-          break;
-        case 8:
-          $price = $provider->daily_price_8h;
-          break;
-      }
-      $item->total_amount = $price;
-      return $item;
-    });
-
     return [
       'headerBar' => $headerBar,
       'summaryInfos' => $summaryInfos,
       'priceSuggested' => $priceSuggested,
       'solicitations' => $solicitations,
+      'todayServices' => $todayServices,
       'nextSchedules' => $nextSchedules,
       'opportunities' => $opportunities,
     ];

+ 39 - 8
app/Services/ScheduleService.php

@@ -10,6 +10,7 @@ use App\Models\ProviderBlockedDay;
 use App\Models\ProviderWorkingDay;
 use App\Rules\ScheduleBusinessRules;
 use Carbon\Carbon;
+use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 
@@ -40,19 +41,21 @@ class ScheduleService
 
   public function createSingleOrMultiple(array $baseData, array $schedules)
   {
-    $createdSchedules = [];
-    foreach ($schedules as $schedule) {
-      try {
+    try {
+      DB::beginTransaction();
+      $createdSchedules = [];
+      foreach ($schedules as $schedule) {
         $datasMerged = array_merge($baseData, $schedule);
         $this->validateProviderAvailability($datasMerged, null);
         $scheduleData = array_merge($datasMerged, [
           'code' => str_pad(random_int(0, 9999), 4, '0', STR_PAD_LEFT),
         ]);
         $createdSchedules[] = Schedule::create($scheduleData);
-
-      } catch (\Exception $e) {
-        throw new \Exception(__($e->getMessage()));
       }
+      DB::commit();
+    } catch (\Exception $e) {
+      DB::rollBack();
+      throw new \Exception(__($e->getMessage()));
     }
 
     return $createdSchedules;
@@ -268,10 +271,10 @@ class ScheduleService
           $date_cleaned = Carbon::parse($schedule->date)->format('Y-m-d');
           $date_time_dispatch = Carbon::parse($date_cleaned . ' ' . $schedule->start_time);
 
-          StartScheduleJob::dispatch($schedule->id)->delay($date_time_dispatch);
+          // StartScheduleJob::dispatch($schedule->id)->delay($date_time_dispatch);
 
           // dispatch de teste em local
-          // StartScheduleJob::dispatch($schedule->id)->delay(now()->addSeconds(15));
+          StartScheduleJob::dispatch($schedule->id)->delay(now()->addSeconds(15));
           break;
         case 'cancelled':
           break;
@@ -289,4 +292,32 @@ class ScheduleService
       throw new \Exception("Não foi possível atualizar o status do agendamento.");
     }
   }
+
+  public function cancelWithReason(int $id, string $cancelText)
+  {
+    try {
+      DB::beginTransaction();
+      $schedule = Schedule::findOrFail($id);
+
+      $allowedStatuses = ['accepted', 'paid'];
+      if (!in_array($schedule->status, $allowedStatuses)) {
+        throw new \Exception("Cancelamento não permitido para o status atual: {$schedule->status}");
+      }
+
+      $cancelled_by = Auth::user()->type;
+
+      $schedule->update([
+        'status'      => 'cancelled',
+        'cancel_text' => $cancelText,
+        'cancelled_by' => $cancelled_by,
+      ]);
+
+      DB::commit();
+      return $schedule->fresh(['client.user', 'provider.user', 'address']);
+    } catch (\Exception $e) {
+      DB::rollBack();
+      Log::error("Erro ao cancelar agendamento: " . $e->getMessage());
+      throw new \Exception("Não foi possível cancelar o agendamento.");
+    }
+  }
 }

+ 87 - 0
app/Services/SearchService.php

@@ -0,0 +1,87 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\Address;
+use App\Models\Client;
+use App\Models\ClientFavoriteProvider;
+use App\Models\Provider;
+use App\Models\Review;
+use App\Models\Schedule;
+use App\Rules\ScheduleBusinessRules;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+class SearchService
+{
+  public function __construct() {}
+
+
+  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();
+  }
+
+}

+ 1 - 1
database/migrations/0001_01_01_000000_create_users_table.php

@@ -16,7 +16,7 @@ return new class extends Migration
             $table->string('name');
             $table->string('email')->unique();
             $table->timestamp('email_verified_at')->nullable();
-            $table->string('password');
+            $table->string('password')->nullable();
             $table->string('type')->default('USER');
             $table->string('language')->default('pt');
             $table->timestamps();

+ 28 - 0
database/migrations/2026_04_09_110218_add_offers_meal_to_schedules_table.php

@@ -0,0 +1,28 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::table('schedules', function (Blueprint $table) {
+            $table->boolean('offers_meal')->nullable()->default(null)->after('end_time');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::table('schedules', function (Blueprint $table) {
+            $table->dropColumn('offers_meal');
+        });
+    }
+};

+ 24 - 0
database/migrations/2026_04_14_000001_add_cancel_text_to_schedules_table.php

@@ -0,0 +1,24 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::table('schedules', function (Blueprint $table) {
+            $table->text('cancel_text')->nullable()->after('offers_meal');
+            $table->string('cancelled_by')->nullable()->after('cancel_text');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::table('schedules', function (Blueprint $table) {
+            $table->dropColumn('cancel_text');
+            $table->dropColumn('cancelled_by');
+        });
+    }
+};

+ 29 - 0
database/migrations/2026_04_20_100127_alter_clients_set_average_rating_default_0.php

@@ -0,0 +1,29 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::table('clients', function (Blueprint $table) {
+         $table->decimal('average_rating', 2, 1)->nullable()->default(0.0)->change();
+
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::table('clients', function (Blueprint $table) {
+                  $table->decimal('average_rating', 2, 1)->nullable()->change();
+        });
+    }
+};

+ 28 - 0
database/migrations/2026_04_20_100221_alter_providers_set_average_rating_default_0.php

@@ -0,0 +1,28 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::table('providers', function (Blueprint $table) {
+            $table->decimal('average_rating', 2, 1)->nullable()->default(0.0)->change();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::table('providers', function (Blueprint $table) {
+            $table->decimal('average_rating', 2, 1)->nullable()->change();
+        });
+    }
+};

+ 5 - 0
database/seeders/UserTypePermissionSeeder.php

@@ -70,6 +70,7 @@ class UserTypePermissionSeeder extends Seeder
             ['scope' => 'config.provider_blocked_day', 'bits' => 271],
             ['scope' => 'config.provider_services_types', 'bits' => 271],
             ['scope' => 'config.service_type', 'bits' => 1],
+            ['scope' => 'config.schedule', 'bits' => 271],
             ['scope' => 'config.custom_schedule', 'bits' => 271],
           ];
           $this->seedUserTypePermissions($providerPermissions, UserTypeEnum::PROVIDER->value);
@@ -84,6 +85,10 @@ class UserTypePermissionSeeder extends Seeder
             ['scope' => 'config.provider_payment_method', 'bits' => 271],
             ['scope' => 'config.client_favorite_provider', 'bits' => 271],
             ['scope' => 'config.client_payment_method', 'bits' => 271],
+            ['scope' => 'config.provider_working_day', 'bits' => 271],
+            ['scope' => 'config.provider_blocked_day', 'bits' => 271],
+            ['scope' => 'config.review', 'bits' => 271],
+            ['scope' => 'config.schedule', 'bits' => 271],
             ['scope' => 'config.custom_schedule', 'bits' => 271],
             ['scope' => 'config.speciality', 'bits' => 271],
             ['scope' => 'config.service_type', 'bits' => 271],

+ 1 - 2
routes/authRoutes/dashboard.php

@@ -4,5 +4,4 @@ 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('/prestadores-busca', [DashboardController::class, 'buscaPrestadores'])->middleware('permission:dashboard,view');
+Route::get('/dados-dashboard-prestador', [DashboardController::class, 'dadosDashboardPrestador'])->middleware('permission:dashboard,view');

+ 1 - 0
routes/authRoutes/schedule.php

@@ -10,4 +10,5 @@ Route::get('/schedule/{id}', [ScheduleController::class, 'show'])->middleware('p
 Route::post('/schedule', [ScheduleController::class, 'store'])->middleware('permission:config.schedule,add');
 Route::put('/schedule/{id}', [ScheduleController::class, 'update'])->middleware('permission:config.schedule,edit');
 Route::patch('/schedule/{id}/status', [ScheduleController::class, 'updateStatus'])->middleware('permission:config.schedule,edit');
+Route::patch('/schedule/{id}/cancel', [ScheduleController::class, 'cancelWithReason'])->middleware('permission:config.schedule,edit');
 Route::delete('/schedule/{id}', [ScheduleController::class, 'destroy'])->middleware('permission:config.schedule,delete');

+ 6 - 0
routes/authRoutes/search.php

@@ -0,0 +1,6 @@
+<?php
+
+use App\Http\Controllers\SearchController;
+use Illuminate\Support\Facades\Route;
+
+Route::get('/prestadores-busca', [SearchController::class, 'buscaPrestadores'])->middleware('permission:dashboard,view');