mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-08 20:22:42 +01:00
Search
This commit is contained in:
parent
681c38e659
commit
783e18a54e
@ -15,7 +15,10 @@ class SchedulerController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
if (auth()->user()->company()->account->latest_version == '0.0.0') {
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
if ($user->company()->account->latest_version == '0.0.0') {
|
||||
return response()->json(['message' => ctrans('texts.scheduler_has_never_run')], 400);
|
||||
} else {
|
||||
return response()->json(['message' => ctrans('texts.scheduler_has_run')], 200);
|
||||
|
91
app/Http/Controllers/SearchController.php
Normal file
91
app/Http/Controllers/SearchController.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Http\Requests\Search\GenericSearchRequest;
|
||||
use App\Models\Invoice;
|
||||
|
||||
class SearchController extends Controller
|
||||
{
|
||||
public function __invoke(GenericSearchRequest $request)
|
||||
{
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
// $search = collect([Client::class, Invoice::class])->each(function )
|
||||
return response()->json([
|
||||
'clients' => $this->clientMap($user),
|
||||
'client_contacts' => $this->clientContactMap($user),
|
||||
'invoices' => $this->invoiceMap($user),
|
||||
], 200);
|
||||
|
||||
}
|
||||
|
||||
private function clientMap(User $user) {
|
||||
|
||||
return Client::query()
|
||||
->company()
|
||||
->when($user->cannot('view_all') || $user->cannot('view_client'), function ($query) use($user) {
|
||||
$query->where('user_id', $user->id);
|
||||
})
|
||||
->cursor()
|
||||
->map(function ($client){
|
||||
return [
|
||||
'name' => $client->present()->name(),
|
||||
'type' => 'client',
|
||||
'id' => $client->hashed_id,
|
||||
'path' => "clients/{$client->hashed_id}/edit"
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
private function clientContactMap(User $user) {
|
||||
|
||||
return ClientContact::query()
|
||||
->company()
|
||||
->with('client')
|
||||
->when($user->cannot('view_all') || $user->cannot('view_client'), function ($query) use($user) {
|
||||
$query->where('user_id', $user->id);
|
||||
})
|
||||
->cursor()
|
||||
->map(function ($contact){
|
||||
return [
|
||||
'name' => $contact->present()->search_display(),
|
||||
'type' => 'client_contact',
|
||||
'id' => $contact->client->hashed_id,
|
||||
'path' => "clients/{$contact->client->hashed_id}"
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
private function invoiceMap(User $user) {
|
||||
|
||||
return Invoice::query()
|
||||
->company()
|
||||
->with('client')
|
||||
->when($user->cannot('view_all') || $user->cannot('view_invoice'), function ($query) use($user) {
|
||||
$query->where('user_id', $user->id);
|
||||
})
|
||||
->cursor()
|
||||
->map(function ($invoice){
|
||||
return [
|
||||
'name' => $invoice->client->present()->name() . ' - ' . $invoice->number,
|
||||
'type' => 'invoice',
|
||||
'id' => $invoice->hashed_id,
|
||||
'path' => "clients/{$invoice->hashed_id}/edit"
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
43
app/Http/Requests/Search/GenericSearchRequest.php
Normal file
43
app/Http/Requests/Search/GenericSearchRequest.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Search;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class GenericSearchRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = [
|
||||
'search' => 'bail|sometimes|string'
|
||||
];
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
@ -32,11 +32,16 @@ class ClientContactPresenter extends EntityPresenter
|
||||
|
||||
public function first_name()
|
||||
{
|
||||
return $this->entity->first_name ?: '';
|
||||
return $this->entity->first_name ?? '';
|
||||
}
|
||||
|
||||
public function last_name()
|
||||
{
|
||||
return $this->entity->last_name ?: '';
|
||||
return $this->entity->last_name ?? '';
|
||||
}
|
||||
|
||||
public function search_display()
|
||||
{
|
||||
return $this->name().' <'.$this->entity->email.'>' ?? '';
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +82,6 @@ class ClientService
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
|
||||
->selectRaw('SUM(payments.amount - payments.applied) as amount')->first()->amount ?? 0;
|
||||
// ->sum(DB::Raw('amount - applied')->getValue(DB::connection()->getQueryGrammar()));
|
||||
|
||||
DB::connection(config('database.default'))->transaction(function () use ($amount) {
|
||||
$this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first();
|
||||
|
@ -111,6 +111,7 @@ use App\Http\Controllers\Reports\ClientContactReportController;
|
||||
use App\Http\Controllers\Reports\PurchaseOrderReportController;
|
||||
use App\Http\Controllers\Reports\RecurringInvoiceReportController;
|
||||
use App\Http\Controllers\Reports\PurchaseOrderItemReportController;
|
||||
use App\Http\Controllers\SearchController;
|
||||
|
||||
Route::group(['middleware' => ['throttle:api', 'api_secret_check']], function () {
|
||||
Route::post('api/v1/signup', [AccountController::class, 'store'])->name('signup.submit');
|
||||
@ -317,7 +318,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
|
||||
Route::post('reports/tax_summary_report', TaxSummaryReportController::class);
|
||||
Route::post('reports/user_sales_report', UserSalesReportController::class);
|
||||
Route::post('reports/preview/{hash}', ReportPreviewController::class);
|
||||
|
||||
Route::post('search', SearchController::class);
|
||||
|
||||
Route::resource('task_schedulers', TaskSchedulerController::class);
|
||||
Route::post('task_schedulers/bulk', [TaskSchedulerController::class, 'bulk'])->name('task_schedulers.bulk');
|
||||
|
61
tests/Feature/Search/SearchApiTest.php
Normal file
61
tests/Feature/Search/SearchApiTest.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace Tests\Feature\Search;
|
||||
|
||||
use Tests\TestCase;
|
||||
use Tests\MockAccountData;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @covers App\Http\Controllers\ActivityController
|
||||
*/
|
||||
class SearchApiTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
use MockAccountData;
|
||||
|
||||
protected function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class
|
||||
);
|
||||
|
||||
$this->withoutExceptionHandling();
|
||||
|
||||
}
|
||||
|
||||
public function testActivityEntity()
|
||||
{
|
||||
|
||||
$response = false;
|
||||
|
||||
$data = [];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/search', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
nlog($response->json());
|
||||
|
||||
}
|
||||
|
||||
}
|
285
tests/Unit/CheckDataTest.php
Normal file
285
tests/Unit/CheckDataTest.php
Normal file
@ -0,0 +1,285 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use Tests\TestCase;
|
||||
use App\Models\User;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Client;
|
||||
use App\Models\Account;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Paymentable;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Models\CompanyToken;
|
||||
use App\Models\ClientContact;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Factory\CompanyUserFactory;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
class CheckDataTest extends TestCase
|
||||
{
|
||||
protected $account;
|
||||
protected $user;
|
||||
protected $company;
|
||||
protected $cu;
|
||||
protected $token;
|
||||
protected $client;
|
||||
protected $faker;
|
||||
/**
|
||||
* Important consideration with Base64
|
||||
* encoding checks.
|
||||
*
|
||||
* No method can guarantee against false positives.
|
||||
*/
|
||||
protected function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->faker = \Faker\Factory::create();
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
private function buildData()
|
||||
{
|
||||
$this->account = Account::factory()->create([
|
||||
'hosted_client_count' => 1000,
|
||||
'hosted_company_count' => 1000,
|
||||
]);
|
||||
|
||||
$this->account->num_users = 3;
|
||||
$this->account->save();
|
||||
|
||||
$this->user = User::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
'confirmation_code' => 'xyz123',
|
||||
'email' => $this->faker->unique()->safeEmail(),
|
||||
]);
|
||||
|
||||
$settings = CompanySettings::defaults();
|
||||
$settings->client_online_payment_notification = false;
|
||||
$settings->client_manual_payment_notification = false;
|
||||
|
||||
$this->company = Company::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
'settings' => $settings,
|
||||
]);
|
||||
|
||||
$this->company->settings = $settings;
|
||||
$this->company->save();
|
||||
|
||||
$this->cu = CompanyUserFactory::create($this->user->id, $this->company->id, $this->account->id);
|
||||
$this->cu->is_owner = true;
|
||||
$this->cu->is_admin = true;
|
||||
$this->cu->is_locked = false;
|
||||
$this->cu->save();
|
||||
|
||||
$this->token = \Illuminate\Support\Str::random(64);
|
||||
|
||||
$company_token = new CompanyToken;
|
||||
$company_token->user_id = $this->user->id;
|
||||
$company_token->company_id = $this->company->id;
|
||||
$company_token->account_id = $this->account->id;
|
||||
$company_token->name = 'test token';
|
||||
$company_token->token = $this->token;
|
||||
$company_token->is_system = true;
|
||||
|
||||
$company_token->save();
|
||||
|
||||
$this->client = Client::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'company_id' => $this->company->id,
|
||||
'is_deleted' => 0,
|
||||
'name' => 'bob',
|
||||
'address1' => '1234',
|
||||
'balance' => 100,
|
||||
'paid_to_date' => 50,
|
||||
]);
|
||||
|
||||
ClientContact::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $this->client->id,
|
||||
'company_id' => $this->company->id,
|
||||
'is_primary' => 1,
|
||||
'first_name' => 'john',
|
||||
'last_name' => 'doe',
|
||||
'email' => 'john@doe.com'
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
public function testDbQueriesRaw5()
|
||||
{
|
||||
$this->buildData();
|
||||
|
||||
$i = Invoice::factory()->count(5)->create([
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $this->client->id,
|
||||
'company_id' => $this->company->id,
|
||||
]);
|
||||
|
||||
Invoice::where('status_id', 2)->cursor()->each(function ($i) {
|
||||
|
||||
$i->service()->markPaid()->save();
|
||||
|
||||
});
|
||||
|
||||
Payment::with('paymentables')->cursor()->each(function($payment){
|
||||
$this->assertNotNull($payment->paymentables()->where('paymentable_type', \App\Models\Credit::class)->get()
|
||||
->sum(\DB::raw('amount')->getValue(\DB::connection()->getQueryGrammar())));
|
||||
});
|
||||
|
||||
Payment::with('paymentables')->cursor()->each(function ($payment) {
|
||||
$this->assertNotNull($payment->paymentables()->where('paymentable_type', \App\Models\Credit::class)->get()
|
||||
->sum('amount'));
|
||||
});
|
||||
|
||||
$amount = Paymentable::first()->payment->paymentables()->where('paymentable_type', 'invnoices')->get()->sum('amount');
|
||||
|
||||
$this->assertNotNull($amount);
|
||||
|
||||
}
|
||||
|
||||
public function testDbQueriesRaw4()
|
||||
{
|
||||
$this->buildData();
|
||||
|
||||
ClientContact::factory()->count(10)->create([
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $this->client->id,
|
||||
'company_id' => $this->company->id,
|
||||
]);
|
||||
|
||||
$clients_refactor = \DB::table('clients')
|
||||
->leftJoin('client_contacts', function ($join){
|
||||
$join->on('client_contacts.client_id', '=', 'clients.id');
|
||||
})
|
||||
->get(['clients.id', \DB::raw('count(client_contacts.id) as contact_count')]);
|
||||
|
||||
// $this->assertNotNull($clients);
|
||||
$this->assertNotNull($clients_refactor);
|
||||
|
||||
}
|
||||
|
||||
public function testDbQueriesRaw3()
|
||||
{
|
||||
$this->buildData();
|
||||
|
||||
User::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
'email' => $this->faker->unique()->safeEmail(),
|
||||
]);
|
||||
|
||||
User::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
'email' => $this->faker->unique()->safeEmail(),
|
||||
]);
|
||||
|
||||
$user_hash = 'a';
|
||||
|
||||
$user_count = User::where('account_id', $this->company->account->id)
|
||||
->where(
|
||||
\DB::raw('CONCAT_WS(" ", first_name, last_name)'),
|
||||
'like',
|
||||
'%'.$user_hash.'%'
|
||||
)
|
||||
->get();
|
||||
|
||||
$user_count_refactor = User::whereRaw("account_id = ? AND CONCAT_WS(' ', first_name, last_name) like ?", [$this->company->account_id, '%'.$user_hash.'%'])
|
||||
->get();
|
||||
|
||||
|
||||
$this->assertEquals($user_count_refactor->count(), $user_count->count());
|
||||
}
|
||||
|
||||
public function testDbRawQueries1()
|
||||
{
|
||||
$this->buildData();
|
||||
|
||||
$results = \DB::select(\DB::raw("
|
||||
SELECT count(clients.id) as count
|
||||
FROM clients
|
||||
")->getValue(\DB::connection()->getQueryGrammar()));
|
||||
|
||||
|
||||
$refactored = \DB::select("
|
||||
SELECT count(clients.id) as count
|
||||
FROM clients
|
||||
");
|
||||
|
||||
$this->assertEquals($refactored[0]->count, $results[0]->count);
|
||||
|
||||
}
|
||||
|
||||
public function testDbRawQueries2()
|
||||
{
|
||||
$this->buildData();
|
||||
|
||||
Payment::factory()->count(5)->create([
|
||||
'client_id' => $this->client->id,
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
Invoice::factory()->count(5)->create([
|
||||
'client_id' => $this->client->id,
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
Invoice::where('status_id', 2)->cursor()->each(function ($i) {
|
||||
|
||||
$i->service()->markPaid()->save();
|
||||
|
||||
});
|
||||
|
||||
$results = \DB::select(\DB::raw("
|
||||
SELECT
|
||||
SUM(payments.amount) as amount
|
||||
FROM payments
|
||||
LEFT JOIN paymentables
|
||||
ON
|
||||
payments.id = paymentables.payment_id
|
||||
WHERE paymentable_type = ?
|
||||
AND paymentables.deleted_at is NULL
|
||||
AND paymentables.amount > 0
|
||||
AND payments.is_deleted = 0
|
||||
AND payments.client_id = ?;
|
||||
")->getValue(\DB::connection()->getQueryGrammar()), ['invoices', $this->client->id]);
|
||||
|
||||
$refactored = \DB::select("
|
||||
SELECT
|
||||
SUM(payments.amount) as amount
|
||||
FROM payments
|
||||
LEFT JOIN paymentables
|
||||
ON
|
||||
payments.id = paymentables.payment_id
|
||||
WHERE paymentable_type = ?
|
||||
AND paymentables.deleted_at is NULL
|
||||
AND paymentables.amount > 0
|
||||
AND payments.is_deleted = 0
|
||||
AND payments.client_id = ?;
|
||||
", ['invoices', $this->client->id]);
|
||||
|
||||
$this->assertEquals($refactored[0]->amount, $results[0]->amount);
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user