feat: implement product and variant CRUD management service with attachment handling
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Domains\Catalog\Controllers;
|
namespace App\Domains\Catalog\Controllers;
|
||||||
|
|
||||||
use App\Domains\Catalog\Models\Product;
|
use App\Domains\Catalog\Models\Product;
|
||||||
|
use App\Domains\Catalog\Requests\ProductDetailRequest;
|
||||||
use App\Domains\Catalog\Requests\StoreProductRequest;
|
use App\Domains\Catalog\Requests\StoreProductRequest;
|
||||||
use App\Domains\Catalog\Requests\UpdateProductRequest;
|
use App\Domains\Catalog\Requests\UpdateProductRequest;
|
||||||
use App\Domains\Catalog\Resources\ProductResource;
|
use App\Domains\Catalog\Resources\ProductResource;
|
||||||
@@ -30,11 +31,11 @@ class ProductController extends Controller
|
|||||||
return ProductResource::make($product)->response()->setStatusCode(201);
|
return ProductResource::make($product)->response()->setStatusCode(201);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show(Request $request, Tenant $tenant, Product $producto, ProductService $productService): ProductResource
|
public function show(ProductDetailRequest $request, Tenant $tenant, Product $producto, ProductService $productService): ProductResource
|
||||||
{
|
{
|
||||||
$producto = $this->resolveScopedProduct($tenant, $producto);
|
$producto = $this->resolveScopedProduct($tenant, $producto);
|
||||||
$variantId = $request->query('variant_id');
|
$variantId = $request->query('variant_id');
|
||||||
$variantId = is_numeric($variantId) ? (int) $variantId : null;
|
$variantId = $variantId !== null ? (int) $variantId : null;
|
||||||
$producto = $productService->getProductDetail($tenant, $producto, $variantId);
|
$producto = $productService->getProductDetail($tenant, $producto, $variantId);
|
||||||
|
|
||||||
return ProductResource::make($producto);
|
return ProductResource::make($producto);
|
||||||
|
|||||||
23
app/Domains/Catalog/Requests/ProductDetailRequest.php
Normal file
23
app/Domains/Catalog/Requests/ProductDetailRequest.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Catalog\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ProductDetailRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'variant_id' => ['sometimes', 'integer'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ use App\Domains\Tenant\Models\Tenant;
|
|||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Http\UploadedFile;
|
use Illuminate\Http\UploadedFile;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
|
||||||
class ProductService
|
class ProductService
|
||||||
@@ -332,6 +333,12 @@ class ProductService
|
|||||||
throw new NotFoundHttpException('Product variant not found for product.');
|
throw new NotFoundHttpException('Product variant not found for product.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($variantId !== null && $selectedVariant->stock <= 0) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'variant_id' => 'La variante seleccionada no tiene stock.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$selectedVariant ??= $product->variants->first();
|
$selectedVariant ??= $product->variants->first();
|
||||||
|
|
||||||
if ($selectedVariant !== null) {
|
if ($selectedVariant !== null) {
|
||||||
|
|||||||
@@ -722,6 +722,52 @@ class ProductControllerTest extends TestCase
|
|||||||
$this->assertEquals(0, $defaultVariant->definitions()->count());
|
$this->assertEquals(0, $defaultVariant->definitions()->count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_it_rejects_product_detail_when_requested_variant_has_no_stock(): void
|
||||||
|
{
|
||||||
|
$product = Product::create([
|
||||||
|
'tenant_codigo' => $this->tenant->codigo,
|
||||||
|
'categoria_id' => 1,
|
||||||
|
'brand_id' => $this->brand->id,
|
||||||
|
'slug' => 'test-product-no-stock',
|
||||||
|
'nombre' => 'Test Product No Stock',
|
||||||
|
'precio' => 100.00,
|
||||||
|
]);
|
||||||
|
$productAttributes = $this->syncVariantAttributes($product);
|
||||||
|
|
||||||
|
$outOfStockVariant = $product->createVariant([
|
||||||
|
'stock' => 0,
|
||||||
|
'definitions' => [
|
||||||
|
[
|
||||||
|
'products_attribute_id' => $productAttributes['size']->id,
|
||||||
|
'value' => 'S',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->getJson("/api/tenants/{$this->tenant->codigo}/productos/{$product->id}?variant_id={$outOfStockVariant->id}");
|
||||||
|
|
||||||
|
$response->assertUnprocessable();
|
||||||
|
$response->assertJsonValidationErrors(['variant_id']);
|
||||||
|
$response->assertJsonPath('errors.variant_id.0', 'La variante seleccionada no tiene stock.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_rejects_product_detail_when_requested_variant_id_is_invalid_format(): void
|
||||||
|
{
|
||||||
|
$product = Product::create([
|
||||||
|
'tenant_codigo' => $this->tenant->codigo,
|
||||||
|
'categoria_id' => 1,
|
||||||
|
'brand_id' => $this->brand->id,
|
||||||
|
'slug' => 'test-product-invalid-format',
|
||||||
|
'nombre' => 'Test Product Invalid Format',
|
||||||
|
'precio' => 100.00,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->getJson("/api/tenants/{$this->tenant->codigo}/productos/{$product->id}?variant_id=abc");
|
||||||
|
|
||||||
|
$response->assertUnprocessable();
|
||||||
|
$response->assertJsonValidationErrors(['variant_id']);
|
||||||
|
}
|
||||||
|
|
||||||
private function createAttachment(string $path): Attachment
|
private function createAttachment(string $path): Attachment
|
||||||
{
|
{
|
||||||
return Attachment::create([
|
return Attachment::create([
|
||||||
|
|||||||
Reference in New Issue
Block a user