Prechádzať zdrojové kódy

crud oportunidades (custom_schedules)

Gustavo Zanatta 2 týždňov pred
rodič
commit
990bd74c67

+ 80 - 0
app/Http/Controllers/CustomScheduleController.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Requests\CustomScheduleRequest;
+use App\Http\Resources\CustomScheduleResource;
+use App\Services\CustomScheduleService;
+use Illuminate\Http\JsonResponse;
+
+class CustomScheduleController extends Controller
+{
+    protected $customScheduleService;
+
+    public function __construct(CustomScheduleService $customScheduleService)
+    {
+        $this->customScheduleService = $customScheduleService;
+    }
+
+    public function index(): JsonResponse
+    {
+        $customSchedules = $this->customScheduleService->getAll();
+        return $this->successResponse(
+            CustomScheduleResource::collection($customSchedules),
+        );
+    }
+
+    public function store(CustomScheduleRequest $request): JsonResponse
+    {
+        try {
+            $validated = $request->validated();
+            $customSchedules = $this->customScheduleService->create($validated);
+            
+            $count = count($customSchedules);
+            $message = $count > 1 
+                ? "{$count} oportunidades criadas com sucesso!"
+                : __("messages.created");
+
+            return $this->successResponse(
+                payload: CustomScheduleResource::collection($customSchedules),
+                message: $message,
+                code: 201,
+            );
+        } catch (\Exception $e) {
+            return $this->errorResponse($e->getMessage(), 422);
+        }
+    }
+
+    public function show(string $id): JsonResponse
+    {
+        $customSchedule = $this->customScheduleService->getById($id);
+        return $this->successResponse(
+            new CustomScheduleResource($customSchedule),
+        );
+    }
+
+    public function update(CustomScheduleRequest $request, string $id): JsonResponse
+    {
+        try {
+            $customSchedule = $this->customScheduleService->update($id, $request->validated());
+            return $this->successResponse(
+                payload: new CustomScheduleResource($customSchedule),
+                message: __("messages.updated"),
+            );
+        } catch (\Exception $e) {
+            return $this->errorResponse($e->getMessage(), 422);
+        }
+    }
+
+    public function destroy(string $id): JsonResponse
+    {
+        try {
+            $this->customScheduleService->delete($id);
+            return $this->successResponse(
+                message: __("messages.deleted"),
+            );
+        } catch (\Exception $e) {
+            return $this->errorResponse($e->getMessage(), 422);
+        }
+    }
+}

+ 64 - 0
app/Http/Requests/CustomScheduleRequest.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class CustomScheduleRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     */
+    public function authorize(): bool
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
+     */
+    public function rules(): array
+    {
+        $rules = [
+            'client_id' => 'required|exists:clients,id',
+            'address_id' => 'required|exists:addresses,id',
+            'address_type' => 'required|in:home,commercial',
+            'service_type_id' => 'required|exists:service_types,id',
+            'description' => 'nullable|string',
+            'min_price' => 'required|numeric|min:0',
+            'max_price' => 'required|numeric|min:0|gte:min_price',
+            'offers_meal' => 'nullable|boolean',
+            'date' => 'required|date|after_or_equal:today',
+            'period_type' => 'required|in:2,4,6,8',
+            'start_time' => 'required|date_format:H:i:s',
+            'end_time' => 'required|date_format:H:i:s|after:start_time',
+            'quantity' => 'nullable|integer|min:1|max:10',
+            'speciality_ids' => 'nullable|array',
+            'speciality_ids.*' => 'exists:specialities,id',
+        ];
+
+        if ($this->isMethod('PUT') || $this->isMethod('PATCH')) {
+            foreach ($rules as $key => $rule) {
+                if (str_starts_with($rule, 'required')) {
+                    $rules[$key] = 'sometimes|' . substr($rule, 9);
+                }
+            }
+        }
+
+        return $rules;
+    }
+
+    /**
+     * Get custom messages for validator errors.
+     */
+    public function messages(): array
+    {
+        return [
+            'max_price.gte' => 'O preço máximo deve ser maior ou igual ao preço mínimo.',
+            'date.after_or_equal' => 'A data deve ser hoje ou uma data futura.',
+            'end_time.after' => 'O horário de término deve ser posterior ao horário de início.',
+        ];
+    }
+}

+ 65 - 0
app/Http/Resources/CustomScheduleResource.php

