281 lines
9.9 KiB
PHP
281 lines
9.9 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Desfile;
|
|
|
|
use App\Domains\Attachable\Enums\AttachmentType;
|
|
use App\Domains\Attachable\Models\Attachment;
|
|
use App\Domains\Auth\Models\User;
|
|
use App\Domains\Authorization\Enums\RoleCode;
|
|
use App\Domains\Catalog\Enums\InventoryPolicy;
|
|
use App\Domains\Catalog\Enums\InventorySubject;
|
|
use App\Domains\Catalog\Models\Attribute;
|
|
use App\Domains\Catalog\Models\CatalogItem;
|
|
use App\Domains\Catalog\Models\Inventory;
|
|
use App\Domains\Catalog\Models\ItemAttribute;
|
|
use App\Domains\Catalog\Models\Variant;
|
|
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\Http\UploadedFile;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Laravel\Sanctum\Sanctum;
|
|
use Tests\TestCase;
|
|
|
|
class EntryControllerTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->seed(AuthorizationSeeder::class);
|
|
WebsiteType::query()->create([
|
|
'codigo' => 'onticket',
|
|
'nombre' => 'OnTicket',
|
|
]);
|
|
}
|
|
|
|
public function test_it_returns_row_configs_and_expands_them_into_seat_variants(): void
|
|
{
|
|
[$tenant, $entry] = $this->configuredEntry();
|
|
$existing = $this->createVariant($entry, 'NORMAL', 'A', '1', '1', 100000);
|
|
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
|
|
|
$this->getJson('/api/v1/adminapp/tenant/desfile/entries')
|
|
->assertOk()
|
|
->assertJsonPath('data.id', $entry->id)
|
|
->assertJsonPath('data.rows.0.type', 'NORMAL')
|
|
->assertJsonPath('data.rows.0.row', '1')
|
|
->assertJsonPath('data.rows.0.max_seat', 1)
|
|
->assertJsonPath('data.rows.0.price', '100000.00');
|
|
|
|
$response = $this->putJson('/api/v1/adminapp/tenant/desfile/entries', [
|
|
'rows' => [
|
|
[
|
|
'type' => 'NORMAL',
|
|
'sector' => 'A',
|
|
'row' => '1',
|
|
'max_seat' => 2,
|
|
'price' => 120000,
|
|
],
|
|
[
|
|
'type' => 'VIP + LUNCH',
|
|
'sector' => 'B',
|
|
'row' => '2',
|
|
'max_seat' => 3,
|
|
'price' => 250000,
|
|
],
|
|
],
|
|
]);
|
|
|
|
$response
|
|
->assertOk()
|
|
->assertJsonCount(2, 'data.rows')
|
|
->assertJsonPath('data.rows.0.max_seat', 2)
|
|
->assertJsonPath('data.rows.0.price', '120000.00')
|
|
->assertJsonPath('data.rows.1.max_seat', 3)
|
|
->assertJsonPath('data.rows.1.type', 'VIP + LUNCH');
|
|
|
|
$this->assertDatabaseCount('variantes', 5);
|
|
$this->assertDatabaseHas('catalog_items', [
|
|
'id' => $entry->id,
|
|
'precio' => 120000,
|
|
]);
|
|
$this->assertSame('120000.00', $existing->fresh()->precio);
|
|
$this->assertSame(2, $entry->variants()->where('precio', 120000)->count());
|
|
$this->assertSame(3, $entry->variants()->where('precio', 250000)->count());
|
|
$this->assertSame(5, Inventory::query()->where('real_stock', 1)->count());
|
|
}
|
|
|
|
public function test_it_replaces_and_toggles_the_entry_image(): void
|
|
{
|
|
Storage::fake('s3');
|
|
[$tenant, $entry] = $this->configuredEntry();
|
|
$oldImage = Attachment::query()->create([
|
|
'path' => 'catalog-items/old.png',
|
|
'filename' => 'old.png',
|
|
'type' => AttachmentType::Image,
|
|
'mime_type' => 'image/png',
|
|
'extension' => 'png',
|
|
'size' => 10,
|
|
]);
|
|
Storage::disk('s3')->put($oldImage->path, 'old');
|
|
$entry->allAttachments()->attach($oldImage->id, ['orden' => 0]);
|
|
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
|
|
|
$this->post('/api/v1/adminapp/tenant/desfile/entries/image', [
|
|
'image' => UploadedFile::fake()->image('plano.png'),
|
|
'is_enabled' => false,
|
|
], ['Accept' => 'application/json'])
|
|
->assertOk()
|
|
->assertJsonPath('data.image.filename', 'plano.png')
|
|
->assertJsonPath('data.image.is_enabled', false);
|
|
|
|
$this->assertDatabaseMissing('attachments', ['id' => $oldImage->id]);
|
|
$this->assertCount(0, $entry->fresh()->attachments);
|
|
$this->assertCount(1, $entry->fresh()->allAttachments);
|
|
|
|
$this->patchJson('/api/v1/adminapp/tenant/desfile/entries/image', [
|
|
'is_enabled' => true,
|
|
])
|
|
->assertOk()
|
|
->assertJsonPath('data.image.is_enabled', true);
|
|
|
|
$this->assertCount(1, $entry->fresh()->attachments);
|
|
}
|
|
|
|
public function test_it_rejects_duplicate_rows(): void
|
|
{
|
|
[$tenant] = $this->configuredEntry();
|
|
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
|
|
|
$payload = [
|
|
'rows' => [
|
|
$this->rowPayload('NORMAL', 'A', '1', 10, 100),
|
|
$this->rowPayload('normal', 'A', '1', 20, 200),
|
|
],
|
|
];
|
|
|
|
$this->putJson('/api/v1/adminapp/tenant/desfile/entries', $payload)
|
|
->assertUnprocessable()
|
|
->assertJsonValidationErrors('rows.1');
|
|
}
|
|
|
|
public function test_it_rejects_rows_and_seat_quantities_outside_the_configured_ranges(): void
|
|
{
|
|
[$tenant] = $this->configuredEntry();
|
|
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
|
|
|
$this->putJson('/api/v1/adminapp/tenant/desfile/entries', [
|
|
'rows' => [
|
|
$this->rowPayload('NORMAL', 'A', '6', 1, 100),
|
|
$this->rowPayload('NORMAL', 'A', '1', 101, 100),
|
|
],
|
|
])
|
|
->assertUnprocessable()
|
|
->assertJsonValidationErrors(['rows.0.row', 'rows.1.max_seat']);
|
|
}
|
|
|
|
public function test_it_cannot_reduce_the_maximum_below_a_reserved_seat(): void
|
|
{
|
|
[$tenant, $entry] = $this->configuredEntry();
|
|
$reserved = $this->createVariant($entry, 'NORMAL', 'A', '1', '3', 100);
|
|
$reserved->inventory()->update(['reserved_stock' => 1]);
|
|
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
|
|
|
$this->putJson('/api/v1/adminapp/tenant/desfile/entries', [
|
|
'rows' => [$this->rowPayload('NORMAL', 'A', '1', 2, 100)],
|
|
])
|
|
->assertUnprocessable()
|
|
->assertJsonValidationErrors('rows');
|
|
|
|
$this->assertDatabaseHas('variantes', ['id' => $reserved->id, 'deleted_at' => null]);
|
|
}
|
|
|
|
/** @return array{Tenant, CatalogItem} */
|
|
private function configuredEntry(string $tenantCode = 'desfile_pura_tendencia'): array
|
|
{
|
|
$tenant = Tenant::query()->create([
|
|
'codigo' => $tenantCode,
|
|
'nombre' => 'Desfile',
|
|
'dominio' => "{$tenantCode}.test",
|
|
'website_type_code' => 'onticket',
|
|
]);
|
|
$menu = Menu::query()->firstOrCreate(
|
|
['code' => 'adminapp.desfile.entradas'],
|
|
['label' => 'Entradas', 'route' => '/admin/desfile/entradas'],
|
|
);
|
|
$tenant->menues()->attach($menu->code);
|
|
$entry = CatalogItem::query()->create([
|
|
'tenant_code' => $tenant->codigo,
|
|
'slug' => 'entrada',
|
|
'nombre' => 'Entrada',
|
|
'precio' => 0,
|
|
'inventory_policy' => InventoryPolicy::Tracked,
|
|
'inventory_subject' => InventorySubject::Seat,
|
|
'has_tickets' => true,
|
|
]);
|
|
|
|
foreach ([
|
|
'tipo' => ['VIP + LUNCH', 'NORMAL'],
|
|
'sector' => ['A', 'B', 'C', 'D'],
|
|
'fila' => array_map('strval', range(1, 5)),
|
|
'asiento' => array_map('strval', range(1, 100)),
|
|
] as $code => $options) {
|
|
$attribute = Attribute::query()->create([
|
|
'tenant_codigo' => $tenant->codigo,
|
|
'codigo' => $code,
|
|
'nombre' => ucfirst($code),
|
|
'type' => FieldType::Select,
|
|
'is_required' => true,
|
|
]);
|
|
foreach ($options as $order => $option) {
|
|
$attribute->options()->create([
|
|
'value' => $option,
|
|
'label' => $option,
|
|
'sort_order' => $order + 1,
|
|
]);
|
|
}
|
|
$entry->itemAttributes()->create([
|
|
'attribute_id' => $attribute->id,
|
|
'sort_order' => $entry->itemAttributes()->count() + 1,
|
|
]);
|
|
}
|
|
|
|
return [$tenant, $entry];
|
|
}
|
|
|
|
private function createVariant(
|
|
CatalogItem $entry,
|
|
string $type,
|
|
string $sector,
|
|
string $row,
|
|
string $seat,
|
|
int $price,
|
|
): Variant {
|
|
$variant = $entry->variants()->create([
|
|
'inventory_id' => Inventory::query()->create(['real_stock' => 1])->id,
|
|
'precio' => $price,
|
|
]);
|
|
$itemAttributes = $entry->itemAttributes()->with('attribute')->get()->keyBy(
|
|
fn (ItemAttribute $itemAttribute): string => $itemAttribute->attribute->codigo,
|
|
);
|
|
|
|
foreach (compact('type', 'sector', 'row', 'seat') as $input => $value) {
|
|
$code = ['type' => 'tipo', 'sector' => 'sector', 'row' => 'fila', 'seat' => 'asiento'][$input];
|
|
$variant->definitions()->create([
|
|
'item_attribute_id' => $itemAttributes[$code]->id,
|
|
'value' => $value,
|
|
]);
|
|
}
|
|
|
|
return $variant;
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
private function rowPayload(
|
|
string $type,
|
|
string $sector,
|
|
string $row,
|
|
int $maxSeat,
|
|
int $price,
|
|
): array {
|
|
return [
|
|
...compact('type', 'sector', 'row', 'price'),
|
|
'max_seat' => $maxSeat,
|
|
];
|
|
}
|
|
|
|
private function createAdminAppUser(Tenant $tenant): User
|
|
{
|
|
return User::factory()->create([
|
|
'rol_codigo' => RoleCode::AdminApp->value,
|
|
'tenant_codigo' => $tenant->codigo,
|
|
]);
|
|
}
|
|
}
|