feat: Implement OnTicketFeaturedGroup management; add controller, service, resource, request, routes, and tests for featured groups

This commit is contained in:
2026-08-04 13:17:00 -03:00
parent 84e45bb964
commit 3a039a6055
7 changed files with 489 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
<?php
namespace App\Domains\Catalog\Controllers\AdminApp;
use App\Domains\Catalog\Models\FeaturedGroup;
use App\Domains\Catalog\Requests\AdminApp\UpsertOnTicketFeaturedGroupRequest;
use App\Domains\Catalog\Resources\AdminApp\OnTicketFeaturedGroupResource;
use App\Domains\Catalog\Services\OnTicketFeaturedGroupService;
use App\Domains\Tenant\Models\Tenant;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
class OnTicketFeaturedGroupController extends Controller
{
public function __construct(
private readonly OnTicketFeaturedGroupService $featuredGroupService,
) {}
public function index(Request $request): AnonymousResourceCollection
{
return OnTicketFeaturedGroupResource::collection(
$this->featuredGroupService->forTenant($this->onTicketTenant($request))
);
}
public function store(UpsertOnTicketFeaturedGroupRequest $request): JsonResponse
{
$featuredGroup = $this->featuredGroupService->create(
$this->onTicketTenant($request),
$request->validated(),
);
return OnTicketFeaturedGroupResource::make($featuredGroup)
->response()
->setStatusCode(201);
}
public function update(
UpsertOnTicketFeaturedGroupRequest $request,
FeaturedGroup $featuredGroup,
): OnTicketFeaturedGroupResource {
$tenant = $this->onTicketTenant($request);
abort_unless($featuredGroup->tenant_code === $tenant->codigo, 404);
return OnTicketFeaturedGroupResource::make(
$this->featuredGroupService->update(
$tenant,
$featuredGroup,
$request->validated(),
)
);
}
private function onTicketTenant(Request $request): Tenant
{
$tenant = $request->user()->tenant()->firstOrFail();
abort_unless($tenant->website_type_code === 'onticket', 404);
return $tenant;
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Domains\Catalog\Requests\AdminApp;
use Illuminate\Foundation\Http\FormRequest;
class UpsertOnTicketFeaturedGroupRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/** @return array<string, mixed> */
public function rules(): array
{
return [
'category_name' => ['required', 'string', 'max:255'],
'is_featured' => ['required', 'boolean'],
];
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Domains\Catalog\Resources\AdminApp;
use App\Domains\Catalog\Enums\ProductLayout;
use App\Domains\Catalog\Models\FeaturedGroup;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/** @mixin FeaturedGroup */
class OnTicketFeaturedGroupResource extends JsonResource
{
/** @return array<string, mixed> */
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'category_id' => $this->category_id,
'category_name' => $this->category->nombre,
'group_name' => $this->group_name,
'is_featured' => $this->product_layout === ProductLayout::Row,
'type' => $this->source_type->value,
'product_layout' => $this->product_layout->value,
'group_layout' => $this->group_layout->value,
'order' => $this->group_order,
];
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace App\Domains\Catalog\Services;
use App\Domains\Catalog\Enums\FeaturedGroupSource;
use App\Domains\Catalog\Enums\GroupLayout;
use App\Domains\Catalog\Enums\ProductLayout;
use App\Domains\Catalog\Models\Category;
use App\Domains\Catalog\Models\FeaturedGroup;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
class OnTicketFeaturedGroupService
{
/** @return Collection<int, FeaturedGroup> */
public function forTenant(Tenant $tenant): Collection
{
return FeaturedGroup::query()
->where('tenant_code', $tenant->codigo)
->where('source_type', FeaturedGroupSource::Category)
->whereHas('category', fn ($query) => $query->where('tenant_code', $tenant->codigo))
->with('category')
->orderBy('group_order')
->orderBy('id')
->get();
}
/** @param array{category_name: string, is_featured: bool} $data */
public function create(Tenant $tenant, array $data): FeaturedGroup
{
return DB::transaction(function () use ($tenant, $data): FeaturedGroup {
$category = $tenant->categories()->create([
'nombre' => $data['category_name'],
]);
$featuredGroup = FeaturedGroup::query()->create([
'tenant_code' => $tenant->codigo,
'source_type' => FeaturedGroupSource::Category,
'category_id' => $category->id,
'product_layout' => $this->productLayout($data['is_featured']),
'group_layout' => GroupLayout::Paginated,
'group_name' => $data['category_name'],
'group_order' => $this->nextOrder($tenant),
]);
return $featuredGroup->setRelation('category', $category);
});
}
/** @param array{category_name: string, is_featured: bool} $data */
public function update(
Tenant $tenant,
FeaturedGroup $featuredGroup,
array $data,
): FeaturedGroup {
return DB::transaction(function () use ($tenant, $featuredGroup, $data): FeaturedGroup {
$featuredGroup = FeaturedGroup::query()
->whereKey($featuredGroup->getKey())
->where('tenant_code', $tenant->codigo)
->where('source_type', FeaturedGroupSource::Category)
->lockForUpdate()
->firstOrFail();
$category = Category::query()
->whereKey($featuredGroup->category_id)
->where('tenant_code', $tenant->codigo)
->lockForUpdate()
->firstOrFail();
$category->update(['nombre' => $data['category_name']]);
$featuredGroup->update([
'group_name' => $data['category_name'],
'product_layout' => $this->productLayout($data['is_featured']),
'group_layout' => GroupLayout::Paginated,
]);
return $featuredGroup->setRelation('category', $category);
});
}
private function productLayout(bool $isFeatured): ProductLayout
{
return $isFeatured ? ProductLayout::Row : ProductLayout::ColumnWithCart;
}
private function nextOrder(Tenant $tenant): int
{
$maximumOrder = FeaturedGroup::query()
->where('tenant_code', $tenant->codigo)
->max('group_order');
return $maximumOrder === null ? 0 : ((int) $maximumOrder) + 1;
}
}

View File

@@ -0,0 +1,15 @@
<?php
use App\Domains\Catalog\Controllers\AdminApp\OnTicketFeaturedGroupController;
use Illuminate\Support\Facades\Route;
Route::prefix('v1/adminapp/tenant')
->middleware(['auth:sanctum', 'adminapp.tenant'])
->group(function (): void {
Route::get('featured-groups', [OnTicketFeaturedGroupController::class, 'index'])
->name('adminapp.featured-groups.index');
Route::post('featured-groups', [OnTicketFeaturedGroupController::class, 'store'])
->name('adminapp.featured-groups.store');
Route::put('featured-groups/{featuredGroup}', [OnTicketFeaturedGroupController::class, 'update'])
->name('adminapp.featured-groups.update');
});

View File

@@ -14,3 +14,5 @@ Route::prefix('tenants/{tenant:codigo}')->group(function (): void {
Route::get('catalog-items/{catalogItem}', [CatalogController::class, 'show']);
Route::post('catalog-items', [CatalogController::class, 'store']);
});
require __DIR__.'/adminapp.php';