feat(desfile): associate tickets menu with desfile tenant and add migration test
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Domains\Ticketing\Ticket\Requests;
|
namespace App\Domains\Ticketing\Ticket\Requests;
|
||||||
|
|
||||||
use App\Domains\Ticketing\Ticket\Models\Ticket;
|
use App\Domains\Ticketing\Ticket\Models\Ticket;
|
||||||
|
use App\Domains\Ticketing\Ticket\Services\AdminAppTicketAttributeService;
|
||||||
use App\Domains\Ticketing\Ticket\Services\AdminAppTicketColumnService;
|
use App\Domains\Ticketing\Ticket\Services\AdminAppTicketColumnService;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
@@ -22,7 +23,7 @@ class AdminAppTicketIndexRequest extends FormRequest
|
|||||||
? []
|
? []
|
||||||
: app(AdminAppTicketColumnService::class)->sortableKeys($tenant);
|
: app(AdminAppTicketColumnService::class)->sortableKeys($tenant);
|
||||||
|
|
||||||
return [
|
$rules = [
|
||||||
'q' => ['sometimes', 'nullable', 'string', 'max:255'],
|
'q' => ['sometimes', 'nullable', 'string', 'max:255'],
|
||||||
'category' => ['sometimes', 'nullable', 'string', 'max:255'],
|
'category' => ['sometimes', 'nullable', 'string', 'max:255'],
|
||||||
'product' => ['sometimes', 'nullable', 'string', 'max:255'],
|
'product' => ['sometimes', 'nullable', 'string', 'max:255'],
|
||||||
@@ -39,5 +40,18 @@ class AdminAppTicketIndexRequest extends FormRequest
|
|||||||
'sort_by' => ['sometimes', 'nullable', 'string', Rule::in($sortableKeys)],
|
'sort_by' => ['sometimes', 'nullable', 'string', Rule::in($sortableKeys)],
|
||||||
'sort_direction' => ['sometimes', 'nullable', 'string', Rule::in(['asc', 'desc'])],
|
'sort_direction' => ['sometimes', 'nullable', 'string', Rule::in(['asc', 'desc'])],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if ($tenant !== null) {
|
||||||
|
foreach (app(AdminAppTicketAttributeService::class)->attributes($tenant) as $attribute) {
|
||||||
|
$rules[$attribute['code']] = [
|
||||||
|
'sometimes',
|
||||||
|
'nullable',
|
||||||
|
'string',
|
||||||
|
Rule::in(array_column($attribute['options'], 'value')),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rules;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Ticketing\Ticket\Services;
|
||||||
|
|
||||||
|
use App\Domains\Commerce\Catalog\Models\CatalogItem;
|
||||||
|
use App\Domains\Core\Tenant\Models\Tenant;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
|
class AdminAppTicketAttributeService
|
||||||
|
{
|
||||||
|
private const DESFILE_PURA_TENDENCIA = 'desfile_pura_tendencia';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<array{
|
||||||
|
* code: string,
|
||||||
|
* label: string,
|
||||||
|
* options: list<array{value: string, label: string}>
|
||||||
|
* }>
|
||||||
|
*/
|
||||||
|
public function attributes(Tenant $tenant): array
|
||||||
|
{
|
||||||
|
if ($tenant->codigo !== self::DESFILE_PURA_TENDENCIA) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = CatalogItem::withTrashed()
|
||||||
|
->where('tenant_code', $tenant->codigo)
|
||||||
|
->where(function (Builder $query): void {
|
||||||
|
$query
|
||||||
|
->where(function (Builder $activeQuery): void {
|
||||||
|
$activeQuery
|
||||||
|
->whereNull('catalog_items.deleted_at')
|
||||||
|
->where('has_tickets', true);
|
||||||
|
})
|
||||||
|
->orWhereHas('sourceTickets');
|
||||||
|
})
|
||||||
|
->with([
|
||||||
|
'itemAttributes' => fn ($query) => $query->orderBy('sort_order')->orderBy('id'),
|
||||||
|
'itemAttributes.attribute.options',
|
||||||
|
'variants' => fn ($query) => $query
|
||||||
|
->withTrashed()
|
||||||
|
->where(fn (Builder $variantQuery): Builder => $variantQuery
|
||||||
|
->whereNull('variantes.deleted_at')
|
||||||
|
->orWhereHas('sourceTickets'))
|
||||||
|
->orderBy('id'),
|
||||||
|
'variants.definitions.itemAttribute.attribute.options',
|
||||||
|
])
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$attributes = [];
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
foreach ($item->itemAttributes as $itemAttribute) {
|
||||||
|
$attribute = $itemAttribute->attribute;
|
||||||
|
if ($attribute === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$code = $attribute->codigo;
|
||||||
|
$attributes[$code] ??= [
|
||||||
|
'code' => $code,
|
||||||
|
'label' => $itemAttribute->ticket_label ?: $attribute->nombre,
|
||||||
|
'sort_order' => $itemAttribute->sort_order,
|
||||||
|
'options' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($item->variants as $variant) {
|
||||||
|
$definition = $variant->definitions->firstWhere('item_attribute_id', $itemAttribute->id);
|
||||||
|
if ($definition === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = (string) $definition->value;
|
||||||
|
$option = $attribute->options->firstWhere('value', $value);
|
||||||
|
$attributes[$code]['options'][$value] = [
|
||||||
|
'value' => $value,
|
||||||
|
'label' => (string) ($option?->label ?: $value),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uasort($attributes, fn (array $left, array $right): int => $left['sort_order'] <=> $right['sort_order']
|
||||||
|
?: $left['label'] <=> $right['label']);
|
||||||
|
|
||||||
|
return array_values(array_map(fn (array $attribute): array => [
|
||||||
|
'code' => $attribute['code'],
|
||||||
|
'label' => $attribute['label'],
|
||||||
|
'options' => array_values($attribute['options']),
|
||||||
|
], $attributes));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,9 +8,17 @@ class AdminAppTicketColumnService
|
|||||||
{
|
{
|
||||||
private const FIESTA_FUTBOL_INFANTIL = 'fiesta_futbol_infantil';
|
private const FIESTA_FUTBOL_INFANTIL = 'fiesta_futbol_infantil';
|
||||||
|
|
||||||
|
private const DESFILE_PURA_TENDENCIA = 'desfile_pura_tendencia';
|
||||||
|
|
||||||
|
public function __construct(private readonly AdminAppTicketAttributeService $attributeService) {}
|
||||||
|
|
||||||
/** @return list<array{key: string, label: string, type: string, sortable: bool, sort_param: string, width: string, excel_width: int}> */
|
/** @return list<array{key: string, label: string, type: string, sortable: bool, sort_param: string, width: string, excel_width: int}> */
|
||||||
public function columns(Tenant $tenant): array
|
public function columns(Tenant $tenant): array
|
||||||
{
|
{
|
||||||
|
if ($tenant->codigo === self::DESFILE_PURA_TENDENCIA) {
|
||||||
|
return $this->desfileColumns($tenant);
|
||||||
|
}
|
||||||
|
|
||||||
$keys = $tenant->codigo === self::FIESTA_FUTBOL_INFANTIL
|
$keys = $tenant->codigo === self::FIESTA_FUTBOL_INFANTIL
|
||||||
? ['order_number', 'category', 'product', 'type', 'date', 'size', 'amount', 'client', 'id', 'status', 'scanned_by']
|
? ['order_number', 'category', 'product', 'type', 'date', 'size', 'amount', 'client', 'id', 'status', 'scanned_by']
|
||||||
: ['order_number', 'product', 'amount', 'client', 'id', 'status', 'scanned_by'];
|
: ['order_number', 'product', 'amount', 'client', 'id', 'status', 'scanned_by'];
|
||||||
@@ -45,6 +53,31 @@ class AdminAppTicketColumnService
|
|||||||
return $columns;
|
return $columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return list<array{key: string, label: string, type: string, sortable: bool, sort_param: string, width: string, excel_width: int}> */
|
||||||
|
private function desfileColumns(Tenant $tenant): array
|
||||||
|
{
|
||||||
|
$attributeColumns = array_map(fn (array $attribute): array => [
|
||||||
|
'key' => $attribute['code'],
|
||||||
|
'label' => $attribute['label'],
|
||||||
|
'type' => 'text',
|
||||||
|
'sortable' => false,
|
||||||
|
'sort_param' => $attribute['code'],
|
||||||
|
'width' => '8%',
|
||||||
|
'excel_width' => 15,
|
||||||
|
], $this->attributeService->attributes($tenant));
|
||||||
|
|
||||||
|
return [
|
||||||
|
$this->definitions()['order_number'],
|
||||||
|
$this->definitions()['product'],
|
||||||
|
...$attributeColumns,
|
||||||
|
$this->definitions()['amount'],
|
||||||
|
$this->definitions()['client'],
|
||||||
|
$this->definitions()['id'],
|
||||||
|
$this->definitions()['status'],
|
||||||
|
$this->definitions()['scanned_by'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/** @return list<array{key: string, label: string, type: string, sortable: bool, sort_param: string, width: string}> */
|
/** @return list<array{key: string, label: string, type: string, sortable: bool, sort_param: string, width: string}> */
|
||||||
public function publicColumns(Tenant $tenant): array
|
public function publicColumns(Tenant $tenant): array
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -48,8 +48,17 @@ class AdminAppTicketRowService
|
|||||||
{
|
{
|
||||||
$details ??= $this->details($ticket);
|
$details ??= $this->details($ticket);
|
||||||
$presentation = $this->presentation($ticket, $details);
|
$presentation = $this->presentation($ticket, $details);
|
||||||
|
$attributeValues = collect($details['variant_properties'] ?? [])
|
||||||
|
->mapWithKeys(fn (array $property): array => [
|
||||||
|
$property['code'] => collect($property['values'] ?? [])
|
||||||
|
->pluck('label')
|
||||||
|
->filter()
|
||||||
|
->implode(', ') ?: '-',
|
||||||
|
])
|
||||||
|
->all();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
...$attributeValues,
|
||||||
'order_number' => $details['order_number'],
|
'order_number' => $details['order_number'],
|
||||||
'category' => $presentation['category'],
|
'category' => $presentation['category'],
|
||||||
'product' => $presentation['product'],
|
'product' => $presentation['product'],
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Domains\Ticketing\Ticket\Services;
|
namespace App\Domains\Ticketing\Ticket\Services;
|
||||||
|
|
||||||
use App\Domains\Core\Auth\Models\User;
|
|
||||||
use App\Domains\Commerce\Catalog\Enums\InventoryPolicy;
|
use App\Domains\Commerce\Catalog\Enums\InventoryPolicy;
|
||||||
use App\Domains\Commerce\Catalog\Models\Inventory;
|
use App\Domains\Commerce\Catalog\Models\Inventory;
|
||||||
use App\Domains\Commerce\Catalog\Models\Variant;
|
use App\Domains\Commerce\Catalog\Models\Variant;
|
||||||
use App\Domains\Commerce\Purchase\Models\PurchaseItem;
|
use App\Domains\Commerce\Purchase\Models\PurchaseItem;
|
||||||
use App\Domains\Commerce\Purchase\Services\PurchaseRefundSummaryService;
|
use App\Domains\Commerce\Purchase\Services\PurchaseRefundSummaryService;
|
||||||
|
use App\Domains\Core\Auth\Models\User;
|
||||||
use App\Domains\Core\Tenant\Models\Tenant;
|
use App\Domains\Core\Tenant\Models\Tenant;
|
||||||
use App\Domains\Ticketing\Ticket\Models\Ticket;
|
use App\Domains\Ticketing\Ticket\Models\Ticket;
|
||||||
use App\Domains\Ticketing\Ticket\Models\TicketRefund;
|
use App\Domains\Ticketing\Ticket\Models\TicketRefund;
|
||||||
@@ -32,6 +32,7 @@ class AdminAppTicketService
|
|||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly AdminAppTicketColumnService $columnService,
|
private readonly AdminAppTicketColumnService $columnService,
|
||||||
|
private readonly AdminAppTicketAttributeService $attributeService,
|
||||||
private readonly AdminAppTicketRowService $rowService,
|
private readonly AdminAppTicketRowService $rowService,
|
||||||
private readonly PurchaseRefundSummaryService $refundSummaryService,
|
private readonly PurchaseRefundSummaryService $refundSummaryService,
|
||||||
) {}
|
) {}
|
||||||
@@ -347,6 +348,13 @@ class AdminAppTicketService
|
|||||||
|
|
||||||
$this->applyStatusFilter($query, $filters['status'] ?? null);
|
$this->applyStatusFilter($query, $filters['status'] ?? null);
|
||||||
|
|
||||||
|
foreach ($this->attributeService->attributes($tenant) as $attribute) {
|
||||||
|
$value = $filters[$attribute['code']] ?? null;
|
||||||
|
if ($value !== null && $value !== '') {
|
||||||
|
$this->whereVariantDefinition($query, $attribute['code'], (string) $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,19 @@ namespace App\Shared\Forms\Services;
|
|||||||
|
|
||||||
use App\Domains\Core\Tenant\Models\Tenant;
|
use App\Domains\Core\Tenant\Models\Tenant;
|
||||||
use App\Domains\Ticketing\Ticket\Models\Ticket;
|
use App\Domains\Ticketing\Ticket\Models\Ticket;
|
||||||
|
use App\Domains\Ticketing\Ticket\Services\AdminAppTicketAttributeService;
|
||||||
use App\Domains\Ticketing\Ticket\Services\AdminAppTicketColumnService;
|
use App\Domains\Ticketing\Ticket\Services\AdminAppTicketColumnService;
|
||||||
|
|
||||||
class TicketFilterFormService
|
class TicketFilterFormService
|
||||||
{
|
{
|
||||||
private const FIESTA_FUTBOL_INFANTIL = 'fiesta_futbol_infantil';
|
private const FIESTA_FUTBOL_INFANTIL = 'fiesta_futbol_infantil';
|
||||||
|
|
||||||
|
private const DESFILE_PURA_TENDENCIA = 'desfile_pura_tendencia';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly TicketFormService $ticketFormService,
|
private readonly TicketFormService $ticketFormService,
|
||||||
private readonly AdminAppTicketColumnService $columnService,
|
private readonly AdminAppTicketColumnService $columnService,
|
||||||
|
private readonly AdminAppTicketAttributeService $attributeService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/** @return array<string, mixed> */
|
/** @return array<string, mixed> */
|
||||||
@@ -27,6 +31,13 @@ class TicketFilterFormService
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($tenant->codigo === self::DESFILE_PURA_TENDENCIA) {
|
||||||
|
$fields = [
|
||||||
|
...$this->desfileFields($tenant),
|
||||||
|
...$this->commonFields(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'code' => 'tickets_filter',
|
'code' => 'tickets_filter',
|
||||||
'action' => '/api/v1/adminapp/tenant/tickets',
|
'action' => '/api/v1/adminapp/tenant/tickets',
|
||||||
@@ -36,6 +47,21 @@ class TicketFilterFormService
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return list<array<string, mixed>> */
|
||||||
|
private function desfileFields(Tenant $tenant): array
|
||||||
|
{
|
||||||
|
return array_map(fn (array $attribute): array => [
|
||||||
|
'name' => $attribute['code'],
|
||||||
|
'query_param' => $attribute['code'],
|
||||||
|
'label' => $attribute['label'],
|
||||||
|
'type' => 'select',
|
||||||
|
'required' => false,
|
||||||
|
'default' => null,
|
||||||
|
'placeholder' => $attribute['label'],
|
||||||
|
'options' => $attribute['options'],
|
||||||
|
], $this->attributeService->attributes($tenant));
|
||||||
|
}
|
||||||
|
|
||||||
/** @return list<array<string, mixed>> */
|
/** @return list<array<string, mixed>> */
|
||||||
private function fiestaFutbolInfantilFields(Tenant $tenant): array
|
private function fiestaFutbolInfantilFields(Tenant $tenant): array
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
private const MENU_CODE = 'adminapp.tickets';
|
||||||
|
|
||||||
|
private const TENANT_CODE = 'desfile_pura_tendencia';
|
||||||
|
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
! DB::table('menues')->where('code', self::MENU_CODE)->exists()
|
||||||
|
|| ! DB::table('tenants')->where('codigo', self::TENANT_CODE)->exists()
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$now = now();
|
||||||
|
|
||||||
|
DB::table('tenants_menues')->updateOrInsert(
|
||||||
|
[
|
||||||
|
'tenant_code' => self::TENANT_CODE,
|
||||||
|
'menu_code' => self::MENU_CODE,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'static_content' => null,
|
||||||
|
'created_at' => $now,
|
||||||
|
'updated_at' => $now,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
DB::table('tenants_menues')
|
||||||
|
->where('tenant_code', self::TENANT_CODE)
|
||||||
|
->where('menu_code', self::MENU_CODE)
|
||||||
|
->delete();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -297,8 +297,10 @@ class MenuSeeder extends Seeder
|
|||||||
'sonder',
|
'sonder',
|
||||||
'fiesta_futbol_infantil',
|
'fiesta_futbol_infantil',
|
||||||
];
|
];
|
||||||
$fiestaCategoryMenuCodes = [
|
$ticketAdminMenuCodes = [
|
||||||
'adminapp.tickets',
|
'adminapp.tickets',
|
||||||
|
];
|
||||||
|
$fiestaCategoryMenuCodes = [
|
||||||
'adminapp.fiesta-futbol-infantil.entradas',
|
'adminapp.fiesta-futbol-infantil.entradas',
|
||||||
'adminapp.fiesta-futbol-infantil.alojamientos',
|
'adminapp.fiesta-futbol-infantil.alojamientos',
|
||||||
'adminapp.fiesta-futbol-infantil.merchandising',
|
'adminapp.fiesta-futbol-infantil.merchandising',
|
||||||
@@ -379,6 +381,10 @@ class MenuSeeder extends Seeder
|
|||||||
$menuCodes = array_diff($menuCodes, $helpMenuCodes);
|
$menuCodes = array_diff($menuCodes, $helpMenuCodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! in_array($tenant->codigo, ['fiesta_futbol_infantil', 'desfile_pura_tendencia'], true)) {
|
||||||
|
$menuCodes = array_diff($menuCodes, $ticketAdminMenuCodes);
|
||||||
|
}
|
||||||
|
|
||||||
if ($tenant->codigo !== 'fiesta_futbol_infantil') {
|
if ($tenant->codigo !== 'fiesta_futbol_infantil') {
|
||||||
$menuCodes = array_diff($menuCodes, $fiestaCategoryMenuCodes);
|
$menuCodes = array_diff($menuCodes, $fiestaCategoryMenuCodes);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Migrations;
|
||||||
|
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class AssociateAdminAppTicketsMenuWithDesfileTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
Schema::create('menues', function (Blueprint $table): void {
|
||||||
|
$table->string('code')->primary();
|
||||||
|
});
|
||||||
|
Schema::create('tenants', function (Blueprint $table): void {
|
||||||
|
$table->string('codigo')->primary();
|
||||||
|
});
|
||||||
|
Schema::create('tenants_menues', function (Blueprint $table): void {
|
||||||
|
$table->id();
|
||||||
|
$table->string('tenant_code');
|
||||||
|
$table->string('menu_code');
|
||||||
|
$table->json('static_content')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->unique(['tenant_code', 'menu_code']);
|
||||||
|
});
|
||||||
|
|
||||||
|
DB::table('menues')->insert(['code' => 'adminapp.tickets']);
|
||||||
|
DB::table('tenants')->insert([
|
||||||
|
['codigo' => 'desfile_pura_tendencia'],
|
||||||
|
['codigo' => 'fiesta_futbol_infantil'],
|
||||||
|
]);
|
||||||
|
DB::table('tenants_menues')->insert([
|
||||||
|
'tenant_code' => 'fiesta_futbol_infantil',
|
||||||
|
'menu_code' => 'adminapp.tickets',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('tenants_menues');
|
||||||
|
Schema::dropIfExists('tenants');
|
||||||
|
Schema::dropIfExists('menues');
|
||||||
|
|
||||||
|
parent::tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_associates_the_tickets_menu_only_with_desfile_and_rolls_back_that_association(): void
|
||||||
|
{
|
||||||
|
$migration = require database_path(
|
||||||
|
'migrations/2026_09_25_000000_associate_adminapp_tickets_menu_with_desfile.php'
|
||||||
|
);
|
||||||
|
|
||||||
|
$migration->down();
|
||||||
|
$migration->up();
|
||||||
|
$migration->up();
|
||||||
|
|
||||||
|
$this->assertSame(1, DB::table('tenants_menues')
|
||||||
|
->where('tenant_code', 'desfile_pura_tendencia')
|
||||||
|
->where('menu_code', 'adminapp.tickets')
|
||||||
|
->count());
|
||||||
|
$this->assertDatabaseHas('tenants_menues', [
|
||||||
|
'tenant_code' => 'fiesta_futbol_infantil',
|
||||||
|
'menu_code' => 'adminapp.tickets',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$migration->down();
|
||||||
|
|
||||||
|
$this->assertDatabaseMissing('tenants_menues', [
|
||||||
|
'tenant_code' => 'desfile_pura_tendencia',
|
||||||
|
'menu_code' => 'adminapp.tickets',
|
||||||
|
]);
|
||||||
|
$this->assertDatabaseHas('tenants_menues', [
|
||||||
|
'tenant_code' => 'fiesta_futbol_infantil',
|
||||||
|
'menu_code' => 'adminapp.tickets',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,22 +2,22 @@
|
|||||||
|
|
||||||
namespace Tests\Feature\Ticket;
|
namespace Tests\Feature\Ticket;
|
||||||
|
|
||||||
use App\Shared\Attachable\Enums\AttachmentType;
|
|
||||||
use App\Shared\Attachable\Models\Attachment;
|
|
||||||
use App\Domains\Core\Auth\Models\User;
|
|
||||||
use App\Domains\Core\Authorization\Enums\RoleCode;
|
|
||||||
use App\Domains\Commerce\Catalog\Models\Attribute;
|
use App\Domains\Commerce\Catalog\Models\Attribute;
|
||||||
use App\Domains\Commerce\Catalog\Models\CatalogItem;
|
use App\Domains\Commerce\Catalog\Models\CatalogItem;
|
||||||
use App\Domains\Commerce\Catalog\Models\Inventory;
|
use App\Domains\Commerce\Catalog\Models\Inventory;
|
||||||
use App\Domains\Commerce\Catalog\Models\Variant;
|
use App\Domains\Commerce\Catalog\Models\Variant;
|
||||||
use App\Domains\Core\Menu\Models\Menu;
|
|
||||||
use App\Domains\Commerce\Purchase\Models\Purchase;
|
use App\Domains\Commerce\Purchase\Models\Purchase;
|
||||||
use App\Domains\Commerce\Purchase\Models\PurchaseItem;
|
use App\Domains\Commerce\Purchase\Models\PurchaseItem;
|
||||||
use App\Shared\Enums\FieldType;
|
use App\Domains\Core\Auth\Models\User;
|
||||||
use App\Domains\Core\Tenant\Models\Tenant;
|
use App\Domains\Core\Authorization\Enums\RoleCode;
|
||||||
|
use App\Domains\Core\Menu\Models\Menu;
|
||||||
use App\Domains\Core\Tenant\Models\AdminWebsiteType;
|
use App\Domains\Core\Tenant\Models\AdminWebsiteType;
|
||||||
|
use App\Domains\Core\Tenant\Models\Tenant;
|
||||||
use App\Domains\Ticketing\Ticket\Models\Ticket;
|
use App\Domains\Ticketing\Ticket\Models\Ticket;
|
||||||
use App\Domains\Ticketing\Ticket\Models\TicketRefund;
|
use App\Domains\Ticketing\Ticket\Models\TicketRefund;
|
||||||
|
use App\Shared\Attachable\Enums\AttachmentType;
|
||||||
|
use App\Shared\Attachable\Models\Attachment;
|
||||||
|
use App\Shared\Enums\FieldType;
|
||||||
use Barryvdh\DomPDF\ServiceProvider as DomPdfServiceProvider;
|
use Barryvdh\DomPDF\ServiceProvider as DomPdfServiceProvider;
|
||||||
use Database\Seeders\AttributeSeeder;
|
use Database\Seeders\AttributeSeeder;
|
||||||
use Database\Seeders\AuthorizationSeeder;
|
use Database\Seeders\AuthorizationSeeder;
|
||||||
@@ -840,6 +840,103 @@ class AdminAppTicketControllerTest extends TestCase
|
|||||||
->assertJsonPath('data.0.variant_properties.0.values.0.label', 'XL');
|
->assertJsonPath('data.0.variant_properties.0.values.0.label', 'XL');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_desfile_exposes_and_applies_filters_and_columns_from_ticket_product_attributes(): void
|
||||||
|
{
|
||||||
|
$tenant = $this->createTenant('desfile_pura_tendencia');
|
||||||
|
$admin = $this->createAdminAppUser($tenant);
|
||||||
|
$this->grantTicketsMenu($tenant);
|
||||||
|
Sanctum::actingAs($admin);
|
||||||
|
|
||||||
|
$item = CatalogItem::query()->create([
|
||||||
|
'tenant_code' => $tenant->codigo,
|
||||||
|
'slug' => 'entrada',
|
||||||
|
'nombre' => 'Entrada',
|
||||||
|
'precio' => '1000.00',
|
||||||
|
'has_tickets' => true,
|
||||||
|
]);
|
||||||
|
$type = Attribute::query()->create([
|
||||||
|
'tenant_codigo' => $tenant->codigo,
|
||||||
|
'codigo' => 'tipo',
|
||||||
|
'nombre' => 'Tipo',
|
||||||
|
'type' => FieldType::Select,
|
||||||
|
]);
|
||||||
|
$type->options()->create(['value' => 'vip', 'label' => 'VIP']);
|
||||||
|
$sector = Attribute::query()->create([
|
||||||
|
'tenant_codigo' => $tenant->codigo,
|
||||||
|
'codigo' => 'sector',
|
||||||
|
'nombre' => 'Sector',
|
||||||
|
'type' => FieldType::Select,
|
||||||
|
]);
|
||||||
|
$sector->options()->createMany([
|
||||||
|
['value' => 'a', 'label' => 'A'],
|
||||||
|
['value' => 'b', 'label' => 'B'],
|
||||||
|
]);
|
||||||
|
$typeItemAttribute = $item->itemAttributes()->create([
|
||||||
|
'attribute_id' => $type->id,
|
||||||
|
'sort_order' => 1,
|
||||||
|
]);
|
||||||
|
$sectorItemAttribute = $item->itemAttributes()->create([
|
||||||
|
'attribute_id' => $sector->id,
|
||||||
|
'sort_order' => 2,
|
||||||
|
'ticket_label' => 'Lado',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$variants = collect(['a', 'b'])->map(function (string $sectorValue) use (
|
||||||
|
$item,
|
||||||
|
$typeItemAttribute,
|
||||||
|
$sectorItemAttribute,
|
||||||
|
): Variant {
|
||||||
|
$variant = Variant::query()->create([
|
||||||
|
'catalog_item_id' => $item->id,
|
||||||
|
'inventory_id' => Inventory::query()->create()->id,
|
||||||
|
]);
|
||||||
|
$variant->definitions()->createMany([
|
||||||
|
['item_attribute_id' => $typeItemAttribute->id, 'value' => 'vip'],
|
||||||
|
['item_attribute_id' => $sectorItemAttribute->id, 'value' => $sectorValue],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $variant;
|
||||||
|
});
|
||||||
|
|
||||||
|
$purchase = $this->createPurchase($tenant, $admin, '2026-09-25 10:00:00');
|
||||||
|
$matchingItem = $this->createPurchaseItem($purchase, $item, $variants[0]);
|
||||||
|
$otherItem = $this->createPurchaseItem($purchase, $item, $variants[1]);
|
||||||
|
$matching = $this->createTicket($tenant, $admin, [
|
||||||
|
'source_purchase_item_id' => $matchingItem->id,
|
||||||
|
'source_catalog_item_id' => $item->id,
|
||||||
|
'source_variant_id' => $variants[0]->id,
|
||||||
|
]);
|
||||||
|
$this->createTicket($tenant, $admin, [
|
||||||
|
'source_purchase_item_id' => $otherItem->id,
|
||||||
|
'source_catalog_item_id' => $item->id,
|
||||||
|
'source_variant_id' => $variants[1]->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->getJson('/api/v1/adminapp/forms/tickets-filter')
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('data.fields.0.name', 'tipo')
|
||||||
|
->assertJsonPath('data.fields.0.label', 'Tipo')
|
||||||
|
->assertJsonPath('data.fields.0.options.0', ['value' => 'vip', 'label' => 'VIP'])
|
||||||
|
->assertJsonPath('data.fields.1.name', 'sector')
|
||||||
|
->assertJsonPath('data.fields.1.label', 'Lado')
|
||||||
|
->assertJsonPath('data.columns.1.key', 'product')
|
||||||
|
->assertJsonPath('data.columns.2.key', 'tipo')
|
||||||
|
->assertJsonPath('data.columns.2.sortable', false)
|
||||||
|
->assertJsonPath('data.columns.3.key', 'sector')
|
||||||
|
->assertJsonPath('data.columns.3.label', 'Lado');
|
||||||
|
|
||||||
|
$this->getJson('/api/v1/adminapp/tenant/tickets?tipo=vip§or=a')
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonCount(1, 'data')
|
||||||
|
->assertJsonPath('data.0.id', $matching->id)
|
||||||
|
->assertJsonPath('data.0.values.tipo', 'VIP')
|
||||||
|
->assertJsonPath('data.0.values.sector', 'A');
|
||||||
|
|
||||||
|
$this->getJson('/api/v1/adminapp/tenant/tickets?sector=invalid')
|
||||||
|
->assertUnprocessable()
|
||||||
|
->assertJsonValidationErrors('sector');
|
||||||
|
}
|
||||||
|
|
||||||
public function test_it_applies_the_ticket_filter_form_values(): void
|
public function test_it_applies_the_ticket_filter_form_values(): void
|
||||||
{
|
{
|
||||||
$tenant = $this->createTenant('fiesta_futbol_infantil');
|
$tenant = $this->createTenant('fiesta_futbol_infantil');
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace Tests\Unit\Ticket;
|
namespace Tests\Unit\Ticket;
|
||||||
|
|
||||||
use App\Domains\Core\Tenant\Models\Tenant;
|
use App\Domains\Core\Tenant\Models\Tenant;
|
||||||
|
use App\Domains\Ticketing\Ticket\Services\AdminAppTicketAttributeService;
|
||||||
use App\Domains\Ticketing\Ticket\Services\AdminAppTicketColumnService;
|
use App\Domains\Ticketing\Ticket\Services\AdminAppTicketColumnService;
|
||||||
use App\Domains\Ticketing\Ticket\Services\AdminAppTicketExcelService;
|
use App\Domains\Ticketing\Ticket\Services\AdminAppTicketExcelService;
|
||||||
use App\Domains\Ticketing\Ticket\Services\AdminAppTicketPdfService;
|
use App\Domains\Ticketing\Ticket\Services\AdminAppTicketPdfService;
|
||||||
@@ -149,7 +150,7 @@ class AdminAppTicketExportServiceTest extends TestCase
|
|||||||
|
|
||||||
private function columnService(): AdminAppTicketColumnService
|
private function columnService(): AdminAppTicketColumnService
|
||||||
{
|
{
|
||||||
return new AdminAppTicketColumnService;
|
return new AdminAppTicketColumnService(new AdminAppTicketAttributeService);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function spreadsheetPath(StreamedResponse $response): string
|
private function spreadsheetPath(StreamedResponse $response): string
|
||||||
|
|||||||
Reference in New Issue
Block a user