From ddf6e94c43caa3ad8ab2eba9f5d0dcf7877e564c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Sep 2021 07:43:01 +1000 Subject: [PATCH 01/14] Make client_id query filter a global filter option --- app/Filters/InvoiceFilters.php | 12 ------------ app/Filters/QueryFilters.php | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/app/Filters/InvoiceFilters.php b/app/Filters/InvoiceFilters.php index df0e557b42..d6350dac6b 100644 --- a/app/Filters/InvoiceFilters.php +++ b/app/Filters/InvoiceFilters.php @@ -67,18 +67,6 @@ class InvoiceFilters extends QueryFilters return $this->builder; } - public function client_id(string $client_id = '') :Builder - { - if (strlen($client_id) == 0) { - return $this->builder; - } - - $this->builder->where('client_id', $this->decodePrimaryKey($client_id)); - - return $this->builder; - - } - public function number(string $number) :Builder { return $this->builder->where('number', $number); diff --git a/app/Filters/QueryFilters.php b/app/Filters/QueryFilters.php index a77a57b045..c80c6b85ec 100644 --- a/app/Filters/QueryFilters.php +++ b/app/Filters/QueryFilters.php @@ -12,6 +12,7 @@ namespace App\Filters; //use Illuminate\Database\Query\Builder; +use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Request; @@ -20,6 +21,8 @@ use Illuminate\Http\Request; */ abstract class QueryFilters { + use MakesHash; + /** * active status. */ @@ -177,6 +180,18 @@ abstract class QueryFilters } + public function client_id(string $client_id = '') :Builder + { + if (strlen($client_id) == 0) { + return $this->builder; + } + + $this->builder->where('client_id', $this->decodePrimaryKey($client_id)); + + return $this->builder; + + } + public function filter_deleted_clients($value) { From e914447097f83ad07d6880599c016ba40bb91902 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Sep 2021 08:21:22 +1000 Subject: [PATCH 02/14] Add withTrashed() when searching for MultiDB users --- app/Libraries/MultiDB.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Libraries/MultiDB.php b/app/Libraries/MultiDB.php index 22d96a9130..c3f3dc5c4a 100644 --- a/app/Libraries/MultiDB.php +++ b/app/Libraries/MultiDB.php @@ -73,12 +73,12 @@ class MultiDB public static function checkUserEmailExists($email) : bool { if (! config('ninja.db.multi_db_enabled')) - return User::where(['email' => $email])->exists(); // true >= 1 emails found / false -> == emails found + return User::where(['email' => $email])->withTrashed()->exists(); // true >= 1 emails found / false -> == emails found $current_db = config('database.default'); foreach (self::$dbs as $db) { - if (User::on($db)->where(['email' => $email])->exists()) { // if user already exists, validation will fail + if (User::on($db)->where(['email' => $email])->withTrashed()->exists()) { // if user already exists, validation will fail self::setDb($current_db); return true; } @@ -107,7 +107,7 @@ class MultiDB $current_db = config('database.default'); foreach (self::$dbs as $db) { - if (User::on($db)->where(['email' => $email])->exists()) { + if (User::on($db)->where(['email' => $email])->withTrashed()->exists()) { if (Company::on($db)->where(['company_key' => $company_key])->exists()) { self::setDb($current_db); return true; @@ -196,7 +196,7 @@ class MultiDB //multi-db active foreach (self::$dbs as $db) { - if (User::on($db)->where('email', $email)->exists()){ + if (User::on($db)->where('email', $email)->withTrashed()->exists()){ self::setDb($db); return true; } From de32d57b5b19c9d30617e9d164267bb1dd44518a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Sep 2021 09:37:35 +1000 Subject: [PATCH 03/14] Working on testing scenarios --- app/Factory/InvoiceFactory.php | 2 +- app/Helpers/Invoice/InvoiceSum.php | 2 +- tests/Feature/MultiPaymentDeleteTest.php | 178 +++++++++++++++++++++++ 3 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 tests/Feature/MultiPaymentDeleteTest.php diff --git a/app/Factory/InvoiceFactory.php b/app/Factory/InvoiceFactory.php index 4295608677..0d3a5e334a 100644 --- a/app/Factory/InvoiceFactory.php +++ b/app/Factory/InvoiceFactory.php @@ -49,7 +49,7 @@ class InvoiceFactory $invoice->user_id = $user_id; $invoice->company_id = $company_id; $invoice->recurring_id = null; - + return $invoice; } } diff --git a/app/Helpers/Invoice/InvoiceSum.php b/app/Helpers/Invoice/InvoiceSum.php index aa3940f8ea..53012df091 100644 --- a/app/Helpers/Invoice/InvoiceSum.php +++ b/app/Helpers/Invoice/InvoiceSum.php @@ -30,7 +30,7 @@ class InvoiceSum public $invoice_item; - public $total_taxes; + public $total_taxes = 0; private $total; diff --git a/tests/Feature/MultiPaymentDeleteTest.php b/tests/Feature/MultiPaymentDeleteTest.php new file mode 100644 index 0000000000..f9f98b688c --- /dev/null +++ b/tests/Feature/MultiPaymentDeleteTest.php @@ -0,0 +1,178 @@ +faker = \Faker\Factory::create(); + + } + + public function testComplexRefundDeleteScenario() + { + $account = Account::factory()->create(); + $company = Company::factory()->create([ + 'account_id' => $account->id, + ]); + + $account->default_company_id = $company->id; + $account->save(); + + $user = User::factory()->create([ + 'account_id' => $account->id, + 'confirmation_code' => '11', + ]); + + $cu = CompanyUserFactory::create($user->id, $company->id, $account->id); + $cu->is_owner = true; + $cu->is_admin = true; + $cu->save(); + + $token = new CompanyToken; + $token->user_id = $user->id; + $token->company_id = $company->id; + $token->account_id = $account->id; + $token->name = 'test token'; + $token->token = 'okeytokey'; + $token->is_system = true; + $token->save(); + + $client = Client::factory()->create([ + 'user_id' => $user->id, + 'company_id' => $company->id, + ]); + + ClientContact::factory()->create([ + 'user_id' => $user->id, + 'client_id' => $client->id, + 'company_id' => $company->id, + 'is_primary' => 1, + ]); + + ClientContact::factory()->create([ + 'user_id' => $user->id, + 'client_id' => $client->id, + 'company_id' => $company->id, + ]); + + $invoice = Invoice::factory()->create([ + 'user_id' => $user->id, + 'client_id' => $client->id, + 'company_id' => $company->id, + 'number' => (string)$this->faker->randomNumber(6), + ]); + + $invoice = InvoiceFactory::create($company->id,$user->id); + $invoice->client_id = $client->id; + $invoice->status_id = Invoice::STATUS_DRAFT; + + $line_items = []; + + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 325; + $item->type_id = 1; + + $line_items[] = $item; + + $invoice->line_items = $line_items; + + $invoice = $invoice->calc()->getInvoice(); + + $this->assertEquals(0, $client->balance); + $this->assertEquals(0, $invoice->balance); + + $invoice = $invoice->service()->markSent()->save(); + + $invoice->fresh(); + $invoice->client->fresh(); + + $this->assertEquals(325, $invoice->balance); + $this->assertEquals(325, $invoice->client->balance); + + $data = [ + 'amount' => 163.0, + 'client_id' => $this->encodePrimaryKey($client->id), + 'invoices' => [ + [ + 'invoice_id' => $this->encodePrimaryKey($invoice->id), + 'amount' => 163, + ], + ], + 'date' => '2019/12/12', + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token->token, + ])->post('/api/v1/payments/', $data); + + $arr = $response->json(); + $payment_id = $arr['data']['id']; + $payment_1 = Payment::whereId($this->decodePrimaryKey($payment_id))->first(); + + $this->assertEquals(162, $invoice->fresh()->balance); + $this->assertEquals(162, $invoice->client->fresh()->balance); + + + $data = [ + 'amount' => 162.0, + 'client_id' => $this->encodePrimaryKey($client->id), + 'invoices' => [ + [ + 'invoice_id' => $this->encodePrimaryKey($invoice->id), + 'amount' => 162, + ], + ], + 'date' => '2019/12/12', + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token->token, + ])->post('/api/v1/payments/', $data); + + $arr = $response->json(); + $payment_id = $arr['data']['id']; + $payment_2 = Payment::whereId($this->decodePrimaryKey($payment_id))->first(); + + $this->assertEquals(0, $invoice->fresh()->balance); + $this->assertEquals(0, $invoice->client->fresh()->balance); + } +} From 72a9584d679d5d2742888ab5d961f62fa8bf8bdd Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Sep 2021 09:42:19 +1000 Subject: [PATCH 04/14] Working on testing scenarios --- tests/Feature/MultiPaymentDeleteTest.php | 39 ++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/Feature/MultiPaymentDeleteTest.php b/tests/Feature/MultiPaymentDeleteTest.php index f9f98b688c..8439f826be 100644 --- a/tests/Feature/MultiPaymentDeleteTest.php +++ b/tests/Feature/MultiPaymentDeleteTest.php @@ -174,5 +174,44 @@ class MultiPaymentDeleteTest extends TestCase $this->assertEquals(0, $invoice->fresh()->balance); $this->assertEquals(0, $invoice->client->fresh()->balance); + + + //refund payment 2 by 63 dollars + + $data = [ + 'id' => $this->encodePrimaryKey($payment_2->id), + 'amount' => 63, + 'date' => '2021/12/12', + 'invoices' => [ + [ + 'invoice_id' => $invoice->hashed_id, + 'amount' => 63, + ], + ], + ]; + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token->token, + ])->post('/api/v1/payments/refund', $data); + + $this->assertEquals(63, $invoice->fresh()->balance); + $this->assertEquals(63, $invoice->client->fresh()->balance); + + + //delete payment 2 + // + $data = [ + 'ids' => [$this->encodePrimaryKey($payment_2->id)], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token->token, + ])->post('/api/v1/payments/bulk?action=delete', $data); + + $this->assertEquals(162, $invoice->fresh()->balance); + $this->assertEquals(162, $invoice->client->fresh()->balance); } } From 69c58917231a2780f4193a8b500491e1331f65fe Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Sep 2021 10:22:33 +1000 Subject: [PATCH 05/14] Working on testing scenarios --- tests/Feature/MultiPaymentDeleteTest.php | 121 ++++++++++++++++++++++- 1 file changed, 119 insertions(+), 2 deletions(-) diff --git a/tests/Feature/MultiPaymentDeleteTest.php b/tests/Feature/MultiPaymentDeleteTest.php index 8439f826be..bf06c8a87b 100644 --- a/tests/Feature/MultiPaymentDeleteTest.php +++ b/tests/Feature/MultiPaymentDeleteTest.php @@ -57,6 +57,7 @@ class MultiPaymentDeleteTest extends TestCase $user = User::factory()->create([ 'account_id' => $account->id, 'confirmation_code' => '11', + 'email' => $this->faker->unique()->safeEmail, ]); $cu = CompanyUserFactory::create($user->id, $company->id, $account->id); @@ -117,6 +118,7 @@ class MultiPaymentDeleteTest extends TestCase $this->assertEquals(0, $client->balance); $this->assertEquals(0, $invoice->balance); +//mark sent $invoice = $invoice->service()->markSent()->save(); @@ -126,6 +128,8 @@ class MultiPaymentDeleteTest extends TestCase $this->assertEquals(325, $invoice->balance); $this->assertEquals(325, $invoice->client->balance); +//payment 163 +// $data = [ 'amount' => 163.0, 'client_id' => $this->encodePrimaryKey($client->id), @@ -147,6 +151,7 @@ class MultiPaymentDeleteTest extends TestCase $payment_id = $arr['data']['id']; $payment_1 = Payment::whereId($this->decodePrimaryKey($payment_id))->first(); +//payment 162 $this->assertEquals(162, $invoice->fresh()->balance); $this->assertEquals(162, $invoice->client->fresh()->balance); @@ -176,7 +181,7 @@ class MultiPaymentDeleteTest extends TestCase $this->assertEquals(0, $invoice->client->fresh()->balance); - //refund payment 2 by 63 dollars +//refund payment 2 by 63 dollars $data = [ 'id' => $this->encodePrimaryKey($payment_2->id), @@ -200,7 +205,7 @@ class MultiPaymentDeleteTest extends TestCase $this->assertEquals(63, $invoice->client->fresh()->balance); - //delete payment 2 +//delete payment 2 // $data = [ 'ids' => [$this->encodePrimaryKey($payment_2->id)], @@ -213,5 +218,117 @@ class MultiPaymentDeleteTest extends TestCase $this->assertEquals(162, $invoice->fresh()->balance); $this->assertEquals(162, $invoice->client->fresh()->balance); + + +// Pay 162 again and create payment #3 + + $data = [ + 'amount' => 162.0, + 'client_id' => $this->encodePrimaryKey($client->id), + 'invoices' => [ + [ + 'invoice_id' => $this->encodePrimaryKey($invoice->id), + 'amount' => 162, + ], + ], + 'date' => '2019/12/12', + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token->token, + ])->post('/api/v1/payments/', $data); + + $arr = $response->json(); + $payment_id = $arr['data']['id']; + $payment_3 = Payment::whereId($this->decodePrimaryKey($payment_id))->first(); + + $invoice->fresh(); + $invoice->client->fresh(); + + $this->assertEquals(0, $invoice->fresh()->balance); + $this->assertEquals(0, $invoice->client->fresh()->balance); + + +//refund payment 3 by 63 + + $data = [ + 'id' => $this->encodePrimaryKey($payment_3->id), + 'amount' => 63, + 'date' => '2021/12/12', + 'invoices' => [ + [ + 'invoice_id' => $invoice->hashed_id, + 'amount' => 63, + ], + ], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token->token, + ])->post('/api/v1/payments/refund', $data); + + $this->assertEquals(63, $invoice->fresh()->balance); + $this->assertEquals(63, $invoice->client->fresh()->balance); + +//payment 4 for 63 + $data = [ + 'amount' => 63.0, + 'client_id' => $this->encodePrimaryKey($client->id), + 'invoices' => [ + [ + 'invoice_id' => $this->encodePrimaryKey($invoice->id), + 'amount' => 63, + ], + ], + 'date' => '2019/12/12', + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token->token, + ])->post('/api/v1/payments/', $data); + + $arr = $response->json(); + $payment_id = $arr['data']['id']; + $payment_4 = Payment::whereId($this->decodePrimaryKey($payment_id))->first(); + + + $this->assertEquals(0, $invoice->fresh()->balance); + $this->assertEquals(0, $invoice->client->fresh()->balance); + +// delete payment 3 +// + + // + $data = [ + 'ids' => [$this->encodePrimaryKey($payment_4->id)], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token->token, + ])->post('/api/v1/payments/bulk?action=delete', $data); + + $this->assertEquals(63, $invoice->fresh()->balance); + $this->assertEquals(63, $invoice->client->fresh()->balance); + + +//set discount of 63 to invoice + + $invoice = $invoice->fresh(); + $invoice->discount = 63; + $invoice->is_amount_discount = true; + $invoice->save(); + + $invoice->calc()->getInvoice()->save(); + $invoice->service()->updateStatus()->save(); + $invoice->ledger()->updateInvoiceBalance(-63, "Update adjustment for invoice {$invoice->number}"); + $invoice->client->service()->updateBalance(-63)->save(); + + $this->assertEquals(0, $invoice->fresh()->balance); + $this->assertEquals(0, $invoice->client->fresh()->balance); + } } From b06d761c8e4f611e0057836d9fdb479ba2d39c56 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Sep 2021 11:04:00 +1000 Subject: [PATCH 06/14] fixes for edge case when deleting a payment on a deleted invoice --- app/Services/Payment/DeletePayment.php | 47 ++++++++++++++++-------- tests/Feature/MultiPaymentDeleteTest.php | 44 +++++++++++++++++++++- 2 files changed, 74 insertions(+), 17 deletions(-) diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php index b4171cc0a0..5a9f483744 100644 --- a/app/Services/Payment/DeletePayment.php +++ b/app/Services/Payment/DeletePayment.php @@ -84,25 +84,40 @@ class DeletePayment nlog("net deletable amount - refunded = {$net_deletable}"); - $paymentable_invoice->service() - ->updateBalance($net_deletable) - ->updatePaidToDate($net_deletable * -1) - ->save(); + if(!$paymentable_invoice->is_deleted) + { + $paymentable_invoice->service() + ->updateBalance($net_deletable) + ->updatePaidToDate($net_deletable * -1) + ->save(); - $paymentable_invoice->ledger() - ->updateInvoiceBalance($net_deletable, "Adjusting invoice {$paymentable_invoice->number} due to deletion of Payment {$this->payment->number}") - ->save(); + $paymentable_invoice->ledger() + ->updateInvoiceBalance($net_deletable, "Adjusting invoice {$paymentable_invoice->number} due to deletion of Payment {$this->payment->number}") + ->save(); - $paymentable_invoice->client - ->service() - ->updateBalance($net_deletable) - ->updatePaidToDate($net_deletable * -1) - ->save(); + $paymentable_invoice->client + ->service() + ->updateBalance($net_deletable) + ->updatePaidToDate($net_deletable * -1) + ->save(); - if ($paymentable_invoice->balance == $paymentable_invoice->amount) { - $paymentable_invoice->service()->setStatus(Invoice::STATUS_SENT)->save(); - } else { - $paymentable_invoice->service()->setStatus(Invoice::STATUS_PARTIAL)->save(); + if ($paymentable_invoice->balance == $paymentable_invoice->amount) { + $paymentable_invoice->service()->setStatus(Invoice::STATUS_SENT)->save(); + } else { + $paymentable_invoice->service()->setStatus(Invoice::STATUS_PARTIAL)->save(); + } + } + else { + + //If the invoice is deleted we only update the meta data on the invoice + //and reduce the clients paid to date + $paymentable_invoice->updatePaidToDate($net_deletable * -1) + ->save(); + + $paymentable_invoice->client + ->service() + ->updatePaidToDate($net_deletable * -1) + ->save(); } }); diff --git a/tests/Feature/MultiPaymentDeleteTest.php b/tests/Feature/MultiPaymentDeleteTest.php index bf06c8a87b..071d565a5f 100644 --- a/tests/Feature/MultiPaymentDeleteTest.php +++ b/tests/Feature/MultiPaymentDeleteTest.php @@ -117,6 +117,7 @@ class MultiPaymentDeleteTest extends TestCase $invoice = $invoice->calc()->getInvoice(); $this->assertEquals(0, $client->balance); + $this->assertEquals(0, $client->paid_to_date); $this->assertEquals(0, $invoice->balance); //mark sent @@ -126,6 +127,7 @@ class MultiPaymentDeleteTest extends TestCase $invoice->client->fresh(); $this->assertEquals(325, $invoice->balance); + $this->assertEquals(0, $invoice->client->fresh()->paid_to_date); $this->assertEquals(325, $invoice->client->balance); //payment 163 @@ -154,6 +156,7 @@ class MultiPaymentDeleteTest extends TestCase //payment 162 $this->assertEquals(162, $invoice->fresh()->balance); $this->assertEquals(162, $invoice->client->fresh()->balance); + $this->assertEquals(163, $invoice->client->fresh()->paid_to_date); $data = [ @@ -179,6 +182,7 @@ class MultiPaymentDeleteTest extends TestCase $this->assertEquals(0, $invoice->fresh()->balance); $this->assertEquals(0, $invoice->client->fresh()->balance); + $this->assertEquals(325, $invoice->client->fresh()->paid_to_date); //refund payment 2 by 63 dollars @@ -203,7 +207,8 @@ class MultiPaymentDeleteTest extends TestCase $this->assertEquals(63, $invoice->fresh()->balance); $this->assertEquals(63, $invoice->client->fresh()->balance); - + $this->assertEquals(262, $invoice->client->fresh()->paid_to_date); + //delete payment 2 // @@ -218,6 +223,7 @@ class MultiPaymentDeleteTest extends TestCase $this->assertEquals(162, $invoice->fresh()->balance); $this->assertEquals(162, $invoice->client->fresh()->balance); + $this->assertEquals(163, $invoice->client->fresh()->paid_to_date); // Pay 162 again and create payment #3 @@ -248,6 +254,7 @@ class MultiPaymentDeleteTest extends TestCase $this->assertEquals(0, $invoice->fresh()->balance); $this->assertEquals(0, $invoice->client->fresh()->balance); + $this->assertEquals(325, $invoice->client->fresh()->paid_to_date); //refund payment 3 by 63 @@ -271,6 +278,7 @@ class MultiPaymentDeleteTest extends TestCase $this->assertEquals(63, $invoice->fresh()->balance); $this->assertEquals(63, $invoice->client->fresh()->balance); + $this->assertEquals(262, $invoice->client->fresh()->paid_to_date); //payment 4 for 63 $data = [ @@ -297,6 +305,7 @@ class MultiPaymentDeleteTest extends TestCase $this->assertEquals(0, $invoice->fresh()->balance); $this->assertEquals(0, $invoice->client->fresh()->balance); + $this->assertEquals(325, $invoice->client->fresh()->paid_to_date); // delete payment 3 // @@ -313,6 +322,7 @@ class MultiPaymentDeleteTest extends TestCase $this->assertEquals(63, $invoice->fresh()->balance); $this->assertEquals(63, $invoice->client->fresh()->balance); + $this->assertEquals(262, $invoice->client->fresh()->paid_to_date); //set discount of 63 to invoice @@ -329,6 +339,38 @@ class MultiPaymentDeleteTest extends TestCase $this->assertEquals(0, $invoice->fresh()->balance); $this->assertEquals(0, $invoice->client->fresh()->balance); + $this->assertEquals(262, $invoice->client->fresh()->paid_to_date); + + +//now delete the invoice + + $data = [ + 'ids' => [$invoice->hashed_id], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token->token, + ])->post('/api/v1/invoices/bulk?action=delete', $data); + + $this->assertEquals(0, $invoice->fresh()->balance); + $this->assertEquals(0, $invoice->client->fresh()->balance); + $this->assertEquals(262, $invoice->client->fresh()->paid_to_date); + + +//Delete payment 4 which is for $162 + $data = [ + 'ids' => [$this->encodePrimaryKey($payment_1->id)], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token->token, + ])->post('/api/v1/payments/bulk?action=delete', $data); + + $this->assertEquals(0, $invoice->fresh()->balance); + $this->assertEquals(0, $invoice->client->fresh()->balance); + $this->assertEquals(0, $invoice->client->fresh()->paid_to_date); } } From d575fac950c09ebb87135a8436d58152857bcd9e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Sep 2021 11:48:08 +1000 Subject: [PATCH 07/14] Fixes for edge cases when applying :MONTH in invoice pdfs --- app/Utils/Traits/MakesInvoiceValues.php | 2 +- tests/Feature/MultiPaymentDeleteTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Utils/Traits/MakesInvoiceValues.php b/app/Utils/Traits/MakesInvoiceValues.php index d37539a5ab..fb53e5a953 100644 --- a/app/Utils/Traits/MakesInvoiceValues.php +++ b/app/Utils/Traits/MakesInvoiceValues.php @@ -483,7 +483,7 @@ trait MakesInvoiceValues $output = (int)$raw - (int)$_value[1]; // 1 (:MONTH) - 4 } - if ($_operation == '/') { + if ($_operation == '/' && (int)$_value[1] != 0) { $output = (int)$raw / (int)$_value[1]; // 1 (:MONTH) / 4 } diff --git a/tests/Feature/MultiPaymentDeleteTest.php b/tests/Feature/MultiPaymentDeleteTest.php index 071d565a5f..c41a54b28f 100644 --- a/tests/Feature/MultiPaymentDeleteTest.php +++ b/tests/Feature/MultiPaymentDeleteTest.php @@ -355,7 +355,7 @@ class MultiPaymentDeleteTest extends TestCase $this->assertEquals(0, $invoice->fresh()->balance); $this->assertEquals(0, $invoice->client->fresh()->balance); - $this->assertEquals(262, $invoice->client->fresh()->paid_to_date); + $this->assertEquals(0, $invoice->client->fresh()->paid_to_date); //Delete payment 4 which is for $162 From 79ef62fefbc341bfab15c359f66a32a85e1299c1 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Sep 2021 13:07:11 +1000 Subject: [PATCH 08/14] Fixes for System Log Filter types --- app/Filters/SystemLogFilters.php | 5 ----- app/Jobs/Mail/NinjaMailerJob.php | 29 ++++++++++++++++--------- app/Utils/Traits/MakesInvoiceValues.php | 5 ++++- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/app/Filters/SystemLogFilters.php b/app/Filters/SystemLogFilters.php index 81eb088e70..1ccf41b01d 100644 --- a/app/Filters/SystemLogFilters.php +++ b/app/Filters/SystemLogFilters.php @@ -34,11 +34,6 @@ class SystemLogFilters extends QueryFilters return $this->builder->where('event_id', $event_id); } - public function client_id(int $client_id) :Builder - { - return $this->builder->where('client_id', $client_id); - } - /** * Filter based on search text. * diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index 057045531e..dc0fef5605 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -30,6 +30,7 @@ use App\Providers\MailServiceProvider; use App\Utils\Ninja; use App\Utils\Traits\MakesHash; use Dacastro4\LaravelGmail\Facade\LaravelGmail; +use GuzzleHttp\Exception\ClientException; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -118,10 +119,18 @@ class NinjaMailerJob implements ShouldQueue nlog("error failed with {$e->getMessage()}"); - if($this->nmo->entity) - $this->entityEmailFailed($e->getMessage()); + $message = $e->getMessage(); - if(Ninja::isHosted()) + if($e instanceof ClientException) { //postmark specific failure + + $response = $e->getResponse(); + $message = $response->Message; + } + + if($this->nmo->entity) + $this->entityEmailFailed($message); + + if(Ninja::isHosted() && (!$e instanceof ClientException)) // Don't send postmark failures to Sentry app('sentry')->captureException($e); } } @@ -241,6 +250,7 @@ class NinjaMailerJob implements ShouldQueue private function logMailError($errors, $recipient_object) { + SystemLogger::dispatch( $errors, SystemLog::CATEGORY_MAIL, @@ -249,19 +259,18 @@ class NinjaMailerJob implements ShouldQueue $recipient_object, $this->nmo->company ); - } - public function failed($exception = null) - { - nlog('mailer job failed'); - nlog($exception->getMessage()); - $job_failure = new EmailFailure($this->nmo->company->company_key); $job_failure->string_metric5 = 'failed_email'; - $job_failure->string_metric6 = substr($exception->getMessage(), 0, 150); + $job_failure->string_metric6 = substr($errors, 0, 150); LightLogs::create($job_failure) ->batch(); } + public function failed($exception = null) + { + + } + } \ No newline at end of file diff --git a/app/Utils/Traits/MakesInvoiceValues.php b/app/Utils/Traits/MakesInvoiceValues.php index fb53e5a953..6dad60647d 100644 --- a/app/Utils/Traits/MakesInvoiceValues.php +++ b/app/Utils/Traits/MakesInvoiceValues.php @@ -301,7 +301,10 @@ trait MakesInvoiceValues $data[$key][$table_type . ".{$_table_type}3"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}3", $item->custom_value3, $this->client); $data[$key][$table_type . ".{$_table_type}4"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}4", $item->custom_value4, $this->client); - $data[$key][$table_type.'.quantity'] = Number::formatValue($item->quantity, $this->client->currency()); + //$data[$key][$table_type.'.quantity'] = Number::formatValue($item->quantity, $this->client->currency()); + + //change quantity from localized number, to decimal format with no trailing zeroes 06/09/21 + $data[$key][$table_type.'.quantity'] = rtrim($item->quantity, "0"); $data[$key][$table_type.'.unit_cost'] = Number::formatMoney($item->cost, $this->client); $data[$key][$table_type.'.cost'] = Number::formatMoney($item->cost, $this->client); From e719c659eadf1d98888f46fbbed8a98381fd68b7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Sep 2021 15:08:41 +1000 Subject: [PATCH 09/14] Fixes for Zoho imports --- app/Import/Transformers/BaseTransformer.php | 2 +- app/Import/Transformers/Zoho/ClientTransformer.php | 2 +- app/Import/Transformers/Zoho/InvoiceTransformer.php | 4 ++-- app/Services/Payment/DeletePayment.php | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/Import/Transformers/BaseTransformer.php b/app/Import/Transformers/BaseTransformer.php index f0b2721c3e..cfe6c3cf12 100644 --- a/app/Import/Transformers/BaseTransformer.php +++ b/app/Import/Transformers/BaseTransformer.php @@ -62,7 +62,7 @@ class BaseTransformer public function getClient($client_name, $client_email) { $clients = $this->maps['company']->clients; - $clients = $clients->where( 'name', $client_name ); + $clients = $clients->where( 'id_number', $client_name ); if ( $clients->count() >= 1 ) { return $clients->first()->id; diff --git a/app/Import/Transformers/Zoho/ClientTransformer.php b/app/Import/Transformers/Zoho/ClientTransformer.php index bee0c94d18..180a76d264 100644 --- a/app/Import/Transformers/Zoho/ClientTransformer.php +++ b/app/Import/Transformers/Zoho/ClientTransformer.php @@ -42,7 +42,7 @@ class ClientTransformer extends BaseTransformer { 'work_phone' => $this->getString( $data, 'Phone' ), 'private_notes' => $this->getString( $data, 'Notes' ), 'website' => $this->getString( $data, 'Website' ), - + 'id_number' => $this->getString( $data, 'Customer ID'), 'address1' => $this->getString( $data, 'Billing Address' ), 'address2' => $this->getString( $data, 'Billing Street2' ), 'city' => $this->getString( $data, 'Billing City' ), diff --git a/app/Import/Transformers/Zoho/InvoiceTransformer.php b/app/Import/Transformers/Zoho/InvoiceTransformer.php index 105fc6fea7..c3659d77e6 100644 --- a/app/Import/Transformers/Zoho/InvoiceTransformer.php +++ b/app/Import/Transformers/Zoho/InvoiceTransformer.php @@ -38,7 +38,7 @@ class InvoiceTransformer extends BaseTransformer { $transformed = [ 'company_id' => $this->maps['company']->id, - 'client_id' => $this->getClient( $this->getString( $invoice_data, 'Company Name' ), null ), + 'client_id' => $this->getClient( $this->getString( $invoice_data, 'Customer ID' ), null ), 'number' => $this->getString( $invoice_data, 'Invoice Number' ), 'date' => isset( $invoice_data['Invoice Date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['Invoice Date'] ) ) : null, 'due_date' => isset( $invoice_data['Due Date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['Due Date'] ) ) : null, @@ -59,7 +59,7 @@ class InvoiceTransformer extends BaseTransformer { 'notes' => $this->getString( $record, 'Item Description' ), 'cost' => $this->getFloat( $record, 'Item Price' ), 'quantity' => $this->getFloat( $record, 'Quantity' ), - 'discount' => $this->getFloat( $record, 'Discount Amount' ), + 'discount' => $this->getString( $record, 'Discount Amount' ), 'is_amount_discount' => true, ]; } diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php index 5a9f483744..bc68506b40 100644 --- a/app/Services/Payment/DeletePayment.php +++ b/app/Services/Payment/DeletePayment.php @@ -111,7 +111,8 @@ class DeletePayment //If the invoice is deleted we only update the meta data on the invoice //and reduce the clients paid to date - $paymentable_invoice->updatePaidToDate($net_deletable * -1) + $paymentable_invoice->service() + ->updatePaidToDate($net_deletable * -1) ->save(); $paymentable_invoice->client From 32b0479101515601cc6aa477e7f93306584326db Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Sep 2021 15:37:52 +1000 Subject: [PATCH 10/14] Fixes for quantity number in line items --- app/Utils/Traits/MakesInvoiceValues.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Utils/Traits/MakesInvoiceValues.php b/app/Utils/Traits/MakesInvoiceValues.php index 6dad60647d..c51486aa32 100644 --- a/app/Utils/Traits/MakesInvoiceValues.php +++ b/app/Utils/Traits/MakesInvoiceValues.php @@ -270,6 +270,8 @@ trait MakesInvoiceValues if (! is_array($items)) { $data; } + + $locale_info = localeconv(); foreach ($items as $key => $item) { if ($table_type == '$product' && $item->type_id != 1) { @@ -304,8 +306,7 @@ trait MakesInvoiceValues //$data[$key][$table_type.'.quantity'] = Number::formatValue($item->quantity, $this->client->currency()); //change quantity from localized number, to decimal format with no trailing zeroes 06/09/21 - $data[$key][$table_type.'.quantity'] = rtrim($item->quantity, "0"); - + $data[$key][$table_type.'.quantity'] = rtrim($item->quantity, $locale_info['decimal_point']); $data[$key][$table_type.'.unit_cost'] = Number::formatMoney($item->cost, $this->client); $data[$key][$table_type.'.cost'] = Number::formatMoney($item->cost, $this->client); From a2fbfbac1070bfd443025d05fdb28193884780c9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Sep 2021 15:48:31 +1000 Subject: [PATCH 11/14] Explicitly define the system log const --- app/PaymentDrivers/BaseDriver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index dd8a5e5701..45cd0b1c3e 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -478,7 +478,7 @@ class BaseDriver extends AbstractPaymentDriver $message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, - $this::SYSTEM_LOG_TYPE, + SystemLog::TYPE_PAYTRACE, $this->client, $this->client->company, ); From 02e8e6e000e8669c9fde0f522d7008aefda0d70d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 7 Sep 2021 13:57:55 +1000 Subject: [PATCH 12/14] Fixes for amounts when formatted with comma's --- .../Controllers/ClientPortal/InvoiceController.php | 10 ++++++++-- app/Jobs/Cron/AutoBillCron.php | 14 +++++++++----- app/Jobs/RecurringInvoice/SendRecurring.php | 2 ++ app/Repositories/BaseRepository.php | 3 ++- app/Repositories/PaymentRepository.php | 8 ++++---- 5 files changed, 25 insertions(+), 12 deletions(-) diff --git a/app/Http/Controllers/ClientPortal/InvoiceController.php b/app/Http/Controllers/ClientPortal/InvoiceController.php index bb8ca40832..6094b85fc6 100644 --- a/app/Http/Controllers/ClientPortal/InvoiceController.php +++ b/app/Http/Controllers/ClientPortal/InvoiceController.php @@ -20,7 +20,9 @@ use App\Utils\Number; use App\Utils\TempFile; use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesHash; +use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Contracts\View\Factory; +use Illuminate\Http\RedirectResponse; use Illuminate\View\View; use ZipStream\Option\Archive; use ZipStream\ZipStream; @@ -86,6 +88,10 @@ class InvoiceController extends Controller ->with('message', ctrans('texts.no_action_provided')); } + /** + * @param array $ids + * @return Factory|View|RedirectResponse + */ private function makePayment(array $ids) { $invoices = Invoice::whereIn('id', $ids) @@ -119,8 +125,8 @@ class InvoiceController extends Controller //format data $invoices->map(function ($invoice) { $invoice->service()->removeUnpaidGatewayFees()->save(); - $invoice->balance = Number::formatValue($invoice->balance, $invoice->client->currency()); - $invoice->partial = Number::formatValue($invoice->partial, $invoice->client->currency()); + $invoice->balance = $invoice->balance > 0 ? Number::formatValue($invoice->balance, $invoice->client->currency()) : 0; + $invoice->partial = $invoice->partial > 0 ? Number::formatValue($invoice->partial, $invoice->client->currency()) : 0; return $invoice; }); diff --git a/app/Jobs/Cron/AutoBillCron.php b/app/Jobs/Cron/AutoBillCron.php index f3918e9fff..0b852cb480 100644 --- a/app/Jobs/Cron/AutoBillCron.php +++ b/app/Jobs/Cron/AutoBillCron.php @@ -56,8 +56,8 @@ class AutoBillCron nlog($auto_bill_partial_invoices->count(). " partial invoices to auto bill"); - $auto_bill_partial_invoices->cursor()->each(function ($invoice) use($db){ - $this->runAutoBiller($invoice, $db); + $auto_bill_partial_invoices->cursor()->each(function ($invoice){ + $this->runAutoBiller($invoice, false); }); $auto_bill_invoices = Invoice::whereDate('due_date', '<=', now()) @@ -69,8 +69,8 @@ class AutoBillCron nlog($auto_bill_invoices->count(). " full invoices to auto bill"); - $auto_bill_invoices->cursor()->each(function ($invoice) use($db){ - $this->runAutoBiller($invoice, $db); + $auto_bill_invoices->cursor()->each(function ($invoice){ + $this->runAutoBiller($invoice, false); }); @@ -115,8 +115,12 @@ class AutoBillCron info("Firing autobill for {$invoice->company_id} - {$invoice->number}"); try{ - MultiDB::setDB($db); + + if($db) + MultiDB::setDB($db); + $invoice->service()->autoBill()->save(); + } catch(\Exception $e) { nlog("Failed to capture payment for {$invoice->company_id} - {$invoice->number} ->" . $e->getMessage()); diff --git a/app/Jobs/RecurringInvoice/SendRecurring.php b/app/Jobs/RecurringInvoice/SendRecurring.php index ba4cd3a0a7..de7ab9bfc3 100644 --- a/app/Jobs/RecurringInvoice/SendRecurring.php +++ b/app/Jobs/RecurringInvoice/SendRecurring.php @@ -147,6 +147,8 @@ class SendRecurring implements ShouldQueue } + //important catch all here - we should never leave contacts send_email to false incase they are permanently set to false in the future. + $this->recurring_invoice->client->contacts()->update(['send_email' => true]); } diff --git a/app/Repositories/BaseRepository.php b/app/Repositories/BaseRepository.php index fae747d854..18970695e2 100644 --- a/app/Repositories/BaseRepository.php +++ b/app/Repositories/BaseRepository.php @@ -28,7 +28,8 @@ class BaseRepository { use MakesHash; use SavesDocuments; - public $import_mode = false; + + public $import_mode = false; /** * @param $entity diff --git a/app/Repositories/PaymentRepository.php b/app/Repositories/PaymentRepository.php index 8a83d29e81..ebcb5add51 100644 --- a/app/Repositories/PaymentRepository.php +++ b/app/Repositories/PaymentRepository.php @@ -170,10 +170,10 @@ class PaymentRepository extends BaseRepository { event( new PaymentWasCreated( $payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null) ) ); } - nlog("payment amount = {$payment->amount}"); - nlog("payment applied = {$payment->applied}"); - nlog("invoice totals = {$invoice_totals}"); - nlog("credit totals = {$credit_totals}"); + // nlog("payment amount = {$payment->amount}"); + // nlog("payment applied = {$payment->applied}"); + // nlog("invoice totals = {$invoice_totals}"); + // nlog("credit totals = {$credit_totals}"); $payment->applied += ($invoice_totals - $credit_totals); //wont work because - check tests // $payment->applied += $invoice_totals; //wont work because - check tests From 916c5e4da803b626b6c97733668032710702f20d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 7 Sep 2021 14:55:09 +1000 Subject: [PATCH 13/14] Trim all emails --- app/Repositories/ClientContactRepository.php | 1 + app/Services/Payment/DeletePayment.php | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/Repositories/ClientContactRepository.php b/app/Repositories/ClientContactRepository.php index 5900ac7eee..f2dc62691e 100644 --- a/app/Repositories/ClientContactRepository.php +++ b/app/Repositories/ClientContactRepository.php @@ -71,6 +71,7 @@ class ClientContactRepository extends BaseRepository $client->company->client_contacts()->where('email', $update_contact->email)->update(['password' => $update_contact->password]); } + $update_contact->email = trim($contact['email']); $update_contact->save(); }); diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php index bc68506b40..b486180550 100644 --- a/app/Services/Payment/DeletePayment.php +++ b/app/Services/Payment/DeletePayment.php @@ -115,10 +115,10 @@ class DeletePayment ->updatePaidToDate($net_deletable * -1) ->save(); - $paymentable_invoice->client - ->service() - ->updatePaidToDate($net_deletable * -1) - ->save(); + // $paymentable_invoice->client + // ->service() + // ->updatePaidToDate($net_deletable * -1) + // ->save(); } }); From 2bed8fc9728b627a3a5e52b97e783753b37a8c29 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 7 Sep 2021 15:12:12 +1000 Subject: [PATCH 14/14] Trim emails --- app/Http/Requests/User/StoreUserRequest.php | 2 ++ app/Http/Requests/User/UpdateUserRequest.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/Http/Requests/User/StoreUserRequest.php b/app/Http/Requests/User/StoreUserRequest.php index e710a33274..cdffdde1f4 100644 --- a/app/Http/Requests/User/StoreUserRequest.php +++ b/app/Http/Requests/User/StoreUserRequest.php @@ -60,6 +60,8 @@ class StoreUserRequest extends Request //unique user rule - check company_user table for user_id / company_id / account_id if none exist we can add the user. ELSE return false + if(array_key_exists('email', $input)) + $input['email'] = trim($input['email']); if (isset($input['company_user'])) { if (! isset($input['company_user']['is_admin'])) { diff --git a/app/Http/Requests/User/UpdateUserRequest.php b/app/Http/Requests/User/UpdateUserRequest.php index 3963b79a4f..b38962afea 100644 --- a/app/Http/Requests/User/UpdateUserRequest.php +++ b/app/Http/Requests/User/UpdateUserRequest.php @@ -45,6 +45,8 @@ class UpdateUserRequest extends Request { $input = $this->all(); + if(array_key_exists('email', $input)) + $input['email'] = trim($input['email']); $this->replace($input); }