@@ -0,0 +1,65 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class CustomScheduleResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @return array<string, mixed>
+     */
+    public function toArray(Request $request): array
+    {
+        return [
+            'id' => $this->id,
+            'schedule_id' => $this->schedule_id,
+            'address_type' => $this->address_type,
+            'service_type_id' => $this->service_type_id,
+            'service_type_name' => $this->whenLoaded('serviceType', function () {
+                return $this->serviceType->description;
+            }),
+            'description' => $this->description,
+            'min_price' => number_format($this->min_price, 2, '.', ''),
+            'max_price' => number_format($this->max_price, 2, '.', ''),
+            'offers_meal' => $this->offers_meal,
+            'created_at' => $this->created_at?->format('Y-m-d H:i:s'),
+            'updated_at' => $this->updated_at?->format('Y-m-d H:i:s'),
+            
+            'schedule' => $this->whenLoaded('schedule', function () {
+                return [
+                    'id' => $this->schedule->id,
+                    'client_id' => $this->schedule->client_id,
+                    'client_name' => $this->schedule->client?->user?->name,
+                    'address_id' => $this->schedule->address_id,
+                    'address' => $this->schedule->address ? [
+                        'street' => $this->schedule->address->street,
+                        'number' => $this->schedule->address->number,
+                        'complement' => $this->schedule->address->complement,
+                        'neighborhood' => $this->schedule->address->neighborhood,
+                        'city' => $this->schedule->address->city,
+                        'state' => $this->schedule->address->state,
+                        'zipcode' => $this->schedule->address->zipcode,
+                    ] : null,
+                    'date' => $this->schedule->date?->format('d/m/Y'),
+                    'period_type' => $this->schedule->period_type,
+                    'start_time' => $this->schedule->start_time,
+                    'end_time' => $this->schedule->end_time,
+                    'status' => $this->schedule->status,
+                ];
+            }),
+            
+            'specialities' => $this->whenLoaded('specialities', function () {
+                return $this->specialities->map(function ($item) {
+                    return [
+                        'id' => $item->speciality->id,
+                        'name' => $item->speciality->name,
+                    ];
+                });
+            }),
+        ];
+    }
+}

+ 45 - 0
app/Models/CustomSchedule.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Eloquent\Relations\HasMany;
+use Illuminate\Database\Eloquent\Relations\HasOne;
+
+class CustomSchedule extends Model
+{
+    use SoftDeletes;
+
+    protected $fillable = [
+        'schedule_id',
+        'address_type',
+        'service_type_id',
+        'description',
+        'min_price',
+        'max_price',
+        'offers_meal',
+    ];
+
+    protected $casts = [
+        'min_price' => 'decimal:2',
+        'max_price' => 'decimal:2',
+        'offers_meal' => 'boolean',
+    ];
+
+    public function schedule(): BelongsTo
+    {
+        return $this->belongsTo(Schedule::class);
+    }
+
+    public function serviceType(): HasOne
+    {
+        return $this->hasOne(ServiceType::class, 'id', 'service_type_id');
+    }
+
+    public function specialities(): HasMany
+    {
+        return $this->hasMany(CustomScheduleSpeciality::class);
+    }
+}

+ 29 - 0
app/Models/CustomScheduleSpeciality.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+
+class CustomScheduleSpeciality extends Model
+{
+    use SoftDeletes;
+
+    protected $table = "custom_schedules_specialities";
+
+    protected $fillable = [
+        'custom_schedule_id',
+        'speciality_id',
+    ];
+
+    public function customSchedule(): BelongsTo
+    {
+        return $this->belongsTo(CustomSchedule::class);
+    }
+    
+    public function speciality(): BelongsTo
+    {
+        return $this->belongsTo(Speciality::class);
+    }
+}

+ 200 - 0
app/Services/CustomScheduleService.php

