mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-08 20:22:42 +01:00
Support multiple tax rates
This commit is contained in:
parent
7a929a51bb
commit
d70bc4e922
@ -324,11 +324,17 @@ class InvoiceController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
// Tax rate options
|
||||
$rates = TaxRate::scope()->orderBy('name')->get();
|
||||
foreach ($rates as $rate) {
|
||||
$options[$rate->rate . ' ' . $rate->name] = $rate->name . ' ' . ($rate->rate+0) . '%';
|
||||
}
|
||||
|
||||
return [
|
||||
'data' => Input::old('data'),
|
||||
'account' => Auth::user()->account->load('country'),
|
||||
'products' => Product::scope()->with('default_tax_rate')->orderBy('product_key')->get(),
|
||||
'taxRates' => TaxRate::scope()->orderBy('name')->get(),
|
||||
'taxRates' => $options,
|
||||
'currencies' => Cache::get('currencies'),
|
||||
'languages' => Cache::get('languages'),
|
||||
'sizes' => Cache::get('sizes'),
|
||||
|
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class SupportMultipleTaxRates extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('invoices', function($table) {
|
||||
$table->decimal('tax_rate', 13, 3)->change();
|
||||
});
|
||||
|
||||
Schema::table('invoice_items', function($table) {
|
||||
$table->decimal('tax_rate', 13, 3)->change();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('invoices', function($table) {
|
||||
$table->decimal('tax_rate', 13, 2)->change();
|
||||
});
|
||||
|
||||
Schema::table('invoice_items', function($table) {
|
||||
$table->decimal('tax_rate', 13, 2)->change();
|
||||
});
|
||||
}
|
||||
}
|
@ -55,6 +55,7 @@ class CurrenciesSeeder extends Seeder
|
||||
['name' => 'Saudi Riyal', 'code' => 'SAR', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['name' => 'Japanese Yen', 'code' => 'JPY', 'symbol' => '¥', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['name' => 'Maldivian Rufiyaa', 'code' => 'MVR', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['name' => 'Costa Rican Colón', 'code' => 'CRC', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
];
|
||||
|
||||
foreach ($currencies as $currency) {
|
||||
|
@ -53,7 +53,7 @@
|
||||
|
||||
<div data-bind="with: invoice">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body" style="padding-bottom: 0px;">
|
||||
<div class="panel-body">
|
||||
|
||||
<div class="row" style="min-height:195px" onkeypress="formEnterClick(event)">
|
||||
<div class="col-md-4" id="col_1">
|
||||
@ -243,9 +243,12 @@
|
||||
style="text-align: right" class="form-control invoice-item" name="quantity"/>
|
||||
</td>
|
||||
<td style="display:none;" data-bind="visible: $root.invoice_item_taxes.show">
|
||||
<select class="form-control" style="width:100%" data-bind="value: tax, options: $root.tax_rates, optionsText: 'displayName', attr: {name: 'invoice_items[' + $index() + '][tax]'}"></select>
|
||||
<input type="text" data-bind="value: tax().name, attr: {name: 'invoice_items[' + $index() + '][tax_name]'}" style="display:none">
|
||||
<input type="text" data-bind="value: tax().rate, attr: {name: 'invoice_items[' + $index() + '][tax_rate]'}" style="display:none">
|
||||
{!! Former::select('')
|
||||
->options($taxRates)
|
||||
->data_bind('value: tax')
|
||||
->raw() !!}
|
||||
<input type="text" data-bind="value: tax_name, attr: {name: 'invoice_items[' + $index() + '][tax_name]'}" style="display:none">
|
||||
<input type="text" data-bind="value: tax_rate, attr: {name: 'invoice_items[' + $index() + '][tax_rate]'}" style="display:none">
|
||||
</td>
|
||||
<td style="text-align:right;padding-top:9px !important" nowrap>
|
||||
<div class="line-total" data-bind="text: totals.total"></div>
|
||||
@ -351,9 +354,12 @@
|
||||
<td>{{ trans('texts.tax') }}</td>
|
||||
@endif
|
||||
<td style="min-width:120px">
|
||||
<select id="taxRateSelect" class="form-control" style="width:100%" data-bind="value: tax, options: $root.tax_rates, optionsText: 'displayName'"></select>
|
||||
<input type="text" name="tax_name" data-bind="value: tax().name" style="display:none">
|
||||
<input type="text" name="tax_rate" data-bind="value: tax().rate" style="display:none">
|
||||
{!! Former::select('')
|
||||
->options($taxRates)
|
||||
->data_bind('value: tax')
|
||||
->raw() !!}
|
||||
<input type="text" name="tax_name" data-bind="value: tax_name" style="display:none">
|
||||
<input type="text" name="tax_rate" data-bind="value: tax_rate" style="display:none">
|
||||
</td>
|
||||
<td style="text-align: right"><span data-bind="text: totals.taxAmount"/></td>
|
||||
</tr>
|
||||
@ -712,11 +718,6 @@
|
||||
// otherwise create blank model
|
||||
window.model = new ViewModel();
|
||||
|
||||
// load the tax rates
|
||||
@foreach ($taxRates as $taxRate)
|
||||
model.addTaxRate({!! $taxRate !!});
|
||||
@endforeach
|
||||
|
||||
var invoice = {!! $invoice !!};
|
||||
ko.mapping.fromJS(invoice, model.invoice().mapping, model.invoice);
|
||||
model.invoice().is_recurring({{ $invoice->is_recurring ? '1' : '0' }});
|
||||
@ -736,7 +737,7 @@
|
||||
model.invoice().custom_taxes2({{ $account->custom_invoice_taxes2 ? 'true' : 'false' }});
|
||||
// set the default account tax rate
|
||||
@if ($account->invoice_taxes && $account->default_tax_rate_id)
|
||||
model.invoice().tax(model.getTaxRateById({{ $account->default_tax_rate ? $account->default_tax_rate->public_id : '' }}));
|
||||
//model.invoice().tax(model.getTaxRateById({{ $account->default_tax_rate ? $account->default_tax_rate->public_id : '' }}));
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@ -776,14 +777,7 @@
|
||||
@endif
|
||||
|
||||
@endif
|
||||
|
||||
model.invoice().tax(model.getTaxRate(model.invoice().tax_name(), model.invoice().tax_rate()));
|
||||
for (var i=0; i<model.invoice().invoice_items().length; i++) {
|
||||
var item = model.invoice().invoice_items()[i];
|
||||
item.tax(model.getTaxRate(item.tax_name(), item.tax_rate()));
|
||||
item.cost(NINJA.parseFloat(item.cost()) != 0 ? roundToTwo(item.cost(), true) : '');
|
||||
}
|
||||
|
||||
|
||||
// display blank instead of '0'
|
||||
if (!NINJA.parseFloat(model.invoice().discount())) model.invoice().discount('');
|
||||
if (!NINJA.parseFloat(model.invoice().partial())) model.invoice().partial('');
|
||||
|
@ -7,8 +7,6 @@ function ViewModel(data) {
|
||||
//self.invoice = data ? false : new InvoiceModel();
|
||||
self.invoice = ko.observable(data ? false : new InvoiceModel());
|
||||
self.expense_currency_id = ko.observable();
|
||||
self.tax_rates = ko.observableArray();
|
||||
self.tax_rates.push(new TaxRateModel()); // add blank row
|
||||
self.products = {!! $products !!};
|
||||
|
||||
self.loadClient = function(client) {
|
||||
@ -47,11 +45,6 @@ function ViewModel(data) {
|
||||
return new InvoiceModel(options.data);
|
||||
}
|
||||
},
|
||||
'tax_rates': {
|
||||
create: function(options) {
|
||||
return new TaxRateModel(options.data);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
if (data) {
|
||||
@ -59,10 +52,7 @@ function ViewModel(data) {
|
||||
}
|
||||
|
||||
self.invoice_taxes.show = ko.computed(function() {
|
||||
if (self.invoice_taxes() && self.tax_rates().length > 1) {
|
||||
return true;
|
||||
}
|
||||
if (self.invoice().tax_rate() > 0) {
|
||||
if (self.invoice_taxes()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -81,40 +71,6 @@ function ViewModel(data) {
|
||||
return false;
|
||||
});
|
||||
|
||||
self.addTaxRate = function(data) {
|
||||
var itemModel = new TaxRateModel(data);
|
||||
self.tax_rates.push(itemModel);
|
||||
applyComboboxListeners();
|
||||
}
|
||||
|
||||
self.getTaxRateById = function(id) {
|
||||
for (var i=0; i<self.tax_rates().length; i++) {
|
||||
var taxRate = self.tax_rates()[i];
|
||||
if (taxRate.public_id() == id) {
|
||||
return taxRate;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
self.getTaxRate = function(name, rate) {
|
||||
for (var i=0; i<self.tax_rates().length; i++) {
|
||||
var taxRate = self.tax_rates()[i];
|
||||
if (taxRate.name() == name && taxRate.rate() == parseFloat(rate)) {
|
||||
return taxRate;
|
||||
}
|
||||
}
|
||||
|
||||
var taxRate = new TaxRateModel();
|
||||
taxRate.name(name);
|
||||
taxRate.rate(parseFloat(rate));
|
||||
if (name) {
|
||||
taxRate.is_deleted(true);
|
||||
self.tax_rates.push(taxRate);
|
||||
}
|
||||
return taxRate;
|
||||
}
|
||||
|
||||
self.showClientForm = function() {
|
||||
trackEvent('/activity', '/view_client_form');
|
||||
self.clientBackup = ko.mapping.toJS(self.invoice().client);
|
||||
@ -251,11 +207,6 @@ function InvoiceModel(data) {
|
||||
return new ItemModel(options.data);
|
||||
}
|
||||
},
|
||||
'tax': {
|
||||
create: function(options) {
|
||||
return new TaxRateModel(options.data);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
self.addItem = function() {
|
||||
@ -282,24 +233,18 @@ function InvoiceModel(data) {
|
||||
return self.has_tasks() ? invoiceLabels['rate'] : invoiceLabels['unit_cost'];
|
||||
}, this);
|
||||
|
||||
self._tax = ko.observable();
|
||||
this.tax = ko.computed({
|
||||
read: function () {
|
||||
return self._tax();
|
||||
return self.tax_rate() + ' ' + self.tax_name();
|
||||
},
|
||||
write: function(value) {
|
||||
if (value) {
|
||||
self._tax(value);
|
||||
self.tax_name(value.name());
|
||||
self.tax_rate(value.rate());
|
||||
} else {
|
||||
self._tax(false);
|
||||
self.tax_name('');
|
||||
self.tax_rate(0);
|
||||
}
|
||||
var rate = value.substr(0, value.indexOf(' '));
|
||||
var name = value.substr(value.indexOf(' ') + 1);
|
||||
self.tax_name(name);
|
||||
self.tax_rate(rate);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
self.wrapped_terms = ko.computed({
|
||||
read: function() {
|
||||
return this.terms();
|
||||
@ -663,55 +608,6 @@ function ContactModel(data) {
|
||||
});
|
||||
}
|
||||
|
||||
function TaxRateModel(data) {
|
||||
var self = this;
|
||||
self.public_id = ko.observable('');
|
||||
self.rate = ko.observable(0);
|
||||
self.name = ko.observable('');
|
||||
self.is_deleted = ko.observable(false);
|
||||
self.is_blank = ko.observable(false);
|
||||
self.actionsVisible = ko.observable(false);
|
||||
|
||||
if (data) {
|
||||
ko.mapping.fromJS(data, {}, this);
|
||||
}
|
||||
|
||||
this.prettyRate = ko.computed({
|
||||
read: function () {
|
||||
return this.rate() ? roundToTwo(this.rate()) : '';
|
||||
},
|
||||
write: function (value) {
|
||||
this.rate(value);
|
||||
},
|
||||
owner: this
|
||||
});
|
||||
|
||||
|
||||
self.displayName = ko.computed({
|
||||
read: function () {
|
||||
var name = self.name() ? self.name() : '';
|
||||
var rate = self.rate() ? parseFloat(self.rate()) + '%' : '';
|
||||
return name + ' ' + rate;
|
||||
},
|
||||
write: function (value) {
|
||||
// do nothing
|
||||
},
|
||||
owner: this
|
||||
});
|
||||
|
||||
self.hideActions = function() {
|
||||
self.actionsVisible(false);
|
||||
}
|
||||
|
||||
self.showActions = function() {
|
||||
self.actionsVisible(true);
|
||||
}
|
||||
|
||||
self.isEmpty = function() {
|
||||
return !self.rate() && !self.name();
|
||||
}
|
||||
}
|
||||
|
||||
function ItemModel(data) {
|
||||
var self = this;
|
||||
self.product_key = ko.observable('');
|
||||
@ -726,15 +622,15 @@ function ItemModel(data) {
|
||||
self.expense_public_id = ko.observable('');
|
||||
self.actionsVisible = ko.observable(false);
|
||||
|
||||
self._tax = ko.observable();
|
||||
this.tax = ko.computed({
|
||||
read: function () {
|
||||
return self._tax();
|
||||
return self.tax_rate() + ' ' + self.tax_name();
|
||||
},
|
||||
write: function(value) {
|
||||
self._tax(value);
|
||||
self.tax_name(value.name());
|
||||
self.tax_rate(value.rate());
|
||||
var rate = value.substr(0, value.indexOf(' '));
|
||||
var name = value.substr(value.indexOf(' ') + 1);
|
||||
self.tax_name(name);
|
||||
self.tax_rate(rate);
|
||||
}
|
||||
})
|
||||
|
||||
@ -758,16 +654,8 @@ function ItemModel(data) {
|
||||
owner: this
|
||||
});
|
||||
|
||||
self.mapping = {
|
||||
'tax': {
|
||||
create: function(options) {
|
||||
return new TaxRateModel(options.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data) {
|
||||
ko.mapping.fromJS(data, self.mapping, this);
|
||||
ko.mapping.fromJS(data, {}, this);
|
||||
}
|
||||
|
||||
self.wrapped_notes = ko.computed({
|
||||
@ -842,7 +730,7 @@ ko.bindingHandlers.typeahead = {
|
||||
}
|
||||
@if ($account->invoice_item_taxes)
|
||||
if (datum.default_tax_rate) {
|
||||
model.tax(self.model.getTaxRateById(datum.default_tax_rate.public_id));
|
||||
//model.tax(self.model.getTaxRateById(datum.default_tax_rate.public_id));
|
||||
}
|
||||
@endif
|
||||
@endif
|
||||
|
Loading…
Reference in New Issue
Block a user