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:
parent
7150a148af
commit
47f38ad54e
@ -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;
|
||||
|
@ -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')
|
||||
|
@ -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 = [];
|
||||
|
||||
|
@ -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');
|
||||
|
110
app/Http/Controllers/TaxRateController.php
Normal file
110
app/Http/Controllers/TaxRateController.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ use Utils;
|
||||
|
||||
class TaxRateRepository
|
||||
{
|
||||
/*
|
||||
public function save($taxRates)
|
||||
{
|
||||
$taxRateIds = [];
|
||||
@ -39,4 +40,5 @@ class TaxRateRepository
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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',
|
||||
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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>
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
{!! Former::checkbox('fill_products')->text(trans('texts.fill_products_help')) !!}
|
||||
{!! Former::checkbox('update_products')->text(trans('texts.update_products_help')) !!}
|
||||
|
||||
{!! 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') !!}
|
||||
|
||||
|
47
resources/views/accounts/tax_rate.blade.php
Normal file
47
resources/views/accounts/tax_rate.blade.php
Normal 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
|
79
resources/views/accounts/tax_rates.blade.php
Normal file
79
resources/views/accounts/tax_rates.blade.php
Normal 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(' ') !!}
|
||||
|
||||
{!! Former::checkbox('invoice_item_taxes')
|
||||
->text(trans('texts.enable_line_item_tax'))
|
||||
->label(' ') !!}
|
||||
|
||||
{!! Former::checkbox('show_item_taxes')
|
||||
->text(trans('texts.show_line_item_tax'))
|
||||
->label(' ') !!}
|
||||
|
||||
|
||||
|
||||
{!! Former::select('default_tax_rate_id')
|
||||
->style('max-width: 250px')
|
||||
->addOption('', '')
|
||||
->fromQuery($taxRates, function($model) { return $model->name . ': ' . $model->rate . '%'; }, 'id') !!}
|
||||
|
||||
|
||||
|
||||
{!! 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
|
@ -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> </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">×</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">
|
||||
<i style="width:12px;" data-bind="click: $root.removeTaxRate, visible: actionsVisible() && !isEmpty()" class="fa fa-minus-circle redlink" title="Remove item"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
{!! 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(' ')->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(' ')->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('');
|
||||
|
Loading…
Reference in New Issue
Block a user