@@ -0,0 +1,200 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\CustomSchedule;
+use App\Models\Schedule;
+use App\Models\CustomScheduleSpeciality;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+class CustomScheduleService
+{
+    public function getAll()
+    {
+        return CustomSchedule::with([
+            'schedule.client.user',
+            'schedule.address',
+            'serviceType',
+            'specialities.speciality'
+        ])
+            ->orderBy('id', 'desc')
+            ->get();
+    }
+
+    public function getById($id)
+    {
+        return CustomSchedule::with([
+            'schedule.client.user',
+            'schedule.address',
+            'serviceType',
+            'specialities.speciality'
+        ])->findOrFail($id);
+    }
+
+    public function create(array $data)
+    {
+        DB::beginTransaction();
+        try {
+            $quantity = $data['quantity'] ?? 1;
+            $specialityIds = $data['speciality_ids'] ?? [];
+            
+            $createdCustomSchedules = [];
+
+            for ($i = 0; $i < $quantity; $i++) {
+                $scheduleData = [
+                    'client_id' => $data['client_id'],
+                    'provider_id' => null,
+                    'address_id' => $data['address_id'],
+                    'date' => $data['date'],
+                    'period_type' => $data['period_type'],
+                    'schedule_type' => 'custom',
+                    'start_time' => $data['start_time'],
+                    'end_time' => $data['end_time'],
+                    'status' => 'pending',
+                    'total_amount' => 0,
+                    'code' => str_pad(random_int(0, 9999), 4, '0', STR_PAD_LEFT),
+                    'code_verified' => false,
+                ];
+
+                $schedule = Schedule::create($scheduleData);
+
+                $customScheduleData = [
+                    'schedule_id' => $schedule->id,
+                    'address_type' => $data['address_type'],
+                    'service_type_id' => $data['service_type_id'],
+                    'description' => $data['description'] ?? null,
+                    'min_price' => $data['min_price'],
+                    'max_price' => $data['max_price'],
+                    'offers_meal' => $data['offers_meal'] ?? false,
+                ];
+
+                $customSchedule = CustomSchedule::create($customScheduleData);
+
+                if (!empty($specialityIds)) {
+                    foreach ($specialityIds as $specialityId) {
+                        CustomScheduleSpeciality::create([
+                            'custom_schedule_id' => $customSchedule->id,
+                            'speciality_id' => $specialityId,
+                        ]);
+                    }
+                }
+
+                $createdCustomSchedules[] = $customSchedule->load([
+                    'schedule.client.user',
+                    'schedule.address',
+                    'serviceType',
+                    'specialities.speciality'
+                ]);
+            }
+
+            DB::commit();
+            return $createdCustomSchedules;
+        } catch (\Exception $e) {
+            DB::rollBack();
+            Log::error('Error creating custom schedule: ' . $e->getMessage());
+            throw $e;
+        }
+    }
+
+    public function update($id, array $data)
+    {
+        DB::beginTransaction();
+        try {
+            $customSchedule = CustomSchedule::findOrFail($id);
+            $schedule = $customSchedule->schedule;
+
+            $scheduleUpdateData = [];
+            if (isset($data['address_id'])) {
+                $scheduleUpdateData['address_id'] = $data['address_id'];
+            }
+            if (isset($data['date'])) {
+                $scheduleUpdateData['date'] = $data['date'];
+            }
+            if (isset($data['period_type'])) {
+                $scheduleUpdateData['period_type'] = $data['period_type'];
+            }
+            if (isset($data['start_time'])) {
+                $scheduleUpdateData['start_time'] = $data['start_time'];
+            }
+            if (isset($data['end_time'])) {
+                $scheduleUpdateData['end_time'] = $data['end_time'];
+            }
+
+            if (!empty($scheduleUpdateData)) {
+                $schedule->update($scheduleUpdateData);
+            }
+
+            $customScheduleUpdateData = [];
+            if (isset($data['address_type'])) {
+                $customScheduleUpdateData['address_type'] = $data['address_type'];
+            }
+            if (isset($data['service_type_id'])) {
+                $customScheduleUpdateData['service_type_id'] = $data['service_type_id'];
+            }
+            if (isset($data['description'])) {
+                $customScheduleUpdateData['description'] = $data['description'];
+            }
+            if (isset($data['min_price'])) {
+                $customScheduleUpdateData['min_price'] = $data['min_price'];
+            }
+            if (isset($data['max_price'])) {
+                $customScheduleUpdateData['max_price'] = $data['max_price'];
+            }
+            if (isset($data['offers_meal'])) {
+                $customScheduleUpdateData['offers_meal'] = $data['offers_meal'];
+            }
+
+            if (!empty($customScheduleUpdateData)) {
+                $customSchedule->update($customScheduleUpdateData);
+            }
+
+            if (isset($data['speciality_ids'])) {
+                $custom_schedule = CustomScheduleSpeciality::where('custom_schedule_id', $customSchedule->id);
+                $custom_schedule->delete();
+                
+                foreach ($data['speciality_ids'] as $specialityId) {
+                    CustomScheduleSpeciality::create([
+                        'custom_schedule_id' => $customSchedule->id,
+                        'speciality_id' => $specialityId,
+                    ]);
+                }
+            }
+
+            DB::commit();
+            return $customSchedule->fresh([
+                'schedule.client.user',
+                'schedule.address',
+                'serviceType',
+                'specialities.speciality'
+            ]);
+        } catch (\Exception $e) {
+            DB::rollBack();
+            Log::error('Error updating custom schedule: ' . $e->getMessage());
+            throw $e;
+        }
+    }
+
+    public function delete($id)
+    {
+        DB::beginTransaction();
+        try {
+            $customSchedule = CustomSchedule::findOrFail($id);
+            $schedule = $customSchedule->schedule;
+            
+            CustomScheduleSpeciality::where('custom_schedule_id', $customSchedule->id)->delete();
+            
+            $customSchedule->delete();
+            
+            $schedule->delete();
+
+            DB::commit();
+            return $customSchedule;
+        } catch (\Exception $e) {
+            DB::rollBack();
+            Log::error('Error deleting custom schedule: ' . $e->getMessage());
+            throw $e;
+        }
+    }
+}

