Gustavo Mantovani 3 өдөр өмнө
parent
commit
c0887ff4ac
49 өөрчлөгдсөн 1446 нэмэгдсэн , 580 устгасан
  1. 1 1
      app/Data/Pagarme/Customer/CustomerRequestData.php
  2. 14 0
      app/Enums/CartStatusEnum.php
  3. 2 2
      app/Http/Controllers/AddressController.php
  4. 63 0
      app/Http/Controllers/CartController.php
  5. 50 0
      app/Http/Controllers/CartItemController.php
  6. 3 3
      app/Http/Controllers/CityController.php
  7. 2 2
      app/Http/Controllers/ClientController.php
  8. 2 2
      app/Http/Controllers/ClientFavoriteProviderController.php
  9. 2 2
      app/Http/Controllers/ClientPaymentMethodController.php
  10. 2 2
      app/Http/Controllers/ClientProviderBlockController.php
  11. 2 2
      app/Http/Controllers/CountryController.php
  12. 2 2
      app/Http/Controllers/MediaController.php
  13. 54 28
      app/Http/Controllers/PaymentController.php
  14. 2 2
      app/Http/Controllers/PermissionController.php
  15. 2 2
      app/Http/Controllers/ProviderBlockedDayController.php
  16. 2 2
      app/Http/Controllers/ProviderClientBlockController.php
  17. 2 2
      app/Http/Controllers/ProviderController.php
  18. 1 1
      app/Http/Controllers/ProviderWorkingDayController.php
  19. 2 2
      app/Http/Controllers/ReviewController.php
  20. 2 2
      app/Http/Controllers/ReviewImprovementController.php
  21. 47 22
      app/Http/Controllers/ScheduleController.php
  22. 2 2
      app/Http/Controllers/StateController.php
  23. 2 2
      app/Http/Controllers/UserController.php
  24. 33 0
      app/Http/Requests/CartItemRequest.php
  25. 81 0
      app/Http/Requests/CartRequest.php
  26. 10 36
      app/Http/Requests/ScheduleRequest.php
  27. 37 0
      app/Http/Resources/CartItemResource.php
  28. 43 0
      app/Http/Resources/CartResource.php
  29. 6 4
      app/Http/Resources/PaymentResource.php
  30. 26 26
      app/Models/Address.php
  31. 58 0
      app/Models/Cart.php
  32. 41 0
      app/Models/CartItem.php
  33. 2 2
      app/Models/Client.php
  34. 38 33
      app/Rules/ScheduleBusinessRules.php
  35. 52 0
      app/Services/CartItemService.php
  36. 117 0
      app/Services/CartService.php
  37. 20 0
      app/Services/Pagarme/Concerns/FormatsPagarmeData.php
  38. 4 5
      app/Services/Pagarme/PagarmeCustomerService.php
  39. 4 3
      app/Services/Pagarme/PagarmePaymentService.php
  40. 158 14
      app/Services/PaymentService.php
  41. 217 243
      app/Services/ScheduleService.php
  42. 5 1
      app/Services/WebhookService.php
  43. 27 0
      database/migrations/2026_06_19_181942_create_carts_table.php
  44. 27 0
      database/migrations/2026_06_19_190028_create_cart_items_table.php
  45. 71 70
      database/seeders/PermissionSeeder.php
  46. 64 55
      database/seeders/UserTypePermissionSeeder.php
  47. 19 0
      routes/authRoutes/cart.php
  48. 19 0
      routes/authRoutes/cart_item.php
  49. 4 3
      routes/authRoutes/payment.php

+ 1 - 1
app/Data/Pagarme/Customer/CustomerRequestData.php

@@ -23,7 +23,7 @@ final readonly class CustomerRequestData extends PagarmeData
         self::requireFilled($this->email, 'email');
         self::requireFilled($this->document, 'document');
         self::requireIn($this->type, ['individual', 'company'], 'type');
-        self::requireIn($this->documentType, ['CPF', 'CNPJ'], 'document_type');
+        self::requireIn($this->documentType, ['CPF', 'CNPJ', 'PASSPORT'], 'document_type');
         self::requireFilled($this->code, 'code');
     }
 

+ 14 - 0
app/Enums/CartStatusEnum.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace App\Enums;
+
+use App\Traits\EnumHelper;
+
+enum CartStatusEnum: string
+{
+    use EnumHelper;
+
+    case OPEN      = 'open';
+    case PAID      = 'paid';
+    case CANCELLED = 'cancelled';
+}

+ 2 - 2
app/Http/Controllers/AddressController.php

@@ -41,7 +41,7 @@ class AddressController extends Controller
         return $this->successResponse(
             payload: new AddressResource($address),
             message: __('messages.created'),
-            code: 201,
+            code:    201,
         );
     }
 
@@ -61,7 +61,7 @@ class AddressController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 

+ 63 - 0
app/Http/Controllers/CartController.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Requests\CartRequest;
+use App\Http\Resources\CartResource;
+use App\Services\CartService;
+use Illuminate\Http\JsonResponse;
+
+class CartController extends Controller
+{
+    public function __construct(
+        protected CartService $service,
+    ) {}
+
+    public function index(): JsonResponse
+    {
+        $items = $this->service->getAll();
+
+        return $this->successResponse(payload: CartResource::collection($items));
+    }
+
+    public function store(CartRequest $request): JsonResponse
+    {
+        $validated = $request->validated();
+
+        $item = $this->hasSchedulePayload($validated)
+            ? $this->service->createWithSchedules($validated)
+            : $this->service->create($validated);
+
+        return $this->successResponse(payload: new CartResource($item), message: __('messages.created'), code: 201);
+    }
+
+    public function show(int $id): JsonResponse
+    {
+        $item = $this->service->findById($id);
+
+        return $this->successResponse(payload: new CartResource($item));
+    }
+
+    public function update(CartRequest $request, int $id): JsonResponse
+    {
+        $item = $this->service->update($id, $request->validated());
+
+        return $this->successResponse(payload: new CartResource($item), message: __('messages.updated'));
+    }
+
+    public function destroy(int $id): JsonResponse
+    {
+        $this->service->delete($id);
+
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+
+    //
+
+    private function hasSchedulePayload(array $data): bool
+    {
+        return ! empty($data['schedules'])
+            || ! empty($data['dates'])
+            || ! empty($data['date']);
+    }
+}

+ 50 - 0
app/Http/Controllers/CartItemController.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Requests\CartItemRequest;
+use App\Http\Resources\CartItemResource;
+use App\Services\CartItemService;
+use Illuminate\Http\JsonResponse;
+
+class CartItemController extends Controller
+{
+    public function __construct(
+        protected CartItemService $service,
+    ) {}
+
+    public function index(): JsonResponse
+    {
+        $items = $this->service->getAll();
+
+        return $this->successResponse(payload: CartItemResource::collection($items));
+    }
+
+    public function store(CartItemRequest $request): JsonResponse
+    {
+        $item = $this->service->create($request->validated());
+
+        return $this->successResponse(payload: new CartItemResource($item), message: __('messages.created'), code: 201);
+    }
+
+    public function show(int $id): JsonResponse
+    {
+        $item = $this->service->findById($id);
+
+        return $this->successResponse(payload: new CartItemResource($item));
+    }
+
+    public function update(CartItemRequest $request, int $id): JsonResponse
+    {
+        $item = $this->service->update($id, $request->validated());
+
+        return $this->successResponse(payload: new CartItemResource($item), message: __('messages.updated'));
+    }
+
+    public function destroy(int $id): JsonResponse
+    {
+        $this->service->delete($id);
+
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+}

+ 3 - 3
app/Http/Controllers/CityController.php

@@ -28,7 +28,7 @@ class CityController extends Controller
         return $this->successResponse(
             payload: new CityResource($item),
             message: __('messages.created'),
-            code: 201,
+            code:    201,
         );
     }
 
@@ -55,14 +55,14 @@ class CityController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 
     public function searchStateCityByDescription(Request $request): JsonResponse
     {
         $state = $request->query('stateUf');
-        $city = $request->query('cityName');
+        $city  = $request->query('cityName');
 
         $result = $this->service->findStateCityByDescription($state, $city);
 

+ 2 - 2
app/Http/Controllers/ClientController.php

@@ -29,7 +29,7 @@ class ClientController extends Controller
         return $this->successResponse(
             payload: new ClientResource($item),
             message: __('messages.created'),
-            code: 201,
+            code:    201,
         );
     }
 
@@ -56,7 +56,7 @@ class ClientController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 

+ 2 - 2
app/Http/Controllers/ClientFavoriteProviderController.php

@@ -38,7 +38,7 @@ class ClientFavoriteProviderController extends Controller
         return $this->successResponse(
             payload: new ClientFavoriteProviderResource($favorite),
             message: __('messages.created'),
-            code: 201,
+            code:    201,
         );
     }
 
@@ -58,7 +58,7 @@ class ClientFavoriteProviderController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 

+ 2 - 2
app/Http/Controllers/ClientPaymentMethodController.php

@@ -39,7 +39,7 @@ class ClientPaymentMethodController extends Controller
         return $this->successResponse(
             payload: new ClientPaymentMethodResource($paymentMethod),
             message: __('messages.created'),
-            code: 201,
+            code:    201,
         );
     }
 
@@ -63,7 +63,7 @@ class ClientPaymentMethodController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 }

+ 2 - 2
app/Http/Controllers/ClientProviderBlockController.php

@@ -29,7 +29,7 @@ class ClientProviderBlockController extends Controller
         return $this->successResponse(
             payload: new ClientProviderBlockResource($block),
             message: __('messages.created'),
-            code: 201,
+            code:    201,
         );
     }
 
@@ -39,7 +39,7 @@ class ClientProviderBlockController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 

+ 2 - 2
app/Http/Controllers/CountryController.php

@@ -27,7 +27,7 @@ class CountryController extends Controller
         return $this->successResponse(
             payload: new CountryResource($item),
             message: __('messages.created'),
-            code: 201,
+            code:    201,
         );
     }
 
@@ -54,7 +54,7 @@ class CountryController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 }

+ 2 - 2
app/Http/Controllers/MediaController.php

@@ -27,7 +27,7 @@ class MediaController extends Controller
         return $this->successResponse(
             payload: new MediaResource($item),
             message: __('messages.created'),
-            code: 201,
+            code:    201,
         );
     }
 
@@ -54,7 +54,7 @@ class MediaController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 }

+ 54 - 28
app/Http/Controllers/PaymentController.php

@@ -31,7 +31,49 @@ class PaymentController extends Controller
         return $this->successResponse(
             payload: new PaymentResource($item),
             message: $this->paymentMessage($item->status->value),
-            code: 201,
+            code:    201,
+        );
+    }
+
+    public function show(int $id): JsonResponse
+    {
+        $item = $this->service->findById($id);
+
+        return $this->successResponse(payload: new PaymentResource($item));
+    }
+
+    public function update(PaymentRequest $request, int $id): JsonResponse
+    {
+        $item = $this->service->update($id, $request->validated());
+
+        return $this->successResponse(payload: new PaymentResource($item), message: __('messages.updated'));
+    }
+
+    public function destroy(int $id): JsonResponse
+    {
+        $this->service->delete($id);
+
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+
+    //
+
+    public function payCart(Request $request): JsonResponse
+    {
+        $validated = $request->validate([
+            'cart_id'                  => ['required', 'integer', 'exists:carts,id'],
+            'payment_method'           => ['required', 'in:credit_card,pix'],
+            'client_payment_method_id' => ['nullable', 'integer', 'exists:client_payment_methods,id'],
+            'card_id'                  => ['nullable', 'string', 'max:255'],
+            'phone'                    => ['nullable', 'string', 'max:20'],
+        ]);
+
+        $items = $this->service->payCart($validated, Auth::id());
+
+        return $this->successResponse(
+            payload: PaymentResource::collection($items),
+            message: __('messages.created'),
+            code:    201,
         );
     }
 
@@ -49,9 +91,10 @@ class PaymentController extends Controller
         }
 
         $item = $this->service->payAcceptedSchedule(
-            schedule: $schedule,
-            paymentMethod: $validated['payment_method'],
+            schedule:              $schedule,
+            paymentMethod:         $validated['payment_method'],
             clientPaymentMethodId: $validated['client_payment_method_id'] ?? null,
+
             options: [
                 'phone'   => $validated['phone'] ?? null,
                 'card_id' => $validated['card_id'] ?? null,
@@ -68,14 +111,11 @@ class PaymentController extends Controller
         return $this->successResponse(
             payload: new PaymentResource($item),
             message: $this->paymentMessage($item->status->value),
-            code: 201,
+            code:    201,
         );
     }
 
-    public function platformFees(): JsonResponse
-    {
-        return $this->successResponse(payload: $this->service->platformFees());
-    }
+    //
 
     public function getSchedulePix(Schedule $schedule): JsonResponse
     {
@@ -88,34 +128,20 @@ class PaymentController extends Controller
         return $this->successResponse(payload: new PaymentResource($item));
     }
 
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-
-        return $this->successResponse(payload: new PaymentResource($item));
-    }
-
-    public function update(PaymentRequest $request, int $id): JsonResponse
+    public function platformFees(): JsonResponse
     {
-        $item = $this->service->update($id, $request->validated());
-
-        return $this->successResponse(payload: new PaymentResource($item), message: __('messages.updated'));
+        return $this->successResponse(payload: $this->service->platformFees());
     }
 
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
+    //
 
     private function paymentMessage(string $status): string
     {
         return match ($status) {
-            'paid' => __('messages.payment_confirmed'),
+            'paid'                                => __('messages.payment_confirmed'),
             'authorized', 'processing', 'pending' => __('messages.payment_pending_confirmation'),
-            'failed' => __('messages.payment_not_confirmed'),
-            default  => __('messages.created'),
+            'failed'                              => __('messages.payment_not_confirmed'),
+            default                               => __('messages.created'),
         };
     }
 }

