diff --git a/app/Domains/Ticket/Resources/AdminApp/AdminAppTicketResource.php b/app/Domains/Ticket/Resources/AdminApp/AdminAppTicketResource.php index 14ffd08..feda27c 100644 --- a/app/Domains/Ticket/Resources/AdminApp/AdminAppTicketResource.php +++ b/app/Domains/Ticket/Resources/AdminApp/AdminAppTicketResource.php @@ -2,10 +2,9 @@ namespace App\Domains\Ticket\Resources\AdminApp; -use App\Domains\Catalog\Models\ItemAttribute; -use App\Domains\Purchase\Models\PurchaseItem; use App\Domains\Ticket\Models\Ticket; use App\Domains\Ticket\Resources\TicketResource; +use App\Domains\Ticket\Services\AdminAppTicketRowService; use Illuminate\Http\Request; /** @mixin Ticket */ @@ -14,74 +13,13 @@ class AdminAppTicketResource extends TicketResource /** @return array */ public function toArray(Request $request): array { - $purchaseItem = $this->sourcePurchaseItem(); + $rowService = app(AdminAppTicketRowService::class); + $details = $rowService->details($this->resource); return [ ...parent::toArray($request), - 'source_purchase_id' => $this->source_purchase_id, - 'order_number' => $this->source_purchase_id, - 'product' => $purchaseItem?->item_nombre - ?? $this->sourceCatalogItem?->nombre - ?? $this->name, - 'amount' => $purchaseItem?->precio_unitario, - 'client' => $this->sourcePurchase?->nombre_apellido ?? $this->user?->nombre_apellido, - 'date' => $this->sourcePurchase?->created_at, - 'status' => $this->status, - 'scanned_by' => $this->scannerUser?->nombre_apellido, - 'variant_properties' => $this->variantProperties(), + ...$details, + 'values' => $rowService->values($this->resource, $details), ]; } - - private function sourcePurchaseItem(): ?PurchaseItem - { - return $this->sourcePurchase?->items->first(function (PurchaseItem $item): bool { - if ($this->source_variant_id !== null) { - return $item->source_variant_id === $this->source_variant_id; - } - - return $item->source_catalog_item_id === $this->source_catalog_item_id - && $item->source_variant_id === null; - }); - } - - /** - * @return list - * }> - */ - private function variantProperties(): array - { - $variant = $this->sourceVariant; - - if ($variant === null) { - return []; - } - - $itemAttributes = $variant->definitions - ->map(fn ($definition) => $definition->itemAttribute) - ->filter() - ->merge($variant->catalogItem?->itemAttributes ?? collect()) - ->unique('id') - ->values(); - - return $variant->selectionOptions($itemAttributes) - ->map(function (array $selection, string $attributeCode) use ($itemAttributes): array { - $itemAttribute = $itemAttributes->first( - fn (ItemAttribute $itemAttribute): bool => $itemAttribute->attribute?->codigo - === $attributeCode, - ); - $values = array_is_list($selection) ? $selection : [$selection]; - - return [ - 'code' => $attributeCode, - 'label' => $itemAttribute?->attribute?->nombre - ?? ($attributeCode === 'event_date' ? 'Fecha' : $attributeCode), - 'values' => array_values($values), - ]; - }) - ->values() - ->all(); - } } diff --git a/app/Domains/Ticket/Services/AdminAppTicketReportService.php b/app/Domains/Ticket/Services/AdminAppTicketReportService.php index d8c64e7..4c2accc 100644 --- a/app/Domains/Ticket/Services/AdminAppTicketReportService.php +++ b/app/Domains/Ticket/Services/AdminAppTicketReportService.php @@ -3,19 +3,11 @@ namespace App\Domains\Ticket\Services; use App\Domains\Ticket\Models\Ticket; -use App\Domains\Ticket\Resources\AdminApp\AdminAppTicketResource; use Illuminate\Support\Collection; class AdminAppTicketReportService { - private const CATEGORY_PRESENTATIONS = [ - 'alojamientos' => ['category' => 'Camping', 'product' => 'tipo_alojamiento', 'type' => null], - 'camping' => ['category' => null, 'product' => 'tipo_alojamiento', 'type' => null], - 'entradas' => ['category' => null, 'product' => 'product', 'type' => null], - 'comidas' => ['category' => 'Comida', 'product' => 'event_date', 'type' => 'horario'], - 'comida' => ['category' => null, 'product' => 'event_date', 'type' => 'horario'], - 'merchandising' => ['category' => null, 'product' => 'product', 'type' => 'color'], - ]; + public function __construct(private readonly AdminAppTicketRowService $rowService) {} /** * @param Collection $tickets @@ -23,90 +15,21 @@ class AdminAppTicketReportService */ public function rows(Collection $tickets): Collection { - return $tickets->values()->map(fn (Ticket $ticket): array => $this->row($ticket)); - } - - /** @return array */ - private function row(Ticket $ticket): array - { - $data = (new AdminAppTicketResource($ticket))->resolve(); - $presentation = $this->presentation($data); - - return [ - 'order_number' => $data['order_number'], - 'category' => $presentation['category'], - 'product' => $presentation['product'], - 'type' => $presentation['type'], - 'amount' => $data['amount'] === null ? null : (float) $data['amount'], - 'client' => $data['client'] ?? 'Sin nombre', - 'ticket' => $data['ticket'], - 'date' => $data['date'], - 'status' => $this->statusLabel($data['status']), - 'scanned_by' => $data['scanned_by'] ?? '-', - ]; + return $this->rowService->rows($tickets); } /** - * @param array $ticket - * @return array{category: string, product: string, type: string} + * @param Collection> $rows + * @param list> $columns + * @return Collection> */ - private function presentation(array $ticket): array + public function displayRows(Collection $rows, array $columns, string $timeZone): Collection { - $sourceCategory = trim((string) ($ticket['category'] ?? '')) ?: '-'; - $configuration = self::CATEGORY_PRESENTATIONS[mb_strtolower($sourceCategory)] ?? null; - - if ($configuration === null) { - return [ - 'category' => $sourceCategory, - 'product' => (string) ($ticket['product'] ?: $ticket['name'] ?: '-'), - 'type' => $this->allPropertyLabels($ticket) ?: '-', - ]; - } - - return [ - 'category' => $configuration['category'] ?? $sourceCategory, - 'product' => $configuration['product'] === 'product' - ? (string) ($ticket['product'] ?: $ticket['name'] ?: '-') - : ($this->propertyLabels($ticket, $configuration['product']) ?: '-'), - 'type' => $configuration['type'] === null - ? '-' - : ($this->propertyLabels($ticket, $configuration['type']) ?: '-'), - ]; + return $this->rowService->displayRows($rows, $columns, $timeZone); } - /** @param array $ticket */ - private function propertyLabels(array $ticket, string $code): string + public function displayValue(mixed $value, string $type, string $timeZone): string { - $property = collect($ticket['variant_properties'] ?? [])->firstWhere('code', $code); - $labels = collect($property['values'] ?? [])->pluck('label')->filter(); - - if ($code === 'event_date') { - $labels = $labels->map(function (string $label): string { - [$day, $month] = array_pad(explode('/', $label), 2, null); - - return $day !== null && $month !== null ? "{$day}/{$month}" : $label; - }); - } - - return $labels->implode(', '); - } - - /** @param array $ticket */ - private function allPropertyLabels(array $ticket): string - { - return collect($ticket['variant_properties'] ?? []) - ->flatMap(fn (array $property): array => $property['values'] ?? []) - ->pluck('label') - ->filter() - ->implode(', '); - } - - private function statusLabel(string $status): string - { - return match ($status) { - Ticket::STATUS_USED => 'Usado', - Ticket::STATUS_EXPIRED => 'Vencido', - default => 'Activo', - }; + return $this->rowService->displayValue($value, $type, $timeZone); } } diff --git a/app/Domains/Ticket/Services/AdminAppTicketRowService.php b/app/Domains/Ticket/Services/AdminAppTicketRowService.php new file mode 100644 index 0000000..b8de06b --- /dev/null +++ b/app/Domains/Ticket/Services/AdminAppTicketRowService.php @@ -0,0 +1,217 @@ + ['category' => 'Camping', 'product' => 'tipo_alojamiento', 'type' => null], + 'camping' => ['category' => null, 'product' => 'tipo_alojamiento', 'type' => null], + 'entradas' => ['category' => null, 'product' => 'product', 'type' => null], + 'comidas' => ['category' => 'Comida', 'product' => 'event_date', 'type' => 'horario'], + 'comida' => ['category' => null, 'product' => 'event_date', 'type' => 'horario'], + 'merchandising' => ['category' => null, 'product' => 'product', 'type' => 'color'], + ]; + + /** @return array */ + public function details(Ticket $ticket): array + { + $purchaseItem = $this->sourcePurchaseItem($ticket); + + return [ + 'source_purchase_id' => $ticket->source_purchase_id, + 'order_number' => $ticket->source_purchase_id, + 'product' => $purchaseItem?->item_nombre + ?? $ticket->sourceCatalogItem?->nombre + ?? $ticket->name, + 'amount' => $purchaseItem?->precio_unitario, + 'client' => $ticket->sourcePurchase?->nombre_apellido ?? $ticket->user?->nombre_apellido, + 'date' => $ticket->sourcePurchase?->created_at, + 'status' => $ticket->status, + 'scanned_by' => $ticket->scannerUser?->nombre_apellido, + 'variant_properties' => $this->variantProperties($ticket), + ]; + } + + /** @param array|null $details */ + public function values(Ticket $ticket, ?array $details = null): array + { + $details ??= $this->details($ticket); + $presentation = $this->presentation($ticket, $details); + + return [ + 'order_number' => $details['order_number'], + 'category' => $presentation['category'], + 'product' => $presentation['product'], + 'type' => $presentation['type'], + 'amount' => $details['amount'] === null ? null : (float) $details['amount'], + 'client' => $details['client'] ?? 'Sin nombre', + 'ticket' => $ticket->ticket, + 'date' => $details['date'], + 'status' => $details['status'], + 'scanned_by' => $details['scanned_by'] ?? '-', + ]; + } + + /** + * @param Collection $tickets + * @return Collection> + */ + public function rows(Collection $tickets): Collection + { + return $tickets->values()->map(fn (Ticket $ticket): array => $this->values($ticket)); + } + + /** + * @param Collection> $rows + * @param list> $columns + * @return Collection> + */ + public function displayRows(Collection $rows, array $columns, string $timeZone): Collection + { + return $rows->map(fn (array $row): array => collect($columns) + ->mapWithKeys(fn (array $column): array => [ + $column['key'] => $this->displayValue( + $row[$column['key']] ?? null, + $column['type'], + $timeZone, + ), + ]) + ->all()); + } + + public function displayValue(mixed $value, string $type, string $timeZone): string + { + if ($value === null || $value === '') { + return '-'; + } + + return match ($type) { + 'order_number' => '#'.$value, + 'currency' => '$'.number_format((float) $value, 2, ',', '.'), + 'date' => ($value instanceof CarbonInterface ? $value : Carbon::parse((string) $value)) + ->copy()->timezone($timeZone)->format('d/m/Y H:i'), + 'status' => match ((string) $value) { + Ticket::STATUS_USED => 'Usado', + Ticket::STATUS_EXPIRED => 'Vencido', + default => 'Activo', + }, + default => (string) $value, + }; + } + + /** @param array $details */ + private function presentation(Ticket $ticket, array $details): array + { + $sourceCategory = trim((string) ($ticket->sourceCatalogItem?->category?->nombre ?? '')) ?: '-'; + + if ($ticket->tenant_code !== self::FIESTA_FUTBOL_INFANTIL) { + return [ + 'category' => $sourceCategory, + 'product' => (string) ($details['product'] ?: $ticket->name ?: '-'), + 'type' => $this->allPropertyLabels($details) ?: '-', + ]; + } + + $configuration = self::CATEGORY_PRESENTATIONS[mb_strtolower($sourceCategory)] ?? null; + if ($configuration === null) { + return [ + 'category' => $sourceCategory, + 'product' => (string) ($details['product'] ?: $ticket->name ?: '-'), + 'type' => $this->allPropertyLabels($details) ?: '-', + ]; + } + + return [ + 'category' => $configuration['category'] ?? $sourceCategory, + 'product' => $configuration['product'] === 'product' + ? (string) ($details['product'] ?: $ticket->name ?: '-') + : ($this->propertyLabels($details, $configuration['product']) ?: '-'), + 'type' => $configuration['type'] === null + ? '-' + : ($this->propertyLabels($details, $configuration['type']) ?: '-'), + ]; + } + + /** @param array $details */ + private function propertyLabels(array $details, string $code): string + { + $property = collect($details['variant_properties'] ?? [])->firstWhere('code', $code); + $labels = collect($property['values'] ?? [])->pluck('label')->filter(); + + if ($code === 'event_date') { + $labels = $labels->map(function (string $label): string { + [$day, $month] = array_pad(explode('/', $label), 2, null); + + return $day !== null && $month !== null ? "{$day}/{$month}" : $label; + }); + } + + return $labels->implode(', '); + } + + /** @param array $details */ + private function allPropertyLabels(array $details): string + { + return collect($details['variant_properties'] ?? []) + ->flatMap(fn (array $property): array => $property['values'] ?? []) + ->pluck('label') + ->filter() + ->implode(', '); + } + + private function sourcePurchaseItem(Ticket $ticket): ?PurchaseItem + { + return $ticket->sourcePurchase?->items->first(function (PurchaseItem $item) use ($ticket): bool { + if ($ticket->source_variant_id !== null) { + return $item->source_variant_id === $ticket->source_variant_id; + } + + return $item->source_catalog_item_id === $ticket->source_catalog_item_id + && $item->source_variant_id === null; + }); + } + + /** @return list}> */ + private function variantProperties(Ticket $ticket): array + { + $variant = $ticket->sourceVariant; + if ($variant === null) { + return []; + } + + $itemAttributes = $variant->definitions + ->map(fn ($definition) => $definition->itemAttribute) + ->filter() + ->merge($variant->catalogItem?->itemAttributes ?? collect()) + ->unique('id') + ->values(); + + return $variant->selectionOptions($itemAttributes) + ->map(function (array $selection, string $attributeCode) use ($itemAttributes): array { + $itemAttribute = $itemAttributes->first( + fn (ItemAttribute $itemAttribute): bool => $itemAttribute->attribute?->codigo + === $attributeCode, + ); + $values = array_is_list($selection) ? $selection : [$selection]; + + return [ + 'code' => $attributeCode, + 'label' => $itemAttribute?->attribute?->nombre + ?? ($attributeCode === 'event_date' ? 'Fecha' : $attributeCode), + 'values' => array_values($values), + ]; + }) + ->values() + ->all(); + } +} diff --git a/tests/Feature/Forms/AdminAppTicketFilterFormControllerTest.php b/tests/Feature/Forms/AdminAppTicketFilterFormControllerTest.php index 296187b..80c5b92 100644 --- a/tests/Feature/Forms/AdminAppTicketFilterFormControllerTest.php +++ b/tests/Feature/Forms/AdminAppTicketFilterFormControllerTest.php @@ -226,7 +226,10 @@ class AdminAppTicketFilterFormControllerTest extends TestCase ])) ->assertOk() ->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.product', '12/10') + ->assertJsonPath('data.0.values.type', 'Cena'); } private function createFiestaFutbolInfantilTenant(): Tenant diff --git a/tests/Feature/Ticket/AdminAppTicketControllerTest.php b/tests/Feature/Ticket/AdminAppTicketControllerTest.php index 6d2e849..5ecbce3 100644 --- a/tests/Feature/Ticket/AdminAppTicketControllerTest.php +++ b/tests/Feature/Ticket/AdminAppTicketControllerTest.php @@ -70,6 +70,8 @@ class AdminAppTicketControllerTest extends TestCase ->assertJsonCount(1, 'data') ->assertJsonPath('data.0.id', $ticket->id) ->assertJsonPath('data.0.tenant_code', $tenant->codigo) + ->assertJsonPath('data.0.values.ticket', $ticket->ticket) + ->assertJsonPath('data.0.values.status', Ticket::STATUS_ACTIVE) ->assertJsonPath('meta.total', 1); }