Add Brand and Category management features with controllers, requests, resources, and API routes

This commit is contained in:
2026-06-24 10:20:13 -03:00
parent 8feaa0051d
commit 6a0080b475
11 changed files with 358 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Domains\Catalog\Controllers;
use App\Domains\Catalog\Models\Brand;
use App\Domains\Catalog\Requests\StoreBrandRequest;
use App\Domains\Catalog\Requests\UpdateBrandRequest;
use App\Domains\Catalog\Resources\BrandResource;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
class BrandController extends Controller
{
public function index(): JsonResponse
{
return BrandResource::collection(Brand::query()->orderByDesc('id')->get())->response();
}
public function store(StoreBrandRequest $request): JsonResponse
{
$validated = $request->validated();
$props = $validated['props'] ?? [];
unset($validated['props']);
$brand = Brand::create($validated);
$brand->syncPropValues($props);
return BrandResource::make($brand)->response()->setStatusCode(201);
}
public function show(Brand $marca): BrandResource
{
return BrandResource::make($marca);
}
public function update(UpdateBrandRequest $request, Brand $marca): BrandResource
{
$validated = $request->validated();
$props = $validated['props'] ?? [];
unset($validated['props']);
$marca->update($validated);
$marca->syncPropValues($props);
return BrandResource::make($marca);
}
public function destroy(Brand $marca): Response
{
$marca->delete();
return response()->noContent();
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Domains\Catalog\Controllers;
use App\Domains\Catalog\Models\Category;
use App\Domains\Catalog\Requests\StoreCategoryRequest;
use App\Domains\Catalog\Requests\UpdateCategoryRequest;
use App\Domains\Catalog\Resources\CategoryResource;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
class CategoryController extends Controller
{
public function index(): JsonResponse
{
return CategoryResource::collection(
Category::query()->with(['parent', 'subCategories','tenant'])->orderByDesc('id')->get()
)->response();
}
public function store(StoreCategoryRequest $request): JsonResponse
{
$validated = $request->validated();
$props = $validated['props'] ?? [];
unset($validated['props']);
$category = Category::create($validated);
$category->syncPropValues($props);
return CategoryResource::make($category->load(['parent', 'subCategories', 'tenant']))
->response()
->setStatusCode(201);
}
public function show(Category $categoria): CategoryResource
{
return CategoryResource::make($categoria->load(['parent', 'subCategories', 'tenant']));
}
public function update(UpdateCategoryRequest $request, Category $categoria): CategoryResource
{
$validated = $request->validated();
$props = $validated['props'] ?? [];
unset($validated['props']);
$categoria->update($validated);
$categoria->syncPropValues($props);
return CategoryResource::make($categoria->load(['parent', 'subCategories', 'tenant']));
}
public function destroy(Category $categoria): Response
{
$categoria->delete();
return response()->noContent();
}
}

View File

@@ -3,11 +3,29 @@
namespace App\Domains\Catalog\Models;
use App\Domains\Prop\Concerns\Propable;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable([
'tenant_codigo',
'nombre',
'descripcion',
])]
class Brand extends Model
{
use HasFactory;
use Propable;
protected $table = 'brands';
/**
* @return BelongsTo<Tenant, $this>
*/
public function tenant(): BelongsTo
{
return $this->belongsTo(Tenant::class, 'tenant_codigo', 'codigo');
}
}

View File

@@ -3,11 +3,58 @@
namespace App\Domains\Catalog\Models;
use App\Domains\Prop\Concerns\Propable;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
#[Fillable([
'tenant_code',
'categoria_id',
'nombre',
])]
class Category extends Model
{
use HasFactory;
use Propable;
protected $table = 'categorias';
protected function casts(): array
{
return [
'categoria_id' => 'integer',
];
}
public function isGlobal(): bool
{
return $this->tenant_code === null;
}
/**
* @return BelongsTo<Tenant, $this>
*/
public function tenant(): BelongsTo
{
return $this->belongsTo(Tenant::class, 'tenant_code', 'codigo');
}
/**
* @return BelongsTo<Category, $this>
*/
public function parent(): BelongsTo
{
return $this->belongsTo(self::class, 'categoria_id');
}
/**
* @return HasMany<Category, $this>
*/
public function subCategories(): HasMany
{
return $this->hasMany(self::class, 'categoria_id');
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Domains\Catalog\Requests;
use App\Domains\Prop\Support\PropRules;
use Illuminate\Foundation\Http\FormRequest;
class StoreBrandRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, mixed>
*/
public function rules(): array
{
return [
'tenant_codigo' => ['required', 'string', 'exists:tenants,codigo'],
'nombre' => ['required', 'string', 'max:255'],
'descripcion' => ['nullable', 'string'],
...PropRules::sync(),
];
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Domains\Catalog\Requests;
use App\Domains\Prop\Support\PropRules;
use Illuminate\Foundation\Http\FormRequest;
class StoreCategoryRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, mixed>
*/
public function rules(): array
{
return [
'tenant_code' => ['required', 'string', 'exists:tenants,codigo'],
'categoria_id' => ['nullable', 'integer', 'exists:categorias,id'],
'nombre' => ['required', 'string', 'max:255'],
...PropRules::sync(),
];
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Domains\Catalog\Requests;
use App\Domains\Prop\Support\PropRules;
use Illuminate\Foundation\Http\FormRequest;
class UpdateBrandRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, mixed>
*/
public function rules(): array
{
return [
'tenant_codigo' => ['required', 'string', 'exists:tenants,codigo'],
'nombre' => ['required', 'string', 'max:255'],
'descripcion' => ['nullable', 'string'],
...PropRules::sync(),
];
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Domains\Catalog\Requests;
use App\Domains\Catalog\Models\Category;
use App\Domains\Prop\Support\PropRules;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class UpdateCategoryRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, mixed>
*/
public function rules(): array
{
/** @var Category|null $category */
$category = $this->route('categoria');
return [
'tenant_code' => ['required', 'string', 'exists:tenants,codigo'],
'categoria_id' => [
'nullable',
'integer',
'exists:categorias,id',
Rule::notIn([$category?->id]),
],
'nombre' => ['required', 'string', 'max:255'],
...PropRules::sync(),
];
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Domains\Catalog\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin \App\Domains\Catalog\Models\Brand
*/
class BrandResource extends JsonResource
{
/**
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'tenant_codigo' => $this->tenant_codigo,
'nombre' => $this->nombre,
'descripcion' => $this->descripcion,
];
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Domains\Catalog\Resources;
use App\Domains\Tenant\Resources\TenantResource;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin \App\Domains\Catalog\Models\Category
*/
class CategoryResource extends JsonResource
{
/**
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'tenant_code' => $this->tenant_code,
'is_global' => $this->resource->isGlobal(),
'tenant' => $this->resource->isGlobal()
? null
: TenantResource::make($this->whenLoaded('tenant')),
'categoria_id' => $this->categoria_id,
'nombre' => $this->nombre,
'parent' => self::make($this->whenLoaded('parent')),
'sub_categories' => self::collection($this->whenLoaded('subCategories')),
];
}
}

View File

@@ -1,6 +1,10 @@
<?php
use App\Domains\Catalog\Controllers\BrandController;
use App\Domains\Catalog\Controllers\CategoryController;
use App\Domains\Catalog\Controllers\ProductController;
use Illuminate\Support\Facades\Route;
Route::apiResource('marcas', BrandController::class)->parameters(['marcas' => 'marca']);
Route::apiResource('categorias', CategoryController::class)->parameters(['categorias' => 'categoria']);
Route::apiResource('productos', ProductController::class);