Bladeren bron

✨ feat(store-item): adicionar módulo de loja de benefícios e interesses

Fase: dev | Origin: melhoria-interna
Gustavo Zanatta 1 week geleden
bovenliggende
commit
adc9b7d782

+ 65 - 0
app/Http/Controllers/StoreItemController.php

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

+ 37 - 0
app/Http/Requests/StoreItemRequest.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace App\Http\Requests;
+
+use App\Enums\StoreItemStatusEnum;
+use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Validation\Rule;
+
+class StoreItemRequest extends FormRequest
+{
+    public function rules(): array
+    {
+        $rules = [
+            'name'            => 'sometimes|string|max:255',
+            'description'     => 'sometimes|nullable|string',
+            'category_id'     => 'sometimes|nullable|integer|exists:categories,id',
+            'price'           => 'sometimes|nullable|numeric|min:0',
+            'associate_price' => 'sometimes|nullable|numeric|min:0',
+            'supplier_price'  => 'sometimes|nullable|numeric|min:0',
+            'size_s'          => 'sometimes|boolean',
+            'stock_s'         => 'sometimes|integer|min:0',
+            'size_m'          => 'sometimes|boolean',
+            'stock_m'         => 'sometimes|integer|min:0',
+            'size_l'          => 'sometimes|boolean',
+            'stock_l'         => 'sometimes|integer|min:0',
+            'size_xl'         => 'sometimes|boolean',
+            'stock_xl'        => 'sometimes|integer|min:0',
+            'status'          => ['sometimes', Rule::enum(StoreItemStatusEnum::class)],
+        ];
+
+        if ($this->isMethod('post')) {
+            $rules['name'] = 'required|string|max:255';
+        }
+
+        return $rules;
+    }
+}

+ 36 - 0
app/Http/Resources/StoreItemResource.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class StoreItemResource extends JsonResource
+{
+    public function toArray(Request $request): array
+    {
+        return [
+            'id'              => $this->id,
+            'name'            => $this->name,
+            'description'     => $this->description,
+            'category_id'     => $this->category_id,
+            'category'        => $this->whenLoaded('category', fn() => new CategoryResource($this->category)),
+            'price'           => $this->price,
+            'associate_price' => $this->associate_price,
+            'supplier_price'  => $this->supplier_price,
+            'size_s'          => $this->size_s,
+            'stock_s'         => $this->stock_s,
+            'size_m'          => $this->size_m,
+            'stock_m'         => $this->stock_m,
+            'size_l'          => $this->size_l,
+            'stock_l'         => $this->stock_l,
+            'size_xl'         => $this->size_xl,
+            'stock_xl'        => $this->stock_xl,
+            'status'          => $this->status,
+            'media'           => $this->whenLoaded('media', fn() => MediaResource::collection($this->media)),
+            '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'),
+        ];
+    }
+}

+ 49 - 0
app/Models/StoreItem.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace App\Models;
+
+use App\Enums\StoreItemStatusEnum;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Eloquent\Relations\HasMany;
+use Illuminate\Database\Eloquent\SoftDeletes;
+
+class StoreItem extends Model
+{
+    use SoftDeletes;
+
+    protected $guarded = ['id'];
+
+    protected function casts(): array
+    {
+        return [
+            'price'          => 'float',
+            'associate_price' => 'float',
+            'supplier_price' => 'float',
+            'size_s'         => 'boolean',
+            'size_m'         => 'boolean',
+            'size_l'         => 'boolean',
+            'size_xl'        => 'boolean',
+            'stock_s'        => 'integer',
+            'stock_m'        => 'integer',
+            'stock_l'        => 'integer',
+            'stock_xl'       => 'integer',
+            'status'         => StoreItemStatusEnum::class,
+        ];
+    }
+
+    public function category(): BelongsTo
+    {
+        return $this->belongsTo(Category::class);
+    }
+
+    public function interests(): HasMany
+    {
+        return $this->hasMany(StoreItemInterest::class);
+    }
+
+    public function media(): HasMany
+    {
+        return $this->hasMany(Media::class, 'source_id')->where('source', 'store_item');
+    }
+}

+ 21 - 0
app/Models/StoreItemInterest.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+
+class StoreItemInterest extends Model
+{
+    protected $guarded = ['id'];
+
+    public function user(): BelongsTo
+    {
+        return $this->belongsTo(User::class);
+    }
+
+    public function storeItem(): BelongsTo
+    {
+        return $this->belongsTo(StoreItem::class);
+    }
+}

