diff --git a/VERSION.txt b/VERSION.txt index d47c52353191..5c637b26e9b1 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.8.36 \ No newline at end of file +5.8.37 \ No newline at end of file diff --git a/app/Helpers/SwissQr/SwissQrGenerator.php b/app/Helpers/SwissQr/SwissQrGenerator.php index 2ccc73ed3c4b..86ed38330aa2 100644 --- a/app/Helpers/SwissQr/SwissQrGenerator.php +++ b/app/Helpers/SwissQr/SwissQrGenerator.php @@ -159,7 +159,7 @@ class SwissQrGenerator // Optionally, add some human-readable information about what the bill is for. $qrBill->setAdditionalInformation( QrBill\DataGroup\Element\AdditionalInformation::create( - $this->invoice->public_notes ? substr($this->invoice->public_notes, 0, 139) : ctrans('texts.invoice_number_placeholder', ['invoice' => $this->invoice->number]) + $this->invoice->public_notes ? substr(strip_tags($this->invoice->public_notes), 0, 139) : ctrans('texts.invoice_number_placeholder', ['invoice' => $this->invoice->number]) ) ); diff --git a/app/Http/Controllers/LicenseController.php b/app/Http/Controllers/LicenseController.php index 78dec754323f..1de2b6d28370 100644 --- a/app/Http/Controllers/LicenseController.php +++ b/app/Http/Controllers/LicenseController.php @@ -158,7 +158,6 @@ class LicenseController extends BaseController /* Catch claim license requests */ if (config('ninja.environment') == 'selfhost') { - // $response = Http::get( "http://ninja.test:8000/claim_license", [ $response = Http::get("https://invoicing.co/claim_license", [ 'license_key' => $license_key, 'product_id' => 3, diff --git a/app/Http/Requests/Client/UpdateClientRequest.php b/app/Http/Requests/Client/UpdateClientRequest.php index b0307d312da3..3ffea3250f05 100644 --- a/app/Http/Requests/Client/UpdateClientRequest.php +++ b/app/Http/Requests/Client/UpdateClientRequest.php @@ -63,7 +63,7 @@ class UpdateClientRequest extends Request $rules['country_id'] = 'integer|nullable|exists:countries,id'; $rules['shipping_country_id'] = 'integer|nullable|exists:countries,id'; $rules['classification'] = 'bail|sometimes|nullable|in:individual,business,company,partnership,trust,charity,government,other'; - $rules['id_number'] = ['sometimes', 'bail', Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id)]; + $rules['id_number'] = ['sometimes', 'bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id)]; $rules['number'] = ['sometimes', 'bail', Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id)]; $rules['settings'] = new ValidClientGroupSettingsRule(); diff --git a/app/Mail/Client/ClientStatement.php b/app/Mail/Client/ClientStatement.php index d7a131fa2f0f..b715840083ed 100644 --- a/app/Mail/Client/ClientStatement.php +++ b/app/Mail/Client/ClientStatement.php @@ -52,7 +52,7 @@ class ClientStatement extends Mailable public function content() { return new Content( - view: $this->data['company']->account->isPremium() ? 'email.template.client_premium' : 'email.template.client', + view: 'email.template.client', text: 'email.template.text', with: [ 'text_body' => $this->data['body'], diff --git a/app/Mail/TemplateEmail.php b/app/Mail/TemplateEmail.php index 84814520e9db..9beac3dbe3b2 100644 --- a/app/Mail/TemplateEmail.php +++ b/app/Mail/TemplateEmail.php @@ -75,7 +75,8 @@ class TemplateEmail extends Mailable $template_name = 'email.template.'.$this->build_email->getTemplate(); if ($this->build_email->getTemplate() == 'light' || $this->build_email->getTemplate() == 'dark') { - $template_name = $this->company->account->isPremium() ? 'email.template.client_premium' : 'email.template.client'; + // $template_name = $this->company->account->isPremium() ? 'email.template.client_premium' : 'email.template.client'; + $template_name = 'email.template.client'; } if ($this->build_email->getTemplate() == 'custom') { diff --git a/app/Mail/VendorTemplateEmail.php b/app/Mail/VendorTemplateEmail.php index 593d711e7cf4..6a6a3a66fc7b 100644 --- a/app/Mail/VendorTemplateEmail.php +++ b/app/Mail/VendorTemplateEmail.php @@ -72,7 +72,8 @@ class VendorTemplateEmail extends Mailable $template_name = 'email.template.'.$this->build_email->getTemplate(); if ($this->build_email->getTemplate() == 'light' || $this->build_email->getTemplate() == 'dark') { - $template_name = $this->company->account->isPremium() ? 'email.template.client_premium' : 'email.template.client'; + // $template_name = $this->company->account->isPremium() ? 'email.template.client_premium' : 'email.template.client'; + $template_name = 'email.template.client'; } if ($this->build_email->getTemplate() == 'custom') { diff --git a/app/Services/Email/EmailDefaults.php b/app/Services/Email/EmailDefaults.php index 06835c75cda8..04b945ff47c8 100644 --- a/app/Services/Email/EmailDefaults.php +++ b/app/Services/Email/EmailDefaults.php @@ -107,10 +107,10 @@ class EmailDefaults match ($this->email->email_object->settings->email_style) { 'plain' => $this->template = 'email.template.plain', - 'light' => $this->template = $this->email->email_object->company->account->isPremium() ? 'email.template.client_premium' : 'email.template.client', - 'dark' => $this->template = $this->email->email_object->company->account->isPremium() ? 'email.template.client_premium' :'email.template.client', + 'light' => $this->template = 'email.template.client', + 'dark' => $this->template = 'email.template.client', 'custom' => $this->template = 'email.template.custom', - default => $this->template = $this->email->email_object->company->account->isPremium() ? 'email.template.client_premium' :'email.template.client', + default => $this->template = 'email.template.client', }; $this->email->email_object->html_template = $this->template; diff --git a/app/Utils/Number.php b/app/Utils/Number.php index bc1739f64c9f..16e82c823194 100644 --- a/app/Utils/Number.php +++ b/app/Utils/Number.php @@ -86,6 +86,33 @@ class Number return rtrim(rtrim(number_format($value, $precision, $decimal, $thousand), '0'), $decimal); } + public static function parseFloat($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, ','); + + 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 thousand separator + $value = str_replace(',', '', $value); + + return (float)$value; + + + } /** * Formats a given value based on the clients currency * BACK to a float. @@ -93,32 +120,9 @@ 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 parseFloatXX($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, ','); - - // 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); - // // $value = str_replace(',', '.', $value); - // return (float) $value; - // } - - // //comma first = traditional thousan separator - // $value = str_replace(',', '', $value); - - // return (float)$value; - - if(!$value) return 0; diff --git a/config/ninja.php b/config/ninja.php index 76621119f951..47ecd5b1aa42 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.36'), - 'app_tag' => env('APP_TAG', '5.8.36'), + 'app_version' => env('APP_VERSION', '5.8.37'), + 'app_tag' => env('APP_TAG', '5.8.37'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', false), diff --git a/database/migrations/2024_03_14_201844_adjust_discount_column_max_resolution.php b/database/migrations/2024_03_14_201844_adjust_discount_column_max_resolution.php new file mode 100644 index 000000000000..3aa0099e3cf0 --- /dev/null +++ b/database/migrations/2024_03_14_201844_adjust_discount_column_max_resolution.php @@ -0,0 +1,45 @@ +decimal('discount', 20, 6)->default(0)->change(); + }); + + + Schema::table('credits', function (Blueprint $table) { + $table->decimal('discount', 20, 6)->default(0)->change(); + }); + + Schema::table('quotes', function (Blueprint $table) { + $table->decimal('discount', 20, 6)->default(0)->change(); + }); + + Schema::table('purchase_orders', function (Blueprint $table) { + $table->decimal('discount', 20, 6)->default(0)->change(); + }); + + Schema::table('recurring_invoices', function (Blueprint $table) { + $table->decimal('discount', 20, 6)->default(0)->change(); + }); + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // + } +}; diff --git a/lang/en/texts.php b/lang/en/texts.php index 3089ec7dc45a..effcbbc80c72 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5269,7 +5269,7 @@ $lang = array( 'payment_provider' => 'Payment Provider', 'select_email_provider' => 'Set your email as the sending user', 'purchase_order_items' => 'Purchase Order Items', - + 'csv_rows_length' => 'No data found in this CSV file', ); return $lang; diff --git a/tests/Feature/InvoiceTest.php b/tests/Feature/InvoiceTest.php index d41a23600555..ff0c79bf41c1 100644 --- a/tests/Feature/InvoiceTest.php +++ b/tests/Feature/InvoiceTest.php @@ -20,6 +20,7 @@ use App\Models\Subscription; use App\Models\ClientContact; use App\Utils\Traits\MakesHash; use App\Models\RecurringInvoice; +use App\Factory\InvoiceItemFactory; use App\Helpers\Invoice\InvoiceSum; use App\Repositories\InvoiceRepository; use Illuminate\Database\Eloquent\Model; @@ -51,6 +52,116 @@ class InvoiceTest extends TestCase $this->makeTestData(); } + public function testMaxDiscount() + { + + + $line_items = []; + + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 100000000; + $item->type_id = '1'; + + $line_items[] = $item; + + $data = [ + 'status_id' => 1, + 'number' => '', + 'discount' => 0, + 'is_amount_discount' => 1, + 'po_number' => '3434343', + 'public_notes' => 'notes', + 'is_deleted' => 0, + 'custom_value1' => 0, + 'custom_value2' => 0, + 'custom_value3' => 0, + 'custom_value4' => 0, + 'client_id' => $this->client->hashed_id, + 'line_items' => $line_items, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/invoices?mark_sent=true',$data) + ->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals(2, $arr['data']['status_id']); + $this->assertEquals(100000000, $arr['data']['amount']); + $this->assertEquals(100000000, $arr['data']['balance']); + + $data = [ + 'status_id' => 1, + 'number' => '', + 'discount' => 100000000, + 'is_amount_discount' => 1, + 'po_number' => '3434343', + 'public_notes' => 'notes', + 'is_deleted' => 0, + 'custom_value1' => 0, + 'custom_value2' => 0, + 'custom_value3' => 0, + 'custom_value4' => 0, + 'client_id' => $this->client->hashed_id, + 'line_items' => $line_items, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/invoices?mark_sent=true', $data) + ->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals(2, $arr['data']['status_id']); + $this->assertEquals(0, $arr['data']['amount']); + $this->assertEquals(0, $arr['data']['balance']); + $this->assertEquals(100000000, $arr['data']['discount']); + + $line_items = []; + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 100000000; + $item->discount = 100000000; + $item->type_id = '1'; + + $line_items[] = $item; + + $data = [ + 'status_id' => 1, + 'number' => '', + 'discount' => 0, + 'is_amount_discount' => 1, + 'po_number' => '3434343', + 'public_notes' => 'notes', + 'is_deleted' => 0, + 'custom_value1' => 0, + 'custom_value2' => 0, + 'custom_value3' => 0, + 'custom_value4' => 0, + 'client_id' => $this->client->hashed_id, + 'line_items' => $line_items, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/invoices?mark_sent=true', $data) + ->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals(2, $arr['data']['status_id']); + $this->assertEquals(0, $arr['data']['amount']); + $this->assertEquals(0, $arr['data']['balance']); + + + } + public function testInvoicePaymentLinkMutation() { diff --git a/tests/Unit/NumberTest.php b/tests/Unit/NumberTest.php index 82b7ae8ba530..191a034517e0 100644 --- a/tests/Unit/NumberTest.php +++ b/tests/Unit/NumberTest.php @@ -24,20 +24,20 @@ 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", + "22" =>"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", + // "1000000" =>"1,000,000", + // "1000000" =>"1.000.000", "1022000.76" =>"1022000.76", "1022000.76" =>"1022000,76", "1022000" =>"1022000", @@ -48,26 +48,39 @@ class NumberTest extends TestCase "1" =>"1.00", "1" =>"1,00", "423545" =>"423545 €", - "423545" =>"423,545 €", - "423545" =>"423.545 €", + // "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", - "9.975" => "9.975" + "9.975" => "9.975", + "9975" => "9.975,", + "9975" => "9.975,00" ]; foreach($floatvals as $key => $value) { - // $this->assertEquals($key, Number::parseFloat2($value)); + $this->assertEquals($key, Number::parseFloat($value)); } } + public function testThreeDecimalFloatAsTax() + { + + $value = '9.975'; + + $res = Number::parseFloat($value); + + $this->assertEquals(9.975, $res); + + } + public function testNegativeFloatParse() {