feat(catalog): add group_order field to catalog items and update related logic

This commit is contained in:
2026-08-25 10:45:12 -03:00
parent 6b06028771
commit 87a4fa6288
9 changed files with 84 additions and 2 deletions

View File

@@ -27,6 +27,7 @@ use Illuminate\Support\Collection;
'type', 'type',
'slug', 'slug',
'nombre', 'nombre',
'group_order',
'descripcion', 'descripcion',
'precio', 'precio',
'inventory_policy', 'inventory_policy',
@@ -47,6 +48,7 @@ class CatalogItem extends Model
'inventory_policy' => InventoryPolicy::Tracked->value, 'inventory_policy' => InventoryPolicy::Tracked->value,
'inventory_subject' => InventorySubject::Product->value, 'inventory_subject' => InventorySubject::Product->value,
'has_tickets' => false, 'has_tickets' => false,
'group_order' => 0,
]; ];
protected function casts(): array protected function casts(): array
@@ -56,6 +58,7 @@ class CatalogItem extends Model
'brand_id' => 'integer', 'brand_id' => 'integer',
'inventory_id' => 'integer', 'inventory_id' => 'integer',
'type' => CatalogItemType::class, 'type' => CatalogItemType::class,
'group_order' => 'integer',
'precio' => 'decimal:2', 'precio' => 'decimal:2',
'inventory_policy' => InventoryPolicy::class, 'inventory_policy' => InventoryPolicy::class,
'inventory_subject' => InventorySubject::class, 'inventory_subject' => InventorySubject::class,

View File

@@ -51,6 +51,7 @@ class StoreCatalogItemRequest extends FormRequest
), ),
], ],
'nombre' => ['required', 'string', 'max:255'], 'nombre' => ['required', 'string', 'max:255'],
'group_order' => ['sometimes', 'integer', 'min:0'],
'descripcion' => ['sometimes', 'nullable', 'string'], 'descripcion' => ['sometimes', 'nullable', 'string'],
'precio' => ['required', 'numeric', 'min:0'], 'precio' => ['required', 'numeric', 'min:0'],
'inventory_policy' => [Rule::prohibitedIf($isBundle), 'sometimes', Rule::enum(InventoryPolicy::class)], 'inventory_policy' => [Rule::prohibitedIf($isBundle), 'sometimes', Rule::enum(InventoryPolicy::class)],

View File

@@ -72,7 +72,9 @@ class FeaturedGroupService
FeaturedGroupSource::Category => $query FeaturedGroupSource::Category => $query
->where('catalog_items.category_id', $featuredGroup->category_id) ->where('catalog_items.category_id', $featuredGroup->category_id)
->orderBy('catalog_items.id'), ->orderBy('catalog_items.id'),
FeaturedGroupSource::All => $query->orderBy('catalog_items.id'), FeaturedGroupSource::All => $query
->orderBy('catalog_items.group_order')
->orderBy('catalog_items.id'),
}; };
} }

View File

@@ -0,0 +1,53 @@
<?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('catalog_items', function (Blueprint $table): void {
$table->unsignedInteger('group_order')->default(0)->after('nombre');
});
$footballOrder = [
1 => [
'slugs' => ['camiseta', 'camiseta-oficial-fnfi'],
'names' => ['Camiseta', 'CAMISETA OFICIAL FNFI'],
],
2 => [
'slugs' => ['alojamiento', 'camping'],
'names' => ['Alojamiento', 'CAMPING'],
],
3 => [
'slugs' => ['abono'],
'names' => ['Abono', 'ABONO'],
],
4 => [
'slugs' => ['comida'],
'names' => ['Comida', 'COMIDA'],
],
];
foreach ($footballOrder as $order => $identifiers) {
DB::table('catalog_items')
->where('tenant_code', 'fiesta_futbol_infantil')
->where(function ($query) use ($identifiers): void {
$query
->whereIn('slug', $identifiers['slugs'])
->orWhereIn('nombre', $identifiers['names']);
})
->update(['group_order' => $order]);
}
}
public function down(): void
{
Schema::table('catalog_items', function (Blueprint $table): void {
$table->dropColumn('group_order');
});
}
};

View File

