1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-17 16:42:48 +01:00

Support for multiple tax rates

This commit is contained in:
Hillel Coren 2016-03-31 12:29:01 +03:00
parent f91f32323f
commit be9e7beaed
13 changed files with 382 additions and 194 deletions

View File

@ -258,10 +258,11 @@ class InvoiceApiController extends BaseAPIController
// initialize the line items
if (isset($data['product_key']) || isset($data['cost']) || isset($data['notes']) || isset($data['qty'])) {
$data['invoice_items'] = [self::prepareItem($data)];
// make sure the tax isn't applied twice (for the invoice and the line item)
unset($data['invoice_items'][0]['tax_name']);
unset($data['invoice_items'][0]['tax_rate']);
unset($data['invoice_items'][0]['tax_name1']);
unset($data['invoice_items'][0]['tax_rate1']);
unset($data['invoice_items'][0]['tax_name2']);
unset($data['invoice_items'][0]['tax_rate2']);
} else {
foreach ($data['invoice_items'] as $index => $item) {
$data['invoice_items'][$index] = self::prepareItem($item);

View File

@ -25,6 +25,13 @@ class Invoice extends EntityModel implements BalanceAffecting
protected $presenter = 'App\Ninja\Presenters\InvoicePresenter';
protected $dates = ['deleted_at'];
protected $fillable = [
'tax_name1',
'tax_rate1',
'tax_name2',
'tax_rate2',
];
protected $casts = [
'is_recurring' => 'boolean',
'has_tasks' => 'boolean',
@ -394,8 +401,10 @@ class Invoice extends EntityModel implements BalanceAffecting
'documents',
'expenses',
'client',
'tax_name',
'tax_rate',
'tax_name1',
'tax_rate1',
'tax_name2',
'tax_rate2',
'account',
'invoice_design',
'invoice_design_id',
@ -476,8 +485,10 @@ class Invoice extends EntityModel implements BalanceAffecting
'custom_value2',
'cost',
'qty',
'tax_name',
'tax_rate',
'tax_name1',
'tax_rate1',
'tax_name2',
'tax_rate2',
]);
}
@ -843,54 +854,68 @@ class Invoice extends EntityModel implements BalanceAffecting
return $total;
}
// if $calculatePaid is true we'll loop through each payment to
// determine the sum, otherwise we'll use the cached paid_to_date amount
public function getTaxes($calculatePaid = false)
{
$taxes = [];
$taxable = $this->getTaxable();
$paidAmount = $this->getAmountPaid($calculatePaid);
if ($this->tax_rate && $this->tax_name) {
$taxAmount = $taxable * ($this->tax_rate / 100);
$taxAmount = round($taxAmount, 2);
if ($this->tax_name1) {
$invoiceTaxAmount = round($taxable * ($this->tax_rate1 / 100), 2);
$invoicePaidAmount = $this->amount && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0;
$this->calculateTax($taxes, $this->tax_name1, $this->tax_rate1, $invoiceTaxAmount, $invoicePaidAmount);
}
if ($taxAmount) {
$taxes[$this->tax_rate . ' ' . $this->tax_name] = [
'name' => $this->tax_name,
'rate' => $this->tax_rate+0,
'amount' => $taxAmount,
'paid' => round($this->getAmountPaid($calculatePaid) / $this->amount * $taxAmount, 2)
];
}
if ($this->tax_name2) {
$invoiceTaxAmount = round($taxable * ($this->tax_rate2 / 100), 2);
$invoicePaidAmount = $this->amount && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0;
$this->calculateTax($taxes, $this->tax_name2, $this->tax_rate2, $invoiceTaxAmount, $invoicePaidAmount);
}
foreach ($this->invoice_items as $invoiceItem) {
if ( ! $invoiceItem->tax_rate || ! $invoiceItem->tax_name) {
continue;
$itemTaxAmount = $this->getItemTaxable($invoiceItem, $taxable);
if ($invoiceItem->tax_name1) {
$itemTaxAmount = round($taxable * ($invoiceItem->tax_rate1 / 100), 2);
$itemPaidAmount = $this->amount && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0;
$this->calculateTax($taxes, $invoiceItem->tax_name1, $invoiceItem->tax_rate1, $itemTaxAmount, $itemPaidAmount);
}
$taxAmount = $this->getItemTaxable($invoiceItem, $taxable);
$taxAmount = $taxable * ($invoiceItem->tax_rate / 100);
$taxAmount = round($taxAmount, 2);
if ($taxAmount) {
$key = $invoiceItem->tax_rate . ' ' . $invoiceItem->tax_name;
if ( ! isset($taxes[$key])) {
$taxes[$key] = [
'amount' => 0,
'paid' => 0
];
}
$taxes[$key]['amount'] += $taxAmount;
$taxes[$key]['paid'] += $this->amount && $taxAmount ? round($this->getAmountPaid($calculatePaid) / $this->amount * $taxAmount, 2) : 0;
$taxes[$key]['name'] = $invoiceItem->tax_name;
$taxes[$key]['rate'] = $invoiceItem->tax_rate+0;
if ($invoiceItem->tax_name2) {
$itemTaxAmount = round($taxable * ($invoiceItem->tax_rate2 / 100), 2);
$itemPaidAmount = $this->amount && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0;
$this->calculateTax($taxes, $invoiceItem->tax_name2, $invoiceItem->tax_rate2, $itemTaxAmount, $itemPaidAmount);
}
}
return $taxes;
}
private function calculateTax(&$taxes, $name, $rate, $amount, $paid)
{
if ( ! $amount) {
return;
}
$amount = round($amount, 2);
$paid = round($paid, 2);
$key = $rate . ' ' . $name;
if ( ! isset($taxes[$key])) {
$taxes[$key] = [
'name' => $name,
'rate' => $rate+0,
'amount' => 0,
'paid' => 0
];
}
$taxes[$key]['amount'] += $amount;
$taxes[$key]['paid'] += $paid;
}
public function hasDocuments(){
if(count($this->documents))return true;
return $this->hasExpenseDocuments();

View File

@ -7,6 +7,13 @@ class InvoiceItem extends EntityModel
use SoftDeletes;
protected $dates = ['deleted_at'];
protected $fillable = [
'tax_name1',
'tax_rate1',
'tax_name2',
'tax_rate2',
];
public function invoice()
{
return $this->belongsTo('App\Models\Invoice');

View File

@ -221,6 +221,8 @@ class InvoiceRepository extends BaseRepository
$invoice = Invoice::scope($publicId)->firstOrFail();
}
$invoice->fill($data);
if ((isset($data['set_default_terms']) && $data['set_default_terms'])
|| (isset($data['set_default_footer']) && $data['set_default_footer'])) {
if (isset($data['set_default_terms']) && $data['set_default_terms']) {
@ -297,12 +299,10 @@ class InvoiceRepository extends BaseRepository
$invoice->invoice_design_id = isset($data['invoice_design_id']) ? $data['invoice_design_id'] : $account->invoice_design_id;
if (isset($data['tax_name']) && isset($data['tax_rate']) && $data['tax_name']) {
$invoice->tax_rate = Utils::parseFloat($data['tax_rate']);
$invoice->tax_name = trim($data['tax_name']);
} else {
$invoice->tax_rate = 0;
$invoice->tax_name = '';
// provide backwards compatability
if (isset($data['tax_name']) && isset($data['tax_rate'])) {
$data['tax_name1'] = $data['tax_name'];
$data['tax_rate1'] = $data['tax_rate'];
}
$total = 0;
@ -323,20 +323,24 @@ class InvoiceRepository extends BaseRepository
foreach ($data['invoice_items'] as $item) {
$item = (array) $item;
if (isset($item['tax_rate']) && Utils::parseFloat($item['tax_rate']) > 0) {
$invoiceItemCost = round(Utils::parseFloat($item['cost']), 2);
$invoiceItemQty = round(Utils::parseFloat($item['qty']), 2);
$invoiceItemTaxRate = Utils::parseFloat($item['tax_rate']);
$lineTotal = $invoiceItemCost * $invoiceItemQty;
$invoiceItemCost = round(Utils::parseFloat($item['cost']), 2);
$invoiceItemQty = round(Utils::parseFloat($item['qty']), 2);
$lineTotal = $invoiceItemCost * $invoiceItemQty;
if ($invoice->discount > 0) {
if ($invoice->is_amount_discount) {
$lineTotal -= round(($lineTotal/$total) * $invoice->discount, 2);
} else {
$lineTotal -= round($lineTotal * ($invoice->discount/100), 2);
}
if ($invoice->discount > 0) {
if ($invoice->is_amount_discount) {
$lineTotal -= round(($lineTotal/$total) * $invoice->discount, 2);
} else {
$lineTotal -= round($lineTotal * ($invoice->discount/100), 2);
}
}
if (isset($item['tax_rate1']) && Utils::parseFloat($item['tax_rate1']) > 0) {
$invoiceItemTaxRate = Utils::parseFloat($item['tax_rate1']);
$itemTax += round($lineTotal * $invoiceItemTaxRate / 100, 2);
}
if (isset($item['tax_rate2']) && Utils::parseFloat($item['tax_rate2']) > 0) {
$invoiceItemTaxRate = Utils::parseFloat($item['tax_rate2']);
$itemTax += round($lineTotal * $invoiceItemTaxRate / 100, 2);
}
}
@ -378,8 +382,9 @@ class InvoiceRepository extends BaseRepository
$total += $invoice->custom_value2;
}
$total += $total * $invoice->tax_rate / 100;
$total = round($total, 2);
$taxAmount1 = round($total * $invoice->tax_rate1 / 100, 2);
$taxAmount2 = round($total * $invoice->tax_rate2 / 100, 2);
$total = round($total + $taxAmount1 + $taxAmount2, 2);
$total += $itemTax;
// custom fields not charged taxes
@ -502,7 +507,7 @@ class InvoiceRepository extends BaseRepository
$invoiceItem->notes = trim($invoice->is_recurring ? $item['notes'] : Utils::processVariables($item['notes']));
$invoiceItem->cost = Utils::parseFloat($item['cost']);
$invoiceItem->qty = Utils::parseFloat($item['qty']);
$invoiceItem->tax_rate = 0;
//$invoiceItem->tax_rate = 0;
if (isset($item['custom_value1'])) {
$invoiceItem->custom_value1 = $item['custom_value1'];
@ -511,11 +516,14 @@ class InvoiceRepository extends BaseRepository
$invoiceItem->custom_value2 = $item['custom_value2'];
}
if (isset($item['tax_rate']) && isset($item['tax_name']) && $item['tax_name']) {
$invoiceItem['tax_rate'] = Utils::parseFloat($item['tax_rate']);
$invoiceItem['tax_name'] = trim($item['tax_name']);
// provide backwards compatability
if (isset($item['tax_name']) && isset($item['tax_rate'])) {
$item['tax_name1'] = $item['tax_name'];
$item['tax_rate1'] = $item['tax_rate'];
}
$invoiceItem->fill($item);
$invoice->invoice_items()->save($invoiceItem);
}
@ -562,8 +570,10 @@ class InvoiceRepository extends BaseRepository
'invoice_footer',
'public_notes',
'invoice_design_id',
'tax_name',
'tax_rate',
'tax_name1',
'tax_rate1',
'tax_name2',
'tax_rate2',
'amount',
'is_quote',
'custom_value1',
@ -597,8 +607,11 @@ class InvoiceRepository extends BaseRepository
'notes',
'cost',
'qty',
'tax_name',
'tax_rate', ] as $field) {
'tax_name1',
'tax_rate1',
'tax_name2',
'tax_rate2',
] as $field) {
$cloneItem->$field = $item->$field;
}
@ -689,8 +702,10 @@ class InvoiceRepository extends BaseRepository
$invoice->public_notes = Utils::processVariables($recurInvoice->public_notes);
$invoice->terms = Utils::processVariables($recurInvoice->terms);
$invoice->invoice_footer = Utils::processVariables($recurInvoice->invoice_footer);
$invoice->tax_name = $recurInvoice->tax_name;
$invoice->tax_rate = $recurInvoice->tax_rate;
$invoice->tax_name1 = $recurInvoice->tax_name1;
$invoice->tax_rate1 = $recurInvoice->tax_rate1;
$invoice->tax_name2 = $recurInvoice->tax_name2;
$invoice->tax_rate2 = $recurInvoice->tax_rate2;
$invoice->invoice_design_id = $recurInvoice->invoice_design_id;
$invoice->custom_value1 = $recurInvoice->custom_value1 ?: 0;
$invoice->custom_value2 = $recurInvoice->custom_value2 ?: 0;
@ -709,8 +724,10 @@ class InvoiceRepository extends BaseRepository
$item->cost = $recurItem->cost;
$item->notes = Utils::processVariables($recurItem->notes);
$item->product_key = Utils::processVariables($recurItem->product_key);
$item->tax_name = $recurItem->tax_name;
$item->tax_rate = $recurItem->tax_rate;
$item->tax_name1 = $recurItem->tax_name1;
$item->tax_rate1 = $recurItem->tax_rate1;
$item->tax_name2 = $recurItem->tax_name2;
$item->tax_rate2 = $recurItem->tax_rate2;
$invoice->invoice_items()->save($item);
}

View File

@ -19,8 +19,10 @@ class InvoiceItemTransformer extends EntityTransformer
'notes' => $item->notes,
'cost' => (float) $item->cost,
'qty' => (float) $item->qty,
'tax_name' => $item->tax_name,
'tax_rate' => (float) $item->tax_rate
'tax_name1' => $item->tax_name1,
'tax_rate1' => (float) $item->tax_rate1,
'tax_name2' => $item->tax_name2,
'tax_rate2' => (float) $item->tax_rate2,
];
}
}

View File

@ -87,8 +87,10 @@ class InvoiceTransformer extends EntityTransformer
'end_date' => $invoice->end_date,
'last_sent_date' => $invoice->last_sent_date,
'recurring_invoice_id' => (int) $invoice->recurring_invoice_id,
'tax_name' => $invoice->tax_name,
'tax_rate' => (float) $invoice->tax_rate,
'tax_name1' => $invoice->tax_name1,
'tax_rate1' => (float) $invoice->tax_rate1,
'tax_name2' => $invoice->tax_name2,
'tax_rate2' => (float) $invoice->tax_rate2,
'amount' => (float) $invoice->amount,
'balance' => (float) $invoice->balance,
'is_amount_discount' => (bool) $invoice->is_amount_discount,

View File

@ -10,7 +10,7 @@
}
],
"require": {
"turbo124/laravel-push-notification": "dev-laravel5",
"turbo124/laravel-push-notification": "dev-laravel5",
"omnipay/mollie": "dev-master#22956c1a62a9662afa5f5d119723b413770ac525",
"omnipay/2checkout": "dev-master#e9c079c2dde0d7ba461903b3b7bd5caf6dee1248",
"omnipay/gocardless": "dev-master",

View File

@ -14,10 +14,24 @@ class SupportMultipleTaxRates extends Migration
{
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();
});
Schema::table('invoices', function($table) {
$table->renameColumn('tax_rate', 'tax_rate1');
$table->renameColumn('tax_name', 'tax_name1');
$table->string('tax_name2')->nullable();
$table->decimal('tax_rate2', 13, 3);
});
Schema::table('invoice_items', function($table) {
$table->renameColumn('tax_rate', 'tax_rate1');
$table->renameColumn('tax_name', 'tax_name1');
$table->string('tax_name2')->nullable();
$table->decimal('tax_rate2', 13, 3);
});
}
/**
@ -28,11 +42,19 @@ class SupportMultipleTaxRates extends Migration
public function down()
{
Schema::table('invoices', function($table) {
$table->decimal('tax_rate', 13, 2)->change();
$table->decimal('tax_rate1', 13, 2)->change();
$table->renameColumn('tax_rate1', 'tax_rate');
$table->renameColumn('tax_name1', 'tax_name');
$table->dropColumn('tax_name2');
$table->dropColumn('tax_rate2');
});
Schema::table('invoice_items', function($table) {
$table->decimal('tax_rate', 13, 2)->change();
$table->decimal('tax_rate1', 13, 2)->change();
$table->renameColumn('tax_rate1', 'tax_rate');
$table->renameColumn('tax_name1', 'tax_name');
$table->dropColumn('tax_name2');
$table->dropColumn('tax_rate2');
});
}
}

View File

@ -30497,8 +30497,10 @@ function calculateAmounts(invoice) {
for (var i=0; i<invoice.invoice_items.length; i++) {
var item = invoice.invoice_items[i];
var taxRate = 0;
var taxName = '';
var taxRate1 = 0;
var taxName1 = '';
var taxRate2 = 0;
var taxName2 = '';
if (item.product_key) {
invoice.has_product_key = true;
@ -30506,9 +30508,14 @@ function calculateAmounts(invoice) {
invoice.has_product_key = true;
}
if (item.tax_rate && parseFloat(item.tax_rate)) {
taxRate = parseFloat(item.tax_rate);
taxName = item.tax_name;
if (item.tax_rate1 && parseFloat(item.tax_rate1)) {
taxRate1 = parseFloat(item.tax_rate1);
taxName1 = item.tax_name1;
}
if (item.tax_rate2 && parseFloat(item.tax_rate2)) {
taxRate2 = parseFloat(item.tax_rate2);
taxName2 = item.tax_name2;
}
// calculate line item tax
@ -30520,18 +30527,28 @@ function calculateAmounts(invoice) {
lineTotal -= roundToTwo(lineTotal * (invoice.discount/100));
}
}
var taxAmount = roundToTwo(lineTotal * taxRate / 100);
if (taxAmount) {
var key = taxName + taxRate;
var taxAmount1 = roundToTwo(lineTotal * taxRate1 / 100);
if (taxAmount1) {
var key = taxName1 + taxRate1;
if (taxes.hasOwnProperty(key)) {
taxes[key].amount += taxAmount;
taxes[key].amount += taxAmount1;
} else {
taxes[key] = {name: taxName, rate:taxRate, amount:taxAmount};
taxes[key] = {name: taxName1, rate:taxRate1, amount:taxAmount1};
}
}
if (item.tax_name) {
var taxAmount2 = roundToTwo(lineTotal * taxRate2 / 100);
if (taxAmount2) {
var key = taxName2 + taxRate2;
if (taxes.hasOwnProperty(key)) {
taxes[key].amount += taxAmount2;
} else {
taxes[key] = {name: taxName2, rate:taxRate2, amount:taxAmount2};
}
}
if (item.tax_name1 || item.tax_name2) {
hasTaxes = true;
}
}
@ -30556,15 +30573,17 @@ function calculateAmounts(invoice) {
total += roundToTwo(invoice.custom_value2);
}
var tax = 0;
if (invoice.tax_rate && parseFloat(invoice.tax_rate)) {
tax = parseFloat(invoice.tax_rate);
taxRate1 = 0;
taxRate2 = 0;
if (invoice.tax_rate1 && parseFloat(invoice.tax_rate1)) {
taxRate1 = parseFloat(invoice.tax_rate1);
}
if (tax) {
var tax = roundToTwo(total * (tax/100));
total = parseFloat(total) + parseFloat(tax);
if (invoice.tax_rate2 && parseFloat(invoice.tax_rate2)) {
taxRate2 = parseFloat(invoice.tax_rate2);
}
taxAmount1 = roundToTwo(total * (taxRate1/100));
taxAmount2 = roundToTwo(total * (taxRate2/100));
total = total + taxAmount1 + taxAmount2;
for (var key in taxes) {
if (taxes.hasOwnProperty(key)) {
@ -30582,7 +30601,8 @@ function calculateAmounts(invoice) {
invoice.total_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance));
invoice.discount_amount = discount;
invoice.tax_amount = tax;
invoice.tax_amount1 = taxAmount1;
invoice.tax_amount2 = taxAmount2;
invoice.item_taxes = taxes;
if (NINJA.parseFloat(invoice.partial)) {
@ -30594,14 +30614,6 @@ function calculateAmounts(invoice) {
return invoice;
}
function getInvoiceTaxRate(invoice) {
var tax = 0;
if (invoice.tax_rate && parseFloat(invoice.tax_rate)) {
tax = parseFloat(invoice.tax_rate);
}
return tax;
}
// http://stackoverflow.com/questions/11941876/correctly-suppressing-warnings-in-datatables
window.alert = (function() {
var nativeAlert = window.alert;
@ -31337,13 +31349,15 @@ NINJA.invoiceLines = function(invoice) {
var qty = NINJA.parseFloat(item.qty) ? roundToTwo(NINJA.parseFloat(item.qty)) + '' : '';
var notes = item.notes;
var productKey = item.product_key;
var tax = '';
var tax1 = '';
var tax2 = '';
if (showItemTaxes) {
if (item.tax && parseFloat(item.tax.rate)) {
tax = parseFloat(item.tax.rate);
} else if (item.tax_rate && parseFloat(item.tax_rate)) {
tax = parseFloat(item.tax_rate);
if (item.tax_name1) {
tax1 = parseFloat(item.tax_rate1);
}
if (item.tax_name2) {
tax2 = parseFloat(item.tax_rate2);
}
}
@ -31380,7 +31394,14 @@ NINJA.invoiceLines = function(invoice) {
row.push({style:["quantity", rowStyle], text:qty || ' '});
}
if (showItemTaxes) {
row.push({style:["tax", rowStyle], text:tax ? (tax.toString() + '%') : ' '});
var str = '';
if (tax1) {
str += tax1.toString() + '% ';
}
if (tax2) {
str += tax2.toString() + '% ';
}
row.push({style:["tax", rowStyle], text:str});
}
row.push({style:["lineTotal", rowStyle], text:lineTotal || ' '});
@ -31452,9 +31473,13 @@ NINJA.subtotals = function(invoice, hideBalance)
}
}
if (invoice.tax_amount) {
var taxStr = invoice.tax_name + ' ' + (invoice.tax_rate*1).toString() + '%';
data.push([{text: taxStr}, {text: formatMoneyInvoice(invoice.tax_amount, invoice)}]);
if (invoice.tax_amount1) {
var taxStr = invoice.tax_name1 + ' ' + (invoice.tax_rate1*1).toString() + '%';
data.push([{text: taxStr}, {text: formatMoneyInvoice(invoice.tax_amount1, invoice)}]);
}
if (invoice.tax_amount2) {
var taxStr = invoice.tax_name2 + ' ' + (invoice.tax_rate2*1).toString() + '%';
data.push([{text: taxStr}, {text: formatMoneyInvoice(invoice.tax_amount2, invoice)}]);
}
if (NINJA.parseFloat(invoice.custom_value1) && invoice.custom_taxes1 != '1') {

View File

@ -350,13 +350,15 @@ NINJA.invoiceLines = function(invoice) {
var qty = NINJA.parseFloat(item.qty) ? roundToTwo(NINJA.parseFloat(item.qty)) + '' : '';
var notes = item.notes;
var productKey = item.product_key;
var tax = '';
var tax1 = '';
var tax2 = '';
if (showItemTaxes) {
if (item.tax && parseFloat(item.tax.rate)) {
tax = parseFloat(item.tax.rate);
} else if (item.tax_rate && parseFloat(item.tax_rate)) {
tax = parseFloat(item.tax_rate);
if (item.tax_name1) {
tax1 = parseFloat(item.tax_rate1);
}
if (item.tax_name2) {
tax2 = parseFloat(item.tax_rate2);
}
}
@ -393,7 +395,14 @@ NINJA.invoiceLines = function(invoice) {
row.push({style:["quantity", rowStyle], text:qty || ' '});
}
if (showItemTaxes) {
row.push({style:["tax", rowStyle], text:tax ? (tax.toString() + '%') : ' '});
var str = '';
if (tax1) {
str += tax1.toString() + '% ';
}
if (tax2) {
str += tax2.toString() + '% ';
}
row.push({style:["tax", rowStyle], text:str});
}
row.push({style:["lineTotal", rowStyle], text:lineTotal || ' '});
@ -465,9 +474,13 @@ NINJA.subtotals = function(invoice, hideBalance)
}
}
if (invoice.tax_amount) {
var taxStr = invoice.tax_name + ' ' + (invoice.tax_rate*1).toString() + '%';
data.push([{text: taxStr}, {text: formatMoneyInvoice(invoice.tax_amount, invoice)}]);
if (invoice.tax_amount1) {
var taxStr = invoice.tax_name1 + ' ' + (invoice.tax_rate1*1).toString() + '%';
data.push([{text: taxStr}, {text: formatMoneyInvoice(invoice.tax_amount1, invoice)}]);
}
if (invoice.tax_amount2) {
var taxStr = invoice.tax_name2 + ' ' + (invoice.tax_rate2*1).toString() + '%';
data.push([{text: taxStr}, {text: formatMoneyInvoice(invoice.tax_amount2, invoice)}]);
}
if (NINJA.parseFloat(invoice.custom_value1) && invoice.custom_taxes1 != '1') {

View File

@ -603,8 +603,10 @@ function calculateAmounts(invoice) {
for (var i=0; i<invoice.invoice_items.length; i++) {
var item = invoice.invoice_items[i];
var taxRate = 0;
var taxName = '';
var taxRate1 = 0;
var taxName1 = '';
var taxRate2 = 0;
var taxName2 = '';
if (item.product_key) {
invoice.has_product_key = true;
@ -612,9 +614,14 @@ function calculateAmounts(invoice) {
invoice.has_product_key = true;
}
if (item.tax_rate && parseFloat(item.tax_rate)) {
taxRate = parseFloat(item.tax_rate);
taxName = item.tax_name;
if (item.tax_rate1 && parseFloat(item.tax_rate1)) {
taxRate1 = parseFloat(item.tax_rate1);
taxName1 = item.tax_name1;
}
if (item.tax_rate2 && parseFloat(item.tax_rate2)) {
taxRate2 = parseFloat(item.tax_rate2);
taxName2 = item.tax_name2;
}
// calculate line item tax
@ -626,18 +633,28 @@ function calculateAmounts(invoice) {
lineTotal -= roundToTwo(lineTotal * (invoice.discount/100));
}
}
var taxAmount = roundToTwo(lineTotal * taxRate / 100);
if (taxAmount) {
var key = taxName + taxRate;
var taxAmount1 = roundToTwo(lineTotal * taxRate1 / 100);
if (taxAmount1) {
var key = taxName1 + taxRate1;
if (taxes.hasOwnProperty(key)) {
taxes[key].amount += taxAmount;
taxes[key].amount += taxAmount1;
} else {
taxes[key] = {name: taxName, rate:taxRate, amount:taxAmount};
taxes[key] = {name: taxName1, rate:taxRate1, amount:taxAmount1};
}
}
if (item.tax_name) {
var taxAmount2 = roundToTwo(lineTotal * taxRate2 / 100);
if (taxAmount2) {
var key = taxName2 + taxRate2;
if (taxes.hasOwnProperty(key)) {
taxes[key].amount += taxAmount2;
} else {
taxes[key] = {name: taxName2, rate:taxRate2, amount:taxAmount2};
}
}
if (item.tax_name1 || item.tax_name2) {
hasTaxes = true;
}
}
@ -662,15 +679,17 @@ function calculateAmounts(invoice) {
total += roundToTwo(invoice.custom_value2);
}
var tax = 0;
if (invoice.tax_rate && parseFloat(invoice.tax_rate)) {
tax = parseFloat(invoice.tax_rate);
taxRate1 = 0;
taxRate2 = 0;
if (invoice.tax_rate1 && parseFloat(invoice.tax_rate1)) {
taxRate1 = parseFloat(invoice.tax_rate1);
}
if (tax) {
var tax = roundToTwo(total * (tax/100));
total = parseFloat(total) + parseFloat(tax);
if (invoice.tax_rate2 && parseFloat(invoice.tax_rate2)) {
taxRate2 = parseFloat(invoice.tax_rate2);
}
taxAmount1 = roundToTwo(total * (taxRate1/100));
taxAmount2 = roundToTwo(total * (taxRate2/100));
total = total + taxAmount1 + taxAmount2;
for (var key in taxes) {
if (taxes.hasOwnProperty(key)) {
@ -688,7 +707,8 @@ function calculateAmounts(invoice) {
invoice.total_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance));
invoice.discount_amount = discount;
invoice.tax_amount = tax;
invoice.tax_amount1 = taxAmount1;
invoice.tax_amount2 = taxAmount2;
invoice.item_taxes = taxes;
if (NINJA.parseFloat(invoice.partial)) {
@ -700,14 +720,6 @@ function calculateAmounts(invoice) {
return invoice;
}
function getInvoiceTaxRate(invoice) {
var tax = 0;
if (invoice.tax_rate && parseFloat(invoice.tax_rate)) {
tax = parseFloat(invoice.tax_rate);
}
return tax;
}
// http://stackoverflow.com/questions/11941876/correctly-suppressing-warnings-in-datatables
window.alert = (function() {
var nativeAlert = window.alert;

View File

@ -17,6 +17,11 @@
label.control-label[for=invoice_number] {
font-weight: normal !important;
}
select.tax-select {
width: 50%;
float: left;
}
</style>
@stop
@ -204,7 +209,7 @@
@endif
<th style="min-width:120px" data-bind="text: costLabel">{{ $invoiceLabels['unit_cost'] }}</th>
<th style="{{ $account->hide_quantity ? 'display:none' : 'min-width:120px' }}" data-bind="text: qtyLabel">{{ $invoiceLabels['quantity'] }}</th>
<th style="min-width:120px;display:none;" data-bind="visible: $root.invoice_item_taxes.show">{{ trans('texts.tax') }}</th>
<th style="min-width:180px;display:none;" data-bind="visible: $root.invoice_item_taxes.show">{{ trans('texts.tax') }}</th>
<th style="min-width:120px;">{{ trans('texts.line_total') }}</th>
<th style="min-width:32px;" class="hide-border"></th>
</tr>
@ -246,10 +251,19 @@
{!! Former::select('')
->addOption('', '')
->options($taxRateOptions)
->data_bind('value: tax')
->data_bind('value: tax1')
->addClass('tax-select')
->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">
<input type="text" data-bind="value: tax_name1, attr: {name: 'invoice_items[' + $index() + '][tax_name1]'}" style="display:none">
<input type="text" data-bind="value: tax_rate1, attr: {name: 'invoice_items[' + $index() + '][tax_rate1]'}" style="display:none">
{!! Former::select('')
->addOption('', '')
->options($taxRateOptions)
->data_bind('value: tax2')
->addClass('tax-select')
->raw() !!}
<input type="text" data-bind="value: tax_name2, attr: {name: 'invoice_items[' + $index() + '][tax_name2]'}" style="display:none">
<input type="text" data-bind="value: tax_rate2, attr: {name: 'invoice_items[' + $index() + '][tax_rate2]'}" style="display:none">
</td>
<td style="text-align:right;padding-top:9px !important" nowrap>
<div class="line-total" data-bind="text: totals.total"></div>
@ -387,10 +401,19 @@
{!! Former::select('')
->addOption('', '')
->options($taxRateOptions)
->data_bind('value: tax')
->addClass('tax-select')
->data_bind('value: tax1')
->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">
<input type="text" name="tax_name1" data-bind="value: tax_name1" style="display:none">
<input type="text" name="tax_rate1" data-bind="value: tax_rate1" style="display:none">
{!! Former::select('')
->addOption('', '')
->options($taxRateOptions)
->addClass('tax-select')
->data_bind('value: tax2')
->raw() !!}
<input type="text" name="tax_name2" data-bind="value: tax_name2" style="display:none">
<input type="text" name="tax_rate2" data-bind="value: tax_rate2" style="display:none">
</td>
<td style="text-align: right"><span data-bind="text: totals.taxAmount"/></td>
</tr>
@ -770,8 +793,8 @@
// set the default account tax rate
@if ($account->invoice_taxes && ! empty($defaultTax))
var defaultTax = {!! $defaultTax !!};
model.invoice().tax_rate(defaultTax.rate);
model.invoice().tax_name(defaultTax.name);
model.invoice().tax_rate1(defaultTax.rate);
model.invoice().tax_name1(defaultTax.name);
@endif
@endif

View File

@ -52,10 +52,11 @@ function ViewModel(data) {
}
self.invoice_taxes.show = ko.computed(function() {
if (self.invoice_taxes()) {
if (self.invoice().tax_name1() || self.invoice().tax_name2()) {
return true;
}
return false;
return self.invoice_taxes() && {{ count($taxRateOptions) ? 'true' : 'false' }};
});
self.invoice_item_taxes.show = ko.computed(function() {
@ -64,7 +65,7 @@ function ViewModel(data) {
}
for (var i=0; i<self.invoice().invoice_items().length; i++) {
var item = self.invoice().invoice_items()[i];
if (item.tax_rate() > 0) {
if (item.tax_name1() || item.tax_name2()) {
return true;
}
}
@ -175,8 +176,10 @@ function InvoiceModel(data) {
self.start_date = ko.observable('');
self.end_date = ko.observable('');
self.last_sent_date = ko.observable('');
self.tax_name = ko.observable();
self.tax_rate = ko.observable();
self.tax_name1 = ko.observable();
self.tax_rate1 = ko.observable();
self.tax_name2 = ko.observable();
self.tax_rate2 = ko.observable();
self.is_recurring = ko.observable(0);
self.is_quote = ko.observable({{ $entityType == ENTITY_QUOTE ? '1' : '0' }});
self.auto_bill = ko.observable();
@ -258,15 +261,27 @@ function InvoiceModel(data) {
return self.has_tasks() ? invoiceLabels['rate'] : invoiceLabels['unit_cost'];
}, this);
this.tax = ko.computed({
this.tax1 = ko.computed({
read: function () {
return self.tax_rate() + ' ' + self.tax_name();
return 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_name(name);
self.tax_rate(rate);
self.tax_name1(name);
self.tax_rate1(rate);
}
})
this.tax2 = ko.computed({
read: function () {
return 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);
}
})
@ -359,15 +374,13 @@ function InvoiceModel(data) {
total = NINJA.parseFloat(total) + customValue2;
}
var taxRate = parseFloat(self.tax_rate());
//if (taxRate > 0) {
// var tax = roundToTwo(total * (taxRate/100));
// return self.formatMoney(tax);
//} else {
// return self.formatMoney(0);
//}
var tax = roundToTwo(total * (taxRate/100));
return self.formatMoney(tax);
var taxRate1 = parseFloat(self.tax_rate1());
var tax1 = roundToTwo(total * (taxRate1/100));
var taxRate2 = parseFloat(self.tax_rate2());
var tax2 = roundToTwo(total * (taxRate2/100));
return self.formatMoney(tax1 + tax2);
});
self.totals.itemTaxes = ko.computed(function() {
@ -383,13 +396,24 @@ function InvoiceModel(data) {
lineTotal -= roundToTwo(lineTotal * (self.discount()/100));
}
}
var taxAmount = roundToTwo(lineTotal * item.tax_rate() / 100);
var taxAmount = roundToTwo(lineTotal * item.tax_rate1() / 100);
if (taxAmount) {
var key = item.tax_name() + item.tax_rate();
var key = item.tax_name1() + item.tax_rate1();
if (taxes.hasOwnProperty(key)) {
taxes[key].amount += taxAmount;
} else {
taxes[key] = {name:item.tax_name(), rate:item.tax_rate(), amount:taxAmount};
taxes[key] = {name:item.tax_name1(), rate:item.tax_rate1(), amount:taxAmount};
}
}
var taxAmount = roundToTwo(lineTotal * item.tax_rate2() / 100);
if (taxAmount) {
var key = item.tax_name2() + item.tax_rate2();
if (taxes.hasOwnProperty(key)) {
taxes[key].amount += taxAmount;
} else {
taxes[key] = {name:item.tax_name2(), rate:item.tax_rate2(), amount:taxAmount};
}
}
}
@ -455,8 +479,9 @@ function InvoiceModel(data) {
total = NINJA.parseFloat(total) + customValue2;
}
var taxRate = parseFloat(self.tax_rate());
total = NINJA.parseFloat(total) + roundToTwo(total * (taxRate/100));
var taxAmount1 = roundToTwo(total * (parseFloat(self.tax_rate1())/100));
var taxAmount2 = roundToTwo(total * (parseFloat(self.tax_rate2())/100));
total = NINJA.parseFloat(total) + taxAmount1 + taxAmount2;
total = roundToTwo(total);
var taxes = self.totals.itemTaxes();
@ -641,21 +666,35 @@ function ItemModel(data) {
self.qty = ko.observable(0);
self.custom_value1 = ko.observable('');
self.custom_value2 = ko.observable('');
self.tax_name = ko.observable('');
self.tax_rate = ko.observable(0);
self.tax_name1 = ko.observable('');
self.tax_rate1 = ko.observable(0);
self.tax_name2 = ko.observable('');
self.tax_rate2 = ko.observable(0);
self.task_public_id = ko.observable('');
self.expense_public_id = ko.observable('');
self.actionsVisible = ko.observable(false);
this.tax = ko.computed({
this.tax1 = ko.computed({
read: function () {
return self.tax_rate() + ' ' + self.tax_name();
return 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_name(name);
self.tax_rate(rate);
self.tax_name1(name);
self.tax_rate1(rate);
}
})
this.tax2 = ko.computed({
read: function () {
return 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);
}
})
@ -794,9 +833,9 @@ ko.bindingHandlers.typeahead = {
}
@if ($account->invoice_item_taxes)
if (datum.default_tax_rate) {
model.tax_rate(datum.default_tax_rate.rate);
model.tax_name(datum.default_tax_rate.name);
model.tax(datum.default_tax_rate.rate + ' ' + datum.default_tax_rate.name);
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);
}
@endif
@endif