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

Working on taxes

This commit is contained in:
Hillel Coren 2013-12-31 21:49:54 +02:00
parent 7a90856025
commit 309442cab3
8 changed files with 212 additions and 61 deletions

View File

@ -134,7 +134,7 @@ class InvoiceController extends \BaseController {
return View::make('invoices.deleted'); return View::make('invoices.deleted');
} }
if ($invoice->invoice_status_id < INVOICE_STATUS_VIEWED) if (!$invoice->isViewed())
{ {
$invoice->invoice_status_id = INVOICE_STATUS_VIEWED; $invoice->invoice_status_id = INVOICE_STATUS_VIEWED;
$invoice->save(); $invoice->save();
@ -241,15 +241,13 @@ class InvoiceController extends \BaseController {
} }
else else
{ {
return Utils::fatalError('Sorry, there was an error processing your payment. Please try again later.<p>');
} }
} }
catch (\Exception $e) catch (\Exception $e)
{ {
exit('Sorry, there was an error processing your payment. Please try again later.<p>'.$e); return Utils::fatalError('Sorry, there was an error processing your payment. Please try again later.<p>'.$e);
} }
exit;
} }
public function do_payment() public function do_payment()
@ -274,9 +272,12 @@ class InvoiceController extends \BaseController {
$payment->transaction_reference = $ref; $payment->transaction_reference = $ref;
$payment->save(); $payment->save();
if ($payment->amount >= $invoice->amount) { if ($payment->amount >= $invoice->amount)
{
$invoice->invoice_status_id = INVOICE_STATUS_PAID; $invoice->invoice_status_id = INVOICE_STATUS_PAID;
} else { }
else
{
$invoice->invoice_status_id = INVOICE_STATUS_PARTIAL; $invoice->invoice_status_id = INVOICE_STATUS_PARTIAL;
} }
$invoice->save(); $invoice->save();
@ -286,12 +287,12 @@ class InvoiceController extends \BaseController {
} }
else else
{ {
exit($response->getMessage()); return Utils::fatalError('Sorry, there was an error processing your payment. Please try again later.<p>'.$response->getMessage());
} }
} }
catch (\Exception $e) catch (\Exception $e)
{ {
exit('Sorry, there was an error processing your payment. Please try again later.' . $e); return Utils::fatalError('Sorry, there was an error processing your payment. Please try again later.<p>'.$e);
} }
} }
@ -404,11 +405,16 @@ class InvoiceController extends \BaseController {
$invoiceData['client_id'] = $client->id; $invoiceData['client_id'] = $client->id;
$invoice = $this->invoiceRepo->save($publicId, $invoiceData); $invoice = $this->invoiceRepo->save($publicId, $invoiceData);
$account = Auth::user()->account;
if ($account->invoice_taxes != $input->invoice_taxes || $account->invoice_item_taxes != $input->invoice_item_taxes)
{
$account->invoice_taxes = $input->invoice_taxes;
$account->invoice_item_taxes = $input->invoice_item_taxes;
$account->save();
}
if ($action == 'email' && $invoice->invoice_status_id == INVOICE_STATUS_DRAFT) if ($action == 'email' && $invoice->invoice_status_id == INVOICE_STATUS_DRAFT)
{ {
$invoice->invoice_status_id = INVOICE_STATUS_SENT;
$invoice->save();
$client->balance = $client->balance + $invoice->amount; $client->balance = $client->balance + $invoice->amount;
$client->save(); $client->save();
} }

View File

@ -125,6 +125,9 @@ class ConfideSetupUsersTable extends Migration {
$t->unsignedInteger('country_id')->nullable(); $t->unsignedInteger('country_id')->nullable();
$t->text('invoice_terms'); $t->text('invoice_terms');
$t->boolean('invoice_taxes')->default(true);
$t->boolean('invoice_item_taxes')->default(false);
$t->foreign('timezone_id')->references('id')->on('timezones'); $t->foreign('timezone_id')->references('id')->on('timezones');
$t->foreign('date_format_id')->references('id')->on('date_formats'); $t->foreign('date_format_id')->references('id')->on('date_formats');
$t->foreign('datetime_format_id')->references('id')->on('datetime_formats'); $t->foreign('datetime_format_id')->references('id')->on('datetime_formats');
@ -303,6 +306,9 @@ class ConfideSetupUsersTable extends Migration {
$t->timestamp('last_sent_date')->nullable(); $t->timestamp('last_sent_date')->nullable();
$t->unsignedInteger('recurring_invoice_id')->index()->nullable(); $t->unsignedInteger('recurring_invoice_id')->index()->nullable();
$t->string('tax_name');
$t->decimal('tax_rate', 13, 4);
$t->decimal('amount', 13, 4); $t->decimal('amount', 13, 4);
$t->decimal('balance', 13, 4); $t->decimal('balance', 13, 4);

View File

@ -2,6 +2,12 @@
class Utils class Utils
{ {
public static function fatalError($error)
{
Log::error($error);
return View::make('error')->with('error', $error);
}
public static function formatPhoneNumber($phoneNumber) public static function formatPhoneNumber($phoneNumber)
{ {
$phoneNumber = preg_replace('/[^0-9]/','',$phoneNumber); $phoneNumber = preg_replace('/[^0-9]/','',$phoneNumber);

View File

@ -42,9 +42,15 @@ class Invoice extends EntityModel
return $this->invoice_status_id >= INVOICE_STATUS_SENT; return $this->invoice_status_id >= INVOICE_STATUS_SENT;
} }
public function isViewed()
{
return $this->invoice_status_id >= INVOICE_STATUS_VIEWED;
}
public function hidePrivateFields() public function hidePrivateFields()
{ {
$this->setVisible(['invoice_number', 'discount', 'po_number', 'invoice_date', 'due_date', 'terms', 'currency_id', 'public_notes', 'amount', 'balance', 'invoice_items', 'client']); $this->setVisible(['invoice_number', 'discount', 'po_number', 'invoice_date', 'due_date', 'terms', 'currency_id', 'public_notes', 'amount', 'balance', 'invoice_items', 'client', 'tax_name', 'tax_rate']);
$this->client->setVisible(['name', 'address1', 'address2', 'city', 'state', 'postal_code', 'work_phone', 'payment_terms', 'contacts']); $this->client->setVisible(['name', 'address1', 'address2', 'city', 'state', 'postal_code', 'work_phone', 'payment_terms', 'contacts']);
foreach ($this->invoice_items as $invoiceItem) foreach ($this->invoice_items as $invoiceItem)

View File

@ -80,7 +80,7 @@ class InvoiceRepository
} }
$invoice->client_id = $data['client_id']; $invoice->client_id = $data['client_id'];
$invoice->discount = $data['discount']; $invoice->discount = floatval($data['discount']);
$invoice->invoice_number = trim($data['invoice_number']); $invoice->invoice_number = trim($data['invoice_number']);
$invoice->invoice_date = Utils::toSqlDate($data['invoice_date']); $invoice->invoice_date = Utils::toSqlDate($data['invoice_date']);
$invoice->due_date = Utils::toSqlDate($data['due_date']); $invoice->due_date = Utils::toSqlDate($data['due_date']);
@ -93,30 +93,19 @@ class InvoiceRepository
$invoice->public_notes = trim($data['public_notes']); $invoice->public_notes = trim($data['public_notes']);
$invoice->po_number = trim($data['po_number']); $invoice->po_number = trim($data['po_number']);
$invoice->currency_id = $data['currency_id']; $invoice->currency_id = $data['currency_id'];
$invoice->tax_rate = 0;
if (isset($data['tax']) && isset($data['tax']->rate) && floatval($data['tax']->rate) > 0)
{
$invoice->tax_rate = floatval($data['tax']->rate);
$invoice->tax_name = trim($data['tax']->name);
}
$invoice->save();
$invoice->invoice_items()->forceDelete();
$total = 0; $total = 0;
foreach ($data['invoice_items'] as $item)
{
if (!isset($item->cost))
{
$item->cost = 0;
}
if (!isset($item->qty))
{
$item->qty = 0;
}
$total += floatval($item->qty) * floatval($item->cost);
}
$invoice->amount = $total;
$invoice->balance = $total;
$invoice->save();
$invoice->invoice_items()->forceDelete();
foreach ($data['invoice_items'] as $item) foreach ($data['invoice_items'] as $item)
{ {
if (!$item->cost && !$item->qty && !$item->product_key && !$item->notes) if (!$item->cost && !$item->qty && !$item->product_key && !$item->notes)
@ -149,17 +138,31 @@ class InvoiceRepository
$invoiceItem->notes = trim($item->notes); $invoiceItem->notes = trim($item->notes);
$invoiceItem->cost = floatval($item->cost); $invoiceItem->cost = floatval($item->cost);
$invoiceItem->qty = floatval($item->qty); $invoiceItem->qty = floatval($item->qty);
$invoiceItem->tax_rate = 0;
if ($item->tax && isset($item->tax->rate) && isset($item->tax->name)) if ($item->tax && isset($item->tax->rate) && floatval($item->tax->rate) > 0)
{ {
$invoiceItem->tax_rate = floatval($item->tax->rate); $invoiceItem->tax_rate = floatval($item->tax->rate);
$invoiceItem->tax_name = trim($item->tax->name); $invoiceItem->tax_name = trim($item->tax->name);
} }
$invoice->invoice_items()->save($invoiceItem); $invoice->invoice_items()->save($invoiceItem);
$total += floatval($item->qty) * floatval($item->cost);
$lineTotal = $invoiceItem->cost * $invoiceItem->qty;
$total += $lineTotal + ($lineTotal * $invoiceItem->tax_rate / 100);
} }
if ($invoice->discount > 0)
{
$total *= (100 - $invoice->discount) / 100;
}
$total += $total * $invoice->tax_rate / 100;
$invoice->amount = $total;
$invoice->balance = $total;
$invoice->save();
return $invoice; return $invoice;
} }
} }

7
app/views/error.blade.php Executable file
View File

@ -0,0 +1,7 @@
@extends('header')
@section('content')
{{ $error }}
@stop

View File

@ -108,36 +108,36 @@
<th>Description</th> <th>Description</th>
<th>Unit Cost</th> <th>Unit Cost</th>
<th>Quantity</th> <th>Quantity</th>
<th data-bind="visible: tax_rates().length > 1">Tax</th> <th data-bind="visible: showInvoiceItemTaxes">Tax</th>
<th>Line&nbsp;Total</th> <th>Line&nbsp;Total</th>
<th class="hide-border"></th> <th class="hide-border"></th>
</tr> </tr>
</thead> </thead>
<tbody data-bind="sortable: { data: invoice_items, afterMove: onDragged }"> <tbody data-bind="sortable: { data: invoice_items, afterMove: onDragged }">
<tr data-bind="event: { mouseover: showActions, mouseout: hideActions }" class="sortable-row"> <tr data-bind="event: { mouseover: showActions, mouseout: hideActions }" class="sortable-row">
<td style="width:20px;" class="hide-border td-icon"> <td style="min-width:20px;" class="hide-border td-icon">
<i data-bind="visible: actionsVisible() &amp;&amp; $parent.invoice_items().length > 1" class="fa fa-sort"></i> <i data-bind="visible: actionsVisible() &amp;&amp; $parent.invoice_items().length > 1" class="fa fa-sort"></i>
</td> </td>
<td style="width:120px"> <td style="min-width:120px">
{{ Former::text('product_key')->useDatalist(Product::getProductKeys($products), 'key')->onkeyup('onItemChange()') {{ Former::text('product_key')->useDatalist(Product::getProductKeys($products), 'key')->onkeyup('onItemChange()')
->raw()->data_bind("value: product_key, valueUpdate: 'afterkeydown'")->addClass('datalist') }} ->raw()->data_bind("value: product_key, valueUpdate: 'afterkeydown'")->addClass('datalist') }}
</td> </td>
<td style="width:300px"> <td style="width:100%">
<textarea data-bind="value: wrapped_notes, valueUpdate: 'afterkeydown'" rows="1" cols="60" style="resize: none;" class="form-control word-wrap"></textarea> <textarea data-bind="value: wrapped_notes, valueUpdate: 'afterkeydown'" rows="1" cols="60" style="resize: none;" class="form-control word-wrap"></textarea>
</td> </td>
<td style="width:100px"> <td style="min-width:120px">
<input onkeyup="onItemChange()" data-bind="value: prettyCost, valueUpdate: 'afterkeydown'" style="text-align: right" class="form-control"//> <input onkeyup="onItemChange()" data-bind="value: prettyCost, valueUpdate: 'afterkeydown'" style="text-align: right" class="form-control"//>
</td> </td>
<td style="width:80px"> <td style="min-width:120px">
<input onkeyup="onItemChange()" data-bind="value: prettyQty, valueUpdate: 'afterkeydown'" style="text-align: right" class="form-control"//> <input onkeyup="onItemChange()" data-bind="value: prettyQty, valueUpdate: 'afterkeydown'" style="text-align: right" class="form-control"//>
</td> </td>
<td style="width:120px; vertical-align:middle" data-bind="visible: $parent.tax_rates().length > 1"> <td style="min-width:120px; vertical-align:middle" data-bind="visible: $parent.showInvoiceItemTaxes">
<select class="form-control" style="width:100%" data-bind="value: tax, options: $parent.tax_rates, optionsText: 'displayName'"></select> <select class="form-control" style="width:100%" data-bind="value: tax, options: $parent.tax_rates, optionsText: 'displayName'"></select>
</td> </td>
<td style="width:100px;text-align: right;padding-top:9px !important"> <td style="min-width:120px;text-align: right;padding-top:9px !important">
<span data-bind="text: total"></span> <span data-bind="text: total"></span>
</td> </td>
<td style="width:20px; cursor:pointer" class="hide-border td-icon"> <td style="min-width:20px; cursor:pointer" class="hide-border td-icon">
&nbsp;<i data-bind="click: $parent.removeItem, visible: actionsVisible() &amp;&amp; $parent.invoice_items().length > 1" class="fa fa-minus-circle" title="Remove item"/> &nbsp;<i data-bind="click: $parent.removeItem, visible: actionsVisible() &amp;&amp; $parent.invoice_items().length > 1" class="fa fa-minus-circle" title="Remove item"/>
</td> </td>
</tr> </tr>
@ -145,22 +145,33 @@
<tfoot> <tfoot>
<tr> <tr>
<td class="hide-border"/> <td class="hide-border"/>
<td data-bind="attr: {colspan: tax_rates().length > 1 ? 3 : 2}"/> <td colspan="2"/>
<td data-bind="visible: showInvoiceItemTaxes"/>
<td colspan="2">Subtotal</td> <td colspan="2">Subtotal</td>
<td style="text-align: right"><span data-bind="text: subtotal"/></td> <td style="text-align: right"><span data-bind="text: subtotal"/></td>
</tr> </tr>
<tr data-bind="visible: discount() > 0"> <tr data-bind="visible: discount() > 0">
<td class="hide-border" data-bind="attr: {colspan: tax_rates().length > 1 ? 4 : 3}"/> <td class="hide-border" colspan="3"/>
<td class="hide-border" data-bind="visible: showInvoiceItemTaxes"/>
<td colspan="2">Discount</td> <td colspan="2">Discount</td>
<td style="text-align: right"><span data-bind="text: discounted"/></td> <td style="text-align: right"><span data-bind="text: discounted"/></td>
</tr> </tr>
<tr data-bind="visible: showInvoiceTaxes">
<td class="hide-border" colspan="3"/>
<td class="hide-border" data-bind="visible: showInvoiceItemTaxes"/>
<td style="vertical-align: middle">Tax</td>
<td><select class="form-control" style="width:100%" data-bind="value: tax, options: tax_rates, optionsText: 'displayName'"></select></td>
<td style="vertical-align: middle; text-align: right"><span data-bind="text: taxAmount"/></td>
</tr>
<tr> <tr>
<td class="hide-border" data-bind="attr: {colspan: tax_rates().length > 1 ? 4 : 3}"/> <td class="hide-border" colspan="3"/>
<td class="hide-border" data-bind="visible: showInvoiceItemTaxes"/>
<td colspan="2">Paid to Date</td> <td colspan="2">Paid to Date</td>
<td style="text-align: right"></td> <td style="text-align: right"></td>
</tr> </tr>
<tr> <tr>
<td class="hide-border" data-bind="attr: {colspan: tax_rates().length > 1 ? 4 : 3}"/> <td class="hide-border" colspan="3"/>
<td class="hide-border" data-bind="visible: showInvoiceItemTaxes"/>
<td colspan="2"><b>Balance Due</b></td> <td colspan="2"><b>Balance Due</b></td>
<td style="text-align: right"><span data-bind="text: total"/></td> <td style="text-align: right"><span data-bind="text: total"/></td>
</tr> </tr>
@ -319,6 +330,12 @@
</tbody> </tbody>
</table> </table>
&nbsp; &nbsp;
{{ Former::checkbox('invoice_taxes')->text('Enable specifying an <b>invoice tax</b>')
->label('Settings')->data_bind('checked: invoice_taxes, enable: tax_rates().length > 1') }}
{{ Former::checkbox('invoice_item_taxes')->text('Enable specifying <b>line item taxes</b>')
->label('&nbsp;')->data_bind('checked: invoice_item_taxes, enable: tax_rates().length > 1') }}
</div> </div>
<div class="modal-footer" style="margin-top: 0px"> <div class="modal-footer" style="margin-top: 0px">
@ -394,9 +411,24 @@
$('#taxModal').on('shown.bs.modal', function () { $('#taxModal').on('shown.bs.modal', function () {
$('#taxModal input:first').focus(); $('#taxModal input:first').focus();
}).on('hidden.bs.modal', function () { }).on('hidden.bs.modal', function () {
console.log('TAX HIDDEN: %s %s', model.invoice_taxes(), model.invoice_item_taxes())
/*
var blank = model.getBlankTaxRate();
if (!model.invoice_taxes()) {
model.tax(blank);
}
if (!model.invoice_item_taxes()) {
for (var i=0; i<model.invoice_items().length; i++) {
var item = model.invoice_items()[i];
item.tax(blank);
}
}
*/
/*
if (model.taxBackup) { if (model.taxBackup) {
} }
*/
}) })
$('#actionDropDown > button:first').click(function() { $('#actionDropDown > button:first').click(function() {
@ -558,12 +590,16 @@
self.due_date = ko.observable(''); self.due_date = ko.observable('');
self.start_date = ko.observable(''); self.start_date = ko.observable('');
self.end_date = ko.observable(''); self.end_date = ko.observable('');
self.tax = ko.observable('');
self.is_recurring = ko.observable(false); self.is_recurring = ko.observable(false);
self.invoice_status_id = ko.observable(0); self.invoice_status_id = ko.observable(0);
self.invoice_items = ko.observableArray(); self.invoice_items = ko.observableArray();
self.tax_rates = ko.observableArray(); self.tax_rates = ko.observableArray();
self.invoice_taxes = ko.observable({{ Auth::user()->account->invoice_taxes ? 'true' : 'false' }});
self.invoice_item_taxes = ko.observable({{ Auth::user()->account->invoice_item_taxes ? 'true' : 'false' }});
self.mapping = { self.mapping = {
'invoice_items': { 'invoice_items': {
create: function(options) { create: function(options) {
@ -590,6 +626,30 @@
owner: this owner: this
}); });
self.showInvoiceTaxes = ko.computed(function() {
if (self.tax_rates().length > 1 && self.invoice_taxes()) {
return true;
}
if (self.tax() && self.tax().rate() > 0) {
return true;
}
return false;
});
self.showInvoiceItemTaxes = ko.computed(function() {
if (self.tax_rates().length > 1 && self.invoice_item_taxes()) {
return true;
}
for (var i=0; i<self.invoice_items().length; i++) {
var item = self.invoice_items()[i];
if (item.tax() && item.tax().rate() > 0) {
return true;
}
}
return false;
});
self.wrapped_notes = ko.computed({ self.wrapped_notes = ko.computed({
read: function() { read: function() {
$('#public_notes').height(this.public_notes().split('\n').length * 36); $('#public_notes').height(this.public_notes().split('\n').length * 36);
@ -653,7 +713,7 @@
$('#emailError').css( "display", "none" ); $('#emailError').css( "display", "none" );
//$('.client_select input.form-control').focus(); //$('.client_select input.form-control').focus();
$('#terms').focus(); $('#invoice_number').focus();
refreshPDF(); refreshPDF();
model.clientBackup = false; model.clientBackup = false;
@ -682,6 +742,15 @@
applyComboboxListeners(); applyComboboxListeners();
} }
self.getBlankTaxRate = function() {
for (var i=0; i<self.tax_rates().length; i++) {
var taxRate = self.tax_rates()[i];
if (!taxRate.name()) {
return taxRate;
}
}
}
this.rawSubtotal = ko.computed(function() { this.rawSubtotal = ko.computed(function() {
var total = 0; var total = 0;
for(var p = 0; p < self.invoice_items().length; ++p) for(var p = 0; p < self.invoice_items().length; ++p)
@ -696,12 +765,32 @@
return total > 0 ? formatMoney(total, self.currency_id()) : ''; return total > 0 ? formatMoney(total, self.currency_id()) : '';
}); });
this.rawDiscounted = ko.computed(function() {
return self.rawSubtotal() * (self.discount()/100);
});
this.discounted = ko.computed(function() { this.discounted = ko.computed(function() {
var total = self.rawSubtotal() * (self.discount()/100); return formatMoney(self.rawDiscounted(), self.currency_id());
return formatMoney(total, self.currency_id());
}); });
self.taxAmount = ko.computed(function() {
var total = self.rawSubtotal();
var discount = parseFloat(self.discount());
if (discount > 0) {
total = total * ((100 - discount)/100);
}
var taxRate = self.tax() ? parseFloat(self.tax().rate()) : 0;
if (taxRate > 0) {
var tax = total * (taxRate/100);
return formatMoney(tax, self.currency_id());
} else {
return formatMoney(0);
}
});
this.total = ko.computed(function() { this.total = ko.computed(function() {
var total = self.rawSubtotal(); var total = self.rawSubtotal();
@ -710,6 +799,11 @@
total = total * ((100 - discount)/100); total = total * ((100 - discount)/100);
} }
var taxRate = self.tax() ? parseFloat(self.tax().rate()) : 0;
if (taxRate > 0) {
total = parseFloat(total) + (total * (taxRate/100));
}
return total > 0 ? formatMoney(total, self.currency_id()) : ''; return total > 0 ? formatMoney(total, self.currency_id()) : '';
}); });
@ -821,9 +915,9 @@
self.displayName = ko.computed(function() { self.displayName = ko.computed(function() {
var name = self.name() ? self.name() : false; var name = self.name() ? self.name() : '';
var rate = self.rate() ? parseFloat(self.rate()) : false; var rate = self.rate() ? parseFloat(self.rate()) + '% -' : '';
return (name && rate) ? (rate + '%' + ' - ' + name) : ''; return rate + name;
}); });
self.hideActions = function() { self.hideActions = function() {

View File

@ -32,7 +32,7 @@ function generatePDF(invoice) {
for (var i=0; i<invoice.invoice_items.length; i++) for (var i=0; i<invoice.invoice_items.length; i++)
{ {
var item = invoice.invoice_items[i]; var item = invoice.invoice_items[i];
if (item.tax && item.tax.rate > 0) { if ((item.tax && item.tax.rate > 0) || (item.tax_rate && parseFloat(item.tax_rate) > 0)) {
hasTaxes = true; hasTaxes = true;
break; break;
} }
@ -141,7 +141,12 @@ function generatePDF(invoice) {
var qty = item.qty ? parseFloat(item.qty) + '' : ''; var qty = item.qty ? parseFloat(item.qty) + '' : '';
var notes = item.notes; var notes = item.notes;
var productKey = item.product_key; var productKey = item.product_key;
var tax = item.tax && parseFloat(item.tax.rate) ? parseFloat(item.tax.rate) + '%' : false; var tax = 0;
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);
}
// show at most one blank line // show at most one blank line
if (shownItem && (!cost || cost == '0.00') && !qty && !notes && !productKey) { if (shownItem && (!cost || cost == '0.00') && !qty && !notes && !productKey) {
@ -155,7 +160,7 @@ function generatePDF(invoice) {
var lineTotal = item.cost * item.qty; var lineTotal = item.cost * item.qty;
if (tax) { if (tax) {
lineTotal += lineTotal * parseFloat(item.tax.rate) / 100; lineTotal += lineTotal * tax / 100;
} }
if (lineTotal) { if (lineTotal) {
total += lineTotal; total += lineTotal;
@ -164,7 +169,7 @@ function generatePDF(invoice) {
var costX = unitCostRight - (doc.getStringUnitWidth(cost) * doc.internal.getFontSize()); var costX = unitCostRight - (doc.getStringUnitWidth(cost) * doc.internal.getFontSize());
var qtyX = qtyRight - (doc.getStringUnitWidth(qty) * doc.internal.getFontSize()); var qtyX = qtyRight - (doc.getStringUnitWidth(qty) * doc.internal.getFontSize());
var taxX = taxRight - (doc.getStringUnitWidth(tax) * doc.internal.getFontSize()); var taxX = taxRight - (doc.getStringUnitWidth(tax+'%') * doc.internal.getFontSize());
var totalX = lineTotalRight - (doc.getStringUnitWidth(lineTotal) * doc.internal.getFontSize()); var totalX = lineTotalRight - (doc.getStringUnitWidth(lineTotal) * doc.internal.getFontSize());
var x = tableTop + (line * tableRowHeight) + 6; var x = tableTop + (line * tableRowHeight) + 6;
if (i==0) x -= 4; if (i==0) x -= 4;
@ -176,7 +181,7 @@ function generatePDF(invoice) {
doc.text(totalX, x, lineTotal); doc.text(totalX, x, lineTotal);
if (tax) { if (tax) {
doc.text(taxX, x, tax); doc.text(taxX, x, tax+'%');
} }
line += doc.splitTextToSize(item.notes, 200).length; line += doc.splitTextToSize(item.notes, 200).length;
@ -206,12 +211,30 @@ function generatePDF(invoice) {
x += 16; x += 16;
doc.text(footerLeft, x, 'Discount'); doc.text(footerLeft, x, 'Discount');
var discount = formatMoney(total * (invoice.discount/100), currencyId, true); var discount = total * (invoice.discount/100);
total -= discount; total -= discount;
discount = formatMoney(discount, currencyId, true);
var discountX = headerRight - (doc.getStringUnitWidth(discount) * doc.internal.getFontSize()); var discountX = headerRight - (doc.getStringUnitWidth(discount) * doc.internal.getFontSize());
doc.text(discountX, x, discount); doc.text(discountX, x, discount);
} }
var tax = 0;
if (invoice.tax && parseFloat(invoice.tax.rate)) {
tax = parseFloat(invoice.tax.rate);
} else if (invoice.tax_rate && parseFloat(invoice.tax_rate)) {
tax = parseFloat(invoice.tax_rate);
}
if (tax) {
x += 16;
doc.text(footerLeft, x, 'Tax ' + tax + '%');
var tax = total * (tax/100);
total = parseFloat(total) + parseFloat(tax);
tax = formatMoney(tax, currencyId, true);
var taxX = headerRight - (doc.getStringUnitWidth(tax) * doc.internal.getFontSize());
doc.text(taxX, x, tax);
}
x += 16; x += 16;
doc.text(footerLeft, x, 'Paid to Date'); doc.text(footerLeft, x, 'Paid to Date');
var paid = formatMoney(0, currencyId, true); var paid = formatMoney(0, currencyId, true);