refactor(catalog): add ticket_label to item attributes and update related logic
This commit is contained in:
@@ -14,6 +14,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
|||||||
'allow_multi_select',
|
'allow_multi_select',
|
||||||
'sort_order',
|
'sort_order',
|
||||||
'show_in_selector',
|
'show_in_selector',
|
||||||
|
'ticket_label',
|
||||||
])]
|
])]
|
||||||
class ItemAttribute extends Model
|
class ItemAttribute extends Model
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ class CatalogItemDetailResource extends JsonResource
|
|||||||
'nombre' => $attribute->nombre,
|
'nombre' => $attribute->nombre,
|
||||||
'sort_order' => $itemAttribute->sort_order,
|
'sort_order' => $itemAttribute->sort_order,
|
||||||
'show_in_selector' => $itemAttribute->show_in_selector,
|
'show_in_selector' => $itemAttribute->show_in_selector,
|
||||||
|
'ticket_label' => $itemAttribute->ticket_label,
|
||||||
'is_required' => $attribute->is_required,
|
'is_required' => $attribute->is_required,
|
||||||
'allow_multi_select' => $itemAttribute->allow_multi_select,
|
'allow_multi_select' => $itemAttribute->allow_multi_select,
|
||||||
'metadata_schema' => $attribute->metadata_schema,
|
'metadata_schema' => $attribute->metadata_schema,
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class CatalogService
|
|||||||
$attributeCodes = $data['attribute_codes'] ?? [];
|
$attributeCodes = $data['attribute_codes'] ?? [];
|
||||||
$multiSelectAttributeCodes = $data['multi_select_attribute_codes'] ?? [];
|
$multiSelectAttributeCodes = $data['multi_select_attribute_codes'] ?? [];
|
||||||
$hiddenAttributeCodes = $data['hidden_attribute_codes'] ?? [];
|
$hiddenAttributeCodes = $data['hidden_attribute_codes'] ?? [];
|
||||||
|
$ticketAttributeLabels = $data['ticket_attribute_labels'] ?? [];
|
||||||
$components = $data['components'] ?? [];
|
$components = $data['components'] ?? [];
|
||||||
$hasDirectStock = array_key_exists('real_stock', $data);
|
$hasDirectStock = array_key_exists('real_stock', $data);
|
||||||
$realStock = (int) ($data['real_stock'] ?? 0);
|
$realStock = (int) ($data['real_stock'] ?? 0);
|
||||||
@@ -73,6 +74,14 @@ class CatalogService
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (array_diff(array_keys($ticketAttributeLabels), $attributeCodes) !== []) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'ticket_attribute_labels' => [
|
||||||
|
__('api.catalog.ticket_label_attribute_not_on_item'),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$this->validateUniqueVariantCombinations($variants, $attributeCodes);
|
$this->validateUniqueVariantCombinations($variants, $attributeCodes);
|
||||||
|
|
||||||
if ($type === CatalogItemType::Bundle) {
|
if ($type === CatalogItemType::Bundle) {
|
||||||
@@ -98,6 +107,7 @@ class CatalogService
|
|||||||
$data['attribute_codes'],
|
$data['attribute_codes'],
|
||||||
$data['multi_select_attribute_codes'],
|
$data['multi_select_attribute_codes'],
|
||||||
$data['hidden_attribute_codes'],
|
$data['hidden_attribute_codes'],
|
||||||
|
$data['ticket_attribute_labels'],
|
||||||
$data['components'],
|
$data['components'],
|
||||||
$data['real_stock'],
|
$data['real_stock'],
|
||||||
$data['reserved_stock'],
|
$data['reserved_stock'],
|
||||||
@@ -124,6 +134,7 @@ class CatalogService
|
|||||||
$attributeCodes,
|
$attributeCodes,
|
||||||
$multiSelectAttributeCodes,
|
$multiSelectAttributeCodes,
|
||||||
$hiddenAttributeCodes,
|
$hiddenAttributeCodes,
|
||||||
|
$ticketAttributeLabels,
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
@@ -461,6 +472,7 @@ class CatalogService
|
|||||||
* @param array<int, string> $attributeCodes
|
* @param array<int, string> $attributeCodes
|
||||||
* @param array<int, string> $multiSelectAttributeCodes
|
* @param array<int, string> $multiSelectAttributeCodes
|
||||||
* @param array<int, string> $hiddenAttributeCodes
|
* @param array<int, string> $hiddenAttributeCodes
|
||||||
|
* @param array<string, string> $ticketAttributeLabels
|
||||||
* @return array<string, ItemAttribute>
|
* @return array<string, ItemAttribute>
|
||||||
*/
|
*/
|
||||||
private function createItemAttributes(
|
private function createItemAttributes(
|
||||||
@@ -468,6 +480,7 @@ class CatalogService
|
|||||||
array $attributeCodes,
|
array $attributeCodes,
|
||||||
array $multiSelectAttributeCodes = [],
|
array $multiSelectAttributeCodes = [],
|
||||||
array $hiddenAttributeCodes = [],
|
array $hiddenAttributeCodes = [],
|
||||||
|
array $ticketAttributeLabels = [],
|
||||||
): array {
|
): array {
|
||||||
$itemAttributes = [];
|
$itemAttributes = [];
|
||||||
$attributeCodes = array_values(array_unique($attributeCodes));
|
$attributeCodes = array_values(array_unique($attributeCodes));
|
||||||
@@ -492,6 +505,7 @@ class CatalogService
|
|||||||
'attribute_id' => $attribute->id,
|
'attribute_id' => $attribute->id,
|
||||||
'allow_multi_select' => in_array($attributeCode, $multiSelectAttributeCodes, true),
|
'allow_multi_select' => in_array($attributeCode, $multiSelectAttributeCodes, true),
|
||||||
'show_in_selector' => ! in_array($attributeCode, $hiddenAttributeCodes, true),
|
'show_in_selector' => ! in_array($attributeCode, $hiddenAttributeCodes, true),
|
||||||
|
'ticket_label' => $ticketAttributeLabels[$attributeCode] ?? null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$itemAttributes[$attributeCode] = $itemAttribute;
|
$itemAttributes[$attributeCode] = $itemAttribute;
|
||||||
|
|||||||
@@ -29,19 +29,27 @@ class TicketPresentationResolver
|
|||||||
return $catalogItem->nombre;
|
return $catalogItem->nombre;
|
||||||
}
|
}
|
||||||
|
|
||||||
$properties = $variant->selectionOptions()
|
$itemAttributes = $variant->catalogItem->itemAttributes;
|
||||||
->flatMap(function (array $option): array {
|
$properties = $variant->selectionOptions($itemAttributes)
|
||||||
if (array_is_list($option)) {
|
->map(function (array $option, string $attributeCode) use ($itemAttributes): ?string {
|
||||||
return collect($option)
|
$labels = collect(array_is_list($option) ? $option : [$option])
|
||||||
->pluck('label')
|
->pluck('label')
|
||||||
->filter(fn ($label): bool => is_string($label) && $label !== '')
|
->filter(fn ($label): bool => is_string($label) && $label !== '')
|
||||||
->all();
|
->implode(', ');
|
||||||
|
|
||||||
|
if ($labels === '') {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$label = $option['label'] ?? null;
|
$ticketLabel = $itemAttributes->first(
|
||||||
|
fn ($itemAttribute): bool => $itemAttribute->attribute?->codigo === $attributeCode,
|
||||||
|
)?->ticket_label;
|
||||||
|
|
||||||
return is_string($label) && $label !== '' ? [$label] : [];
|
return is_string($ticketLabel) && trim($ticketLabel) !== ''
|
||||||
|
? trim($ticketLabel).' '.$labels
|
||||||
|
: $labels;
|
||||||
})
|
})
|
||||||
|
->filter()
|
||||||
->values();
|
->values();
|
||||||
|
|
||||||
return $properties->isEmpty()
|
return $properties->isEmpty()
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?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->string('ticket_label', 100)->nullable()->after('show_in_selector');
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (['sector' => 'Lado', 'fila' => 'Fila', 'asiento' => 'Asiento'] as $code => $label) {
|
||||||
|
$itemAttributeIds = 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', 'desfile_pura_tendencia')
|
||||||
|
->where('attribute.codigo', $code)
|
||||||
|
->pluck('item_attributes.id');
|
||||||
|
|
||||||
|
DB::table('item_attributes')
|
||||||
|
->whereIn('id', $itemAttributeIds)
|
||||||
|
->update(['ticket_label' => $label]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('item_attributes', function (Blueprint $table): void {
|
||||||
|
$table->dropColumn('ticket_label');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -170,6 +170,11 @@ class DesfilePuraTendenciaSeeder extends Seeder
|
|||||||
'inventory_subject' => InventorySubject::Seat->value,
|
'inventory_subject' => InventorySubject::Seat->value,
|
||||||
'has_tickets' => true,
|
'has_tickets' => true,
|
||||||
'attribute_codes' => array_keys($attributes),
|
'attribute_codes' => array_keys($attributes),
|
||||||
|
'ticket_attribute_labels' => [
|
||||||
|
'sector' => 'Lado',
|
||||||
|
'fila' => 'Fila',
|
||||||
|
'asiento' => 'Asiento',
|
||||||
|
],
|
||||||
'variants' => $this->entryVariants(),
|
'variants' => $this->entryVariants(),
|
||||||
'images' => [
|
'images' => [
|
||||||
$this->uploadedImage(
|
$this->uploadedImage(
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ return [
|
|||||||
'event_date_attribute_required' => 'The event_date attribute is required for event date variants.',
|
'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.',
|
'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.',
|
'hidden_attribute_not_on_item' => 'Hidden attributes must also be present in attribute_codes.',
|
||||||
|
'ticket_label_attribute_not_on_item' => 'Ticket-label attributes must also be present in attribute_codes.',
|
||||||
'event_date_selection_required' => 'At least one event date must be selected.',
|
'event_date_selection_required' => 'At least one event date must be selected.',
|
||||||
'single_event_date_required' => 'Exactly 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.',
|
'event_date_wrong_tenant' => 'Every event date must belong to the catalog item tenant.',
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ return [
|
|||||||
'event_date_attribute_required' => 'El atributo event_date es obligatorio para las variantes con fecha de evento.',
|
'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.',
|
'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.',
|
'hidden_attribute_not_on_item' => 'Los atributos ocultos también deben estar incluidos en attribute_codes.',
|
||||||
|
'ticket_label_attribute_not_on_item' => 'Los atributos con etiqueta de ticket también deben estar incluidos en attribute_codes.',
|
||||||
'event_date_selection_required' => 'Debe seleccionar al menos una fecha de evento.',
|
'event_date_selection_required' => 'Debe seleccionar al menos una fecha de evento.',
|
||||||
'single_event_date_required' => 'Debe seleccionar exactamente 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.',
|
'event_date_wrong_tenant' => 'Todas las fechas del evento deben pertenecer al tenant del ítem de catálogo.',
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class CatalogSchemaTest extends TestCase
|
|||||||
$this->assertTrue(Schema::hasTable('item_attributes'));
|
$this->assertTrue(Schema::hasTable('item_attributes'));
|
||||||
$this->assertTrue(Schema::hasColumn('item_attributes', 'sort_order'));
|
$this->assertTrue(Schema::hasColumn('item_attributes', 'sort_order'));
|
||||||
$this->assertTrue(Schema::hasColumn('item_attributes', 'show_in_selector'));
|
$this->assertTrue(Schema::hasColumn('item_attributes', 'show_in_selector'));
|
||||||
|
$this->assertTrue(Schema::hasColumn('item_attributes', 'ticket_label'));
|
||||||
$this->assertTrue(Schema::hasTable('variant_values'));
|
$this->assertTrue(Schema::hasTable('variant_values'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -196,6 +196,26 @@ class CatalogServiceTest extends TestCase
|
|||||||
$this->assertFalse($item->itemAttributes->sole()->show_in_selector);
|
$this->assertFalse($item->itemAttributes->sole()->show_in_selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_it_can_configure_an_attribute_label_for_ticket_names(): void
|
||||||
|
{
|
||||||
|
$attribute = $this->createAttribute('sector');
|
||||||
|
|
||||||
|
$item = $this->service->create([
|
||||||
|
'tenant_code' => $this->tenant->codigo,
|
||||||
|
'slug' => 'ticket-labeled-attribute-item',
|
||||||
|
'nombre' => 'Ticket labeled attribute item',
|
||||||
|
'precio' => 100,
|
||||||
|
'attribute_codes' => [$attribute->codigo],
|
||||||
|
'ticket_attribute_labels' => [$attribute->codigo => 'Lado'],
|
||||||
|
'variants' => [[
|
||||||
|
'real_stock' => 5,
|
||||||
|
'values' => [$attribute->codigo => 'A'],
|
||||||
|
]],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertSame('Lado', $item->itemAttributes->sole()->ticket_label);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_it_allows_the_same_event_date_with_different_attribute_values(): void
|
public function test_it_allows_the_same_event_date_with_different_attribute_values(): void
|
||||||
{
|
{
|
||||||
$sector = $this->createAttribute('sector');
|
$sector = $this->createAttribute('sector');
|
||||||
|
|||||||
@@ -191,6 +191,20 @@ class DesfilePuraTendenciaSeederTest extends TestCase
|
|||||||
->pluck('attribute.codigo')
|
->pluck('attribute.codigo')
|
||||||
->all(),
|
->all(),
|
||||||
);
|
);
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'tipo' => null,
|
||||||
|
'sector' => 'Lado',
|
||||||
|
'fila' => 'Fila',
|
||||||
|
'asiento' => 'Asiento',
|
||||||
|
],
|
||||||
|
DB::table('item_attributes')
|
||||||
|
->join('attribute', 'attribute.id', '=', 'item_attributes.attribute_id')
|
||||||
|
->where('item_attributes.catalog_item_id', $catalogItem->id)
|
||||||
|
->orderBy('item_attributes.sort_order')
|
||||||
|
->pluck('item_attributes.ticket_label', 'attribute.codigo')
|
||||||
|
->all(),
|
||||||
|
);
|
||||||
|
|
||||||
$variants = DB::table('variantes')->where('catalog_item_id', $catalogItem->id);
|
$variants = DB::table('variantes')->where('catalog_item_id', $catalogItem->id);
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ class TicketGeneratorServiceTest extends TestCase
|
|||||||
$itemSize = $item->itemAttributes()->create([
|
$itemSize = $item->itemAttributes()->create([
|
||||||
'attribute_id' => $size->id,
|
'attribute_id' => $size->id,
|
||||||
'sort_order' => 2,
|
'sort_order' => 2,
|
||||||
|
'ticket_label' => 'Talle',
|
||||||
]);
|
]);
|
||||||
$variant = $item->variants()->create([
|
$variant = $item->variants()->create([
|
||||||
'inventory_id' => Inventory::query()->create()->id,
|
'inventory_id' => Inventory::query()->create()->id,
|
||||||
@@ -142,7 +143,7 @@ class TicketGeneratorServiceTest extends TestCase
|
|||||||
->generate($item, $this->user, 1, $variant->id)
|
->generate($item, $this->user, 1, $variant->id)
|
||||||
->sole();
|
->sole();
|
||||||
|
|
||||||
$this->assertSame('Shirt (Negro, XL)', $ticket->name);
|
$this->assertSame('Shirt (Negro, Talle XL)', $ticket->name);
|
||||||
$this->assertSame('Descripción de la variante', $ticket->description);
|
$this->assertSame('Descripción de la variante', $ticket->description);
|
||||||
|
|
||||||
$item->update([
|
$item->update([
|
||||||
@@ -153,7 +154,7 @@ class TicketGeneratorServiceTest extends TestCase
|
|||||||
$variant->update(['descripcion' => 'Descripción actualizada de la variante']);
|
$variant->update(['descripcion' => 'Descripción actualizada de la variante']);
|
||||||
$ticket = $ticket->fresh();
|
$ticket = $ticket->fresh();
|
||||||
|
|
||||||
$this->assertSame('Remera (Azul, XL)', $ticket->name);
|
$this->assertSame('Remera (Azul, Talle XL)', $ticket->name);
|
||||||
$this->assertSame('Descripción actualizada de la variante', $ticket->description);
|
$this->assertSame('Descripción actualizada de la variante', $ticket->description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user