@@ -78,6 +78,7 @@ class FiestaFutbolInfantilProductSeeder extends Seeder
$this->createProduct($tenant, [ $this->createProduct($tenant, [
'slug' => 'camiseta', 'slug' => 'camiseta',
'nombre' => 'Camiseta', 'nombre' => 'Camiseta',
'group_order' => 1,
'category_id' => $categories['merchandising']->id, 'category_id' => $categories['merchandising']->id,
'precio' => 18000, 'precio' => 18000,
'attribute_codes' => ['color', 'talle'], 'attribute_codes' => ['color', 'talle'],
@@ -92,6 +93,7 @@ class FiestaFutbolInfantilProductSeeder extends Seeder
$this->createProduct($tenant, [ $this->createProduct($tenant, [
'slug' => 'alojamiento', 'slug' => 'alojamiento',
'nombre' => 'Alojamiento', 'nombre' => 'Alojamiento',
'group_order' => 2,
'category_id' => $categories['alojamientos']->id, 'category_id' => $categories['alojamientos']->id,
'precio' => 35000, 'precio' => 35000,
'attribute_codes' => ['tipo_alojamiento'], 'attribute_codes' => ['tipo_alojamiento'],
@@ -104,6 +106,7 @@ class FiestaFutbolInfantilProductSeeder extends Seeder
$this->createProduct($tenant, [ $this->createProduct($tenant, [
'slug' => 'comida', 'slug' => 'comida',
'nombre' => 'Comida', 'nombre' => 'Comida',
'group_order' => 4,
'category_id' => $categories['comidas']->id, 'category_id' => $categories['comidas']->id,
'precio' => 4000, 'precio' => 4000,
'attribute_codes' => ['event_date', 'horario', 'servicio'], 'attribute_codes' => ['event_date', 'horario', 'servicio'],
@@ -126,6 +129,7 @@ class FiestaFutbolInfantilProductSeeder extends Seeder
$this->createProduct($tenant, [ $this->createProduct($tenant, [
'slug' => 'abono', 'slug' => 'abono',
'nombre' => 'Abono', 'nombre' => 'Abono',
'group_order' => 3,
'category_id' => $categories['entradas']->id, 'category_id' => $categories['entradas']->id,
'precio' => 40000, 'precio' => 40000,
'has_tickets' => true, 'has_tickets' => true,

View File

@@ -354,8 +354,10 @@ class CatalogControllerTest extends TestCase
]); ]);
$food = $this->createItem($tenant, 'Hamburger'); $food = $this->createItem($tenant, 'Hamburger');
$food->update(['group_order' => 2]);
$food->category()->associate($category)->save(); $food->category()->associate($category)->save();
$this->createItem($tenant, 'Parking'); $parking = $this->createItem($tenant, 'Parking');
$parking->update(['group_order' => 1]);
$this->getJson("/api/tenants/{$tenant->codigo}/catalog") $this->getJson("/api/tenants/{$tenant->codigo}/catalog")
->assertOk() ->assertOk()
@@ -364,6 +366,9 @@ class CatalogControllerTest extends TestCase
->assertJsonPath('0.items.0.nombre', 'Hamburger') ->assertJsonPath('0.items.0.nombre', 'Hamburger')
->assertJsonPath('1.title', 'All products') ->assertJsonPath('1.title', 'All products')
->assertJsonCount(2, '1.items.data') ->assertJsonCount(2, '1.items.data')
->assertJsonPath('1.items.data.0.nombre', 'Parking')
->assertJsonPath('1.items.data.1.nombre', 'Hamburger')
->assertJsonMissingPath('1.items.data.0.group_order')
->assertJsonPath('1.items.meta.total', 2); ->assertJsonPath('1.items.meta.total', 2);
} }

View File

@@ -31,6 +31,7 @@ class CatalogItemControllerTest extends TestCase
$response = $this->postJson("/api/tenants/{$tenant->codigo}/catalog-items", [ $response = $this->postJson("/api/tenants/{$tenant->codigo}/catalog-items", [
'slug' => 'shirt', 'slug' => 'shirt',
'nombre' => 'Shirt', 'nombre' => 'Shirt',
'group_order' => 7,
'precio' => 100, 'precio' => 100,
'max_units_per_user' => 4, 'max_units_per_user' => 4,
'attribute_codes' => [$attribute->codigo], 'attribute_codes' => [$attribute->codigo],
@@ -47,6 +48,7 @@ class CatalogItemControllerTest extends TestCase
$response $response
->assertCreated() ->assertCreated()
->assertJsonPath('data.nombre', 'Shirt') ->assertJsonPath('data.nombre', 'Shirt')
->assertJsonMissingPath('data.group_order')
->assertJsonPath('data.max_units_per_user', 4) ->assertJsonPath('data.max_units_per_user', 4)
->assertJsonCount(2, 'data.images') ->assertJsonCount(2, 'data.images')
->assertJsonCount(1, 'data.variants') ->assertJsonCount(1, 'data.variants')
@@ -57,6 +59,7 @@ class CatalogItemControllerTest extends TestCase
$this->assertSame([0, 1], $item->attachments()->get()->pluck('pivot.orden')->all()); $this->assertSame([0, 1], $item->attachments()->get()->pluck('pivot.orden')->all());
$this->assertSame(4, $item->max_units_per_user); $this->assertSame(4, $item->max_units_per_user);
$this->assertSame(7, $item->group_order);
$this->assertSame([0], $variant->attachments()->get()->pluck('pivot.orden')->all()); $this->assertSame([0], $variant->attachments()->get()->pluck('pivot.orden')->all());
$this->assertDatabaseHas('catalog_items_attachments', [ $this->assertDatabaseHas('catalog_items_attachments', [
'catalog_item_id' => $item->id, 'catalog_item_id' => $item->id,

View File

@@ -70,6 +70,14 @@ class FiestaFutbolInfantilProductSeederTest extends TestCase
['abono', 'alojamiento', 'camiseta', 'comida'], ['abono', 'alojamiento', 'camiseta', 'comida'],
CatalogItem::query()->where('tenant_code', $tenant->codigo)->orderBy('slug')->pluck('slug')->all(), CatalogItem::query()->where('tenant_code', $tenant->codigo)->orderBy('slug')->pluck('slug')->all(),
); );
$this->assertSame(
['camiseta', 'alojamiento', 'abono', 'comida'],
CatalogItem::query()
->where('tenant_code', $tenant->codigo)
->orderBy('group_order')
->pluck('slug')
->all(),
);
$this->assertTrue( $this->assertTrue(
CatalogItem::query() CatalogItem::query()
->where('tenant_code', $tenant->codigo) ->where('tenant_code', $tenant->codigo)

View File

@@ -83,11 +83,13 @@ class CatalogModelsTest extends TestCase
public function test_catalog_item_is_the_catalog_root(): void public function test_catalog_item_is_the_catalog_root(): void
{ {
$item = new CatalogItem; $item = new CatalogItem;
$this->assertSame(0, $item->group_order);
$item->setRawAttributes([ $item->setRawAttributes([
'category_id' => '10', 'category_id' => '10',
'brand_id' => '20', 'brand_id' => '20',
'inventory_id' => '30', 'inventory_id' => '30',
'type' => CatalogItemType::Standard->value, 'type' => CatalogItemType::Standard->value,
'group_order' => '4',
'precio' => '12.50', 'precio' => '12.50',
'inventory_policy' => InventoryPolicy::Tracked->value, 'inventory_policy' => InventoryPolicy::Tracked->value,
'inventory_subject' => InventorySubject::Seat->value, 'inventory_subject' => InventorySubject::Seat->value,
@@ -100,6 +102,7 @@ class CatalogModelsTest extends TestCase
$this->assertSame(20, $item->brand_id); $this->assertSame(20, $item->brand_id);
$this->assertSame(30, $item->inventory_id); $this->assertSame(30, $item->inventory_id);
$this->assertSame(CatalogItemType::Standard, $item->type); $this->assertSame(CatalogItemType::Standard, $item->type);
$this->assertSame(4, $item->group_order);
$this->assertSame('12.50', $item->precio); $this->assertSame('12.50', $item->precio);
$this->assertSame(InventoryPolicy::Tracked, $item->inventory_policy); $this->assertSame(InventoryPolicy::Tracked, $item->inventory_policy);
$this->assertSame(InventorySubject::Seat, $item->inventory_subject); $this->assertSame(InventorySubject::Seat, $item->inventory_subject);