Add invoice full text search
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
c3a0381a0a
commit
784b49c3dd
|
@ -19,9 +19,9 @@ class InvoiceIndexAction
|
||||||
/**
|
/**
|
||||||
* @return LengthAwarePaginator<Invoice>
|
* @return LengthAwarePaginator<Invoice>
|
||||||
*/
|
*/
|
||||||
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
|
public function asController(ActionRequest $request): Response
|
||||||
|
@ -32,7 +32,7 @@ class InvoiceIndexAction
|
||||||
$filter = InvoiceFilterScope::fromRequest($request->input('filter', ''));
|
$filter = InvoiceFilterScope::fromRequest($request->input('filter', ''));
|
||||||
|
|
||||||
return Inertia::render('invoice/Index', [
|
return Inertia::render('invoice/Index', [
|
||||||
'data' => InvoiceResource::collection($this->handle($filter)),
|
'data' => InvoiceResource::collection($this->handle($filter)->paginate(15)),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,14 @@ use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Laravel\Scout\Searchable;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
|
|
||||||
class Invoice extends Model
|
class Invoice extends Model
|
||||||
{
|
{
|
||||||
/** @use HasFactory<InvoiceFactory> */
|
/** @use HasFactory<InvoiceFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
use Searchable;
|
||||||
|
|
||||||
public $guarded = [];
|
public $guarded = [];
|
||||||
|
|
||||||
|
@ -154,4 +156,20 @@ class Invoice extends Model
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the indexable data array for the model.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toSearchableArray(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'to' => implode(', ', $this->to),
|
||||||
|
'usage' => $this->usage,
|
||||||
|
'greeting' => $this->greeting,
|
||||||
|
'mail_email' => $this->mail_email,
|
||||||
|
'status' => $this->status->value,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@ namespace App\Invoice\Scopes;
|
||||||
use App\Invoice\Enums\InvoiceStatus;
|
use App\Invoice\Enums\InvoiceStatus;
|
||||||
use App\Invoice\Models\Invoice;
|
use App\Invoice\Models\Invoice;
|
||||||
use App\Lib\Filter;
|
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\MapInputName;
|
||||||
use Spatie\LaravelData\Attributes\MapOutputName;
|
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||||
use Spatie\LaravelData\Mappers\SnakeCaseMapper;
|
use Spatie\LaravelData\Mappers\SnakeCaseMapper;
|
||||||
|
@ -15,32 +16,32 @@ use Spatie\LaravelData\Mappers\SnakeCaseMapper;
|
||||||
*/
|
*/
|
||||||
#[MapInputName(SnakeCaseMapper::class)]
|
#[MapInputName(SnakeCaseMapper::class)]
|
||||||
#[MapOutputName(SnakeCaseMapper::class)]
|
#[MapOutputName(SnakeCaseMapper::class)]
|
||||||
class InvoiceFilterScope extends Filter
|
class InvoiceFilterScope extends ScoutFilter
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param array<int, string> $statuses
|
* @param array<int, string> $statuses
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public ?array $statuses = null,
|
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;
|
$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]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Form\Models\Form;
|
use App\Form\Models\Form;
|
||||||
|
use App\Invoice\Models\Invoice;
|
||||||
use App\Member\Member;
|
use App\Member\Member;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@ -153,8 +154,17 @@ return [
|
||||||
'pagination' => [
|
'pagination' => [
|
||||||
'maxTotalHits' => 1000000,
|
'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,
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Invoice\Models\Invoice;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Laravel\Scout\Console\SyncIndexSettingsCommand;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Artisan::call(SyncIndexSettingsCommand::class);
|
||||||
|
foreach (Invoice::get() as $invoice) {
|
||||||
|
$invoice->searchable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
};
|
|
@ -74,6 +74,7 @@
|
||||||
</ui-popup>
|
</ui-popup>
|
||||||
<page-filter breakpoint="xl" :filterable="false">
|
<page-filter breakpoint="xl" :filterable="false">
|
||||||
<template #buttons>
|
<template #buttons>
|
||||||
|
<f-text id="search" :model-value="getFilter('search')" label="Suchen …" size="sm" @update:model-value="setFilter('search', $event)"></f-text>
|
||||||
<f-multipleselect
|
<f-multipleselect
|
||||||
id="statuses"
|
id="statuses"
|
||||||
:options="meta.statuses"
|
:options="meta.statuses"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\Invoice;
|
namespace Tests\Feature\EndToEnd;
|
||||||
|
|
||||||
use App\Invoice\BillKind;
|
use App\Invoice\BillKind;
|
||||||
use App\Invoice\Enums\InvoiceStatus;
|
use App\Invoice\Enums\InvoiceStatus;
|
||||||
|
@ -9,8 +9,11 @@ use App\Invoice\Models\InvoicePosition;
|
||||||
use App\Member\Member;
|
use App\Member\Member;
|
||||||
use App\Payment\Subscription;
|
use App\Payment\Subscription;
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Tests\EndToEndTestCase;
|
||||||
|
use Tests\Feature\Invoice\ReceiverRequestFactory;
|
||||||
|
|
||||||
uses(DatabaseTransactions::class);
|
uses(DatabaseTransactions::class);
|
||||||
|
uses(EndToEndTestCase::class);
|
||||||
|
|
||||||
it('testItDisplaysInvoices', function () {
|
it('testItDisplaysInvoices', function () {
|
||||||
$this->login()->loginNami()->withoutExceptionHandling();
|
$this->login()->loginNami()->withoutExceptionHandling();
|
||||||
|
@ -25,6 +28,7 @@ it('testItDisplaysInvoices', function () {
|
||||||
->status(InvoiceStatus::SENT)
|
->status(InvoiceStatus::SENT)
|
||||||
->create(['usage' => 'Usa', 'mail_email' => 'a@b.de']);
|
->create(['usage' => 'Usa', 'mail_email' => 'a@b.de']);
|
||||||
|
|
||||||
|
sleep(2);
|
||||||
test()->get(route('invoice.index'))
|
test()->get(route('invoice.index'))
|
||||||
->assertInertiaPath('data.data.0.to.name', 'Familie Blabla')
|
->assertInertiaPath('data.data.0.to.name', 'Familie Blabla')
|
||||||
->assertInertiaPath('data.data.0.id', $invoice->id)
|
->assertInertiaPath('data.data.0.id', $invoice->id)
|
||||||
|
@ -78,19 +82,35 @@ it('testValuesCanBeNull', function () {
|
||||||
test()->login()->loginNami()->withoutExceptionHandling();
|
test()->login()->loginNami()->withoutExceptionHandling();
|
||||||
Invoice::factory()->create();
|
Invoice::factory()->create();
|
||||||
|
|
||||||
|
sleep(2);
|
||||||
test()->get(route('invoice.index'))
|
test()->get(route('invoice.index'))
|
||||||
->assertInertiaPath('data.data.0.sent_at_human', '');
|
->assertInertiaPath('data.data.0.sent_at_human', '');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('testItFiltersForInvoiceStatus', function () {
|
it('filters for invoice status', function (array $filter, int $count) {
|
||||||
test()->login()->loginNami()->withoutExceptionHandling();
|
test()->login()->loginNami()->withoutExceptionHandling();
|
||||||
Invoice::factory()->status(InvoiceStatus::NEW)->create();
|
Invoice::factory()->status(InvoiceStatus::NEW)->create();
|
||||||
Invoice::factory()->status(InvoiceStatus::SENT)->count(2)->create();
|
Invoice::factory()->status(InvoiceStatus::SENT)->count(2)->create();
|
||||||
Invoice::factory()->status(InvoiceStatus::PAID)->count(3)->create();
|
Invoice::factory()->status(InvoiceStatus::PAID)->count(3)->create();
|
||||||
|
|
||||||
test()->callFilter('invoice.index', [])->assertInertiaCount('data.data', 3);
|
sleep(2);
|
||||||
test()->callFilter('invoice.index', ['statuses' => []])->assertInertiaCount('data.data', 0);
|
test()->callFilter('invoice.index', $filter)->assertInertiaCount('data.data', $count);
|
||||||
test()->callFilter('invoice.index', ['statuses' => ['Neu']])->assertInertiaCount('data.data', 1);
|
})->with([
|
||||||
test()->callFilter('invoice.index', ['statuses' => ['Neu', 'Rechnung beglichen']])->assertInertiaCount('data.data', 4);
|
[[], 3],
|
||||||
test()->callFilter('invoice.index', ['statuses' => ['Neu', 'Rechnung beglichen', 'Rechnung gestellt']])->assertInertiaCount('data.data', 6);
|
[['statuses' => []], 0],
|
||||||
});
|
[['statuses' => ['Neu']], 1],
|
||||||
|
[['statuses' => ['Neu', 'Rechnung beglichen']], 4],
|
||||||
|
[['statuses' => ['Neu', 'Rechnung beglichen', 'Rechnung gestellt']], 6],
|
||||||
|
]);
|
||||||
|
|
||||||
|
it('searches invoice usage', function (array $filter, int $count) {
|
||||||
|
test()->login()->loginNami()->withoutExceptionHandling();
|
||||||
|
Invoice::factory()->status(InvoiceStatus::NEW)->create(['usage' => 'Kein Zweck']);
|
||||||
|
Invoice::factory()->status(InvoiceStatus::NEW)->create(['usage' => 'Mitgliedsbeitrag']);
|
||||||
|
|
||||||
|
sleep(2);
|
||||||
|
test()->callFilter('invoice.index', $filter)->assertInertiaCount('data.data', $count);
|
||||||
|
})->with([
|
||||||
|
[['search' => 'Mitgliedsbeitrag'], 1],
|
||||||
|
[['search' => 'Kein'], 1],
|
||||||
|
]);
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Tests;
|
namespace Tests;
|
||||||
|
|
||||||
use App\Form\Models\Form;
|
use App\Form\Models\Form;
|
||||||
|
use App\Invoice\Models\Invoice;
|
||||||
use App\Member\Member;
|
use App\Member\Member;
|
||||||
use Laravel\Scout\Console\FlushCommand;
|
use Laravel\Scout\Console\FlushCommand;
|
||||||
use Laravel\Scout\Console\SyncIndexSettingsCommand;
|
use Laravel\Scout\Console\SyncIndexSettingsCommand;
|
||||||
|
@ -26,6 +27,7 @@ abstract class EndToEndTestCase extends TestCase
|
||||||
config()->set('scout.driver', 'meilisearch');
|
config()->set('scout.driver', 'meilisearch');
|
||||||
Artisan::call(FlushCommand::class, ['model' => Member::class]);
|
Artisan::call(FlushCommand::class, ['model' => Member::class]);
|
||||||
Artisan::call(FlushCommand::class, ['model' => Form::class]);
|
Artisan::call(FlushCommand::class, ['model' => Form::class]);
|
||||||
|
Artisan::call(FlushCommand::class, ['model' => Invoice::class]);
|
||||||
Artisan::call(SyncIndexSettingsCommand::class);
|
Artisan::call(SyncIndexSettingsCommand::class);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
|
Loading…
Reference in New Issue