From acb756b2291aa1bd8ca9abf1797fae2038608f51 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 21 Aug 2022 09:13:06 +1000 Subject: [PATCH 01/15] Additions for hosted --- .../Requests/Expense/StoreExpenseRequest.php | 19 +++- .../Requests/Expense/UpdateExpenseRequest.php | 30 ++++--- .../Ninja/UserQualityNotification.php | 89 +++++++++++++++++++ app/Observers/UserObserver.php | 6 +- 4 files changed, 128 insertions(+), 16 deletions(-) create mode 100644 app/Notifications/Ninja/UserQualityNotification.php diff --git a/app/Http/Requests/Expense/StoreExpenseRequest.php b/app/Http/Requests/Expense/StoreExpenseRequest.php index 90b0734a41..b08ee31f5d 100644 --- a/app/Http/Requests/Expense/StoreExpenseRequest.php +++ b/app/Http/Requests/Expense/StoreExpenseRequest.php @@ -14,6 +14,7 @@ namespace App\Http\Requests\Expense; use App\Http\Requests\Request; use App\Http\ValidationRules\Expense\UniqueExpenseNumberRule; use App\Models\Expense; +use App\Models\Project; use App\Models\PurchaseOrder; use App\Utils\Traits\MakesHash; use Illuminate\Validation\Rule; @@ -65,13 +66,29 @@ class StoreExpenseRequest extends Request $input['color'] = ''; } + + /* Ensure the project is related */ + if (array_key_exists('project_id', $input) && isset($input['project_id'])) { + $project = Project::withTrashed()->find($input['project_id'])->company()->first(); + + if($project){ + $input['client_id'] = $project->client_id; + } + else + { + unset($input['project_id']); + } + + } + + $this->replace($input); } public function messages() { return [ - 'unique' => ctrans('validation.unique', ['attribute' => 'email']), + // 'unique' => ctrans('validation.unique', ['attribute' => 'number']), ]; } } diff --git a/app/Http/Requests/Expense/UpdateExpenseRequest.php b/app/Http/Requests/Expense/UpdateExpenseRequest.php index e1e0ad0944..aabe07f659 100644 --- a/app/Http/Requests/Expense/UpdateExpenseRequest.php +++ b/app/Http/Requests/Expense/UpdateExpenseRequest.php @@ -12,6 +12,7 @@ namespace App\Http\Requests\Expense; use App\Http\Requests\Request; +use App\Models\Project; use App\Utils\Traits\ChecksEntityStatus; use App\Utils\Traits\MakesHash; use Illuminate\Validation\Rule; @@ -35,10 +36,7 @@ class UpdateExpenseRequest extends Request { /* Ensure we have a client name, and that all emails are unique*/ $rules = []; - // $rules['country_id'] = 'integer|nullable'; - - // $rules['contacts.*.email'] = 'nullable|distinct'; - + if (isset($this->number)) { $rules['number'] = Rule::unique('expenses')->where('company_id', auth()->user()->company()->id)->ignore($this->expense->id); } @@ -46,16 +44,6 @@ class UpdateExpenseRequest extends Request return $this->globalRules($rules); } - public function messages() - { - return [ - 'unique' => ctrans('validation.unique', ['attribute' => 'email']), - 'email' => ctrans('validation.email', ['attribute' => 'email']), - 'name.required' => ctrans('validation.required', ['attribute' => 'name']), - 'required' => ctrans('validation.required', ['attribute' => 'email']), - ]; - } - public function prepareForValidation() { $input = $this->all(); @@ -74,6 +62,20 @@ class UpdateExpenseRequest extends Request $input['currency_id'] = (string) auth()->user()->company()->settings->currency_id; } + /* Ensure the project is related */ + if (array_key_exists('project_id', $input) && isset($input['project_id'])) { + $project = Project::withTrashed()->find($input['project_id'])->company()->first(); + + if($project){ + $input['client_id'] = $project->client_id; + } + else + { + unset($input['project_id']); + } + + } + $this->replace($input); } } diff --git a/app/Notifications/Ninja/UserQualityNotification.php b/app/Notifications/Ninja/UserQualityNotification.php new file mode 100644 index 0000000000..a90ce15e9b --- /dev/null +++ b/app/Notifications/Ninja/UserQualityNotification.php @@ -0,0 +1,89 @@ +user = $user; + $this->account_key = $account_key; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via($notifiable) + { + return ['slack']; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return MailMessage + */ + public function toMail($notifiable) + { + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * @return array + */ + public function toArray($notifiable) + { + return [ + // + ]; + } + + public function toSlack($notifiable) + { + + $content = "User Quality notification {$this->user->present()->name()} \n"; + $content .= "Account: {$this->account_key }\n"; + + return (new SlackMessage) + ->success() + ->from(ctrans('texts.notification_bot')) + ->image('https://app.invoiceninja.com/favicon.png') + ->content($content); + } +} diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index 115cd66f0a..c6bdf94d42 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -23,7 +23,8 @@ class UserObserver */ public function created(User $user) { - // + if(class_exists(\Modules\Admin\Jobs\Account\UserQuality::class)) + (new \Modules\Admin\Jobs\Account\UserQuality($user, $user->account->key))->run(); } /** @@ -34,6 +35,9 @@ class UserObserver */ public function updated(User $user) { + + if(class_exists(\Modules\Admin\Jobs\Account\UserQuality::class)) + (new \Modules\Admin\Jobs\Account\UserQuality($user, $user->account->key))->run(); } /** From 5e8bcc04dc54545dec57a93a5fa0a45365f4c540 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 21 Aug 2022 09:26:58 +1000 Subject: [PATCH 02/15] Update user observer to dispatch a job --- app/Observers/UserObserver.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index c6bdf94d42..64e7e69bd0 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -23,8 +23,10 @@ class UserObserver */ public function created(User $user) { - if(class_exists(\Modules\Admin\Jobs\Account\UserQuality::class)) - (new \Modules\Admin\Jobs\Account\UserQuality($user, $user->account->key))->run(); + + if(class_exists(\Modules\Admin\Jobs\Account\UserQuality::class)) + \Modules\Admin\Jobs\Account\UserQuality::dispatch($user, $user->account->key); + } /** @@ -36,8 +38,9 @@ class UserObserver public function updated(User $user) { - if(class_exists(\Modules\Admin\Jobs\Account\UserQuality::class)) - (new \Modules\Admin\Jobs\Account\UserQuality($user, $user->account->key))->run(); + if(class_exists(\Modules\Admin\Jobs\Account\UserQuality::class)) + \Modules\Admin\Jobs\Account\UserQuality::dispatch($user, $user->account->key); + } /** From c1f5b5a629aa2cd45aea11173b4bf921764f07b4 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 21 Aug 2022 15:16:55 +1000 Subject: [PATCH 03/15] Set relative path for canvaskit --- resources/views/index/index.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/index/index.blade.php b/resources/views/index/index.blade.php index 2c26b35c7b..f66926c193 100644 --- a/resources/views/index/index.blade.php +++ b/resources/views/index/index.blade.php @@ -21,7 +21,7 @@ From 6862fabbe0c945104b6d7f99b3e689dddbb486ba Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 21 Aug 2022 15:33:32 +1000 Subject: [PATCH 04/15] Clean up for mail listener --- app/Listeners/Mail/MailSentListener.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Listeners/Mail/MailSentListener.php b/app/Listeners/Mail/MailSentListener.php index f2cbcd736b..daf79ac9e8 100644 --- a/app/Listeners/Mail/MailSentListener.php +++ b/app/Listeners/Mail/MailSentListener.php @@ -42,13 +42,16 @@ class MailSentListener implements ShouldQueue */ public function handle(MessageSent $event) { - if(!Ninja::isHosted()); + if(!Ninja::isHosted()) return; $message_id = $event->sent->getMessageId(); $message = MessageConverter::toEmail($event->sent->getOriginalMessage()); + if(!$message->getHeaders()->get('x-invitation')) + return; + $invitation_key = $message->getHeaders()->get('x-invitation')->getValue(); if($message_id && $invitation_key) From 77d489211e334f7a863a97463a4d18cfbd18bfd4 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 22 Aug 2022 07:53:15 +1000 Subject: [PATCH 05/15] Fixes for task requests --- app/Http/Requests/Task/StoreTaskRequest.php | 4 ++-- app/Http/Requests/Task/UpdateTaskRequest.php | 2 +- app/Utils/Number.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Http/Requests/Task/StoreTaskRequest.php b/app/Http/Requests/Task/StoreTaskRequest.php index 8d0d7af7b8..f73a7cb5fc 100644 --- a/app/Http/Requests/Task/StoreTaskRequest.php +++ b/app/Http/Requests/Task/StoreTaskRequest.php @@ -70,8 +70,8 @@ class StoreTaskRequest extends Request /* Ensure the project is related */ if (array_key_exists('project_id', $input) && isset($input['project_id'])) { - $project = Project::withTrashed()->find($input['project_id'])->company()->first(); - + $project = Project::withTrashed()->where('id', $input['project_id'])->company()->first(); +; if($project){ $input['client_id'] = $project->client_id; } diff --git a/app/Http/Requests/Task/UpdateTaskRequest.php b/app/Http/Requests/Task/UpdateTaskRequest.php index c60924df0b..7e4babdb3f 100644 --- a/app/Http/Requests/Task/UpdateTaskRequest.php +++ b/app/Http/Requests/Task/UpdateTaskRequest.php @@ -69,7 +69,7 @@ class UpdateTaskRequest extends Request /* Ensure the project is related */ if (array_key_exists('project_id', $input) && isset($input['project_id'])) { - $project = Project::withTrashed()->find($input['project_id'])->company()->first(); + $project = Project::withTrashed()->where('id', $input['project_id'])->company()->first(); if($project){ $input['client_id'] = $project->client_id; diff --git a/app/Utils/Number.php b/app/Utils/Number.php index 2aa06e0f97..e7e0c66673 100644 --- a/app/Utils/Number.php +++ b/app/Utils/Number.php @@ -90,7 +90,7 @@ class Number return (float) $s; } - // remove all seperators from first part and keep the end + // remove all separators from first part and keep the end $s = str_replace('.', '', substr($s, 0, -3)).substr($s, -3); // return float From 5070b2745e237e68f86dd7bfd513dacec089d934 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 22 Aug 2022 08:24:36 +1000 Subject: [PATCH 06/15] Adjust credit balance on client record --- app/Services/Credit/CreditService.php | 1 + app/Services/Credit/MarkSent.php | 5 ++ tests/Feature/ClientTest.php | 68 +++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) diff --git a/app/Services/Credit/CreditService.php b/app/Services/Credit/CreditService.php index eeac8f4047..092f6ef985 100644 --- a/app/Services/Credit/CreditService.php +++ b/app/Services/Credit/CreditService.php @@ -142,6 +142,7 @@ class CreditService $client = $this->credit->client->fresh(); $client->service() ->updatePaidToDate($adjustment) + ->adjustCreditBalance($adjustment * -1) ->save(); event('eloquent.created: App\Models\Payment', $payment); diff --git a/app/Services/Credit/MarkSent.php b/app/Services/Credit/MarkSent.php index 8773c939c5..40daa3edc3 100644 --- a/app/Services/Credit/MarkSent.php +++ b/app/Services/Credit/MarkSent.php @@ -45,6 +45,11 @@ class MarkSent ->touchPdf() ->save(); + $this->client + ->service() + ->adjustCreditBalance($this->credit->amount) + ->save(); + event(new CreditWasMarkedSent($this->credit, $this->credit->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); return $this->credit; diff --git a/tests/Feature/ClientTest.php b/tests/Feature/ClientTest.php index 934bd32418..e17378dee5 100644 --- a/tests/Feature/ClientTest.php +++ b/tests/Feature/ClientTest.php @@ -13,11 +13,13 @@ namespace Tests\Feature; use App\DataMapper\CompanySettings; use App\DataMapper\DefaultSettings; +use App\Factory\InvoiceItemFactory; use App\Models\Account; use App\Models\Client; use App\Models\ClientContact; use App\Models\Company; use App\Models\CompanyToken; +use App\Models\Credit; use App\Models\User; use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\Model; @@ -61,6 +63,72 @@ class ClientTest extends TestCase $this->makeTestData(); } + private function buildLineItems() + { + $line_items = []; + + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 10; + + $line_items[] = $item; + + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 10; + + $line_items[] = $item; + + return $line_items; + } + + public function testCreditBalance() + { + $this->client->credit_balance = 0; + $this->client->save(); + + $this->assertEquals(0, $this->client->credit_balance); + + $credit = [ + 'status_id' => 1, + 'number' => 'dfdfd', + 'discount' => 0, + 'is_amount_discount' => 1, + 'number' => '34343xx43', + 'public_notes' => 'notes', + 'is_deleted' => 0, + 'custom_value1' => 0, + 'custom_value2' => 0, + 'custom_value3' => 0, + 'custom_value4' => 0, + 'status' => 1, + 'client_id' => $this->encodePrimaryKey($this->client->id), + 'line_items' => $this->buildLineItems() + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/credits/', $credit) + ->assertStatus(200); + + $arr = $response->json(); + + $credit_id = $arr['data']['id']; + + $credit = Credit::find($this->decodePrimaryKey($credit_id)); + + $this->assertNotNull($credit); + + $this->assertEquals(0, $credit->balance); + + $credit->service()->markSent()->save(); + + $this->assertEquals(20, $credit->balance); + $this->assertEquals(20, $credit->client->fresh()->credit_balance); + + } + public function testStoreClientUsingCountryCode() { $data = [ From 59e3ab999388974b3cc385690b9b4a61f61457b6 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 22 Aug 2022 08:48:52 +1000 Subject: [PATCH 07/15] Working on client credit balance field --- app/Repositories/BaseRepository.php | 5 +++ tests/Feature/ClientTest.php | 47 ++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/app/Repositories/BaseRepository.php b/app/Repositories/BaseRepository.php index 3d1f623e5d..969bfc5736 100644 --- a/app/Repositories/BaseRepository.php +++ b/app/Repositories/BaseRepository.php @@ -339,6 +339,11 @@ class BaseRepository else event('eloquent.updated: App\Models\Credit', $model); + if (($state['finished_amount'] != $state['starting_amount']) && ($model->status_id != Credit::STATUS_DRAFT)) { + + $model->client->service()->adjustCreditBalance(($state['finished_amount'] - $state['starting_amount']))->save(); + } + } if ($model instanceof Quote) { diff --git a/tests/Feature/ClientTest.php b/tests/Feature/ClientTest.php index e17378dee5..e321b29c6a 100644 --- a/tests/Feature/ClientTest.php +++ b/tests/Feature/ClientTest.php @@ -63,21 +63,18 @@ class ClientTest extends TestCase $this->makeTestData(); } - private function buildLineItems() + private function buildLineItems($number = 2) { $line_items = []; - $item = InvoiceItemFactory::create(); - $item->quantity = 1; - $item->cost = 10; + for($x=0; $x<$number; $x++) + { + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 10; - $line_items[] = $item; - - $item = InvoiceItemFactory::create(); - $item->quantity = 1; - $item->cost = 10; - - $line_items[] = $item; + $line_items[] = $item; + } return $line_items; } @@ -127,6 +124,34 @@ class ClientTest extends TestCase $this->assertEquals(20, $credit->balance); $this->assertEquals(20, $credit->client->fresh()->credit_balance); + //lets now update the credit and increase its balance, this should also increase the credit balance + + $data = [ + 'number' => 'dfdfd', + 'discount' => 0, + 'is_amount_discount' => 1, + 'number' => '34343xx43', + 'public_notes' => 'notes', + 'is_deleted' => 0, + 'custom_value1' => 0, + 'custom_value2' => 0, + 'custom_value3' => 0, + 'custom_value4' => 0, + 'status' => 1, + 'client_id' => $this->encodePrimaryKey($this->client->id), + 'line_items' => $this->buildLineItems(3) + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->put('/api/v1/credits/'.$credit->hashed_id, $data) + ->assertStatus(200); + + $credit = $credit->fresh(); + + $this->assertEquals(30, $credit->balance); + $this->assertEquals(30, $credit->client->fresh()->credit_balance); } public function testStoreClientUsingCountryCode() From b5f61d22a7f095bbea7019874abfc978c9416939 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 22 Aug 2022 10:27:11 +1000 Subject: [PATCH 08/15] Tests for credit balance --- app/Jobs/Credit/ApplyCreditPayment.php | 7 ++++ app/Repositories/CreditRepository.php | 27 ++++++++++++++ app/Repositories/PaymentRepository.php | 4 +-- app/Services/Credit/CreditService.php | 23 ++++++++++++ tests/Unit/CreditBalanceTest.php | 50 ++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 2 deletions(-) diff --git a/app/Jobs/Credit/ApplyCreditPayment.php b/app/Jobs/Credit/ApplyCreditPayment.php index 8c3f671391..398e5a7eae 100644 --- a/app/Jobs/Credit/ApplyCreditPayment.php +++ b/app/Jobs/Credit/ApplyCreditPayment.php @@ -85,6 +85,13 @@ class ApplyCreditPayment implements ShouldQueue ->save(); } + //22-08-2022 + $this->credit + ->client + ->service() + ->adjustCreditBalance($this->amount * -1) + ->save(); + /* Update Payment Applied Amount*/ $this->payment->save(); } diff --git a/app/Repositories/CreditRepository.php b/app/Repositories/CreditRepository.php index d9876e4ba2..b506fda26b 100644 --- a/app/Repositories/CreditRepository.php +++ b/app/Repositories/CreditRepository.php @@ -43,4 +43,31 @@ class CreditRepository extends BaseRepository { return CreditInvitation::where('key', $key)->first(); } + + public function delete($credit) + { + if ($credit->is_deleted) { + return; + } + + $credit = $credit->service()->deleteCredit()->save(); + + return parent::restore($credit); + + } + + public function restore($credit) + { + //we cannot restore a deleted payment. + if ($credit->is_deleted) { + return; + } + + parent::restore($credit); + + $credit = $credit->service()->restoreCredit()->save(); + + return $credit; + } + } diff --git a/app/Repositories/PaymentRepository.php b/app/Repositories/PaymentRepository.php index 1bdb494294..833c58ba4f 100644 --- a/app/Repositories/PaymentRepository.php +++ b/app/Repositories/PaymentRepository.php @@ -166,7 +166,7 @@ class PaymentRepository extends BaseRepository { if ($credit) { $credit = $credit->service()->markSent()->save(); - ApplyCreditPayment::dispatchNow($credit, $payment, $paid_credit['amount'], $credit->company); + (new ApplyCreditPayment($credit, $payment, $paid_credit['amount'], $credit->company))->handle(); } } } @@ -245,7 +245,7 @@ class PaymentRepository extends BaseRepository { event(new PaymentWasDeleted($payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); return $payment; - //return parent::delete($payment); + } public function restore($payment) diff --git a/app/Services/Credit/CreditService.php b/app/Services/Credit/CreditService.php index 092f6ef985..4a1c5e52d2 100644 --- a/app/Services/Credit/CreditService.php +++ b/app/Services/Credit/CreditService.php @@ -257,6 +257,29 @@ class CreditService return $this; } + public function deleteCredit() + { + $this->credit + ->client + ->service() + ->adjustCreditBalance($this->credit->balance * -1) + ->save(); + + return $this; + } + + + public function restoreCredit() + { + $this->credit + ->client + ->service() + ->adjustCreditBalance($this->credit->balance) + ->save(); + + return $this; + } + /** * Saves the credit. * @return Credit object diff --git a/tests/Unit/CreditBalanceTest.php b/tests/Unit/CreditBalanceTest.php index ea780f8d5f..322ce031a3 100644 --- a/tests/Unit/CreditBalanceTest.php +++ b/tests/Unit/CreditBalanceTest.php @@ -70,4 +70,54 @@ class CreditBalanceTest extends TestCase $this->assertEquals($this->client->service()->getCreditBalance(), 0); } + + public function testCreditDeleteCheckClientBalance() + { + $credit = Credit::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'client_id' => $this->client->id, + 'balance' => 10, + 'number' => 'testing-number-01', + 'status_id' => Credit::STATUS_SENT, + ]); + + $credit->client->credit_balance = 10; + $credit->push(); + + + //delete invoice + $data = [ + 'ids' => [$credit->hashed_id], + ]; + + //restore invoice + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/credits/bulk?action=delete', $data)->assertStatus(200); + + $client = $credit->client->fresh(); + + $this->assertEquals(0, $client->credit_balance); + + //restore invoice + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/credits/bulk?action=restore', $data)->assertStatus(200); + + $client = $credit->client->fresh(); + + $this->assertEquals(10, $client->credit_balance); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/credits/bulk?action=archive', $data)->assertStatus(200); + + $client = $credit->client->fresh(); + + $this->assertEquals(10, $client->credit_balance); + } } From 35e21e45510d50db160fc4846c756420ea48f0d6 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 22 Aug 2022 10:44:36 +1000 Subject: [PATCH 09/15] Update credit balances when a payment is deleted --- app/Services/Payment/DeletePayment.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php index c4599abe12..6ce1065e0b 100644 --- a/app/Services/Payment/DeletePayment.php +++ b/app/Services/Payment/DeletePayment.php @@ -162,6 +162,7 @@ class DeletePayment $client ->service() ->updatePaidToDate(($paymentable_credit->pivot->amount) * -1) + ->adjustCreditBalance($paymentable_credit->pivot->amount) ->save(); }); } From b63b3c707e6d173203c2b6bfc86542fb3fffeea3 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 22 Aug 2022 11:07:11 +1000 Subject: [PATCH 10/15] Add daily checks for credit balances --- app/Jobs/Ninja/CompanySizeCheck.php | 13 +++++++++++++ app/Repositories/CreditRepository.php | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/Jobs/Ninja/CompanySizeCheck.php b/app/Jobs/Ninja/CompanySizeCheck.php index cc9f3f50bb..4a365a7dad 100644 --- a/app/Jobs/Ninja/CompanySizeCheck.php +++ b/app/Jobs/Ninja/CompanySizeCheck.php @@ -12,6 +12,7 @@ namespace App\Jobs\Ninja; use App\Libraries\MultiDB; +use App\Models\Client; use App\Models\Company; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -61,5 +62,17 @@ class CompanySizeCheck implements ShouldQueue $company->account->companies()->update(['is_large' => true]); } }); + + nlog("updating all client credit balances"); + + Client::where('updated_at', '>', now()->subDay()) + ->cursor() + ->each(function ($client){ + + $client->credit_balance = $client->service()->getCreditBalance(); + $client->save(); + + }); + } } diff --git a/app/Repositories/CreditRepository.php b/app/Repositories/CreditRepository.php index b506fda26b..66edc9268a 100644 --- a/app/Repositories/CreditRepository.php +++ b/app/Repositories/CreditRepository.php @@ -52,7 +52,7 @@ class CreditRepository extends BaseRepository $credit = $credit->service()->deleteCredit()->save(); - return parent::restore($credit); + return parent::delete($credit); } From b99e6231007596d126620e23cae1136a8a5fee48 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 22 Aug 2022 11:21:43 +1000 Subject: [PATCH 11/15] fixes for restoring a credit --- app/Repositories/CreditRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Repositories/CreditRepository.php b/app/Repositories/CreditRepository.php index 66edc9268a..da752f8fbf 100644 --- a/app/Repositories/CreditRepository.php +++ b/app/Repositories/CreditRepository.php @@ -59,7 +59,7 @@ class CreditRepository extends BaseRepository public function restore($credit) { //we cannot restore a deleted payment. - if ($credit->is_deleted) { + if (! $credit->trashed()) { return; } From 4e3f52a1eeecffc65cd3dfc253f2fbc24c842c76 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 22 Aug 2022 13:24:33 +1000 Subject: [PATCH 12/15] Refactor for payments to improve query efficiency --- app/Http/Requests/Payment/StorePaymentRequest.php | 4 +++- app/Http/ValidationRules/Credit/ValidCreditsRules.php | 5 ++++- app/Http/ValidationRules/Payment/ValidInvoicesRules.php | 8 +++++++- app/Jobs/Ninja/CompanySizeCheck.php | 2 ++ app/Repositories/PaymentRepository.php | 7 +++++-- tests/Feature/Import/ImportCompanyTest.php | 2 +- 6 files changed, 22 insertions(+), 6 deletions(-) diff --git a/app/Http/Requests/Payment/StorePaymentRequest.php b/app/Http/Requests/Payment/StorePaymentRequest.php index 3341ddf678..7fc64f69c1 100644 --- a/app/Http/Requests/Payment/StorePaymentRequest.php +++ b/app/Http/Requests/Payment/StorePaymentRequest.php @@ -68,7 +68,9 @@ class StorePaymentRequest extends Request if (isset($input['credits']) && is_array($input['credits']) !== false) { foreach ($input['credits'] as $key => $value) { if (array_key_exists('credit_id', $input['credits'][$key])) { - $input['credits'][$key]['credit_id'] = $value['credit_id']; + // $input['credits'][$key]['credit_id'] = $value['credit_id']; + $input['credits'][$key]['credit_id'] = $this->decodePrimaryKey($value['credit_id']); + $credits_total += $value['amount']; } } diff --git a/app/Http/ValidationRules/Credit/ValidCreditsRules.php b/app/Http/ValidationRules/Credit/ValidCreditsRules.php index f80b24e5bf..f745092fe9 100644 --- a/app/Http/ValidationRules/Credit/ValidCreditsRules.php +++ b/app/Http/ValidationRules/Credit/ValidCreditsRules.php @@ -50,11 +50,14 @@ class ValidCreditsRules implements Rule } $unique_array = []; + + $cred_collection = Credit::withTrashed()->whereIn('id', array_column($this->input['credits'], 'credit_id'))->get(); foreach ($this->input['credits'] as $credit) { $unique_array[] = $credit['credit_id']; - $cred = Credit::find($this->decodePrimaryKey($credit['credit_id'])); + // $cred = Credit::find($this->decodePrimaryKey($credit['credit_id'])); + $cred = $cred_collection->firstWhere('id', $credit['credit_id']); if (! $cred) { $this->error_msg = ctrans('texts.credit_not_found'); diff --git a/app/Http/ValidationRules/Payment/ValidInvoicesRules.php b/app/Http/ValidationRules/Payment/ValidInvoicesRules.php index a3360fcd9c..d432a42272 100644 --- a/app/Http/ValidationRules/Payment/ValidInvoicesRules.php +++ b/app/Http/ValidationRules/Payment/ValidInvoicesRules.php @@ -51,6 +51,9 @@ class ValidInvoicesRules implements Rule $unique_array = []; + ///// + $inv_collection = Invoice::withTrashed()->whereIn('id', array_column($this->input['invoices'], 'invoice_id'))->get(); + //todo optimize this into a single query foreach ($this->input['invoices'] as $invoice) { $unique_array[] = $invoice['invoice_id']; @@ -61,7 +64,10 @@ class ValidInvoicesRules implements Rule return false; } - $inv = Invoice::withTrashed()->whereId($invoice['invoice_id'])->first(); + ///// + $inv = $inv_collection->firstWhere('id', $invoice['invoice_id']); + + // $inv = Invoice::withTrashed()->whereId($invoice['invoice_id'])->first(); if (! $inv) { $this->error_msg = ctrans('texts.invoice_not_found'); diff --git a/app/Jobs/Ninja/CompanySizeCheck.php b/app/Jobs/Ninja/CompanySizeCheck.php index 4a365a7dad..c47d3cb652 100644 --- a/app/Jobs/Ninja/CompanySizeCheck.php +++ b/app/Jobs/Ninja/CompanySizeCheck.php @@ -55,6 +55,8 @@ class CompanySizeCheck implements ShouldQueue private function check() { + nlog("Checking all company sizes"); + Company::where('is_large', false)->withCount(['invoices', 'clients', 'products'])->cursor()->each(function ($company) { if ($company->invoices_count > 500 || $company->products_count > 500 || $company->clients_count > 500) { nlog("Marking company {$company->id} as large"); diff --git a/app/Repositories/PaymentRepository.php b/app/Repositories/PaymentRepository.php index 833c58ba4f..916267f8ba 100644 --- a/app/Repositories/PaymentRepository.php +++ b/app/Repositories/PaymentRepository.php @@ -157,12 +157,15 @@ class PaymentRepository extends BaseRepository { if (array_key_exists('credits', $data) && is_array($data['credits'])) { $credit_totals = array_sum(array_column($data['credits'], 'amount')); - $credits = Credit::whereIn('id', $this->transformKeys(array_column($data['credits'], 'credit_id')))->get(); + // $credits = Credit::whereIn('id', $this->transformKeys(array_column($data['credits'], 'credit_id')))->get(); + + $credits = Credit::whereIn('id', array_column($data['credits'], 'credit_id'))->get(); + $payment->credits()->saveMany($credits); //todo optimize into a single query foreach ($data['credits'] as $paid_credit) { - $credit = Credit::withTrashed()->find($this->decodePrimaryKey($paid_credit['credit_id'])); + $credit = Credit::withTrashed()->find($paid_credit['credit_id']); if ($credit) { $credit = $credit->service()->markSent()->save(); diff --git a/tests/Feature/Import/ImportCompanyTest.php b/tests/Feature/Import/ImportCompanyTest.php index 9d5d7f30df..30f66317a5 100644 --- a/tests/Feature/Import/ImportCompanyTest.php +++ b/tests/Feature/Import/ImportCompanyTest.php @@ -78,7 +78,7 @@ class ImportCompanyTest extends TestCase { parent::setUp(); - $this->artisan('db:seed'); + // $this->artisan('db:seed'); $this->withoutMiddleware( ThrottleRequests::class From 2c8d25eeb39a2f97f820fa9c1c10452bf0aebae1 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 22 Aug 2022 13:49:07 +1000 Subject: [PATCH 13/15] Refactor payment queries for improved efficiency --- app/Repositories/PaymentRepository.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/Repositories/PaymentRepository.php b/app/Repositories/PaymentRepository.php index 916267f8ba..885df9fba0 100644 --- a/app/Repositories/PaymentRepository.php +++ b/app/Repositories/PaymentRepository.php @@ -139,7 +139,8 @@ class PaymentRepository extends BaseRepository { //todo optimize this into a single query foreach ($data['invoices'] as $paid_invoice) { - $invoice = Invoice::withTrashed()->whereId($paid_invoice['invoice_id'])->first(); + // $invoice = Invoice::withTrashed()->whereId($paid_invoice['invoice_id'])->first(); + $invoice = $invoices->firstWhere('id', $paid_invoice['invoice_id']); if ($invoice) { $invoice = $invoice->service() @@ -165,8 +166,9 @@ class PaymentRepository extends BaseRepository { //todo optimize into a single query foreach ($data['credits'] as $paid_credit) { - $credit = Credit::withTrashed()->find($paid_credit['credit_id']); - + // $credit = Credit::withTrashed()->find($paid_credit['credit_id']); + $credit = $credits->firstWhere('id', $paid_credit['credit_id']); + if ($credit) { $credit = $credit->service()->markSent()->save(); (new ApplyCreditPayment($credit, $payment, $paid_credit['amount'], $credit->company))->handle(); From 16a1c65354aa4132fc94a82c6c63fa7c61663215 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 22 Aug 2022 15:05:02 +1000 Subject: [PATCH 14/15] valid credits rules --- app/Http/Requests/Payment/StorePaymentRequest.php | 5 +---- app/Http/Requests/Payment/UpdatePaymentRequest.php | 10 +++++++++- .../ValidationRules/ValidCreditsPresentRule.php | 14 ++++++++++---- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/app/Http/Requests/Payment/StorePaymentRequest.php b/app/Http/Requests/Payment/StorePaymentRequest.php index 7fc64f69c1..94b873f461 100644 --- a/app/Http/Requests/Payment/StorePaymentRequest.php +++ b/app/Http/Requests/Payment/StorePaymentRequest.php @@ -76,9 +76,6 @@ class StorePaymentRequest extends Request } } - // if (array_key_exists('amount', $input)) - // $input['amount'] = 0; - if (isset($input['credits']) && is_array($input['credits']) === false) { $input['credits'] = null; } @@ -99,7 +96,7 @@ class StorePaymentRequest extends Request public function rules() { $rules = [ - 'amount' => ['numeric', 'bail', new PaymentAmountsBalanceRule(), new ValidCreditsPresentRule()], + 'amount' => ['numeric', 'bail', new PaymentAmountsBalanceRule(), new ValidCreditsPresentRule($this->all())], 'client_id' => 'bail|required|exists:clients,id', 'invoices.*.invoice_id' => 'bail|required|distinct|exists:invoices,id', 'invoices.*.amount' => 'bail|required', diff --git a/app/Http/Requests/Payment/UpdatePaymentRequest.php b/app/Http/Requests/Payment/UpdatePaymentRequest.php index 0071c2ec69..94cf3f859e 100644 --- a/app/Http/Requests/Payment/UpdatePaymentRequest.php +++ b/app/Http/Requests/Payment/UpdatePaymentRequest.php @@ -36,7 +36,7 @@ class UpdatePaymentRequest extends Request public function rules() { $rules = [ - 'invoices' => ['array', new PaymentAppliedValidAmount, new ValidCreditsPresentRule], + 'invoices' => ['array', new PaymentAppliedValidAmount, new ValidCreditsPresentRule($this->all())], 'invoices.*.invoice_id' => 'distinct', 'documents' => 'mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx', ]; @@ -79,6 +79,14 @@ class UpdatePaymentRequest extends Request } } } + + if (isset($input['credits']) && is_array($input['credits']) !== false) { + foreach ($input['credits'] as $key => $value) { + if (array_key_exists('credits', $input['credits'][$key])) { + $input['credits'][$key]['credit_id'] = $this->decodePrimaryKey($value['credit_id']); + } + } + } $this->replace($input); } diff --git a/app/Http/ValidationRules/ValidCreditsPresentRule.php b/app/Http/ValidationRules/ValidCreditsPresentRule.php index 3cc5dab680..22bcdd7e0c 100644 --- a/app/Http/ValidationRules/ValidCreditsPresentRule.php +++ b/app/Http/ValidationRules/ValidCreditsPresentRule.php @@ -22,6 +22,13 @@ class ValidCreditsPresentRule implements Rule { use MakesHash; + private $input; + + public function __construct($input) + { + $this->input = $input; + } + /** * @param string $attribute * @param mixed $value @@ -44,11 +51,10 @@ class ValidCreditsPresentRule implements Rule { //todo need to ensure the clients credits are here not random ones! - if (request()->input('credits') && is_array(request()->input('credits')) && count(request()->input('credits')) > 0) { - $credit_collection = Credit::whereIn('id', $this->transformKeys(array_column(request()->input('credits'), 'credit_id'))) - ->count(); + if (array_key_exists('credits', $this->input) && is_array($this->input['credits']) && count($this->input['credits']) > 0) { + $credit_collection = Credit::whereIn('id', array_column($this->input['credits'], 'credit_id'))->count(); - return $credit_collection == count(request()->input('credits')); + return $credit_collection == count($this->input['credits']); } return true; From a07b955d79186f846658ed1bf55bdf692a1d6da2 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 22 Aug 2022 18:12:54 +1000 Subject: [PATCH 15/15] Version bump --- VERSION.txt | 2 +- app/Http/Requests/Payment/StorePaymentRequest.php | 2 +- config/ninja.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 9562ed5038..b262615750 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.5.14 \ No newline at end of file +5.5.15 \ No newline at end of file diff --git a/app/Http/Requests/Payment/StorePaymentRequest.php b/app/Http/Requests/Payment/StorePaymentRequest.php index 94b873f461..056e92515c 100644 --- a/app/Http/Requests/Payment/StorePaymentRequest.php +++ b/app/Http/Requests/Payment/StorePaymentRequest.php @@ -103,7 +103,7 @@ class StorePaymentRequest extends Request 'invoices.*.invoice_id' => new ValidInvoicesRules($this->all()), 'credits.*.credit_id' => 'bail|required|exists:credits,id', 'credits.*.credit_id' => new ValidCreditsRules($this->all()), - 'credits.*.amount' => ['required', new CreditsSumRule($this->all())], + 'credits.*.amount' => ['bail','required', new CreditsSumRule($this->all())], 'invoices' => new ValidPayableInvoicesRule(), 'number' => ['nullable', 'bail', Rule::unique('payments')->where('company_id', auth()->user()->company()->id)], diff --git a/config/ninja.php b/config/ninja.php index 1f8ce1c6bd..6bacd49a47 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -14,8 +14,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => '5.5.14', - 'app_tag' => '5.5.14', + 'app_version' => '5.5.15', + 'app_tag' => '5.5.15', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''),