Optimierung 2.7: Company::scopeSearchByTerm fuer die Admin-Combobox
Die in admin/create und admin/edit identische Inline-Firmensuche (Volltext-oder-LIKE) inkl. des 2x duplizierten supportsFullTextSearch()- Treiберchecks in einen Company::scopeSearchByTerm() zentralisiert; beide Editoren nutzen ihn jetzt (Verhalten unveraendert). Ungenutzte DB-Importe entfernt. Customer nutzt bereits CustomerCompanyContext (eigene join-/user-gescopte Query, daher nicht zusammengelegt). Tests: neuer Scope-Test; volle Suite gruen (799 passed, 1 skipped). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
83d2ee659a
commit
86ba6c1a51
4 changed files with 58 additions and 28 deletions
|
|
@ -7,12 +7,14 @@ use App\Enums\Portal;
|
|||
use App\Models\Concerns\HasUniqueSlug;
|
||||
use App\Scopes\PortalScope;
|
||||
use Database\Factories\CompanyFactory;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
|
|
@ -72,6 +74,35 @@ class Company extends Model
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Firmensuche für die Admin-Combobox: Volltext (name/email/slug) bei
|
||||
* unterstützenden Treibern und Suchbegriffen ab 3 Zeichen, sonst LIKE.
|
||||
* Bei leerem Begriff bleibt die Query unverändert. Single Source of Truth
|
||||
* für die zuvor in mehreren Editoren duplizierte Such- und Treiberlogik.
|
||||
*/
|
||||
public function scopeSearchByTerm(Builder $query, string $term): Builder
|
||||
{
|
||||
$term = Portal::stripTrailingAbbreviation($term);
|
||||
|
||||
if (! filled($term)) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$supportsFullText = mb_strlen($term) >= 3
|
||||
&& in_array(DB::connection()->getDriverName(), ['mysql', 'pgsql'], true);
|
||||
|
||||
return $query->where(function (Builder $query) use ($term, $supportsFullText): void {
|
||||
if ($supportsFullText) {
|
||||
$query->whereFullText(['name', 'email', 'slug'], $term);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$query->where('name', 'like', '%'.$term.'%')
|
||||
->orWhere('slug', 'like', '%'.$term.'%');
|
||||
});
|
||||
}
|
||||
|
||||
public function owner(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'owner_user_id');
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ use App\Services\Admin\AdminPerformanceCache;
|
|||
use App\Services\PressRelease\PressReleaseHtmlSanitizer;
|
||||
use Flux\Flux;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Livewire\Attributes\Computed;
|
||||
|
|
@ -170,15 +169,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
$term = Portal::stripTrailingAbbreviation($this->companySearch);
|
||||
|
||||
$companies = Company::withoutGlobalScopes()
|
||||
->when(filled($term), function ($q) use ($term): void {
|
||||
if ($this->supportsFullTextSearch($term)) {
|
||||
$q->whereFullText(['name', 'email', 'slug'], $term);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$q->where('name', 'like', '%' . $term . '%')->orWhere('slug', 'like', '%' . $term . '%');
|
||||
})
|
||||
->searchByTerm($term)
|
||||
->when(blank($term) && $this->companyId, fn($q) => $q->whereIn('id', [(int) $this->companyId]))
|
||||
->when(blank($term) && !$this->companyId, fn($q) => $q->whereRaw('0 = 1'))
|
||||
->orderBy('name')
|
||||
|
|
@ -215,10 +206,6 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
return app(AdminPerformanceCache::class)->remember(AdminPerformanceCache::ActiveCategoryOptions, AdminPerformanceCache::OptionsTtl, fn() => Category::query()->with('translations')->where('is_active', true)->orderBy('id')->get());
|
||||
}
|
||||
|
||||
private function supportsFullTextSearch(string $term): bool
|
||||
{
|
||||
return mb_strlen($term) >= 3 && in_array(DB::connection()->getDriverName(), ['mysql', 'pgsql'], true);
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-8 pr-editor-shell" x-data="{ tagInput: '' }">
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ use App\Services\PressRelease\PressReleaseHtmlSanitizer;
|
|||
use App\Services\PressRelease\PressReleaseService;
|
||||
use Flux\Flux;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Livewire\Attributes\Computed;
|
||||
|
|
@ -351,15 +350,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
|
|||
$term = Portal::stripTrailingAbbreviation($this->companySearch);
|
||||
|
||||
$companies = Company::withoutGlobalScopes()
|
||||
->when(filled($term), function ($q) use ($term): void {
|
||||
if ($this->supportsFullTextSearch($term)) {
|
||||
$q->whereFullText(['name', 'email', 'slug'], $term);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$q->where('name', 'like', '%' . $term . '%')->orWhere('slug', 'like', '%' . $term . '%');
|
||||
})
|
||||
->searchByTerm($term)
|
||||
->when(blank($term) && $this->companyId, fn($q) => $q->whereIn('id', [(int) $this->companyId]))
|
||||
->when(blank($term) && !$this->companyId, fn($q) => $q->whereRaw('0 = 1'))
|
||||
->orderBy('name')
|
||||
|
|
@ -401,10 +392,6 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
|
|||
return app(AdminPerformanceCache::class)->remember(AdminPerformanceCache::ActiveCategoryOptions, AdminPerformanceCache::OptionsTtl, fn() => Category::query()->with('translations')->where('is_active', true)->orderBy('id')->get());
|
||||
}
|
||||
|
||||
private function supportsFullTextSearch(string $term): bool
|
||||
{
|
||||
return mb_strlen($term) >= 3 && in_array(DB::connection()->getDriverName(), ['mysql', 'pgsql'], true);
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-8 pr-editor-shell" x-data="{ tagInput: '' }">
|
||||
|
|
|
|||
25
tests/Feature/CompanySearchByTermTest.php
Normal file
25
tests/Feature/CompanySearchByTermTest.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Company;
|
||||
|
||||
test('searchByTerm matches name and slug, ignoring non-matches', function () {
|
||||
$acme = Company::factory()->create(['name' => 'Acme Industries', 'slug' => 'acme-industries']);
|
||||
$globex = Company::factory()->create(['name' => 'Globex Corporation', 'slug' => 'globex-corp']);
|
||||
|
||||
$results = Company::query()->searchByTerm('Acme')->pluck('id');
|
||||
|
||||
expect($results)->toContain($acme->id)
|
||||
->not->toContain($globex->id);
|
||||
});
|
||||
|
||||
test('searchByTerm returns the query unchanged for a blank term', function () {
|
||||
Company::factory()->count(3)->create();
|
||||
|
||||
expect(Company::query()->searchByTerm('')->count())->toBe(Company::query()->count());
|
||||
});
|
||||
|
||||
test('searchByTerm strips a trailing portal abbreviation from the term', function () {
|
||||
$company = Company::factory()->create(['name' => 'Musterfirma']);
|
||||
|
||||
expect(Company::query()->searchByTerm('Musterfirma (PE)')->pluck('id'))->toContain($company->id);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue