Merge remote-tracking branch 'upstream/v5-develop' into 1314-subscriptions-v3

This commit is contained in:
Benjamin Beganović 2024-03-15 01:29:22 +01:00
commit e9be90779a
14 changed files with 219 additions and 45 deletions

View File

@ -1 +1 @@
5.8.36 5.8.37

View File

@ -159,7 +159,7 @@ class SwissQrGenerator
// Optionally, add some human-readable information about what the bill is for. // Optionally, add some human-readable information about what the bill is for.
$qrBill->setAdditionalInformation( $qrBill->setAdditionalInformation(
QrBill\DataGroup\Element\AdditionalInformation::create( 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])
) )
); );

View File

@ -158,7 +158,6 @@ class LicenseController extends BaseController
/* Catch claim license requests */ /* Catch claim license requests */
if (config('ninja.environment') == 'selfhost') { if (config('ninja.environment') == 'selfhost') {
// $response = Http::get( "http://ninja.test:8000/claim_license", [
$response = Http::get("https://invoicing.co/claim_license", [ $response = Http::get("https://invoicing.co/claim_license", [
'license_key' => $license_key, 'license_key' => $license_key,
'product_id' => 3, 'product_id' => 3,

View File

@ -63,7 +63,7 @@ class UpdateClientRequest extends Request
$rules['country_id'] = 'integer|nullable|exists:countries,id'; $rules['country_id'] = 'integer|nullable|exists:countries,id';
$rules['shipping_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['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['number'] = ['sometimes', 'bail', Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id)];
$rules['settings'] = new ValidClientGroupSettingsRule(); $rules['settings'] = new ValidClientGroupSettingsRule();

View File

@ -52,7 +52,7 @@ class ClientStatement extends Mailable
public function content() public function content()
{ {
return new 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', text: 'email.template.text',
with: [ with: [
'text_body' => $this->data['body'], 'text_body' => $this->data['body'],

View File

@ -75,7 +75,8 @@ class TemplateEmail extends Mailable
$template_name = 'email.template.'.$this->build_email->getTemplate(); $template_name = 'email.template.'.$this->build_email->getTemplate();
if ($this->build_email->getTemplate() == 'light' || $this->build_email->getTemplate() == 'dark') { 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') { if ($this->build_email->getTemplate() == 'custom') {

View File

@ -72,7 +72,8 @@ class VendorTemplateEmail extends Mailable
$template_name = 'email.template.'.$this->build_email->getTemplate(); $template_name = 'email.template.'.$this->build_email->getTemplate();
if ($this->build_email->getTemplate() == 'light' || $this->build_email->getTemplate() == 'dark') { 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') { if ($this->build_email->getTemplate() == 'custom') {

View File

@ -107,10 +107,10 @@ class EmailDefaults
match ($this->email->email_object->settings->email_style) { match ($this->email->email_object->settings->email_style) {
'plain' => $this->template = 'email.template.plain', 'plain' => $this->template = 'email.template.plain',
'light' => $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 = $this->email->email_object->company->account->isPremium() ? 'email.template.client_premium' :'email.template.client', 'dark' => $this->template = 'email.template.client',
'custom' => $this->template = 'email.template.custom', '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; $this->email->email_object->html_template = $this->template;

View File

@ -86,6 +86,33 @@ class Number
return rtrim(rtrim(number_format($value, $precision, $decimal, $thousand), '0'), $decimal); 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 * Formats a given value based on the clients currency
* BACK to a float. * BACK to a float.
@ -93,32 +120,9 @@ class Number
* @param string $value The formatted number to be converted back to float * @param string $value The formatted number to be converted back to float
* @return float The formatted value * @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) if(!$value)
return 0; return 0;

View File

@ -17,8 +17,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => env('APP_VERSION', '5.8.36'), 'app_version' => env('APP_VERSION', '5.8.37'),
'app_tag' => env('APP_TAG', '5.8.36'), 'app_tag' => env('APP_TAG', '5.8.37'),
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', false), 'api_secret' => env('API_SECRET', false),

View File

@ -0,0 +1,45 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('invoices', function (Blueprint $table) {
$table->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
{
//
}
};

View File

@ -5269,7 +5269,7 @@ $lang = array(
'payment_provider' => 'Payment Provider', 'payment_provider' => 'Payment Provider',
'select_email_provider' => 'Set your email as the sending user', 'select_email_provider' => 'Set your email as the sending user',
'purchase_order_items' => 'Purchase Order Items', 'purchase_order_items' => 'Purchase Order Items',
'csv_rows_length' => 'No data found in this CSV file',
); );
return $lang; return $lang;

View File

@ -20,6 +20,7 @@ use App\Models\Subscription;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use App\Models\RecurringInvoice; use App\Models\RecurringInvoice;
use App\Factory\InvoiceItemFactory;
use App\Helpers\Invoice\InvoiceSum; use App\Helpers\Invoice\InvoiceSum;
use App\Repositories\InvoiceRepository; use App\Repositories\InvoiceRepository;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@ -51,6 +52,116 @@ class InvoiceTest extends TestCase
$this->makeTestData(); $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() public function testInvoicePaymentLinkMutation()
{ {

View File

@ -24,20 +24,20 @@ class NumberTest extends TestCase
public function testRangeOfNumberFormats() public function testRangeOfNumberFormats()
{ {
$floatvals = [ $floatvals = [
"22000.76" =>"22 000,76", "22000.76" =>"22 000,76",
"22000.76" =>"22.000,76", "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" =>"22,000",
"22000" =>"22.000", "22" =>"22.000",
"22000" =>"22.000,",
"22000.76" =>"22000.76", "22000.76" =>"22000.76",
"22000.76" =>"22000,76", "22000.76" =>"22000,76",
"1022000.76" =>"1.022.000,76", "1022000.76" =>"1.022.000,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.76" =>"1022000,76", "1022000.76" =>"1022000,76",
"1022000" =>"1022000", "1022000" =>"1022000",
@ -48,26 +48,39 @@ class NumberTest extends TestCase
"1" =>"1.00", "1" =>"1.00",
"1" =>"1,00", "1" =>"1,00",
"423545" =>"423545 €", "423545" =>"423545 €",
"423545" =>"423,545 €", // "423545" =>"423,545 €",
"423545" =>"423.545 €", // "423545" =>"423.545 €",
"1" =>"1,00 €", "1" =>"1,00 €",
"1.02" =>"€ 1.02", "1.02" =>"€ 1.02",
"1000.02" =>"1'000,02 EUR", "1000.02" =>"1'000,02 EUR",
"1000.02" =>"1 000.02$", "1000.02" =>"1 000.02$",
"1000.02" =>"1,000.02$", "1000.02" =>"1,000.02$",
"1000.02" =>"1.000,02 EURO", "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) { 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() public function testNegativeFloatParse()
{ {