feat: implement product variant CRUD operations and tenant configuration seeding
This commit is contained in:
@@ -21,7 +21,7 @@ class ProductVariantController extends Controller
|
|||||||
{
|
{
|
||||||
$query = ProductVariant::query()
|
$query = ProductVariant::query()
|
||||||
->whereHas('product', fn ($query) => $query->where('tenant_codigo', $tenant->codigo))
|
->whereHas('product', fn ($query) => $query->where('tenant_codigo', $tenant->codigo))
|
||||||
->with(['product', 'definitions.attribute'])
|
->with(['product', 'definitions.attribute', 'attachments'])
|
||||||
->latest();
|
->latest();
|
||||||
|
|
||||||
return ProductVariantResource::collection($query->paginateFromRequest())->response();
|
return ProductVariantResource::collection($query->paginateFromRequest())->response();
|
||||||
@@ -41,7 +41,7 @@ class ProductVariantController extends Controller
|
|||||||
{
|
{
|
||||||
$productVariant = $this->resolveScopedVariant($tenant, $productVariant);
|
$productVariant = $this->resolveScopedVariant($tenant, $productVariant);
|
||||||
|
|
||||||
return ProductVariantResource::make($productVariant->load(['product', 'definitions.attribute']));
|
return ProductVariantResource::make($productVariant->load(['product', 'definitions.attribute', 'attachments']));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(UpdateProductVariantRequest $request, Tenant $tenant, ProductVariant $productVariant, ProductService $productService): ProductVariantResource
|
public function update(UpdateProductVariantRequest $request, Tenant $tenant, ProductVariant $productVariant, ProductService $productService): ProductVariantResource
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Domains\Catalog\Requests;
|
namespace App\Domains\Catalog\Requests;
|
||||||
|
|
||||||
|
use App\Domains\Shared\Rules\ImageOrBase64Rule;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
@@ -59,6 +60,8 @@ class StoreProductVariantRequest extends FormRequest
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
'definitions.*.value' => ['nullable', 'string'],
|
'definitions.*.value' => ['nullable', 'string'],
|
||||||
|
'images' => ['sometimes', 'nullable', 'array'],
|
||||||
|
'images.*' => ['required', new ImageOrBase64Rule()],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Domains\Catalog\Requests;
|
namespace App\Domains\Catalog\Requests;
|
||||||
|
|
||||||
use App\Domains\Catalog\Models\ProductVariant;
|
use App\Domains\Catalog\Models\ProductVariant;
|
||||||
|
use App\Domains\Shared\Rules\ImageOrBase64Rule;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
@@ -63,6 +64,8 @@ class UpdateProductVariantRequest extends FormRequest
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
'definitions.*.value' => ['nullable', 'string'],
|
'definitions.*.value' => ['nullable', 'string'],
|
||||||
|
'images' => ['sometimes', 'nullable', 'array'],
|
||||||
|
'images.*' => ['required', new ImageOrBase64Rule()],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ class ProductVariantResource extends JsonResource
|
|||||||
'precio' => $this->precio,
|
'precio' => $this->precio,
|
||||||
'product' => ProductResource::make($this->whenLoaded('product')),
|
'product' => ProductResource::make($this->whenLoaded('product')),
|
||||||
'definitions' => ProductVariantDefinitionResource::collection($this->whenLoaded('definitions')),
|
'definitions' => ProductVariantDefinitionResource::collection($this->whenLoaded('definitions')),
|
||||||
|
'images' => $this->whenLoaded('attachments', fn () =>
|
||||||
|
$this->attachments->map(fn ($attachment) => $attachment->getTemporaryUrl(1440))->values()
|
||||||
|
),
|
||||||
'created_at' => $this->created_at,
|
'created_at' => $this->created_at,
|
||||||
'updated_at' => $this->updated_at,
|
'updated_at' => $this->updated_at,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,14 +2,17 @@
|
|||||||
|
|
||||||
namespace App\Domains\Catalog\Services;
|
namespace App\Domains\Catalog\Services;
|
||||||
|
|
||||||
|
use App\Domains\Attachable\Services\AttachmentService;
|
||||||
use App\Domains\Catalog\Models\Attribute;
|
use App\Domains\Catalog\Models\Attribute;
|
||||||
use App\Domains\Catalog\Models\Product;
|
use App\Domains\Catalog\Models\Product;
|
||||||
use App\Domains\Catalog\Models\ProductVariant;
|
use App\Domains\Catalog\Models\ProductVariant;
|
||||||
use App\Domains\Tenant\Models\Tenant;
|
use App\Domains\Tenant\Models\Tenant;
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class ProductService
|
class ProductService
|
||||||
{
|
{
|
||||||
|
public function __construct(protected AttachmentService $attachmentService) {}
|
||||||
/**
|
/**
|
||||||
* Create a product.
|
* Create a product.
|
||||||
*
|
*
|
||||||
@@ -80,9 +83,16 @@ class ProductService
|
|||||||
public function createVariant(Product $product, array $data): ProductVariant
|
public function createVariant(Product $product, array $data): ProductVariant
|
||||||
{
|
{
|
||||||
return DB::transaction(function () use ($product, $data) {
|
return DB::transaction(function () use ($product, $data) {
|
||||||
|
$images = $data['images'] ?? [];
|
||||||
|
unset($data['images']);
|
||||||
|
|
||||||
$variant = $product->createVariant($data);
|
$variant = $product->createVariant($data);
|
||||||
|
|
||||||
return $variant->load(['product', 'definitions.attribute']);
|
if (! empty($images)) {
|
||||||
|
$this->syncImages($variant, $images);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $variant->load(['product', 'definitions.attribute', 'attachments']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,11 +104,19 @@ class ProductService
|
|||||||
public function updateVariant(ProductVariant $variant, array $data): ProductVariant
|
public function updateVariant(ProductVariant $variant, array $data): ProductVariant
|
||||||
{
|
{
|
||||||
return DB::transaction(function () use ($variant, $data) {
|
return DB::transaction(function () use ($variant, $data) {
|
||||||
|
$hasImages = array_key_exists('images', $data);
|
||||||
|
$images = $data['images'] ?? [];
|
||||||
|
unset($data['images']);
|
||||||
|
|
||||||
/** @var Product $product */
|
/** @var Product $product */
|
||||||
$product = $variant->product;
|
$product = $variant->product;
|
||||||
$updatedVariant = $product->updateVariant($variant, $data);
|
$updatedVariant = $product->updateVariant($variant, $data);
|
||||||
|
|
||||||
return $updatedVariant->load(['product', 'definitions.attribute']);
|
if ($hasImages) {
|
||||||
|
$this->syncImages($updatedVariant, $images);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $updatedVariant->load(['product', 'definitions.attribute', 'attachments']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,6 +156,29 @@ class ProductService
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload a list of image files/base64 strings and sync them to a variant.
|
||||||
|
*
|
||||||
|
* When called on update, the existing attachments are detached first so the
|
||||||
|
* final set always matches exactly what was sent in the request.
|
||||||
|
*
|
||||||
|
* @param array<int, UploadedFile|string> $images
|
||||||
|
*/
|
||||||
|
protected function syncImages(ProductVariant $variant, array $images): void
|
||||||
|
{
|
||||||
|
// Detach any existing attachments before replacing them
|
||||||
|
$variant->attachments()->detach();
|
||||||
|
|
||||||
|
$attachmentIds = [];
|
||||||
|
|
||||||
|
foreach ($images as $image) {
|
||||||
|
$attachment = $this->attachmentService->store($image, 'variants');
|
||||||
|
$attachmentIds[] = $attachment->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$variant->attachments()->sync($attachmentIds);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete an attribute.
|
* Delete an attribute.
|
||||||
*/
|
*/
|
||||||
|
|||||||
54
app/Domains/Shared/Rules/ImageOrBase64Rule.php
Normal file
54
app/Domains/Shared/Rules/ImageOrBase64Rule.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Shared\Rules;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
|
|
||||||
|
class ImageOrBase64Rule implements ValidationRule
|
||||||
|
{
|
||||||
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||||
|
{
|
||||||
|
if ($value instanceof UploadedFile) {
|
||||||
|
$mime = $value->getMimeType();
|
||||||
|
if (! str_starts_with((string) $mime, 'image/')) {
|
||||||
|
$fail("The :attribute must be a valid image file.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($value)) {
|
||||||
|
$payload = trim($value);
|
||||||
|
|
||||||
|
if ($payload === '') {
|
||||||
|
$fail("The :attribute must not be empty.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$declaredMimeType = null;
|
||||||
|
if (preg_match('/^data:(?<mime>[-\w.+\/]+);base64,(?<data>.+)$/s', $payload, $matches) === 1) {
|
||||||
|
$declaredMimeType = strtolower($matches['mime']);
|
||||||
|
$payload = $matches['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$decoded = base64_decode(preg_replace('/\s+/', '', $payload), true);
|
||||||
|
if ($decoded === false || $decoded === '') {
|
||||||
|
$fail("The :attribute must be a valid base64-encoded image.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$finfo = new \finfo(FILEINFO_MIME_TYPE);
|
||||||
|
$detectedMime = $finfo->buffer($decoded);
|
||||||
|
$mime = $detectedMime ?: $declaredMimeType;
|
||||||
|
|
||||||
|
if (! $mime || ! str_starts_with((string) $mime, 'image/')) {
|
||||||
|
$fail("The :attribute must be a valid image (file or base64).");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fail("The :attribute must be an image file or a base64-encoded image string.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Domains\Tenant\Requests;
|
namespace App\Domains\Tenant\Requests;
|
||||||
|
|
||||||
|
use App\Domains\Shared\Rules\ImageOrBase64Rule;
|
||||||
use App\Domains\Tenant\Support\TenantDomainNormalizer;
|
use App\Domains\Tenant\Support\TenantDomainNormalizer;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
use Illuminate\Http\UploadedFile;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
class StoreTenantRequest extends FormRequest
|
class StoreTenantRequest extends FormRequest
|
||||||
@@ -36,51 +35,8 @@ class StoreTenantRequest extends FormRequest
|
|||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
$logoRule = [
|
$logoRule = ['required', new ImageOrBase64Rule()];
|
||||||
'required',
|
|
||||||
static function (string $attribute, mixed $value, Closure $fail): void {
|
|
||||||
if ($value instanceof UploadedFile) {
|
|
||||||
$mime = $value->getMimeType();
|
|
||||||
if (! str_starts_with($mime, 'image/')) {
|
|
||||||
$fail("The {$attribute} must be a valid image or SVG.");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_string($value)) {
|
|
||||||
if (Str::isUuid($value)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$payload = trim($value);
|
|
||||||
$declaredMimeType = null;
|
|
||||||
if (preg_match('/^data:(?<mime>[-\w.+\/]+);base64,(?<data>.+)$/s', $payload, $matches) === 1) {
|
|
||||||
$declaredMimeType = strtolower($matches['mime']);
|
|
||||||
$payload = $matches['data'];
|
|
||||||
}
|
|
||||||
$decoded = base64_decode(preg_replace('/\s+/', '', $payload), true);
|
|
||||||
if ($decoded === false || $decoded === '') {
|
|
||||||
$fail("The {$attribute} must be a valid base64 image or SVG.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$finfo = new \finfo(FILEINFO_MIME_TYPE);
|
|
||||||
$mime = $finfo->buffer($decoded);
|
|
||||||
if (! $mime && $declaredMimeType) {
|
|
||||||
$mime = $declaredMimeType;
|
|
||||||
}
|
|
||||||
if (! $mime || ! str_starts_with($mime, 'image/')) {
|
|
||||||
$fail("The {$attribute} must be a valid image or SVG.");
|
|
||||||
}
|
|
||||||
} catch (\Throwable) {
|
|
||||||
$fail("The {$attribute} must be a valid image or SVG.");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fail("The {$attribute} must be a valid file or base64 string.");
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'codigo' => ['required', 'string', 'max:255', Rule::unique('tenants', 'codigo')],
|
'codigo' => ['required', 'string', 'max:255', Rule::unique('tenants', 'codigo')],
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Domains\Tenant\Requests;
|
namespace App\Domains\Tenant\Requests;
|
||||||
|
|
||||||
|
use App\Domains\Shared\Rules\ImageOrBase64Rule;
|
||||||
use App\Domains\Tenant\Models\Tenant;
|
use App\Domains\Tenant\Models\Tenant;
|
||||||
use App\Domains\Tenant\Support\TenantDomainNormalizer;
|
use App\Domains\Tenant\Support\TenantDomainNormalizer;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
use Illuminate\Http\UploadedFile;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
class UpdateTenantRequest extends FormRequest
|
class UpdateTenantRequest extends FormRequest
|
||||||
@@ -42,51 +41,8 @@ class UpdateTenantRequest extends FormRequest
|
|||||||
/** @var Tenant|null $tenant */
|
/** @var Tenant|null $tenant */
|
||||||
$tenant = $this->route('tenant');
|
$tenant = $this->route('tenant');
|
||||||
|
|
||||||
$logoRule = [
|
$logoRule = ['nullable', new ImageOrBase64Rule()];
|
||||||
'nullable',
|
|
||||||
static function (string $attribute, mixed $value, Closure $fail): void {
|
|
||||||
if ($value instanceof UploadedFile) {
|
|
||||||
$mime = $value->getMimeType();
|
|
||||||
if (! str_starts_with($mime, 'image/')) {
|
|
||||||
$fail("The {$attribute} must be a valid image or SVG.");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_string($value)) {
|
|
||||||
if (Str::isUuid($value)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$payload = trim($value);
|
|
||||||
$declaredMimeType = null;
|
|
||||||
if (preg_match('/^data:(?<mime>[-\w.+\/]+);base64,(?<data>.+)$/s', $payload, $matches) === 1) {
|
|
||||||
$declaredMimeType = strtolower($matches['mime']);
|
|
||||||
$payload = $matches['data'];
|
|
||||||
}
|
|
||||||
$decoded = base64_decode(preg_replace('/\s+/', '', $payload), true);
|
|
||||||
if ($decoded === false || $decoded === '') {
|
|
||||||
$fail("The {$attribute} must be a valid base64 image or SVG.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$finfo = new \finfo(FILEINFO_MIME_TYPE);
|
|
||||||
$mime = $finfo->buffer($decoded);
|
|
||||||
if (! $mime && $declaredMimeType) {
|
|
||||||
$mime = $declaredMimeType;
|
|
||||||
}
|
|
||||||
if (! $mime || ! str_starts_with($mime, 'image/')) {
|
|
||||||
$fail("The {$attribute} must be a valid image or SVG.");
|
|
||||||
}
|
|
||||||
} catch (\Throwable) {
|
|
||||||
$fail("The {$attribute} must be a valid image or SVG.");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fail("The {$attribute} must be a valid file or base64 string.");
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'codigo' => [
|
'codigo' => [
|
||||||
|
|||||||
114
database/seeders/AttributeSeeder.php
Normal file
114
database/seeders/AttributeSeeder.php
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use App\Domains\Catalog\Models\Attribute;
|
||||||
|
use App\Domains\Catalog\Models\Product;
|
||||||
|
use App\Domains\Shared\Enums\FieldType;
|
||||||
|
use App\Domains\Tenant\Models\Tenant;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
|
class AttributeSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$tenants = Tenant::all();
|
||||||
|
|
||||||
|
foreach ($tenants as $tenant) {
|
||||||
|
// Seed Talle (Size - Text options) attribute
|
||||||
|
$existingTalle = Attribute::query()
|
||||||
|
->where('tenant_codigo', $tenant->codigo)
|
||||||
|
->where('codigo', 'talle')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($existingTalle) {
|
||||||
|
Product::deleteAttribute($existingTalle);
|
||||||
|
}
|
||||||
|
|
||||||
|
Product::createAttribute($tenant, [
|
||||||
|
'codigo' => 'talle',
|
||||||
|
'nombre' => 'Talle',
|
||||||
|
'type' => FieldType::Select->value,
|
||||||
|
'is_required' => true,
|
||||||
|
'options' => [
|
||||||
|
['value' => 'S', 'label' => 'S', 'sort_order' => 1],
|
||||||
|
['value' => 'M', 'label' => 'M', 'sort_order' => 2],
|
||||||
|
['value' => 'L', 'label' => 'L', 'sort_order' => 3],
|
||||||
|
['value' => 'XL', 'label' => 'XL', 'sort_order' => 4],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Seed Talle Numérico (Numeric Size options) attribute
|
||||||
|
$existingTalleNumerico = Attribute::query()
|
||||||
|
->where('tenant_codigo', $tenant->codigo)
|
||||||
|
->where('codigo', 'talle_numerico')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($existingTalleNumerico) {
|
||||||
|
Product::deleteAttribute($existingTalleNumerico);
|
||||||
|
}
|
||||||
|
|
||||||
|
Product::createAttribute($tenant, [
|
||||||
|
'codigo' => 'talle_numerico',
|
||||||
|
'nombre' => 'Talle Numérico',
|
||||||
|
'type' => FieldType::Select->value,
|
||||||
|
'is_required' => true,
|
||||||
|
'options' => [
|
||||||
|
['value' => '38', 'label' => '38', 'sort_order' => 1],
|
||||||
|
['value' => '40', 'label' => '40', 'sort_order' => 2],
|
||||||
|
['value' => '42', 'label' => '42', 'sort_order' => 3],
|
||||||
|
['value' => '44', 'label' => '44', 'sort_order' => 4],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Seed Color attribute
|
||||||
|
$existingColor = Attribute::query()
|
||||||
|
->where('tenant_codigo', $tenant->codigo)
|
||||||
|
->where('codigo', 'color')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($existingColor) {
|
||||||
|
Product::deleteAttribute($existingColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
Product::createAttribute($tenant, [
|
||||||
|
'codigo' => 'color',
|
||||||
|
'nombre' => 'Color',
|
||||||
|
'type' => FieldType::Select->value,
|
||||||
|
'is_required' => true,
|
||||||
|
'metadata_schema' => [
|
||||||
|
'hex' => ['type' => 'string'],
|
||||||
|
],
|
||||||
|
'options' => [
|
||||||
|
[
|
||||||
|
'value' => 'Negro',
|
||||||
|
'label' => 'Negro',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'metadata' => ['hex' => '#000000'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'value' => 'Gris',
|
||||||
|
'label' => 'Gris',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'metadata' => ['hex' => '#808080'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'value' => 'Blanco',
|
||||||
|
'label' => 'Blanco',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'metadata' => ['hex' => '#FFFFFF'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'value' => 'Azul',
|
||||||
|
'label' => 'Azul',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'metadata' => ['hex' => '#0000FF'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ class DatabaseSeeder extends Seeder
|
|||||||
|
|
||||||
$this->call([
|
$this->call([
|
||||||
TenantSeeder::class,
|
TenantSeeder::class,
|
||||||
|
AttributeSeeder::class,
|
||||||
CategorySeeder::class,
|
CategorySeeder::class,
|
||||||
BrandSeeder::class,
|
BrandSeeder::class,
|
||||||
]);
|
]);
|
||||||
|
|||||||
Reference in New Issue
Block a user