405 lines
16 KiB
PHP
405 lines
16 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\FiestaFutbolInfantil;
|
|
|
|
use App\Domains\Auth\Models\User;
|
|
use App\Domains\Authorization\Enums\RoleCode;
|
|
use App\Domains\Catalog\Models\Attribute;
|
|
use App\Domains\Catalog\Models\CatalogItem;
|
|
use App\Domains\Catalog\Models\Category;
|
|
use App\Domains\Catalog\Models\Inventory;
|
|
use App\Domains\Catalog\Models\Variant;
|
|
use App\Domains\Event\Services\EventService;
|
|
use App\Domains\Menu\Models\Menu;
|
|
use App\Domains\Shared\Enums\FieldType;
|
|
use App\Domains\Tenant\Models\Tenant;
|
|
use App\Domains\Tenant\Models\WebsiteType;
|
|
use Database\Seeders\AuthorizationSeeder;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Carbon;
|
|
use Laravel\Sanctum\Sanctum;
|
|
use Tests\TestCase;
|
|
|
|
class FoodControllerTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->seed(AuthorizationSeeder::class);
|
|
WebsiteType::query()->create([
|
|
'codigo' => 'onticket',
|
|
'nombre' => 'OnTicket',
|
|
]);
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
Carbon::setTestNow();
|
|
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function test_authentication_is_required(): void
|
|
{
|
|
$this->postJson('/api/v1/adminapp/tenant/foods', ['variants' => []])
|
|
->assertUnauthorized();
|
|
}
|
|
|
|
public function test_it_creates_one_food_item_with_multiple_variants(): void
|
|
{
|
|
[$tenant, $firstDate, $secondDate] = $this->configuredTenant();
|
|
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
|
|
|
$this->postJson('/api/v1/adminapp/tenant/foods', [
|
|
'variants' => [
|
|
$this->variantPayload($firstDate->id, 'Almuerzo', 'Comedor', 1700, 10000),
|
|
$this->variantPayload($secondDate->id, 'Cena', 'Vianda', 900, 8000),
|
|
],
|
|
])
|
|
->assertOk()
|
|
->assertJsonPath('data.name', 'Comida')
|
|
->assertJsonCount(2, 'data.variants')
|
|
->assertJsonPath('data.variants.0.schedule', 'Almuerzo')
|
|
->assertJsonPath('data.variants.0.service', 'Comedor')
|
|
->assertJsonPath('data.variants.0.stock', 1700)
|
|
->assertJsonPath('data.variants.0.price', '10000.00')
|
|
->assertJsonPath('data.variants.1.price', '8000.00');
|
|
|
|
$this->getJson('/api/v1/adminapp/tenant/foods')
|
|
->assertOk()
|
|
->assertJsonPath('data.name', 'Comida')
|
|
->assertJsonCount(2, 'data.variants')
|
|
->assertJsonPath('data.variants.0.schedule', 'Almuerzo')
|
|
->assertJsonPath('data.variants.1.service', 'Vianda');
|
|
|
|
$this->assertDatabaseCount('catalog_items', 1);
|
|
$this->assertDatabaseCount('variantes', 2);
|
|
$this->assertDatabaseCount('inventories', 2);
|
|
$this->assertDatabaseCount('variant_event_dates', 2);
|
|
$this->assertDatabaseCount('variant_values', 4);
|
|
$food = CatalogItem::query()->where('slug', 'comida')->sole();
|
|
$this->assertDatabaseHas('item_attributes', [
|
|
'catalog_item_id' => $food->id,
|
|
'attribute_id' => Attribute::query()->where('codigo', 'event_date')->sole()->id,
|
|
'sort_order' => 1,
|
|
]);
|
|
$this->assertDatabaseHas('item_attributes', [
|
|
'catalog_item_id' => $food->id,
|
|
'attribute_id' => Attribute::query()->where('codigo', 'horario')->sole()->id,
|
|
'sort_order' => 2,
|
|
]);
|
|
$this->assertDatabaseHas('item_attributes', [
|
|
'catalog_item_id' => $food->id,
|
|
'attribute_id' => Attribute::query()->where('codigo', 'servicio')->sole()->id,
|
|
'sort_order' => 3,
|
|
]);
|
|
$this->assertDatabaseHas('catalog_items', [
|
|
'tenant_code' => $tenant->codigo,
|
|
'slug' => 'comida',
|
|
'nombre' => 'Comida',
|
|
'has_tickets' => true,
|
|
'category_id' => Category::query()
|
|
->where('tenant_code', $tenant->codigo)
|
|
->where('nombre', 'Comidas')
|
|
->sole()
|
|
->id,
|
|
'precio' => 8000,
|
|
]);
|
|
}
|
|
|
|
public function test_it_updates_variants_with_an_id_and_creates_variants_without_one(): void
|
|
{
|
|
[$tenant, $firstDate, $secondDate] = $this->configuredTenant();
|
|
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
|
|
|
$created = $this->postJson('/api/v1/adminapp/tenant/foods', [
|
|
'variants' => [
|
|
$this->variantPayload($firstDate->id, 'Almuerzo', 'Comedor', 100, 10000),
|
|
],
|
|
])->assertOk();
|
|
$variantId = $created->json('data.variants.0.id');
|
|
|
|
$updated = $this->variantPayload($secondDate->id, 'Cena', 'Vianda', 80, 8500);
|
|
$updated['id'] = $variantId;
|
|
$updated['description'] = 'Cena para llevar';
|
|
|
|
$this->postJson('/api/v1/adminapp/tenant/foods', [
|
|
'variants' => [
|
|
$updated,
|
|
$this->variantPayload($firstDate->id, 'Almuerzo', 'Vianda', 50, 9000),
|
|
],
|
|
])
|
|
->assertOk()
|
|
->assertJsonCount(2, 'data.variants')
|
|
->assertJsonPath('data.variants.0.id', $variantId)
|
|
->assertJsonPath('data.variants.0.event_date_id', $secondDate->id)
|
|
->assertJsonPath('data.variants.0.schedule', 'Cena')
|
|
->assertJsonPath('data.variants.0.service', 'Vianda')
|
|
->assertJsonPath('data.variants.0.description', 'Cena para llevar')
|
|
->assertJsonPath('data.variants.0.stock', 80)
|
|
->assertJsonPath('data.variants.0.price', '8500.00');
|
|
|
|
$this->assertDatabaseCount('catalog_items', 1);
|
|
$this->assertDatabaseCount('variantes', 2);
|
|
$this->assertDatabaseHas('variantes', [
|
|
'id' => $variantId,
|
|
'event_date_id' => $secondDate->id,
|
|
'descripcion' => 'Cena para llevar',
|
|
'precio' => 8500,
|
|
]);
|
|
}
|
|
|
|
public function test_it_separates_current_and_historical_food_variants(): void
|
|
{
|
|
Carbon::setTestNow('2026-09-01 12:00:00');
|
|
[$tenant, $rescheduledDate, $suspendedDate] = $this->configuredTenant();
|
|
$completedDate = $tenant->eventDates()->create([
|
|
'date' => '2026-10-11',
|
|
'time_start' => '00:00',
|
|
'time_end' => '23:59',
|
|
]);
|
|
$activeDate = $tenant->eventDates()->create([
|
|
'date' => '2026-10-21',
|
|
'time_start' => '00:00',
|
|
'time_end' => '23:59',
|
|
]);
|
|
$admin = $this->createAdminAppUser($tenant);
|
|
Sanctum::actingAs($admin);
|
|
|
|
$this->postJson('/api/v1/adminapp/tenant/foods', [
|
|
'variants' => [
|
|
$this->variantPayload($rescheduledDate->id, 'Almuerzo', 'Comedor', 100, 10000),
|
|
$this->variantPayload($suspendedDate->id, 'Cena', 'Vianda', 90, 9000),
|
|
$this->variantPayload($completedDate->id, 'Cena', 'Comedor', 80, 8000),
|
|
$this->variantPayload($activeDate->id, 'Almuerzo', 'Vianda', 70, 7000),
|
|
],
|
|
])->assertOk();
|
|
|
|
$eventService = app(EventService::class);
|
|
$eventService->rescheduleDateForTenant(
|
|
$tenant,
|
|
$rescheduledDate,
|
|
['date' => '2026-10-20'],
|
|
$admin,
|
|
);
|
|
$eventService->suspendDateForTenant($tenant, $suspendedDate, $admin);
|
|
|
|
Carbon::setTestNow('2026-10-15 12:00:00');
|
|
$response = $this->getJson('/api/v1/adminapp/tenant/foods')->assertOk();
|
|
|
|
$response->assertJsonCount(2, 'data.variants')->assertJsonCount(3, 'data.history');
|
|
$history = collect($response->json('data.history'));
|
|
|
|
$rescheduled = $history->firstWhere('status', 'rescheduled');
|
|
$this->assertSame('REPROGRAMADA', $rescheduled['status_text']);
|
|
$this->assertSame('2026-10-09', $rescheduled['event_date']);
|
|
$this->assertSame('2026-10-20', $rescheduled['replacement_event_date']);
|
|
$this->assertCount(1, $rescheduled['variants']);
|
|
|
|
$suspended = $history->firstWhere('status', 'suspended');
|
|
$this->assertSame('CANCELADA', $suspended['status_text']);
|
|
$this->assertNull($suspended['replacement_event_date']);
|
|
|
|
$completed = $history->firstWhere('status', 'completed');
|
|
$this->assertSame('FINALIZADA', $completed['status_text']);
|
|
$this->assertSame('2026-10-11', $completed['event_date']);
|
|
$this->assertNull($completed['replacement_event_date']);
|
|
|
|
}
|
|
|
|
public function test_it_updates_only_the_stock_of_historical_food_variants(): void
|
|
{
|
|
[$tenant, $historicalDate, $activeDate] = $this->configuredTenant();
|
|
$admin = $this->createAdminAppUser($tenant);
|
|
Sanctum::actingAs($admin);
|
|
|
|
$created = $this->postJson('/api/v1/adminapp/tenant/foods', [
|
|
'variants' => [
|
|
$this->variantPayload($historicalDate->id, 'Almuerzo', 'Comedor', 100, 10000),
|
|
$this->variantPayload($activeDate->id, 'Cena', 'Vianda', 80, 8000),
|
|
],
|
|
])->assertOk();
|
|
$historicalVariantId = $created->json('data.variants.0.id');
|
|
$activeVariantId = $created->json('data.variants.1.id');
|
|
|
|
app(EventService::class)->rescheduleDateForTenant(
|
|
$tenant,
|
|
$historicalDate,
|
|
['date' => $activeDate->date->format('Y-m-d')],
|
|
$admin,
|
|
);
|
|
$historicalVariant = Variant::query()->findOrFail($historicalVariantId);
|
|
$replacementVariant = $historicalVariant->replacement()->firstOrFail();
|
|
$replacementInventoryId = $replacementVariant->inventory_id;
|
|
|
|
// Simula variantes creadas antes de que la reprogramación separara sus inventarios.
|
|
$replacementVariant->update(['inventory_id' => $historicalVariant->inventory_id]);
|
|
Inventory::query()->whereKey($replacementInventoryId)->delete();
|
|
|
|
$updated = $this->patchJson('/api/v1/adminapp/tenant/foods/history-stock', [
|
|
'variants' => [['id' => $historicalVariantId, 'stock' => 45]],
|
|
])
|
|
->assertOk()
|
|
->assertJsonPath('data.history.0.variants.0.id', $historicalVariantId)
|
|
->assertJsonPath('data.history.0.variants.0.stock', 45);
|
|
$replacement = collect($updated->json('data.variants'))
|
|
->firstWhere('schedule', 'Almuerzo');
|
|
$this->assertSame(100, $replacement['stock']);
|
|
|
|
$historicalInventoryId = Variant::query()->findOrFail($historicalVariantId)->inventory_id;
|
|
$currentInventoryId = $replacementVariant->fresh()->inventory_id;
|
|
$this->assertNotSame($historicalInventoryId, $currentInventoryId);
|
|
$this->assertDatabaseHas('inventories', [
|
|
'id' => $historicalInventoryId,
|
|
'real_stock' => 45,
|
|
]);
|
|
|
|
$this->patchJson('/api/v1/adminapp/tenant/foods/history-stock', [
|
|
'variants' => [['id' => $activeVariantId, 'stock' => 20]],
|
|
])
|
|
->assertUnprocessable()
|
|
->assertJsonValidationErrors(['variants.0.id']);
|
|
}
|
|
|
|
public function test_it_deletes_food_records_and_removes_the_empty_product(): void
|
|
{
|
|
[$tenant, $firstDate, $secondDate] = $this->configuredTenant();
|
|
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
|
|
|
$created = $this->postJson('/api/v1/adminapp/tenant/foods', [
|
|
'variants' => [
|
|
$this->variantPayload($firstDate->id, 'Almuerzo', 'Comedor', 100, 10000),
|
|
$this->variantPayload($secondDate->id, 'Cena', 'Vianda', 50, 8000),
|
|
],
|
|
])->assertOk();
|
|
$firstId = $created->json('data.variants.0.id');
|
|
$secondId = $created->json('data.variants.1.id');
|
|
|
|
$this->deleteJson("/api/v1/adminapp/tenant/foods/{$secondId}")
|
|
->assertNoContent();
|
|
|
|
$this->assertDatabaseHas('catalog_items', [
|
|
'tenant_code' => $tenant->codigo,
|
|
'slug' => 'comida',
|
|
'precio' => 10000,
|
|
]);
|
|
$this->assertSoftDeleted('variantes', ['id' => $secondId]);
|
|
$this->assertSame(1, Variant::query()->count());
|
|
$this->assertDatabaseCount('inventories', 2);
|
|
|
|
$this->deleteJson("/api/v1/adminapp/tenant/foods/{$firstId}")
|
|
->assertNoContent();
|
|
|
|
$this->assertSoftDeleted('variantes', ['id' => $firstId]);
|
|
$this->assertSame(0, CatalogItem::query()->count());
|
|
$this->assertSame(0, Variant::query()->count());
|
|
$this->assertDatabaseCount('inventories', 2);
|
|
}
|
|
|
|
public function test_it_rejects_duplicate_combinations(): void
|
|
{
|
|
[$tenant, $firstDate] = $this->configuredTenant();
|
|
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
|
|
|
$this->postJson('/api/v1/adminapp/tenant/foods', [
|
|
'variants' => [
|
|
$this->variantPayload($firstDate->id, 'Almuerzo', 'Comedor', 100, 10000),
|
|
$this->variantPayload($firstDate->id, ' almuerzo ', ' comedor ', 100, 10000),
|
|
],
|
|
])
|
|
->assertUnprocessable()
|
|
->assertJsonValidationErrors(['variants.1']);
|
|
|
|
$this->assertDatabaseCount('catalog_items', 0);
|
|
}
|
|
|
|
/** @return array{Tenant, mixed, mixed} */
|
|
private function configuredTenant(): array
|
|
{
|
|
$tenant = Tenant::query()->create([
|
|
'codigo' => 'fiesta_futbol_infantil',
|
|
'nombre' => 'Fiesta Fútbol Infantil',
|
|
'dominio' => 'fiesta.test',
|
|
'website_type_code' => 'onticket',
|
|
]);
|
|
$eventDateAttribute = Attribute::query()->create([
|
|
'tenant_codigo' => $tenant->codigo,
|
|
'codigo' => 'event_date',
|
|
'nombre' => 'Fecha',
|
|
'type' => FieldType::EventDate,
|
|
'is_required' => true,
|
|
]);
|
|
$scheduleAttribute = Attribute::query()->create([
|
|
'tenant_codigo' => $tenant->codigo,
|
|
'codigo' => 'horario',
|
|
'nombre' => 'Horario',
|
|
'type' => FieldType::Select,
|
|
'is_required' => true,
|
|
]);
|
|
$scheduleAttribute->options()->createMany([
|
|
['value' => 'Almuerzo', 'label' => 'Almuerzo', 'sort_order' => 1],
|
|
['value' => 'Cena', 'label' => 'Cena', 'sort_order' => 2],
|
|
]);
|
|
$serviceAttribute = Attribute::query()->create([
|
|
'tenant_codigo' => $tenant->codigo,
|
|
'codigo' => 'servicio',
|
|
'nombre' => 'Servicio',
|
|
'type' => FieldType::Select,
|
|
'is_required' => true,
|
|
]);
|
|
$serviceAttribute->options()->createMany([
|
|
['value' => 'Comedor', 'label' => 'Comedor', 'sort_order' => 1],
|
|
['value' => 'Vianda', 'label' => 'Vianda', 'sort_order' => 2],
|
|
]);
|
|
$firstDate = $tenant->eventDates()->create([
|
|
'date' => '2026-10-09',
|
|
'time_start' => '00:00',
|
|
'time_end' => '23:59',
|
|
]);
|
|
$secondDate = $tenant->eventDates()->create([
|
|
'date' => '2026-10-10',
|
|
'time_start' => '00:00',
|
|
'time_end' => '23:59',
|
|
]);
|
|
$menu = Menu::query()->create([
|
|
'code' => 'adminapp.fiesta-futbol-infantil.comida',
|
|
'label' => 'Comida',
|
|
'route' => '/admin/comidas',
|
|
]);
|
|
$tenant->menues()->attach($menu->code);
|
|
|
|
$this->assertNotNull($eventDateAttribute);
|
|
|
|
return [$tenant, $firstDate, $secondDate];
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
private function variantPayload(
|
|
int $eventDateId,
|
|
string $schedule,
|
|
string $service,
|
|
int $stock,
|
|
float $price,
|
|
): array {
|
|
return [
|
|
'event_date_id' => $eventDateId,
|
|
'schedule' => $schedule,
|
|
'service' => $service,
|
|
'description' => null,
|
|
'stock' => $stock,
|
|
'price' => $price,
|
|
];
|
|
}
|
|
|
|
private function createAdminAppUser(Tenant $tenant): User
|
|
{
|
|
return User::factory()->create([
|
|
'rol_codigo' => RoleCode::AdminApp->value,
|
|
'tenant_codigo' => $tenant->codigo,
|
|
]);
|
|
}
|
|
}
|