1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-08 20:22:42 +01:00
This commit is contained in:
David Bomba 2023-09-04 11:04:51 +10:00
parent 681c38e659
commit 783e18a54e
8 changed files with 493 additions and 5 deletions

View File

@ -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);

View 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"
];
});
}
}

View 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);
}
}

View File

@ -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.'>' ?? '';
}
}

View File

@ -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();

View File

@ -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');

View 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());
}
}

View 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);
}
}