feat: add brand and category relationships to products, update requests and resources, and seed product catalog from images
This commit is contained in:
@@ -20,7 +20,7 @@ class ProductController extends Controller
|
||||
return ProductResource::collection(
|
||||
Product::query()
|
||||
->where('tenant_codigo', $tenant->codigo)
|
||||
->with('attachments')
|
||||
->with(['attachments', 'brand', 'category'])
|
||||
->latest()
|
||||
->paginateFromRequest()
|
||||
)->response();
|
||||
@@ -37,7 +37,13 @@ class ProductController extends Controller
|
||||
{
|
||||
$producto = $this->resolveScopedProduct($tenant, $producto);
|
||||
|
||||
return ProductResource::make($producto->load(['attachments', 'variants.definitions.attribute', 'variants.attachments']));
|
||||
return ProductResource::make($producto->load([
|
||||
'attachments',
|
||||
'brand',
|
||||
'category',
|
||||
'variants.definitions.attribute',
|
||||
'variants.attachments',
|
||||
]));
|
||||
}
|
||||
|
||||
public function update(UpdateProductRequest $request, Tenant $tenant, Product $producto, ProductService $productService): ProductResource
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use App\Domains\Attachable\Models\Attachment;
|
||||
use App\Domains\Catalog\Models\Brand;
|
||||
use App\Domains\Catalog\Models\Category;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -14,6 +16,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
#[Fillable([
|
||||
'tenant_codigo',
|
||||
'categoria_id',
|
||||
'brand_id',
|
||||
'slug',
|
||||
'nombre',
|
||||
'descripcion',
|
||||
@@ -29,6 +32,7 @@ class Product extends Model
|
||||
{
|
||||
return [
|
||||
'categoria_id' => 'integer',
|
||||
'brand_id' => 'integer',
|
||||
'precio' => 'decimal:2',
|
||||
];
|
||||
}
|
||||
@@ -41,6 +45,22 @@ class Product extends Model
|
||||
return $this->belongsTo(Tenant::class, 'tenant_codigo', 'codigo');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<Category, $this>
|
||||
*/
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Category::class, 'categoria_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<Brand, $this>
|
||||
*/
|
||||
public function brand(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Brand::class, 'brand_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany<ProductVariant, $this>
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,13 @@ class StoreProductRequest extends FormRequest
|
||||
{
|
||||
return [
|
||||
'categoria_id' => ['required', 'integer'],
|
||||
'brand_id' => [
|
||||
'required',
|
||||
'integer',
|
||||
Rule::exists('brands', 'id')->where(
|
||||
fn ($query) => $query->where('tenant_codigo', $this->route('tenant')?->codigo)
|
||||
),
|
||||
],
|
||||
'slug' => ['required', 'string', 'max:255', Rule::unique('productos', 'slug')],
|
||||
'nombre' => ['required', 'string', 'max:255'],
|
||||
'descripcion' => ['nullable', 'string'],
|
||||
|
||||
@@ -24,6 +24,13 @@ class UpdateProductRequest extends FormRequest
|
||||
|
||||
return [
|
||||
'categoria_id' => ['required', 'integer'],
|
||||
'brand_id' => [
|
||||
'required',
|
||||
'integer',
|
||||
Rule::exists('brands', 'id')->where(
|
||||
fn ($query) => $query->where('tenant_codigo', $this->route('tenant')?->codigo)
|
||||
),
|
||||
],
|
||||
'slug' => [
|
||||
'required',
|
||||
'string',
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
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;
|
||||
|
||||
@@ -19,10 +21,13 @@ class ProductResource extends JsonResource
|
||||
'id' => $this->id,
|
||||
'tenant_codigo' => $this->tenant_codigo,
|
||||
'categoria_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')),
|
||||
'images' => $this->whenLoaded('attachments', fn () =>
|
||||
$this->attachments->map(fn ($attachment) => $attachment->getTemporaryUrl(1440))->values()
|
||||
),
|
||||
|
||||
@@ -37,7 +37,7 @@ class ProductService
|
||||
$this->syncProductImages($product, $images);
|
||||
}
|
||||
|
||||
return $product->load(['attributes', 'attachments']);
|
||||
return $product->load(['attributes', 'attachments', 'brand', 'category']);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ class ProductService
|
||||
$this->syncProductImages($product, $images);
|
||||
}
|
||||
|
||||
return $product->load(['attributes', 'attachments']);
|
||||
return $product->load(['attributes', 'attachments', 'brand', 'category']);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ class ProductService
|
||||
{
|
||||
DB::transaction(function () use ($product) {
|
||||
foreach ($product->variants as $variant) {
|
||||
$this->deleteVariantAttachments($variant);
|
||||
$product->deleteVariant($variant);
|
||||
}
|
||||
|
||||
@@ -108,7 +109,7 @@ class ProductService
|
||||
$variant = $product->createVariant($data);
|
||||
|
||||
if (! empty($images)) {
|
||||
$this->syncImages($variant, $images);
|
||||
$this->syncVariantImages($variant, $images);
|
||||
}
|
||||
|
||||
return $variant->load(['product', 'definitions.attribute', 'attachments']);
|
||||
@@ -132,7 +133,7 @@ class ProductService
|
||||
$updatedVariant = $product->updateVariant($variant, $data);
|
||||
|
||||
if ($hasImages) {
|
||||
$this->syncImages($updatedVariant, $images);
|
||||
$this->syncVariantImages($updatedVariant, $images);
|
||||
}
|
||||
|
||||
return $updatedVariant->load(['product', 'definitions.attribute', 'attachments']);
|
||||
@@ -147,6 +148,7 @@ class ProductService
|
||||
DB::transaction(function () use ($variant) {
|
||||
/** @var Product $product */
|
||||
$product = $variant->product;
|
||||
$this->deleteVariantAttachments($variant);
|
||||
$product->deleteVariant($variant);
|
||||
});
|
||||
}
|
||||
@@ -183,14 +185,9 @@ class ProductService
|
||||
*
|
||||
* @param array<int, UploadedFile|string> $images
|
||||
*/
|
||||
protected function syncImages(ProductVariant $variant, array $images): void
|
||||
protected function syncVariantImages(ProductVariant $variant, array $images): void
|
||||
{
|
||||
// Detach pivot record and delete attachment from S3 and database
|
||||
$existing = $variant->attachments()->get();
|
||||
$variant->attachments()->detach();
|
||||
foreach ($existing as $attachment) {
|
||||
$this->attachmentService->delete($attachment);
|
||||
}
|
||||
$this->deleteVariantAttachments($variant);
|
||||
|
||||
$attachmentIds = [];
|
||||
|
||||
@@ -205,7 +202,7 @@ class ProductService
|
||||
/**
|
||||
* Upload a list of image files/base64 strings and sync them to a product.
|
||||
*
|
||||
* Same logic as syncImages but for products without variants.
|
||||
* Same logic as syncVariantImages but for products without variants.
|
||||
*
|
||||
* @param array<int, UploadedFile|string> $images
|
||||
*/
|
||||
@@ -228,6 +225,16 @@ class ProductService
|
||||
$product->attachments()->sync($attachmentIds);
|
||||
}
|
||||
|
||||
protected function deleteVariantAttachments(ProductVariant $variant): void
|
||||
{
|
||||
$existing = $variant->attachments()->get();
|
||||
$variant->attachments()->detach();
|
||||
|
||||
foreach ($existing as $attachment) {
|
||||
$this->attachmentService->delete($attachment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an attribute.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user