mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
working in taxes
This commit is contained in:
parent
130a176888
commit
f323d0ed95
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,6 @@
|
||||
/app/config/staging
|
||||
/app/config/development
|
||||
/app/storage/
|
||||
/app/storage
|
||||
/public/logo
|
||||
/public/build
|
||||
/bootstrap/compiled.php
|
||||
|
@ -348,6 +348,7 @@ class InvoiceController extends \BaseController {
|
||||
'products' => Product::scope()->get(array('product_key','notes','cost','qty')),
|
||||
'countries' => Country::orderBy('name')->get(),
|
||||
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
|
||||
'taxRates' => TaxRate::scope()->orderBy('name')->get(),
|
||||
'frequencies' => array(
|
||||
1 => 'Weekly',
|
||||
2 => 'Two weeks',
|
||||
|
@ -10,7 +10,6 @@ class ConfideSetupUsersTable extends Migration {
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::dropIfExists('tax_rates');
|
||||
Schema::dropIfExists('themes');
|
||||
Schema::dropIfExists('credits');
|
||||
Schema::dropIfExists('activities');
|
||||
@ -20,6 +19,7 @@ class ConfideSetupUsersTable extends Migration {
|
||||
Schema::dropIfExists('payments');
|
||||
Schema::dropIfExists('invoice_items');
|
||||
Schema::dropIfExists('products');
|
||||
Schema::dropIfExists('tax_rates');
|
||||
Schema::dropIfExists('contacts');
|
||||
Schema::dropIfExists('invoices');
|
||||
Schema::dropIfExists('password_reminders');
|
||||
@ -313,6 +313,24 @@ class ConfideSetupUsersTable extends Migration {
|
||||
$t->unique( array('account_id','public_id') );
|
||||
});
|
||||
|
||||
Schema::create('tax_rates', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('account_id')->index();
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->string('name');
|
||||
$t->decimal('rate', 10, 2);
|
||||
|
||||
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
$t->foreign('user_id')->references('id')->on('users');
|
||||
|
||||
$t->unsignedInteger('public_id');
|
||||
$t->unique( array('account_id','public_id') );
|
||||
});
|
||||
|
||||
Schema::create('products', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
@ -341,6 +359,7 @@ class ConfideSetupUsersTable extends Migration {
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->unsignedInteger('invoice_id')->index();
|
||||
$t->unsignedInteger('product_id')->nullable();
|
||||
$t->unsignedInteger('tax_rate_id')->nullable();
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
@ -354,6 +373,7 @@ class ConfideSetupUsersTable extends Migration {
|
||||
|
||||
$t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
|
||||
$t->foreign('product_id')->references('id')->on('products');
|
||||
$t->foreign('tax_rate_id')->references('id')->on('tax_rates');
|
||||
$t->foreign('user_id')->references('id')->on('users');
|
||||
|
||||
$t->unsignedInteger('public_id');
|
||||
@ -412,24 +432,6 @@ class ConfideSetupUsersTable extends Migration {
|
||||
$t->unique( array('account_id','public_id') );
|
||||
});
|
||||
|
||||
Schema::create('tax_rates', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('account_id')->index();
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->string('name');
|
||||
$t->decimal('rate', 10, 2);
|
||||
|
||||
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
$t->foreign('user_id')->references('id')->on('users');
|
||||
|
||||
$t->unsignedInteger('public_id');
|
||||
$t->unique( array('account_id','public_id') );
|
||||
});
|
||||
|
||||
Schema::create('activities', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
@ -461,7 +463,6 @@ class ConfideSetupUsersTable extends Migration {
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('tax_rates');
|
||||
Schema::dropIfExists('themes');
|
||||
Schema::dropIfExists('credits');
|
||||
Schema::dropIfExists('activities');
|
||||
@ -471,6 +472,7 @@ class ConfideSetupUsersTable extends Migration {
|
||||
Schema::dropIfExists('payments');
|
||||
Schema::dropIfExists('invoice_items');
|
||||
Schema::dropIfExists('products');
|
||||
Schema::dropIfExists('tax_rates');
|
||||
Schema::dropIfExists('contacts');
|
||||
Schema::dropIfExists('invoices');
|
||||
Schema::dropIfExists('password_reminders');
|
||||
|
@ -47,9 +47,11 @@ class EntityModel extends Eloquent
|
||||
|
||||
public function scopeScope($query, $publicId = false, $accountId = false)
|
||||
{
|
||||
if (!$accountId) {
|
||||
if (!$accountId)
|
||||
{
|
||||
$accountId = Auth::user()->account_id;
|
||||
}
|
||||
|
||||
$query->whereAccountId($accountId);
|
||||
|
||||
if ($publicId)
|
||||
|
@ -4,6 +4,7 @@ use Invoice;
|
||||
use InvoiceItem;
|
||||
use Product;
|
||||
use Utils;
|
||||
use TaxRate;
|
||||
|
||||
class InvoiceRepository
|
||||
{
|
||||
@ -134,6 +135,24 @@ class InvoiceRepository
|
||||
$product->save();
|
||||
}
|
||||
|
||||
$taxRate = false;
|
||||
if ($item->tax)
|
||||
{
|
||||
if ($item->tax->public_id)
|
||||
{
|
||||
$taxRate = TaxRate::scope($item->tax->public_id)->firstOrFail();
|
||||
}
|
||||
else
|
||||
{
|
||||
$taxRate = TaxRate::createNew();
|
||||
}
|
||||
|
||||
$taxRate->rate = floatval($item->tax->rate);
|
||||
$taxRate->name = trim($item->tax->name);
|
||||
|
||||
$taxRate->save();
|
||||
}
|
||||
|
||||
$invoiceItem = InvoiceItem::createNew();
|
||||
$invoiceItem->product_id = isset($product) ? $product->id : null;
|
||||
$invoiceItem->product_key = trim($item->product_key);
|
||||
@ -141,6 +160,13 @@ class InvoiceRepository
|
||||
$invoiceItem->cost = floatval($item->cost);
|
||||
$invoiceItem->qty = floatval($item->qty);
|
||||
|
||||
if ($taxRate)
|
||||
{
|
||||
$invoiceItem->tax_rate_id = $taxRate->id;
|
||||
$invoiceItem->tax_rate = $taxRate->rate;
|
||||
$invoiceItem->tax_name = $taxRate->name;
|
||||
}
|
||||
|
||||
$invoice->invoice_items()->save($invoiceItem);
|
||||
$total += floatval($item->qty) * floatval($item->cost);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
@section('head')
|
||||
|
||||
<meta name="csrf-token" content="<?= csrf_token() ?>">
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script>
|
||||
<script src="{{ asset('js/jquery-ui.min.js') }}" type="text/javascript"></script>
|
||||
@if (Auth::check() && Auth::user()->theme_id)
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('css/themes/'.Auth::user()->theme->name.'.min.css') }}"/>
|
||||
@else
|
||||
|
@ -114,7 +114,7 @@
|
||||
<input onkeyup="onItemChange()" data-bind="value: qty, valueUpdate: 'afterkeydown'" style="text-align: right" class="form-control" onchange="refreshPDF()"//>
|
||||
</td>
|
||||
<td style="width:80px; vertical-align:middle" data-bind="visible: $parent.tax_rates().length > 1">
|
||||
<select style="width:100%" data-bind="options: $parent.tax_rates"></select>
|
||||
<select style="width:100%" data-bind="value: tax, options: $parent.tax_rates, optionsText: 'displayName'"></select>
|
||||
</td>
|
||||
<td style="width:100px;text-align: right;padding-top:9px !important">
|
||||
<span data-bind="text: total"></span>
|
||||
@ -126,7 +126,8 @@
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td class="hide-border" data-bind="attr: {colspan: tax_rates().length > 1 ? 4 : 3}"/>
|
||||
<td class="hide-border"/>
|
||||
<td data-bind="attr: {colspan: tax_rates().length > 1 ? 3 : 2}"/>
|
||||
<td colspan="2">Subtotal</td>
|
||||
<td style="text-align: right"><span data-bind="text: subtotal"/></td>
|
||||
</tr>
|
||||
@ -352,22 +353,21 @@
|
||||
//$('[name="client_combobox"]').focus();
|
||||
@endif
|
||||
|
||||
$('#clientModal').on('hidden.bs.modal', function () {
|
||||
$('#clientModal').on('shown.bs.modal', function () {
|
||||
$('#name').focus();
|
||||
}).on('hidden.bs.modal', function () {
|
||||
if (model.clientBackup) {
|
||||
console.log("Loading backup");
|
||||
//console.log(model.clientBackup);
|
||||
model.loadClient(model.clientBackup);
|
||||
refreshPDF();
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
$('#clientModal').on('shown.bs.modal', function () {
|
||||
$('#name').focus();
|
||||
})
|
||||
|
||||
$('#taxModal').on('shown.bs.modal', function () {
|
||||
$('#taxModal input:first').focus();
|
||||
}).on('hidden.bs.modal', function () {
|
||||
if (model.taxBackup) {
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
$('#actionDropDown > button:first').click(function() {
|
||||
@ -557,10 +557,13 @@
|
||||
|
||||
|
||||
self.showTaxesForm = function() {
|
||||
self.taxBackup = ko.mapping.toJS(self.tax_rates);
|
||||
|
||||
$('#taxModal').modal('show');
|
||||
}
|
||||
|
||||
self.taxFormComplete = function() {
|
||||
model.taxBackup = false;
|
||||
$('#taxModal').modal('hide');
|
||||
}
|
||||
|
||||
@ -606,8 +609,14 @@
|
||||
}
|
||||
|
||||
self.addItem = function() {
|
||||
self.invoice_items.push(new ItemModel());
|
||||
var itemModel = new ItemModel();
|
||||
self.invoice_items.push(itemModel);
|
||||
applyComboboxListeners();
|
||||
|
||||
itemModel.tax.subscribe(function (data) {
|
||||
console.log('Tax change...');
|
||||
console.log(data)
|
||||
});
|
||||
}
|
||||
|
||||
self.removeTaxRate = function(taxRate) {
|
||||
@ -615,8 +624,8 @@
|
||||
//refreshPDF();
|
||||
}
|
||||
|
||||
self.addTaxRate = function() {
|
||||
self.tax_rates.push(new TaxRateModel());
|
||||
self.addTaxRate = function(data) {
|
||||
self.tax_rates.push(new TaxRateModel(data));
|
||||
applyComboboxListeners();
|
||||
}
|
||||
|
||||
@ -725,19 +734,30 @@
|
||||
|
||||
function TaxRateModel(data) {
|
||||
var self = this;
|
||||
this.rate = ko.observable();
|
||||
this.name = ko.observable('');
|
||||
this.actionsVisible = ko.observable(false);
|
||||
self.public_id = ko.observable('');
|
||||
self.rate = ko.observable();
|
||||
self.name = ko.observable('');
|
||||
self.actionsVisible = ko.observable(false);
|
||||
|
||||
this.hideActions = function() {
|
||||
this.actionsVisible(false);
|
||||
if (data) {
|
||||
ko.mapping.fromJS(data, {}, this);
|
||||
}
|
||||
|
||||
self.displayName = ko.computed(function() {
|
||||
var name = self.name() ? self.name() : false;
|
||||
var rate = self.rate() ? self.rate() : false;
|
||||
return (name && rate) ? (rate + '%' + ' - ' + name) : '';
|
||||
});
|
||||
|
||||
self.hideActions = function() {
|
||||
self.actionsVisible(false);
|
||||
}
|
||||
|
||||
this.showActions = function() {
|
||||
this.actionsVisible(true);
|
||||
self.showActions = function() {
|
||||
self.actionsVisible(true);
|
||||
}
|
||||
|
||||
this.isEmpty = function() {
|
||||
self.isEmpty = function() {
|
||||
return !self.rate() && !self.name();
|
||||
}
|
||||
}
|
||||
@ -748,14 +768,23 @@
|
||||
this.notes = ko.observable('');
|
||||
this.cost = ko.observable();
|
||||
this.qty = ko.observable();
|
||||
this.tax_rate = ko.observable();
|
||||
this.tax_name = ko.observable('');
|
||||
this.tax = ko.observable();
|
||||
this.actionsVisible = ko.observable(false);
|
||||
|
||||
if (data) {
|
||||
ko.mapping.fromJS(data, {}, this);
|
||||
}
|
||||
|
||||
console.log('data: ' + data);
|
||||
for (var i=0; i<model.tax_rates().length; i++) {
|
||||
var taxRate = model.tax_rates()[i];
|
||||
if (data && (data.tax_name == taxRate.name())) {
|
||||
self.tax(taxRate);
|
||||
} else if (!data && !taxRate.tax_name) {
|
||||
self.tax(taxRate);
|
||||
}
|
||||
}
|
||||
|
||||
self.wrapped_notes = ko.computed({
|
||||
read: function() {
|
||||
return this.notes();
|
||||
@ -771,10 +800,10 @@
|
||||
this.rawTotal = ko.computed(function() {
|
||||
var cost = parseFloat(self.cost());
|
||||
var qty = parseFloat(self.qty());
|
||||
var tax = parseFloat(self.tax_rate());
|
||||
var taxRate = self.tax() ? parseFloat(self.tax().rate()) : 0;
|
||||
var value = cost * qty;
|
||||
if (tax > 0) {
|
||||
//value = value * ((100 - this.tax())/100);
|
||||
if (taxRate > 0) {
|
||||
value += value * (taxRate/100);
|
||||
}
|
||||
return value ? value : '';
|
||||
});
|
||||
@ -793,8 +822,12 @@
|
||||
}
|
||||
|
||||
this.isEmpty = function() {
|
||||
return !self.product_key() && !self.notes() && !self.cost() && !self.qty() && !self.tax();
|
||||
return !self.product_key() && !self.notes() && !self.cost() && !self.qty();
|
||||
}
|
||||
|
||||
this.onSelect = function(){
|
||||
console.log("select");
|
||||
}
|
||||
}
|
||||
|
||||
function onItemChange()
|
||||
@ -846,6 +879,9 @@
|
||||
|
||||
|
||||
window.model = new InvoiceModel();
|
||||
@foreach ($taxRates as $taxRate)
|
||||
model.addTaxRate({{ $taxRate }});
|
||||
@endforeach
|
||||
@if ($invoice)
|
||||
var invoice = {{ $invoice }};
|
||||
ko.mapping.fromJS(invoice, model.mapping, model);
|
||||
@ -860,11 +896,10 @@
|
||||
model.invoice_number('{{ $invoiceNumber }}');
|
||||
model.terms(wordWrapText('{{ $account->invoice_terms }}', 250));
|
||||
@endif
|
||||
model.addItem();
|
||||
model.addTaxRate();
|
||||
model.addItem();
|
||||
ko.applyBindings(model);
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
@stop
|
12
public/js/jquery-ui.min.js
vendored
Executable file
12
public/js/jquery-ui.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
@ -132,6 +132,7 @@ function generatePDF(invoice) {
|
||||
var qty = item.qty ? parseFloat(item.qty) + '' : '';
|
||||
var notes = item.notes;
|
||||
var productKey = item.product_key;
|
||||
var tax = item.tax && parseFloat(item.tax.rate) ? parseFloat(item.tax.rate) + '%' : false;
|
||||
|
||||
// show at most one blank line
|
||||
if (shownItem && (!cost || cost == '0.00') && !qty && !notes && !productKey) {
|
||||
@ -144,11 +145,17 @@ function generatePDF(invoice) {
|
||||
productKey = processVariables(productKey);
|
||||
|
||||
var lineTotal = item.cost * item.qty;
|
||||
if (lineTotal) total += lineTotal;
|
||||
if (tax) {
|
||||
lineTotal += lineTotal * parseFloat(item.tax.rate) / 100;
|
||||
}
|
||||
if (lineTotal) {
|
||||
total += lineTotal;
|
||||
}
|
||||
lineTotal = formatNumber(lineTotal);
|
||||
|
||||
var costX = unitCostRight - (doc.getStringUnitWidth(cost) * doc.internal.getFontSize());
|
||||
var qtyX = qtyRight - (doc.getStringUnitWidth(qty) * doc.internal.getFontSize());
|
||||
var taxX = taxRight - (doc.getStringUnitWidth(tax) * doc.internal.getFontSize());
|
||||
var totalX = lineTotalRight - (doc.getStringUnitWidth(lineTotal) * doc.internal.getFontSize());
|
||||
var x = tableTop + (line * rowHeight) + 6;
|
||||
|
||||
@ -158,6 +165,10 @@ function generatePDF(invoice) {
|
||||
doc.text(qtyX, x, qty);
|
||||
doc.text(totalX, x, lineTotal);
|
||||
|
||||
if (tax) {
|
||||
doc.text(taxX, x, tax);
|
||||
}
|
||||
|
||||
line += doc.splitTextToSize(item.notes, 200).length;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user