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\Timezone;
use App\Models\Industry; use App\Models\Industry;
use App\Models\InvoiceDesign; use App\Models\InvoiceDesign;
use App\Models\TaxRate;
use App\Ninja\Repositories\AccountRepository; use App\Ninja\Repositories\AccountRepository;
use App\Ninja\Mailers\UserMailer; use App\Ninja\Mailers\UserMailer;
use App\Ninja\Mailers\ContactMailer; use App\Ninja\Mailers\ContactMailer;
@ -164,6 +165,8 @@ class AccountController extends BaseController
return self::showTemplates(); return self::showTemplates();
} elseif ($section === ACCOUNT_PRODUCTS) { } elseif ($section === ACCOUNT_PRODUCTS) {
return self::showProducts(); return self::showProducts();
} elseif ($section === ACCOUNT_TAX_RATES) {
return self::showTaxRates();
} else { } else {
$data = [ $data = [
'account' => Account::with('users')->findOrFail(Auth::user()->account_id), 'account' => Account::with('users')->findOrFail(Auth::user()->account_id),
@ -238,14 +241,32 @@ class AccountController extends BaseController
private function showProducts() private function showProducts()
{ {
$columns = ['product', 'description', 'unit_cost'];
if (Auth::user()->account->invoice_item_taxes) {
$columns[] = 'tax_rate';
}
$columns[] = 'action';
$data = [ $data = [
'account' => Auth::user()->account, 'account' => Auth::user()->account,
'title' => trans('texts.product_library'), 'title' => trans('texts.product_library'),
'columns' => Utils::trans($columns),
]; ];
return View::make('accounts.products', $data); 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) private function showInvoiceDesign($section)
{ {
$account = Auth::user()->account->load('country'); $account = Auth::user()->account->load('country');
@ -349,6 +370,8 @@ class AccountController extends BaseController
return AccountController::saveEmailTemplates(); return AccountController::saveEmailTemplates();
} elseif ($section === ACCOUNT_PRODUCTS) { } elseif ($section === ACCOUNT_PRODUCTS) {
return AccountController::saveProducts(); 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); 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() private function saveProducts()
{ {
$account = Auth::user()->account; $account = Auth::user()->account;

View File

@ -19,6 +19,11 @@ use App\Ninja\Repositories\AccountRepository;
class AccountGatewayController extends BaseController class AccountGatewayController extends BaseController
{ {
public function index()
{
return Redirect::to('settings/' . ACCOUNT_PAYMENTS);
}
public function getDatatable() public function getDatatable()
{ {
$query = DB::table('account_gateways') $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\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\TaxRateRepository;
use App\Events\InvoiceViewed; use App\Events\InvoiceViewed;
class InvoiceController extends BaseController class InvoiceController extends BaseController
@ -39,16 +38,14 @@ class InvoiceController extends BaseController
protected $mailer; protected $mailer;
protected $invoiceRepo; protected $invoiceRepo;
protected $clientRepo; 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(); parent::__construct();
$this->mailer = $mailer; $this->mailer = $mailer;
$this->invoiceRepo = $invoiceRepo; $this->invoiceRepo = $invoiceRepo;
$this->clientRepo = $clientRepo; $this->clientRepo = $clientRepo;
$this->taxRateRepo = $taxRateRepo;
} }
public function index() public function index()
@ -393,7 +390,7 @@ class InvoiceController extends BaseController
return [ return [
'account' => Auth::user()->account->load('country'), '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'), 'countries' => Cache::get('countries'),
'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(), 'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(),
'taxRates' => TaxRate::scope()->orderBy('name')->get(), 'taxRates' => TaxRate::scope()->orderBy('name')->get(),
@ -523,8 +520,6 @@ class InvoiceController extends BaseController
{ {
$invoice = $input->invoice; $invoice = $input->invoice;
$this->taxRateRepo->save($input->tax_rates);
$clientData = (array) $invoice->client; $clientData = (array) $invoice->client;
$client = $this->clientRepo->save($invoice->client->public_id, $clientData); $client = $this->clientRepo->save($invoice->client->public_id, $clientData);
@ -532,18 +527,6 @@ class InvoiceController extends BaseController
$invoiceData['client_id'] = $client->id; $invoiceData['client_id'] = $client->id;
$invoice = $this->invoiceRepo->save($publicId, $invoiceData, $entityType); $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'); $client->load('contacts');
$sendInvoiceIds = []; $sendInvoiceIds = [];

View File

@ -12,21 +12,38 @@ use Session;
use Redirect; use Redirect;
use App\Models\Product; use App\Models\Product;
use App\Models\TaxRate;
class ProductController extends BaseController class ProductController extends BaseController
{ {
public function index()
{
return Redirect::to('settings/' . ACCOUNT_PRODUCTS);
}
public function getDatatable() public function getDatatable()
{ {
$account = Auth::user()->account;
$query = DB::table('products') $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.account_id', '=', Auth::user()->account_id)
->where('products.deleted_at', '=', null) ->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) $datatable = Datatable::query($query)
->addColumn('product_key', function ($model) { return link_to('products/'.$model->public_id.'/edit', $model->product_key); }) ->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('notes', function ($model) { return nl2br(Str::limit($model->notes, 100)); })
->addColumn('cost', function ($model) { return Utils::formatMoney($model->cost); }) ->addColumn('cost', function ($model) { return Utils::formatMoney($model->cost); });
->addColumn('dropdown', function ($model) {
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;"> return '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown"> <button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span> '.trans('texts.select').' <span class="caret"></span>
@ -44,7 +61,11 @@ class ProductController extends BaseController
public function edit($publicId) public function edit($publicId)
{ {
$account = Auth::user()->account;
$data = [ $data = [
'account' => $account,
'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->get(['id', 'name', 'rate']) : null,
'product' => Product::scope($publicId)->firstOrFail(), 'product' => Product::scope($publicId)->firstOrFail(),
'method' => 'PUT', 'method' => 'PUT',
'url' => 'products/'.$publicId, 'url' => 'products/'.$publicId,
@ -56,7 +77,11 @@ class ProductController extends BaseController
public function create() public function create()
{ {
$account = Auth::user()->account;
$data = [ $data = [
'account' => $account,
'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->get(['id', 'name', 'rate']) : null,
'product' => null, 'product' => null,
'method' => 'POST', 'method' => 'POST',
'url' => 'products', 'url' => 'products',
@ -87,6 +112,8 @@ class ProductController extends BaseController
$product->product_key = trim(Input::get('product_key')); $product->product_key = trim(Input::get('product_key'));
$product->notes = trim(Input::get('notes')); $product->notes = trim(Input::get('notes'));
$product->cost = trim(Input::get('cost')); $product->cost = trim(Input::get('cost'));
$product->default_tax_rate_id = Input::get('default_tax_rate_id');
$product->save(); $product->save();
$message = $productPublicId ? trans('texts.updated_product') : trans('texts.created_product'); $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 class TokenController extends BaseController
{ {
public function index()
{
return Redirect::to('settings/' . ACCOUNT_API_TOKENS);
}
public function getDatatable() public function getDatatable()
{ {
$query = DB::table('account_tokens') $query = DB::table('account_tokens')

View File

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

View File

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

View File

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

View File

@ -11,4 +11,9 @@ class Product extends EntityModel
{ {
return Product::scope()->where('product_key', '=', $key)->first(); 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 class TaxRateRepository
{ {
/*
public function save($taxRates) public function save($taxRates)
{ {
$taxRateIds = []; $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' => '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' => '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' => '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) { 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>', 'fill_products_help' => 'Selecting a product will automatically <b>fill in the description and cost</b>',
'update_products' => 'Auto-update products', 'update_products' => 'Auto-update products',
'update_products_help' => 'Updating an invoice will automatically <b>update the product library</b>', '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', 'edit_product' => 'Edit Product',
'archive_product' => 'Archive Product', 'archive_product' => 'Archive Product',
'updated_product' => 'Successfully updated product', 'updated_product' => 'Successfully updated product',
@ -831,7 +831,17 @@ return array(
'referral_code_help' => 'Earn money by sharing our app online', 'referral_code_help' => 'Earn money by sharing our app online',
'enable_with_stripe' => 'Enable | Requires Stripe', '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>', 'fill_products_help' => 'Selecting a product will automatically <b>fill in the description and cost</b>',
'update_products' => 'Auto-update products', 'update_products' => 'Auto-update products',
'update_products_help' => 'Updating an invoice will automatically <b>update the product library</b>', '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', 'edit_product' => 'Edit Product',
'archive_product' => 'Archive Product', 'archive_product' => 'Archive Product',
'updated_product' => 'Successfully updated product', 'updated_product' => 'Successfully updated product',

View File

@ -25,6 +25,13 @@
{!! Former::textarea('notes') !!} {!! Former::textarea('notes') !!}
{!! Former::text('cost') !!} {!! 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>
</div> </div>

View File

@ -19,7 +19,7 @@
{!! Former::checkbox('fill_products')->text(trans('texts.fill_products_help')) !!} {!! Former::checkbox('fill_products')->text(trans('texts.fill_products_help')) !!}
{!! Former::checkbox('update_products')->text(trans('texts.update_products_help')) !!} {!! Former::checkbox('update_products')->text(trans('texts.update_products_help')) !!}
&nbsp; &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() !!} {!! Former::close() !!}
</div> </div>
</div> </div>
@ -30,16 +30,12 @@
->appendIcon(Icon::create('plus-sign')) !!} ->appendIcon(Icon::create('plus-sign')) !!}
{!! Datatable::table() {!! Datatable::table()
->addColumn( ->addColumn($columns)
trans('texts.product'),
trans('texts.description'),
trans('texts.unit_cost'),
trans('texts.action'))
->setUrl(url('api/products/')) ->setUrl(url('api/products/'))
->setOptions('sPaginationType', 'bootstrap') ->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false) ->setOptions('bFilter', false)
->setOptions('bAutoWidth', 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]]]) ->setOptions('aoColumnDefs', [['bSortable'=>false, 'aTargets'=>[3]]])
->render('datatable') !!} ->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'") !!} {!! Former::text('custom_text_value2')->label($account->custom_invoice_text_label2)->data_bind("value: custom_text_value2, valueUpdate: 'afterkeydown'") !!}
@endif @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>
</div>
</div>
<p>&nbsp;</p>
<div class="table-responsive"> <div class="table-responsive">
<table class="table invoice-table"> <table class="table invoice-table">
<thead> <thead>
@ -501,61 +491,6 @@
</div> </div>
</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 fade" id="recurringModal" tabindex="-1" role="dialog" aria-labelledby="recurringModalLabel" aria-hidden="true">
<div class="modal-dialog" style="min-width:150px"> <div class="modal-dialog" style="min-width:150px">
<div class="modal-content"> <div class="modal-content">
@ -607,12 +542,6 @@
$('input[name=client]').val({{ $client->public_id }}); $('input[name=client]').val({{ $client->public_id }});
@endif @endif
/*
if (clients.length == 0) {
$('.client_select input.form-control').prop('disabled', true);
}
*/
var $input = $('select#client'); var $input = $('select#client');
$input.combobox().on('change', function(e) { $input.combobox().on('change', function(e) {
var clientId = parseInt($('input[name=client]').val(), 10); var clientId = parseInt($('input[name=client]').val(), 10);
@ -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() { $('#relatedActions > button:first').click(function() {
onPaymentClick(); onPaymentClick();
}); });
@ -725,6 +646,11 @@
if (!model.qty()) { if (!model.qty()) {
model.qty(1); 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; break;
} }
} }
@ -739,7 +665,6 @@
invoice.is_pro = {{ Auth::user()->isPro() ? 'true' : 'false' }}; invoice.is_pro = {{ Auth::user()->isPro() ? 'true' : 'false' }};
invoice.is_quote = {{ $entityType == ENTITY_QUOTE ? 'true' : 'false' }}; invoice.is_quote = {{ $entityType == ENTITY_QUOTE ? 'true' : 'false' }};
invoice.contact = _.findWhere(invoice.client.contacts, {send_invoice: true}); invoice.contact = _.findWhere(invoice.client.contacts, {send_invoice: true});
invoice.account.show_item_taxes = $('#show_item_taxes').is(':checked');
if (invoice.is_recurring) { if (invoice.is_recurring) {
invoice.invoice_number = '0000'; invoice.invoice_number = '0000';
@ -959,14 +884,6 @@
} }
} }
function taxModalEnterClick(event) {
if (event.keyCode === 13){
event.preventDefault();
model.taxFormComplete();
return false;
}
}
function ViewModel(data) { function ViewModel(data) {
var self = this; var self = this;
self.showMore = ko.observable(false); self.showMore = ko.observable(false);
@ -974,6 +891,8 @@
//self.invoice = data ? false : new InvoiceModel(); //self.invoice = data ? false : new InvoiceModel();
self.invoice = ko.observable(data ? false : new InvoiceModel()); self.invoice = ko.observable(data ? false : new InvoiceModel());
self.tax_rates = ko.observableArray(); self.tax_rates = ko.observableArray();
self.tax_rates.push(new TaxRateModel()); // add blank row
self.loadClient = function(client) { self.loadClient = function(client) {
ko.mapping.fromJS(client, model.invoice().client().mapping, model.invoice().client); ko.mapping.fromJS(client, model.invoice().client().mapping, model.invoice().client);
@ -1023,7 +942,7 @@
} }
self.invoice_taxes.show = ko.computed(function() { self.invoice_taxes.show = ko.computed(function() {
if (self.tax_rates().length > 2 && self.invoice_taxes()) { if (self.invoice_taxes()) {
return true; return true;
} }
if (self.invoice().tax_rate() > 0) { if (self.invoice().tax_rate() > 0) {
@ -1033,7 +952,7 @@
}); });
self.invoice_item_taxes.show = ko.computed(function() { 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; return true;
} }
for (var i=0; i<self.invoice().invoice_items().length; i++) { for (var i=0; i<self.invoice().invoice_items().length; i++) {
@ -1045,42 +964,21 @@
return false; 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) { self.addTaxRate = function(data) {
var itemModel = new TaxRateModel(data); var itemModel = new TaxRateModel(data);
self.tax_rates.push(itemModel); self.tax_rates.push(itemModel);
applyComboboxListeners(); applyComboboxListeners();
} }
/* self.getTaxRateById = function(id) {
self.getBlankTaxRate = function() {
for (var i=0; i<self.tax_rates().length; i++) { for (var i=0; i<self.tax_rates().length; i++) {
var taxRate = self.tax_rates()[i]; var taxRate = self.tax_rates()[i];
if (!taxRate.name()) { if (taxRate.public_id() == id) {
return taxRate; return taxRate;
} }
} }
return false;
} }
*/
self.getTaxRate = function(name, rate) { self.getTaxRate = function(name, rate) {
for (var i=0; i<self.tax_rates().length; i++) { for (var i=0; i<self.tax_rates().length; i++) {
@ -1100,18 +998,6 @@
return 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();
}
self.showClientForm = function() { self.showClientForm = function() {
trackEvent('/activity', '/view_client_form'); trackEvent('/activity', '/view_client_form');
self.clientBackup = ko.mapping.toJS(self.invoice().client); self.clientBackup = ko.mapping.toJS(self.invoice().client);
@ -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) function onPartialChange(silent)
{ {
var val = NINJA.parseFloat($('#partial').val()); var val = NINJA.parseFloat($('#partial').val());
@ -1874,7 +1744,6 @@
window.model = new ViewModel({!! $data !!}); window.model = new ViewModel({!! $data !!});
@else @else
window.model = new ViewModel(); window.model = new ViewModel();
model.addTaxRate();
@foreach ($taxRates as $taxRate) @foreach ($taxRates as $taxRate)
model.addTaxRate({!! $taxRate !!}); model.addTaxRate({!! $taxRate !!});
@endforeach @endforeach
@ -1890,15 +1759,13 @@
} }
} }
model.invoice().addItem(); model.invoice().addItem();
//model.addTaxRate();
@else @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_taxes1({{ $account->custom_invoice_taxes1 ? 'true' : 'false' }});
model.invoice().custom_taxes2({{ $account->custom_invoice_taxes2 ? '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 @endif
@if (isset($tasks) && $tasks) @if (isset($tasks) && $tasks)
@ -1925,7 +1792,6 @@
item.tax(model.getTaxRate(item.tax_name(), item.tax_rate())); item.tax(model.getTaxRate(item.tax_name(), item.tax_rate()));
item.cost(NINJA.parseFloat(item.cost()) != 0 ? roundToTwo(item.cost(), true) : ''); item.cost(NINJA.parseFloat(item.cost()) != 0 ? roundToTwo(item.cost(), true) : '');
} }
onTaxRateChange();
// display blank instead of '0' // display blank instead of '0'
if (!NINJA.parseFloat(model.invoice().discount())) model.invoice().discount(''); if (!NINJA.parseFloat(model.invoice().discount())) model.invoice().discount('');