+ 2 - 2
app/Http/Controllers/PermissionController.php

@@ -27,7 +27,7 @@ class PermissionController extends Controller
         return $this->successResponse(
             payload: new PermissionResource($item),
             message: __('messages.created'),
-            code: 201,
+            code:    201,
         );
     }
 
@@ -54,7 +54,7 @@ class PermissionController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 }

+ 2 - 2
app/Http/Controllers/ProviderBlockedDayController.php

@@ -38,7 +38,7 @@ class ProviderBlockedDayController extends Controller
         return $this->successResponse(
             payload: new ProviderBlockedDayResource($blockedDay),
             message: __('messages.created'),
-            code: 201,
+            code:    201,
         );
     }
 
@@ -62,7 +62,7 @@ class ProviderBlockedDayController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 }

+ 2 - 2
app/Http/Controllers/ProviderClientBlockController.php

@@ -29,7 +29,7 @@ class ProviderClientBlockController extends Controller
         return $this->successResponse(
             payload: new ProviderClientBlockResource($block),
             message: __('messages.created'),
-            code: 201,
+            code:    201,
         );
     }
 
@@ -39,7 +39,7 @@ class ProviderClientBlockController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 

+ 2 - 2
app/Http/Controllers/ProviderController.php

@@ -31,7 +31,7 @@ class ProviderController extends Controller
         return $this->successResponse(
             payload: new ProviderResource($item),
             message: __('messages.created'),
-            code: 201,
+            code:    201,
         );
     }
 
@@ -68,7 +68,7 @@ class ProviderController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 

+ 1 - 1
app/Http/Controllers/ProviderWorkingDayController.php

@@ -37,7 +37,7 @@ class ProviderWorkingDayController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 }

+ 2 - 2
app/Http/Controllers/ReviewController.php

@@ -84,7 +84,7 @@ class ReviewController extends Controller
         return $this->successResponse(
             payload: new ReviewResource($review),
             message: __('messages.created'),
-            code: 201,
+            code:    201,
         );
     }
 
@@ -104,7 +104,7 @@ class ReviewController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 }

+ 2 - 2
app/Http/Controllers/ReviewImprovementController.php

@@ -29,7 +29,7 @@ class ReviewImprovementController extends Controller
         return $this->successResponse(
             payload: new ReviewImprovementResource($item),
             message: __('messages.created'),
-            code: 201,
+            code:    201,
         );
     }
 
@@ -39,7 +39,7 @@ class ReviewImprovementController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 }

+ 47 - 22
app/Http/Controllers/ScheduleController.php

@@ -7,6 +7,7 @@ use App\Http\Resources\ScheduleResource;
 use App\Services\ScheduleService;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
+use Illuminate\Support\Arr;
 
 class ScheduleController extends Controller
 {
@@ -31,18 +32,35 @@ class ScheduleController extends Controller
         try {
             $validated = $request->validated();
 
-            $schedules = $this->scheduleService->createSingleOrMultiple($validated, $validated['schedules']);
+            $schedules = $this->scheduleService->createSingleOrMultiple(
+                Arr::except($validated, ['date', 'dates']),
+                $this->scheduleItems($validated),
+            );
 
             return $this->successResponse(
                 payload: ScheduleResource::collection($schedules),
                 message: count($schedules).' '.__('schedules.schedules_created'),
-                code: 201,
+                code:    201,
             );
         } catch (\Exception $e) {
             return $this->errorResponse($e->getMessage(), 422);
         }
     }
 
+    private function scheduleItems(array $data): array
+    {
+        if (! empty($data['dates'])) {
+            return array_map(
+                fn (string $date) => ['date' => $date],
+                $data['dates'],
+            );
+        }
+
+        return [
+            ['date' => $data['date']],
+        ];
+    }
+
     public function show(string $id): JsonResponse
     {
         $schedule = $this->scheduleService->getById($id);
@@ -72,10 +90,27 @@ class ScheduleController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 
+    //
+
+    public function clientProviderBlocks(Request $request): JsonResponse
+    {
+        $validated = $request->validate([
+            'client_id'   => 'required|integer|exists:clients,id',
+            'provider_id' => 'required|integer|exists:providers,id',
+        ]);
+
+        $blocks = $this->scheduleService->getClientProviderBlocks(
+            (int) $validated['client_id'],
+            (int) $validated['provider_id'],
+        );
+
+        return $this->successResponse(payload: $blocks);
+    }
+
     public function groupedByClient(): JsonResponse
     {
         $grouped = $this->scheduleService->getSchedulesDefaultGroupedByClient();
@@ -92,14 +127,16 @@ class ScheduleController extends Controller
         );
     }
 
-    public function updateStatus(string $id, ScheduleRequest $request): JsonResponse
+    //
+
+    public function cancelWithReason(string $id, Request $request): JsonResponse
     {
         try {
             $validated = $request->validate([
-                'status' => 'required|in:pending,accepted,rejected,paid,cancelled,started,finished',
+                'cancel_text' => 'required|string|min:5|max:1000',
             ]);
 
-            $schedule = $this->scheduleService->updateStatus($id, $validated['status']);
+            $schedule = $this->scheduleService->cancelWithReason((int) $id, $validated['cancel_text']);
 
             return $this->successResponse(
                 payload: new ScheduleResource($schedule),
@@ -110,14 +147,14 @@ class ScheduleController extends Controller
         }
     }
 
-    public function cancelWithReason(string $id, Request $request): JsonResponse
+    public function updateStatus(string $id, ScheduleRequest $request): JsonResponse
     {
         try {
             $validated = $request->validate([
-                'cancel_text' => 'required|string|min:5|max:1000',
+                'status' => 'required|in:pending,accepted,rejected,paid,cancelled,started,finished',
             ]);
 
-            $schedule = $this->scheduleService->cancelWithReason((int) $id, $validated['cancel_text']);
+            $schedule = $this->scheduleService->updateStatus($id, $validated['status']);
 
             return $this->successResponse(
                 payload: new ScheduleResource($schedule),
@@ -128,18 +165,6 @@ class ScheduleController extends Controller
         }
     }
 
-    public function clientProviderBlocks(Request $request): JsonResponse
-    {
-        $validated = $request->validate([
-            'client_id'   => 'required|integer|exists:clients,id',
-            'provider_id' => 'required|integer|exists:providers,id',
-        ]);
-
-        $blocks = $this->scheduleService->getClientProviderBlocks(
-            (int) $validated['client_id'],
-            (int) $validated['provider_id'],
-        );
+    //
 
-        return $this->successResponse(payload: $blocks);
-    }
 }

+ 2 - 2
app/Http/Controllers/StateController.php

@@ -27,7 +27,7 @@ class StateController extends Controller
         return $this->successResponse(
             payload: new StateResource($item),
             message: __('messages.created'),
-            code: 201,
+            code:    201,
         );
     }
 
@@ -54,7 +54,7 @@ class StateController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 }

+ 2 - 2
app/Http/Controllers/UserController.php

@@ -36,7 +36,7 @@ class UserController extends Controller
         return $this->successResponse(
             payload: new UserResource($item),
             message: __('messages.created'),
-            code: 201,
+            code:    201,
         );
     }
 
@@ -63,7 +63,7 @@ class UserController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 

+ 33 - 0
app/Http/Requests/CartItemRequest.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class CartItemRequest extends FormRequest
+{
+    public function rules(): array
+    {
+        $rules = [
+            'cart_id'     => ['sometimes', 'integer', 'exists:carts,id'],
+            'schedule_id' => ['sometimes', 'integer', 'exists:schedules,id'],
+        ];
+
+        if ($this->isMethod('POST')) {
+            $rules['cart_id']     = ['required', 'integer', 'exists:carts,id'];
+            $rules['schedule_id'] = ['required', 'integer', 'exists:schedules,id'];
+        }
+
+        return $rules;
+    }
+
+    /**
+    * Add custom messages when needed
+    * public function messages(): array
+    * {
+    *   return [
+    *        'field.required' => __('message.algo'),
+    *    ];
+    * }
+    */
+}

+ 81 - 0
app/Http/Requests/CartRequest.php

@@ -0,0 +1,81 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class CartRequest extends FormRequest
+{
+    public function authorize(): bool
+    {
+        return true;
+    }
+
+    public function rules(): array
+    {
+        $rules = [
+            'client_id'                => ['sometimes', 'integer', 'exists:clients,id'],
+            'provider_id'              => ['sometimes', 'integer', 'exists:providers,id'],
+            'address_id'               => ['sometimes', 'integer', 'exists:addresses,id'],
+            'date'                     => ['nullable', 'date', 'after_or_equal:today'],
+            'dates'                    => ['nullable', 'array'],
+            'dates.*'                  => ['date', 'after_or_equal:today'],
+            'schedules'                => ['nullable', 'array'],
+            'schedules.*.provider_id'  => ['sometimes', 'integer', 'exists:providers,id'],
+            'schedules.*.date'         => ['required', 'date', 'after_or_equal:today'],
+            'schedules.*.period_type'  => ['required', 'in:2,4,6,8'],
+            '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', 'in:2,4,6,8'],
+            'start_time'               => ['sometimes', 'date_format:H:i'],
+            'end_time'                 => ['sometimes', 'date_format:H:i', 'after:start_time'],
+            'total_amount'             => ['sometimes', 'numeric', 'min:0'],
+            'schedule_type'            => ['sometimes', 'in:default,custom'],
+        ];
+
+        if ($this->isMethod('POST')) {
+            $rules['client_id'] = ['required', 'integer', 'exists:clients,id'];
+
+            if ($this->hasSchedulePayload()) {
+                $rules['provider_id']              = ['required_without:schedules', 'integer', 'exists:providers,id'];
+                $rules['address_id']               = ['required', 'integer', 'exists:addresses,id'];
+                $rules['date']                     = ['required_without_all:dates,schedules', 'nullable', 'date', 'after_or_equal:today'];
+                $rules['dates']                    = ['nullable', 'array'];
+                $rules['dates.*']                  = ['date', 'after_or_equal:today'];
+                $rules['schedules']                = ['nullable', 'array', 'min:1'];
+                $rules['schedules.*.provider_id']  = ['required_without:provider_id', 'integer', 'exists:providers,id'];
+                $rules['schedules.*.date']         = ['required', 'date', 'after_or_equal:today'];
+                $rules['schedules.*.period_type']  = ['required', 'in:2,4,6,8'];
+                $rules['schedules.*.start_time']   = ['required', 'date_format:H:i'];
+                $rules['schedules.*.end_time']     = ['required', 'date_format:H:i', 'after:schedules.*.start_time'];
+                $rules['schedules.*.total_amount'] = ['required', 'numeric', 'min:0'];
+                $rules['period_type']              = ['required_without:schedules', 'in:2,4,6,8'];
+                $rules['start_time']               = ['required_without:schedules', 'date_format:H:i'];
+                $rules['end_time']                 = ['required_without:schedules', 'date_format:H:i', 'after:start_time'];
+                $rules['total_amount']             = ['required_without:schedules', 'numeric', 'min:0'];
+            }
+        }
+
+        return $rules;
+    }
+
+    private function hasSchedulePayload(): bool
+    {
+        return $this->filled('date')
+            || $this->filled('dates')
+            || $this->filled('schedules');
+    }
+
+    /**
+    * Add custom messages when needed
+    * public function messages(): array
+    * {
+    *   return [
+    *        'field.required' => __('message.algo'),
+    *    ];
+    * }
+    */
+}

+ 10 - 36
app/Http/Requests/ScheduleRequest.php

