Procházet zdrojové kódy

custom schedules + fluxos de status + regra bloqueio 2 por semana

Gustavo Zanatta před 1 týdnem
rodič
revize
aa0503d88f

+ 191 - 56
app/Http/Controllers/CustomScheduleController.php

@@ -3,78 +3,213 @@
 namespace App\Http\Controllers;
 
 use App\Http\Requests\CustomScheduleRequest;
+use App\Http\Requests\CustomScheduleProposeRequest;
+use App\Http\Requests\CustomScheduleRefuseOpportunityRequest;
+use App\Http\Requests\CustomScheduleVerifyCodeRequest;
 use App\Http\Resources\CustomScheduleResource;
 use App\Services\CustomScheduleService;
 use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Facades\Log;
 
 class CustomScheduleController extends Controller
 {
-    protected $customScheduleService;
+  protected $customScheduleService;
 
-    public function __construct(CustomScheduleService $customScheduleService)
-    {
-        $this->customScheduleService = $customScheduleService;
+  public function __construct(CustomScheduleService $customScheduleService)
+  {
+    $this->customScheduleService = $customScheduleService;
+  }
+
+  public function index(): JsonResponse
+  {
+    $customSchedules = $this->customScheduleService->getAll();
+    return $this->successResponse(
+      CustomScheduleResource::collection($customSchedules),
+    );
+  }
+
+  public function store(CustomScheduleRequest $request): JsonResponse
+  {
+    try {
+      $validated = $request->validated();
+      $customSchedules = $this->customScheduleService->create($validated);
+
+      $count = count($customSchedules);
+      $message = $count > 1
+        ? "{$count} oportunidades criadas com sucesso!"
+        : __("messages.created");
+
+      return $this->successResponse(
+        payload: CustomScheduleResource::collection($customSchedules),
+        message: $message,
+        code: 201,
+      );
+    } catch (\Exception $e) {
+      return $this->errorResponse($e->getMessage(), 422);
+    }
+  }
+
+  public function show(string $id): JsonResponse
+  {
+    $customSchedule = $this->customScheduleService->getById($id);
+    return $this->successResponse(
+      new CustomScheduleResource($customSchedule),
+    );
+  }
+
+  public function update(CustomScheduleRequest $request, string $id): JsonResponse
+  {
+    try {
+      $customSchedule = $this->customScheduleService->update($id, $request->validated());
+      return $this->successResponse(
+        payload: new CustomScheduleResource($customSchedule),
+        message: __("messages.updated"),
+      );
+    } catch (\Exception $e) {
+      return $this->errorResponse($e->getMessage(), 422);
+    }
+  }
+
+  public function destroy(string $id): JsonResponse
+  {
+    try {
+      $this->customScheduleService->delete($id);
+      return $this->successResponse(
+        message: __("messages.deleted"),
+      );
+    } catch (\Exception $e) {
+      return $this->errorResponse($e->getMessage(), 422);
+    }
+  }
+
+  public function groupedByClientCustom(): JsonResponse
+  {
+    $grouped = $this->customScheduleService->getSchedulesCustomGroupedByClient();
+    return $this->successResponse($grouped);
+  }
+
+  public function available(CustomScheduleRequest $request)
+  {
+    try {
+      $providerId = $request->query('provider_id');
+
+      if (!$providerId) {
+        return $this->errorResponse('Provider ID é obrigatório', 400);
+      }
+
+      $opportunities = $this->customScheduleService->getAvailableOpportunities($providerId);
+
+      return $this->successResponse($opportunities);
+    } catch (\Exception $e) {
+      Log::error('Error fetching available opportunities: ' . $e->getMessage());
+      return $this->errorResponse($e->getMessage(), 500);
+    }
+  }
+
+  public function providerProposals(CustomScheduleRequest $request)
+  {
+    try {
+      $providerId = $request->query('provider_id');
+
+      if (!$providerId) {
+        return $this->errorResponse('Provider ID é obrigatório', 400);
+      }
+
+      $proposals = $this->customScheduleService->getProviderProposals($providerId);
+
+      return $this->successResponse($proposals);
+    } catch (\Exception $e) {
+      Log::error('Error fetching provider proposals: ' . $e->getMessage());
+      return $this->errorResponse($e->getMessage(), 500);
+    }
+  }
+
+  public function opportunityProposals($scheduleId)
+  {
+    try {
+      $proposals = $this->customScheduleService->getOpportunityProposals($scheduleId);
+
+      return $this->successResponse($proposals);
+    } catch (\Exception $e) {
+      Log::error('Error fetching opportunity proposals: ' . $e->getMessage());
+      return $this->errorResponse($e->getMessage(), 500);
+    }
+  }
+
+  public function propose(CustomScheduleProposeRequest $request, $scheduleId)
+  {
+    try {
+      $providerId = $request->input('provider_id');
+
+      if (!$providerId) {
+        return $this->errorResponse('Provider ID é obrigatório', 400);
+      }
+
+      $proposal = $this->customScheduleService->proposeOpportunity($scheduleId, $providerId);
+
+      return $this->successResponse($proposal, 'Proposta enviada com sucesso!', 201);
+    } catch (\Exception $e) {
+      Log::error('Error creating proposal: ' . $e->getMessage());
+      return $this->errorResponse($e->getMessage(), 400);
     }
+  }
 
-    public function index(): JsonResponse
-    {
-        $customSchedules = $this->customScheduleService->getAll();
-        return $this->successResponse(
-            CustomScheduleResource::collection($customSchedules),
-        );
+  public function acceptProposal($proposalId)
+  {
+    try {
+      $schedule = $this->customScheduleService->acceptProposal($proposalId);
+
+      return $this->successResponse($schedule, 'Prestador aceito com sucesso!');
+    } catch (\Exception $e) {
+      Log::error('Error accepting proposal: ' . $e->getMessage());
+      return $this->errorResponse($e->getMessage(), 400);
     }
+  }
+
+  public function refuseProposal($proposalId)
+  {
+    try {
+      $this->customScheduleService->refuseProposal($proposalId);
 
-    public function store(CustomScheduleRequest $request): JsonResponse
-    {
-        try {
-            $validated = $request->validated();
-            $customSchedules = $this->customScheduleService->create($validated);
-            
-            $count = count($customSchedules);
-            $message = $count > 1 
-                ? "{$count} oportunidades criadas com sucesso!"
-                : __("messages.created");
-
-            return $this->successResponse(
-                payload: CustomScheduleResource::collection($customSchedules),
-                message: $message,
-                code: 201,
-            );
-        } catch (\Exception $e) {
-            return $this->errorResponse($e->getMessage(), 422);
-        }
+      return $this->successResponse(null, 'Prestador recusado.');
+    } catch (\Exception $e) {
+      Log::error('Error refusing proposal: ' . $e->getMessage());
+      return $this->errorResponse($e->getMessage(), 400);
     }
+  }
 
-    public function show(string $id): JsonResponse
-    {
-        $customSchedule = $this->customScheduleService->getById($id);
-        return $this->successResponse(
-            new CustomScheduleResource($customSchedule),
-        );
+  public function getProvidersProposalsAndOpportunities($providerId)
+  {
+    try {
+      $data = $this->customScheduleService->getProvidersProposalsAndOpportunities($providerId);
+      return $this->successResponse($data);
+    } catch (\Exception $e) {
+      Log::error('Error fetching provider proposals and opportunities: ' . $e->getMessage());
+      return $this->errorResponse($e->getMessage(), 500);
     }
+  }
 
-    public function update(CustomScheduleRequest $request, string $id): JsonResponse
-    {
-        try {
-            $customSchedule = $this->customScheduleService->update($id, $request->validated());
-            return $this->successResponse(
-                payload: new CustomScheduleResource($customSchedule),
-                message: __("messages.updated"),
-            );
-        } catch (\Exception $e) {
-            return $this->errorResponse($e->getMessage(), 422);
-        }
+  public function verifyCode($scheduleId, CustomScheduleVerifyCodeRequest $request) 
+  {
+    try {
+      $result = $this->customScheduleService->verifyScheduleCode($scheduleId, $request->input('code'));
+
+      return $this->successResponse($result, 'Código verificado com sucesso!');
+    } catch (\Exception $e) {
+      Log::error('Error verifying schedule code: ' . $e->getMessage());
+      return $this->errorResponse($e->getMessage(), 500);
     }
+  }
+
+  public function refuseOpportunity($scheduleId, CustomScheduleRefuseOpportunityRequest $request)
+  {
+    try {
+      $this->customScheduleService->refuseOpportunity($scheduleId, $request->input('provider_id'));
 
-    public function destroy(string $id): JsonResponse
-    {
-        try {
-            $this->customScheduleService->delete($id);
-            return $this->successResponse(
-                message: __("messages.deleted"),
-            );
-        } catch (\Exception $e) {
-            return $this->errorResponse($e->getMessage(), 422);
-        }
+      return $this->successResponse(null, 'Oportunidade recusada.');
+    } catch (\Exception $e) {
+      Log::error('Error refusing opportunity: ' . $e->getMessage());
+      return $this->errorResponse($e->getMessage(), 400);
     }
+  }
 }

+ 88 - 88
app/Http/Controllers/ScheduleController.php

@@ -10,105 +10,105 @@ use Illuminate\Support\Facades\Request;
 
 class ScheduleController extends Controller
 {
-    protected $scheduleService;
+  protected $scheduleService;
 
-    public function __construct(ScheduleService $scheduleService)
-    {
-        $this->scheduleService = $scheduleService;
-    }
+  public function __construct(ScheduleService $scheduleService)
+  {
+    $this->scheduleService = $scheduleService;
+  }
 
-    public function index(): JsonResponse
-    {
-        $schedules = $this->scheduleService->getAll();
-        return $this->successResponse(
-            ScheduleResource::collection($schedules),
-        );
-    }
+  public function index(): JsonResponse
+  {
+    $schedules = $this->scheduleService->getAll();
+    return $this->successResponse(
+      ScheduleResource::collection($schedules),
+    );
+  }
 
-    public function store(ScheduleRequest $request): JsonResponse
-    {
-        try {
-            $validated = $request->validated();
-            
-            if (isset($validated['schedules']) && is_array($validated['schedules']) && count($validated['schedules']) > 0) {
-                $baseData = [
-                    'client_id' => $validated['client_id'],
-                    'provider_id' => $validated['provider_id'],
-                    'address_id' => $validated['address_id'],
-                    'schedule_type' => $validated['schedule_type'] ?? 'default',
-                    'status' => $validated['status'] ?? 'pending',
-                ];
-                
-                $schedules = $this->scheduleService->createMultiple($baseData, $validated['schedules']);
-                return $this->successResponse(
-                    payload: ScheduleResource::collection($schedules),
-                    message: count($schedules) . " " . __("schedules.schedules_created"),
-                    code: 201,
-                );
-            }
-            
-            $schedule = $this->scheduleService->create($validated);
-            return $this->successResponse(
-                payload: new ScheduleResource($schedule),
-                message: __("messages.created"),
-                code: 201,
-            );
-        } catch (\Exception $e) {
-            return $this->errorResponse($e->getMessage(), 422);
-        }
-    }
+  public function store(ScheduleRequest $request): JsonResponse
+  {
+    try {
+      $validated = $request->validated();
+
+      if (isset($validated['schedules']) && is_array($validated['schedules']) && count($validated['schedules']) > 0) {
+        $baseData = [
+          'client_id' => $validated['client_id'],
+          'provider_id' => $validated['provider_id'],
+          'address_id' => $validated['address_id'],
+          'schedule_type' => $validated['schedule_type'] ?? 'default',
+          'status' => $validated['status'] ?? 'pending',
+        ];
 
-    public function show(string $id): JsonResponse
-    {
-        $schedule = $this->scheduleService->getById($id);
+        $schedules = $this->scheduleService->createMultiple($baseData, $validated['schedules']);
         return $this->successResponse(
-            new ScheduleResource($schedule),
+          payload: ScheduleResource::collection($schedules),
+          message: count($schedules) . " " . __("schedules.schedules_created"),
+          code: 201,
         );
-    }
+      }
 
-    public function update(ScheduleRequest $request, string $id): JsonResponse
-    {
-        try {
-            $schedule = $this->scheduleService->update($id, $request->validated());
-            return $this->successResponse(
-                payload: new ScheduleResource($schedule),
-                message: __("messages.updated"),
-            );
-        } catch (\Exception $e) {
-            return $this->errorResponse($e->getMessage(), 422);
-        }
+      $schedule = $this->scheduleService->create($validated);
+      return $this->successResponse(
+        payload: new ScheduleResource($schedule),
+        message: __("messages.created"),
+        code: 201,
+      );
+    } catch (\Exception $e) {
+      return $this->errorResponse($e->getMessage(), 422);
     }
+  }
 
-    public function destroy(string $id): JsonResponse
-    {
-        $this->scheduleService->delete($id);
-        return $this->successResponse(
-            message: __("messages.deleted"),
-            code: 204,
-        );
-    }
+  public function show(string $id): JsonResponse
+  {
+    $schedule = $this->scheduleService->getById($id);
+    return $this->successResponse(
+      new ScheduleResource($schedule),
+    );
+  }
 
-    public function groupedByClient(): JsonResponse
-    {
-        $grouped = $this->scheduleService->getGroupedByClient();
-        return $this->successResponse($grouped);
+  public function update(ScheduleRequest $request, string $id): JsonResponse
+  {
+    try {
+      $schedule = $this->scheduleService->update($id, $request->validated());
+      return $this->successResponse(
+        payload: new ScheduleResource($schedule),
+        message: __("messages.updated"),
+      );
+    } catch (\Exception $e) {
+      return $this->errorResponse($e->getMessage(), 422);
     }
+  }
+
+  public function destroy(string $id): JsonResponse
+  {
+    $this->scheduleService->delete($id);
+    return $this->successResponse(
+      message: __("messages.deleted"),
+      code: 204,
+    );
+  }
+
+  public function groupedByClient(): JsonResponse
+  {
+    $grouped = $this->scheduleService->getSchedulesDefaultGroupedByClient();
+    return $this->successResponse($grouped);
+  }
+
+  public function updateStatus(string $id, ScheduleRequest $request): JsonResponse
+  {
+    try {
+      $validated = $request->validate([
+        'status' => 'required|in:pending,accepted,rejected,paid,cancelled,started,finished'
+      ]);
+
+      $schedule = $this->scheduleService->updateStatus($id, $validated['status']);
 
-    public function updateStatus(string $id, ScheduleRequest $request): JsonResponse
-    {
-        try {
-            $validated = $request->validate([
-                'status' => 'required|in:pending,accepted,rejected,paid,cancelled,started,finished'
-            ]);
-
-            $schedule = $this->scheduleService->updateStatus($id, $validated['status']);
-            
-            return $this->successResponse(
-                payload: new ScheduleResource($schedule),
-                message: __("messages.updated"),
-            );
-        } catch (\Exception $e) {
-            return $this->errorResponse($e->getMessage(), 422);
-        }
+      return $this->successResponse(
+        payload: new ScheduleResource($schedule),
+        message: __("messages.updated"),
+      );
+    } catch (\Exception $e) {
+      return $this->errorResponse($e->getMessage(), 422);
     }
+  }
 }

+ 38 - 0
app/Http/Requests/CustomScheduleProposeRequest.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class CustomScheduleProposeRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     */
+    public function authorize(): bool
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
+     */
+    public function rules(): array
+    {
+        $rules = [
+            'provider_id' => 'required|exists:providers,id',
+        ];
+
+        return $rules;
+    }
+
+    /**
+     * Get custom messages for validator errors.
+     */
+    public function messages(): array
+    {
+        return [];
+    }
+}

+ 38 - 0
app/Http/Requests/CustomScheduleRefuseOpportunityRequest.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class CustomScheduleRefuseOpportunityRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     */
+    public function authorize(): bool
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
+     */
+    public function rules(): array
+    {
+        $rules = [
+            'provider_id' => 'required|exists:providers,id',
+        ];
+
+        return $rules;
+    }
+
+    /**
+     * Get custom messages for validator errors.
+     */
+    public function messages(): array
+    {
+        return [];
+    }
+}

+ 38 - 0
app/Http/Requests/CustomScheduleVerifyCodeRequest.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class CustomScheduleVerifyCodeRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     */
+    public function authorize(): bool
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
+     */
+    public function rules(): array
+    {
+        $rules = [
+            'code' => 'required|string|max:4',
+        ];
+
+        return $rules;
+    }
+
+    /**
+     * Get custom messages for validator errors.
+     */
+    public function messages(): array
+    {
+        return [];
+    }
+}

+ 2 - 2
app/Http/Resources/CustomScheduleResource.php

@@ -55,8 +55,8 @@ class CustomScheduleResource extends JsonResource
             'specialities' => $this->whenLoaded('specialities', function () {
                 return $this->specialities->map(function ($item) {
                     return [
-                        'id' => $item->speciality->id,
-                        'name' => $item->speciality->name,
+                        'id' => $item->id,
+                        'name' => $item->description,
                     ];
                 });
             }),

+ 2 - 1
app/Models/CustomSchedule.php

@@ -40,6 +40,7 @@ class CustomSchedule extends Model
 
     public function specialities(): HasMany
     {
-        return $this->hasMany(CustomScheduleSpeciality::class);
+        return $this->hasMany(CustomScheduleSpeciality::class, 'custom_schedule_id', 'id')->leftJoin('specialities', 'custom_schedules_specialities.speciality_id', '=', 'specialities.id')
+            ->select('specialities.id', 'specialities.description', 'custom_schedules_specialities.custom_schedule_id');
     }
 }

+ 15 - 0
app/Models/Schedule.php

@@ -45,4 +45,19 @@ class Schedule extends Model
     {
         return $this->belongsTo(Address::class);
     }
+
+    public function customSchedule()
+    {
+        return $this->hasOne(CustomSchedule::class);
+    }
+
+    public function proposals()
+    {
+        return $this->hasMany(ScheduleProposal::class);
+    }
+
+    public function refuses()
+    {
+        return $this->hasMany(ScheduleRefuse::class);
+    }
 }

+ 33 - 0
app/Models/ScheduleProposal.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+
+class ScheduleProposal extends Model
+{
+    use SoftDeletes;
+
+    protected $fillable = [
+        'schedule_id',
+        'provider_id',
+    ];
+
+    protected $casts = [
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+        'deleted_at' => 'datetime',
+    ];
+
+    public function schedule(): BelongsTo
+    {
+        return $this->belongsTo(Schedule::class);
+    }
+
+    public function provider(): BelongsTo
+    {
+        return $this->belongsTo(Provider::class);
+    }
+}

+ 33 - 0
app/Models/ScheduleRefuse.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+
+class ScheduleRefuse extends Model
+{
+    use SoftDeletes;
+
+    protected $fillable = [
+        'schedule_id',
+        'provider_id',
+    ];
+
+    protected $casts = [
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+        'deleted_at' => 'datetime',
+    ];
+
+    public function schedule(): BelongsTo
+    {
+        return $this->belongsTo(Schedule::class);
+    }
+
+    public function provider(): BelongsTo
+    {
+        return $this->belongsTo(Provider::class);
+    }
+}

+ 46 - 0
app/Rules/ScheduleBusinessRules.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Rules;
+
+use App\Models\Schedule;
+use Carbon\Carbon;
+
+class ScheduleBusinessRules
+{
+    // Status que devem ser ignorados na validação de limite por semana
+    private const EXCLUDED_STATUSES = ['cancelled', 'rejected'];
+
+    /**
+     * Valida se o prestador pode ter mais um agendamento com o cliente na semana
+     * Limite: 2 agendamentos por semana (domingo a sábado)
+     * 
+     * @param int $clientId
+     * @param int $providerId
+     * @param string $date (Y-m-d)
+     * @param int|null $excludeScheduleId
+     * @return bool
+     * @throws \Exception
+     */
+    public static function validateWeeklyScheduleLimit($clientId, $providerId, $date, $excludeScheduleId = null)
+    {
+        $date = Carbon::parse($date);
+        
+        $weekStart = $date->copy()->startOfWeek(Carbon::SUNDAY);
+        $weekEnd = $date->copy()->endOfWeek(Carbon::SATURDAY);
+
+        $weeklySchedulesCount = Schedule::where('client_id', $clientId)
+            ->where('provider_id', $providerId)
+            ->whereNotIn('status', self::EXCLUDED_STATUSES)
+            ->whereBetween('date', [$weekStart->format('Y-m-d'), $weekEnd->format('Y-m-d')])
+            ->when($excludeScheduleId, function ($query) use ($excludeScheduleId) {
+                $query->where('id', '!=', $excludeScheduleId);
+            })
+            ->count();
+
+        if ($weeklySchedulesCount >= 2) {
+            throw new \Exception(__('validation.custom.schedule.weekly_limit_exceeded'));
+        }
+
+        return true;
+    }
+}

+ 510 - 174
app/Services/CustomScheduleService.php

@@ -5,196 +5,532 @@ namespace App\Services;
 use App\Models\CustomSchedule;
 use App\Models\Schedule;
 use App\Models\CustomScheduleSpeciality;
+use App\Models\Provider;
+use App\Models\ProviderBlockedDay;
+use App\Models\ProviderWorkingDay;
+use App\Models\ScheduleProposal;
+use App\Models\ScheduleRefuse;
+use App\Rules\ScheduleBusinessRules;
 use Carbon\Carbon;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 
 class CustomScheduleService
 {
-    public function getAll()
-    {
-        return CustomSchedule::with([
-            'schedule.client.user',
-            'schedule.address',
-            'serviceType',
-            'specialities.speciality'
-        ])
-            ->orderBy('id', 'desc')
-            ->get();
-    }
+  public function getAll()
+  {
+    $custom_schedules = CustomSchedule::with([
+      'schedule.client.user',
+      'schedule.address',
+      'serviceType',
+      'specialities.speciality'
+    ])
+      ->orderBy('id', 'desc')
+      ->get();
 
-    public function getById($id)
-    {
-        return CustomSchedule::with([
-            'schedule.client.user',
-            'schedule.address',
-            'serviceType',
-            'specialities.speciality'
-        ])->findOrFail($id);
-    }
+    return $custom_schedules;
+  }
 
-    public function create(array $data)
-    {
-        DB::beginTransaction();
-        try {
-            $quantity = $data['quantity'] ?? 1;
-            $specialityIds = $data['speciality_ids'] ?? [];
-            
-            $createdCustomSchedules = [];
-
-            for ($i = 0; $i < $quantity; $i++) {
-                $scheduleData = [
-                    'client_id' => $data['client_id'],
-                    'provider_id' => null,
-                    'address_id' => $data['address_id'],
-                    'date' => $data['date'],
-                    'period_type' => $data['period_type'],
-                    'schedule_type' => 'custom',
-                    'start_time' => $data['start_time'],
-                    'end_time' => $data['end_time'],
-                    'status' => 'pending',
-                    'total_amount' => 0,
-                    'code' => str_pad(random_int(0, 9999), 4, '0', STR_PAD_LEFT),
-                    'code_verified' => false,
-                ];
+  public function getById($id)
+  {
+    return CustomSchedule::with([
+      'schedule.client.user',
+      'schedule.address',
+      'serviceType',
+      'specialities.speciality'
+    ])->findOrFail($id);
+  }
 
-                $schedule = Schedule::create($scheduleData);
+  public function create(array $data)
+  {
+    DB::beginTransaction();
+    try {
+      $quantity = $data['quantity'] ?? 1;
+      $specialityIds = $data['speciality_ids'] ?? [];
 
-                $customScheduleData = [
-                    'schedule_id' => $schedule->id,
-                    'address_type' => $data['address_type'],
-                    'service_type_id' => $data['service_type_id'],
-                    'description' => $data['description'] ?? null,
-                    'min_price' => $data['min_price'],
-                    'max_price' => $data['max_price'],
-                    'offers_meal' => $data['offers_meal'] ?? false,
-                ];
+      $createdCustomSchedules = [];
 
-                $customSchedule = CustomSchedule::create($customScheduleData);
-
-                if (!empty($specialityIds)) {
-                    foreach ($specialityIds as $specialityId) {
-                        CustomScheduleSpeciality::create([
-                            'custom_schedule_id' => $customSchedule->id,
-                            'speciality_id' => $specialityId,
-                        ]);
-                    }
-                }
-
-                $createdCustomSchedules[] = $customSchedule->load([
-                    'schedule.client.user',
-                    'schedule.address',
-                    'serviceType',
-                    'specialities.speciality'
-                ]);
-            }
-
-            DB::commit();
-            return $createdCustomSchedules;
-        } catch (\Exception $e) {
-            DB::rollBack();
-            Log::error('Error creating custom schedule: ' . $e->getMessage());
-            throw $e;
-        }
-    }
+      for ($i = 0; $i < $quantity; $i++) {
+        $scheduleData = [
+          'client_id' => $data['client_id'],
+          'provider_id' => null,
+          'address_id' => $data['address_id'],
+          'date' => $data['date'],
+          'period_type' => $data['period_type'],
+          'schedule_type' => 'custom',
+          'start_time' => $data['start_time'],
+          'end_time' => $data['end_time'],
+          'status' => 'pending',
+          'total_amount' => 0,
+          'code' => str_pad(random_int(0, 9999), 4, '0', STR_PAD_LEFT),
+          'code_verified' => false,
+        ];
 
-    public function update($id, array $data)
-    {
-        DB::beginTransaction();
-        try {
-            $customSchedule = CustomSchedule::findOrFail($id);
-            $schedule = $customSchedule->schedule;
-
-            $scheduleUpdateData = [];
-            if (isset($data['address_id'])) {
-                $scheduleUpdateData['address_id'] = $data['address_id'];
-            }
-            if (isset($data['date'])) {
-                $scheduleUpdateData['date'] = $data['date'];
-            }
-            if (isset($data['period_type'])) {
-                $scheduleUpdateData['period_type'] = $data['period_type'];
-            }
-            if (isset($data['start_time'])) {
-                $scheduleUpdateData['start_time'] = $data['start_time'];
-            }
-            if (isset($data['end_time'])) {
-                $scheduleUpdateData['end_time'] = $data['end_time'];
-            }
-
-            if (!empty($scheduleUpdateData)) {
-                $schedule->update($scheduleUpdateData);
-            }
-
-            $customScheduleUpdateData = [];
-            if (isset($data['address_type'])) {
-                $customScheduleUpdateData['address_type'] = $data['address_type'];
-            }
-            if (isset($data['service_type_id'])) {
-                $customScheduleUpdateData['service_type_id'] = $data['service_type_id'];
-            }
-            if (isset($data['description'])) {
-                $customScheduleUpdateData['description'] = $data['description'];
-            }
-            if (isset($data['min_price'])) {
-                $customScheduleUpdateData['min_price'] = $data['min_price'];
-            }
-            if (isset($data['max_price'])) {
-                $customScheduleUpdateData['max_price'] = $data['max_price'];
-            }
-            if (isset($data['offers_meal'])) {
-                $customScheduleUpdateData['offers_meal'] = $data['offers_meal'];
-            }
-
-            if (!empty($customScheduleUpdateData)) {
-                $customSchedule->update($customScheduleUpdateData);
-            }
-
-            if (isset($data['speciality_ids'])) {
-                $custom_schedule = CustomScheduleSpeciality::where('custom_schedule_id', $customSchedule->id);
-                $custom_schedule->delete();
-                
-                foreach ($data['speciality_ids'] as $specialityId) {
-                    CustomScheduleSpeciality::create([
-                        'custom_schedule_id' => $customSchedule->id,
-                        'speciality_id' => $specialityId,
-                    ]);
-                }
-            }
-
-            DB::commit();
-            return $customSchedule->fresh([
-                'schedule.client.user',
-                'schedule.address',
-                'serviceType',
-                'specialities.speciality'
+        $schedule = Schedule::create($scheduleData);
+
+        $customScheduleData = [
+          'schedule_id' => $schedule->id,
+          'address_type' => $data['address_type'],
+          'service_type_id' => $data['service_type_id'],
+          'description' => $data['description'] ?? null,
+          'min_price' => $data['min_price'],
+          'max_price' => $data['max_price'],
+          'offers_meal' => $data['offers_meal'] ?? false,
+        ];
+
+        $customSchedule = CustomSchedule::create($customScheduleData);
+
+        if (!empty($specialityIds)) {
+          foreach ($specialityIds as $specialityId) {
+            CustomScheduleSpeciality::create([
+              'custom_schedule_id' => $customSchedule->id,
+              'speciality_id' => $specialityId,
             ]);
-        } catch (\Exception $e) {
-            DB::rollBack();
-            Log::error('Error updating custom schedule: ' . $e->getMessage());
-            throw $e;
+          }
         }
+
+        $createdCustomSchedules[] = $customSchedule->load([
+          'schedule.client.user',
+          'schedule.address',
+          'serviceType',
+          'specialities.speciality'
+        ]);
+      }
+
+      DB::commit();
+      return $createdCustomSchedules;
+    } catch (\Exception $e) {
+      DB::rollBack();
+      Log::error('Error creating custom schedule: ' . $e->getMessage());
+      throw $e;
     }
+  }
 
-    public function delete($id)
-    {
-        DB::beginTransaction();
-        try {
-            $customSchedule = CustomSchedule::findOrFail($id);
-            $schedule = $customSchedule->schedule;
-            
-            CustomScheduleSpeciality::where('custom_schedule_id', $customSchedule->id)->delete();
-            
-            $customSchedule->delete();
-            
-            $schedule->delete();
-
-            DB::commit();
-            return $customSchedule;
-        } catch (\Exception $e) {
-            DB::rollBack();
-            Log::error('Error deleting custom schedule: ' . $e->getMessage());
-            throw $e;
+  public function update($id, array $data)
+  {
+    DB::beginTransaction();
+    try {
+      $customSchedule = CustomSchedule::findOrFail($id);
+      $schedule = $customSchedule->schedule;
+
+      $scheduleUpdateData = [];
+      if (isset($data['address_id'])) {
+        $scheduleUpdateData['address_id'] = $data['address_id'];
+      }
+      if (isset($data['date'])) {
+        $scheduleUpdateData['date'] = $data['date'];
+      }
+      if (isset($data['period_type'])) {
+        $scheduleUpdateData['period_type'] = $data['period_type'];
+      }
+      if (isset($data['start_time'])) {
+        $scheduleUpdateData['start_time'] = $data['start_time'];
+      }
+      if (isset($data['end_time'])) {
+        $scheduleUpdateData['end_time'] = $data['end_time'];
+      }
+
+      if (!empty($scheduleUpdateData)) {
+        $schedule->update($scheduleUpdateData);
+      }
+
+      $customScheduleUpdateData = [];
+      if (isset($data['address_type'])) {
+        $customScheduleUpdateData['address_type'] = $data['address_type'];
+      }
+      if (isset($data['service_type_id'])) {
+        $customScheduleUpdateData['service_type_id'] = $data['service_type_id'];
+      }
+      if (isset($data['description'])) {
+        $customScheduleUpdateData['description'] = $data['description'];
+      }
+      if (isset($data['min_price'])) {
+        $customScheduleUpdateData['min_price'] = $data['min_price'];
+      }
+      if (isset($data['max_price'])) {
+        $customScheduleUpdateData['max_price'] = $data['max_price'];
+      }
+      if (isset($data['offers_meal'])) {
+        $customScheduleUpdateData['offers_meal'] = $data['offers_meal'];
+      }
+
+      if (!empty($customScheduleUpdateData)) {
+        $customSchedule->update($customScheduleUpdateData);
+      }
+
+      if (isset($data['speciality_ids'])) {
+        $custom_schedule = CustomScheduleSpeciality::where('custom_schedule_id', $customSchedule->id);
+        $custom_schedule->delete();
+
+        foreach ($data['speciality_ids'] as $specialityId) {
+          CustomScheduleSpeciality::create([
+            'custom_schedule_id' => $customSchedule->id,
+            'speciality_id' => $specialityId,
+          ]);
         }
+      }
+
+      DB::commit();
+      return $customSchedule->fresh([
+        'schedule.client.user',
+        'schedule.address',
+        'serviceType',
+        'specialities.speciality'
+      ]);
+    } catch (\Exception $e) {
+      DB::rollBack();
+      Log::error('Error updating custom schedule: ' . $e->getMessage());
+      throw $e;
     }
+  }
+
+  public function delete($id)
+  {
+    DB::beginTransaction();
+    try {
+      $customSchedule = CustomSchedule::findOrFail($id);
+      $schedule = $customSchedule->schedule;
+
+      CustomScheduleSpeciality::where('custom_schedule_id', $customSchedule->id)->delete();
+
+      $customSchedule->delete();
+
+      $schedule->delete();
+
+      DB::commit();
+      return $customSchedule;
+    } catch (\Exception $e) {
+      DB::rollBack();
+      Log::error('Error deleting custom schedule: ' . $e->getMessage());
+      throw $e;
+    }
+  }
+
+  public function getSchedulesCustomGroupedByClient()
+  {
+    $schedules = Schedule::with(['client.user', 'provider.user', 'address', 'customSchedule.serviceType', 'customSchedule.specialities'])
+      ->orderBy('id', 'desc')
+      ->where('schedule_type', 'custom')
+      ->get();
+
+    $grouped = $this->formatCustomSchedules($schedules);
+   
+    return $grouped;
+  }
+
+  public function getAvailableOpportunities($providerId)
+  {
+    $opportunities = Schedule::with([
+        'client.user',
+        'address',
+        'customSchedule.serviceType',
+        'customSchedule.specialities'
+      ])
+      ->leftJoin('schedule_refuses', function ($join) use ($providerId) {
+        $join->on('schedules.id', '=', 'schedule_refuses.schedule_id')
+          ->where('schedule_refuses.provider_id', $providerId);
+      })
+      ->whereNull('schedule_refuses.id')
+      ->where('schedules.schedule_type', 'custom')
+      ->where('schedules.status', 'pending')
+      ->whereNull('schedules.provider_id')
+      ->select('schedules.*')
+      ->get();
+
+    $availableOpportunities = $opportunities->filter(function ($opportunity) use ($providerId) {
+      try {
+        return $this->checkProviderAvailability($providerId, $opportunity);
+      } catch (\Exception $e) {
+        return false;
+      }
+    });
+
+    return $availableOpportunities->values();
+  }
+
+  public function getProviderProposals($providerId)
+  {
+    return ScheduleProposal::with([
+      'schedule.client.user',
+      'schedule.address',
+      'schedule.address.city',
+      'schedule.address.state',
+      'schedule.customSchedule.serviceType',
+      'schedule.customSchedule.specialities',
+      'schedule.provider.user'
+    ])
+      ->where('provider_id', $providerId)
+      ->orderBy('created_at', 'desc')
+      ->get();
+  }
+
+  public function getOpportunityProposals($scheduleId)
+  {
+    return ScheduleProposal::with(['provider.user'])
+      ->where('schedule_id', $scheduleId)
+      ->orderBy('created_at', 'desc')
+      ->get();
+  }
+
+  public function proposeOpportunity($scheduleId, $providerId)
+  {
+    $schedule = Schedule::findOrFail($scheduleId);
+
+    if ($schedule->provider_id) {
+      throw new \Exception(__('validation.custom.opportunity.already_assigned'));
+    }
+
+    $existingProposal = ScheduleProposal::where('schedule_id', $scheduleId)
+      ->where('provider_id', $providerId)
+      ->first();
+
+    if ($existingProposal) {
+      throw new \Exception(__('validation.custom.opportunity.proposal_already_sent'));
+    }
+
+    $wasRefused = ScheduleRefuse::where('schedule_id', $scheduleId)
+      ->where('provider_id', $providerId)
+      ->exists();
+
+    if ($wasRefused) {
+      throw new \Exception(__('validation.custom.opportunity.provider_refused'));
+    }
+
+    $this->checkProviderAvailability($providerId, $schedule);
+
+    return ScheduleProposal::create([
+      'schedule_id' => $scheduleId,
+      'provider_id' => $providerId,
+    ]);
+  }
+
+  public function acceptProposal($proposalId)
+  {
+    return DB::transaction(function () use ($proposalId) {
+      $proposal = ScheduleProposal::findOrFail($proposalId);
+      $schedule = $proposal->schedule;
+
+      if ($schedule->provider_id) {
+        throw new \Exception(__('validation.custom.opportunity.already_assigned'));
+      }
+
+      $provider = Provider::find($proposal->provider_id);
+      switch ($schedule->period_type) {
+        case '8':
+          $baseAmount = $provider->daily_price_8h;
+          break;
+        case '6':
+          $baseAmount = $provider->daily_price_6h;
+          break;
+        case '4':
+          $baseAmount = $provider->daily_price_4h;
+          break;
+        case '2':
+          $baseAmount = $provider->daily_price_2h;
+          break;
+        default:
+      }
+      $schedule->total_amount = $baseAmount;
+      $schedule->save();
+
+      $schedule->update([
+        'provider_id' => $proposal->provider_id,
+        'status' => 'accepted',
+      ]);
+
+      ScheduleProposal::where('schedule_id', $schedule->id)
+        ->where('id', '!=', $proposalId)
+        ->delete();
+
+      return $schedule->fresh(['provider.user']);
+    });
+  }
+
+  public function refuseProposal($proposalId)
+  {
+    return DB::transaction(function () use ($proposalId) {
+      $proposal = ScheduleProposal::findOrFail($proposalId);
+
+      ScheduleRefuse::create([
+        'schedule_id' => $proposal->schedule_id,
+        'provider_id' => $proposal->provider_id,
+      ]);
+
+      $proposal->delete();
+
+      return true;
+    });
+  }
+
+  private function checkProviderAvailability($providerId, $schedule)
+  {
+    $date = Carbon::parse($schedule->date);
+    $startTime = $schedule->start_time;
+    $endTime = $schedule->end_time;
+    $dayOfWeek = $date->dayOfWeek;
+
+    ScheduleBusinessRules::validateWeeklyScheduleLimit(
+      $schedule->client_id,
+      $providerId,
+      $schedule->date
+    );
+
+    $startHour = (int) substr($startTime, 0, 2);
+    $endHour = (int) substr($endTime, 0, 2);
+
+    $periods = [];
+    if ($startHour < 13) {
+      $periods[] = 'morning';
+    }
+    if ($endHour >= 13 || ($startHour < 13 && $endHour > 12)) {
+      $periods[] = 'afternoon';
+    }
+
+    foreach ($periods as $period) {
+      $workingDay = ProviderWorkingDay::where('provider_id', $providerId)
+        ->where('day', $dayOfWeek)
+        ->where('period', $period)
+        ->first();
+
+      if (!$workingDay) {
+        throw new \Exception(__('validation.custom.opportunity.provider_not_working'));
+      }
+    }
+
+    $blockedDay = ProviderBlockedDay::where('provider_id', $providerId)
+      ->where('date', $date->format('Y-m-d'))
+      ->where(function ($query) use ($startTime, $endTime) {
+        $query->where('period', 'full')
+          ->orWhere(function ($q) use ($startTime, $endTime) {
+            $q->where('period', 'partial')
+              ->where(function ($q2) use ($startTime, $endTime) {
+                $q2->whereBetween('init_hour', [$startTime, $endTime])
+                  ->orWhereBetween('end_hour', [$startTime, $endTime])
+                  ->orWhere(function ($q3) use ($startTime, $endTime) {
+                    $q3->where('init_hour', '<=', $startTime)
+                      ->where('end_hour', '>=', $endTime);
+                  });
+              });
+          });
+      })
+      ->first();
+
+    if ($blockedDay) {
+      throw new \Exception(__('validation.custom.opportunity.provider_blocked'));
+    }
+    $excluded_status = ['cancelled', 'rejected'];
+    $conflictingSchedule = Schedule::where('provider_id', $providerId)
+      ->where('date', $date->format('Y-m-d'))
+      ->whereNotIn('status', $excluded_status)
+      ->where(function ($query) use ($startTime, $endTime) {
+        $query->whereBetween('start_time', [$startTime, $endTime])
+          ->orWhereBetween('end_time', [$startTime, $endTime])
+          ->orWhere(function ($q) use ($startTime, $endTime) {
+            $q->where('start_time', '<=', $startTime)
+              ->where('end_time', '>=', $endTime);
+          });
+      })
+      ->first();
+
+    if ($conflictingSchedule) {
+      throw new \Exception(__('validation.custom.opportunity.schedule_conflict'));
+    }
+
+    return true;
+  }
+
+  public function getProvidersProposalsAndOpportunities($providerId)
+  {
+    $proposals = $this->getProviderProposals($providerId);
+    $opportunities = $this->formatCustomSchedules($this->getAvailableOpportunities($providerId));
+
+    return [
+      'proposals' => $proposals,
+      'opportunities' => $opportunities,
+    ];
+  }
+
+  public function formatCustomSchedules($schedules)
+  {
+    $grouped = $schedules->groupBy('client_id')->map(function ($clientSchedules) {
+      $firstSchedule = $clientSchedules->first();
+      return [
+        'client_id' => $firstSchedule->client_id,
+        'client_name' => $firstSchedule->client->user->name ?? 'N/A',
+        'schedules' => $clientSchedules->map(function ($schedule) {
+          $customSchedule = $schedule->customSchedule;
+          return [
+            'id' => $schedule->id,
+            'date' => $schedule->date ? Carbon::parse($schedule->date)->format('d/m/Y') : null,
+            'start_time' => $schedule->start_time,
+            'end_time' => $schedule->end_time,
+            'period_type' => $schedule->period_type,
+            'status' => $schedule->status,
+            'total_amount' => $schedule->total_amount,
+            'code' => $schedule->code,
+            'code_verified' => $schedule->code_verified,
+            'provider_id' => $schedule->provider_id,
+            'provider_name' => $schedule->provider?->user->name ?? 'N/A',
+            'address' => $schedule->address ? [
+              'id' => $schedule->address->id,
+              'address' => $schedule->address->address,
+              'complement' => $schedule->address->complement,
+              'zip_code' => $schedule->address->zip_code,
+              'city' => $schedule->address->city->name ?? '',
+              'state' => $schedule->address->city->state->name ?? '',
+            ] : null,
+            'client_name' => $schedule->client->user->name ?? 'N/A',
+            'custom_schedule' => $customSchedule ? [
+              'id' => $customSchedule->id,
+              'address_type' => $customSchedule->address_type,
+              'service_type_id' => $customSchedule->service_type_id,
+              'service_type_name' => $customSchedule->serviceType?->description ?? 'N/A',
+              'description' => $customSchedule->description,
+              'min_price' => $customSchedule->min_price,
+              'max_price' => $customSchedule->max_price,
+              'offers_meal' => $customSchedule->offers_meal,
+              'specialities' => $customSchedule->specialities->map(function ($speciality) {
+                return [
+                  'id' => $speciality->id,
+                  'description' => $speciality->description,
+                ];
+              })->values()
+            ] : null,
+          ];
+        })->values()
+      ];
+    })->sortBy('id')->values();
+
+    return $grouped;
+  }
+
+  public function verifyScheduleCode($scheduleId, $code)
+  {
+    $schedule = Schedule::findOrFail($scheduleId);
+
+    if ($schedule->code_verified) {
+      throw new \Exception(__('validation.custom.opportunity.code_already_verified'));
+    }
+
+    if ($schedule->code !== $code) {
+      throw new \Exception(__('validation.custom.opportunity.invalid_code'));
+    }
+
+    $schedule->update([
+      'code_verified' => true,
+      'status' => 'started',
+    ]);
+
+    return $schedule;
+  }
+
+  public function refuseOpportunity($scheduleId, $providerId)
+  {
+    $schedule_refuse = ScheduleRefuse::create([
+      'schedule_id' => $scheduleId,
+      'provider_id' => $providerId,
+    ]);
+
+    return $schedule_refuse;
+  }
 }

+ 222 - 206
app/Services/ScheduleService.php

@@ -6,238 +6,254 @@ use App\Models\Schedule;
 use App\Models\Provider;
 use App\Models\ProviderBlockedDay;
 use App\Models\ProviderWorkingDay;
+use App\Rules\ScheduleBusinessRules;
 use Carbon\Carbon;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 
 class ScheduleService
 {
-    public function getAll()
-    {
-        return Schedule::with(['client.user', 'provider.user', 'address'])
-            ->where('schedule_type', 'default')
-            ->orderBy('date', 'desc')
-            ->orderBy('start_time', 'desc')
-            ->get();
+  public function getAll()
+  {
+    return Schedule::with(['client.user', 'provider.user', 'address'])
+      ->where('schedule_type', 'default')
+      ->orderBy('date', 'desc')
+      ->orderBy('start_time', 'desc')
+      ->get();
+  }
+
+  public function getById($id)
+  {
+    return Schedule::with(['client.user', 'provider.user', 'address'])->findOrFail($id);
+  }
+
+  public function create(array $data)
+  {
+    $data['code'] = str_pad(random_int(0, 9999), 4, '0', STR_PAD_LEFT);
+
+    $provider = Provider::findOrFail($data['provider_id']);
+    $data['total_amount'] = $this->calculateAmount($provider, $data['period_type']);
+
+    $this->validateProviderAvailability($data);
+    $this->validateWeeklyScheduleLimit($data);
+
+    return Schedule::create($data);
+  }
+
+  public function createMultiple(array $baseData, array $schedules)
+  {
+    foreach ($schedules as $schedule) {
+      $validationData = array_merge($baseData, $schedule);
+      try {
+        $this->validateProviderAvailability($validationData);
+        $this->validateWeeklyScheduleLimit($validationData);
+      } catch (\Exception $e) {
+        throw new \Exception("Prestador não disponível para a data " . Carbon::parse($schedule['date'])->format('d/m/Y') . ": " . $e->getMessage());
+      }
     }
 
-    public function getById($id)
-    {
-        return Schedule::with(['client.user', 'provider.user', 'address'])->findOrFail($id);
+    $createdSchedules = [];
+
+    foreach ($schedules as $schedule) {
+      $scheduleData = array_merge($baseData, $schedule, [
+        'code' => str_pad(random_int(0, 9999), 4, '0', STR_PAD_LEFT),
+      ]);
+
+      $createdSchedules[] = Schedule::create($scheduleData);
     }
 
-    public function create(array $data)
-    {
-        $data['code'] = str_pad(random_int(0, 9999), 4, '0', STR_PAD_LEFT);
-        
-        $provider = Provider::findOrFail($data['provider_id']);
-        $data['total_amount'] = $this->calculateAmount($provider, $data['period_type']);
+    return $createdSchedules;
+  }
 
-        $this->validateProviderAvailability($data);
+  public function update($id, array $data)
+  {
+    $schedule = Schedule::findOrFail($id);
 
-        return Schedule::create($data);
+    if (isset($data['provider_id']) || isset($data['period_type'])) {
+      $providerId = $data['provider_id'] ?? $schedule->provider_id;
+      $periodType = $data['period_type'] ?? $schedule->period_type;
+      $provider = Provider::findOrFail($providerId);
+      $data['total_amount'] = $this->calculateAmount($provider, $periodType);
     }
 
-    public function createMultiple(array $baseData, array $schedules)
-    {
-        foreach ($schedules as $schedule) {
-            $validationData = array_merge($baseData, $schedule);
-            try {
-                $this->validateProviderAvailability($validationData);
-            } catch (\Exception $e) {
-                throw new \Exception("Prestador não disponível para a data " . Carbon::parse($schedule['date'])->format('d/m/Y') . ": " . $e->getMessage());
-            }
-        }
-
-        $createdSchedules = [];
-        
-        foreach ($schedules as $schedule) {
-            $scheduleData = array_merge($baseData, $schedule, [
-                'code' => str_pad(random_int(0, 9999), 4, '0', STR_PAD_LEFT),
-            ]);
-            
-            $createdSchedules[] = Schedule::create($scheduleData);
-        }
-
-        return $createdSchedules;
+    if (isset($data['date']) || isset($data['start_time']) || isset($data['provider_id'])) {
+      $validationData = array_merge($schedule->toArray(), $data);
+      $this->validateProviderAvailability($validationData, $id);
     }
 
-    public function update($id, array $data)
-    {
-        $schedule = Schedule::findOrFail($id);
-
-        if (isset($data['provider_id']) || isset($data['period_type'])) {
-            $providerId = $data['provider_id'] ?? $schedule->provider_id;
-            $periodType = $data['period_type'] ?? $schedule->period_type;
-            $provider = Provider::findOrFail($providerId);
-            $data['total_amount'] = $this->calculateAmount($provider, $periodType);
-        }
-
-        if (isset($data['date']) || isset($data['start_time']) || isset($data['provider_id'])) {
-            $validationData = array_merge($schedule->toArray(), $data);
-            $this->validateProviderAvailability($validationData, $id);
-        }
+    $schedule->update($data);
+    return $schedule->fresh(['client.user', 'provider.user', 'address']);
+  }
+
+  public function delete($id)
+  {
+    $schedule = Schedule::findOrFail($id);
+    $schedule->delete();
+    return $schedule;
+  }
+
+  private function calculateAmount(Provider $provider, string $periodType): float
+  {
+    $hourlyRates = [
+      '2' => $provider->daily_price_2h ?? 0,
+      '4' => $provider->daily_price_4h ?? 0,
+      '6' => $provider->daily_price_6h ?? 0,
+      '8' => $provider->daily_price_8h ?? 0,
+    ];
+
+    return $hourlyRates[$periodType] ?? 0;
+  }
+
+  private function validateProviderAvailability(array $data, $excludeScheduleId = null)
+  {
+    $provider = Provider::findOrFail($data['provider_id']);
+    $date = Carbon::parse($data['date']);
+    $startTime = $data['start_time'];
+    $endTime = $data['end_time'];
+
+    $dayOfWeek = $date->dayOfWeek;
+
+    $startHour = (int) substr($startTime, 0, 2);
+    $period = $startHour < 12 ? 'morning' : 'afternoon';
+
+    $workingDay = ProviderWorkingDay::where('provider_id', $data['provider_id'])
+      ->where('day', $dayOfWeek)
+      ->where('period', $period)
+      ->first();
+
+    if (!$workingDay) {
+      throw new \Exception("Prestador não trabalha neste dia/período.");
+    }
 
-        $schedule->update($data);
-        return $schedule->fresh(['client.user', 'provider.user', 'address']);
+    $blockedDay = ProviderBlockedDay::where('provider_id', $data['provider_id'])
+      ->where('date', $date->format('Y-m-d'))
+      ->where(function ($query) use ($startTime, $endTime) {
+        $query->where('period', 'full')
+          ->orWhere(function ($q) use ($startTime, $endTime) {
+            $q->where('period', 'partial')
+              ->where(function ($q2) use ($startTime, $endTime) {
+                $q2->whereBetween('init_hour', [$startTime, $endTime])
+                  ->orWhereBetween('end_hour', [$startTime, $endTime])
+                  ->orWhere(function ($q3) use ($startTime, $endTime) {
+                    $q3->where('init_hour', '<=', $startTime)
+                      ->where('end_hour', '>=', $endTime);
+                  });
+              });
+          });
+      })
+      ->first();
+
+    if ($blockedDay) {
+      throw new \Exception("Prestador possui bloqueio neste dia/horário.");
     }
 
-    public function delete($id)
-    {
-        $schedule = Schedule::findOrFail($id);
-        $schedule->delete();
-        return $schedule;
+    $conflictingSchedule = Schedule::where('provider_id', $data['provider_id'])
+      ->where('date', $date->format('Y-m-d'))
+      ->whereIn('status', ['pending', 'accepted', 'paid', 'started'])
+      ->where(function ($query) use ($startTime, $endTime) {
+        $query->whereBetween('start_time', [$startTime, $endTime])
+          ->orWhereBetween('end_time', [$startTime, $endTime])
+          ->orWhere(function ($q) use ($startTime, $endTime) {
+            $q->where('start_time', '<=', $startTime)
+              ->where('end_time', '>=', $endTime);
+          });
+      })
+      ->when($excludeScheduleId, function ($query) use ($excludeScheduleId) {
+        $query->where('id', '!=', $excludeScheduleId);
+      })
+      ->first();
+
+    if ($conflictingSchedule) {
+      throw new \Exception("Prestador já possui agendamento neste horário.");
     }
 
-    private function calculateAmount(Provider $provider, string $periodType): float
-    {
-        $hourlyRates = [
-            '2' => $provider->daily_price_2h ?? 0,
-            '4' => $provider->daily_price_4h ?? 0,
-            '6' => $provider->daily_price_6h ?? 0,
-            '8' => $provider->daily_price_8h ?? 0,
-        ];
+    return true;
+  }
 
-        return $hourlyRates[$periodType] ?? 0;
+  private function validateWeeklyScheduleLimit(array $data)
+  {
+    if (isset($data['schedule_type']) && $data['schedule_type'] !== 'default') {
+      return true;
     }
 
-    private function validateProviderAvailability(array $data, $excludeScheduleId = null)
-    {
-        $provider = Provider::findOrFail($data['provider_id']);
-        $date = Carbon::parse($data['date']);
-        $startTime = $data['start_time'];
-        $endTime = $data['end_time'];
-        
-        $dayOfWeek = $date->dayOfWeek;
-
-        $startHour = (int) substr($startTime, 0, 2);
-        $period = $startHour < 12 ? 'morning' : 'afternoon';
-
-        $workingDay = ProviderWorkingDay::where('provider_id', $data['provider_id'])
-            ->where('day', $dayOfWeek)
-            ->where('period', $period)
-            ->first();
-
-        if (!$workingDay) {
-            throw new \Exception("Prestador não trabalha neste dia/período.");
-        }
-
-        $blockedDay = ProviderBlockedDay::where('provider_id', $data['provider_id'])
-            ->where('date', $date->format('Y-m-d'))
-            ->where(function ($query) use ($startTime, $endTime) {
-                $query->where('period', 'full')
-                    ->orWhere(function ($q) use ($startTime, $endTime) {
-                        $q->where('period', 'partial')
-                            ->where(function ($q2) use ($startTime, $endTime) {
-                                $q2->whereBetween('init_hour', [$startTime, $endTime])
-                                    ->orWhereBetween('end_hour', [$startTime, $endTime])
-                                    ->orWhere(function ($q3) use ($startTime, $endTime) {
-                                        $q3->where('init_hour', '<=', $startTime)
-                                            ->where('end_hour', '>=', $endTime);
-                                    });
-                            });
-                    });
-            })
-            ->first();
-
-        if ($blockedDay) {
-            throw new \Exception("Prestador possui bloqueio neste dia/horário.");
-        }
-
-        $conflictingSchedule = Schedule::where('provider_id', $data['provider_id'])
-            ->where('date', $date->format('Y-m-d'))
-            ->whereIn('status', ['pending', 'accepted', 'paid', 'started'])
-            ->where(function ($query) use ($startTime, $endTime) {
-                $query->whereBetween('start_time', [$startTime, $endTime])
-                    ->orWhereBetween('end_time', [$startTime, $endTime])
-                    ->orWhere(function ($q) use ($startTime, $endTime) {
-                        $q->where('start_time', '<=', $startTime)
-                            ->where('end_time', '>=', $endTime);
-                    });
-            })
-            ->when($excludeScheduleId, function ($query) use ($excludeScheduleId) {
-                $query->where('id', '!=', $excludeScheduleId);
-            })
-            ->first();
-
-        if ($conflictingSchedule) {
-            throw new \Exception("Prestador já possui agendamento neste horário.");
-        }
-
-        return true;
+    return ScheduleBusinessRules::validateWeeklyScheduleLimit(
+      $data['client_id'],
+      $data['provider_id'],
+      $data['date']
+    );
+  }
+
+  public function getSchedulesDefaultGroupedByClient()
+  {
+    $schedules = Schedule::with(['client.user', 'provider.user', 'address'])
+      ->orderBy('id', 'desc')
+      ->where('schedule_type', 'default')
+      ->select(
+        'schedules.*'
+
+      )
+      ->get();
+
+    $grouped = $schedules->groupBy('client_id')->map(function ($clientSchedules) {
+      $firstSchedule = $clientSchedules->first();
+      return [
+        'client_id' => $firstSchedule->client_id,
+        'client_name' => $firstSchedule->client->user->name ?? 'N/A',
+        'schedules' => $clientSchedules->map(function ($schedule) {
+          return [
+            'id' => $schedule->id,
+            'date' => $schedule->date ? Carbon::parse($schedule->date)->format('d/m/Y') : null,
+            'start_time' => $schedule->start_time,
+            'end_time' => $schedule->end_time,
+            'period_type' => $schedule->period_type,
+            'status' => $schedule->status,
+            'total_amount' => $schedule->total_amount,
+            'code' => $schedule->code,
+            'code_verified' => $schedule->code_verified,
+            'provider_id' => $schedule->provider_id,
+            'provider_name' => $schedule->provider->user->name ?? 'N/A',
+            'address' => $schedule->address ? [
+              'id' => $schedule->address->id,
+              'address' => $schedule->address->address,
+              'complement' => $schedule->address->complement,
+              'zip_code' => $schedule->address->zip_code,
+              'city' => $schedule->address->city->name ?? '',
+              'state' => $schedule->address->city->state->name ?? '',
+            ] : null,
+            'client_name' => $schedule->client->user->name ?? 'N/A',
+          ];
+        })->values()
+      ];
+    })->sortBy('id')->values();
+
+    return $grouped;
+  }
+
+  public function updateStatus($id, string $status)
+  {
+    $schedule = Schedule::findOrFail($id);
+
+    $allowedTransitions = [
+      'pending' => ['accepted', 'rejected'],
+      'accepted' => ['paid', 'cancelled'],
+      'paid' => ['cancelled', 'started'],
+      'started' => ['finished'],
+      'rejected' => [],
+      'cancelled' => [],
+      'finished' => [],
+    ];
+
+    $currentStatus = $schedule->status;
+
+    if (!isset($allowedTransitions[$currentStatus])) {
+      throw new \Exception("Status atual inválido.");
     }
 
-    public function getGroupedByClient()
-    {
-        $schedules = Schedule::with(['client.user', 'provider.user', 'address'])
-            // ->orderBy('date', 'desc')
-            ->orderBy('id', 'desc')
-            ->select(
-                'schedules.*'
-                
-            )
-            ->get();
-
-        $grouped = $schedules->groupBy('client_id')->map(function ($clientSchedules) {
-            $firstSchedule = $clientSchedules->first();
-            return [
-                'client_id' => $firstSchedule->client_id,
-                'client_name' => $firstSchedule->client->user->name ?? 'N/A',
-                'schedules' => $clientSchedules->map(function ($schedule) {
-                    return [
-                        'id' => $schedule->id,
-                        'date' => $schedule->date ? Carbon::parse($schedule->date)->format('d/m/Y') : null,
-                        'start_time' => $schedule->start_time,
-                        'end_time' => $schedule->end_time,
-                        'period_type' => $schedule->period_type,
-                        'status' => $schedule->status,
-                        'total_amount' => $schedule->total_amount,
-                        'code' => $schedule->code,
-                        'code_verified' => $schedule->code_verified,
-                        'provider_id' => $schedule->provider_id,
-                        'provider_name' => $schedule->provider->user->name ?? 'N/A',
-                        'address' => $schedule->address ? [
-                            'id' => $schedule->address->id,
-                            'address' => $schedule->address->address,
-                            'complement' => $schedule->address->complement,
-                            'zip_code' => $schedule->address->zip_code,
-                            'city' => $schedule->address->city->name ?? '',
-                            'state' => $schedule->address->city->state->name ?? '',
-                        ] : null,
-                        'client_name' => $schedule->client->user->name ?? 'N/A',
-                    ];
-                })->values()
-            ];
-        })->sortBy('id')->values();
-
-        return $grouped;
+    if (!in_array($status, $allowedTransitions[$currentStatus])) {
+      throw new \Exception("Transição de status não permitida: {$currentStatus} → {$status}");
     }
 
-    public function updateStatus($id, string $status)
-    {
-        $schedule = Schedule::findOrFail($id);
-        
-        $allowedTransitions = [
-            'pending' => ['accepted', 'rejected'],
-            'accepted' => ['paid', 'cancelled'],
-            'paid' => ['cancelled', 'started'],
-            'started' => ['finished'],
-            'rejected' => [],
-            'cancelled' => [],
-            'finished' => [],
-        ];
-
-        $currentStatus = $schedule->status;
-        
-        if (!isset($allowedTransitions[$currentStatus])) {
-            throw new \Exception("Status atual inválido.");
-        }
-
-        if (!in_array($status, $allowedTransitions[$currentStatus])) {
-            throw new \Exception("Transição de status não permitida: {$currentStatus} → {$status}");
-        }
-
-        $schedule->update(['status' => $status]);
-        return $schedule->fresh(['client.user', 'provider.user', 'address']);
-    }
+    $schedule->update(['status' => $status]);
+    return $schedule->fresh(['client.user', 'provider.user', 'address']);
+  }
 }

+ 34 - 0
database/migrations/2026_02_24_192720_create_schedule_proposals_table.php

@@ -0,0 +1,34 @@
+<?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::create('schedule_proposals', function (Blueprint $table) {
+            $table->id();
+            $table->foreignId('schedule_id')->constrained('schedules')->onDelete('cascade');
+            $table->foreignId('provider_id')->constrained('providers')->onDelete('cascade');
+            $table->timestamps();
+            $table->softDeletes();
+
+            $table->index('schedule_id');
+            $table->index('provider_id');
+            $table->index('deleted_at');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('schedule_proposals');
+    }
+};

+ 34 - 0
database/migrations/2026_02_24_192804_create_schedule_refuses_table.php

@@ -0,0 +1,34 @@
+<?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::create('schedule_refuses', function (Blueprint $table) {
+            $table->id();
+            $table->foreignId('schedule_id')->constrained('schedules')->onDelete('cascade');
+            $table->foreignId('provider_id')->constrained('providers')->onDelete('cascade');
+            $table->timestamps();
+            $table->softDeletes();
+
+            $table->index('schedule_id');
+            $table->index('provider_id');
+            $table->index('deleted_at');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('schedule_refuses');
+    }
+};

+ 12 - 0
database/seeders/PermissionSeeder.php

@@ -94,6 +94,18 @@ class PermissionSeeder extends Seeder
                         "bits" => 271,
                         "children" => [],
                     ],
+                    [
+                        "scope" => "config.schedule_proposal",
+                        "description" => "Propostas de Agendamento",
+                        "bits" => 271,
+                        "children" => [],
+                    ],
+                    [
+                        "scope" => "config.schedule_refuse",
+                        "description" => "Recusas de Agendamento",
+                        "bits" => 271,
+                        "children" => [],
+                    ],
                     [
                         "scope" => "config.country",
                         "description" => "Configurações de Países",

+ 13 - 0
lang/en/validation.php

@@ -199,6 +199,19 @@ return [
         'address_type' => [
             'invalid' => 'The address type is invalid.',
         ],
+        'schedule' => [
+            'weekly_limit_exceeded' => 'This provider already has 2 schedules with this client in the same week.',
+        ],
+        'opportunity' => [
+            'already_assigned' => 'This opportunity already has a provider assigned.',
+            'proposal_already_sent' => 'You have already sent a proposal for this opportunity.',
+            'provider_refused' => 'You were previously refused for this opportunity.',
+            'provider_not_working' => 'Provider does not work on this day/period.',
+            'provider_blocked' => 'Provider has a block on this day/time.',
+            'schedule_conflict' => 'Provider already has a schedule at this time.',
+            'code_already_verified' => 'The code has already been verified for this schedule.',
+            'invalid_code' => 'The provided code is invalid.',
+        ],
     ],
 
     /*

+ 13 - 0
lang/es/validation.php

@@ -199,6 +199,19 @@ return [
         'address_type' => [
             'invalid' => 'El tipo de dirección no es válido.',
         ],
+        'schedule' => [
+            'weekly_limit_exceeded' => 'Este proveedor ya tiene 2 agendas con este cliente en la misma semana.',
+        ],
+        'opportunity' => [
+            'already_assigned' => 'Esta oportunidad ya tiene un proveedor asignado.',
+            'proposal_already_sent' => 'Ya has enviado una propuesta para esta oportunidad.',
+            'provider_refused' => 'Fuiste rechazado anteriormente para esta oportunidad.',
+            'provider_not_working' => 'El proveedor no trabaja en este día/período.',
+            'provider_blocked' => 'El proveedor tiene un bloqueo en este día/horario.',
+            'schedule_conflict' => 'El proveedor ya tiene una agenda en este horario.',
+            'code_already_verified' => 'El código ya fue verificado para este agendamiento.',
+            'invalid_code' => 'El código informado es inválido.',
+        ],
     ],
 
     /*

+ 13 - 0
lang/pt/validation.php

@@ -200,6 +200,19 @@ return [
         'address_type' => [
             'invalid' => 'O tipo de endereço é inválido.',
         ],
+        'schedule' => [
+            'weekly_limit_exceeded' => 'Este prestador já possui 2 agendamentos com este cliente na mesma semana.',
+        ],
+        'opportunity' => [
+            'already_assigned' => 'Esta oportunidade já possui um prestador atribuído.',
+            'proposal_already_sent' => 'Você já enviou uma proposta para esta oportunidade.',
+            'provider_refused' => 'Você foi recusado anteriormente para esta oportunidade.',
+            'provider_not_working' => 'Prestador não trabalha neste dia/período.',
+            'provider_blocked' => 'Prestador possui bloqueio neste dia/horário.',
+            'schedule_conflict' => 'Prestador já possui agendamento neste horário.',
+            'code_already_verified' => 'O código já foi verificado para este agendamento.',
+            'invalid_code' => 'O código informado é inválido.',
+        ],
     ],
 
     /*

+ 6 - 6
routes/authRoutes/client_favorite_provider.php

@@ -3,9 +3,9 @@
 use App\Http\Controllers\ClientFavoriteProviderController;
 use Illuminate\Support\Facades\Route;
 
-Route::get('/client/favorite-providers/{clientId}', [ClientFavoriteProviderController::class, 'index']);
-Route::get('/client/favorited-providers/{clientId}', [ClientFavoriteProviderController::class, 'getFavoritedProviders']);
-Route::get('/client/favorite-provider/{id}', [ClientFavoriteProviderController::class, 'show']);
-Route::post('/client/favorite-provider', [ClientFavoriteProviderController::class, 'store']);
-Route::put('/client/favorite-provider/{id}', [ClientFavoriteProviderController::class, 'update']);
-Route::delete('/client/favorite-provider/{id}', [ClientFavoriteProviderController::class, 'destroy']);
+Route::get('/client/favorite-providers/{clientId}', [ClientFavoriteProviderController::class, 'index'])->middleware('permission:config.client_favorite_provider,view');
+Route::get('/client/favorited-providers/{clientId}', [ClientFavoriteProviderController::class, 'getFavoritedProviders'])->middleware('permission:config.client_favorite_provider,view');
+Route::get('/client/favorite-provider/{id}', [ClientFavoriteProviderController::class, 'show'])->middleware('permission:config.client_favorite_provider,view');
+Route::post('/client/favorite-provider', [ClientFavoriteProviderController::class, 'store'])->middleware('permission:config.client_favorite_provider,add');
+Route::put('/client/favorite-provider/{id}', [ClientFavoriteProviderController::class, 'update'])->middleware('permission:config.client_favorite_provider,edit');
+Route::delete('/client/favorite-provider/{id}', [ClientFavoriteProviderController::class, 'destroy'])->middleware('permission:config.client_favorite_provider,delete');

+ 5 - 5
routes/authRoutes/client_payment_method.php

@@ -3,8 +3,8 @@
 use App\Http\Controllers\ClientPaymentMethodController;
 use Illuminate\Support\Facades\Route;
 
-Route::get('/client/payment-methods/{clientId}', [ClientPaymentMethodController::class, 'index']);
-Route::get('/client/payment-method/{id}', [ClientPaymentMethodController::class, 'show']);
-Route::post('/client/payment-method', [ClientPaymentMethodController::class, 'store']);
-Route::put('/client/payment-method/{id}', [ClientPaymentMethodController::class, 'update']);
-Route::delete('/client/payment-method/{id}', [ClientPaymentMethodController::class, 'destroy']);
+Route::get('/client/payment-methods/{clientId}', [ClientPaymentMethodController::class, 'index'])->middleware('permission:config.client_payment_method,view');
+Route::get('/client/payment-method/{id}', [ClientPaymentMethodController::class, 'show'])->middleware('permission:config.client_payment_method,view');
+Route::post('/client/payment-method', [ClientPaymentMethodController::class, 'store'])->middleware('permission:config.client_payment_method,add');
+Route::put('/client/payment-method/{id}', [ClientPaymentMethodController::class, 'update'])->middleware('permission:config.client_payment_method,edit');
+Route::delete('/client/payment-method/{id}', [ClientPaymentMethodController::class, 'destroy'])->middleware('permission:config.client_payment_method,delete');

+ 13 - 1
routes/authRoutes/custom_schedule.php

@@ -7,4 +7,16 @@ Route::get('/custom-schedule', [CustomScheduleController::class, 'index'])->midd
 Route::get('/custom-schedule/{id}', [CustomScheduleController::class, 'show'])->middleware('permission:config.custom_schedule,view');
 Route::post('/custom-schedule', [CustomScheduleController::class, 'store'])->middleware('permission:config.custom_schedule,add');
 Route::put('/custom-schedule/{id}', [CustomScheduleController::class, 'update'])->middleware('permission:config.custom_schedule,edit');
-Route::delete('/custom-schedule/{id}', [CustomScheduleController::class, 'destroy'])->middleware('permission:config.custom_schedule,delete');
+Route::delete('/custom-schedule/{id}', [CustomScheduleController::class, 'destroy'])->middleware('permission:config.custom_schedule,delete');
+Route::get('/schedules/grouped-by-client/custom', [CustomScheduleController::class, 'groupedByClientCustom']);
+
+Route::get('/custom-schedule/available', [CustomScheduleController::class, 'available'])->middleware('permission:config.custom_schedule,view');
+Route::get('/custom-schedule/provider-proposals', [CustomScheduleController::class, 'providerProposals'])->middleware('permission:config.custom_schedule,view');
+Route::get('/custom-schedule/{scheduleId}/proposals', [CustomScheduleController::class, 'opportunityProposals'])->middleware('permission:config.custom_schedule,view');
+Route::post('/custom-schedule/{scheduleId}/propose', [CustomScheduleController::class, 'propose'])->middleware('permission:config.custom_schedule,add');
+Route::post('/custom-schedule/{proposalId}/accept', [CustomScheduleController::class, 'acceptProposal'])->middleware('permission:config.custom_schedule,edit');
+Route::post('/custom-schedule/{proposalId}/refuse', [CustomScheduleController::class, 'refuseProposal'])->middleware('permission:config.custom_schedule,delete');
+Route::post('/custom-schedule/{proposalId}/refuse-opportunity', [CustomScheduleController::class, 'refuseOpportunity'])->middleware('permission:config.custom_schedule,delete');
+
+Route::get('/custom-schedule-proposals-provider/{providerId}', [CustomScheduleController::class, 'getProvidersProposalsAndOpportunities'])->middleware('permission:config.custom_schedule,view');
+Route::post('/custom-schedule-verify-code/{scheduleId}', [CustomScheduleController::class, 'verifyCode'])->middleware('permission:config.custom_schedule,edit');

+ 7 - 7
routes/authRoutes/schedule.php

@@ -3,10 +3,10 @@
 use App\Http\Controllers\ScheduleController;
 use Illuminate\Support\Facades\Route;
 
-Route::get('/schedules', [ScheduleController::class, 'index']);
-Route::get('/schedules/grouped-by-client', [ScheduleController::class, 'groupedByClient']);
-Route::get('/schedule/{id}', [ScheduleController::class, 'show']);
-Route::post('/schedule', [ScheduleController::class, 'store']);
-Route::put('/schedule/{id}', [ScheduleController::class, 'update']);
-Route::patch('/schedule/{id}/status', [ScheduleController::class, 'updateStatus']);
-Route::delete('/schedule/{id}', [ScheduleController::class, 'destroy']);
+Route::get('/schedules', [ScheduleController::class, 'index'])->middleware('permission:config.schedule,view');
+Route::get('/schedules/grouped-by-client', [ScheduleController::class, 'groupedByClient'])->middleware('permission:config.schedule,view');
+Route::get('/schedule/{id}', [ScheduleController::class, 'show'])->middleware('permission:config.schedule,view');
+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::delete('/schedule/{id}', [ScheduleController::class, 'destroy'])->middleware('permission:config.schedule,delete');