1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-09 20:52:56 +01:00

Added support for default taxes

This commit is contained in:
Hillel Coren 2015-10-21 14:11:08 +03:00
parent 7150a148af
commit 47f38ad54e
20 changed files with 452 additions and 216 deletions

View File

@ -36,6 +36,7 @@ use App\Models\Gateway;
use App\Models\Timezone;
use App\Models\Industry;
use App\Models\InvoiceDesign;
use App\Models\TaxRate;
use App\Ninja\Repositories\AccountRepository;
use App\Ninja\Mailers\UserMailer;
use App\Ninja\Mailers\ContactMailer;
@ -164,6 +165,8 @@ class AccountController extends BaseController
return self::showTemplates();
} elseif ($section === ACCOUNT_PRODUCTS) {
return self::showProducts();
} elseif ($section === ACCOUNT_TAX_RATES) {
return self::showTaxRates();
} else {
$data = [
'account' => Account::with('users')->findOrFail(Auth::user()->account_id),
@ -238,14 +241,32 @@ class AccountController extends BaseController
private function showProducts()
{
$columns = ['product', 'description', 'unit_cost'];
if (Auth::user()->account->invoice_item_taxes) {
$columns[] = 'tax_rate';
}
$columns[] = 'action';
$data = [
'account' => Auth::user()->account,
'title' => trans('texts.product_library'),
'columns' => Utils::trans($columns),
];
return View::make('accounts.products', $data);
}
private function showTaxRates()
{
$data = [
'account' => Auth::user()->account,
'title' => trans('texts.tax_rates'),
'taxRates' => TaxRate::scope()->get(['id', 'name', 'rate']),
];
return View::make('accounts.tax_rates', $data);
}
private function showInvoiceDesign($section)
{
$account = Auth::user()->account->load('country');
@ -349,6 +370,8 @@ class AccountController extends BaseController
return AccountController::saveEmailTemplates();
} elseif ($section === ACCOUNT_PRODUCTS) {
return AccountController::saveProducts();
} elseif ($section === ACCOUNT_TAX_RATES) {
return AccountController::saveTaxRates();
}
}
@ -398,6 +421,20 @@ class AccountController extends BaseController
return Redirect::to('settings/' . ACCOUNT_TEMPLATES_AND_REMINDERS);
}
private function saveTaxRates()
{
$account = Auth::user()->account;
$account->invoice_taxes = Input::get('invoice_taxes') ? true : false;
$account->invoice_item_taxes = Input::get('invoice_item_taxes') ? true : false;
$account->show_item_taxes = Input::get('show_item_taxes') ? true : false;
$account->default_tax_rate_id = Input::get('default_tax_rate_id');
$account->save();
Session::flash('message', trans('texts.updated_settings'));
return Redirect::to('settings/' . ACCOUNT_TAX_RATES);
}
private function saveProducts()
{
$account = Auth::user()->account;

View File

@ -19,6 +19,11 @@ use App\Ninja\Repositories\AccountRepository;
class AccountGatewayController extends BaseController
{
public function index()
{
return Redirect::to('settings/' . ACCOUNT_PAYMENTS);
}
public function getDatatable()
{
$query = DB::table('account_gateways')

View File

@ -31,7 +31,6 @@ use App\Models\Gateway;
use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\TaxRateRepository;
use App\Events\InvoiceViewed;
class InvoiceController extends BaseController
@ -39,16 +38,14 @@ class InvoiceController extends BaseController
protected $mailer;
protected $invoiceRepo;
protected $clientRepo;
protected $taxRateRepo;
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, TaxRateRepository $taxRateRepo)
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo)
{
parent::__construct();
$this->mailer = $mailer;
$this->invoiceRepo = $invoiceRepo;
$this->clientRepo = $clientRepo;
$this->taxRateRepo = $taxRateRepo;
}
public function index()
@ -393,7 +390,7 @@ class InvoiceController extends BaseController
return [
'account' => Auth::user()->account->load('country'),
'products' => Product::scope()->orderBy('id')->get(array('product_key', 'notes', 'cost', 'qty')),
'products' => Product::scope()->with('default_tax_rate')->orderBy('id')->get(),
'countries' => Cache::get('countries'),
'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(),
'taxRates' => TaxRate::scope()->orderBy('name')->get(),
@ -523,8 +520,6 @@ class InvoiceController extends BaseController
{
$invoice = $input->invoice;
$this->taxRateRepo->save($input->tax_rates);
$clientData = (array) $invoice->client;
$client = $this->clientRepo->save($invoice->client->public_id, $clientData);
@ -532,18 +527,6 @@ class InvoiceController extends BaseController
$invoiceData['client_id'] = $client->id;
$invoice = $this->invoiceRepo->save($publicId, $invoiceData, $entityType);
$account = Auth::user()->account;
if ($account->invoice_taxes != $input->invoice_taxes
|| $account->invoice_item_taxes != $input->invoice_item_taxes
|| $account->invoice_design_id != $input->invoice->invoice_design_id
|| $account->show_item_taxes != $input->show_item_taxes) {
$account->invoice_taxes = $input->invoice_taxes;
$account->invoice_item_taxes = $input->invoice_item_taxes;
$account->invoice_design_id = $input->invoice->invoice_design_id;
$account->show_item_taxes = $input->show_item_taxes;
$account->save();
}
$client->load('contacts');
$sendInvoiceIds = [];

View File

@ -12,51 +12,76 @@ use Session;
use Redirect;
use App\Models\Product;
use App\Models\TaxRate;
class ProductController extends BaseController
{
public function index()
{
return Redirect::to('settings/' . ACCOUNT_PRODUCTS);
}
public function getDatatable()
{
$account = Auth::user()->account;
$query = DB::table('products')
->leftJoin('tax_rates', function($join){
$join->on('tax_rates.id', '=', 'products.default_tax_rate_id')
->whereNull('tax_rates.deleted_at');
})
->where('products.account_id', '=', Auth::user()->account_id)
->where('products.deleted_at', '=', null)
->select('products.public_id', 'products.product_key', 'products.notes', 'products.cost');
->select('products.public_id', 'products.product_key', 'products.notes', 'products.cost', 'tax_rates.name as tax_name', 'tax_rates.rate as tax_rate');
return Datatable::query($query)
->addColumn('product_key', function ($model) { return link_to('products/'.$model->public_id.'/edit', $model->product_key); })
->addColumn('notes', function ($model) { return nl2br(Str::limit($model->notes, 100)); })
->addColumn('cost', function ($model) { return Utils::formatMoney($model->cost); })
->addColumn('dropdown', function ($model) {
return '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="'.URL::to('products/'.$model->public_id).'/edit">'.uctrans('texts.edit_product').'</a></li>
<li class="divider"></li>
<li><a href="'.URL::to('products/'.$model->public_id).'/archive">'.uctrans('texts.archive_product').'</a></li>
</ul>
</div>';
})
->orderColumns(['cost', 'product_key', 'cost'])
->make();
$datatable = Datatable::query($query)
->addColumn('product_key', function ($model) { return link_to('products/'.$model->public_id.'/edit', $model->product_key); })
->addColumn('notes', function ($model) { return nl2br(Str::limit($model->notes, 100)); })
->addColumn('cost', function ($model) { return Utils::formatMoney($model->cost); });
if ($account->invoice_item_taxes) {
$datatable->addColumn('tax_rate', function ($model) { return $model->tax_rate ? ($model->tax_name . ' ' . $model->tax_rate . '%') : ''; });
}
return $datatable->addColumn('dropdown', function ($model) {
return '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="'.URL::to('products/'.$model->public_id).'/edit">'.uctrans('texts.edit_product').'</a></li>
<li class="divider"></li>
<li><a href="'.URL::to('products/'.$model->public_id).'/archive">'.uctrans('texts.archive_product').'</a></li>
</ul>
</div>';
})
->orderColumns(['cost', 'product_key', 'cost'])
->make();
}
public function edit($publicId)
{
$account = Auth::user()->account;
$data = [
'product' => Product::scope($publicId)->firstOrFail(),
'method' => 'PUT',
'url' => 'products/'.$publicId,
'title' => trans('texts.edit_product'),
];
'account' => $account,
'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->get(['id', 'name', 'rate']) : null,
'product' => Product::scope($publicId)->firstOrFail(),
'method' => 'PUT',
'url' => 'products/'.$publicId,
'title' => trans('texts.edit_product'),
];
return View::make('accounts.product', $data);
}
public function create()
{
$account = Auth::user()->account;
$data = [
'account' => $account,
'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->get(['id', 'name', 'rate']) : null,
'product' => null,
'method' => 'POST',
'url' => 'products',
@ -87,6 +112,8 @@ class ProductController extends BaseController
$product->product_key = trim(Input::get('product_key'));
$product->notes = trim(Input::get('notes'));
$product->cost = trim(Input::get('cost'));
$product->default_tax_rate_id = Input::get('default_tax_rate_id');
$product->save();
$message = $productPublicId ? trans('texts.updated_product') : trans('texts.created_product');

View File

@ -0,0 +1,110 @@
<?php namespace App\Http\Controllers;
use Auth;
use Str;
use DB;
use Datatable;
use Utils;
use URL;
use View;
use Input;
use Session;
use Redirect;
use App\Models\TaxRate;
class TaxRateController extends BaseController
{
public function index()
{
return Redirect::to('settings/' . ACCOUNT_TAX_RATES);
}
public function getDatatable()
{
$query = DB::table('tax_rates')
->where('tax_rates.account_id', '=', Auth::user()->account_id)
->where('tax_rates.deleted_at', '=', null)
->select('tax_rates.public_id', 'tax_rates.name', 'tax_rates.rate');
return Datatable::query($query)
->addColumn('name', function ($model) { return link_to('tax_rates/'.$model->public_id.'/edit', $model->name); })
->addColumn('rate', function ($model) { return $model->rate . '%'; })
->addColumn('dropdown', function ($model) {
return '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="'.URL::to('tax_rates/'.$model->public_id).'/edit">'.uctrans('texts.edit_tax_rate').'</a></li>
<li class="divider"></li>
<li><a href="'.URL::to('tax_rates/'.$model->public_id).'/archive">'.uctrans('texts.archive_tax_rate').'</a></li>
</ul>
</div>';
})
->orderColumns(['name', 'rate'])
->make();
}
public function edit($publicId)
{
$data = [
'taxRate' => TaxRate::scope($publicId)->firstOrFail(),
'method' => 'PUT',
'url' => 'tax_rates/'.$publicId,
'title' => trans('texts.edit_tax_rate'),
];
return View::make('accounts.tax_rate', $data);
}
public function create()
{
$data = [
'taxRate' => null,
'method' => 'POST',
'url' => 'tax_rates',
'title' => trans('texts.create_tax_rate'),
];
return View::make('accounts.tax_rate', $data);
}
public function store()
{
return $this->save();
}
public function update($publicId)
{
return $this->save($publicId);
}
private function save($publicId = false)
{
if ($publicId) {
$taxRate = TaxRate::scope($publicId)->firstOrFail();
} else {
$taxRate = TaxRate::createNew();
}
$taxRate->name = trim(Input::get('name'));
$taxRate->rate = Utils::parseFloat(Input::get('rate'));
$taxRate->save();
$message = $publicId ? trans('texts.updated_tax_rate') : trans('texts.created_tax_rate');
Session::flash('message', $message);
return Redirect::to('settings/' . ACCOUNT_TAX_RATES);
}
public function archive($publicId)
{
$tax_rate = TaxRate::scope($publicId)->firstOrFail();
$tax_rate->delete();
Session::flash('message', trans('texts.archived_tax_rate'));
return Redirect::to('settings/' . ACCOUNT_TAX_RATES);
}
}

View File

@ -25,6 +25,11 @@ use App\Ninja\Repositories\AccountRepository;
class TokenController extends BaseController
{
public function index()
{
return Redirect::to('settings/' . ACCOUNT_API_TOKENS);
}
public function getDatatable()
{
$query = DB::table('account_tokens')

View File

@ -35,6 +35,11 @@ class UserController extends BaseController
$this->userMailer = $userMailer;
}
public function index()
{
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
}
public function getDatatable()
{
$query = DB::table('users')

View File

@ -114,6 +114,10 @@ Route::group(['middleware' => 'auth'], function() {
Route::resource('products', 'ProductController');
Route::get('products/{product_id}/archive', 'ProductController@archive');
Route::get('api/tax_rates', array('as'=>'api.tax_rates', 'uses'=>'TaxRateController@getDatatable'));
Route::resource('tax_rates', 'TaxRateController');
Route::get('tax_rates/{tax_rates_id}/archive', 'TaxRateController@archive');
Route::get('company/{section}/{subSection?}', 'AccountController@redirectLegacy');
Route::get('settings/data_visualizations', 'ReportController@d3');
Route::get('settings/charts_and_reports', 'ReportController@showReports');
@ -258,6 +262,7 @@ if (!defined('CONTACT_EMAIL')) {
define('ACCOUNT_PAYMENTS', 'online_payments');
define('ACCOUNT_MAP', 'import_map');
define('ACCOUNT_EXPORT', 'export');
define('ACCOUNT_TAX_RATES', 'tax_rates');
define('ACCOUNT_PRODUCTS', 'products');
define('ACCOUNT_ADVANCED_SETTINGS', 'advanced_settings');
define('ACCOUNT_INVOICE_SETTINGS', 'invoice_settings');

View File

@ -20,6 +20,7 @@ class Account extends Eloquent
ACCOUNT_USER_DETAILS,
ACCOUNT_LOCALIZATION,
ACCOUNT_PAYMENTS,
ACCOUNT_TAX_RATES,
ACCOUNT_PRODUCTS,
ACCOUNT_NOTIFICATIONS,
ACCOUNT_IMPORT_EXPORT,
@ -106,6 +107,11 @@ class Account extends Eloquent
return $this->belongsTo('App\Models\Industry');
}
public function default_tax_rate()
{
return $this->belongsTo('App\Models\TaxRate');
}
public function isGatewayConfigured($gatewayId = 0)
{
$this->load('account_gateways');

View File

@ -11,4 +11,9 @@ class Product extends EntityModel
{
return Product::scope()->where('product_key', '=', $key)->first();
}
public function default_tax_rate()
{
return $this->belongsTo('App\Models\TaxRate');
}
}

View File

@ -5,6 +5,7 @@ use Utils;
class TaxRateRepository
{
/*
public function save($taxRates)
{
$taxRateIds = [];
@ -39,4 +40,5 @@ class TaxRateRepository
}
}
}
*/
}

View File

@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
class AddDefaultTaxRates extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function ($table) {
$table->unsignedInteger('default_tax_rate_id')->nullable();
$table->smallInteger('recurring_hour')->default(DEFAULT_SEND_RECURRING_HOUR);
});
Schema::table('products', function ($table) {
$table->unsignedInteger('default_tax_rate_id')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function ($table) {
$table->dropColumn('default_tax_rate_id');
$table->dropColumn('recurring_hour');
});
Schema::table('products', function ($table) {
$table->dropColumn('default_tax_rate_id');
});
}
}

View File

@ -96,6 +96,7 @@ class PaymentLibrariesSeeder extends Seeder
['name' => 'Hong Kong Dollar', 'code' => 'HKD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Indonesian Rupiah', 'code' => 'IDR', 'symbol' => 'Rp', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Mexican Peso', 'code' => 'MXN', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Egyptian Pound', 'code' => 'EGP', 'symbol' => '£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
];
foreach ($currencies as $currency) {

View File

@ -337,7 +337,7 @@ return array(
'fill_products_help' => 'Selecting a product will automatically <b>fill in the description and cost</b>',
'update_products' => 'Auto-update products',
'update_products_help' => 'Updating an invoice will automatically <b>update the product library</b>',
'create_product' => 'Create Product',
'create_product' => 'Add Product',
'edit_product' => 'Edit Product',
'archive_product' => 'Archive Product',
'updated_product' => 'Successfully updated product',
@ -831,7 +831,17 @@ return array(
'referral_code_help' => 'Earn money by sharing our app online',
'enable_with_stripe' => 'Enable | Requires Stripe',
'tax_settings' => 'Tax Settings',
'create_tax_rate' => 'Add Tax Rate',
'updated_tax_rate' => 'Successfully updated tax rate',
'created_tax_rate' => 'Successfully created tax rate',
'edit_tax_rate' => 'Edit tax rate',
'archive_tax_rate' => 'Archive tax rate',
'archived_tax_rate' => 'Successfully archived the tax rate',
'default_tax_rate_id' => 'Default Tax Rate',
'tax_rate' => 'Tax Rate',
);

View File

@ -337,7 +337,7 @@ return array(
'fill_products_help' => 'Selecting a product will automatically <b>fill in the description and cost</b>',
'update_products' => 'Auto-update products',
'update_products_help' => 'Updating an invoice will automatically <b>update the product library</b>',
'create_product' => 'Create Product',
'create_product' => 'Add Product',
'edit_product' => 'Edit Product',
'archive_product' => 'Archive Product',
'updated_product' => 'Successfully updated product',

View File

@ -25,6 +25,13 @@
{!! Former::textarea('notes') !!}
{!! Former::text('cost') !!}
@if ($account->invoice_item_taxes)
{!! Former::select('default_tax_rate_id')
->addOption('', '')
->label(trans('texts.tax_rate'))
->fromQuery($taxRates, function($model) { return $model->name . ' ' . $model->rate . '%'; }, 'id') !!}
@endif
</div>
</div>

View File

@ -19,7 +19,7 @@
{!! Former::checkbox('fill_products')->text(trans('texts.fill_products_help')) !!}
{!! Former::checkbox('update_products')->text(trans('texts.update_products_help')) !!}
&nbsp;
{!! Former::actions( Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) ) !!}
{!! Former::actions( Button::success(trans('texts.save'))->submit()->appendIcon(Icon::create('floppy-disk')) ) !!}
{!! Former::close() !!}
</div>
</div>
@ -30,16 +30,12 @@
->appendIcon(Icon::create('plus-sign')) !!}
{!! Datatable::table()
->addColumn(
trans('texts.product'),
trans('texts.description'),
trans('texts.unit_cost'),
trans('texts.action'))
->addColumn($columns)
->setUrl(url('api/products/'))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
->setOptions('bAutoWidth', false)
->setOptions('aoColumns', [[ "sWidth"=> "20%" ], [ "sWidth"=> "45%" ], ["sWidth"=> "20%"], ["sWidth"=> "15%" ]])
//->setOptions('aoColumns', [[ "sWidth"=> "15%" ], [ "sWidth"=> "35%" ]])
->setOptions('aoColumnDefs', [['bSortable'=>false, 'aTargets'=>[3]]])
->render('datatable') !!}

View File

@ -0,0 +1,47 @@
@extends('header')
@section('content')
@parent
@include('accounts.nav', ['selected' => ACCOUNT_TAX_RATES])
{!! Former::open($url)->method($method)
->rules([
'name' => 'required',
'rate' => 'required'
])
->addClass('warn-on-exit') !!}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! $title !!}</h3>
</div>
<div class="panel-body form-padding-right">
@if ($taxRate)
{{ Former::populate($taxRate) }}
@endif
{!! Former::text('name')->label('texts.name') !!}
{!! Former::text('rate')->label('texts.rate')->append('%') !!}
</div>
</div>
{!! Former::actions(
Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/settings/tax_rates'))->appendIcon(Icon::create('remove-circle')),
Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk'))
) !!}
{!! Former::close() !!}
<script type="text/javascript">
$(function() {
$('#name').focus();
});
</script>
@stop

View File

@ -0,0 +1,79 @@
@extends('header')
@section('content')
@parent
@include('accounts.nav', ['selected' => ACCOUNT_TAX_RATES])
{!! Former::open()->addClass('warn-on-exit') !!}
{{ Former::populate($account) }}
{{ Former::populateField('invoice_taxes', intval($account->invoice_taxes)) }}
{{ Former::populateField('invoice_item_taxes', intval($account->invoice_item_taxes)) }}
{{ Former::populateField('show_item_taxes', intval($account->show_item_taxes)) }}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.tax_settings') !!}</h3>
</div>
<div class="panel-body">
{!! Former::checkbox('invoice_taxes')
->text(trans('texts.enable_invoice_tax'))
->label('&nbsp;') !!}
{!! Former::checkbox('invoice_item_taxes')
->text(trans('texts.enable_line_item_tax'))
->label('&nbsp;') !!}
{!! Former::checkbox('show_item_taxes')
->text(trans('texts.show_line_item_tax'))
->label('&nbsp;') !!}
&nbsp;
{!! Former::select('default_tax_rate_id')
->style('max-width: 250px')
->addOption('', '')
->fromQuery($taxRates, function($model) { return $model->name . ': ' . $model->rate . '%'; }, 'id') !!}
&nbsp;
{!! Former::actions( Button::success(trans('texts.save'))->submit()->appendIcon(Icon::create('floppy-disk')) ) !!}
{!! Former::close() !!}
</div>
</div>
{!! Button::primary(trans('texts.create_tax_rate'))
->asLinkTo(URL::to('/tax_rates/create'))
->withAttributes(['class' => 'pull-right'])
->appendIcon(Icon::create('plus-sign')) !!}
{!! Datatable::table()
->addColumn(
trans('texts.name'),
trans('texts.rate'),
trans('texts.action'))
->setUrl(url('api/tax_rates/'))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
->setOptions('bAutoWidth', false)
->setOptions('aoColumns', [[ "sWidth"=> "40%" ], [ "sWidth"=> "40%" ], ["sWidth"=> "20%"]])
->setOptions('aoColumnDefs', [['bSortable'=>false, 'aTargets'=>[2]]])
->render('datatable') !!}
<script>
window.onDatatableReady = function() {
$('tbody tr').mouseover(function() {
$(this).closest('tr').find('.tr-action').css('visibility','visible');
}).mouseout(function() {
$dropdown = $(this).closest('tr').find('.tr-action');
if (!$dropdown.hasClass('open')) {
$dropdown.css('visibility','hidden');
}
});
}
</script>
@stop

View File

@ -147,19 +147,9 @@
{!! Former::text('custom_text_value2')->label($account->custom_invoice_text_label2)->data_bind("value: custom_text_value2, valueUpdate: 'afterkeydown'") !!}
@endif
<div class="form-group" style="margin-bottom: 8px">
<label for="taxes" class="control-label col-lg-4 col-sm-4">{{ trans('texts.taxes') }}</label>
<div class="col-lg-8 col-sm-8" style="padding-top: 10px">
<a href="#" data-bind="click: $root.showTaxesForm"><i class="glyphicon glyphicon-list-alt"></i> {{ trans('texts.manage_rates') }}</a>
</div>
</div>
</div>
</div>
<p>&nbsp;</p>
<div class="table-responsive">
<table class="table invoice-table">
<thead>
@ -251,7 +241,7 @@
</div>
</td>
<td class="hide-border" style="display:none" data-bind="visible: $root.invoice_item_taxes.show"/>
<td class="hide-border" style="display:none" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="{{ $account->hide_quantity ? 1 : 2 }}">{{ trans('texts.subtotal') }}</td>
<td style="text-align: right"><span data-bind="text: totals.subtotal"/></td>
</tr>
@ -501,61 +491,6 @@
</div>
</div>
<div class="modal fade" id="taxModal" tabindex="-1" role="dialog" aria-labelledby="taxModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="taxModalLabel">{{ trans('texts.tax_rates') }}</h4>
</div>
<div style="background-color: #fff" onkeypress="taxModalEnterClick(event)">
<table class="table invoice-table sides-padded" style="margin-bottom: 0px !important">
<thead>
<tr>
<th class="hide-border"></th>
<th class="hide-border">{{ trans('texts.name') }}</th>
<th class="hide-border">{{ trans('texts.rate') }}</th>
<th class="hide-border"></th>
</tr>
</thead>
<tbody data-bind="foreach: $root.tax_rates.filtered">
<tr data-bind="event: { mouseover: showActions, mouseout: hideActions }">
<td style="width:30px" class="hide-border"></td>
<td style="width:60px">
<input onkeyup="onTaxRateChange()" data-bind="value: name, valueUpdate: 'afterkeydown'" class="form-control" onchange="refreshPDF(true)"//>
</td>
<td style="width:60px">
<input onkeyup="onTaxRateChange()" data-bind="value: prettyRate, valueUpdate: 'afterkeydown'" style="text-align: right" class="form-control" onchange="refreshPDF(true)"//>
</td>
<td style="width:30px; cursor:pointer" class="hide-border td-icon">
&nbsp;<i style="width:12px;" data-bind="click: $root.removeTaxRate, visible: actionsVisible() &amp;&amp; !isEmpty()" class="fa fa-minus-circle redlink" title="Remove item"/>
</td>
</tr>
</tbody>
</table>
&nbsp;
{!! Former::checkbox('invoice_taxes')->text(trans('texts.enable_invoice_tax'))
->label(trans('texts.settings'))->data_bind('checked: $root.invoice_taxes, enable: $root.tax_rates().length > 1') !!}
{!! Former::checkbox('invoice_item_taxes')->text(trans('texts.enable_line_item_tax'))
->label('&nbsp;')->data_bind('checked: $root.invoice_item_taxes, enable: $root.tax_rates().length > 1') !!}
{!! Former::checkbox('show_item_taxes')->text(trans('texts.show_line_item_tax'))
->label('&nbsp;')->data_bind('checked: $root.show_item_taxes, enable: $root.tax_rates().length > 1') !!}
<br/>
</div>
<div class="modal-footer" style="margin-top: 0px">
<!-- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> -->
<button type="button" class="btn btn-primary" data-bind="click: $root.taxFormComplete">{{ trans('texts.done') }}</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="recurringModal" tabindex="-1" role="dialog" aria-labelledby="recurringModalLabel" aria-hidden="true">
<div class="modal-dialog" style="min-width:150px">
<div class="modal-content">
@ -606,12 +541,6 @@
@if ($client && !$invoice)
$('input[name=client]').val({{ $client->public_id }});
@endif
/*
if (clients.length == 0) {
$('.client_select input.form-control').prop('disabled', true);
}
*/
var $input = $('select#client');
$input.combobox().on('change', function(e) {
@ -669,14 +598,6 @@
}
})
$('#taxModal').on('shown.bs.modal', function () {
$('#taxModal input:first').focus();
}).on('hidden.bs.modal', function () {
// if the user changed the tax rates we need to trigger the
// change event on the selects for the model to get updated
$('table.invoice-table select').trigger('change');
})
$('#relatedActions > button:first').click(function() {
onPaymentClick();
});
@ -725,6 +646,11 @@
if (!model.qty()) {
model.qty(1);
}
@if ($account->invoice_item_taxes)
if (product.default_tax_rate) {
model.tax(self.model.getTaxRateById(product.default_tax_rate.public_id));
}
@endif
break;
}
}
@ -739,7 +665,6 @@
invoice.is_pro = {{ Auth::user()->isPro() ? 'true' : 'false' }};
invoice.is_quote = {{ $entityType == ENTITY_QUOTE ? 'true' : 'false' }};
invoice.contact = _.findWhere(invoice.client.contacts, {send_invoice: true});
invoice.account.show_item_taxes = $('#show_item_taxes').is(':checked');
if (invoice.is_recurring) {
invoice.invoice_number = '0000';
@ -959,14 +884,6 @@
}
}
function taxModalEnterClick(event) {
if (event.keyCode === 13){
event.preventDefault();
model.taxFormComplete();
return false;
}
}
function ViewModel(data) {
var self = this;
self.showMore = ko.observable(false);
@ -974,6 +891,8 @@
//self.invoice = data ? false : new InvoiceModel();
self.invoice = ko.observable(data ? false : new InvoiceModel());
self.tax_rates = ko.observableArray();
self.tax_rates.push(new TaxRateModel()); // add blank row
self.loadClient = function(client) {
ko.mapping.fromJS(client, model.invoice().client().mapping, model.invoice().client);
@ -997,7 +916,7 @@
self.invoice().due_date(dueDate);
// We're using the datepicker to handle the date formatting
self.invoice().due_date($('#due_date').val());
}
}
@endif
}
@ -1023,7 +942,7 @@
}
self.invoice_taxes.show = ko.computed(function() {
if (self.tax_rates().length > 2 && self.invoice_taxes()) {
if (self.invoice_taxes()) {
return true;
}
if (self.invoice().tax_rate() > 0) {
@ -1033,7 +952,7 @@
});
self.invoice_item_taxes.show = ko.computed(function() {
if (self.tax_rates().length > 2 && self.invoice_item_taxes()) {
if (self.invoice_item_taxes()) {
return true;
}
for (var i=0; i<self.invoice().invoice_items().length; i++) {
@ -1045,50 +964,29 @@
return false;
});
self.tax_rates.filtered = ko.computed(function() {
var i = 0;
for (i; i<self.tax_rates().length; i++) {
var taxRate = self.tax_rates()[i];
if (taxRate.isEmpty()) {
break;
}
}
var rates = self.tax_rates().concat();
rates.splice(i, 1);
return rates;
});
self.removeTaxRate = function(taxRate) {
self.tax_rates.remove(taxRate);
//refreshPDF();
}
self.addTaxRate = function(data) {
var itemModel = new TaxRateModel(data);
self.tax_rates.push(itemModel);
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;
}
}
}
*/
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);
@ -1097,19 +995,7 @@
taxRate.is_deleted(true);
self.tax_rates.push(taxRate);
}
return taxRate;
}
self.showTaxesForm = function() {
self.taxBackup = ko.mapping.toJS(self.tax_rates);
$('#taxModal').modal('show');
}
self.taxFormComplete = function() {
model.taxBackup = false;
$('#taxModal').modal('hide');
refreshPDF();
return taxRate;
}
self.showClientForm = function() {
@ -1649,8 +1535,8 @@
self.actionsVisible = ko.observable(false);
if (data) {
ko.mapping.fromJS(data, {}, this);
}
ko.mapping.fromJS(data, {}, this);
}
this.prettyRate = ko.computed({
read: function () {
@ -1803,22 +1689,6 @@
}
}
function onTaxRateChange()
{
var emptyCount = 0;
for(var i=0; i<model.tax_rates().length; i++) {
var taxRate = model.tax_rates()[i];
if (taxRate.isEmpty()) {
emptyCount++;
}
}
for(var i=0; i<2-emptyCount; i++) {
model.addTaxRate();
}
}
function onPartialChange(silent)
{
var val = NINJA.parseFloat($('#partial').val());
@ -1874,7 +1744,6 @@
window.model = new ViewModel({!! $data !!});
@else
window.model = new ViewModel();
model.addTaxRate();
@foreach ($taxRates as $taxRate)
model.addTaxRate({!! $taxRate !!});
@endforeach
@ -1890,15 +1759,13 @@
}
}
model.invoice().addItem();
//model.addTaxRate();
@else
// TODO: Add the first tax rate for new invoices by adding a new db field to the tax codes types to set the default
//if(model.invoice_taxes() && model.tax_rates().length > 2) {
// var tax = model.tax_rates()[1];
// model.invoice().tax(tax);
//}
model.invoice().custom_taxes1({{ $account->custom_invoice_taxes1 ? 'true' : 'false' }});
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 : '' }}));
@endif
@endif
@if (isset($tasks) && $tasks)
@ -1925,7 +1792,6 @@
item.tax(model.getTaxRate(item.tax_name(), item.tax_rate()));
item.cost(NINJA.parseFloat(item.cost()) != 0 ? roundToTwo(item.cost(), true) : '');
}
onTaxRateChange();
// display blank instead of '0'
if (!NINJA.parseFloat(model.invoice().discount())) model.invoice().discount('');