diff --git a/VERSION.txt b/VERSION.txt index d6403175aa5d..a905c895a289 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.3.10 \ No newline at end of file +5.3.12 \ No newline at end of file diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 288a0a60a8d0..8b7bf4c6ff30 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -654,6 +654,7 @@ class CompanySettings extends BaseSettings '$product.discount', '$product.tax', '$product.line_total', + '$product.gross_line_total', ], 'task_columns' =>[ '$task.service', @@ -663,9 +664,11 @@ class CompanySettings extends BaseSettings '$task.discount', '$task.tax', '$task.line_total', + '$task.gross_line_total', ], 'total_columns' => [ '$net_subtotal', + '$gross_subtotal', '$subtotal', '$discount', '$custom_surcharge1', diff --git a/app/DataMapper/InvoiceItem.php b/app/DataMapper/InvoiceItem.php index 48eb8aaa326a..70a6e9d6bf2c 100644 --- a/app/DataMapper/InvoiceItem.php +++ b/app/DataMapper/InvoiceItem.php @@ -43,6 +43,8 @@ class InvoiceItem public $line_total = 0; + public $gross_line_total = 0; + public $date = ''; public $custom_value1 = ''; @@ -72,6 +74,7 @@ class InvoiceItem 'tax_rate3' => 'float', 'sort_id' => 'string', 'line_total' => 'float', + 'gross_line_total' => 'float', 'date' => 'string', 'custom_value1' => 'string', 'custom_value2' => 'string', diff --git a/app/Factory/InvoiceItemFactory.php b/app/Factory/InvoiceItemFactory.php index 970754561a53..e0895a88606c 100644 --- a/app/Factory/InvoiceItemFactory.php +++ b/app/Factory/InvoiceItemFactory.php @@ -35,6 +35,7 @@ class InvoiceItemFactory $item->tax_rate3 = 0; $item->sort_id = 0; $item->line_total = 0; + $item->gross_line_total = 0; $item->custom_value1 = ''; $item->custom_value2 = ''; $item->custom_value3 = ''; diff --git a/app/Helpers/Invoice/InvoiceItemSum.php b/app/Helpers/Invoice/InvoiceItemSum.php index a542c017c926..ba20fb2712b1 100644 --- a/app/Helpers/Invoice/InvoiceItemSum.php +++ b/app/Helpers/Invoice/InvoiceItemSum.php @@ -28,6 +28,8 @@ class InvoiceItemSum private $line_total; + private $gross_line_total; + private $currency; private $total_taxes; @@ -38,6 +40,8 @@ class InvoiceItemSum private $sub_total; + private $gross_sub_total; + private $total_discount; private $tax_collection; @@ -83,6 +87,8 @@ class InvoiceItemSum { $this->sub_total += $this->getLineTotal(); + $this->gross_sub_total += $this->getGrossLineTotal(); + $this->line_items[] = $this->item; return $this; @@ -144,10 +150,11 @@ class InvoiceItemSum if($item_tax_rate3_total != 0) $this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total); - $this->setTotalTaxes($this->formatValue($item_tax, $this->currency->precision)); + $this->item->gross_line_total = $this->getLineTotal() + $item_tax; + return $this; } @@ -186,6 +193,11 @@ class InvoiceItemSum return $this->item->line_total; } + public function getGrossLineTotal() + { + return $this->item->gross_line_total; + } + public function getLineItems() { return $this->line_items; @@ -208,6 +220,11 @@ class InvoiceItemSum return $this->sub_total; } + public function getGrossSubTotal() + { + return $this->gross_sub_total; + } + public function setSubTotal($value) { $this->sub_total = $value; @@ -263,6 +280,7 @@ class InvoiceItemSum if ($item_tax_rate3_total != 0) { $this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total); } + } $this->setTotalTaxes($item_tax); diff --git a/app/Helpers/Invoice/InvoiceItemSumInclusive.php b/app/Helpers/Invoice/InvoiceItemSumInclusive.php index c65d0a520a9f..7f4620ee54bc 100644 --- a/app/Helpers/Invoice/InvoiceItemSumInclusive.php +++ b/app/Helpers/Invoice/InvoiceItemSumInclusive.php @@ -177,6 +177,11 @@ class InvoiceItemSumInclusive return $this->item->line_total; } + public function getGrossLineTotal() + { + return $this->item->line_total; + } + public function getLineItems() { return $this->line_items; @@ -199,6 +204,11 @@ class InvoiceItemSumInclusive return $this->sub_total; } + public function getGrossSubTotal() + { + return $this->sub_total; + } + public function setSubTotal($value) { $this->sub_total = $value; diff --git a/app/Helpers/Invoice/InvoiceSum.php b/app/Helpers/Invoice/InvoiceSum.php index 53012df09105..a66a04d912f1 100644 --- a/app/Helpers/Invoice/InvoiceSum.php +++ b/app/Helpers/Invoice/InvoiceSum.php @@ -42,6 +42,8 @@ class InvoiceSum private $sub_total; + private $gross_sub_total; + /** * Constructs the object with Invoice and Settings object. * @@ -75,7 +77,8 @@ class InvoiceSum $this->invoice->line_items = $this->invoice_items->getLineItems(); $this->total = $this->invoice_items->getSubTotal(); $this->setSubTotal($this->invoice_items->getSubTotal()); - + $this->setGrossSubTotal($this->invoice_items->getGrossSubTotal()); + return $this; } @@ -266,6 +269,18 @@ class InvoiceSum return $this; } + public function getGrossSubTotal() + { + return $this->gross_sub_total; + } + + public function setGrossSubTotal($value) + { + $this->gross_sub_total = $value; + + return $this; + } + public function getTotalDiscount() { return $this->total_discount; diff --git a/app/Helpers/Invoice/InvoiceSumInclusive.php b/app/Helpers/Invoice/InvoiceSumInclusive.php index 9eaf75b15cff..49a0b5593da5 100644 --- a/app/Helpers/Invoice/InvoiceSumInclusive.php +++ b/app/Helpers/Invoice/InvoiceSumInclusive.php @@ -259,6 +259,11 @@ class InvoiceSumInclusive return $this->sub_total; } + public function getGrossSubTotal() + { + return $this->sub_total; + } + public function setSubTotal($value) { $this->sub_total = $value; diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 466cd559525f..b4cfa4aabf4a 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -738,6 +738,7 @@ class BaseController extends Controller //pass referral code to front end $data['rc'] = request()->has('rc') ? request()->input('rc') : ''; $data['build'] = request()->has('build') ? request()->input('build') : ''; + $data['user_agent'] = request()->server('HTTP_USER_AGENT'); $data['path'] = $this->setBuild(); diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 7f52eedb2968..631819765c22 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -512,6 +512,9 @@ class ClientController extends BaseController $ids = request()->input('ids'); $clients = Client::withTrashed()->whereIn('id', $this->transformKeys($ids))->cursor(); + if(!in_array($action, ['restore','archive','delete'])) + return response()->json(['message' => 'That action is not available.'], 400); + $clients->each(function ($client, $key) use ($action) { if (auth()->user()->can('edit', $client)) { $this->client_repo->{$action}($client); diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index 3a073b374afd..c47fa3f2178a 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -210,7 +210,10 @@ class QuoteController extends BaseController $quote = $this->quote_repo->save($request->all(), QuoteFactory::create(auth()->user()->company()->id, auth()->user()->id)); - $quote = $quote->service()->fillDefaults()->save(); + $quote = $quote->service() + ->fillDefaults() + ->triggeredActions($request) + ->save(); event(new QuoteWasCreated($quote, $quote->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); diff --git a/app/Models/Client.php b/app/Models/Client.php index 2cf3509897fe..dfbba29ed506 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -75,7 +75,6 @@ class Client extends BaseModel implements HasLocalePreference 'shipping_postal_code', 'shipping_country_id', 'settings', - 'payment_terms', 'vat_number', 'id_number', 'group_settings_id', diff --git a/app/PaymentDrivers/Stripe/SOFORT.php b/app/PaymentDrivers/Stripe/SOFORT.php index 821698e16054..f9d6bfd2e0a6 100644 --- a/app/PaymentDrivers/Stripe/SOFORT.php +++ b/app/PaymentDrivers/Stripe/SOFORT.php @@ -42,6 +42,7 @@ class SOFORT $data['return_url'] = $this->buildReturnUrl(); $data['stripe_amount'] = $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency()); $data['client'] = $this->stripe->client; + $data['customer'] = $this->stripe->findOrCreateCustomer()->id; $data['country'] = $this->stripe->client->country->iso_3166_2; $this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, ['stripe_amount' => $data['stripe_amount']]); diff --git a/app/Services/Quote/QuoteService.php b/app/Services/Quote/QuoteService.php index 8d2435ae6533..6d1fd5e340fb 100644 --- a/app/Services/Quote/QuoteService.php +++ b/app/Services/Quote/QuoteService.php @@ -16,6 +16,7 @@ use App\Jobs\Util\UnlinkFile; use App\Models\Invoice; use App\Models\Quote; use App\Repositories\QuoteRepository; +use App\Services\Quote\TriggeredActions; use App\Utils\Ninja; use App\Utils\Traits\MakesHash; @@ -177,6 +178,13 @@ class QuoteService return $this; } + public function triggeredActions($request) + { + $this->quote = (new TriggeredActions($this->quote, $request))->run(); + + return $this; + } + public function deletePdf() { $this->quote->invitations->each(function ($invitation){ diff --git a/app/Services/Quote/TriggeredActions.php b/app/Services/Quote/TriggeredActions.php new file mode 100644 index 000000000000..055d2216df89 --- /dev/null +++ b/app/Services/Quote/TriggeredActions.php @@ -0,0 +1,66 @@ +request = $request; + + $this->quote = $quote; + } + + public function run() + { + + if ($this->request->has('send_email') && $this->request->input('send_email') == 'true') { + $this->sendEmail(); + } + + if ($this->request->has('mark_sent') && $this->request->input('mark_sent') == 'true') { + $this->quote = $this->quote->service()->markSent()->save(); + } + + + return $this->quote; + } + + private function sendEmail() + { + + $reminder_template = $this->quote->calculateTemplate('quote'); + //$reminder_template = 'payment'; + + $this->quote->invitations->load('contact.client.country', 'quote.client.country', 'quote.company')->each(function ($invitation) use ($reminder_template) { + EmailEntity::dispatch($invitation, $this->quote->company, $reminder_template); + }); + + if ($this->quote->invitations->count() > 0) { + event(new QuoteWasEmailed($this->quote->invitations->first(), $this->quote->company, Ninja::eventVars(), 'quote')); + } + } +} diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 5621c0c00704..a678ce62721e 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -175,6 +175,7 @@ class HtmlEngine $data['$invoice.discount'] = ['value' => Number::formatMoney($this->entity_calc->getTotalDiscount(), $this->client) ?: ' ', 'label' => ctrans('texts.discount')]; $data['$discount'] = &$data['$invoice.discount']; $data['$subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.subtotal')]; + $data['$gross_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getGrossSubTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.subtotal')]; if($this->entity->uses_inclusive_taxes) $data['$net_subtotal'] = ['value' => Number::formatMoney(($this->entity_calc->getSubTotal() - $this->entity->total_taxes), $this->client) ?: ' ', 'label' => ctrans('texts.net_subtotal')]; @@ -380,6 +381,7 @@ class HtmlEngine $data['$product.tax_name2'] = ['value' => '', 'label' => ctrans('texts.tax')]; $data['$product.tax_name3'] = ['value' => '', 'label' => ctrans('texts.tax')]; $data['$product.line_total'] = ['value' => '', 'label' => ctrans('texts.line_total')]; + $data['$product.gross_line_total'] = ['value' => '', 'label' => ctrans('texts.line_total')]; $data['$product.description'] = ['value' => '', 'label' => ctrans('texts.description')]; $data['$product.unit_cost'] = ['value' => '', 'label' => ctrans('texts.unit_cost')]; $data['$product.product1'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'product1')]; @@ -399,6 +401,7 @@ class HtmlEngine $data['$task.tax_name2'] = ['value' => '', 'label' => ctrans('texts.tax')]; $data['$task.tax_name3'] = ['value' => '', 'label' => ctrans('texts.tax')]; $data['$task.line_total'] = ['value' => '', 'label' => ctrans('texts.line_total')]; + $data['$task.gross_line_total'] = ['value' => '', 'label' => ctrans('texts.line_total')]; $data['$task.service'] = ['value' => '', 'label' => ctrans('texts.service')]; $data['$task.task1'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'task1')]; $data['$task.task2'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'task2')]; diff --git a/app/Utils/Traits/MakesInvoiceValues.php b/app/Utils/Traits/MakesInvoiceValues.php index c51486aa3237..f9813ef96595 100644 --- a/app/Utils/Traits/MakesInvoiceValues.php +++ b/app/Utils/Traits/MakesInvoiceValues.php @@ -311,6 +311,7 @@ trait MakesInvoiceValues $data[$key][$table_type.'.cost'] = Number::formatMoney($item->cost, $this->client); $data[$key][$table_type.'.line_total'] = Number::formatMoney($item->line_total, $this->client); + $data[$key][$table_type.'.gross_line_total'] = Number::formatMoney($item->gross_line_total, $this->client); if (isset($item->discount) && $item->discount > 0) { if ($item->is_amount_discount) { diff --git a/config/ninja.php b/config/ninja.php index 1364c4d095ed..6efdd3ae38d2 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -14,8 +14,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => '5.3.10', - 'app_tag' => '5.3.10', + 'app_version' => '5.3.12', + 'app_tag' => '5.3.12', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), diff --git a/resources/js/clients/payments/stripe-sofort.js b/resources/js/clients/payments/stripe-sofort.js index ec8aeb4efa53..2caf346ed232 100644 --- a/resources/js/clients/payments/stripe-sofort.js +++ b/resources/js/clients/payments/stripe-sofort.js @@ -27,6 +27,7 @@ class ProcessSOFORT { handle = () => { let data = { type: 'sofort', + customer: document.querySelector('meta[name="customer"]').content, amount: document.querySelector('meta[name="amount"]').content, currency: 'eur', redirect: { diff --git a/resources/views/index/index.blade.php b/resources/views/index/index.blade.php index 178ffca48b76..03f70989f814 100644 --- a/resources/views/index/index.blade.php +++ b/resources/views/index/index.blade.php @@ -1,5 +1,5 @@ - + diff --git a/resources/views/portal/ninja2020/gateways/stripe/sofort/pay.blade.php b/resources/views/portal/ninja2020/gateways/stripe/sofort/pay.blade.php index 36f9352cac76..dd2f5fe36e26 100644 --- a/resources/views/portal/ninja2020/gateways/stripe/sofort/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/stripe/sofort/pay.blade.php @@ -6,6 +6,7 @@ + @endsection @section('gateway_content') diff --git a/tests/Unit/InvoiceItemTest.php b/tests/Unit/InvoiceItemTest.php index 6a75f97c602d..7b6f8e5621c6 100644 --- a/tests/Unit/InvoiceItemTest.php +++ b/tests/Unit/InvoiceItemTest.php @@ -51,6 +51,29 @@ class InvoiceItemTest extends TestCase $this->assertEquals($item_calc->getLineTotal(), 10); } + public function testInvoiceItemTotalSimpleWithGrossTaxes() + { + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 10; + $item->is_amount_discount = true; + $item->tax_rate1 = 10; + + $settings = new \stdClass; + $settings->inclusive_taxes = false; + $settings->precision = 2; + + $this->invoice->line_items = [$item]; + + $item_calc = new InvoiceItemSum($this->invoice, $settings); + $item_calc->process(); + + $this->assertEquals($item_calc->getLineTotal(), 10); + $this->assertEquals($item_calc->getGrossLineTotal(), 11); + } + + + public function testInvoiceItemTotalSimpleWithDiscount() { $item = InvoiceItemFactory::create(); @@ -71,6 +94,29 @@ class InvoiceItemTest extends TestCase $this->assertEquals($item_calc->getLineTotal(), 8); } + public function testInvoiceItemTotalSimpleWithDiscountAndGrossLineTotal() + { + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 10; + $item->is_amount_discount = true; + $item->discount = 2; + $item->tax_rate1 = 10; + + $this->invoice->line_items = [$item]; + + $settings = new \stdClass; + $settings->inclusive_taxes = false; + $settings->precision = 2; + + $item_calc = new InvoiceItemSum($this->invoice, $settings); + $item_calc->process(); + + $this->assertEquals($item_calc->getLineTotal(), 8); + $this->assertEquals($item_calc->getGrossLineTotal(), 8.8); + + } + public function testInvoiceItemTotalSimpleWithDiscountWithPrecision() { $item = InvoiceItemFactory::create(); diff --git a/tests/Unit/InvoiceTest.php b/tests/Unit/InvoiceTest.php index 034d27a16c7c..233403634f28 100644 --- a/tests/Unit/InvoiceTest.php +++ b/tests/Unit/InvoiceTest.php @@ -123,6 +123,7 @@ class InvoiceTest extends TestCase $this->invoice_calc->build(); $this->assertEquals($this->invoice_calc->getSubTotal(), 20); + // $this->assertEquals($this->invoice_calc->getGrossSubTotal(), 22); //$this->assertEquals($this->invoice_calc->getTotal(), 21.5); //$this->assertEquals($this->invoice_calc->getBalance(), 21.5); //$this->assertEquals($this->invoice_calc->getTotalTaxes(), 1.5); @@ -216,6 +217,7 @@ class InvoiceTest extends TestCase $this->invoice_calc->build(); $this->assertEquals($this->invoice_calc->getSubTotal(), 20); + $this->assertEquals($this->invoice_calc->getGrossSubTotal(), 22); //$this->assertEquals($this->invoice_calc->getTotal(), 26); //$this->assertEquals($this->invoice_calc->getBalance(), 26); //$this->assertEquals($this->invoice_calc->getTotalTaxes(), 4);