@@ -20,18 +20,12 @@ class ScheduleRequest extends FormRequest
             'date'                     => 'nullable|date|after_or_equal:today',
             'dates'                    => 'nullable|array',
             'dates.*'                  => 'date|after_or_equal:today',
-            'schedules'                => 'nullable|array',
-            'schedules.*.date'         => 'required|date|after_or_equal:today',
-            'schedules.*.period_type'  => 'required|in:2,4,6,8',
-            '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',
             'end_time'                 => 'sometimes|required|date_format:H:i|after:start_time',
+            'total_amount'             => 'sometimes|required|numeric|min:0',
             'status'                   => 'sometimes|in:pending,accepted,rejected,paid,cancelled,started,finished',
             'code_verified'            => 'sometimes|boolean',
         ];
@@ -40,18 +34,13 @@ class ScheduleRequest extends FormRequest
             $rules['client_id']                = 'required|exists:clients,id';
             $rules['provider_id']              = 'required|exists:providers,id';
             $rules['address_id']               = 'required|exists:addresses,id';
-            $rules['date']                     = 'required_without_all:dates,schedules|nullable|date|after_or_equal:today';
+            $rules['date']                     = 'required_without:dates|nullable|date|after_or_equal:today';
             $rules['dates']                    = 'nullable|array';
             $rules['dates.*']                  = 'date|after_or_equal:today';
-            $rules['schedules']                = 'nullable|array|min:1';
-            $rules['schedules.*.date']         = 'required|date|after_or_equal:today';
-            $rules['schedules.*.period_type']  = 'required|in:2,4,6,8';
-            $rules['schedules.*.start_time']   = 'required|date_format:H:i';
-            $rules['schedules.*.end_time']     = 'required|date_format:H:i|after:schedules.*.start_time';
-            $rules['schedules.*.total_amount'] = 'required|numeric|min:0';
-            $rules['period_type']              = 'required_without:schedules|in:2,4,6,8';
-            $rules['start_time']               = 'required_without:schedules|date_format:H:i';
-            $rules['end_time']                 = 'required_without:schedules|date_format:H:i|after:start_time';
+            $rules['period_type']              = 'required|in:2,4,6,8';
+            $rules['start_time']               = 'required|date_format:H:i';
+            $rules['end_time']                 = 'required|date_format:H:i|after:start_time';
+            $rules['total_amount']             = 'required|numeric|min:0';
             $rules['status']                   = 'in:pending';
         }
 
@@ -67,37 +56,22 @@ class ScheduleRequest extends FormRequest
             'provider_id.exists'                 => 'Prestador não encontrado.',
             'address_id.required'                => 'O endereço é obrigatório.',
             'address_id.exists'                  => 'Endereço não encontrado.',
-            'date.required_without_all'          => 'A data é obrigatória quando não há múltiplas datas ou agendamentos.',
+            'date.required_without'              => 'A data é obrigatória quando não há múltiplas datas.',
             'date.date'                          => 'Data inválida.',
             'date.after_or_equal'                => 'A data deve ser hoje ou futura.',
             'dates.array'                        => 'Datas devem ser um array.',
             'dates.*.date'                       => 'Uma das datas é inválida.',
             'dates.*.after_or_equal'             => 'Todas as datas devem ser hoje ou futuras.',
-            'schedules.array'                    => 'Agendamentos devem ser um array.',
-            'schedules.min'                      => 'É necessário pelo menos um agendamento.',
-            'schedules.*.date.required'          => 'A data do agendamento é obrigatória.',
-            'schedules.*.date.date'              => 'Data do agendamento inválida.',
-            'schedules.*.date.after_or_equal'    => 'A data do agendamento deve ser hoje ou futura.',
-            'schedules.*.period_type.required'   => 'O período do agendamento é obrigatório.',
-            'schedules.*.period_type.in'         => 'Período do agendamento inválido.',
-            'schedules.*.start_time.required'    => 'O horário de início do agendamento é obrigatório.',
-            'schedules.*.start_time.date_format' => 'Formato de horário de início inválido.',
-            'schedules.*.end_time.required'      => 'O horário de término do agendamento é obrigatório.',
-            'schedules.*.end_time.date_format'   => 'Formato de horário de término inválido.',
-            'schedules.*.end_time.after'         => 'O horário de término deve ser após o horário de início.',
-            'schedules.*.total_amount.required'  => 'O valor total do agendamento é obrigatório.',
-            'schedules.*.total_amount.numeric'   => 'O valor total deve ser numérico.',
-            'schedules.*.total_amount.min'       => 'O valor total deve ser maior ou igual a zero.',
             'period_type.required'               => 'O período é obrigatório.',
-            'period_type.required_without'       => 'O período é obrigatório quando não há agendamentos múltiplos.',
             'period_type.in'                     => 'Período inválido.',
             'start_time.required'                => 'O horário de início é obrigatório.',
-            'start_time.required_without'        => 'O horário de início é obrigatório quando não há agendamentos múltiplos.',
             'start_time.date_format'             => 'Formato de horário inválido.',
             'end_time.required'                  => 'O horário de término é obrigatório.',
-            'end_time.required_without'          => 'O horário de término é obrigatório quando não há agendamentos múltiplos.',
             'end_time.date_format'               => 'Formato de horário inválido.',
             'end_time.after'                     => 'O horário de término deve ser após o horário de início.',
+            'total_amount.required'              => 'O valor total é obrigatório.',
+            'total_amount.numeric'               => 'O valor total deve ser numérico.',
+            'total_amount.min'                   => 'O valor total deve ser maior ou igual a zero.',
             'status.in'                          => 'Status inválido.',
         ];
     }

+ 37 - 0
app/Http/Resources/CartItemResource.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
+use App\Models\CartItem;
+
+class CartItemResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @return array<string, mixed>
+     */
+    public function toArray(Request $request): array
+    {
+        return [
+            'id'          => $this->id,
+            'cart_id'     => $this->cart_id,
+            'schedule_id' => $this->schedule_id,
+            'schedule'    => new ScheduleResource($this->whenLoaded('schedule')),
+            'created_at'  => $this->created_at?->toISOString(),
+            'updated_at'  => $this->updated_at?->toISOString(),
+        ];
+    }
+
+    /**
+     * @param \Illuminate\Database\Eloquent\Collection<CartItem> $resource
+     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<CartItemResource>
+     */
+    public static function collection($resource): AnonymousResourceCollection
+    {
+        return parent::collection($resource);
+    }
+}

+ 43 - 0
app/Http/Resources/CartResource.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
+use App\Models\Cart;
+
+class CartResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @return array<string, mixed>
+     */
+    public function toArray(Request $request): array
+    {
+        return [
+            'id'           => $this->id,
+            'client_id'    => $this->client_id,
+            'status'       => $this->status?->value,
+            'schedule_ids' => $this->whenLoaded('items', fn () => $this->items->pluck('schedule_id')->values()),
+            'items'        => $this->whenLoaded('items', fn () => CartItemResource::collection($this->items)),
+
+            'schedules' => $this->whenLoaded('items', fn () => ScheduleResource::collection(
+                $this->items->pluck('schedule')->filter()->values(),
+            )),
+
+            'created_at'   => $this->created_at?->toISOString(),
+            'updated_at'   => $this->updated_at?->toISOString(),
+        ];
+    }
+
+    /**
+     * @param \Illuminate\Database\Eloquent\Collection<Cart> $resource
+     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<CartResource>
+     */
+    public static function collection($resource): AnonymousResourceCollection
+    {
+        return parent::collection($resource);
+    }
+}

+ 6 - 4
app/Http/Resources/PaymentResource.php

@@ -19,6 +19,7 @@ class PaymentResource extends JsonResource
         return [
             'id'                          => $this->id,
             'schedule_id'                 => $this->schedule_id,
+            'schedule'                    => new ScheduleResource($this->whenLoaded('schedule')),
             'client_id'                   => $this->client_id,
             'provider_id'                 => $this->provider_id,
             'client_name'                 => $this->client?->user?->name,
@@ -67,18 +68,19 @@ class PaymentResource extends JsonResource
             return null;
         }
 
-        $charge = $this->gateway_payload['charges'][0] ?? [];
-        $transaction = $charge['last_transaction'] ?? [];
+        $charge      = $this->gateway_payload['charges'][0] ?? [];
+        $transaction = $charge['last_transaction']          ?? [];
 
         $expiresAt = $charge['expires_at']
             ?? $transaction['expires_at']
             ?? $this->expires_at?->toISOString();
 
         return [
-            'qr_code'     => $transaction['qr_code'] ?? null,
+            'qr_code'     => $transaction['qr_code']     ?? null,
             'qr_code_url' => $transaction['qr_code_url'] ?? null,
             'expires_at'  => $expiresAt,
-            'status'      => $transaction['status'] ?? null,
+            'status'      => $transaction['status']      ?? null,
         ];
     }
+
 }

+ 26 - 26
app/Models/Address.php

