From 784b49c3dde7d01a16beff12f6ea0252423c60a9 Mon Sep 17 00:00:00 2001 From: philipp lang Date: Sun, 15 Dec 2024 16:05:38 +0100 Subject: [PATCH] Add invoice full text search --- app/Invoice/Actions/InvoiceIndexAction.php | 6 +-- app/Invoice/Models/Invoice.php | 18 ++++++++ app/Invoice/Scopes/InvoiceFilterScope.php | 41 ++++++++++--------- config/scout.php | 12 +++++- .../2024_12_15_155941_update_search_index.php | 27 ++++++++++++ resources/js/views/invoice/Index.vue | 1 + .../Invoice/InvoiceIndexActionTest.php | 36 ++++++++++++---- tests/EndToEndTestCase.php | 2 + 8 files changed, 111 insertions(+), 32 deletions(-) create mode 100644 database/migrations/2024_12_15_155941_update_search_index.php rename tests/{Feature => EndToEnd}/Invoice/InvoiceIndexActionTest.php (80%) diff --git a/app/Invoice/Actions/InvoiceIndexAction.php b/app/Invoice/Actions/InvoiceIndexAction.php index f9472e4c..d64325a5 100644 --- a/app/Invoice/Actions/InvoiceIndexAction.php +++ b/app/Invoice/Actions/InvoiceIndexAction.php @@ -19,9 +19,9 @@ class InvoiceIndexAction /** * @return LengthAwarePaginator */ - public function handle(InvoiceFilterScope $filter): LengthAwarePaginator + public function handle(InvoiceFilterScope $filter) { - return Invoice::withFilter($filter)->with('positions')->paginate(15); + return $filter->getQuery()->query(fn ($q) => $q->with('positions')); } public function asController(ActionRequest $request): Response @@ -32,7 +32,7 @@ class InvoiceIndexAction $filter = InvoiceFilterScope::fromRequest($request->input('filter', '')); return Inertia::render('invoice/Index', [ - 'data' => InvoiceResource::collection($this->handle($filter)), + 'data' => InvoiceResource::collection($this->handle($filter)->paginate(15)), ]); } } diff --git a/app/Invoice/Models/Invoice.php b/app/Invoice/Models/Invoice.php index 0ae41b96..008b789d 100644 --- a/app/Invoice/Models/Invoice.php +++ b/app/Invoice/Models/Invoice.php @@ -17,12 +17,14 @@ use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; +use Laravel\Scout\Searchable; use stdClass; class Invoice extends Model { /** @use HasFactory */ use HasFactory; + use Searchable; public $guarded = []; @@ -154,4 +156,20 @@ class Invoice extends Model ]); } } + + /** + * Get the indexable data array for the model. + * + * @return array + */ + public function toSearchableArray(): array + { + return [ + 'to' => implode(', ', $this->to), + 'usage' => $this->usage, + 'greeting' => $this->greeting, + 'mail_email' => $this->mail_email, + 'status' => $this->status->value, + ]; + } } diff --git a/app/Invoice/Scopes/InvoiceFilterScope.php b/app/Invoice/Scopes/InvoiceFilterScope.php index fc39c08c..941f58a3 100644 --- a/app/Invoice/Scopes/InvoiceFilterScope.php +++ b/app/Invoice/Scopes/InvoiceFilterScope.php @@ -5,7 +5,8 @@ namespace App\Invoice\Scopes; use App\Invoice\Enums\InvoiceStatus; use App\Invoice\Models\Invoice; use App\Lib\Filter; -use Illuminate\Database\Eloquent\Builder; +use App\Lib\ScoutFilter; +use Laravel\Scout\Builder; use Spatie\LaravelData\Attributes\MapInputName; use Spatie\LaravelData\Attributes\MapOutputName; use Spatie\LaravelData\Mappers\SnakeCaseMapper; @@ -15,32 +16,32 @@ use Spatie\LaravelData\Mappers\SnakeCaseMapper; */ #[MapInputName(SnakeCaseMapper::class)] #[MapOutputName(SnakeCaseMapper::class)] -class InvoiceFilterScope extends Filter +class InvoiceFilterScope extends ScoutFilter { /** * @param array $statuses */ public function __construct( public ?array $statuses = null, + public ?string $search = null ) { - } - - /** - * @inheritdoc - */ - public function apply(Builder $query): Builder - { - $query = $query->whereIn('status', $this->statuses); - - return $query; - } - - /** - * @inheritdoc - */ - public function toDefault(): self - { $this->statuses = $this->statuses === null ? InvoiceStatus::defaultVisibleValues()->toArray() : $this->statuses; - return $this; + } + + /** + * @inheritdoc + */ + public function getQuery(): Builder + { + $this->search = $this->search ?: ''; + + return Invoice::search($this->search, function ($engine, string $query, array $options) { + if (empty($this->statuses)) { + $filter = 'status = "asa6aeruuni4BahC7Wei6ahm1"'; + } else { + $filter = collect($this->statuses)->map(fn (string $status) => "status = \"$status\"")->join(' OR '); + } + return $engine->search($query, [...$options, 'filter' => $filter]); + }); } } diff --git a/config/scout.php b/config/scout.php index 451ec719..d76b800e 100644 --- a/config/scout.php +++ b/config/scout.php @@ -1,6 +1,7 @@ [ 'maxTotalHits' => 1000000, ] - ] + ], + Invoice::class => [ + 'filterableAttributes' => ['to', 'usage', 'greeting', 'mail_email', 'status', 'id'], + 'searchableAttributes' => ['to', 'usage', 'greeting', 'mail_email', 'status', 'id'], + 'sortableAttributes' => [], + 'displayedAttributes' => ['to', 'usage', 'greeting', 'mail_email', 'status', 'id'], + 'pagination' => [ + 'maxTotalHits' => 1000000, + ] + ], ], ], diff --git a/database/migrations/2024_12_15_155941_update_search_index.php b/database/migrations/2024_12_15_155941_update_search_index.php new file mode 100644 index 00000000..17609f81 --- /dev/null +++ b/database/migrations/2024_12_15_155941_update_search_index.php @@ -0,0 +1,27 @@ +searchable(); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // + } +}; diff --git a/resources/js/views/invoice/Index.vue b/resources/js/views/invoice/Index.vue index 1419b5d3..59308663 100644 --- a/resources/js/views/invoice/Index.vue +++ b/resources/js/views/invoice/Index.vue @@ -74,6 +74,7 @@