From 8ca9d0ff0ee3f754701e49c773d8c30420da1e68 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 26 Feb 2024 07:32:23 +1100 Subject: [PATCH 01/11] Better handling of failsafe for US State calculations --- app/DataMapper/Tax/BaseRule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/DataMapper/Tax/BaseRule.php b/app/DataMapper/Tax/BaseRule.php index d921f2006887..337ea4003119 100644 --- a/app/DataMapper/Tax/BaseRule.php +++ b/app/DataMapper/Tax/BaseRule.php @@ -264,7 +264,7 @@ class BaseRule implements RuleInterface return USStates::getState(strlen($this->client->postal_code) > 1 ? $this->client->postal_code : $this->client->shipping_postal_code); } catch (\Exception $e) { - return $this->client->company->country()->iso_3166_2 == 'US' ? $this->client->company->tax_data->seller_subregion : 'CA'; + return 'CA'; } } From bdbe8f8f6cce374825023ebce91b515a5c9ad0c7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 28 Feb 2024 18:09:13 +1100 Subject: [PATCH 02/11] Remove redundant include --- app/Jobs/Util/ReminderJob.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/Jobs/Util/ReminderJob.php b/app/Jobs/Util/ReminderJob.php index c299232d2e50..e73897378eec 100644 --- a/app/Jobs/Util/ReminderJob.php +++ b/app/Jobs/Util/ReminderJob.php @@ -26,7 +26,6 @@ use App\Utils\Traits\MakesReminders; use Illuminate\Support\Facades\Auth; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; -use Spatie\OpenTelemetry\Jobs\TraceAware; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; From 2858ff8946ea3b5931745a3005758d41065dcf3b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 28 Feb 2024 19:04:06 +1100 Subject: [PATCH 03/11] Additional checks --- app/Console/Commands/CheckData.php | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/app/Console/Commands/CheckData.php b/app/Console/Commands/CheckData.php index ba95b675c5dd..b843f33cb9e4 100644 --- a/app/Console/Commands/CheckData.php +++ b/app/Console/Commands/CheckData.php @@ -947,7 +947,35 @@ class CheckData extends Command }); + Company::whereDoesntHave('company_users', function ($query){ + $query->where('is_owner', 1); + }) + ->cursor() + ->when(Ninja::isHosted()) + ->each(function ($c){ + $this->logMessage("Orphan Account # {$c->account_id}"); + + }); + + CompanyUser::whereDoesntHave('tokens') + ->cursor() + ->when(Ninja::isHosted()) + ->each(function ($cu){ + + $this->logMessage("Missing tokens for Company User # {$cu->id}"); + + }); + + + CompanyUser::whereDoesntHave('user') + ->cursor() + ->when(Ninja::isHosted()) + ->each(function ($cu) { + + $this->logMessage("Missing user for Company User # {$cu->id}"); + + }); } From 3ad5de1d2cbe53445c47966d1a3c9a413fb1e28d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 28 Feb 2024 21:15:43 +1100 Subject: [PATCH 04/11] Add filters for documents --- app/Filters/DocumentFilters.php | 39 ++++++- app/Models/Document.php | 20 ++++ app/Transformers/DocumentTransformer.php | 1 + app/Utils/SystemHealth.php | 1 + lang/en/texts.php | 1 + tests/Feature/DocumentsApiTest.php | 136 ++++++++++++++++++++++- 6 files changed, 193 insertions(+), 5 deletions(-) diff --git a/app/Filters/DocumentFilters.php b/app/Filters/DocumentFilters.php index 73b9b97a5b59..15856a2e762d 100644 --- a/app/Filters/DocumentFilters.php +++ b/app/Filters/DocumentFilters.php @@ -29,11 +29,13 @@ class DocumentFilters extends QueryFilters */ public function filter(string $filter = ''): Builder { + if (strlen($filter) == 0) { return $this->builder; } - return $this->builder; + return $this->builder->where('name', 'like', '%'.$filter.'%'); + } /** @@ -47,9 +49,42 @@ class DocumentFilters extends QueryFilters */ public function client_id(string $client_id = ''): Builder { - return $this->builder; + + return $this->builder->where(function ($query) use ($client_id) { + $query->whereHasMorph('documentable', [ + \App\Models\Invoice::class, + \App\Models\Quote::class, + \App\Models\Credit::class, + \App\Models\Expense::class, + \App\Models\Payment::class, + \App\Models\Task::class], function ($q2) use ($client_id) { + $q2->where('client_id', $this->decodePrimaryKey($client_id)); + })->orWhereHasMorph('documentable', [\App\Models\Client::class], function ($q3) use ($client_id) { + $q3->where('id', $this->decodePrimaryKey($client_id)); + }); + }); + } + public function type(string $types = '') + { + $types = explode(',', $types); + + foreach ($types as $type) + { + match($type) { + 'private' => $this->builder->where('is_public', 0), + 'public' => $this->builder->where('is_public', 1), + 'pdf' => $this->builder->where('type', 'pdf'), + 'image' => $this->builder->whereIn('type', ['png','jpeg','jpg','gif','svg']), + 'other' => $this->builder->whereNotIn('type', ['pdf','png','jpeg','jpg','gif','svg']), + default => $this->builder, + }; + } + + return $this->builder; + } + /** * Sorts the list based on $sort. * diff --git a/app/Models/Document.php b/app/Models/Document.php index 061210041ff8..8b2a45f7a508 100644 --- a/app/Models/Document.php +++ b/app/Models/Document.php @@ -208,6 +208,26 @@ class Document extends BaseModel return ctrans('texts.document'); } + public function link() + { + $entity_id = $this->encodePrimaryKey($this->documentable_id); + + match($this->documentable_type) { + 'App\Models\Vendor' => $link = "vendors/{$entity_id}", + 'App\Models\Project' => $link = "projects/{$entity_id}", + 'invoices' => $link = "invoices/{$entity_id}/edit", + 'App\Models\Quote' => $link = "quotes/{$entity_id}/edit", + 'App\Models\Credit' => $link = "credits/{$entity_id}/edit", + 'App\Models\Expense' => $link = "expenses/{$entity_id}/edit", + 'App\Models\Payment' => $link = "payments/{$entity_id}/edit", + 'App\Models\Task' => $link = "tasks/{$entity_id}/edit", + 'App\Models\Client' => $link = "clients/{$entity_id}", + default => $link = '' + }; + + return $link; + } + public function compress(): mixed { diff --git a/app/Transformers/DocumentTransformer.php b/app/Transformers/DocumentTransformer.php index c916efe9d440..baab0e4271fd 100644 --- a/app/Transformers/DocumentTransformer.php +++ b/app/Transformers/DocumentTransformer.php @@ -52,6 +52,7 @@ class DocumentTransformer extends EntityTransformer 'created_at' => (int) $document->created_at, 'is_deleted' => (bool) false, 'is_public' => (bool) $document->is_public, + 'link' => (string) $document->link(), ]; } } diff --git a/app/Utils/SystemHealth.php b/app/Utils/SystemHealth.php index 6b60a7d845e7..c862091c162e 100644 --- a/app/Utils/SystemHealth.php +++ b/app/Utils/SystemHealth.php @@ -84,6 +84,7 @@ class SystemHealth 'trailing_slash' => (bool) self::checkUrlState(), 'file_permissions' => (string) self::checkFileSystem(), 'exchange_rate_api_not_configured' => (bool)self::checkCurrencySanity(), + 'api_version' => (string) config('ninja.app_version'), ]; } diff --git a/lang/en/texts.php b/lang/en/texts.php index 11811703ee24..217ce04ed9f1 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5240,6 +5240,7 @@ $lang = array( 'use_available_payments' => 'Use Available Payments', 'test_email_sent' => 'Successfully sent email', 'gateway_type' => 'Gateway Type', + 'save_template_body' => 'Would you like to save this import mapping as a template for future use?', ); return $lang; diff --git a/tests/Feature/DocumentsApiTest.php b/tests/Feature/DocumentsApiTest.php index 2c46c6b2effb..4537019e37ae 100644 --- a/tests/Feature/DocumentsApiTest.php +++ b/tests/Feature/DocumentsApiTest.php @@ -11,13 +11,14 @@ namespace Tests\Feature; +use Tests\TestCase; +use App\Models\Task; use App\Models\Document; +use Tests\MockAccountData; use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\Session; -use Tests\MockAccountData; -use Tests\TestCase; +use Illuminate\Foundation\Testing\DatabaseTransactions; /** * @test @@ -44,6 +45,135 @@ class DocumentsApiTest extends TestCase Model::reguard(); } + public function testDocumentFilters() + { + Document::query()->withTrashed()->cursor()->each(function ($d){ + $d->forceDelete(); + }); + + $d = Document::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'name' => 'searchable.jpg', + 'type' => 'jpg', + ]); + + $this->client->documents()->save($d); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get("/api/v1/documents/{$d->hashed_id}?client_id={$this->client->hashed_id}"); + + $response->assertStatus(200); + + $this->assertCount(1, $response->json()); + } + + + public function testDocumentFilters2() + { + Document::query()->withTrashed()->cursor()->each(function ($d){ + $d->forceDelete(); + }); + + $d = Document::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'name' => 'searchable.jpg', + 'type' => 'jpg', + ]); + + $this->task->documents()->save($d); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get("/api/v1/documents/{$d->hashed_id}?client_id={$this->client->hashed_id}"); + + $response->assertStatus(200); + + $this->assertCount(1, $response->json()); + } + + public function testDocumentFilters3() + { + Document::query()->withTrashed()->cursor()->each(function ($d){ + $d->forceDelete(); + }); + + $d = Document::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'name' => 'searchable.jpg', + 'type' => 'jpg', + ]); + + $t = Task::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $this->client->id, + ]); + + $t->documents()->save($d); + + $dd = Document::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'name' => 'searchable2.jpg', + 'type' => 'jpg', + ]); + + $this->client->documents()->save($dd); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get("/api/v1/documents?client_id={$this->client->hashed_id}"); + + $response->assertStatus(200); + + $this->assertCount(2, $response->json()['data']); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get("/api/v1/documents?client_id={$this->client->hashed_id}&filter=craycray"); + + $response->assertStatus(200); + + $this->assertCount(0, $response->json()['data']); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get("/api/v1/documents?client_id={$this->client->hashed_id}&filter=s"); + + $response->assertStatus(200); + + $this->assertCount(2, $response->json()['data']); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get("/api/v1/documents?client_id={$this->client->hashed_id}&filter=searchable"); + + $response->assertStatus(200); + + $this->assertCount(2, $response->json()['data']); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get("/api/v1/documents?client_id={$this->client->hashed_id}&filter=searchable2"); + + $response->assertStatus(200); + + $this->assertCount(1, $response->json()['data']); + + } + + public function testIsPublicTypesForDocumentRequest() { $d = Document::factory()->create([ From d96a5ca4f23d0f323e56c65ac592ae573b5e7ab3 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 29 Feb 2024 12:01:02 +1100 Subject: [PATCH 05/11] V5.8.31 --- VERSION.txt | 2 +- app/Helpers/Invoice/InvoiceItemSum.php | 6 ++-- .../Invoice/InvoiceItemSumInclusive.php | 6 ++-- app/Helpers/Invoice/InvoiceSum.php | 2 ++ app/Helpers/Invoice/InvoiceSumInclusive.php | 3 +- config/ninja.php | 4 +-- tests/Unit/InvoiceItemTest.php | 36 +++++++++++++++++++ 7 files changed, 48 insertions(+), 11 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 40fdb9da290f..1dcfa2b73936 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.8.30 \ No newline at end of file +5.8.31 \ No newline at end of file diff --git a/app/Helpers/Invoice/InvoiceItemSum.php b/app/Helpers/Invoice/InvoiceItemSum.php index f9dc63c972b7..b4b8bd5980d1 100644 --- a/app/Helpers/Invoice/InvoiceItemSum.php +++ b/app/Helpers/Invoice/InvoiceItemSum.php @@ -277,7 +277,7 @@ class InvoiceItemSum $item_tax += $item_tax_rate1_total; - if (strlen($this->item->tax_name1) > 2) { + if (strlen($this->item->tax_name1) > 1) { $this->groupTax($this->item->tax_name1, $this->item->tax_rate1, $item_tax_rate1_total); } @@ -285,7 +285,7 @@ class InvoiceItemSum $item_tax += $item_tax_rate2_total; - if (strlen($this->item->tax_name2) > 2) { + if (strlen($this->item->tax_name2) > 1) { $this->groupTax($this->item->tax_name2, $this->item->tax_rate2, $item_tax_rate2_total); } @@ -293,7 +293,7 @@ class InvoiceItemSum $item_tax += $item_tax_rate3_total; - if (strlen($this->item->tax_name3) > 2) { + if (strlen($this->item->tax_name3) > 1) { $this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total); } diff --git a/app/Helpers/Invoice/InvoiceItemSumInclusive.php b/app/Helpers/Invoice/InvoiceItemSumInclusive.php index 299de3153076..a09bc9314a64 100644 --- a/app/Helpers/Invoice/InvoiceItemSumInclusive.php +++ b/app/Helpers/Invoice/InvoiceItemSumInclusive.php @@ -231,7 +231,7 @@ class InvoiceItemSumInclusive /** @var float $item_tax */ $item_tax += $this->formatValue($item_tax_rate1_total, $this->currency->precision); - if (strlen($this->item->tax_name1) > 2) { + if (strlen($this->item->tax_name1) > 1) { $this->groupTax($this->item->tax_name1, $this->item->tax_rate1, $item_tax_rate1_total); } @@ -239,7 +239,7 @@ class InvoiceItemSumInclusive $item_tax += $this->formatValue($item_tax_rate2_total, $this->currency->precision); - if (strlen($this->item->tax_name2) > 2) { + if (strlen($this->item->tax_name2) > 1) { $this->groupTax($this->item->tax_name2, $this->item->tax_rate2, $item_tax_rate2_total); } @@ -247,7 +247,7 @@ class InvoiceItemSumInclusive $item_tax += $this->formatValue($item_tax_rate3_total, $this->currency->precision); - if (strlen($this->item->tax_name3) > 2) { + if (strlen($this->item->tax_name3) > 1) { $this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total); } diff --git a/app/Helpers/Invoice/InvoiceSum.php b/app/Helpers/Invoice/InvoiceSum.php index fd1472c18e7f..de498a2dfa91 100644 --- a/app/Helpers/Invoice/InvoiceSum.php +++ b/app/Helpers/Invoice/InvoiceSum.php @@ -316,8 +316,10 @@ class InvoiceSum $this->tax_map = collect(); $keys = $this->invoice_items->getGroupedTaxes()->pluck('key')->unique(); +nlog($keys); $values = $this->invoice_items->getGroupedTaxes(); +nlog($values); foreach ($keys as $key) { $tax_name = $values->filter(function ($value, $k) use ($key) { diff --git a/app/Helpers/Invoice/InvoiceSumInclusive.php b/app/Helpers/Invoice/InvoiceSumInclusive.php index d213874e68b2..3786b55f99e6 100644 --- a/app/Helpers/Invoice/InvoiceSumInclusive.php +++ b/app/Helpers/Invoice/InvoiceSumInclusive.php @@ -340,8 +340,7 @@ class InvoiceSumInclusive $this->total_taxes += $total_line_tax; } -nlog($this->tax_map); -nlog($this->total_taxes); + return $this; } diff --git a/config/ninja.php b/config/ninja.php index 3f9061733a16..d9174d53721e 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -17,8 +17,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => env('APP_VERSION', '5.8.30'), - 'app_tag' => env('APP_TAG', '5.8.30'), + 'app_version' => env('APP_VERSION', '5.8.31'), + 'app_tag' => env('APP_TAG', '5.8.31'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', false), diff --git a/tests/Unit/InvoiceItemTest.php b/tests/Unit/InvoiceItemTest.php index 6b736f4005e5..d3aeb1a72999 100644 --- a/tests/Unit/InvoiceItemTest.php +++ b/tests/Unit/InvoiceItemTest.php @@ -36,6 +36,42 @@ class InvoiceItemTest extends TestCase } + public function testEdgeCasewithDiscountsPercentageAndTaxCalculations() + { + $invoice = InvoiceFactory::create($this->company->id, $this->user->id); + $invoice->client_id = $this->client->id; + $invoice->uses_inclusive_taxes = false; + $invoice->is_amount_discount =false; + $invoice->discount = 0; + $invoice->tax_rate1 = 0; + $invoice->tax_rate2 = 0; + $invoice->tax_rate3 = 0; + $invoice->tax_name1 = ''; + $invoice->tax_name2 = ''; + $invoice->tax_name3 = ''; + + $line_items = []; + + $line_item = new InvoiceItem; + $line_item->quantity = 1; + $line_item->cost = 100; + $line_item->tax_rate1 = 22; + $line_item->tax_name1 = 'Km'; + $line_item->product_key = 'Test'; + $line_item->notes = 'Test'; + $line_item->is_amount_discount = false; + $line_items[] = $line_item; + + $invoice->line_items = $line_items; + $invoice->save(); + + $invoice = $invoice->calc()->getInvoice(); + + $this->assertEquals(122, $invoice->amount); + $this->assertEquals(22, $invoice->total_taxes); + } + + public function testDiscountsWithInclusiveTaxes() { $invoice = InvoiceFactory::create($this->company->id, $this->user->id); From 902cae6585e193fdf8f76d1fb60a3250885a9ece Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 29 Feb 2024 12:17:44 +1100 Subject: [PATCH 06/11] Add missing prop --- app/Helpers/Invoice/InvoiceSum.php | 2 -- app/Utils/HtmlEngine.php | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/Helpers/Invoice/InvoiceSum.php b/app/Helpers/Invoice/InvoiceSum.php index de498a2dfa91..fd1472c18e7f 100644 --- a/app/Helpers/Invoice/InvoiceSum.php +++ b/app/Helpers/Invoice/InvoiceSum.php @@ -316,10 +316,8 @@ class InvoiceSum $this->tax_map = collect(); $keys = $this->invoice_items->getGroupedTaxes()->pluck('key')->unique(); -nlog($keys); $values = $this->invoice_items->getGroupedTaxes(); -nlog($values); foreach ($keys as $key) { $tax_name = $values->filter(function ($value, $k) use ($key) { diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index db365f1e8dff..8316b6a7e4a5 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -397,7 +397,8 @@ class HtmlEngine $data['$credit.date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()), 'label' => ctrans('texts.credit_date')]; $data['$balance'] = ['value' => Number::formatMoney($this->getBalance(), $this->client) ?: ' ', 'label' => ctrans('texts.balance')]; $data['$credit.balance'] = ['value' => Number::formatMoney($this->entity_calc->getBalance(), $this->client) ?: ' ', 'label' => ctrans('texts.credit_balance')]; - + $data['$client.credit_balance'] = &$data['$credit.balance']; + $data['$invoice.balance'] = &$data['$balance']; $data['$taxes'] = ['value' => Number::formatMoney($this->entity_calc->getItemTotalTaxes(), $this->client) ?: ' ', 'label' => ctrans('texts.taxes')]; $data['$invoice.taxes'] = &$data['$taxes']; From e22d3effc62cafed2841fa30416586c2c65db497 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 29 Feb 2024 12:31:59 +1100 Subject: [PATCH 07/11] improve parseFloat --- app/Utils/Number.php | 45 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/app/Utils/Number.php b/app/Utils/Number.php index ca7a8ad59eca..6a10998d3538 100644 --- a/app/Utils/Number.php +++ b/app/Utils/Number.php @@ -143,6 +143,51 @@ class Number // return (float) $s; } + /* + //next iteration of float parsing + public static function parseFloatv2($value) + { + + if(!$value) { + return 0; + } + + //remove everything except for numbers, decimals, commas and hyphens + $value = preg_replace('/[^0-9.,-]+/', '', $value); + + $decimal = strpos($value, '.'); + $comma = strpos($value, ','); + + //check the 3rd last character + if(!in_array(substr($value, -3, 1), [".", ","])) { + + if($comma && (substr($value, -3, 1) != ".")) { + $value .= ".00"; + } elseif($decimal && (substr($value, -3, 1) != ",")) { + $value .= ",00"; + } + + } + + $decimal = strpos($value, '.'); + $comma = strpos($value, ','); + + if($comma === false) { //no comma must be a decimal number already + return (float) $value; + } + + if($decimal < $comma) { //decimal before a comma = euro + $value = str_replace(['.',','], ['','.'], $value); + return (float) $value; + } + + //comma first = traditional thousan separator + $value = str_replace(',', '', $value); + + return (float)$value; + + } + */ public static function parseStringFloat($value) { $value = preg_replace('/[^0-9-.]+/', '', $value); From c4807be9dfb1c3ca4fcabf906e57d8d4478af71e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 29 Feb 2024 13:06:11 +1100 Subject: [PATCH 08/11] Updates for parseformat --- app/Models/Document.php | 3 ++- app/Utils/Number.php | 11 ++++----- tests/Unit/NumberTest.php | 47 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/app/Models/Document.php b/app/Models/Document.php index 8b2a45f7a508..53be87341a4d 100644 --- a/app/Models/Document.php +++ b/app/Models/Document.php @@ -211,6 +211,7 @@ class Document extends BaseModel public function link() { $entity_id = $this->encodePrimaryKey($this->documentable_id); + $link = ''; match($this->documentable_type) { 'App\Models\Vendor' => $link = "vendors/{$entity_id}", @@ -222,7 +223,7 @@ class Document extends BaseModel 'App\Models\Payment' => $link = "payments/{$entity_id}/edit", 'App\Models\Task' => $link = "tasks/{$entity_id}/edit", 'App\Models\Client' => $link = "clients/{$entity_id}", - default => $link = '' + default => $link = '', }; return $link; diff --git a/app/Utils/Number.php b/app/Utils/Number.php index 6a10998d3538..53e030093c2c 100644 --- a/app/Utils/Number.php +++ b/app/Utils/Number.php @@ -93,7 +93,7 @@ class Number * @param string $value The formatted number to be converted back to float * @return float The formatted value */ - public static function parseFloat($value) + public static function parseFloat2($value) { if(!$value) return 0; @@ -104,7 +104,7 @@ class Number $decimal = strpos($value, '.'); $comma = strpos($value, ','); - if(!$comma) //no comma must be a decimal number already + if($comma === false) //no comma must be a decimal number already return (float) $value; if($decimal < $comma){ //decimal before a comma = euro @@ -143,9 +143,9 @@ class Number // return (float) $s; } - /* + //next iteration of float parsing - public static function parseFloatv2($value) + public static function parseFloat($value) { if(!$value) { @@ -187,7 +187,8 @@ class Number return (float)$value; } - */ + + public static function parseStringFloat($value) { $value = preg_replace('/[^0-9-.]+/', '', $value); diff --git a/tests/Unit/NumberTest.php b/tests/Unit/NumberTest.php index feecae8abf7d..538a48e338e4 100644 --- a/tests/Unit/NumberTest.php +++ b/tests/Unit/NumberTest.php @@ -20,6 +20,53 @@ use Tests\TestCase; */ class NumberTest extends TestCase { + + public function testRangeOfNumberFormats() + { + + + $floatvals = [ + "22000.76" =>"22 000,76", + "22000.76" =>"22.000,76", + "22000.76" =>"22,000.76", + "22000" =>"22 000", + "22000" =>"22,000", + "22000" =>"22.000", + "22000.76" =>"22000.76", + "22000.76" =>"22000,76", + "1022000.76" =>"1.022.000,76", + "1022000.76" =>"1,022,000.76", + "1000000" =>"1,000,000", + "1000000" =>"1.000.000", + "1022000.76" =>"1022000.76", + "1022000.76" =>"1022000,76", + "1022000" =>"1022000", + "0.76" =>"0.76", + "0.76" =>"0,76", + "0" =>"0.00", + "0" =>"0,00", + "1" =>"1.00", + "1" =>"1,00", + "423545" =>"423545 €", + "423545" =>"423,545 €", + "423545" =>"423.545 €", + "1" =>"1,00 €", + "1.02" =>"€ 1.02", + "1000.02" =>"1'000,02 EUR", + "1000.02" =>"1 000.02$", + "1000.02" =>"1,000.02$", + "1000.02" =>"1.000,02 EURO" + ]; + + + foreach($floatvals as $key => $value) { + + $this->assertEquals($key, Number::parseFloat($value)); + + } + + } + public function testNegativeFloatParse() { From 3772d2a16529a8b8657d9ed1757c9e0ae83f56d8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 29 Feb 2024 14:02:32 +1100 Subject: [PATCH 09/11] Updates for smtp --- app/Http/Controllers/SmtpController.php | 22 +++++++++---- app/Http/Requests/Smtp/CheckSmtpRequest.php | 36 ++++++++++++++++++--- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/SmtpController.php b/app/Http/Controllers/SmtpController.php index b28275e62fff..bf03012f017b 100644 --- a/app/Http/Controllers/SmtpController.php +++ b/app/Http/Controllers/SmtpController.php @@ -30,16 +30,24 @@ class SmtpController extends BaseController $user = auth()->user(); $company = $user->company(); + $smtp_host = $request->input('smtp_host', $company->smtp_host); + $smtp_port = $request->input('smtp_port', $company->smtp_port); + $smtp_username = $request->input('smtp_username', $company->smtp_username); + $smtp_password = $request->input('smtp_password', $company->smtp_password); + $smtp_encryption = $request->input('smtp_encryption', $company->smtp_encryption ?? 'tls'); + $smtp_local_domain = $request->input('smtp_local_domain', strlen($company->smtp_local_domain) > 2 ? $company->smtp_local_domain : null); + $smtp_verify_peer = $request->input('verify_peer', $company->smtp_verify_peer ?? true); + config([ 'mail.mailers.smtp' => [ 'transport' => 'smtp', - 'host' => $request->input('smtp_host', $company->smtp_host), - 'port' => $request->input('smtp_port', $company->smtp_port), - 'username' => $request->input('smtp_username', $company->smtp_username), - 'password' => $request->input('smtp_password', $company->smtp_password), - 'encryption' => $request->input('smtp_encryption', $company->smtp_encryption ?? 'tls'), - 'local_domain' => $request->input('smtp_local_domain', strlen($company->smtp_local_domain) > 2 ? $company->smtp_local_domain : null), - 'verify_peer' => $request->input('verify_peer', $company->smtp_verify_peer ?? true), + 'host' => $smtp_host, + 'port' => $smtp_port, + 'username' => $smtp_username, + 'password' => $smtp_password, + 'encryption' => $smtp_encryption, + 'local_domain' => $smtp_local_domain, + 'verify_peer' => $smtp_verify_peer, 'timeout' => 5, ], ]); diff --git a/app/Http/Requests/Smtp/CheckSmtpRequest.php b/app/Http/Requests/Smtp/CheckSmtpRequest.php index d4a4c22914af..186a68271425 100644 --- a/app/Http/Requests/Smtp/CheckSmtpRequest.php +++ b/app/Http/Requests/Smtp/CheckSmtpRequest.php @@ -36,18 +36,46 @@ class CheckSmtpRequest extends Request public function rules() { return [ + 'smtp_host' => 'sometimes|nullable|string|min:3', + 'smtp_port' => 'sometimes|nullable|integer', + 'smtp_username' => 'sometimes|nullable|string|min:3', + 'smtp_password' => 'sometimes|nullable|string|min:3', ]; } public function prepareForValidation() { + + /** @var \App\Models\User $user */ + $user = auth()->user(); + $company = $user->company(); + $input = $this->input(); - if(isset($input['smtp_username']) && $input['smtp_username'] == '********') - unset($input['smtp_username']); + if(isset($input['smtp_username']) && $input['smtp_username'] == '********'){ + // unset($input['smtp_username']); + $input['smtp_username'] = $company->smtp_username; + } + + if(isset($input['smtp_password'])&& $input['smtp_password'] == '********'){ + // unset($input['smtp_password']); + $input['smtp_password'] = $company->smtp_password; + } + + if(isset($input['smtp_host']) && strlen($input['smtp_host']) >=3){ + + } + else { + $input['smtp_host'] = $company->smtp_host; + } + + + if(isset($input['smtp_port']) && strlen($input['smtp_port']) >= 3) { + + } else { + $input['smtp_port'] = $company->smtp_port; + } - if(isset($input['smtp_password'])&& $input['smtp_password'] == '********') - unset($input['smtp_password']); $this->replace($input); } From 631527e8e3a0bfd688a721aa1a13fda1be2716fe Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 29 Feb 2024 19:58:59 +1100 Subject: [PATCH 10/11] Fixes for redirect after stripe connect --- app/Http/Controllers/StripeConnectController.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/StripeConnectController.php b/app/Http/Controllers/StripeConnectController.php index a8de13091bf4..e28eb61d858f 100644 --- a/app/Http/Controllers/StripeConnectController.php +++ b/app/Http/Controllers/StripeConnectController.php @@ -54,6 +54,8 @@ class StripeConnectController extends BaseController $redirect_uri = config('ninja.app_url').'/stripe/completed'; $endpoint = "https://connect.stripe.com/oauth/authorize?response_type=code&client_id={$stripe_client_id}&redirect_uri={$redirect_uri}&scope=read_write&state={$token}"; + \Illuminate\Support\Facades\Cache::pull($token); + return redirect($endpoint); } @@ -148,7 +150,8 @@ class StripeConnectController extends BaseController } //response here - return view('auth.connect.completed', ['url' => $redirect_uri]); + // return view('auth.connect.completed', ['url' => $redirect_uri]); + return redirect($redirect_uri); } } From d9184bb0ab4c738f8e39736c6e9ad4547d3111ee Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 29 Feb 2024 21:34:29 +1100 Subject: [PATCH 11/11] Fixes for stripe connect --- app/Http/Controllers/StripeConnectController.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/StripeConnectController.php b/app/Http/Controllers/StripeConnectController.php index e28eb61d858f..3aa9639a48c0 100644 --- a/app/Http/Controllers/StripeConnectController.php +++ b/app/Http/Controllers/StripeConnectController.php @@ -66,6 +66,8 @@ class StripeConnectController extends BaseController if ($request->has('error') && $request->error == 'access_denied') { return view('auth.connect.access_denied'); } + + $response = false; try { /** @class \stdClass $response @@ -90,6 +92,11 @@ class StripeConnectController extends BaseController nlog($response); } catch (\Exception $e) { + + + } + + if(!$response) { return view('auth.connect.access_denied'); } @@ -146,12 +153,12 @@ class StripeConnectController extends BaseController if(isset($request->getTokenContent()['is_react']) && $request->getTokenContent()['is_react']) { $redirect_uri = config('ninja.react_url').'/#/settings/online_payments'; } else { - $redirect_uri = config('ninja.app_url').'/stripe/completed'; + $redirect_uri = config('ninja.app_url'); } //response here - // return view('auth.connect.completed', ['url' => $redirect_uri]); - return redirect($redirect_uri); + return view('auth.connect.completed', ['url' => $redirect_uri]); + // return redirect($redirect_uri); } }