feat(tickets): refine FFI variant filters
This commit is contained in:
@@ -23,7 +23,7 @@ class TicketFilterFormService
|
|||||||
if ($tenant->codigo === self::FIESTA_FUTBOL_INFANTIL) {
|
if ($tenant->codigo === self::FIESTA_FUTBOL_INFANTIL) {
|
||||||
$fields = [
|
$fields = [
|
||||||
...$this->fiestaFutbolInfantilFields($tenant),
|
...$this->fiestaFutbolInfantilFields($tenant),
|
||||||
...$fields,
|
...$this->commonFields(includeDate: false),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,20 +55,40 @@ class TicketFilterFormService
|
|||||||
'value' => $category['value'],
|
'value' => $category['value'],
|
||||||
'label' => $category['label'],
|
'label' => $category['label'],
|
||||||
'children' => [
|
'children' => [
|
||||||
'field' => 'product',
|
[
|
||||||
'disabled' => $category['products'] === [],
|
'field' => 'product',
|
||||||
'options' => array_map(
|
'disabled' => $category['products'] === [],
|
||||||
fn (array $product): array => [
|
'options' => array_map(
|
||||||
'value' => $product['value'],
|
fn (array $product): array => [
|
||||||
'label' => $product['label'],
|
'value' => $product['value'],
|
||||||
'children' => [
|
'label' => $product['label'],
|
||||||
'field' => 'type',
|
'children' => [
|
||||||
'disabled' => $product['types'] === [],
|
[
|
||||||
'options' => $product['types'],
|
'field' => 'type',
|
||||||
|
'disabled' => $product['types'] === [],
|
||||||
|
'options' => array_map(
|
||||||
|
fn (array $type): array => [
|
||||||
|
'value' => $type['value'],
|
||||||
|
'label' => $type['label'],
|
||||||
|
'children' => [[
|
||||||
|
'field' => 'size',
|
||||||
|
'disabled' => ($type['sizes'] ?? []) === [],
|
||||||
|
'options' => $type['sizes'] ?? [],
|
||||||
|
]],
|
||||||
|
],
|
||||||
|
$product['types'],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
$category['products'],
|
||||||
$category['products'],
|
),
|
||||||
),
|
],
|
||||||
|
[
|
||||||
|
'field' => 'date',
|
||||||
|
'disabled' => ! in_array($category['value'], ['comidas', 'comida'], true),
|
||||||
|
'options' => [],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
$form['categories'],
|
$form['categories'],
|
||||||
@@ -76,21 +96,26 @@ class TicketFilterFormService
|
|||||||
],
|
],
|
||||||
$this->dependentSelect('product', 'Producto', 'category'),
|
$this->dependentSelect('product', 'Producto', 'category'),
|
||||||
$this->dependentSelect('type', 'Tipo', 'product'),
|
$this->dependentSelect('type', 'Tipo', 'product'),
|
||||||
|
$this->dependentSelect('size', 'Talle', 'type'),
|
||||||
|
[
|
||||||
|
...$this->dependentSelect('date', 'Fecha', 'category'),
|
||||||
|
'type' => 'date',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return list<array<string, mixed>> */
|
/** @return list<array<string, mixed>> */
|
||||||
private function commonFields(): array
|
private function commonFields(bool $includeDate = true): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[
|
...($includeDate ? [[
|
||||||
'name' => 'date',
|
'name' => 'date',
|
||||||
'query_param' => 'date',
|
'query_param' => 'date',
|
||||||
'label' => 'Fecha',
|
'label' => 'Fecha',
|
||||||
'type' => 'date',
|
'type' => 'date',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'default' => null,
|
'default' => null,
|
||||||
],
|
]] : []),
|
||||||
[
|
[
|
||||||
'name' => 'status',
|
'name' => 'status',
|
||||||
'query_param' => 'status',
|
'query_param' => 'status',
|
||||||
|
|||||||
@@ -54,6 +54,18 @@ class TicketFormService
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, array{label: string|null, product: string, type: string|null, size: string|null, order: int}>
|
||||||
|
*/
|
||||||
|
private const FILTER_CATEGORY_PRESENTATIONS = [
|
||||||
|
'entradas' => ['label' => null, 'product' => self::PRODUCT, 'type' => null, 'size' => null, 'order' => 1],
|
||||||
|
'alojamientos' => ['label' => 'Camping', 'product' => 'tipo_alojamiento', 'type' => null, 'size' => null, 'order' => 2],
|
||||||
|
'camping' => ['label' => null, 'product' => 'tipo_alojamiento', 'type' => null, 'size' => null, 'order' => 2],
|
||||||
|
'comidas' => ['label' => 'Comida', 'product' => 'horario', 'type' => 'servicio', 'size' => null, 'order' => 3],
|
||||||
|
'comida' => ['label' => null, 'product' => 'horario', 'type' => 'servicio', 'size' => null, 'order' => 3],
|
||||||
|
'merchandising' => ['label' => null, 'product' => self::PRODUCT, 'type' => 'color', 'size' => 'talle', 'order' => 4],
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array{
|
* @return array{
|
||||||
* statuses: list<array{value: string, label: string}>,
|
* statuses: list<array{value: string, label: string}>,
|
||||||
@@ -79,7 +91,7 @@ class TicketFormService
|
|||||||
->orderBy('nombre')
|
->orderBy('nombre')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
return $this->build($items);
|
return $this->build($items, self::CATEGORY_PRESENTATIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,7 +166,7 @@ class TicketFormService
|
|||||||
->orderBy('nombre')
|
->orderBy('nombre')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
return $this->build($items);
|
return $this->build($items, self::FILTER_CATEGORY_PRESENTATIONS, includeSizes: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -172,17 +184,18 @@ class TicketFormService
|
|||||||
* }>
|
* }>
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
private function build(Collection $items): array
|
private function build(Collection $items, array $presentations, bool $includeSizes = false): array
|
||||||
{
|
{
|
||||||
$categories = [];
|
$categories = [];
|
||||||
|
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
$sourceCategory = trim((string) $item->category?->nombre);
|
$sourceCategory = trim((string) $item->category?->nombre);
|
||||||
$categoryValue = mb_strtolower($sourceCategory);
|
$categoryValue = mb_strtolower($sourceCategory);
|
||||||
$presentation = self::CATEGORY_PRESENTATIONS[$categoryValue] ?? [
|
$presentation = $presentations[$categoryValue] ?? [
|
||||||
'label' => null,
|
'label' => null,
|
||||||
'product' => self::PRODUCT,
|
'product' => self::PRODUCT,
|
||||||
'type' => null,
|
'type' => null,
|
||||||
|
'size' => null,
|
||||||
'order' => PHP_INT_MAX,
|
'order' => PHP_INT_MAX,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -193,18 +206,28 @@ class TicketFormService
|
|||||||
'products' => [],
|
'products' => [],
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($this->products($item, $presentation['product'], $presentation['type']) as $product) {
|
foreach ($this->products(
|
||||||
|
$item,
|
||||||
|
$presentation['product'],
|
||||||
|
$presentation['type'],
|
||||||
|
$presentation['size'] ?? null,
|
||||||
|
) as $product) {
|
||||||
$productValue = $product['value'];
|
$productValue = $product['value'];
|
||||||
$existingProduct = $categories[$categoryValue]['products'][$productValue] ?? [
|
$existingProduct = $categories[$categoryValue]['products'][$productValue] ?? [
|
||||||
'value' => $productValue,
|
'value' => $productValue,
|
||||||
'label' => $product['label'],
|
'label' => $product['label'],
|
||||||
'types' => [],
|
'types' => [],
|
||||||
|
'sizes' => [],
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($product['types'] as $type) {
|
foreach ($product['types'] as $type) {
|
||||||
$existingProduct['types'][$type['value']] = $type;
|
$existingProduct['types'][$type['value']] = $type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($product['sizes'] as $size) {
|
||||||
|
$existingProduct['sizes'][$size['value']] = $size;
|
||||||
|
}
|
||||||
|
|
||||||
$categories[$categoryValue]['products'][$productValue] = $existingProduct;
|
$categories[$categoryValue]['products'][$productValue] = $existingProduct;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,6 +250,7 @@ class TicketFormService
|
|||||||
'value' => $product['value'],
|
'value' => $product['value'],
|
||||||
'label' => $product['label'],
|
'label' => $product['label'],
|
||||||
'types' => array_values($product['types']),
|
'types' => array_values($product['types']),
|
||||||
|
...($includeSizes ? ['sizes' => array_values($product['sizes'])] : []),
|
||||||
],
|
],
|
||||||
$category['products'],
|
$category['products'],
|
||||||
)),
|
)),
|
||||||
@@ -252,16 +276,18 @@ class TicketFormService
|
|||||||
* @return list<array{
|
* @return list<array{
|
||||||
* value: string,
|
* value: string,
|
||||||
* label: string,
|
* label: string,
|
||||||
* types: list<array{value: string, label: string}>
|
* types: list<array{value: string, label: string}>,
|
||||||
|
* sizes: list<array{value: string, label: string}>
|
||||||
* }>
|
* }>
|
||||||
*/
|
*/
|
||||||
private function products(CatalogItem $item, string $productCode, ?string $typeCode): array
|
private function products(CatalogItem $item, string $productCode, ?string $typeCode, ?string $sizeCode): array
|
||||||
{
|
{
|
||||||
if ($productCode === self::PRODUCT) {
|
if ($productCode === self::PRODUCT) {
|
||||||
return [[
|
return [[
|
||||||
'value' => $item->slug,
|
'value' => $item->slug,
|
||||||
'label' => $item->nombre,
|
'label' => $item->nombre,
|
||||||
'types' => $this->types($item, $typeCode),
|
'types' => $this->types($item, $typeCode, $sizeCode),
|
||||||
|
'sizes' => $this->types($item, $sizeCode),
|
||||||
]];
|
]];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,10 +300,20 @@ class TicketFormService
|
|||||||
'value' => $productValue,
|
'value' => $productValue,
|
||||||
'label' => $this->optionLabel($productOption['label'], $productCode),
|
'label' => $this->optionLabel($productOption['label'], $productCode),
|
||||||
'types' => [],
|
'types' => [],
|
||||||
|
'sizes' => [],
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($this->variantOptions($variant, $typeCode) as $typeOption) {
|
foreach ($this->variantOptions($variant, $typeCode) as $typeOption) {
|
||||||
$products[$productValue]['types'][$typeOption['value']] = $typeOption;
|
$this->mergeTypeOption(
|
||||||
|
$products[$productValue]['types'],
|
||||||
|
$typeOption,
|
||||||
|
$variant,
|
||||||
|
$sizeCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->variantOptions($variant, $sizeCode) as $sizeOption) {
|
||||||
|
$products[$productValue]['sizes'][$sizeOption['value']] = $sizeOption;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,23 +323,51 @@ class TicketFormService
|
|||||||
'value' => $product['value'],
|
'value' => $product['value'],
|
||||||
'label' => $product['label'],
|
'label' => $product['label'],
|
||||||
'types' => array_values($product['types']),
|
'types' => array_values($product['types']),
|
||||||
|
'sizes' => array_values($product['sizes']),
|
||||||
],
|
],
|
||||||
$products,
|
$products,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return list<array{value: string, label: string}> */
|
/** @return list<array<string, mixed>> */
|
||||||
private function types(CatalogItem $item, ?string $typeCode): array
|
private function types(CatalogItem $item, ?string $typeCode, ?string $sizeCode = null): array
|
||||||
{
|
{
|
||||||
$types = [];
|
$types = [];
|
||||||
|
|
||||||
foreach ($item->variants as $variant) {
|
foreach ($item->variants as $variant) {
|
||||||
foreach ($this->variantOptions($variant, $typeCode) as $typeOption) {
|
foreach ($this->variantOptions($variant, $typeCode) as $typeOption) {
|
||||||
$types[$typeOption['value']] = $typeOption;
|
$this->mergeTypeOption($types, $typeOption, $variant, $sizeCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_values($types);
|
return array_values(array_map(function (array $type) use ($sizeCode): array {
|
||||||
|
if ($sizeCode !== null) {
|
||||||
|
$type['sizes'] = array_values($type['sizes']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $type;
|
||||||
|
}, $types));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, mixed>> $types
|
||||||
|
* @param array{value: string, label: string} $typeOption
|
||||||
|
*/
|
||||||
|
private function mergeTypeOption(
|
||||||
|
array &$types,
|
||||||
|
array $typeOption,
|
||||||
|
Variant $variant,
|
||||||
|
?string $sizeCode,
|
||||||
|
): void {
|
||||||
|
$typeValue = $typeOption['value'];
|
||||||
|
$types[$typeValue] ??= [
|
||||||
|
...$typeOption,
|
||||||
|
...($sizeCode !== null ? ['sizes' => []] : []),
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($this->variantOptions($variant, $sizeCode) as $sizeOption) {
|
||||||
|
$types[$typeValue]['sizes'][$sizeOption['value']] = $sizeOption;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return list<array{value: string, label: string}> */
|
/** @return list<array{value: string, label: string}> */
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class AdminAppTicketIndexRequest extends FormRequest
|
|||||||
'product' => ['sometimes', 'nullable', 'string', 'max:255'],
|
'product' => ['sometimes', 'nullable', 'string', 'max:255'],
|
||||||
'type' => ['sometimes', 'nullable', 'string', 'max:255'],
|
'type' => ['sometimes', 'nullable', 'string', 'max:255'],
|
||||||
'date' => ['sometimes', 'nullable', 'date_format:Y-m-d'],
|
'date' => ['sometimes', 'nullable', 'date_format:Y-m-d'],
|
||||||
|
'size' => ['sometimes', 'nullable', 'string', 'max:255'],
|
||||||
'status' => [
|
'status' => [
|
||||||
'sometimes',
|
'sometimes',
|
||||||
'nullable',
|
'nullable',
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class AdminAppTicketService
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array{q?: string|null, category?: string|null, product?: string|null, type?: string|null, date?: string|null, status?: string|null, page?: int, per_page?: int, sort_by?: string|null, sort_direction?: string|null} $filters
|
* @param array{q?: string|null, category?: string|null, product?: string|null, type?: string|null, date?: string|null, size?: string|null, status?: string|null, page?: int, per_page?: int, sort_by?: string|null, sort_direction?: string|null} $filters
|
||||||
*/
|
*/
|
||||||
public function search(Tenant $tenant, array $filters = []): AdminAppTicketResult
|
public function search(Tenant $tenant, array $filters = []): AdminAppTicketResult
|
||||||
{
|
{
|
||||||
@@ -60,7 +60,7 @@ class AdminAppTicketService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array{q?: string|null, category?: string|null, product?: string|null, type?: string|null, date?: string|null, status?: string|null, sort_by?: string|null, sort_direction?: string|null} $filters
|
* @param array{q?: string|null, category?: string|null, product?: string|null, type?: string|null, date?: string|null, size?: string|null, status?: string|null, sort_by?: string|null, sort_direction?: string|null} $filters
|
||||||
* @return Collection<int, Ticket>
|
* @return Collection<int, Ticket>
|
||||||
*/
|
*/
|
||||||
public function ticketsForExport(Tenant $tenant, array $filters = []): Collection
|
public function ticketsForExport(Tenant $tenant, array $filters = []): Collection
|
||||||
@@ -76,7 +76,7 @@ class AdminAppTicketService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array{q?: string|null, category?: string|null, product?: string|null, type?: string|null, date?: string|null, status?: string|null, page?: int, per_page?: int} $filters
|
* @param array{q?: string|null, category?: string|null, product?: string|null, type?: string|null, date?: string|null, size?: string|null, status?: string|null, page?: int, per_page?: int} $filters
|
||||||
* @return Builder<Ticket>
|
* @return Builder<Ticket>
|
||||||
*/
|
*/
|
||||||
private function baseQuery(Tenant $tenant, array $filters): Builder
|
private function baseQuery(Tenant $tenant, array $filters): Builder
|
||||||
@@ -104,9 +104,19 @@ class AdminAppTicketService
|
|||||||
->when($filters['type'] ?? null, function (Builder $query, string $type) use ($filters): void {
|
->when($filters['type'] ?? null, function (Builder $query, string $type) use ($filters): void {
|
||||||
$this->applyTypeFilter($query, (string) ($filters['category'] ?? ''), $type);
|
$this->applyTypeFilter($query, (string) ($filters['category'] ?? ''), $type);
|
||||||
})
|
})
|
||||||
->when($filters['date'] ?? null, fn (Builder $query, string $date): Builder => $query
|
->when($filters['date'] ?? null, function (Builder $query, string $date) use ($tenant): void {
|
||||||
->whereHas('sourcePurchase', fn (Builder $purchaseQuery): Builder => $purchaseQuery
|
if ($tenant->codigo === 'fiesta_futbol_infantil') {
|
||||||
->whereDate('created_at', $date)));
|
$this->applyEventDateFilter($query, $date);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->whereHas('sourcePurchase', fn (Builder $purchaseQuery): Builder => $purchaseQuery
|
||||||
|
->whereDate('created_at', $date));
|
||||||
|
})
|
||||||
|
->when($filters['size'] ?? null, function (Builder $query, string $size) use ($filters): void {
|
||||||
|
$this->applySizeFilter($query, (string) ($filters['category'] ?? ''), $size);
|
||||||
|
});
|
||||||
|
|
||||||
$this->applyStatusFilter($query, $filters['status'] ?? null);
|
$this->applyStatusFilter($query, $filters['status'] ?? null);
|
||||||
|
|
||||||
@@ -125,14 +135,7 @@ class AdminAppTicketService
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (in_array($category, ['comidas', 'comida'], true)) {
|
if (in_array($category, ['comidas', 'comida'], true)) {
|
||||||
$query->whereHas('sourceVariant', function (Builder $variantQuery) use ($product): void {
|
$this->whereVariantDefinition($query, 'horario', $product);
|
||||||
$variantQuery->where(function (Builder $dateQuery) use ($product): void {
|
|
||||||
$dateQuery
|
|
||||||
->where('event_date_id', $product)
|
|
||||||
->orWhereHas('eventDates', fn (Builder $eventDateQuery): Builder => $eventDateQuery
|
|
||||||
->whereKey($product));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -145,7 +148,7 @@ class AdminAppTicketService
|
|||||||
private function applyTypeFilter(Builder $query, string $category, string $type): void
|
private function applyTypeFilter(Builder $query, string $category, string $type): void
|
||||||
{
|
{
|
||||||
$attribute = match ($this->normalizedCategory($category)) {
|
$attribute = match ($this->normalizedCategory($category)) {
|
||||||
'comidas', 'comida' => 'horario',
|
'comidas', 'comida' => 'servicio',
|
||||||
'merchandising' => 'color',
|
'merchandising' => 'color',
|
||||||
default => null,
|
default => null,
|
||||||
};
|
};
|
||||||
@@ -155,6 +158,31 @@ class AdminAppTicketService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param Builder<Ticket> $query */
|
||||||
|
private function applyEventDateFilter(Builder $query, string $date): void
|
||||||
|
{
|
||||||
|
$query
|
||||||
|
->whereHas('sourceCatalogItem.category', fn (Builder $categoryQuery): Builder => $categoryQuery
|
||||||
|
->whereRaw('LOWER(nombre) IN (?, ?)', ['comidas', 'comida']))
|
||||||
|
->whereHas('sourceVariant', function (Builder $variantQuery) use ($date): void {
|
||||||
|
$variantQuery->where(function (Builder $dateQuery) use ($date): void {
|
||||||
|
$dateQuery
|
||||||
|
->whereHas('eventDate', fn (Builder $eventDateQuery): Builder => $eventDateQuery
|
||||||
|
->whereDate('date', $date))
|
||||||
|
->orWhereHas('eventDates', fn (Builder $eventDateQuery): Builder => $eventDateQuery
|
||||||
|
->whereDate('date', $date));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param Builder<Ticket> $query */
|
||||||
|
private function applySizeFilter(Builder $query, string $category, string $size): void
|
||||||
|
{
|
||||||
|
if ($this->normalizedCategory($category) === 'merchandising') {
|
||||||
|
$this->whereVariantDefinition($query, 'talle', $size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @param Builder<Ticket> $query */
|
/** @param Builder<Ticket> $query */
|
||||||
private function whereVariantDefinition(Builder $query, string $attribute, string $value): void
|
private function whereVariantDefinition(Builder $query, string $attribute, string $value): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -102,17 +102,25 @@ class AdminAppTicketFilterFormControllerTest extends TestCase
|
|||||||
->assertJsonPath('data.fields.2.name', 'type')
|
->assertJsonPath('data.fields.2.name', 'type')
|
||||||
->assertJsonPath('data.fields.2.query_param', 'type')
|
->assertJsonPath('data.fields.2.query_param', 'type')
|
||||||
->assertJsonPath('data.fields.2.depends_on', 'product')
|
->assertJsonPath('data.fields.2.depends_on', 'product')
|
||||||
->assertJsonPath('data.fields.3.name', 'date')
|
->assertJsonPath('data.fields.3.name', 'size')
|
||||||
->assertJsonPath('data.fields.3.query_param', 'date')
|
->assertJsonPath('data.fields.3.query_param', 'size')
|
||||||
->assertJsonPath('data.fields.4.name', 'status')
|
->assertJsonPath('data.fields.3.depends_on', 'type')
|
||||||
->assertJsonPath('data.fields.4.query_param', 'status')
|
->assertJsonPath('data.fields.4.name', 'date')
|
||||||
|
->assertJsonPath('data.fields.4.query_param', 'date')
|
||||||
|
->assertJsonPath('data.fields.4.depends_on', 'category')
|
||||||
|
->assertJsonPath('data.fields.5.name', 'status')
|
||||||
|
->assertJsonPath('data.fields.5.query_param', 'status')
|
||||||
->assertJsonPath('data.columns.0.key', 'order_number')
|
->assertJsonPath('data.columns.0.key', 'order_number')
|
||||||
->assertJsonPath('data.columns.0.sort_param', 'order_number')
|
->assertJsonPath('data.columns.0.sort_param', 'order_number')
|
||||||
->assertJsonPath('data.columns.1.key', 'category')
|
->assertJsonPath('data.columns.1.key', 'category')
|
||||||
->assertJsonPath('data.columns.2.key', 'product')
|
->assertJsonPath('data.columns.2.key', 'product')
|
||||||
->assertJsonPath('data.columns.2.sortable', false)
|
->assertJsonPath('data.columns.2.sortable', false)
|
||||||
->assertJsonPath('data.columns.3.key', 'type')
|
->assertJsonPath('data.columns.3.key', 'type')
|
||||||
->assertJsonPath('data.columns.3.sortable', false);
|
->assertJsonPath('data.columns.3.sortable', false)
|
||||||
|
->assertJsonPath('data.columns.4.key', 'date')
|
||||||
|
->assertJsonPath('data.columns.4.sortable', false)
|
||||||
|
->assertJsonPath('data.columns.5.key', 'size')
|
||||||
|
->assertJsonPath('data.columns.5.sortable', false);
|
||||||
|
|
||||||
$categories = collect($response->json('data.fields.0.options'));
|
$categories = collect($response->json('data.fields.0.options'));
|
||||||
$this->assertSame(
|
$this->assertSame(
|
||||||
@@ -121,22 +129,63 @@ class AdminAppTicketFilterFormControllerTest extends TestCase
|
|||||||
);
|
);
|
||||||
|
|
||||||
$entries = $categories->firstWhere('value', 'entradas');
|
$entries = $categories->firstWhere('value', 'entradas');
|
||||||
$this->assertSame('Abono', $entries['children']['options'][0]['label']);
|
$this->assertSame('Abono', $entries['children'][0]['options'][0]['label']);
|
||||||
$this->assertTrue($entries['children']['options'][0]['children']['disabled']);
|
$this->assertTrue($entries['children'][0]['options'][0]['children'][0]['disabled']);
|
||||||
|
$this->assertTrue($entries['children'][1]['disabled']);
|
||||||
|
|
||||||
$camping = $categories->firstWhere('value', 'alojamientos');
|
$camping = $categories->firstWhere('value', 'alojamientos');
|
||||||
$this->assertSame(
|
$this->assertSame(
|
||||||
['Carpa', 'Motorhome'],
|
['Carpa', 'Motorhome'],
|
||||||
collect($camping['children']['options'])->pluck('label')->all(),
|
collect($camping['children'][0]['options'])->pluck('label')->all(),
|
||||||
);
|
);
|
||||||
$this->assertTrue($camping['children']['options'][0]['children']['disabled']);
|
$this->assertTrue($camping['children'][0]['options'][0]['children'][0]['disabled']);
|
||||||
|
|
||||||
$merchandise = $categories->firstWhere('value', 'merchandising');
|
$merchandise = $categories->firstWhere('value', 'merchandising');
|
||||||
$this->assertSame('Camiseta', $merchandise['children']['options'][0]['label']);
|
$merchandiseProduct = $merchandise['children'][0]['options'][0];
|
||||||
|
$this->assertSame('Camiseta', $merchandiseProduct['label']);
|
||||||
$this->assertSame(
|
$this->assertSame(
|
||||||
['Verde', 'Blanco'],
|
['Verde', 'Blanco'],
|
||||||
collect($merchandise['children']['options'][0]['children']['options'])->pluck('label')->all(),
|
collect($merchandiseProduct['children'][0]['options'])->pluck('label')->all(),
|
||||||
);
|
);
|
||||||
|
$this->assertSame(
|
||||||
|
['14', 'S', 'M', 'L', 'XL', 'XXL'],
|
||||||
|
collect($merchandiseProduct['children'][0]['options'][0]['children'][0]['options'])
|
||||||
|
->pluck('label')
|
||||||
|
->all(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_merchandise_sizes_are_scoped_to_the_selected_product_and_color(): void
|
||||||
|
{
|
||||||
|
$tenant = $this->createFiestaFutbolInfantilTenant();
|
||||||
|
$this->grantTicketsMenu($tenant);
|
||||||
|
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
||||||
|
|
||||||
|
$merchandise = CatalogItem::query()
|
||||||
|
->where('tenant_code', $tenant->codigo)
|
||||||
|
->where('slug', 'camiseta')
|
||||||
|
->with([
|
||||||
|
'itemAttributes.attribute.options',
|
||||||
|
'variants.definitions.itemAttribute.attribute.options',
|
||||||
|
])
|
||||||
|
->firstOrFail();
|
||||||
|
$whiteXxl = $merchandise->variants->first(fn ($variant): bool => $variant->selectionValues()->get('color') === 'Blanco'
|
||||||
|
&& $variant->selectionValues()->get('talle') === 'XXL');
|
||||||
|
$this->assertNotNull($whiteXxl);
|
||||||
|
app(CatalogService::class)->deleteVariant($whiteXxl);
|
||||||
|
|
||||||
|
$response = $this->getJson('/api/v1/adminapp/forms/tickets-filter')->assertOk();
|
||||||
|
$merchandiseCategory = collect($response->json('data.fields.0.options'))
|
||||||
|
->firstWhere('value', 'merchandising');
|
||||||
|
$product = $merchandiseCategory['children'][0]['options'][0];
|
||||||
|
$colors = collect($product['children'][0]['options']);
|
||||||
|
$greenSizes = collect($colors->firstWhere('value', 'Verde')['children'][0]['options'])
|
||||||
|
->pluck('value');
|
||||||
|
$whiteSizes = collect($colors->firstWhere('value', 'Blanco')['children'][0]['options'])
|
||||||
|
->pluck('value');
|
||||||
|
|
||||||
|
$this->assertContains('XXL', $greenSizes);
|
||||||
|
$this->assertNotContains('XXL', $whiteSizes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_a_food_schedule_is_only_returned_when_a_variant_exists_for_the_date(): void
|
public function test_a_food_schedule_is_only_returned_when_a_variant_exists_for_the_date(): void
|
||||||
@@ -145,7 +194,6 @@ class AdminAppTicketFilterFormControllerTest extends TestCase
|
|||||||
$this->grantTicketsMenu($tenant);
|
$this->grantTicketsMenu($tenant);
|
||||||
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
||||||
|
|
||||||
$date = $tenant->eventDates()->orderByDesc('date')->firstOrFail();
|
|
||||||
$food = CatalogItem::query()
|
$food = CatalogItem::query()
|
||||||
->where('tenant_code', $tenant->codigo)
|
->where('tenant_code', $tenant->codigo)
|
||||||
->where('slug', 'comida')
|
->where('slug', 'comida')
|
||||||
@@ -158,15 +206,12 @@ class AdminAppTicketFilterFormControllerTest extends TestCase
|
|||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
|
|
||||||
$food->variants
|
$food->variants
|
||||||
->filter(fn ($variant): bool => $variant->selectedEventDates()->contains('id', $date->id)
|
->filter(fn ($variant): bool => $variant->selectionValues()->get('horario') === 'Almuerzo')
|
||||||
&& $variant->selectionValues()->get('horario') === 'Almuerzo')
|
|
||||||
->each->delete();
|
->each->delete();
|
||||||
|
|
||||||
$response = $this->getJson('/api/v1/adminapp/forms/tickets-filter')->assertOk();
|
$response = $this->getJson('/api/v1/adminapp/forms/tickets-filter')->assertOk();
|
||||||
$foodCategory = collect($response->json('data.fields.0.options'))->firstWhere('value', 'comidas');
|
$foodCategory = collect($response->json('data.fields.0.options'))->firstWhere('value', 'comidas');
|
||||||
$dateProduct = collect($foodCategory['children']['options'])
|
$schedules = collect($foodCategory['children'][0]['options'])->pluck('value');
|
||||||
->firstWhere('value', (string) $date->id);
|
|
||||||
$schedules = collect($dateProduct['children']['options'])->pluck('value');
|
|
||||||
|
|
||||||
$this->assertNotContains('Almuerzo', $schedules);
|
$this->assertNotContains('Almuerzo', $schedules);
|
||||||
$this->assertContains('Desayuno', $schedules);
|
$this->assertContains('Desayuno', $schedules);
|
||||||
@@ -195,6 +240,7 @@ class AdminAppTicketFilterFormControllerTest extends TestCase
|
|||||||
&& $variant->selectionValues()->get('horario') === 'Cena';
|
&& $variant->selectionValues()->get('horario') === 'Cena';
|
||||||
});
|
});
|
||||||
$this->assertNotNull($historicalVariant);
|
$this->assertNotNull($historicalVariant);
|
||||||
|
$historicalService = $historicalVariant->selectionValues()->get('servicio');
|
||||||
|
|
||||||
$ticket = Ticket::query()->create([
|
$ticket = Ticket::query()->create([
|
||||||
'tenant_code' => $tenant->codigo,
|
'tenant_code' => $tenant->codigo,
|
||||||
@@ -213,26 +259,29 @@ class AdminAppTicketFilterFormControllerTest extends TestCase
|
|||||||
|
|
||||||
$response = $this->getJson('/api/v1/adminapp/forms/tickets-filter')->assertOk();
|
$response = $this->getJson('/api/v1/adminapp/forms/tickets-filter')->assertOk();
|
||||||
$foodCategory = collect($response->json('data.fields.0.options'))->firstWhere('value', 'comidas');
|
$foodCategory = collect($response->json('data.fields.0.options'))->firstWhere('value', 'comidas');
|
||||||
$products = collect($foodCategory['children']['options']);
|
$products = collect($foodCategory['children'][0]['options']);
|
||||||
|
|
||||||
$this->assertCount(1, $products);
|
$this->assertCount(1, $products);
|
||||||
$this->assertSame('12/10', $products->first()['label']);
|
$this->assertSame('Cena', $products->first()['label']);
|
||||||
$this->assertSame(
|
$this->assertSame(
|
||||||
['Cena'],
|
[$historicalService],
|
||||||
collect($products->first()['children']['options'])->pluck('label')->all(),
|
collect($products->first()['children'][0]['options'])->pluck('label')->all(),
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->getJson('/api/v1/adminapp/tenant/tickets?'.http_build_query([
|
$this->getJson('/api/v1/adminapp/tenant/tickets?'.http_build_query([
|
||||||
'category' => 'comidas',
|
'category' => 'comidas',
|
||||||
'product' => $products->first()['value'],
|
'product' => $products->first()['value'],
|
||||||
'type' => 'Cena',
|
'type' => $historicalService,
|
||||||
|
'date' => '2026-10-12',
|
||||||
]))
|
]))
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertJsonCount(1, 'data')
|
->assertJsonCount(1, 'data')
|
||||||
->assertJsonPath('data.0.id', $ticket->id)
|
->assertJsonPath('data.0.id', $ticket->id)
|
||||||
->assertJsonPath('data.0.values.category', 'Comida')
|
->assertJsonPath('data.0.values.category', 'Comida')
|
||||||
->assertJsonPath('data.0.values.product', '12/10')
|
->assertJsonPath('data.0.values.product', 'Cena')
|
||||||
->assertJsonPath('data.0.values.type', 'Cena');
|
->assertJsonPath('data.0.values.type', $historicalService)
|
||||||
|
->assertJsonPath('data.0.values.date', '12/10')
|
||||||
|
->assertJsonPath('data.0.values.size', '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createFiestaFutbolInfantilTenant(): Tenant
|
private function createFiestaFutbolInfantilTenant(): Tenant
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ class AdminAppTicketControllerTest extends TestCase
|
|||||||
->assertJsonPath('data.0.values.id', $ticket->id)
|
->assertJsonPath('data.0.values.id', $ticket->id)
|
||||||
->assertJsonPath('data.0.values.status', Ticket::STATUS_ACTIVE)
|
->assertJsonPath('data.0.values.status', Ticket::STATUS_ACTIVE)
|
||||||
->assertJsonMissingPath('data.0.values.ticket')
|
->assertJsonMissingPath('data.0.values.ticket')
|
||||||
->assertJsonMissingPath('data.0.values.date')
|
->assertJsonPath('data.0.values.date', '-')
|
||||||
|
->assertJsonPath('data.0.values.size', '-')
|
||||||
->assertJsonMissingPath('data.0.date')
|
->assertJsonMissingPath('data.0.date')
|
||||||
->assertJsonPath('meta.total', 1);
|
->assertJsonPath('meta.total', 1);
|
||||||
}
|
}
|
||||||
@@ -278,6 +279,13 @@ class AdminAppTicketControllerTest extends TestCase
|
|||||||
&& $variant->selectionValues()->get('horario') === 'Almuerzo');
|
&& $variant->selectionValues()->get('horario') === 'Almuerzo');
|
||||||
$this->assertNotNull($dinner);
|
$this->assertNotNull($dinner);
|
||||||
$this->assertNotNull($lunch);
|
$this->assertNotNull($lunch);
|
||||||
|
$dinnerService = $dinner->selectionValues()->get('servicio');
|
||||||
|
$otherDate = $tenant->eventDates()->where('id', '!=', $date->id)->firstOrFail();
|
||||||
|
$otherDinner = $food->variants->first(fn (Variant $variant): bool => $variant
|
||||||
|
->selectedEventDates()->contains('id', $otherDate->id)
|
||||||
|
&& $variant->selectionValues()->get('horario') === 'Cena'
|
||||||
|
&& $variant->selectionValues()->get('servicio') === $dinnerService);
|
||||||
|
$this->assertNotNull($otherDinner);
|
||||||
|
|
||||||
$matchingPurchase = $this->createPurchase($tenant, $admin, '2026-08-20 10:00:00');
|
$matchingPurchase = $this->createPurchase($tenant, $admin, '2026-08-20 10:00:00');
|
||||||
$otherPurchase = $this->createPurchase($tenant, $admin, '2026-08-21 10:00:00');
|
$otherPurchase = $this->createPurchase($tenant, $admin, '2026-08-21 10:00:00');
|
||||||
@@ -296,24 +304,75 @@ class AdminAppTicketControllerTest extends TestCase
|
|||||||
$this->createTicket($tenant, $admin, [
|
$this->createTicket($tenant, $admin, [
|
||||||
'source_purchase_id' => $otherPurchase->id,
|
'source_purchase_id' => $otherPurchase->id,
|
||||||
'source_catalog_item_id' => $food->id,
|
'source_catalog_item_id' => $food->id,
|
||||||
'source_variant_id' => $dinner->id,
|
'source_variant_id' => $otherDinner->id,
|
||||||
'used_at' => now(),
|
'used_at' => now(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->getJson('/api/v1/adminapp/tenant/tickets?'.http_build_query([
|
$this->getJson('/api/v1/adminapp/tenant/tickets?'.http_build_query([
|
||||||
'category' => 'comidas',
|
'category' => 'comidas',
|
||||||
'product' => (string) $date->id,
|
'product' => 'Cena',
|
||||||
'type' => 'Cena',
|
'type' => $dinnerService,
|
||||||
'date' => '2026-08-20',
|
'date' => $date->date->format('Y-m-d'),
|
||||||
'status' => Ticket::STATUS_USED,
|
'status' => Ticket::STATUS_USED,
|
||||||
]))
|
]))
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertJsonCount(1, 'data')
|
->assertJsonCount(1, 'data')
|
||||||
->assertJsonPath('data.0.id', $matching->id)
|
->assertJsonPath('data.0.id', $matching->id)
|
||||||
|
->assertJsonPath('data.0.values.product', 'Cena')
|
||||||
|
->assertJsonPath('data.0.values.type', $dinnerService)
|
||||||
|
->assertJsonPath('data.0.values.date', $date->date->format('d/m'))
|
||||||
->assertJsonPath('scanned_tickets', 1)
|
->assertJsonPath('scanned_tickets', 1)
|
||||||
->assertJsonPath('total_tickets', 1);
|
->assertJsonPath('total_tickets', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_it_exposes_and_filters_merchandise_color_and_size(): void
|
||||||
|
{
|
||||||
|
$tenant = $this->createTenant('fiesta_futbol_infantil');
|
||||||
|
$admin = $this->createAdminAppUser($tenant);
|
||||||
|
$this->grantTicketsMenu($tenant);
|
||||||
|
$this->seed([AttributeSeeder::class, FiestaFutbolInfantilProductSeeder::class]);
|
||||||
|
Sanctum::actingAs($admin);
|
||||||
|
|
||||||
|
$merchandise = CatalogItem::query()
|
||||||
|
->where('tenant_code', $tenant->codigo)
|
||||||
|
->where('slug', 'camiseta')
|
||||||
|
->with([
|
||||||
|
'variants.definitions.itemAttribute.attribute.options',
|
||||||
|
'variants.eventDates',
|
||||||
|
'variants.eventDate',
|
||||||
|
])
|
||||||
|
->firstOrFail();
|
||||||
|
$greenMedium = $merchandise->variants->first(fn (Variant $variant): bool => $variant->selectionValues()->get('color') === 'Verde'
|
||||||
|
&& $variant->selectionValues()->get('talle') === 'M');
|
||||||
|
$whiteMedium = $merchandise->variants->first(fn (Variant $variant): bool => $variant->selectionValues()->get('color') === 'Blanco'
|
||||||
|
&& $variant->selectionValues()->get('talle') === 'M');
|
||||||
|
$this->assertNotNull($greenMedium);
|
||||||
|
$this->assertNotNull($whiteMedium);
|
||||||
|
|
||||||
|
$matching = $this->createTicket($tenant, $admin, [
|
||||||
|
'source_catalog_item_id' => $merchandise->id,
|
||||||
|
'source_variant_id' => $greenMedium->id,
|
||||||
|
]);
|
||||||
|
$this->createTicket($tenant, $admin, [
|
||||||
|
'source_catalog_item_id' => $merchandise->id,
|
||||||
|
'source_variant_id' => $whiteMedium->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->getJson('/api/v1/adminapp/tenant/tickets?'.http_build_query([
|
||||||
|
'category' => 'merchandising',
|
||||||
|
'product' => 'camiseta',
|
||||||
|
'type' => 'Verde',
|
||||||
|
'size' => 'M',
|
||||||
|
]))
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonCount(1, 'data')
|
||||||
|
->assertJsonPath('data.0.id', $matching->id)
|
||||||
|
->assertJsonPath('data.0.values.product', 'Camiseta')
|
||||||
|
->assertJsonPath('data.0.values.type', 'Verde')
|
||||||
|
->assertJsonPath('data.0.values.date', '-')
|
||||||
|
->assertJsonPath('data.0.values.size', 'M');
|
||||||
|
}
|
||||||
|
|
||||||
public function test_it_filters_computed_active_and_expired_statuses(): void
|
public function test_it_filters_computed_active_and_expired_statuses(): void
|
||||||
{
|
{
|
||||||
$tenant = $this->createTenant('fiesta_futbol_infantil');
|
$tenant = $this->createTenant('fiesta_futbol_infantil');
|
||||||
|
|||||||
Reference in New Issue
Block a user