feat(catalog): add show_in_selector attribute to item attributes and update related logic
This commit is contained in:
@@ -13,6 +13,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
'attribute_id',
|
||||
'allow_multi_select',
|
||||
'sort_order',
|
||||
'show_in_selector',
|
||||
])]
|
||||
class ItemAttribute extends Model
|
||||
{
|
||||
@@ -20,6 +21,10 @@ class ItemAttribute extends Model
|
||||
|
||||
protected $table = 'item_attributes';
|
||||
|
||||
protected $attributes = [
|
||||
'show_in_selector' => true,
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
@@ -27,6 +32,7 @@ class ItemAttribute extends Model
|
||||
'attribute_id' => 'integer',
|
||||
'allow_multi_select' => 'boolean',
|
||||
'sort_order' => 'integer',
|
||||
'show_in_selector' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,15 @@ class StoreCatalogItemRequest extends FormRequest
|
||||
fn ($query) => $query->where('tenant_codigo', $tenantCode)
|
||||
),
|
||||
],
|
||||
'hidden_attribute_codes' => [Rule::prohibitedIf($isBundle), 'sometimes', 'array'],
|
||||
'hidden_attribute_codes.*' => [
|
||||
'required',
|
||||
'string',
|
||||
'distinct',
|
||||
Rule::exists('attribute', 'codigo')->where(
|
||||
fn ($query) => $query->where('tenant_codigo', $tenantCode)
|
||||
),
|
||||
],
|
||||
'images' => ['sometimes', 'array'],
|
||||
'images.*' => ['required', new ImageOrBase64Rule],
|
||||
'variants' => [Rule::prohibitedIf($isBundle), 'sometimes', 'array'],
|
||||
|
||||
@@ -89,6 +89,7 @@ class CatalogItemDetailResource extends JsonResource
|
||||
'codigo' => $attribute->codigo,
|
||||
'nombre' => $attribute->nombre,
|
||||
'sort_order' => $itemAttribute->sort_order,
|
||||
'show_in_selector' => $itemAttribute->show_in_selector,
|
||||
'is_required' => $attribute->is_required,
|
||||
'allow_multi_select' => $itemAttribute->allow_multi_select,
|
||||
'metadata_schema' => $attribute->metadata_schema,
|
||||
|
||||
@@ -37,6 +37,7 @@ class CatalogService
|
||||
$images = $data['images'] ?? [];
|
||||
$attributeCodes = $data['attribute_codes'] ?? [];
|
||||
$multiSelectAttributeCodes = $data['multi_select_attribute_codes'] ?? [];
|
||||
$hiddenAttributeCodes = $data['hidden_attribute_codes'] ?? [];
|
||||
$components = $data['components'] ?? [];
|
||||
$hasDirectStock = array_key_exists('real_stock', $data);
|
||||
$realStock = (int) ($data['real_stock'] ?? 0);
|
||||
@@ -64,6 +65,14 @@ class CatalogService
|
||||
]);
|
||||
}
|
||||
|
||||
if (array_diff($hiddenAttributeCodes, $attributeCodes) !== []) {
|
||||
throw ValidationException::withMessages([
|
||||
'hidden_attribute_codes' => [
|
||||
__('api.catalog.hidden_attribute_not_on_item'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$this->validateUniqueVariantCombinations($variants, $attributeCodes);
|
||||
|
||||
if ($type === CatalogItemType::Bundle) {
|
||||
@@ -88,6 +97,7 @@ class CatalogService
|
||||
$data['images'],
|
||||
$data['attribute_codes'],
|
||||
$data['multi_select_attribute_codes'],
|
||||
$data['hidden_attribute_codes'],
|
||||
$data['components'],
|
||||
$data['real_stock'],
|
||||
$data['reserved_stock'],
|
||||
@@ -109,7 +119,12 @@ class CatalogService
|
||||
|
||||
$catalogItem = CatalogItem::query()->create($data);
|
||||
$itemAttributes = $type === CatalogItemType::Standard
|
||||
? $this->createItemAttributes($catalogItem, $attributeCodes, $multiSelectAttributeCodes)
|
||||
? $this->createItemAttributes(
|
||||
$catalogItem,
|
||||
$attributeCodes,
|
||||
$multiSelectAttributeCodes,
|
||||
$hiddenAttributeCodes,
|
||||
)
|
||||
: [];
|
||||
|
||||
if ($type === CatalogItemType::Bundle) {
|
||||
@@ -488,12 +503,14 @@ class CatalogService
|
||||
/**
|
||||
* @param array<int, string> $attributeCodes
|
||||
* @param array<int, string> $multiSelectAttributeCodes
|
||||
* @param array<int, string> $hiddenAttributeCodes
|
||||
* @return array<string, ItemAttribute>
|
||||
*/
|
||||
private function createItemAttributes(
|
||||
CatalogItem $catalogItem,
|
||||
array $attributeCodes,
|
||||
array $multiSelectAttributeCodes = [],
|
||||
array $hiddenAttributeCodes = [],
|
||||
): array {
|
||||
$itemAttributes = [];
|
||||
$attributeCodes = array_values(array_unique($attributeCodes));
|
||||
@@ -517,6 +534,7 @@ class CatalogService
|
||||
$itemAttribute = $catalogItem->itemAttributes()->create([
|
||||
'attribute_id' => $attribute->id,
|
||||
'allow_multi_select' => in_array($attributeCode, $multiSelectAttributeCodes, true),
|
||||
'show_in_selector' => ! in_array($attributeCode, $hiddenAttributeCodes, true),
|
||||
]);
|
||||
|
||||
$itemAttributes[$attributeCode] = $itemAttribute;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('item_attributes', function (Blueprint $table): void {
|
||||
$table->boolean('show_in_selector')->default(true)->after('sort_order');
|
||||
});
|
||||
|
||||
$abonoDateAttributeIds = DB::table('item_attributes')
|
||||
->join('catalog_items', 'catalog_items.id', '=', 'item_attributes.catalog_item_id')
|
||||
->join('attribute', 'attribute.id', '=', 'item_attributes.attribute_id')
|
||||
->where('catalog_items.tenant_code', 'fiesta_futbol_infantil')
|
||||
->where('catalog_items.slug', 'abono')
|
||||
->where('attribute.codigo', 'event_date')
|
||||
->pluck('item_attributes.id');
|
||||
|
||||
DB::table('item_attributes')
|
||||
->whereIn('id', $abonoDateAttributeIds)
|
||||
->update(['show_in_selector' => false]);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('item_attributes', function (Blueprint $table): void {
|
||||
$table->dropColumn('show_in_selector');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -132,6 +132,7 @@ class FiestaFutbolInfantilProductSeeder extends Seeder
|
||||
'has_tickets' => true,
|
||||
'attribute_codes' => ['event_date'],
|
||||
'multi_select_attribute_codes' => ['event_date'],
|
||||
'hidden_attribute_codes' => ['event_date'],
|
||||
'variants' => [[
|
||||
'real_stock' => 120,
|
||||
'event_date_ids' => $dateIds->all(),
|
||||
|
||||
@@ -111,6 +111,7 @@ return [
|
||||
'direct_inventory_forbidden' => 'An item with variants cannot have direct inventory.',
|
||||
'event_date_attribute_required' => 'The event_date attribute is required for event date variants.',
|
||||
'multi_select_attribute_not_on_item' => 'Multi-select attributes must also be present in attribute_codes.',
|
||||
'hidden_attribute_not_on_item' => 'Hidden attributes must also be present in attribute_codes.',
|
||||
'event_date_selection_required' => 'At least one event date must be selected.',
|
||||
'single_event_date_required' => 'Exactly one event date must be selected.',
|
||||
'event_date_wrong_tenant' => 'Every event date must belong to the catalog item tenant.',
|
||||
|
||||
@@ -111,6 +111,7 @@ return [
|
||||
'direct_inventory_forbidden' => 'Un ítem con variantes no puede tener inventario directo.',
|
||||
'event_date_attribute_required' => 'El atributo event_date es obligatorio para las variantes con fecha de evento.',
|
||||
'multi_select_attribute_not_on_item' => 'Los atributos multiselección también deben estar incluidos en attribute_codes.',
|
||||
'hidden_attribute_not_on_item' => 'Los atributos ocultos también deben estar incluidos en attribute_codes.',
|
||||
'event_date_selection_required' => 'Debe seleccionar al menos una fecha de evento.',
|
||||
'single_event_date_required' => 'Debe seleccionar exactamente una fecha de evento.',
|
||||
'event_date_wrong_tenant' => 'Todas las fechas del evento deben pertenecer al tenant del ítem de catálogo.',
|
||||
|
||||
@@ -276,6 +276,26 @@ class CatalogItemDetailControllerTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function test_it_exposes_whether_an_item_attribute_should_be_shown_in_the_selector(): void
|
||||
{
|
||||
$tenant = $this->createTenant('detail-hidden-attribute');
|
||||
$item = $this->createItem($tenant, 'Hidden attribute');
|
||||
$attribute = Attribute::query()->create([
|
||||
'tenant_codigo' => $tenant->codigo,
|
||||
'codigo' => 'internal_type',
|
||||
'nombre' => 'Internal type',
|
||||
'type' => FieldType::String,
|
||||
]);
|
||||
$item->itemAttributes()->create([
|
||||
'attribute_id' => $attribute->id,
|
||||
'show_in_selector' => false,
|
||||
]);
|
||||
|
||||
$this->getJson("/api/tenants/{$tenant->codigo}/catalog-items/{$item->id}")
|
||||
->assertOk()
|
||||
->assertJsonPath('data.attributes.0.show_in_selector', false);
|
||||
}
|
||||
|
||||
private function createItem(
|
||||
Tenant $tenant,
|
||||
string $name,
|
||||
|
||||
@@ -25,6 +25,7 @@ class CatalogSchemaTest extends TestCase
|
||||
$this->assertTrue(Schema::hasTable('variantes'));
|
||||
$this->assertTrue(Schema::hasTable('item_attributes'));
|
||||
$this->assertTrue(Schema::hasColumn('item_attributes', 'sort_order'));
|
||||
$this->assertTrue(Schema::hasColumn('item_attributes', 'show_in_selector'));
|
||||
$this->assertTrue(Schema::hasTable('variant_values'));
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,26 @@ class CatalogServiceTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function test_it_can_hide_an_item_attribute_from_the_product_selector(): void
|
||||
{
|
||||
$attribute = $this->createAttribute('internal_type');
|
||||
|
||||
$item = $this->service->create([
|
||||
'tenant_code' => $this->tenant->codigo,
|
||||
'slug' => 'hidden-attribute-item',
|
||||
'nombre' => 'Hidden attribute item',
|
||||
'precio' => 100,
|
||||
'attribute_codes' => [$attribute->codigo],
|
||||
'hidden_attribute_codes' => [$attribute->codigo],
|
||||
'variants' => [[
|
||||
'real_stock' => 5,
|
||||
'values' => [$attribute->codigo => 'internal'],
|
||||
]],
|
||||
]);
|
||||
|
||||
$this->assertFalse($item->itemAttributes->sole()->show_in_selector);
|
||||
}
|
||||
|
||||
public function test_it_allows_the_same_event_date_with_different_attribute_values(): void
|
||||
{
|
||||
$sector = $this->createAttribute('sector');
|
||||
|
||||
@@ -128,6 +128,7 @@ class FiestaFutbolInfantilProductSeederTest extends TestCase
|
||||
->sole();
|
||||
$dateAttribute = $abono->itemAttributes->firstWhere('attribute.codigo', 'event_date');
|
||||
$this->assertTrue($dateAttribute->allow_multi_select);
|
||||
$this->assertFalse($dateAttribute->show_in_selector);
|
||||
$this->assertEqualsCanonicalizing(
|
||||
[4],
|
||||
$abono->variants->map(fn ($variant): int => $variant->eventDates->count())->all(),
|
||||
|
||||
Reference in New Issue
Block a user