*/ public function scannedBy(User $scanner): Collection { return $this->baseQuery() ->where('tenant_code', $scanner->tenant_codigo) ->where('scanner_user_id', $scanner->getKey()) ->orderByDesc('used_at') ->orderByDesc('id') ->get(); } public function detail(User $scanner, string $ticketUuid): Ticket { $categoryIds = $this->scannerCategoryIds($scanner); return $this->baseQuery() ->where('tenant_code', $scanner->tenant_codigo) ->where('ticket', $ticketUuid) ->where(function (Builder $query) use ($scanner, $categoryIds): void { $query ->where('scanner_user_id', $scanner->getKey()) ->orWhereHas( 'sourceCatalogItem', fn (Builder $catalogItemQuery): Builder => $catalogItemQuery ->whereIn('category_id', $categoryIds) ); }) ->firstOrFail(); } public function scan(User $scanner, string $ticketUuid): Ticket { return DB::transaction(function () use ($scanner, $ticketUuid): Ticket { $ticket = $this->baseQuery() ->where('tenant_code', $scanner->tenant_codigo) ->where('ticket', $ticketUuid) ->lockForUpdate() ->firstOrFail(); if (! $this->scannerCanScan($scanner, $ticket)) { throw ValidationException::withMessages([ 'ticket' => __('api.ticket.scanner_category_forbidden'), ]); } if ($ticket->is_used) { throw ValidationException::withMessages([ 'ticket' => __('api.ticket.already_scanned'), ]); } if (! $ticket->is_valid) { throw ValidationException::withMessages([ 'ticket' => $ticket->is_expired ? __('api.ticket.expired_for_scan') : __('api.ticket.not_valid_for_scan'), ]); } $ticket->forceFill([ 'used_at' => now(), 'scanner_user_id' => $scanner->getKey(), ])->save(); return $ticket->refresh()->load($this->relations()); }); } /** @return Builder */ private function baseQuery(): Builder { return Ticket::query()->with($this->relations()); } /** @return array */ private function relations(): array { return [ 'validityGroups.validityTimes', 'sourceCatalogItem', 'sourceVariant.eventDate', 'sourceVariant.catalogItem', ]; } /** @return array */ private function scannerCategoryIds(User $scanner): array { return $scanner->scanCategories() ->pluck('categorias.id') ->map(fn (mixed $id): int => (int) $id) ->all(); } private function scannerCanScan(User $scanner, Ticket $ticket): bool { $categoryId = $ticket->sourceCatalogItem?->category_id; return $categoryId !== null && $scanner->scanCategories() ->where('categorias.id', $categoryId) ->exists(); } }