3 Commits

10 changed files with 73 additions and 30 deletions

View File

@@ -10,6 +10,7 @@ use App\Domains\Tenant\Models\Tenant;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class CategoryController extends Controller
@@ -18,10 +19,14 @@ class CategoryController extends Controller
{
return CategoryResource::collection(
Category::query()
->where(function ($query) use ($tenant): void {
$query
->where('tenant_code', $tenant->codigo)
->orWhereNull('tenant_code');
})
->with(['parent', 'subCategories', 'tenant'])
->orderByDesc('id')
->paginateFromRequest()
->get()
)->response();
}
@@ -47,6 +52,7 @@ class CategoryController extends Controller
public function update(UpdateCategoryRequest $request, Tenant $tenant, Category $categoria): CategoryResource
{
$categoria = $this->resolveScopedCategory($tenant, $categoria);
$this->ensureCategoryIsMutable($categoria);
$categoria->update($request->validated());
return CategoryResource::make($categoria->load(['parent', 'subCategories', 'tenant']));
@@ -55,6 +61,7 @@ class CategoryController extends Controller
public function destroy(Tenant $tenant, Category $categoria): Response
{
$categoria = $this->resolveScopedCategory($tenant, $categoria);
$this->ensureCategoryIsMutable($categoria);
$categoria->delete();
return response()->noContent();
@@ -62,10 +69,17 @@ class CategoryController extends Controller
protected function resolveScopedCategory(Tenant $tenant, Category $category): Category
{
if ($category->tenant_code !== $tenant->codigo) {
if ($category->tenant_code !== null && $category->tenant_code !== $tenant->codigo) {
throw new NotFoundHttpException('Category not found for tenant.');
}
return $category;
}
protected function ensureCategoryIsMutable(Category $category): void
{
if ($category->isGlobal()) {
throw new AccessDeniedHttpException('Global categories are read-only.');
}
}
}

View File

@@ -20,7 +20,11 @@ class ProductController extends Controller
return ProductResource::collection(
Product::query()
->where('tenant_codigo', $tenant->codigo)
->with(['attachments', 'brand', 'category'])
->with([
'attachments' => fn ($query) => $query->orderBy('attachments.id')->limit(1),
'brand',
'category',
])
->latest()
->paginateFromRequest()
)->response();
@@ -41,7 +45,7 @@ class ProductController extends Controller
'attachments',
'brand',
'category',
'variants.definitions.attribute',
'variants.definitions.attribute.options',
'variants.attachments',
]));
}

View File

@@ -21,7 +21,7 @@ class ProductVariantController extends Controller
{
$query = ProductVariant::query()
->whereHas('product', fn ($query) => $query->where('tenant_codigo', $tenant->codigo))
->with(['product', 'definitions.attribute', 'attachments'])
->with(['product', 'definitions.attribute.options', 'attachments'])
->latest();
return ProductVariantResource::collection($query->paginateFromRequest())->response();
@@ -41,7 +41,7 @@ class ProductVariantController extends Controller
{
$productVariant = $this->resolveScopedVariant($tenant, $productVariant);
return ProductVariantResource::make($productVariant->load(['product', 'definitions.attribute', 'attachments']));
return ProductVariantResource::make($productVariant->load(['product', 'definitions.attribute.options', 'attachments']));
}
public function update(UpdateProductVariantRequest $request, Tenant $tenant, ProductVariant $productVariant, ProductService $productService): ProductVariantResource

View File

@@ -17,7 +17,6 @@ class AttributeResource extends JsonResource
{
return [
'id' => $this->id,
'tenant_codigo' => $this->tenant_codigo,
'codigo' => $this->codigo,
'nombre' => $this->nombre,
'is_required' => $this->is_required,

View File

@@ -17,7 +17,6 @@ class BrandResource extends JsonResource
{
return [
'id' => $this->id,
'tenant_codigo' => $this->tenant_codigo,
'nombre' => $this->nombre,
'descripcion' => $this->descripcion,
];

View File

@@ -2,7 +2,6 @@
namespace App\Domains\Catalog\Resources;
use App\Domains\Tenant\Resources\TenantResource;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
@@ -18,15 +17,28 @@ class CategoryResource extends JsonResource
{
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')),
'parent' => $this->whenLoaded('parent', fn () => $this->serializeRelatedCategory($this->parent)),
'sub_categories' => $this->whenLoaded('subCategories', fn () =>
$this->subCategories->map(fn ($category) => $this->serializeRelatedCategory($category))->values()
),
];
}
/**
* @return array<string, mixed>|null
*/
protected function serializeRelatedCategory(?\App\Domains\Catalog\Models\Category $category): ?array
{
if ($category === null) {
return null;
}
return [
'id' => $category->id,
'is_global' => $category->isGlobal(),
'nombre' => $category->nombre,
];
}
}

View File

@@ -2,8 +2,6 @@
namespace App\Domains\Catalog\Resources;
use App\Domains\Catalog\Resources\BrandResource;
use App\Domains\Catalog\Resources\CategoryResource;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
@@ -20,20 +18,20 @@ class ProductResource extends JsonResource
return [
'id' => $this->id,
'tenant_codigo' => $this->tenant_codigo,
'categoria_id' => $this->categoria_id,
'category_id' => $this->categoria_id,
'brand_id' => $this->brand_id,
'slug' => $this->slug,
'nombre' => $this->nombre,
'descripcion' => $this->descripcion,
'precio' => $this->precio,
'category' => CategoryResource::make($this->whenLoaded('category')),
'brand' => BrandResource::make($this->whenLoaded('brand')),
'category' => $this->whenLoaded('category', fn () => $this->category?->nombre),
'brand' => $this->whenLoaded('brand', fn () => $this->brand?->nombre),
'images' => $this->whenLoaded('attachments', fn () =>
$this->attachments->map(fn ($attachment) => $attachment->getTemporaryUrl(1440))->values()
$this->attachments
->map(fn ($attachment) => $attachment->getTemporaryUrl(1440))
->values()
),
'variants' => ProductVariantResource::collection($this->whenLoaded('variants')),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}

View File

@@ -20,7 +20,26 @@ class ProductVariantDefinitionResource extends JsonResource
'producto_variante_id' => $this->producto_variante_id,
'attribute_id' => $this->attribute_id,
'value' => $this->value,
'attribute' => AttributeResource::make($this->whenLoaded('attribute')),
'attribute' => $this->whenLoaded('attribute', fn () => $this->attribute?->nombre),
'metadata' => $this->resolveMetadata(),
];
}
/**
* @return array<string, mixed>|null
*/
protected function resolveMetadata(): ?array
{
if (! $this->relationLoaded('attribute') || ! $this->attribute?->relationLoaded('options')) {
return null;
}
$option = $this->attribute->options->firstWhere('value', $this->value);
if ($option === null || $option->metadata === null) {
return null;
}
return $option->metadata;
}
}

View File

@@ -40,8 +40,6 @@ class ProductVariantResource extends JsonResource
?->values()
?? collect();
}),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}

View File

@@ -112,7 +112,7 @@ class ProductService
$this->syncVariantImages($variant, $images);
}
return $variant->load(['product', 'definitions.attribute', 'attachments']);
return $variant->load(['product', 'definitions.attribute.options', 'attachments']);
});
}
@@ -136,7 +136,7 @@ class ProductService
$this->syncVariantImages($updatedVariant, $images);
}
return $updatedVariant->load(['product', 'definitions.attribute', 'attachments']);
return $updatedVariant->load(['product', 'definitions.attribute.options', 'attachments']);
});
}