+ 76 - 0
app/Services/StoreItemService.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\StoreItem;
+use App\Models\StoreItemInterest;
+use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\DB;
+
+class StoreItemService
+{
+    public function getAll(): Collection
+    {
+        return StoreItem::with('category')->orderBy('name')->get();
+    }
+
+    public function findById(int $id): ?StoreItem
+    {
+        return StoreItem::with(['category', 'media'])->find($id);
+    }
+
+    public function create(array $data): StoreItem
+    {
+        return StoreItem::create($data);
+    }
+
+    public function update(int $id, array $data): ?StoreItem
+    {
+        $model = StoreItem::find($id);
+
+        if (!$model) {
+            return null;
+        }
+
+        $model->update($data);
+        return $model->fresh(['category']);
+    }
+
+    public function delete(int $id): bool
+    {
+        $model = StoreItem::find($id);
+
+        if (!$model) {
+            return false;
+        }
+
+        return $model->delete();
+    }
+
+    public function getMyInterests(): Collection
+    {
+        $userId = Auth::id();
+        return StoreItem::with('category')
+            ->whereHas('interests', fn($q) => $q->where('user_id', $userId))
+            ->orderBy('name')
+            ->get();
+    }
+
+    public function toggleInterest(int $storeItemId): array
+    {
+        $userId = Auth::id();
+
+        $existing = StoreItemInterest::where('user_id', $userId)
+            ->where('store_item_id', $storeItemId)
+            ->first();
+
+        if ($existing) {
+            $existing->delete();
+            return ['interested' => false];
+        }
+
+        StoreItemInterest::create(['user_id' => $userId, 'store_item_id' => $storeItemId]);
+        return ['interested' => true];
+    }
+}

+ 40 - 0
database/migrations/2025_01_01_000010_create_store_items_table.php

@@ -0,0 +1,40 @@
+<?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('store_items', function (Blueprint $table) {
+            $table->id();
+            $table->string('name');
+            $table->text('description')->nullable();
+            $table->foreignId('category_id')->nullable()->constrained('categories')->nullOnDelete();
+            $table->decimal('price', 10, 2)->nullable();
+            $table->decimal('associate_price', 10, 2)->nullable();
+            $table->decimal('supplier_price', 10, 2)->nullable();
+            $table->boolean('size_s')->default(false);
+            $table->integer('stock_s')->default(0);
+            $table->boolean('size_m')->default(false);
+            $table->integer('stock_m')->default(0);
+            $table->boolean('size_l')->default(false);
+            $table->integer('stock_l')->default(0);
+            $table->boolean('size_xl')->default(false);
+            $table->integer('stock_xl')->default(0);
+            $table->string('status')->default('active');
+            $table->timestamps();
+            $table->softDeletes();
+
+            $table->index('category_id');
+            $table->index('status');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('store_items');
+    }
+};

+ 26 - 0
database/migrations/2025_01_01_000011_create_store_item_interests_table.php

@@ -0,0 +1,26 @@
+<?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('store_item_interests', function (Blueprint $table) {
+            $table->id();
+            $table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
+            $table->foreignId('store_item_id')->constrained('store_items')->cascadeOnDelete();
+            $table->timestamps();
+
+            $table->unique(['user_id', 'store_item_id']);
+            $table->index('store_item_id');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('store_item_interests');
+    }
+};

+ 20 - 0
routes/authRoutes/store_item.php

@@ -0,0 +1,20 @@
+<?php
+
+use App\Http\Controllers\StoreItemController;
+use Illuminate\Support\Facades\Route;
+
+Route::controller(StoreItemController::class)->prefix('store-item')->group(function () {
+    Route::get('/', 'index')->middleware('permission:loja.item,view');
+
+    Route::post('/', 'store')->middleware('permission:loja.item,add');
+
+    Route::get('/{id}', 'show')->middleware('permission:loja.item,view');
+
+    Route::put('/{id}', 'update')->middleware('permission:loja.item,edit');
+
+    Route::delete('/{id}', 'destroy')->middleware('permission:loja.item,delete');
+
+    Route::get('/my/interests', 'myInterests')->middleware('permission:loja.item,view');
+
+    Route::post('/{id}/interest', 'toggleInterest')->middleware('permission:loja.item,view');
+});