337 lines
11 KiB
PHP
337 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Domains\Forms\Services;
|
|
|
|
use App\Domains\Catalog\Models\CatalogItem;
|
|
use App\Domains\Catalog\Models\Variant;
|
|
use App\Domains\Tenant\Models\Tenant;
|
|
use App\Domains\Ticket\Models\Ticket;
|
|
use Illuminate\Database\Eloquent\Collection;
|
|
|
|
class TicketFormService
|
|
{
|
|
private const PRODUCT = 'product';
|
|
|
|
/**
|
|
* @var array<string, array{label: string|null, product: string, type: string|null, order: int}>
|
|
*/
|
|
private const CATEGORY_PRESENTATIONS = [
|
|
'entradas' => [
|
|
'label' => null,
|
|
'product' => self::PRODUCT,
|
|
'type' => null,
|
|
'order' => 1,
|
|
],
|
|
'alojamientos' => [
|
|
'label' => 'Camping',
|
|
'product' => 'tipo_alojamiento',
|
|
'type' => null,
|
|
'order' => 2,
|
|
],
|
|
'camping' => [
|
|
'label' => null,
|
|
'product' => 'tipo_alojamiento',
|
|
'type' => null,
|
|
'order' => 2,
|
|
],
|
|
'comidas' => [
|
|
'label' => 'Comida',
|
|
'product' => 'event_date',
|
|
'type' => 'horario',
|
|
'order' => 3,
|
|
],
|
|
'comida' => [
|
|
'label' => null,
|
|
'product' => 'event_date',
|
|
'type' => 'horario',
|
|
'order' => 3,
|
|
],
|
|
'merchandising' => [
|
|
'label' => null,
|
|
'product' => self::PRODUCT,
|
|
'type' => 'color',
|
|
'order' => 4,
|
|
],
|
|
];
|
|
|
|
/**
|
|
* @return array{
|
|
* statuses: list<array{value: string, label: string}>,
|
|
* categories: list<array{
|
|
* value: string,
|
|
* label: string,
|
|
* products: list<array{
|
|
* value: string,
|
|
* label: string,
|
|
* types: list<array{value: string, label: string}>
|
|
* }>
|
|
* }>
|
|
* }
|
|
*/
|
|
public function get(Tenant $tenant): array
|
|
{
|
|
$items = CatalogItem::query()
|
|
->where('tenant_code', $tenant->codigo)
|
|
->where('has_tickets', true)
|
|
->whereHas('category')
|
|
->with($this->relations())
|
|
->orderBy('group_order')
|
|
->orderBy('nombre')
|
|
->get();
|
|
|
|
return $this->build($items);
|
|
}
|
|
|
|
/**
|
|
* Return active catalog options plus soft-deleted sources still referenced by
|
|
* tickets, so historical tickets never become impossible to filter.
|
|
*
|
|
* @return array{
|
|
* statuses: list<array{value: string, label: string}>,
|
|
* categories: list<array{
|
|
* value: string,
|
|
* label: string,
|
|
* products: list<array{
|
|
* value: string,
|
|
* label: string,
|
|
* types: list<array{value: string, label: string}>
|
|
* }>
|
|
* }>
|
|
* }
|
|
*/
|
|
public function getForFilters(Tenant $tenant): array
|
|
{
|
|
$historicalVariantIds = Ticket::query()
|
|
->where('tenant_code', $tenant->codigo)
|
|
->whereNotNull('source_variant_id')
|
|
->distinct()
|
|
->pluck('source_variant_id')
|
|
->map(fn ($id): int => (int) $id)
|
|
->all();
|
|
$historicalCatalogItemIds = Ticket::query()
|
|
->where('tenant_code', $tenant->codigo)
|
|
->whereNotNull('source_catalog_item_id')
|
|
->distinct()
|
|
->pluck('source_catalog_item_id')
|
|
->map(fn ($id): int => (int) $id)
|
|
->merge(
|
|
Variant::withTrashed()
|
|
->whereKey($historicalVariantIds)
|
|
->pluck('catalog_item_id')
|
|
->map(fn ($id): int => (int) $id),
|
|
)
|
|
->unique()
|
|
->values()
|
|
->all();
|
|
|
|
$items = CatalogItem::withTrashed()
|
|
->where('tenant_code', $tenant->codigo)
|
|
->whereHas('category')
|
|
->where(function ($query) use ($historicalCatalogItemIds): void {
|
|
$query
|
|
->where(function ($activeQuery): void {
|
|
$activeQuery
|
|
->whereNull('catalog_items.deleted_at')
|
|
->where('has_tickets', true);
|
|
})
|
|
->orWhereIn('catalog_items.id', $historicalCatalogItemIds);
|
|
})
|
|
->with([
|
|
'category',
|
|
'itemAttributes.attribute.options',
|
|
'variants' => fn ($query) => $query
|
|
->withTrashed()
|
|
->where(function ($variantQuery) use ($historicalVariantIds): void {
|
|
$variantQuery
|
|
->whereNull('variantes.deleted_at')
|
|
->orWhereIn('variantes.id', $historicalVariantIds);
|
|
}),
|
|
'variants.definitions.itemAttribute.attribute.options',
|
|
'variants.eventDates',
|
|
'variants.eventDate',
|
|
])
|
|
->orderBy('group_order')
|
|
->orderBy('nombre')
|
|
->get();
|
|
|
|
return $this->build($items);
|
|
}
|
|
|
|
/**
|
|
* @param Collection<int, CatalogItem> $items
|
|
* @return array{
|
|
* statuses: list<array{value: string, label: string}>,
|
|
* categories: list<array{
|
|
* value: string,
|
|
* label: string,
|
|
* products: list<array{
|
|
* value: string,
|
|
* label: string,
|
|
* types: list<array{value: string, label: string}>
|
|
* }>
|
|
* }>
|
|
* }
|
|
*/
|
|
private function build(Collection $items): array
|
|
{
|
|
$categories = [];
|
|
|
|
foreach ($items as $item) {
|
|
$sourceCategory = trim((string) $item->category?->nombre);
|
|
$categoryValue = mb_strtolower($sourceCategory);
|
|
$presentation = self::CATEGORY_PRESENTATIONS[$categoryValue] ?? [
|
|
'label' => null,
|
|
'product' => self::PRODUCT,
|
|
'type' => null,
|
|
'order' => PHP_INT_MAX,
|
|
];
|
|
|
|
$categories[$categoryValue] ??= [
|
|
'value' => $categoryValue,
|
|
'label' => $presentation['label'] ?? $sourceCategory,
|
|
'order' => $presentation['order'],
|
|
'products' => [],
|
|
];
|
|
|
|
foreach ($this->products($item, $presentation['product'], $presentation['type']) as $product) {
|
|
$productValue = $product['value'];
|
|
$existingProduct = $categories[$categoryValue]['products'][$productValue] ?? [
|
|
'value' => $productValue,
|
|
'label' => $product['label'],
|
|
'types' => [],
|
|
];
|
|
|
|
foreach ($product['types'] as $type) {
|
|
$existingProduct['types'][$type['value']] = $type;
|
|
}
|
|
|
|
$categories[$categoryValue]['products'][$productValue] = $existingProduct;
|
|
}
|
|
}
|
|
|
|
uasort($categories, fn (array $left, array $right): int => $left['order'] <=> $right['order']
|
|
?: $left['label'] <=> $right['label']);
|
|
|
|
return [
|
|
'statuses' => [
|
|
['value' => Ticket::STATUS_ACTIVE, 'label' => 'Activo'],
|
|
['value' => Ticket::STATUS_USED, 'label' => 'Usado'],
|
|
['value' => Ticket::STATUS_EXPIRED, 'label' => 'Vencido'],
|
|
],
|
|
'categories' => array_values(array_map(
|
|
fn (array $category): array => [
|
|
'value' => $category['value'],
|
|
'label' => $category['label'],
|
|
'products' => array_values(array_map(
|
|
fn (array $product): array => [
|
|
'value' => $product['value'],
|
|
'label' => $product['label'],
|
|
'types' => array_values($product['types']),
|
|
],
|
|
$category['products'],
|
|
)),
|
|
],
|
|
$categories,
|
|
)),
|
|
];
|
|
}
|
|
|
|
/** @return list<string> */
|
|
private function relations(): array
|
|
{
|
|
return [
|
|
'category',
|
|
'itemAttributes.attribute.options',
|
|
'variants.definitions.itemAttribute.attribute.options',
|
|
'variants.eventDates',
|
|
'variants.eventDate',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return list<array{
|
|
* value: string,
|
|
* label: string,
|
|
* types: list<array{value: string, label: string}>
|
|
* }>
|
|
*/
|
|
private function products(CatalogItem $item, string $productCode, ?string $typeCode): array
|
|
{
|
|
if ($productCode === self::PRODUCT) {
|
|
return [[
|
|
'value' => $item->slug,
|
|
'label' => $item->nombre,
|
|
'types' => $this->types($item, $typeCode),
|
|
]];
|
|
}
|
|
|
|
$products = [];
|
|
|
|
foreach ($item->variants as $variant) {
|
|
foreach ($this->variantOptions($variant, $productCode) as $productOption) {
|
|
$productValue = $productOption['value'];
|
|
$products[$productValue] ??= [
|
|
'value' => $productValue,
|
|
'label' => $this->optionLabel($productOption['label'], $productCode),
|
|
'types' => [],
|
|
];
|
|
|
|
foreach ($this->variantOptions($variant, $typeCode) as $typeOption) {
|
|
$products[$productValue]['types'][$typeOption['value']] = $typeOption;
|
|
}
|
|
}
|
|
}
|
|
|
|
return array_values(array_map(
|
|
fn (array $product): array => [
|
|
'value' => $product['value'],
|
|
'label' => $product['label'],
|
|
'types' => array_values($product['types']),
|
|
],
|
|
$products,
|
|
));
|
|
}
|
|
|
|
/** @return list<array{value: string, label: string}> */
|
|
private function types(CatalogItem $item, ?string $typeCode): array
|
|
{
|
|
$types = [];
|
|
|
|
foreach ($item->variants as $variant) {
|
|
foreach ($this->variantOptions($variant, $typeCode) as $typeOption) {
|
|
$types[$typeOption['value']] = $typeOption;
|
|
}
|
|
}
|
|
|
|
return array_values($types);
|
|
}
|
|
|
|
/** @return list<array{value: string, label: string}> */
|
|
private function variantOptions(Variant $variant, ?string $attributeCode): array
|
|
{
|
|
if ($attributeCode === null) {
|
|
return [];
|
|
}
|
|
|
|
$selection = $variant->selectionOptions($variant->catalogItem->itemAttributes)
|
|
->get($attributeCode);
|
|
|
|
if ($selection === null) {
|
|
return [];
|
|
}
|
|
|
|
return array_is_list($selection) ? $selection : [$selection];
|
|
}
|
|
|
|
private function optionLabel(string $label, string $attributeCode): string
|
|
{
|
|
if ($attributeCode !== 'event_date') {
|
|
return $label;
|
|
}
|
|
|
|
[$day, $month] = array_pad(explode('/', $label), 2, null);
|
|
|
|
return $day !== null && $month !== null ? "{$day}/{$month}" : $label;
|
|
}
|
|
}
|