Improving coverage of Global Search
This commit is contained in:
parent
8231d967dd
commit
2795faa0fe
|
|
@ -19,6 +19,7 @@ use App\Models\Invoice;
|
|||
use App\Models\Project;
|
||||
use Elastic\Elasticsearch\ClientBuilder;
|
||||
use App\Http\Requests\Search\GenericSearchRequest;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SearchController extends Controller
|
||||
{
|
||||
|
|
@ -44,6 +45,8 @@ class SearchController extends Controller
|
|||
|
||||
private array $projects = [];
|
||||
|
||||
private array $tasks = [];
|
||||
|
||||
public function __invoke(GenericSearchRequest $request)
|
||||
{
|
||||
if (config('scout.driver') == 'elastic') {
|
||||
|
|
@ -87,17 +90,41 @@ class SearchController extends Controller
|
|||
$params = [
|
||||
// 'index' => 'clients,invoices,client_contacts',
|
||||
// 'index' => 'clients,invoices,client_contacts,quotes,expenses,credits,recurring_invoices,vendors,vendor_contacts,purchase_orders,projects',
|
||||
'index' => 'clients_v2,invoices_v2,client_contacts_v2,quotes_v2,expenses_v2,credits_v2,recurring_invoices_v2,vendors_v2,vendor_contacts_v2,purchase_orders_v2,projects_v2',
|
||||
'index' => 'clients_v2,invoices_v2,client_contacts_v2,quotes_v2,expenses_v2,credits_v2,recurring_invoices_v2,vendors_v2,vendor_contacts_v2,purchase_orders_v2,projects_v2,tasks_v2',
|
||||
'body' => [
|
||||
'query' => [
|
||||
'bool' => [
|
||||
'must' => [
|
||||
'should' => [
|
||||
[
|
||||
'multi_match' => [
|
||||
'query' => $search,
|
||||
'fields' => ['*'],
|
||||
'fuzziness' => 'AUTO',
|
||||
]
|
||||
],
|
||||
// Safe nested search that won't fail on missing fields
|
||||
[
|
||||
'nested' => [
|
||||
'path' => 'line_items',
|
||||
'query' => [
|
||||
'multi_match' => [
|
||||
'query' => $search,
|
||||
'fields' => [
|
||||
'line_items.product_key^2',
|
||||
'line_items.notes^2',
|
||||
'line_items.custom_value1',
|
||||
'line_items.custom_value2',
|
||||
'line_items.custom_value3',
|
||||
'line_items.custom_value4'
|
||||
],
|
||||
'fuzziness' => 'AUTO',
|
||||
]
|
||||
],
|
||||
'ignore_unmapped' => true
|
||||
]
|
||||
],
|
||||
],
|
||||
'minimum_should_match' => 1,
|
||||
'filter' => [
|
||||
'match' => [
|
||||
'company_key' => $company->company_key,
|
||||
|
|
@ -109,8 +136,11 @@ class SearchController extends Controller
|
|||
],
|
||||
];
|
||||
|
||||
|
||||
$results = $elastic->search($params);
|
||||
|
||||
nlog($results['hits']);
|
||||
|
||||
$this->mapResults($results['hits']['hits'] ?? []);
|
||||
|
||||
return response()->json([
|
||||
|
|
@ -125,6 +155,7 @@ class SearchController extends Controller
|
|||
'vendor_contacts' => $this->vendor_contacts,
|
||||
'purchase_orders' => $this->purchase_orders,
|
||||
'projects' => $this->projects,
|
||||
'tasks' => $this->tasks,
|
||||
'settings' => $this->settingsMap(),
|
||||
], 200);
|
||||
|
||||
|
|
@ -134,8 +165,8 @@ class SearchController extends Controller
|
|||
{
|
||||
|
||||
foreach ($results as $result) {
|
||||
switch ($result['_index']) {
|
||||
case 'clients':
|
||||
switch (true) {
|
||||
case Str::startsWith($result['_index'], 'clients'):
|
||||
|
||||
if ($result['_source']['is_deleted']) { //do not return deleted results
|
||||
break;
|
||||
|
|
@ -149,7 +180,7 @@ class SearchController extends Controller
|
|||
];
|
||||
|
||||
break;
|
||||
case 'invoices':
|
||||
case Str::startsWith($result['_index'], 'invoices'):
|
||||
|
||||
if ($result['_source']['is_deleted']) { //do not return deleted invoices
|
||||
break;
|
||||
|
|
@ -163,7 +194,7 @@ class SearchController extends Controller
|
|||
'path' => "/invoices/{$result['_source']['hashed_id']}/edit"
|
||||
];
|
||||
break;
|
||||
case 'client_contacts':
|
||||
case Str::startsWith($result['_index'], 'client_contacts'):
|
||||
|
||||
if ($result['_source']['__soft_deleted']) {
|
||||
break;
|
||||
|
|
@ -176,7 +207,7 @@ class SearchController extends Controller
|
|||
'path' => "/clients/{$result['_source']['client_id']}"
|
||||
];
|
||||
break;
|
||||
case 'quotes':
|
||||
case Str::startsWith($result['_index'], 'quotes'):
|
||||
|
||||
if ($result['_source']['__soft_deleted']) {
|
||||
break;
|
||||
|
|
@ -191,7 +222,7 @@ class SearchController extends Controller
|
|||
|
||||
break;
|
||||
|
||||
case 'expenses':
|
||||
case Str::startsWith($result['_index'], 'expenses'):
|
||||
|
||||
if ($result['_source']['__soft_deleted']) {
|
||||
break;
|
||||
|
|
@ -206,7 +237,7 @@ class SearchController extends Controller
|
|||
|
||||
break;
|
||||
|
||||
case 'credits':
|
||||
case Str::startsWith($result['_index'], 'credits'):
|
||||
|
||||
if ($result['_source']['__soft_deleted']) {
|
||||
break;
|
||||
|
|
@ -221,7 +252,7 @@ class SearchController extends Controller
|
|||
|
||||
break;
|
||||
|
||||
case 'recurring_invoices':
|
||||
case Str::startsWith($result['_index'], 'recurring_invoices'):
|
||||
|
||||
if ($result['_source']['__soft_deleted']) {
|
||||
break;
|
||||
|
|
@ -236,7 +267,7 @@ class SearchController extends Controller
|
|||
|
||||
break;
|
||||
|
||||
case 'vendors':
|
||||
case Str::startsWith($result['_index'], 'vendors'):
|
||||
|
||||
if ($result['_source']['__soft_deleted']) {
|
||||
break;
|
||||
|
|
@ -251,7 +282,7 @@ class SearchController extends Controller
|
|||
|
||||
break;
|
||||
|
||||
case 'vendor_contacts':
|
||||
case Str::startsWith($result['_index'], 'vendor_contacts'):
|
||||
|
||||
if ($result['_source']['__soft_deleted']) {
|
||||
break;
|
||||
|
|
@ -266,7 +297,7 @@ class SearchController extends Controller
|
|||
|
||||
break;
|
||||
|
||||
case 'purchase_orders':
|
||||
case Str::startsWith($result['_index'], 'purchase_orders'):
|
||||
|
||||
if ($result['_source']['__soft_deleted']) {
|
||||
break;
|
||||
|
|
@ -281,7 +312,7 @@ class SearchController extends Controller
|
|||
|
||||
break;
|
||||
|
||||
case 'projects':
|
||||
case Str::startsWith($result['_index'], 'projects'):
|
||||
|
||||
if ($result['_source']['__soft_deleted']) {
|
||||
break;
|
||||
|
|
@ -294,6 +325,20 @@ class SearchController extends Controller
|
|||
'path' => "/projects/{$result['_source']['hashed_id']}"
|
||||
];
|
||||
|
||||
break;
|
||||
case Str::startsWith($result['_index'], 'tasks'):
|
||||
|
||||
if ($result['_source']['is_deleted']) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->tasks[] = [
|
||||
'name' => $result['_source']['name'],
|
||||
'type' => '/task',
|
||||
'id' => $result['_source']['hashed_id'],
|
||||
'path' => "/tasks/{$result['_source']['hashed_id']}/edit"
|
||||
];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -267,9 +267,9 @@ class Client extends BaseModel implements HasLocalePreference
|
|||
return [
|
||||
'id' => $this->company->db.":".$this->id,
|
||||
'name' => $name,
|
||||
'is_deleted' => $this->is_deleted,
|
||||
'is_deleted' => (bool)$this->is_deleted,
|
||||
'hashed_id' => $this->hashed_id,
|
||||
'number' => $this->number,
|
||||
'number' => (string)$this->number,
|
||||
'id_number' => $this->id_number,
|
||||
'vat_number' => $this->vat_number,
|
||||
'balance' => $this->balance,
|
||||
|
|
|
|||
|
|
@ -169,6 +169,11 @@ class ClientContact extends Authenticatable implements HasLocalePreference
|
|||
'email',
|
||||
];
|
||||
|
||||
public function searchableAs(): string
|
||||
{
|
||||
return 'client_contacts_v2';
|
||||
}
|
||||
|
||||
public function toSearchableArray()
|
||||
{
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
|||
* @property \App\Models\Client $client
|
||||
* @property \App\Models\Vendor|null $vendor
|
||||
* @property-read \App\Models\Location|null $location
|
||||
* @property-read mixed $pivot
|
||||
* @property-read mixed $pivotcredi
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $company_ledger
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
|
||||
|
|
@ -223,8 +223,8 @@ class Credit extends BaseModel
|
|||
'id' => $this->company->db.":".$this->id,
|
||||
'name' => ctrans('texts.credit') . " " . $this->number . " | " . $this->client->present()->name() . ' | ' . Number::formatMoney($this->amount, $this->company) . ' | ' . $this->translateDate($this->date, $this->company->date_format(), $locale),
|
||||
'hashed_id' => $this->hashed_id,
|
||||
'number' => $this->number,
|
||||
'is_deleted' => $this->is_deleted,
|
||||
'number' => (string)$this->number,
|
||||
'is_deleted' => (bool)$this->is_deleted,
|
||||
'amount' => (float) $this->amount,
|
||||
'balance' => (float) $this->balance,
|
||||
'due_date' => $this->due_date,
|
||||
|
|
|
|||
|
|
@ -187,8 +187,8 @@ class Expense extends BaseModel
|
|||
'id' => $this->company->db.":".$this->id,
|
||||
'name' => ctrans('texts.expense') . " " . ($this->number ?? '') . ' | ' . Number::formatMoney($this->amount, $this->company) . ' | ' . $this->translateDate($this->date, $this->company->date_format(), $locale),
|
||||
'hashed_id' => $this->hashed_id,
|
||||
'number' => $this->number,
|
||||
'is_deleted' => $this->is_deleted,
|
||||
'number' => (string)$this->number,
|
||||
'is_deleted' => (bool)$this->is_deleted,
|
||||
'amount' => (float) $this->amount,
|
||||
'date' => $this->date ?? null,
|
||||
'custom_value1' => (string)$this->custom_value1,
|
||||
|
|
@ -196,6 +196,8 @@ class Expense extends BaseModel
|
|||
'custom_value3' => (string)$this->custom_value3,
|
||||
'custom_value4' => (string)$this->custom_value4,
|
||||
'company_key' => $this->company->company_key,
|
||||
'public_notes' => (string)$this->public_notes,
|
||||
'private_notes' => (string)$this->private_notes
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -269,8 +269,8 @@ class Invoice extends BaseModel
|
|||
'id' => (string)$this->company->db.":".$this->id,
|
||||
'name' => ctrans('texts.invoice') . " " . $this->number . " | " . $this->client->present()->name() . ' | ' . Number::formatMoney($this->amount, $this->company) . ' | ' . $this->translateDate($this->date, $this->company->date_format(), $locale),
|
||||
'hashed_id' => $this->hashed_id,
|
||||
'number' => $this->number,
|
||||
'is_deleted' => $this->is_deleted,
|
||||
'number' => (string)$this->number,
|
||||
'is_deleted' => (bool)$this->is_deleted,
|
||||
'amount' => (float) $this->amount,
|
||||
'balance' => (float) $this->balance,
|
||||
'due_date' => $this->due_date,
|
||||
|
|
|
|||
|
|
@ -116,8 +116,8 @@ class Project extends BaseModel
|
|||
'id' => (string)$this->company->db.":".$this->id,
|
||||
'name' => ctrans('texts.project') . " " . $this->number . ' | ' . $this->name . " | " . $this->client->present()->name(),
|
||||
'hashed_id' => $this->hashed_id,
|
||||
'number' => $this->number,
|
||||
'is_deleted' => $this->is_deleted,
|
||||
'number' => (string)$this->number,
|
||||
'is_deleted' => (bool)$this->is_deleted,
|
||||
'task_rate' => (float) $this->task_rate,
|
||||
'budgeted_hours' => (float) $this->budgeted_hours,
|
||||
'due_date' => $this->due_date,
|
||||
|
|
|
|||
|
|
@ -228,8 +228,8 @@ class PurchaseOrder extends BaseModel
|
|||
'id' => $this->company->db.":".$this->id,
|
||||
'name' => ctrans('texts.purchase_order') . " " . $this->number . " | " . $this->vendor->present()->name() . ' | ' . Number::formatMoney($this->amount, $this->company) . ' | ' . $this->translateDate($this->date, $this->company->date_format(), $locale),
|
||||
'hashed_id' => $this->hashed_id,
|
||||
'number' => $this->number,
|
||||
'is_deleted' => $this->is_deleted,
|
||||
'number' => (string)$this->number,
|
||||
'is_deleted' => (bool)$this->is_deleted,
|
||||
'amount' => (float) $this->amount,
|
||||
'balance' => (float) $this->balance,
|
||||
'due_date' => $this->due_date,
|
||||
|
|
|
|||
|
|
@ -220,8 +220,8 @@ class Quote extends BaseModel
|
|||
'id' => $this->company->db.":".$this->id,
|
||||
'name' => ctrans('texts.quote') . " " . ($this->number ?? '') . " | " . $this->client->present()->name() . ' | ' . Number::formatMoney($this->amount, $this->company) . ' | ' . $this->translateDate($this->date, $this->company->date_format(), $locale),
|
||||
'hashed_id' => $this->hashed_id,
|
||||
'number' => $this->number,
|
||||
'is_deleted' => $this->is_deleted,
|
||||
'number' => (string)$this->number,
|
||||
'is_deleted' => (bool)$this->is_deleted,
|
||||
'amount' => (float) $this->amount,
|
||||
'balance' => (float) $this->balance,
|
||||
'due_date' => $this->due_date,
|
||||
|
|
|
|||
|
|
@ -138,15 +138,6 @@ class RecurringInvoice extends BaseModel
|
|||
use PresentableTrait;
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* Get the index name for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function searchableAs(): string
|
||||
{
|
||||
return 'recurring_invoices_v2';
|
||||
}
|
||||
|
||||
protected $presenter = RecurringInvoicePresenter::class;
|
||||
|
||||
|
|
@ -280,17 +271,65 @@ class RecurringInvoice extends BaseModel
|
|||
'remaining_cycles',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Get the index name for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function searchableAs(): string
|
||||
{
|
||||
return 'recurring_invoices_v2';
|
||||
}
|
||||
|
||||
public function toSearchableArray()
|
||||
{
|
||||
$locale = $this->company->locale();
|
||||
App::setLocale($locale);
|
||||
|
||||
// Properly cast line items to ensure correct types
|
||||
$line_items = [];
|
||||
if ($this->line_items) {
|
||||
foreach ($this->line_items as $item) {
|
||||
$line_items[] = [
|
||||
'quantity' => (float)($item->quantity ?? 0),
|
||||
'net_cost' => (float)($item->net_cost ?? 0),
|
||||
'cost' => (float)($item->cost ?? 0),
|
||||
'product_key' => (string)($item->product_key ?? ''),
|
||||
'product_cost' => (float)($item->product_cost ?? 0),
|
||||
'notes' => (string)($item->notes ?? ''),
|
||||
'discount' => (float)($item->discount ?? 0),
|
||||
'is_amount_discount' => (bool)($item->is_amount_discount ?? false),
|
||||
'tax_name1' => (string)($item->tax_name1 ?? ''),
|
||||
'tax_rate1' => (float)($item->tax_rate1 ?? 0),
|
||||
'tax_name2' => (string)($item->tax_name2 ?? ''),
|
||||
'tax_rate2' => (float)($item->tax_rate2 ?? 0),
|
||||
'tax_name3' => (string)($item->tax_name3 ?? ''),
|
||||
'tax_rate3' => (float)($item->tax_rate3 ?? 0),
|
||||
'sort_id' => (string)($item->sort_id ?? ''),
|
||||
'line_total' => (float)($item->line_total ?? 0),
|
||||
'gross_line_total' => (float)($item->gross_line_total ?? 0),
|
||||
'tax_amount' => (float)($item->tax_amount ?? 0),
|
||||
'date' => (string)($item->date ?? ''),
|
||||
'custom_value1' => (string)($item->custom_value1 ?? ''),
|
||||
'custom_value2' => (string)($item->custom_value2 ?? ''),
|
||||
'custom_value3' => (string)($item->custom_value3 ?? ''),
|
||||
'custom_value4' => (string)($item->custom_value4 ?? ''),
|
||||
'type_id' => (string)($item->type_id ?? ''),
|
||||
'tax_id' => (string)($item->tax_id ?? ''),
|
||||
'task_id' => (string)($item->task_id ?? ''),
|
||||
'expense_id' => (string)($item->expense_id ?? ''),
|
||||
'unit_code' => (string)($item->unit_code ?? ''),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $this->company->db.":".$this->id,
|
||||
'name' => ctrans('texts.recurring_invoice') . " " . $this->number . " | " . $this->client->present()->name() . ' | ' . Number::formatMoney($this->amount, $this->company) . ' | ' . $this->translateDate($this->date, $this->company->date_format(), $locale),
|
||||
'hashed_id' => $this->hashed_id,
|
||||
'number' => $this->number,
|
||||
'is_deleted' => $this->is_deleted,
|
||||
'number' => (string)$this->number,
|
||||
'is_deleted' => (bool)$this->is_deleted,
|
||||
'amount' => (float) $this->amount,
|
||||
'balance' => (float) $this->balance,
|
||||
'due_date' => $this->due_date,
|
||||
|
|
@ -301,6 +340,7 @@ class RecurringInvoice extends BaseModel
|
|||
'custom_value4' => (string)$this->custom_value4,
|
||||
'company_key' => $this->company->company_key,
|
||||
'po_number' => (string)$this->po_number,
|
||||
'line_items' => $line_items,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -109,6 +109,16 @@ class Task extends BaseModel
|
|||
use Filterable;
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* Get the index name for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function searchableAs(): string
|
||||
{
|
||||
return 'tasks_v2';
|
||||
}
|
||||
|
||||
protected $fillable = [
|
||||
'client_id',
|
||||
'invoice_id',
|
||||
|
|
|
|||
|
|
@ -171,9 +171,9 @@ class Vendor extends BaseModel
|
|||
return [
|
||||
'id' => $this->company->db.":".$this->id,
|
||||
'name' => $name,
|
||||
'is_deleted' => $this->is_deleted,
|
||||
'is_deleted' => (bool)$this->is_deleted,
|
||||
'hashed_id' => $this->hashed_id,
|
||||
'number' => $this->number,
|
||||
'number' => (string)$this->number,
|
||||
'id_number' => $this->id_number,
|
||||
'vat_number' => $this->vat_number,
|
||||
'phone' => $this->phone,
|
||||
|
|
|
|||
|
|
@ -126,20 +126,25 @@ class VendorContact extends Authenticatable implements HasLocalePreference
|
|||
'send_email',
|
||||
];
|
||||
|
||||
public function searchableAs(): string
|
||||
{
|
||||
return 'vendor_contacts_v2';
|
||||
}
|
||||
|
||||
public function toSearchableArray()
|
||||
{
|
||||
return [
|
||||
'id' => $this->company->db.":".$this->id,
|
||||
'name' => $this->present()->search_display(),
|
||||
'hashed_id' => $this->hashed_id,
|
||||
'email' => $this->email,
|
||||
'first_name' => $this->first_name,
|
||||
'last_name' => $this->last_name,
|
||||
'phone' => $this->phone,
|
||||
'custom_value1' => $this->custom_value1,
|
||||
'custom_value2' => $this->custom_value2,
|
||||
'custom_value3' => $this->custom_value3,
|
||||
'custom_value4' => $this->custom_value4,
|
||||
'email' => (string)$this->email,
|
||||
'first_name' => (string)$this->first_name,
|
||||
'last_name' => (string)$this->last_name,
|
||||
'phone' => (string)$this->phone,
|
||||
'custom_value1' => (string)$this->custom_value1,
|
||||
'custom_value2' => (string)$this->custom_value2,
|
||||
'custom_value3' => (string)$this->custom_value3,
|
||||
'custom_value4' => (string)$this->custom_value4,
|
||||
'company_key' => $this->company->company_key,
|
||||
'vendor_id' => $this->vendor->hashed_id,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ final class CreateRecurringInvoicesIndex implements MigrationInterface
|
|||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Force drop any existing indices to avoid mapping conflicts
|
||||
Index::dropIfExists('recurring_invoices_v2');
|
||||
Index::dropIfExists('recurring_invoices');
|
||||
|
||||
$mapping = [
|
||||
'properties' => [
|
||||
// Core recurring invoice fields
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ final class CreateVendorContactsIndex implements MigrationInterface
|
|||
'company_key' => ['type' => 'keyword'],
|
||||
'vendor_id' => ['type' => 'keyword'],
|
||||
'send_email' => ['type' => 'boolean'],
|
||||
'last_login' => ['type' => 'date'],
|
||||
]
|
||||
];
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue