From c4cdc45a93d2c6d4264fec67c4cfa896d745eb64 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 2 Jan 2017 13:38:58 +0200 Subject: [PATCH] Add inclusive tax rates #552 --- app/Http/Controllers/ExpenseController.php | 2 +- app/Http/Controllers/InvoiceController.php | 8 +- app/Http/Controllers/ProductController.php | 4 +- app/Models/TaxRate.php | 3 +- app/Ninja/Datatables/TaxRateDatatable.php | 6 + app/Ninja/Repositories/TaxRateRepository.php | 10 +- app/Ninja/Transformers/TaxRateTransformer.php | 4 +- .../2017_01_01_214241_add_inclusive_taxes.php | 60 ++++++++++ resources/lang/en/texts.php | 4 +- .../views/accounts/localization.blade.php | 1 - resources/views/accounts/tax_rate.blade.php | 15 ++- resources/views/accounts/tax_rates.blade.php | 3 +- resources/views/invoices/edit.blade.php | 18 +-- resources/views/invoices/knockout.blade.php | 108 ++++++++++++++---- 14 files changed, 199 insertions(+), 47 deletions(-) create mode 100644 database/migrations/2017_01_01_214241_add_inclusive_taxes.php diff --git a/app/Http/Controllers/ExpenseController.php b/app/Http/Controllers/ExpenseController.php index cc850cbf3272..e159b268f22c 100644 --- a/app/Http/Controllers/ExpenseController.php +++ b/app/Http/Controllers/ExpenseController.php @@ -253,7 +253,7 @@ class ExpenseController extends BaseController 'customLabel1' => Auth::user()->account->custom_vendor_label1, 'customLabel2' => Auth::user()->account->custom_vendor_label2, 'categories' => ExpenseCategory::whereAccountId(Auth::user()->account_id)->withArchived()->orderBy('name')->get(), - 'taxRates' => TaxRate::scope()->orderBy('name')->get(), + 'taxRates' => TaxRate::scope()->whereIsInclusive(false)->orderBy('name')->get(), ]; } diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index a04f32471d71..5c92f4117c3e 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -328,7 +328,11 @@ class InvoiceController extends BaseController $defaultTax = false; foreach ($rates as $rate) { - $options[$rate->rate . ' ' . $rate->name] = $rate->name . ' ' . ($rate->rate+0) . '%'; + $name = $rate->name . ' ' . ($rate->rate+0) . '%'; + if ($rate->is_inclusive) { + $name .= ' - ' . trans('texts.inclusive'); + } + $options[($rate->is_inclusive ? '1 ' : '0 ') . $rate->rate . ' ' . $rate->name] = $name; // load default invoice tax if ($rate->id == $account->default_tax_rate_id) { @@ -342,7 +346,7 @@ class InvoiceController extends BaseController if (isset($options[$key])) { continue; } - $options[$key] = $rate['name'] . ' ' . $rate['rate'] . '%'; + $options['0 ' . $key] = $rate['name'] . ' ' . $rate['rate'] . '%'; } } diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index e9ab8dd005a8..3dd650d5a5fd 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -74,7 +74,7 @@ class ProductController extends BaseController $data = [ 'account' => $account, - 'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->get(['id', 'name', 'rate']) : null, + 'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->whereIsInclusive(false)->get(['id', 'name', 'rate']) : null, 'product' => $product, 'entity' => $product, 'method' => 'PUT', @@ -94,7 +94,7 @@ class ProductController extends BaseController $data = [ 'account' => $account, - 'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->get(['id', 'name', 'rate']) : null, + 'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->whereIsInclusive(false)->get(['id', 'name', 'rate']) : null, 'product' => null, 'method' => 'POST', 'url' => 'products', diff --git a/app/Models/TaxRate.php b/app/Models/TaxRate.php index f041c735d989..f9c25afc389c 100644 --- a/app/Models/TaxRate.php +++ b/app/Models/TaxRate.php @@ -18,7 +18,8 @@ class TaxRate extends EntityModel */ protected $fillable = [ 'name', - 'rate' + 'rate', + 'is_inclusive', ]; /** diff --git a/app/Ninja/Datatables/TaxRateDatatable.php b/app/Ninja/Datatables/TaxRateDatatable.php index 3e16aba9ee19..4cc763d87ea4 100644 --- a/app/Ninja/Datatables/TaxRateDatatable.php +++ b/app/Ninja/Datatables/TaxRateDatatable.php @@ -20,6 +20,12 @@ class TaxRateDatatable extends EntityDatatable function ($model) { return $model->rate . '%'; } + ], + [ + 'type', + function ($model) { + return $model->is_inclusive ? trans('texts.inclusive') : trans('texts.exclusive'); + } ] ]; } diff --git a/app/Ninja/Repositories/TaxRateRepository.php b/app/Ninja/Repositories/TaxRateRepository.php index f737256e85bf..52f229afd9af 100644 --- a/app/Ninja/Repositories/TaxRateRepository.php +++ b/app/Ninja/Repositories/TaxRateRepository.php @@ -16,7 +16,13 @@ class TaxRateRepository extends BaseRepository return DB::table('tax_rates') ->where('tax_rates.account_id', '=', $accountId) ->where('tax_rates.deleted_at', '=', null) - ->select('tax_rates.public_id', 'tax_rates.name', 'tax_rates.rate', 'tax_rates.deleted_at'); + ->select( + 'tax_rates.public_id', + 'tax_rates.name', + 'tax_rates.rate', + 'tax_rates.deleted_at', + 'tax_rates.is_inclusive' + ); } public function save($data, $taxRate = null) @@ -29,7 +35,7 @@ class TaxRateRepository extends BaseRepository } else { $taxRate = TaxRate::createNew(); } - + $taxRate->fill($data); $taxRate->save(); diff --git a/app/Ninja/Transformers/TaxRateTransformer.php b/app/Ninja/Transformers/TaxRateTransformer.php index 3b0f5ee19978..07b8aa565ad0 100644 --- a/app/Ninja/Transformers/TaxRateTransformer.php +++ b/app/Ninja/Transformers/TaxRateTransformer.php @@ -13,6 +13,7 @@ class TaxRateTransformer extends EntityTransformer * @SWG\Property(property="name", type="string", example="GST") * @SWG\Property(property="account_key", type="string", example="asimplestring", readOnly=true) * @SWG\Property(property="rate", type="float", example=17.5) + * @SWG\Property(property="is_inclusive", type="boolean", example=false) * @SWG\Property(property="updated_at", type="date-time", example="2016-01-01 12:10:00") * @SWG\Property(property="archived_at", type="date-time", example="2016-01-01 12:10:00") */ @@ -23,8 +24,9 @@ class TaxRateTransformer extends EntityTransformer 'id' => (int) $taxRate->public_id, 'name' => $taxRate->name, 'rate' => (float) $taxRate->rate, + 'is_inclusive' => (bool) $taxRate->is_inclusive, 'updated_at' => $this->getTimestamp($taxRate->updated_at), 'archived_at' => $this->getTimestamp($taxRate->deleted_at), ]); } -} \ No newline at end of file +} diff --git a/database/migrations/2017_01_01_214241_add_inclusive_taxes.php b/database/migrations/2017_01_01_214241_add_inclusive_taxes.php new file mode 100644 index 000000000000..10102c283a82 --- /dev/null +++ b/database/migrations/2017_01_01_214241_add_inclusive_taxes.php @@ -0,0 +1,60 @@ +boolean('is_inclusive')->default(false); + }); + + Schema::table('companies', function ($table) + { + $table->enum('bluevine_status', ['ignored', 'signed_up'])->nullable(); + }); + + DB::statement('UPDATE companies + LEFT JOIN accounts ON accounts.company_id = companies.id AND accounts.bluevine_status IS NOT NULL + SET companies.bluevine_status = accounts.bluevine_status'); + + Schema::table('accounts', function($table) + { + $table->dropColumn('bluevine_status'); + $table->text('bcc_email')->nullable(); + }); + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('tax_rates', function($table) + { + $table->dropColumn('is_inclusive'); + }); + + Schema::table('companies', function($table) + { + $table->dropColumn('bluevine_status'); + }); + + Schema::table('accounts', function ($table) + { + $table->enum('bluevine_status', ['ignored', 'signed_up'])->nullable(); + $table->dropColumn('bcc_email'); + }); + } +} diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 59ac0f23f28c..db754ea1773c 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -2291,7 +2291,9 @@ $LANG = array( 'iphone_app_message' => 'Consider downloading our :link', 'iphone_app' => 'iPhone app', 'logged_in' => 'Logged In', - 'switch_to_primary' => 'Switch to your primary company (:name) to manage your plan.' + 'switch_to_primary' => 'Switch to your primary company (:name) to manage your plan.', + 'inclusive' => 'Inclusive', + 'exclusive' => 'Exclusive', ); diff --git a/resources/views/accounts/localization.blade.php b/resources/views/accounts/localization.blade.php index 737088460da5..fecfb8086a6b 100644 --- a/resources/views/accounts/localization.blade.php +++ b/resources/views/accounts/localization.blade.php @@ -34,7 +34,6 @@ trans('texts.currency_symbol') . ': ' => array('name' => 'show_currency_code', 'value' => 0), trans('texts.currency_code') . ': ' => array('name' => 'show_currency_code', 'value' => 1), ])->inline() - ->check('timer') ->label(' ') ->addGroupClass('currrency_radio') !!}
diff --git a/resources/views/accounts/tax_rate.blade.php b/resources/views/accounts/tax_rate.blade.php index 5889c7aecb96..57ed0245ae12 100644 --- a/resources/views/accounts/tax_rate.blade.php +++ b/resources/views/accounts/tax_rate.blade.php @@ -1,6 +1,6 @@ @extends('header') -@section('content') +@section('content') @parent @include('accounts.nav', ['selected' => ACCOUNT_TAX_RATES]) @@ -21,15 +21,24 @@ @if ($taxRate) {{ Former::populate($taxRate) }} + {{ Former::populateField('is_inclusive', intval($taxRate->is_inclusive)) }} @endif {!! Former::text('name')->label('texts.name') !!} {!! Former::text('rate')->label('texts.rate')->append('%') !!} + {!! Former::radios('is_inclusive')->radios([ + trans('texts.exclusive') => array('name' => 'is_inclusive', 'value' => 0), + trans('texts.inclusive') => array('name' => 'is_inclusive', 'value' => 1), + ])->inline() + ->check(0) + ->label('type') !!} + + - {!! Former::actions( + {!! Former::actions( Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/settings/tax_rates'))->appendIcon(Icon::create('remove-circle')), Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) ) !!} @@ -44,4 +53,4 @@ -@stop \ No newline at end of file +@stop diff --git a/resources/views/accounts/tax_rates.blade.php b/resources/views/accounts/tax_rates.blade.php index add364c23595..8d1e327e97fe 100644 --- a/resources/views/accounts/tax_rates.blade.php +++ b/resources/views/accounts/tax_rates.blade.php @@ -65,12 +65,13 @@ ->addColumn( trans('texts.name'), trans('texts.rate'), + trans('texts.type'), trans('texts.action')) ->setUrl(url('api/tax_rates/')) ->setOptions('sPaginationType', 'bootstrap') ->setOptions('bFilter', false) ->setOptions('bAutoWidth', false) - ->setOptions('aoColumns', [[ "sWidth"=> "40%" ], [ "sWidth"=> "40%" ], ["sWidth"=> "20%"]]) + ->setOptions('aoColumns', [[ "sWidth"=> "25%" ], [ "sWidth"=> "25%" ], ["sWidth"=> "25%"], ["sWidth"=> "25%"]]) ->setOptions('aoColumnDefs', [['bSortable'=>false, 'aTargets'=>[2]]]) ->render('datatable') !!} diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 261dcf2a4785..771a136382ca 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -297,19 +297,19 @@ style="text-align: right" class="form-control invoice-item" name="quantity"/> - {!! Former::select('') - ->addOption('', '') - ->options($taxRateOptions) - ->data_bind('value: tax1') - ->addClass($account->enable_second_tax_rate ? 'tax-select' : '') - ->raw() !!} + {!! Former::select('') + ->addOption('', '') + ->options($taxRateOptions) + ->data_bind('value: tax1, event:{change:onTax1Change}') + ->addClass($account->enable_second_tax_rate ? 'tax-select' : '') + ->raw() !!}
{!! Former::select('') ->addOption('', '') ->options($taxRateOptions) - ->data_bind('value: tax2') + ->data_bind('value: tax2, event:{change:onTax2Change}') ->addClass('tax-select') ->raw() !!}
@@ -452,7 +452,7 @@ ->addOption('', '') ->options($taxRateOptions) ->addClass($account->enable_second_tax_rate ? 'tax-select' : '') - ->data_bind('value: tax1') + ->data_bind('value: tax1, event:{change:onTax1Change}') ->raw() !!} @@ -461,7 +461,7 @@ ->addOption('', '') ->options($taxRateOptions) ->addClass('tax-select') - ->data_bind('value: tax2') + ->data_bind('value: tax2, event:{change:onTax2Change}') ->raw() !!} diff --git a/resources/views/invoices/knockout.blade.php b/resources/views/invoices/knockout.blade.php index fb558e71536b..50b48916387f 100644 --- a/resources/views/invoices/knockout.blade.php +++ b/resources/views/invoices/knockout.blade.php @@ -179,8 +179,10 @@ function InvoiceModel(data) { self.last_sent_date = ko.observable(''); self.tax_name1 = ko.observable(); self.tax_rate1 = ko.observable(); + self.tax_rate1IsInclusive = ko.observable(0); self.tax_name2 = ko.observable(); self.tax_rate2 = ko.observable(); + self.tax_rate2IsInclusive = ko.observable(0); self.is_recurring = ko.observable(0); self.is_quote = ko.observable({{ $entityType == ENTITY_QUOTE ? '1' : '0' }}); self.auto_bill = ko.observable(0); @@ -268,25 +270,25 @@ function InvoiceModel(data) { this.tax1 = ko.computed({ read: function () { - return self.tax_rate1() + ' ' + self.tax_name1(); + return self.tax_rate1IsInclusive() + ' ' + self.tax_rate1() + ' ' + self.tax_name1(); }, write: function(value) { - var rate = value.substr(0, value.indexOf(' ')); - var name = value.substr(value.indexOf(' ') + 1); - self.tax_name1(name); - self.tax_rate1(rate); + var parts = value.split(' '); + self.tax_rate1IsInclusive(parts.shift()); + self.tax_rate1(parts.shift()); + self.tax_name1(parts.join(' ')); } }) this.tax2 = ko.computed({ read: function () { - return self.tax_rate2() + ' ' + self.tax_name2(); + return self.tax_rate2IsInclusive() + ' ' + self.tax_rate2() + ' ' + self.tax_name2(); }, write: function(value) { - var rate = value.substr(0, value.indexOf(' ')); - var name = value.substr(value.indexOf(' ') + 1); - self.tax_name2(name); - self.tax_rate2(rate); + var parts = value.split(' '); + self.tax_rate2IsInclusive(parts.shift()); + self.tax_rate2(parts.shift()); + self.tax_name2(parts.join(' ')); } }) @@ -498,6 +500,37 @@ function InvoiceModel(data) { self.showResetFooter = function() { return self.default_footer() && self.invoice_footer() != self.default_footer(); } + + self.applyInclusivTax = function(taxRate) { + for (var i=0; iinvoice_item_taxes) if (datum.default_tax_rate) { - model.tax_rate1(datum.default_tax_rate.rate); - model.tax_name1(datum.default_tax_rate.name); - model.tax1(datum.default_tax_rate.rate + ' ' + datum.default_tax_rate.name); + var $select = $(this).parentsUntil('tbody').find('select').first(); + $select.val('0 ' + datum.default_tax_rate.rate + ' ' + datum.default_tax_rate.name).trigger('change'); } @endif @endif