@@ -31,32 +31,32 @@ use Illuminate\Support\Facades\DB;
  * @property float|null $longitude
  * @property-read \App\Models\City|null $city
  * @property-read \App\Models\State|null $state
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address newModelQuery()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address newQuery()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address onlyTrashed()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address query()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereAddress($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereAddressType($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereCityId($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereComplement($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereCreatedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereDeletedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereDistrict($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereHasComplement($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereId($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereInstructions($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereIsPrimary($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereLatitude($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereLongitude($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereNickname($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereNumber($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereSource($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereSourceId($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereStateId($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereUpdatedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereZipCode($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address withTrashed(bool $withTrashed = true)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Address withoutTrashed()
+ * @method static Builder<static>|Address newModelQuery()
+ * @method static Builder<static>|Address newQuery()
+ * @method static Builder<static>|Address onlyTrashed()
+ * @method static Builder<static>|Address query()
+ * @method static Builder<static>|Address whereAddress($value)
+ * @method static Builder<static>|Address whereAddressType($value)
+ * @method static Builder<static>|Address whereCityId($value)
+ * @method static Builder<static>|Address whereComplement($value)
+ * @method static Builder<static>|Address whereCreatedAt($value)
+ * @method static Builder<static>|Address whereDeletedAt($value)
+ * @method static Builder<static>|Address whereDistrict($value)
+ * @method static Builder<static>|Address whereHasComplement($value)
+ * @method static Builder<static>|Address whereId($value)
+ * @method static Builder<static>|Address whereInstructions($value)
+ * @method static Builder<static>|Address whereIsPrimary($value)
+ * @method static Builder<static>|Address whereLatitude($value)
+ * @method static Builder<static>|Address whereLongitude($value)
+ * @method static Builder<static>|Address whereNickname($value)
+ * @method static Builder<static>|Address whereNumber($value)
+ * @method static Builder<static>|Address whereSource($value)
+ * @method static Builder<static>|Address whereSourceId($value)
+ * @method static Builder<static>|Address whereStateId($value)
+ * @method static Builder<static>|Address whereUpdatedAt($value)
+ * @method static Builder<static>|Address whereZipCode($value)
+ * @method static Builder<static>|Address withTrashed(bool $withTrashed = true)
+ * @method static Builder<static>|Address withoutTrashed()
  * @mixin \Eloquent
  */
 class Address extends Model

+ 58 - 0
app/Models/Cart.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace App\Models;
+
+use App\Enums\CartStatusEnum;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
+
+/**
+ * @property CartStatusEnum $status
+ * @property-read \App\Models\Client|null $client
+ * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CartItem> $items
+ * @property-read int|null $items_count
+ * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Schedule> $schedules
+ * @property-read int|null $schedules_count
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart newModelQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart newQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart onlyTrashed()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart query()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart withTrashed(bool $withTrashed = true)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart withoutTrashed()
+ * @mixin \Eloquent
+ */
+class Cart extends Model
+{
+    use HasFactory, SoftDeletes;
+
+    protected $table = 'carts';
+
+    protected $fillable = [
+        'client_id',
+        'status',
+    ];
+
+    protected $casts = [
+        'status'     => CartStatusEnum::class,
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+        'deleted_at' => 'datetime',
+    ];
+
+    public function client()
+    {
+        return $this->belongsTo(Client::class);
+    }
+
+    public function schedules()
+    {
+        return $this->belongsToMany(Schedule::class, 'cart_items')
+            ->withTimestamps();
+    }
+
+    public function items()
+    {
+        return $this->hasMany(CartItem::class);
+    }
+}

+ 41 - 0
app/Models/CartItem.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * @property-read \App\Models\Cart|null $cart
+ * @property-read \App\Models\Schedule|null $schedule
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|CartItem newModelQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|CartItem newQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|CartItem query()
+ * @mixin \Eloquent
+ */
+class CartItem extends Model
+{
+    use HasFactory;
+
+    protected $table = 'cart_items';
+
+    protected $fillable = [
+        'cart_id',
+        'schedule_id',
+    ];
+
+    protected $casts = [
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+    ];
+
+    public function cart()
+    {
+        return $this->belongsTo(Cart::class);
+    }
+
+    public function schedule()
+    {
+        return $this->belongsTo(Schedule::class);
+    }
+}

+ 2 - 2
app/Models/Client.php

@@ -38,8 +38,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Client whereCreatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Client whereDeletedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Client whereDocument($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Client whereExternalCustomerCode($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Client whereExternalCustomerId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Client whereGatewayCustomerCode($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Client whereGatewayCustomerId($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Client whereId($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Client whereIdempotencyKey($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Client whereProfileMediaId($value)

+ 38 - 33
app/Rules/ScheduleBusinessRules.php

@@ -15,16 +15,17 @@ use Illuminate\Support\Collection;
 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
+     * @param  int      $clientId
+     * @param  int      $providerId
+     * @param  string   $date  (Y-m-d)
+     * @param  int|null $excludeScheduleId
      * @return bool
      *
      * @throws \Exception
@@ -34,7 +35,7 @@ class ScheduleBusinessRules
         $date = Carbon::parse($date);
 
         $weekStart = $date->copy()->startOfWeek(Carbon::SUNDAY);
-        $weekEnd = $date->copy()->endOfWeek(Carbon::SATURDAY);
+        $weekEnd   = $date->copy()->endOfWeek(Carbon::SATURDAY);
 
         $weeklySchedulesCount = Schedule::where('client_id', $clientId)
             ->where('provider_id', $providerId)
@@ -55,9 +56,9 @@ class ScheduleBusinessRules
     /**
      * Valida se o prestador tem horário de trabalho cadastrado para o dia da semana e período
      *
-     * @param  int  $provider_id
-     * @param  int  $day_of_week  (0 - domingo, 6 - sábado)
-     * @param  string  $period  ('morning' ou 'afternoon')
+     * @param  int    $provider_id
+     * @param  int    $day_of_week (0 - domingo, 6 - sábado)
+     * @param  string $period      ('morning' ou 'afternoon')
      * @return bool
      *
      * @throws \Exception
@@ -79,10 +80,10 @@ class ScheduleBusinessRules
     /**
      * Valida se o prestador tem bloqueio cadastrado para o dia e horário
      *
-     * @param  int  $provider_id
-     * @param  string  $date_ymd  (Y-m-d)
-     * @param  string  $start_time  (H:i:s)
-     * @param  string  $end_time  (H:i:s)
+     * @param  int    $provider_id
+     * @param  string $date_ymd   (Y-m-d)
+     * @param  string $start_time (H:i:s)
+     * @param  string $end_time   (H:i:s)
      * @return bool
      *
      * @throws \Exception
@@ -115,6 +116,7 @@ class ScheduleBusinessRules
     }
 
     // apenas para custom_schedules
+
     public static function validatePricePeriod($provider_id, $min_price, $max_price, $period_type)
     {
         if ($min_price < 0 || $max_price < 0) {
@@ -126,29 +128,30 @@ class ScheduleBusinessRules
         }
 
         $provider = Provider::find($provider_id);
+
         $min_price_proportional = 0;
         $max_price_proportional = 0;
+        $provider_price_period  = 0;
 
-        $provider_price_period = 0;
         switch ($period_type) {
             case '2': // 2 horas
-                $provider_price_period = $provider->daily_price_2h;
+                $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;
+                $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;
+                $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;
+                $provider_price_period  = $provider->daily_price_8h;
                 $min_price_proportional = $min_price;
                 $max_price_proportional = $max_price;
 
@@ -167,11 +170,11 @@ class ScheduleBusinessRules
     /**
      * Valida se o prestador tem outro agendamento no mesmo dia e horário
      *
-     * @param  int  $provider_id
-     * @param  string  $date_ymd  (Y-m-d)
-     * @param  string  $start_time  (H:i:s)
-     * @param  string  $end_time  (H:i:s)
-     * @param  int|null  $exclude_schedule_id  (id do agendamento a ser excluído da validação, usado para edição de agendamento)
+     * @param  int      $provider_id
+     * @param  string   $date_ymd   (Y-m-d)
+     * @param  string   $start_time (H:i:s)
+     * @param  string   $end_time   (H:i:s)
+     * @param  int|null $exclude_schedule_id  (id do agendamento a ser excluído da validação, usado para edição de agendamento)
      * @return bool
      *
      * @throws \Exception
@@ -204,10 +207,10 @@ class ScheduleBusinessRules
     /**
      * Valida se o prestador tem outro agendamento com o mesmo cliente no mesmo dia e horário
      *
-     * @param  int  $provider_id
-     * @param  string  $date_ymd  (Y-m-d)
-     * @param  string  $start_time  (H:i:s)
-     * @param  string  $end_time  (H:i:s)
+     * @param  int       $provider_id
+     * @param  string    $date_ymd   (Y-m-d)
+     * @param  string    $start_time (H:i:s)
+     * @param  string    $end_time   (H:i:s)
      * @param  int|null  $exclude_schedule_id  (id do agendamento a ser excluído da validação, usado para edição de agendamento)
      * @return bool
      *
@@ -231,11 +234,11 @@ class ScheduleBusinessRules
     /**
      * Valida se o prestador tem outro agendamento com o mesmo cliente no mesmo dia e horário, ignorando o horário
      *
-     * @param  int  $provider_id
-     * @param  string  $date_ymd  (Y-m-d)
-     * @param  string  $start_time  (H:i:s)
-     * @param  string  $end_time  (H:i:s)
-     * @param  int|null  $exclude_schedule_id  (id do agendamento a ser excluído da validação, usado para edição de agendamento)
+     * @param  int      $provider_id
+     * @param  string   $date_ymd   (Y-m-d)
+     * @param  string   $start_time (H:i:s)
+     * @param  string   $end_time   (H:i:s)
+     * @param  int|null $exclude_schedule_id  (id do agendamento a ser excluído da validação, usado para edição de agendamento)
      * @return bool
      *
      * @throws \Exception
@@ -322,10 +325,12 @@ class ScheduleBusinessRules
     public static function getBlockedProviderIdsForClient(int $client_id): Collection
     {
         // Prestadores que bloquearam este cliente (ProviderClientBlock)
+
         $blockedByProvider = ProviderClientBlock::where('client_id', $client_id)
             ->pluck('provider_id');
 
         // Prestadores que este cliente bloqueou (ClientProviderBlock)
+
         $blockedByClient = ClientProviderBlock::where('client_id', $client_id)
             ->pluck('provider_id');
 
@@ -352,8 +357,8 @@ class ScheduleBusinessRules
      *  2. Prestador NÃO tem ProviderBlockedDay com period = 'all' nessa data.
      *     (period = 'morning' ou 'afternoon' = bloqueio parcial → ainda disponível)
      *
-     * @param  string  $date_ymd  Y-m-d
-     * @param  Collection  $providerIds  conjunto de IDs a filtrar
+     * @param  string     $date_ymd  Y-m-d
+     * @param  Collection $providerIds  conjunto de IDs a filtrar
      */
     public static function getAvailableProviderIdsForDate(string $date_ymd, Collection $providerIds): Collection
     {

+ 52 - 0
app/Services/CartItemService.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\CartItem;
+use Illuminate\Database\Eloquent\Collection;
+
+class CartItemService
+{
+    public function getAll(): Collection
+    {
+        return CartItem::query()
+            ->orderBy('created_at', 'desc')
+            ->get();
+    }
+
+    public function findById(int $id): ?CartItem
+    {
+        return CartItem::find($id);
+    }
+
+    public function create(array $data): CartItem
+    {
+        return CartItem::create($data);
+    }
+
+    public function update(int $id, array $data): ?CartItem
+    {
+        $model = $this->findById($id);
+
+        if (!$model) {
+            return null;
+        }
+
+        $model->update($data);
+
+        return $model->fresh();
+    }
+
+    public function delete(int $id): bool
+    {
+        $model = $this->findById($id);
+
+        if (!$model) {
+            return false;
+        }
+
+        return $model->delete();
+    }
+
+    // Add custom business logic methods here
+}

+ 117 - 0
app/Services/CartService.php

@@ -0,0 +1,117 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\Cart;
+use Illuminate\Support\Arr;
+use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Support\Facades\DB;
+
+class CartService
+{
+    public function getAll(): Collection
+    {
+        return Cart::query()
+            ->with(['items.schedule'])
+            ->orderBy('created_at', 'desc')
+            ->get();
+    }
+
+    public function findById(int $id): ?Cart
+    {
+        return Cart::query()
+            ->with(['items.schedule'])
+            ->find($id);
+    }
+
+    public function create(array $data): Cart
+    {
+        return Cart::create($data);
+    }
+
+    public function createWithSchedules(array $data): Cart
+    {
+        return DB::transaction(function () use ($data) {
+            $items = $this->scheduleItems($data);
+
+            $schedules = app(ScheduleService::class)->createSingleOrMultiple(
+                baseData:  $this->baseScheduleData($data),
+                schedules: $items,
+            );
+
+            $cart = Cart::create([
+                'client_id' => $data['client_id'],
+            ]);
+
+            foreach ($schedules as $schedule) {
+                $cart->items()->create([
+                    'schedule_id' => $schedule->id,
+                ]);
+            }
+
+            return $cart->fresh(['items.schedule.client.user', 'items.schedule.provider.user', 'items.schedule.address']);
+        });
+    }
+
+    public function update(int $id, array $data): ?Cart
+    {
+        $model = $this->findById($id);
+
+        if (! $model) {
+            return null;
+        }
+
+        $model->update($data);
+
+        return $model->fresh();
+    }
+
+    public function delete(int $id): bool
+    {
+        $model = $this->findById($id);
+
+        if (! $model) {
+            return false;
+        }
+
+        return $model->delete();
+    }
+
+    //
+
+    private function baseScheduleData(array $data): array
+    {
+        return Arr::except($data, [
+            'schedules',
+            'dates',
+            'date',
+            'period_type',
+            'start_time',
+            'end_time',
+            'total_amount',
+            'offers_meal',
+        ]);
+    }
+
+    private function scheduleItems(array $data): array
+    {
+        if (! empty($data['schedules'])) {
+            return $data['schedules'];
+        }
+
+        if (! empty($data['dates'])) {
+            return array_map(
+                fn (string $date) => array_merge(
+                    Arr::only($data, ['period_type', 'start_time', 'end_time', 'total_amount', 'offers_meal']),
+                    ['date' => $date],
+                ),
+                $data['dates'],
+            );
+        }
+
+        return [
+            Arr::only($data, ['date', 'period_type', 'start_time', 'end_time', 'total_amount', 'offers_meal']),
+        ];
+    }
+
+}

+ 20 - 0
app/Services/Pagarme/Concerns/FormatsPagarmeData.php

@@ -12,6 +12,26 @@ trait FormatsPagarmeData
         return preg_replace('/\D+/', '', (string) $value) ?? '';
     }
 
+    protected function customerDocument(?string $value): string
+    {
+        return trim((string) $value);
+    }
+
+    protected function customerDocumentType(string $document): string
+    {
+        if (preg_match('/[A-Za-z]/', $document) === 1) {
+            return 'PASSPORT';
+        }
+
+        $length = strlen($this->digits($document));
+
+        return match ($length) {
+            11      => 'CPF',
+            14      => 'CNPJ',
+            default => 'PASSPORT',
+        };
+    }
+
     protected function extractAddressParts(array $data): array
     {
         $addressLine   = trim((string) ($data['address'] ?? ''));

+ 4 - 5
app/Services/Pagarme/PagarmeCustomerService.php

@@ -28,9 +28,8 @@ class PagarmeCustomerService
         $email = $client->user?->email ?? $data['email'] ?? null;
         $code  = $client->ensureGatewayCode();
 
-        $document = $this->digits($client->document ?? $data['document'] ?? null);
-
-        $documentLen = strlen($document);
+        $document     = $this->customerDocument($client->document ?? $data['document'] ?? null);
+        $documentType = $this->customerDocumentType($document);
 
         $address = $this->buildAddressData($data);
         $phones  = $this->buildPhones($client->user?->phone ?? $data['phone'] ?? null);
@@ -39,8 +38,8 @@ class PagarmeCustomerService
             name:         $name,
             email:        (string) $email,
             document:     $document,
-            type:         $documentLen === 14 ? 'company' : 'individual',
-            documentType: $documentLen === 14 ? 'CNPJ' : 'CPF',
+            type:         $documentType === 'CNPJ' ? 'company' : 'individual',
+            documentType: $documentType,
             code:         $code,
             address:      $address,
             phones:       $phones,

+ 4 - 3
app/Services/Pagarme/PagarmePaymentService.php

@@ -294,7 +294,8 @@ class PagarmePaymentService
             throw new \InvalidArgumentException('Endereco do agendamento nao encontrado para criar pedido no Pagar.me.');
         }
 
-        $document = $this->digits($client->document);
+        $document     = $this->customerDocument($client->document);
+        $documentType = $this->customerDocumentType($document);
 
         $phone = $this->buildPhonePayload($user->phone)
             ?: $this->buildPhonePayload($options['phone'] ?? null);
@@ -347,8 +348,8 @@ class PagarmePaymentService
             name:         $user->name,
             email:        $user->email,
             document:     $document,
-            type:         strlen($document) === 14 ? 'company' : 'individual',
-            documentType: strlen($document) === 14 ? 'CNPJ' : 'CPF',
+            type:         $documentType === 'CNPJ' ? 'company' : 'individual',
+            documentType: $documentType,
             code:         $client->ensureGatewayCode(),
             address:      $customerAddress,
             phones:       $customerPhones,

+ 158 - 14
app/Services/PaymentService.php

@@ -2,15 +2,19 @@
 
 namespace App\Services;
 
+use App\Enums\CartStatusEnum;
 use App\Enums\PaymentSplitStatusEnum;
 use App\Enums\PaymentStatusEnum;
+use App\Models\Cart;
 use App\Models\ClientPaymentMethod;
 use App\Models\Payment;
 use App\Models\PaymentSplit;
 use App\Models\Schedule;
 use App\Services\Pagarme\PagarmePaymentService;
 use Carbon\Carbon;
+use Illuminate\Auth\Access\AuthorizationException;
 use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Support\Collection as SupportCollection;
 use Illuminate\Support\Str;
 
 class PaymentService
@@ -22,7 +26,7 @@ class PaymentService
     public function getAll(): Collection
     {
         return Payment::query()
-            ->with(['client.user', 'provider.user'])
+            ->with(['client.user', 'provider.user', 'schedule'])
             ->orderBy('created_at', 'desc')
             ->get();
     }
@@ -30,7 +34,7 @@ class PaymentService
     public function findById(int $id): ?Payment
     {
         return Payment::query()
-            ->with(['client.user', 'provider.user'])
+            ->with(['client.user', 'provider.user', 'schedule'])
             ->find($id);
     }
 
@@ -63,6 +67,8 @@ class PaymentService
         return $model->delete();
     }
 
+    //
+
     public function platformFees(): array
     {
         return $this->pagarmePaymentService->platformFeeRates();
@@ -71,10 +77,7 @@ class PaymentService
     //
 
     public function payAcceptedSchedule(
-        Schedule $schedule,
-        string $paymentMethod,
-        ?int $clientPaymentMethodId = null,
-        array $options = []
+        Schedule $schedule, string $paymentMethod, ?int $clientPaymentMethodId = null, array $options = []
     ): Payment {
         $schedule->loadMissing(['client', 'provider', 'customSchedule.serviceType']);
 
@@ -138,8 +141,7 @@ class PaymentService
         }
 
         $clientPaymentMethod = null;
-
-        $cardId = null;
+        $cardId              = null;
 
         if ($paymentMethod === 'credit_card') {
             if (! $clientPaymentMethodId && empty($options['card_id'])) {
@@ -215,11 +217,11 @@ class PaymentService
 
         try {
             $orderResponse = $this->pagarmePaymentService->processPayment(
-                payment: $payment,
-                schedule: $schedule,
+                payment:       $payment,
+                schedule:      $schedule,
                 paymentMethod: $paymentMethod,
-                cardId: $cardId,
-                options: $options,
+                cardId:        $cardId,
+                options:       $options,
             );
         } catch (\Throwable $e) {
             $payment->forceFill([
@@ -242,6 +244,38 @@ class PaymentService
         return $payment;
     }
 
+    public function payCart(array $data, int $userId): SupportCollection
+    {
+        $cart = Cart::query()
+            ->with(['items.schedule.client', 'items.schedule.provider'])
+            ->findOrFail($data['cart_id']);
+
+        $schedules = $this->cartSchedules($cart);
+
+        if ($schedules->contains(fn (Schedule $schedule) => $schedule->client?->user_id !== $userId)) {
+            throw new AuthorizationException;
+        }
+
+        $this->validateCartSchedules($schedules, $data['payment_method']);
+
+        $payments = $schedules->map(fn (Schedule $schedule) => $this->payAcceptedSchedule(
+            schedule:              $schedule,
+            paymentMethod:         $data['payment_method'],
+            clientPaymentMethodId: $data['client_payment_method_id'] ?? null,
+
+            options: [
+                'phone'   => $data['phone']   ?? null,
+                'card_id' => $data['card_id'] ?? null,
+            ],
+        ));
+
+        $this->syncCartStatusAfterPayments($cart);
+
+        return $payments;
+    }
+
+    //
+
     public function getOrCreatePixPayment(Schedule $schedule): Payment
     {
         $existingPayment = Payment::query()
@@ -320,10 +354,120 @@ class PaymentService
 
     public function syncScheduleStatusAfterPayment(Schedule $schedule, Payment $payment): void
     {
-        if ($payment->status !== PaymentStatusEnum::PAID || $schedule->status === 'paid') {
+        if ($payment->status !== PaymentStatusEnum::PAID) {
             return;
         }
 
-        $schedule->update(['status' => 'paid']);
+        if ($schedule->status !== 'paid') {
+            $schedule->update(['status' => 'paid']);
+        }
+
+        $this->syncCartsForSchedule($schedule);
+    }
+
+    private function validateCartSchedules(SupportCollection $schedules, string $paymentMethod): void
+    {
+        if ($schedules->isEmpty()) {
+            throw new \InvalidArgumentException('Carrinho precisa ter ao menos um agendamento.');
+        }
+
+        if (! in_array($paymentMethod, ['credit_card', 'pix'], true)) {
+            throw new \InvalidArgumentException('Forma de pagamento invalida.');
+        }
+
+        $clientIds = $schedules->pluck('client_id')->unique()->values();
+
+        if ($clientIds->count() !== 1) {
+            throw new \InvalidArgumentException('Todos os agendamentos do carrinho precisam ser do mesmo cliente.');
+        }
+
+        $schedules->each(function (Schedule $schedule): void {
+            $schedule->loadMissing(['client', 'provider', 'customSchedule.serviceType']);
+
+            if ($schedule->status !== 'accepted') {
+                throw new \InvalidArgumentException("Agendamento {$schedule->id} precisa estar aceito para ser pago.");
+            }
+
+            if (! $schedule->provider_id || ! $schedule->provider) {
+                throw new \InvalidArgumentException("Agendamento {$schedule->id} precisa ter prestador confirmado para gerar pagamento.");
+            }
+
+            if ((float) $schedule->total_amount <= 0) {
+                throw new \InvalidArgumentException("Agendamento {$schedule->id} precisa ter valor maior que zero para gerar pagamento.");
+            }
+
+            if (empty($schedule->provider->recipient_id)) {
+                throw new \InvalidArgumentException("Prestador do agendamento {$schedule->id} precisa ter recipient_id do Pagar.me para receber split.");
+            }
+
+            $existingPayment = Payment::query()
+                ->where('schedule_id', $schedule->id)
+                ->whereIn('status', [
+                    PaymentStatusEnum::PENDING->value,
+                    PaymentStatusEnum::PROCESSING->value,
+                    PaymentStatusEnum::AUTHORIZED->value,
+                    PaymentStatusEnum::PAID->value,
+                ])
+                ->latest('id')
+                ->first();
+
+            if ($existingPayment) {
+                throw new \InvalidArgumentException("Ja existe um pagamento em andamento para o agendamento {$schedule->id}.");
+            }
+        });
+    }
+
+    //
+
+    private function cartSchedules(Cart $cart): SupportCollection
+    {
+        $schedules = $cart->items
+            ->map(fn ($item) => $item->schedule)
+            ->filter()
+            ->values();
+
+        if ($schedules->isEmpty() || $schedules->count() !== $cart->items->count()) {
+            throw new \InvalidArgumentException('Um ou mais agendamentos nao foram encontrados.');
+        }
+
+        return $schedules;
+    }
+
+    private function syncCartsForSchedule(Schedule $schedule): void
+    {
+        Cart::query()
+            ->whereHas('items', fn ($query) => $query->where('schedule_id', $schedule->id))
+            ->with('items')
+            ->get()
+            ->each(fn (Cart $cart) => $this->syncCartStatusAfterPayments($cart));
+    }
+
+    private function syncCartStatusAfterPayments(Cart $cart): void
+    {
+        $cart->loadMissing('items');
+
+        $scheduleIds = $cart->items
+            ->pluck('schedule_id')
+            ->filter()
+            ->unique()
+            ->values();
+
+        if ($scheduleIds->isEmpty()) {
+            return;
+        }
+
+        $paidSchedulesCount = Payment::query()
+            ->whereIn('schedule_id', $scheduleIds)
+            ->where('status', PaymentStatusEnum::PAID->value)
+            ->distinct('schedule_id')
+            ->count('schedule_id');
+
+        if ($paidSchedulesCount !== $scheduleIds->count()) {
+            return;
+        }
+
+        if ($cart->status !== CartStatusEnum::PAID) {
+            $cart->update(['status' => CartStatusEnum::PAID->value]);
+        }
     }
 }

+ 217 - 243
app/Services/ScheduleService.php

@@ -25,15 +25,6 @@ class ScheduleService
             ->get();
     }
 
-    public function getFinished()
-    {
-        return Schedule::with(['client.user', 'provider.user'])
-            ->where('status', 'finished')
-            ->orderBy('date', 'desc')
-            ->orderBy('start_time', 'desc')
-            ->get();
-    }
-
     public function getById($id)
     {
         return Schedule::with(['client.user', 'provider.user', 'address'])->findOrFail($id);
@@ -42,13 +33,11 @@ class ScheduleService
     public function createSingleOrMultiple(array $baseData, array $schedules)
     {
         try {
-
             DB::beginTransaction();
 
             $createdSchedules = [];
 
             foreach ($schedules as $schedule) {
-
                 $datasMerged = array_merge($baseData, $schedule);
 
                 $this->validateProviderAvailability($datasMerged, null);
@@ -59,23 +48,18 @@ class ScheduleService
 
                 $newSchedule = Schedule::create($scheduleData);
 
-                /* NOTIFICAÇÃO PRESTADOR */
-                if ($newSchedule->provider_id) {
+                // NOTIFICAÇÃO PRESTADOR
 
+                if ($newSchedule->provider_id) {
                     $notificationService = app(NotificationService::class);
 
                     $notificationService->create([
-                        'title' => 'Nova solicitação de diária!',
-
+                        'title'       => 'Nova solicitação de diária!',
                         'description' => 'Você recebeu uma nova solicitação de diária.',
-
-                        'origin' => 'schedule',
-
-                        'origin_id' => $newSchedule->id,
-
-                        'type' => NotificationTypeEnum::SCHEDULE_PROVIDER_CLIENT_NEW_SOLICITATION->value,
-
-                        'user_id' => $newSchedule->provider->user_id,
+                        'origin'      => 'schedule',
+                        'origin_id'   => $newSchedule->id,
+                        'type'        => NotificationTypeEnum::SCHEDULE_PROVIDER_CLIENT_NEW_SOLICITATION->value,
+                        'user_id'     => $newSchedule->provider->user_id,
                     ]);
                 }
 
@@ -84,7 +68,6 @@ class ScheduleService
 
             DB::commit();
         } catch (\Exception $e) {
-
             DB::rollBack();
 
             throw new \Exception(__($e->getMessage()));
@@ -100,7 +83,9 @@ class ScheduleService
         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);
         }
 
@@ -124,84 +109,55 @@ class ScheduleService
         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)
+    public function getClientProviderBlocks(int $clientId, int $providerId): array
     {
-        $provider_id = $data['provider_id'];
-        $client_id = $data['client_id'];
-
-        $date = Carbon::parse($data['date']);
-        $dayOfWeek = $date->dayOfWeek;
-        $startTime = $data['start_time'];
-        $endTime = $data['end_time'];
-        $date_ymd = $date->format('Y-m-d');
-        $period = $startTime < '13:00:00' ? 'morning' : 'afternoon';
-
-        // bloqueio 2 schedules por semana para o mesmo client e provider
-        ScheduleBusinessRules::validateWeeklyScheduleLimit(
-            $client_id,
-            $provider_id,
-            $data['date'],
-            $excludeScheduleId
-        );
-
-        // bloqueio provider trabalha no dia/periodo
-        ScheduleBusinessRules::validateWorkingDay(
-            $provider_id,
-            $dayOfWeek,
-            $period
-        );
-
-        // bloqueio provider tem blockedday para dia/hora
-        ScheduleBusinessRules::validateBlockedDay(
-            $provider_id,
-            $date->format('Y-m-d'),
-            $startTime,
-            $endTime
-        );
+        $today = Carbon::today()->format('Y-m-d');
 
-        // bloqueio provider tem outro agendamento para dia/hora
-        ScheduleBusinessRules::validateConflictingSchedule(
-            $provider_id,
-            $date->format('Y-m-d'),
-            $startTime,
-            $endTime,
-            $excludeScheduleId
-        );
+        $schedules = Schedule::where('client_id', $clientId)
+            ->where('provider_id', $providerId)
+            ->whereNotIn('status', self::EXCLUDED_STATUSES)
+            ->whereDate('date', '>=', $today)
+            ->orderBy('date')
+            ->orderBy('start_time')
+            ->get(['id', 'date', 'start_time', 'end_time', 'status']);
 
-        // bloqueio provider tem outra proposta na mesma data
-        ScheduleBusinessRules::validateConflictingProposalSameDate(
-            $provider_id,
-            $date_ymd,
-            $startTime,
-            $endTime,
-            null
-        );
+        $existingSchedules = $schedules->map(function ($schedule) {
+            return [
+                'id'         => $schedule->id,
+                'date'       => Carbon::parse($schedule->date)->format('Y-m-d'),
+                'start_time' => $schedule->start_time,
+                'end_time'   => $schedule->end_time,
+                'status'     => $schedule->status,
+            ];
+        })->values();
 
-        // bloqueio caso o client tenha bloqueado o provider
-        ScheduleBusinessRules::validateClientNotBlockedByProvider(
-            $client_id,
-            $provider_id
-        );
+        $fullyBlockedWeeks = $schedules
+            ->groupBy(function ($schedule) {
+                return Carbon::parse($schedule->date)
+                    ->startOfWeek(Carbon::SUNDAY)
+                    ->format('Y-m-d');
+            })
+            ->filter(function ($weekSchedules) {
+                return $weekSchedules->count() >= 2;
+            })
+            ->keys()
+            ->values();
 
-        // bloqueio caso o provider tenha bloqueado o client
-        ScheduleBusinessRules::validateProviderNotBlockedByClient(
-            $client_id,
-            $provider_id
-        );
+        return [
+            'existing_schedules'  => $existingSchedules,
+            'fully_blocked_weeks' => $fullyBlockedWeeks,
+        ];
+    }
 
-        return true;
+    public function getFinished()
+    {
+        return Schedule::with(['client.user', 'provider.user'])
+            ->where('status', 'finished')
+            ->orderBy('date', 'desc')
+            ->orderBy('start_time', 'desc')
+            ->get();
     }
 
     public function getSchedulesDefaultGroupedByClient()
@@ -220,7 +176,8 @@ class ScheduleService
             return [
                 'client_id'   => $firstSchedule->client_id,
                 'client_name' => $firstSchedule->client->user->name ?? 'N/A',
-                'schedules'   => $clientSchedules->map(function ($schedule) {
+
+                'schedules' => $clientSchedules->map(function ($schedule) {
                     return [
                         'id'            => $schedule->id,
                         'date'          => $schedule->date ? Carbon::parse($schedule->date)->format('d/m/Y') : null,
@@ -234,16 +191,19 @@ class ScheduleService
                         'client_id'     => $schedule->client_id,
                         'provider_id'   => $schedule->provider_id,
                         'provider_name' => $schedule->provider->user->name ?? 'N/A',
-                        'address'       => $schedule->address ? [
+
+                        '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 ?? '',
+                            'city'       => $schedule->address->city->name        ?? '',
                             'state'      => $schedule->address->city->state->name ?? '',
                         ] : null,
+
                         'client_name' => $schedule->client->user->name ?? 'N/A',
-                        'reviews'     => $schedule->reviews->map(function ($review) {
+
+                        'reviews' => $schedule->reviews->map(function ($review) {
                             return [
                                 'id'           => $review->id,
                                 'stars'        => $review->stars,
@@ -252,6 +212,7 @@ class ScheduleService
                                 'origin_id'    => $review->origin_id,
                                 'created_at'   => Carbon::parse($review->created_at)->format('Y-m-d H:i'),
                                 'updated_at'   => Carbon::parse($review->updated_at)->format('Y-m-d H:i'),
+
                                 'improvements' => $review->reviewsImprovements->map(function ($ri) {
                                     return [
                                         'id'                    => $ri->id,
@@ -269,10 +230,46 @@ class ScheduleService
         return $grouped;
     }
 
+    //
+
+    public function cancelWithReason(int $id, string $cancelText)
+    {
+        try {
+            DB::beginTransaction();
+
+            $schedule = Schedule::findOrFail($id);
+
+            $allowedStatuses = ['accepted', 'paid', 'pending'];
+
+            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.');
+        }
+    }
+
     public function updateStatus($id, string $status)
     {
         try {
             DB::beginTransaction();
+
             $schedule = Schedule::findOrFail($id);
 
             $allowedTransitions = [
@@ -298,73 +295,54 @@ class ScheduleService
             $schedule->update(['status' => $status]);
 
             switch ($status) {
-
                 case 'pending':
-
                     break;
 
                 case 'accepted':
-
                     $notificationService = app(NotificationService::class);
 
                     // CLIENTE
-                    $notificationService->create([
-                        'title' => 'Agendamento aceito!',
-
-                        'description' => $schedule->provider->user->name.
-                            ' aceitou sua solicitação de diária.',
 
-                        'origin' => 'schedule',
-
-                        'origin_id' => $schedule->id,
-
-                        'type' => NotificationTypeEnum::SCHEDULE_CLIENT_PROVIDER_ACCEPTED->value,
-
-                        'user_id' => $schedule->client->user_id,
+                    $notificationService->create([
+                        'title'       => 'Agendamento aceito!',
+                        'description' => $schedule->provider->user->name. ' aceitou sua solicitação de diária.',
+                        'origin'      => 'schedule',
+                        'origin_id'   => $schedule->id,
+                        'type'        => NotificationTypeEnum::SCHEDULE_CLIENT_PROVIDER_ACCEPTED->value,
+                        'user_id'     => $schedule->client->user_id,
                     ]);
 
                     break;
 
                 case 'rejected':
-
                     $notificationService = app(NotificationService::class);
 
                     // CLIENTE
-                    $notificationService->create([
-                        'title' => 'Agendamento recusado!',
 
+                    $notificationService->create([
+                        'title'       => 'Agendamento recusado!',
                         'description' => 'O diarista não poderá atender. Veja outros profissionais disponíveis.',
-
-                        'origin' => 'schedule',
-
-                        'origin_id' => $schedule->id,
-
-                        'type' => NotificationTypeEnum::SCHEDULE_CLIENT_PROVIDER_REFUSED->value,
-
-                        'user_id' => $schedule->client->user_id,
+                        'origin'      => 'schedule',
+                        'origin_id'   => $schedule->id,
+                        'type'        => NotificationTypeEnum::SCHEDULE_CLIENT_PROVIDER_REFUSED->value,
+                        'user_id'     => $schedule->client->user_id,
                     ]);
 
                     break;
 
                 case 'paid':
-
                     $notificationService = app(NotificationService::class);
 
                     // PRESTADOR
-                    if ($schedule->provider_id) {
 
+                    if ($schedule->provider_id) {
                         $notificationService->create([
-                            'title' => 'Pagamento confirmado!',
-
+                            'title'       => 'Pagamento confirmado!',
                             'description' => 'O cliente confirmou o pagamento da diária.',
-
-                            'origin' => 'schedule',
-
-                            'origin_id' => $schedule->id,
-
-                            'type' => NotificationTypeEnum::SCHEDULE_PROVIDER_START->value,
-
-                            'user_id' => $schedule->provider->user_id,
+                            'origin'      => 'schedule',
+                            'origin_id'   => $schedule->id,
+                            'type'        => NotificationTypeEnum::SCHEDULE_PROVIDER_START->value,
+                            'user_id'     => $schedule->provider->user_id,
                         ]);
                     }
 
@@ -381,47 +359,36 @@ class ScheduleService
                     break;
 
                 case 'cancelled':
-
                     $notificationService = app(NotificationService::class);
 
                     switch (Auth::user()->type) {
                         case 'client':
-
                             // PRESTADOR
-                            if ($schedule->provider_id) {
 
-                                $notificationService->create(['title' => 'Agendamento cancelado!',
+                            if ($schedule->provider_id) {
 
+                                $notificationService->create([
+                                    'title'       => 'Agendamento cancelado!',
                                     'description' => 'O cliente cancelou a diária.',
-
-                                    'origin' => 'schedule',
-
-                                    'origin_id' => $schedule->id,
-
-                                    'type' => NotificationTypeEnum::SCHEDULE_PROVIDER_CLIENT_CANCELLED->value,
-
-                                    'user_id' => $schedule->provider->user_id,
+                                    'origin'      => 'schedule',
+                                    'origin_id'   => $schedule->id,
+                                    'type'        => NotificationTypeEnum::SCHEDULE_PROVIDER_CLIENT_CANCELLED->value,
+                                    'user_id'     => $schedule->provider->user_id,
                                 ]);
                             }
 
                             break;
 
                         case 'provider':
-
                             // CLIENTE
-                            $notificationService->create([
-                                'title' => 'Agendamento cancelado!',
-
-                                'description' => $schedule->provider->user->name.
-                                    ' cancelou a diária.',
-
-                                'origin' => 'schedule',
-
-                                'origin_id' => $schedule->id,
-
-                                'type' => NotificationTypeEnum::SCHEDULE_CLIENT_PROVIDER_CANCELLED->value,
 
-                                'user_id' => $schedule->client->user_id,
+                            $notificationService->create([
+                                'title'       => 'Agendamento cancelado!',
+                                'description' => $schedule->provider->user->name. ' cancelou a diária.',
+                                'origin'      => 'schedule',
+                                'origin_id'   => $schedule->id,
+                                'type'        => NotificationTypeEnum::SCHEDULE_CLIENT_PROVIDER_CANCELLED->value,
+                                'user_id'     => $schedule->client->user_id,
                             ]);
 
                             break;
@@ -433,45 +400,33 @@ class ScheduleService
                     break;
 
                 case 'started':
-
                     $notificationService = app(NotificationService::class);
 
                     // CLIENTE
-                    $notificationService->create([
-                        'title' => 'Diarista a caminho!',
-
-                        'description' => 'Informe o código '.
-                            $schedule->code.
-                            ' para confirmar a chegada da diarista e liberar o início do serviço.',
-
-                        'origin' => 'schedule',
-
-                        'origin_id' => $schedule->id,
 
-                        'type' => NotificationTypeEnum::SCHEDULE_CLIENT_PROVIDER_COMING->value,
-
-                        'user_id' => $schedule->client->user_id,
+                    $notificationService->create([
+                        'title'       => 'Diarista a caminho!',
+                        'description' => 'Informe o código '. $schedule->code. ' para confirmar a chegada da diarista e liberar o início do serviço.',
+                        'origin'      => 'schedule',
+                        'origin_id'   => $schedule->id,
+                        'type'        => NotificationTypeEnum::SCHEDULE_CLIENT_PROVIDER_COMING->value,
+                        'user_id'     => $schedule->client->user_id,
                     ]);
 
                     break;
 
                 case 'finished':
-
                     $notificationService = app(NotificationService::class);
 
                     // CLIENTE
-                    $notificationService->create([
-                        'title' => 'Diária finalizada!',
 
+                    $notificationService->create([
+                        'title'       => 'Diária finalizada!',
                         'description' => 'Avalie o serviço feito pelo diarista e conte-nos como foi sua experiência.',
-
-                        'origin' => 'schedule',
-
-                        'origin_id' => $schedule->id,
-
-                        'type' => NotificationTypeEnum::SCHEDULE_CLIENT_PROVIDER_FINISHED->value,
-
-                        'user_id' => $schedule->client->user_id,
+                        'origin'      => 'schedule',
+                        'origin_id'   => $schedule->id,
+                        'type'        => NotificationTypeEnum::SCHEDULE_CLIENT_PROVIDER_FINISHED->value,
+                        'user_id'     => $schedule->client->user_id,
                     ]);
 
                     break;
@@ -482,81 +437,100 @@ class ScheduleService
             return $schedule->fresh(['client.user', 'provider.user', 'address']);
         } catch (\Exception $e) {
             DB::rollBack();
+
             Log::error('Erro ao atualizar status do agendamento: '.$e->getMessage());
+
             throw new \Exception('Não foi possível atualizar o status do agendamento.');
         }
     }
 
-    public function cancelWithReason(int $id, string $cancelText)
+    //
+
+    private function calculateAmount(Provider $provider, string $periodType): float
     {
-        try {
-            DB::beginTransaction();
+        $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,
+        ];
 
-            $schedule = Schedule::findOrFail($id);
+        return $hourlyRates[$periodType] ?? 0;
+    }
 
-            $allowedStatuses = ['accepted', 'paid', 'pending'];
+    private function validateProviderAvailability(array $data, $excludeScheduleId = null)
+    {
+        $provider_id = $data['provider_id'];
+        $client_id   = $data['client_id'];
 
-            if (! in_array($schedule->status, $allowedStatuses)) {
-                throw new \Exception("Cancelamento não permitido para o status atual: {$schedule->status}");
-            }
+        $date = Carbon::parse($data['date']);
 
-            $cancelled_by = Auth::user()->type;
+        $dayOfWeek = $date->dayOfWeek;
+        $startTime = $data['start_time'];
+        $endTime   = $data['end_time'];
+        $date_ymd  = $date->format('Y-m-d');
+        $period    = $startTime < '13:00:00' ? 'morning' : 'afternoon';
 
-            $schedule->update([
-                'status'       => 'cancelled',
-                'cancel_text'  => $cancelText,
-                'cancelled_by' => $cancelled_by,
-            ]);
+        // bloqueio 2 schedules por semana para o mesmo client e provider
 
-            DB::commit();
+        ScheduleBusinessRules::validateWeeklyScheduleLimit(
+            $client_id,
+            $provider_id,
+            $data['date'],
+            $excludeScheduleId
+        );
 
-            return $schedule->fresh(['client.user', 'provider.user', 'address']);
-        } catch (\Exception $e) {
-            DB::rollBack();
+        // bloqueio provider trabalha no dia/periodo
 
-            Log::error('Erro ao cancelar agendamento: '.$e->getMessage());
+        ScheduleBusinessRules::validateWorkingDay(
+            $provider_id,
+            $dayOfWeek,
+            $period
+        );
 
-            throw new \Exception('Não foi possível cancelar o agendamento.');
-        }
-    }
+        // bloqueio provider tem blockedday para dia/hora
 
-    public function getClientProviderBlocks(int $clientId, int $providerId): array
-    {
-        $today = Carbon::today()->format('Y-m-d');
+        ScheduleBusinessRules::validateBlockedDay(
+            $provider_id,
+            $date->format('Y-m-d'),
+            $startTime,
+            $endTime
+        );
 
-        $schedules = Schedule::where('client_id', $clientId)
-            ->where('provider_id', $providerId)
-            ->whereNotIn('status', self::EXCLUDED_STATUSES)
-            ->whereDate('date', '>=', $today)
-            ->orderBy('date')
-            ->orderBy('start_time')
-            ->get(['id', 'date', 'start_time', 'end_time', 'status']);
+        // bloqueio provider tem outro agendamento para dia/hora
 
-        $existingSchedules = $schedules->map(function ($schedule) {
-            return [
-                'id'         => $schedule->id,
-                'date'       => Carbon::parse($schedule->date)->format('Y-m-d'),
-                'start_time' => $schedule->start_time,
-                'end_time'   => $schedule->end_time,
-                'status'     => $schedule->status,
-            ];
-        })->values();
+        ScheduleBusinessRules::validateConflictingSchedule(
+            $provider_id,
+            $date->format('Y-m-d'),
+            $startTime,
+            $endTime,
+            $excludeScheduleId
+        );
 
-        $fullyBlockedWeeks = $schedules
-            ->groupBy(function ($schedule) {
-                return Carbon::parse($schedule->date)
-                    ->startOfWeek(Carbon::SUNDAY)
-                    ->format('Y-m-d');
-            })
-            ->filter(function ($weekSchedules) {
-                return $weekSchedules->count() >= 2;
-            })
-            ->keys()
-            ->values();
+        // bloqueio provider tem outra proposta na mesma data
 
-        return [
-            'existing_schedules'  => $existingSchedules,
-            'fully_blocked_weeks' => $fullyBlockedWeeks,
-        ];
+        ScheduleBusinessRules::validateConflictingProposalSameDate(
+            $provider_id,
+            $date_ymd,
+            $startTime,
+            $endTime,
+            null
+        );
+
+        // bloqueio caso o client tenha bloqueado o provider
+
+        ScheduleBusinessRules::validateClientNotBlockedByProvider(
+            $client_id,
+            $provider_id
+        );
+
+        // bloqueio caso o provider tenha bloqueado o client
+
+        ScheduleBusinessRules::validateProviderNotBlockedByClient(
+            $client_id,
+            $provider_id
+        );
+
+        return true;
     }
 }

+ 5 - 1
app/Services/WebhookService.php

@@ -94,7 +94,11 @@ class WebhookService
         try {
             $payment = $this->pagarmePaymentService->applyGatewayResponseToPayment($payment, $orderResponse);
 
-            $this->paymentService->syncScheduleStatusAfterPayment($payment->schedule, $payment);
+            $payment->loadMissing('schedule');
+
+            if ($payment->schedule) {
+                $this->paymentService->syncScheduleStatusAfterPayment($payment->schedule, $payment);
+            }
 
             $webhook->update([
                 'payment_id'   => $payment->id,

+ 27 - 0
database/migrations/2026_06_19_181942_create_carts_table.php

@@ -0,0 +1,27 @@
+<?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::create('carts', function (Blueprint $table) {
+            $table->id();
+            $table->foreignId('client_id')->constrained('clients')->onDelete('cascade');
+            $table->string('status')->default('open');
+
+            $table->timestamps();
+            $table->softDeletes();
+
+            $table->index('client_id');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('carts');
+    }
+};

+ 27 - 0
database/migrations/2026_06_19_190028_create_cart_items_table.php

@@ -0,0 +1,27 @@
+<?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::create('cart_items', function (Blueprint $table) {
+            $table->id();
+            $table->foreignId('cart_id')->constrained('carts')->onDelete('cascade');
+            $table->foreignId('schedule_id')->constrained('schedules')->onDelete('cascade');
+
+            $table->timestamps();
+
+            $table->unique(['cart_id', 'schedule_id']);
+            $table->index('schedule_id');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('cart_items');
+    }
+};

+ 71 - 70
database/seeders/PermissionSeeder.php

@@ -16,55 +16,24 @@ class PermissionSeeder extends Seeder
     {
         // Criação de Permissões
         /*
-        view = 1
-        add = 2
-        edit = 4
+        view   = 1
+        add    = 2
+        edit   = 4
         delete = 8
-        print = 16
+        print  = 16
         export = 32
         import = 64
-        limit = 128
-        menu = 256
+        limit  = 128
+        menu   = 256
 
         para cada cada bit selecionado se faz a soma dos valores.
         */
         $permissions = [
-            [
-                'scope'       => 'dashboard',
-                'description' => 'Dashboard',
-                'bits'        => 511,
-                'children'    => [],
-            ],
-            [
-                'scope'       => 'payment',
-                'description' => 'Pagamentos',
-                'bits'        => 257,
-                'children'    => [
-                    [
-                        'scope'       => 'payment-split',
-                        'description' => 'Splits de Pagamento',
-                        'bits'        => 1,
-                        'children'    => [],
-                    ],
-                ],
-            ],
             [
                 'scope'       => 'config',
                 'description' => 'Configurações',
                 'bits'        => 271,
                 'children'    => [
-                    [
-                        'scope'       => 'config.user',
-                        'description' => 'Configurações de Usuários',
-                        'bits'        => 271,
-                        'children'    => [],
-                    ],
-                    [
-                        'scope'       => 'config.permission',
-                        'description' => 'Configurações de Permissões',
-                        'bits'        => 271,
-                        'children'    => [],
-                    ],
                     [
                         'scope'       => 'config.address',
                         'description' => 'Configurações de Endereços',
@@ -90,8 +59,8 @@ class PermissionSeeder extends Seeder
                         'children'    => [],
                     ],
                     [
-                        'scope'       => 'config.provider_client_block',
-                        'description' => 'Clientes Bloqueados do Prestador',
+                        'scope'       => 'config.client_payment_method',
+                        'description' => 'Configurações de Métodos de Pagamento do Cliente',
                         'bits'        => 271,
                         'children'    => [],
                     ],
@@ -102,14 +71,8 @@ class PermissionSeeder extends Seeder
                         'children'    => [],
                     ],
                     [
-                        'scope'       => 'config.client_payment_method',
-                        'description' => 'Configurações de Métodos de Pagamento do Cliente',
-                        'bits'        => 271,
-                        'children'    => [],
-                    ],
-                    [
-                        'scope'       => 'config.schedule',
-                        'description' => 'Agendamentos',
+                        'scope'       => 'config.country',
+                        'description' => 'Configurações de Países',
                         'bits'        => 271,
                         'children'    => [],
                     ],
@@ -120,38 +83,38 @@ class PermissionSeeder extends Seeder
                         'children'    => [],
                     ],
                     [
-                        'scope'       => 'config.schedule_proposal',
-                        'description' => 'Propostas de Agendamento',
+                        'scope'       => 'config.improvement_type',
+                        'description' => 'Configurações de Tipos de Melhoria',
                         'bits'        => 271,
                         'children'    => [],
                     ],
                     [
-                        'scope'       => 'config.schedule_refuse',
-                        'description' => 'Recusas de Agendamento',
+                        'scope'       => 'config.media',
+                        'description' => 'Configurações de Mídia',
                         'bits'        => 271,
                         'children'    => [],
                     ],
                     [
-                        'scope'       => 'config.country',
-                        'description' => 'Configurações de Países',
+                        'scope'       => 'config.permission',
+                        'description' => 'Configurações de Permissões',
                         'bits'        => 271,
                         'children'    => [],
                     ],
                     [
-                        'scope'       => 'config.state',
-                        'description' => 'Configurações de Estados',
+                        'scope'       => 'config.provider',
+                        'description' => 'Configurações de Prestadores',
                         'bits'        => 271,
                         'children'    => [],
                     ],
                     [
-                        'scope'       => 'config.provider',
-                        'description' => 'Configurações de Prestadores',
+                        'scope'       => 'config.provider_blocked_day',
+                        'description' => 'Configurações de Dias Bloqueados do Prestador',
                         'bits'        => 271,
                         'children'    => [],
                     ],
                     [
-                        'scope'       => 'config.provider_speciality',
-                        'description' => 'Configurações de Especialidades do Prestador',
+                        'scope'       => 'config.provider_client_block',
+                        'description' => 'Clientes Bloqueados do Prestador',
                         'bits'        => 271,
                         'children'    => [],
                     ],
@@ -161,6 +124,12 @@ class PermissionSeeder extends Seeder
                         'bits'        => 271,
                         'children'    => [],
                     ],
+                    [
+                        'scope'       => 'config.provider_speciality',
+                        'description' => 'Configurações de Especialidades do Prestador',
+                        'bits'        => 271,
+                        'children'    => [],
+                    ],
                     [
                         'scope'       => 'config.provider_working_day',
                         'description' => 'Configurações de Dias de Trabalho do Prestador',
@@ -168,20 +137,32 @@ class PermissionSeeder extends Seeder
                         'children'    => [],
                     ],
                     [
-                        'scope'       => 'config.provider_blocked_day',
-                        'description' => 'Configurações de Dias Bloqueados do Prestador',
+                        'scope'       => 'config.review',
+                        'description' => 'Avaliações',
                         'bits'        => 271,
                         'children'    => [],
                     ],
                     [
-                        'scope'       => 'config.improvement_type',
-                        'description' => 'Configurações de Tipos de Melhoria',
+                        'scope'       => 'config.review_improvement',
+                        'description' => 'Melhorias das Avaliações',
                         'bits'        => 271,
                         'children'    => [],
                     ],
                     [
-                        'scope'       => 'config.media',
-                        'description' => 'Configurações de Mídia',
+                        'scope'       => 'config.schedule',
+                        'description' => 'Agendamentos',
+                        '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'    => [],
                     ],
@@ -198,14 +179,14 @@ class PermissionSeeder extends Seeder
                         'children'    => [],
                     ],
                     [
-                        'scope'       => 'config.review',
-                        'description' => 'Avaliações',
+                        'scope'       => 'config.state',
+                        'description' => 'Configurações de Estados',
                         'bits'        => 271,
                         'children'    => [],
                     ],
                     [
-                        'scope'       => 'config.review_improvement',
-                        'description' => 'Melhorias das Avaliações',
+                        'scope'       => 'config.user',
+                        'description' => 'Configurações de Usuários',
                         'bits'        => 271,
                         'children'    => [],
                     ],
@@ -218,6 +199,25 @@ class PermissionSeeder extends Seeder
                     ],
                 ],
             ],
+            [
+                'scope'       => 'dashboard',
+                'description' => 'Dashboard',
+                'bits'        => 511,
+                'children'    => [],
+            ],
+            [
+                'scope'       => 'payment',
+                'description' => 'Pagamentos',
+                'bits'        => 257,
+                'children'    => [
+                    [
+                        'scope'       => 'payment-split',
+                        'description' => 'Splits de Pagamento',
+                        'bits'        => 1,
+                        'children'    => [],
+                    ],
+                ],
+            ],
         ];
 
         $this->createPermissionsAndChildren(permissions: $permissions);
@@ -235,6 +235,7 @@ class PermissionSeeder extends Seeder
     ): void {
         foreach ($permissions as $permissionData) {
             $children = $permissionData['children'];
+
             unset($permissionData['children']);
 
             $permissionNode = Permission::updateOrCreate(
@@ -249,7 +250,7 @@ class PermissionSeeder extends Seeder
             if (! empty($children)) {
                 $this->createPermissionsAndChildren(
                     permissions: $children,
-                    parent: $permissionNode,
+                    parent:      $permissionNode,
                 );
             }
         }

+ 64 - 55
database/seeders/UserTypePermissionSeeder.php

@@ -23,83 +23,91 @@ class UserTypePermissionSeeder extends Seeder
                             'bits'  => $permission->bits,
                         ];
                     })->toArray();
+
                     $this->seedUserTypePermissions($permissions, UserTypeEnum::ADMIN->value);
+
                     break;
 
                 case UserTypeEnum::USER:
                     $userPermissions = [
-                        ['scope' => 'dashboard', 'bits' => 1],
-                        ['scope' => 'payment', 'bits' => 257],
-                        ['scope' => 'payment-split', 'bits' => 1],
-                        ['scope' => 'config.user', 'bits' => 5],
-                        ['scope' => 'config.address', 'bits' => 271],
-                        ['scope' => 'config.city', 'bits' => 1],
-                        ['scope' => 'config.client', 'bits' => 271],
+                        ['scope' => 'config.address',                  'bits' => 271],
+                        ['scope' => 'config.city',                     'bits' => 1],
+                        ['scope' => 'config.client',                   'bits' => 271],
                         ['scope' => 'config.client_favorite_provider', 'bits' => 271],
-                        ['scope' => 'config.provider_client_block', 'bits' => 271],
-                        ['scope' => 'config.client_provider_block', 'bits' => 271],
-                        ['scope' => 'config.client_payment_method', 'bits' => 271],
-                        ['scope' => 'config.schedule', 'bits' => 271],
-                        ['scope' => 'config.custom_schedule', 'bits' => 271],
-                        ['scope' => 'config.country', 'bits' => 1],
-                        ['scope' => 'config.state', 'bits' => 1],
-                        ['scope' => 'config.provider', 'bits' => 271],
-                        ['scope' => 'config.provider_speciality', 'bits' => 271],
-                        ['scope' => 'config.provider_services_types', 'bits' => 271],
-                        ['scope' => 'config.provider_working_day', 'bits' => 271],
-                        ['scope' => 'config.provider_blocked_day', 'bits' => 271],
-                        ['scope' => 'config.improvement_type', 'bits' => 271],
-                        ['scope' => 'config.media', 'bits' => 271],
-                        ['scope' => 'config.service_type', 'bits' => 271],
-                        ['scope' => 'config.speciality', 'bits' => 271],
-                        ['scope' => 'config.review', 'bits' => 271],
-                        ['scope' => 'config.review_improvement', 'bits' => 271],
-                        ['scope' => 'notification', 'bits' => 271],
+                        ['scope' => 'config.client_payment_method',    'bits' => 271],
+                        ['scope' => 'config.client_provider_block',    'bits' => 271],
+                        ['scope' => 'config.country',                  'bits' => 1],
+                        ['scope' => 'config.custom_schedule',          'bits' => 271],
+                        ['scope' => 'config.improvement_type',         'bits' => 271],
+                        ['scope' => 'config.media',                    'bits' => 271],
+                        ['scope' => 'config.provider',                 'bits' => 271],
+                        ['scope' => 'config.provider_blocked_day',     'bits' => 271],
+                        ['scope' => 'config.provider_client_block',    'bits' => 271],
+                        ['scope' => 'config.provider_services_types',  'bits' => 271],
+                        ['scope' => 'config.provider_speciality',      'bits' => 271],
+                        ['scope' => 'config.provider_working_day',     'bits' => 271],
+                        ['scope' => 'config.review',                   'bits' => 271],
+                        ['scope' => 'config.review_improvement',       'bits' => 271],
+                        ['scope' => 'config.schedule',                 'bits' => 271],
+                        ['scope' => 'config.service_type',             'bits' => 271],
+                        ['scope' => 'config.speciality',               'bits' => 271],
+                        ['scope' => 'config.state',                    'bits' => 1],
+                        ['scope' => 'config.user',                     'bits' => 5],
+                        ['scope' => 'dashboard',                       'bits' => 1],
+                        ['scope' => 'notification',                    'bits' => 271],
+                        ['scope' => 'payment',                         'bits' => 257],
+                        ['scope' => 'payment-split',                   'bits' => 1],
                     ];
+
                     $this->seedUserTypePermissions($userPermissions, UserTypeEnum::USER->value);
+
                     break;
 
                 case UserTypeEnum::PROVIDER:
                     $providerPermissions = [
-                        ['scope' => 'dashboard', 'bits' => 1],
-                        ['scope' => 'config.user', 'bits' => 5],
-                        ['scope' => 'config.address', 'bits' => 271],
-                        ['scope' => 'config.city', 'bits' => 1],
-                        ['scope' => 'config.provider', 'bits' => 5],
-                        ['scope' => 'config.provider_working_day', 'bits' => 271],
-                        ['scope' => 'config.provider_blocked_day', 'bits' => 271],
+                        ['scope' => 'config.address',                 'bits' => 271],
+                        ['scope' => 'config.city',                    'bits' => 1],
+                        ['scope' => 'config.custom_schedule',         'bits' => 271],
+                        ['scope' => 'config.improvement_type',        'bits' => 1],
+                        ['scope' => 'config.provider',                'bits' => 5],
+                        ['scope' => 'config.provider_blocked_day',    'bits' => 271],
+                        ['scope' => 'config.provider_client_block',   'bits' => 9],
                         ['scope' => 'config.provider_services_types', 'bits' => 271],
-                        ['scope' => 'config.service_type', 'bits' => 1],
-                        ['scope' => 'config.schedule', 'bits' => 271],
-                        ['scope' => 'config.custom_schedule', 'bits' => 271],
-                        ['scope' => 'config.improvement_type', 'bits' => 1],
-                        ['scope' => 'config.review', 'bits' => 271],
-                        ['scope' => 'config.provider_client_block', 'bits' => 9],
-                        ['scope' => 'notification', 'bits' => 271],
+                        ['scope' => 'config.provider_working_day',    'bits' => 271],
+                        ['scope' => 'config.review',                  'bits' => 271],
+                        ['scope' => 'config.schedule',                'bits' => 271],
+                        ['scope' => 'config.service_type',            'bits' => 1],
+                        ['scope' => 'config.user',                    'bits' => 5],
+                        ['scope' => 'dashboard',                      'bits' => 1],
+                        ['scope' => 'notification',                   'bits' => 271],
                     ];
+
                     $this->seedUserTypePermissions($providerPermissions, UserTypeEnum::PROVIDER->value);
+
                     break;
 
                 case UserTypeEnum::CLIENT:
                     $clientPermissions = [
-                        ['scope' => 'dashboard', 'bits' => 1],
-                        ['scope' => 'config.user', 'bits' => 5],
-                        ['scope' => 'config.address', 'bits' => 271],
-                        ['scope' => 'config.city', 'bits' => 1],
+                        ['scope' => 'config.address',                  'bits' => 271],
+                        ['scope' => 'config.city',                     'bits' => 1],
                         ['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.improvement_type', 'bits' => 1],
-                        ['scope' => 'config.review', 'bits' => 271],
-                        ['scope' => 'config.client_provider_block', 'bits' => 9],
-                        ['scope' => 'config.schedule', 'bits' => 271],
-                        ['scope' => 'config.custom_schedule', 'bits' => 271],
-                        ['scope' => 'config.speciality', 'bits' => 271],
-                        ['scope' => 'config.service_type', 'bits' => 271],
-                        ['scope' => 'notification', 'bits' => 271],
+                        ['scope' => 'config.client_payment_method',    'bits' => 271],
+                        ['scope' => 'config.client_provider_block',    'bits' => 9],
+                        ['scope' => 'config.custom_schedule',          'bits' => 271],
+                        ['scope' => 'config.improvement_type',         'bits' => 1],
+                        ['scope' => 'config.provider_blocked_day',     'bits' => 271],
+                        ['scope' => 'config.provider_working_day',     'bits' => 271],
+                        ['scope' => 'config.review',                   'bits' => 271],
+                        ['scope' => 'config.schedule',                 'bits' => 271],
+                        ['scope' => 'config.service_type',             'bits' => 271],
+                        ['scope' => 'config.speciality',               'bits' => 271],
+                        ['scope' => 'config.user',                     'bits' => 5],
+                        ['scope' => 'dashboard',                       'bits' => 1],
+                        ['scope' => 'notification',                    'bits' => 271],
                     ];
+
                     $this->seedUserTypePermissions($clientPermissions, UserTypeEnum::CLIENT->value);
+
                     break;
             }
         }
@@ -109,6 +117,7 @@ class UserTypePermissionSeeder extends Seeder
     {
         foreach ($permissions as $permissionData) {
             $permission = Permission::where('scope', $permissionData['scope'])->first();
+
             if ($permission) {
                 UserTypePermission::updateOrCreate(
                     [

+ 19 - 0
routes/authRoutes/cart.php

@@ -0,0 +1,19 @@
+<?php
+
+use App\Http\Controllers\CartController;
+use Illuminate\Support\Facades\Route;
+
+Route::get('/cart',         [CartController::class, 'index'])
+    ->middleware('permission:cart,view');
+
+Route::post('/cart',        [CartController::class, 'store'])
+    ->middleware('permission:cart,add');
+
+Route::get('/cart/{id}',    [CartController::class, 'show'])
+    ->middleware('permission:cart,view');
+
+Route::put('/cart/{id}',    [CartController::class, 'update'])
+    ->middleware('permission:cart,edit');
+
+Route::delete('/cart/{id}', [CartController::class, 'destroy'])
+    ->middleware('permission:cart,delete');

+ 19 - 0
routes/authRoutes/cart_item.php

@@ -0,0 +1,19 @@
+<?php
+
+use App\Http\Controllers\CartItemController;
+use Illuminate\Support\Facades\Route;
+
+Route::get('/cart-item',         [CartItemController::class, 'index'])
+    ->middleware('permission:cart-item,view');
+
+Route::post('/cart-item',        [CartItemController::class, 'store'])
+    ->middleware('permission:cart-item,add');
+
+Route::get('/cart-item/{id}',    [CartItemController::class, 'show'])
+    ->middleware('permission:cart-item,view');
+
+Route::put('/cart-item/{id}',    [CartItemController::class, 'update'])
+    ->middleware('permission:cart-item,edit');
+
+Route::delete('/cart-item/{id}', [CartItemController::class, 'destroy'])
+    ->middleware('permission:cart-item,delete');

+ 4 - 3
routes/authRoutes/payment.php

@@ -6,10 +6,11 @@ use App\Http\Controllers\PaymentController;
 Route::get('/payment',  [PaymentController::class, 'index'])->middleware('permission:payment,view');
 Route::post('/payment', [PaymentController::class, 'store'])->middleware('permission:payment,add');
 
-Route::get('/payment/platform-fees', [PaymentController::class, 'platformFees'])->middleware('permission:config.schedule,view');
+Route::get('/payment/platform-fees', [PaymentController::class, 'platformFees']);
 
-Route::get('/payment/schedule/{schedule}/pix',  [PaymentController::class, 'getSchedulePix'])->middleware('permission:config.schedule,view');
-Route::post('/payment/schedule/{schedule}/pay', [PaymentController::class, 'paySchedule'])->middleware('permission:config.schedule,edit');
+Route::get('/payment/schedule/{schedule}/pix',  [PaymentController::class, 'getSchedulePix']);
+Route::post('/payment/schedule/{schedule}/pay', [PaymentController::class, 'paySchedule']);
+Route::post('/payment/cart/pay',                [PaymentController::class, 'payCart']);
 
 Route::get('/payment/{id}',    [PaymentController::class, 'show'])->middleware('permission:payment,view');
 Route::put('/payment/{id}',    [PaymentController::class, 'update'])->middleware('permission:payment,edit');