Ver Fonte

✨ feat(partner-agreement): adicionar módulo de parceiros e convênios com upload de logo e mídia

Fase: dev | Origin: melhoria-interna
Gustavo Zanatta há 1 semana atrás
pai
commit
d5ee718eab

+ 77 - 0
app/Http/Controllers/PartnerAgreementController.php

@@ -0,0 +1,77 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Requests\PartnerAgreementRequest;
+use App\Http\Requests\UploadLogoRequest;
+use App\Http\Requests\UploadMediaRequest;
+use App\Http\Resources\MediaResource;
+use App\Http\Resources\PartnerAgreementResource;
+use App\Services\MediaService;
+use App\Services\PartnerAgreementService;
+use Illuminate\Http\JsonResponse;
+
+class PartnerAgreementController extends Controller
+{
+    public function __construct(
+        protected PartnerAgreementService $service,
+        protected MediaService $mediaService,
+    ) {}
+
+    public function index(): JsonResponse
+    {
+        $items = $this->service->getAll();
+        return $this->successResponse(payload: PartnerAgreementResource::collection($items));
+    }
+
+    public function store(PartnerAgreementRequest $request): JsonResponse
+    {
+        $item = $this->service->create($request->validated());
+        return $this->successResponse(
+            payload: new PartnerAgreementResource($item),
+            message: __('messages.created'),
+            code: 201,
+        );
+    }
+
+    public function show(int $id): JsonResponse
+    {
+        $item = $this->service->findById($id);
+        return $this->successResponse(payload: new PartnerAgreementResource($item));
+    }
+
+    public function update(PartnerAgreementRequest $request, int $id): JsonResponse
+    {
+        $item = $this->service->update($id, $request->validated());
+        return $this->successResponse(
+            payload: new PartnerAgreementResource($item),
+            message: __('messages.updated'),
+        );
+    }
+
+    public function destroy(int $id): JsonResponse
+    {
+        $this->service->delete($id);
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+
+    public function uploadLogo(UploadLogoRequest $request, int $id): JsonResponse
+    {
+        $media = $this->mediaService->uploadLogo($request->file('logo'), $id);
+        return $this->successResponse(payload: new MediaResource($media), code: 201);
+    }
+
+    public function uploadMedia(UploadMediaRequest $request, int $id): JsonResponse
+    {
+        $file      = $request->file('file');
+        $mediaType = str_starts_with($file->getMimeType(), 'image/') ? 'imagem' : 'documento';
+        $media     = $this->mediaService->upload($file, 'partner_agreement', $id, $mediaType);
+        return $this->successResponse(payload: new MediaResource($media), code: 201);
+    }
+
+    public function deleteMedia(int $id, int $mediaId): JsonResponse
+    {
+        $this->mediaService->delete($mediaId);
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+}

+ 46 - 0
app/Http/Requests/PartnerAgreementRequest.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Http\Requests;
+
+use App\Enums\PartnerAgreementStatusEnum;
+use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Validation\Rule;
+
+class PartnerAgreementRequest extends FormRequest
+{
+    public function rules(): array
+    {
+        $cnpjUnique = 'unique:partner_agreements,cnpj';
+        if ($this->isMethod('put') && $this->route('id')) {
+            $cnpjUnique = 'unique:partner_agreements,cnpj,' . $this->route('id');
+        }
+
+        $rules = [
+            'company_name'        => 'sometimes|string|max:255',
+            'cnpj'                => ['sometimes', 'nullable', 'string', 'max:18', $cnpjUnique],
+            'category_id'         => 'sometimes|nullable|integer|exists:categories,id',
+            'responsible'         => 'sometimes|nullable|string|max:255',
+            'discount_percentage' => 'sometimes|nullable|numeric|min:0|max:100',
+            'description'         => 'sometimes|nullable|string',
+            'email'               => 'sometimes|nullable|email|max:255',
+            'phone'               => 'sometimes|nullable|string|max:20',
+            'whatsapp'            => 'sometimes|nullable|string|max:20',
+            'website'             => 'sometimes|nullable|string|max:255',
+            'address'             => 'sometimes|nullable|string|max:255',
+            'neighborhood'        => 'sometimes|nullable|string|max:255',
+            'city_id'             => 'sometimes|nullable|integer|exists:cities,id',
+            'state_id'            => 'sometimes|nullable|integer|exists:states,id',
+            'zip_code'            => 'sometimes|nullable|string|max:9',
+            'working_hours'       => 'sometimes|nullable|string|max:255',
+            'contract_start'      => 'sometimes|nullable|date',
+            'contract_end'        => 'sometimes|nullable|date|after_or_equal:contract_start',
+            'status'              => ['sometimes', Rule::enum(PartnerAgreementStatusEnum::class)],
+        ];
+
+        if ($this->isMethod('post')) {
+            $rules['company_name'] = 'required|string|max:255';
+        }
+
+        return $rules;
+    }
+}

+ 15 - 0
app/Http/Requests/UploadLogoRequest.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class UploadLogoRequest extends FormRequest
+{
+    public function rules(): array
+    {
+        return [
+            'logo' => 'required|image|max:4096',
+        ];
+    }
+}

+ 15 - 0
app/Http/Requests/UploadMediaRequest.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class UploadMediaRequest extends FormRequest
+{
+    public function rules(): array
+    {
+        return [
+            'file' => 'required|file|max:10240',
+        ];
+    }
+}

+ 45 - 0
app/Http/Resources/PartnerAgreementResource.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+use App\Http\Resources\MediaResource;
+
+class PartnerAgreementResource extends JsonResource
+{
+    public function toArray(Request $request): array
+    {
+        return [
+            'id'                  => $this->id,
+            'company_name'        => $this->company_name,
+            'cnpj'                => $this->cnpj,
+            'category_id'         => $this->category_id,
+            'category'            => $this->whenLoaded('category', fn() => new CategoryResource($this->category)),
+            'responsible'         => $this->responsible,
+            'discount_percentage' => $this->discount_percentage,
+            'description'         => $this->description,
+            'email'               => $this->email,
+            'phone'               => $this->phone,
+            'whatsapp'            => $this->whatsapp,
+            'website'             => $this->website,
+            'address'             => $this->address,
+            'neighborhood'        => $this->neighborhood,
+            'city_id'             => $this->city_id,
+            'city'                => $this->whenLoaded('city', fn() => new CityResource($this->city)),
+            'state_id'            => $this->state_id,
+            'state'               => $this->whenLoaded('state', fn() => new StateResource($this->state)),
+            'zip_code'            => $this->zip_code,
+            'working_hours'       => $this->working_hours,
+            'contract_start'      => $this->contract_start?->format('Y-m-d'),
+            'contract_end'        => $this->contract_end?->format('Y-m-d'),
+            'status'              => $this->status,
+            'logo'                => $this->whenLoaded('logo', fn() => $this->logo ? new MediaResource($this->logo) : null),
+            'media'               => $this->whenLoaded('media', fn() => MediaResource::collection($this->media)),
+            'services'            => $this->whenLoaded('services', fn() => PartnerAgreementServiceResource::collection($this->services)),
+            'created_at'          => Carbon::parse($this->created_at)->format('Y-m-d H:i:s'),
+            'updated_at'          => Carbon::parse($this->updated_at)->format('Y-m-d H:i:s'),
+        ];
+    }
+}

+ 62 - 0
app/Models/PartnerAgreement.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace App\Models;
+
+use App\Enums\PartnerAgreementStatusEnum;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Eloquent\Relations\HasMany;
+use Illuminate\Database\Eloquent\Relations\HasOne;
+use Illuminate\Database\Eloquent\SoftDeletes;
+
+class PartnerAgreement extends Model
+{
+    use SoftDeletes;
+
+    protected $guarded = ['id'];
+
+    protected function casts(): array
+    {
+        return [
+            'discount_percentage' => 'float',
+            'contract_start'      => 'date',
+            'contract_end'        => 'date',
+            'status'              => PartnerAgreementStatusEnum::class,
+        ];
+    }
+
+    public function category(): BelongsTo
+    {
+        return $this->belongsTo(Category::class);
+    }
+
+    public function city(): BelongsTo
+    {
+        return $this->belongsTo(City::class);
+    }
+
+    public function state(): BelongsTo
+    {
+        return $this->belongsTo(State::class);
+    }
+
+    public function services(): HasMany
+    {
+        return $this->hasMany(PartnerAgreementService::class);
+    }
+
+    public function appointments(): HasMany
+    {
+        return $this->hasMany(Appointment::class);
+    }
+
+    public function logo(): HasOne
+    {
+        return $this->hasOne(Media::class, 'source_id')->where('source', 'partner_agreement_logo');
+    }
+
+    public function media(): HasMany
+    {
+        return $this->hasMany(Media::class, 'source_id')->where('source', 'partner_agreement');
+    }
+}

+ 49 - 0
app/Services/PartnerAgreementService.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\PartnerAgreement;
+use Illuminate\Database\Eloquent\Collection;
+
+class PartnerAgreementService
+{
+    public function getAll(): Collection
+    {
+        return PartnerAgreement::with(['category', 'city', 'state', 'logo'])
+            ->orderBy('company_name')
+            ->get();
+    }
+
+    public function findById(int $id): ?PartnerAgreement
+    {
+        return PartnerAgreement::with(['category', 'city', 'state', 'services', 'logo', 'media'])->find($id);
+    }
+
+    public function create(array $data): PartnerAgreement
+    {
+        return PartnerAgreement::create($data);
+    }
+
+    public function update(int $id, array $data): ?PartnerAgreement
+    {
+        $model = PartnerAgreement::find($id);
+
+        if (!$model) {
+            return null;
+        }
+
+        $model->update($data);
+        return $model->fresh(['category', 'city', 'state', 'logo', 'media']);
+    }
+
+    public function delete(int $id): bool
+    {
+        $model = PartnerAgreement::find($id);
+
+        if (!$model) {
+            return false;
+        }
+
+        return $model->delete();
+    }
+}

+ 46 - 0
database/migrations/2025_01_01_000006_create_partner_agreements_table.php

@@ -0,0 +1,46 @@
+<?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('partner_agreements', function (Blueprint $table) {
+            $table->id();
+            $table->string('company_name');
+            $table->string('cnpj', 18)->nullable()->unique();
+            $table->foreignId('category_id')->nullable()->constrained('categories')->nullOnDelete();
+            $table->string('responsible')->nullable();
+            $table->decimal('discount_percentage', 5, 2)->nullable();
+            $table->text('description')->nullable();
+            $table->string('email')->nullable();
+            $table->string('phone', 20)->nullable();
+            $table->string('whatsapp', 20)->nullable();
+            $table->string('website')->nullable();
+            $table->string('address')->nullable();
+            $table->string('neighborhood')->nullable();
+            $table->foreignId('city_id')->nullable()->constrained('cities')->nullOnDelete();
+            $table->foreignId('state_id')->nullable()->constrained('states')->nullOnDelete();
+            $table->string('zip_code', 9)->nullable();
+            $table->string('working_hours')->nullable();
+            $table->date('contract_start')->nullable();
+            $table->date('contract_end')->nullable();
+            $table->string('status')->default('active');
+            $table->timestamps();
+            $table->softDeletes();
+
+            $table->index('category_id');
+            $table->index('city_id');
+            $table->index('state_id');
+            $table->index('status');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('partner_agreements');
+    }
+};

+ 20 - 0
routes/authRoutes/partner_agreement.php

@@ -0,0 +1,20 @@
+<?php
+
+use App\Http\Controllers\PartnerAgreementController;
+use Illuminate\Support\Facades\Route;
+
+Route::controller(PartnerAgreementController::class)->prefix('partner-agreement')->group(function () {
+    Route::get('/', 'index')->middleware('permission:parceiro.convenio,view');
+
+    Route::post('/', 'store')->middleware('permission:parceiro.convenio,add');
+
+    Route::get('/{id}', 'show')->middleware('permission:parceiro.convenio,view');
+
+    Route::put('/{id}', 'update')->middleware('permission:parceiro.convenio,edit');
+
+    Route::delete('/{id}', 'destroy')->middleware('permission:parceiro.convenio,delete');
+
+    Route::post('/{id}/logo', 'uploadLogo')->middleware('permission:parceiro.convenio,edit');
+    Route::post('/{id}/media', 'uploadMedia')->middleware('permission:parceiro.convenio,edit');
+    Route::delete('/{id}/media/{mediaId}', 'deleteMedia')->middleware('permission:parceiro.convenio,edit');
+});