+ 46 - 0
app/Services/CustomScheduleSpecialityService.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\CustomScheduleSpeciality;
+
+class CustomScheduleSpecialityService
+{
+    public function getAll()
+    {
+        return CustomScheduleSpeciality::with(['customSchedule', 'speciality'])
+            ->orderBy('created_at', 'desc')
+            ->get();
+    }
+
+    public function getById($id)
+    {
+        return CustomScheduleSpeciality::with(['customSchedule', 'speciality'])->findOrFail($id);
+    }
+
+    public function getByCustomScheduleId($customScheduleId)
+    {
+        return CustomScheduleSpeciality::with(['speciality'])
+            ->where('custom_schedule_id', $customScheduleId)
+            ->get();
+    }
+
+    public function create(array $data)
+    {
+        return CustomScheduleSpeciality::create($data);
+    }
+
+    public function update($id, array $data)
+    {
+        $customScheduleSpeciality = CustomScheduleSpeciality::findOrFail($id);
+        $customScheduleSpeciality->update($data);
+        return $customScheduleSpeciality->fresh(['customSchedule', 'speciality']);
+    }
+
+    public function delete($id)
+    {
+        $customScheduleSpeciality = CustomScheduleSpeciality::findOrFail($id);
+        $customScheduleSpeciality->delete();
+        return $customScheduleSpeciality;
+    }
+}

+ 1 - 0
app/Services/ScheduleService.php

@@ -15,6 +15,7 @@ class ScheduleService
     public function getAll()
     {
         return Schedule::with(['client.user', 'provider.user', 'address'])
+            ->where('schedule_type', 'default')
             ->orderBy('date', 'desc')
             ->orderBy('start_time', 'desc')
             ->get();

+ 32 - 0
database/migrations/2026_02_23_130114_make_provider_id_nullable_in_schedules_table.php

@@ -0,0 +1,32 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::table('schedules', function (Blueprint $table) {
+            $table->dropForeign(['provider_id']);
+            $table->unsignedBigInteger('provider_id')->nullable()->change();
+            $table->foreign('provider_id')->references('id')->on('providers')->onDelete('cascade');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::table('schedules', function (Blueprint $table) {
+            $table->dropForeign(['provider_id']);
+            $table->unsignedBigInteger('provider_id')->nullable(false)->change();
+            $table->foreign('provider_id')->references('id')->on('providers')->onDelete('cascade');
+        });
+    }
+};

+ 38 - 0
database/migrations/2026_02_23_130530_create_custom_schedules_table.php

@@ -0,0 +1,38 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::create('custom_schedules', function (Blueprint $table) {
+            $table->id();
+            $table->unsignedBigInteger('schedule_id')->index();
+            $table->enum('address_type', ['home', 'commercial']);
+            $table->unsignedBigInteger('service_type_id')->index();
+            $table->text('description')->nullable();
+            $table->decimal('min_price', 10, 2);
+            $table->decimal('max_price', 10, 2);
+            $table->boolean('offers_meal')->default(false);
+            $table->timestamps();
+            $table->softDeletes();
+
+            $table->foreign('schedule_id')->references('id')->on('schedules')->onDelete('cascade');
+            $table->foreign('service_type_id')->references('id')->on('service_types')->onDelete('cascade');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('custom_schedules');
+    }
+};

+ 33 - 0
database/migrations/2026_02_23_130613_create_custom_schedules_specialities_table.php

@@ -0,0 +1,33 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::create('custom_schedules_specialities', function (Blueprint $table) {
+            $table->id();
+            $table->unsignedBigInteger('custom_schedule_id')->index();
+            $table->unsignedBigInteger('speciality_id')->index();
+            $table->timestamps();
+            $table->softDeletes();
+
+            $table->foreign('custom_schedule_id')->references('id')->on('custom_schedules')->onDelete('cascade');
+            $table->foreign('speciality_id')->references('id')->on('specialities')->onDelete('set null');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('custom_schedules_specialities');
+    }
+};

+ 6 - 0
database/seeders/PermissionSeeder.php

@@ -88,6 +88,12 @@ class PermissionSeeder extends Seeder
                         "bits" => 271,
                         "children" => [],
                     ],
+                    [
+                        "scope" => "config.custom_schedule",
+                        "description" => "Oportunidades (Agendamentos Personalizados)",
+                        "bits" => 271,
+                        "children" => [],
+                    ],
                     [
                         "scope" => "config.country",
                         "description" => "Configurações de Países",

+ 2 - 0
database/seeders/UserTypePermissionSeeder.php

@@ -36,6 +36,8 @@ class UserTypePermissionSeeder extends Seeder
                         ['scope' => 'config.client', 'bits' => 271],
                         ['scope' => 'config.client_favorite_provider', '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],

+ 10 - 0
routes/authRoutes/custom_schedule.php

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