1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 05:02:36 +01:00

Support multiple tax rates

This commit is contained in:
Hillel Coren 2016-03-27 18:06:02 +03:00
parent 7a929a51bb
commit d70bc4e922
5 changed files with 75 additions and 148 deletions

View File

@ -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'),

View File

@ -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();
});
}
}

View File

@ -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) {

View File

@ -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('');

View File

@ -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