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

working on currency support

This commit is contained in:
Hillel Coren 2013-12-29 19:40:11 +02:00
parent 4ff55936d7
commit ef24ac2c36
27 changed files with 605 additions and 291 deletions

View File

@ -57,4 +57,5 @@ Configure config/database.php and then initialize the database
* [briannesbitt/Carbon](https://github.com/briannesbitt/Carbon) - A simple API extension for DateTime with PHP 5.3+ * [briannesbitt/Carbon](https://github.com/briannesbitt/Carbon) - A simple API extension for DateTime with PHP 5.3+
* [thomaspark/bootswatch](https://github.com/thomaspark/bootswatch) - Themes for Bootstrap * [thomaspark/bootswatch](https://github.com/thomaspark/bootswatch) - Themes for Bootstrap
* [mozilla/pdf.js](https://github.com/mozilla/pdf.js) - PDF Reader in JavaScript * [mozilla/pdf.js](https://github.com/mozilla/pdf.js) - PDF Reader in JavaScript
* [nnnick/Chart.js](https://github.com/nnnick/Chart.js) - Simple HTML5 Charts using the <canvas> tag * [nnnick/Chart.js](https://github.com/nnnick/Chart.js) - Simple HTML5 Charts using the <canvas> tag
* [josscrowcroft/accounting.js](https://github.com/josscrowcroft/accounting.js) - A lightweight JavaScript library for number, money and currency formatting

View File

@ -92,6 +92,7 @@ class AccountController extends \BaseController {
'timezones' => Timezone::orderBy('location')->get(), 'timezones' => Timezone::orderBy('location')->get(),
'dateFormats' => DateFormat::all(), 'dateFormats' => DateFormat::all(),
'datetimeFormats' => DatetimeFormat::all(), 'datetimeFormats' => DatetimeFormat::all(),
'currencies' => Currency::orderBy('name')->get(),
]; ];
foreach ($data['gateways'] as $gateway) foreach ($data['gateways'] as $gateway)
@ -406,6 +407,7 @@ class AccountController extends \BaseController {
$account->timezone_id = Input::get('timezone_id') ? Input::get('timezone_id') : null; $account->timezone_id = Input::get('timezone_id') ? Input::get('timezone_id') : null;
$account->date_format_id = Input::get('date_format_id') ? Input::get('date_format_id') : null; $account->date_format_id = Input::get('date_format_id') ? Input::get('date_format_id') : null;
$account->datetime_format_id = Input::get('datetime_format_id') ? Input::get('datetime_format_id') : null; $account->datetime_format_id = Input::get('datetime_format_id') ? Input::get('datetime_format_id') : null;
$account->currency_id = Input::get('currency_id') ? Input::get('currency_id') : null;
$account->invoice_terms = Input::get('invoice_terms'); $account->invoice_terms = Input::get('invoice_terms');
$account->save(); $account->save();

View File

@ -9,7 +9,7 @@ class ActivityController extends \BaseController {
return Datatable::collection(Activity::scope()->where('client_id','=',$clientId)->get()) return Datatable::collection(Activity::scope()->where('client_id','=',$clientId)->get())
->addColumn('date', function($model) { return Utils::timestampToDateTimeString(strtotime($model->created_at)); }) ->addColumn('date', function($model) { return Utils::timestampToDateTimeString(strtotime($model->created_at)); })
->addColumn('message', function($model) { return $model->message; }) ->addColumn('message', function($model) { return $model->message; })
->addColumn('balance', function($model) { return '$' . $model->balance; }) ->addColumn('balance', function($model) { return Utils::formatMoney($model->balance, $model->account->currency_id); })
->orderColumns('date') ->orderColumns('date')
->make(); ->make();
} }

View File

@ -26,7 +26,7 @@ class ClientController extends \BaseController {
->where('clients.account_id', '=', Auth::user()->account_id) ->where('clients.account_id', '=', Auth::user()->account_id)
->where('clients.deleted_at', '=', null) ->where('clients.deleted_at', '=', null)
->where('contacts.is_primary', '=', true) ->where('contacts.is_primary', '=', true)
->select('clients.public_id','clients.name','contacts.first_name','contacts.last_name','clients.balance','clients.last_login','clients.created_at','clients.work_phone','contacts.email'); ->select('clients.public_id','clients.name','contacts.first_name','contacts.last_name','clients.balance','clients.last_login','clients.created_at','clients.work_phone','contacts.email','clients.currency_id');
$filter = Input::get('sSearch'); $filter = Input::get('sSearch');
if ($filter) if ($filter)
@ -51,7 +51,7 @@ class ClientController extends \BaseController {
->addColumn('email', function($model) { return $model->email ? HTML::mailto($model->email, $model->email) : ''; }) ->addColumn('email', function($model) { return $model->email ? HTML::mailto($model->email, $model->email) : ''; })
->addColumn('work_phone', function($model) { return Utils::formatPhoneNumber($model->work_phone); }) ->addColumn('work_phone', function($model) { return Utils::formatPhoneNumber($model->work_phone); })
->addColumn('last_login', function($model) { return Utils::timestampToDateString($model->last_login); }) ->addColumn('last_login', function($model) { return Utils::timestampToDateString($model->last_login); })
->addColumn('balance', function($model) { return '$' . $model->balance; }) ->addColumn('balance', function($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
->addColumn('dropdown', function($model) ->addColumn('dropdown', function($model)
{ {
return '<div class="btn-group tr-action" style="visibility:hidden;"> return '<div class="btn-group tr-action" style="visibility:hidden;">
@ -139,6 +139,7 @@ class ClientController extends \BaseController {
'title' => '- ' . $client->name, 'title' => '- ' . $client->name,
'clientSizes' => ClientSize::orderBy('id')->get(), 'clientSizes' => ClientSize::orderBy('id')->get(),
'clientIndustries' => ClientIndustry::orderBy('name')->get(), 'clientIndustries' => ClientIndustry::orderBy('name')->get(),
'currencies' => Currency::orderBy('name')->get(),
'countries' => Country::orderBy('name')->get()); 'countries' => Country::orderBy('name')->get());
return View::make('clients.edit', $data); return View::make('clients.edit', $data);
} }
@ -184,6 +185,7 @@ class ClientController extends \BaseController {
$client->notes = trim(Input::get('notes')); $client->notes = trim(Input::get('notes'));
$client->client_size_id = Input::get('client_size_id') ? Input::get('client_size_id') : null; $client->client_size_id = Input::get('client_size_id') ? Input::get('client_size_id') : null;
$client->client_industry_id = Input::get('client_industry_id') ? Input::get('client_industry_id') : null; $client->client_industry_id = Input::get('client_industry_id') ? Input::get('client_industry_id') : null;
$client->currency_id = Input::get('currency_id') ? Input::get('currency_id') : null;
$client->website = trim(Input::get('website')); $client->website = trim(Input::get('website'));
$client->save(); $client->save();

View File

@ -23,7 +23,7 @@ class CreditController extends \BaseController {
->where('clients.account_id', '=', Auth::user()->account_id) ->where('clients.account_id', '=', Auth::user()->account_id)
->where('clients.deleted_at', '=', null) ->where('clients.deleted_at', '=', null)
->where('credits.deleted_at', '=', null) ->where('credits.deleted_at', '=', null)
->select('credits.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', 'credits.amount', 'credits.credit_date'); ->select('credits.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', 'credits.amount', 'credits.credit_date', 'credits.currency_id');
if ($clientPublicId) { if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId); $query->where('clients.public_id', '=', $clientPublicId);
@ -45,8 +45,8 @@ class CreditController extends \BaseController {
->addColumn('client_name', function($model) { return link_to('clients/' . $model->client_public_id, $model->client_name); }); ->addColumn('client_name', function($model) { return link_to('clients/' . $model->client_public_id, $model->client_name); });
} }
return $table->addColumn('amount', function($model){ return '$' . money_format('%i', $model->amount); }) return $table->addColumn('amount', function($model){ return Utils::formatMoney($model->amount, $model->currency_id); })
->addColumn('credit_date', function($model) { return Utils::timestampToDateString($model->credit_date); }) ->addColumn('credit_date', function($model) { return Utils::fromSqlDate($model->credit_date); })
->addColumn('dropdown', function($model) ->addColumn('dropdown', function($model)
{ {
return '<div class="btn-group tr-action" style="visibility:hidden;"> return '<div class="btn-group tr-action" style="visibility:hidden;">
@ -79,6 +79,7 @@ class CreditController extends \BaseController {
'method' => 'POST', 'method' => 'POST',
'url' => 'credits', 'url' => 'credits',
'title' => '- New Credit', 'title' => '- New Credit',
'currencies' => Currency::orderBy('name')->get(),
'clients' => Client::scope()->orderBy('name')->get()); 'clients' => Client::scope()->orderBy('name')->get());
return View::make('credits.edit', $data); return View::make('credits.edit', $data);
@ -93,6 +94,7 @@ class CreditController extends \BaseController {
'method' => 'PUT', 'method' => 'PUT',
'url' => 'credits/' . $publicId, 'url' => 'credits/' . $publicId,
'title' => '- Edit Credit', 'title' => '- Edit Credit',
'currencies' => Currency::orderBy('name')->get(),
'clients' => Client::scope()->orderBy('name')->get()); 'clients' => Client::scope()->orderBy('name')->get());
return View::make('credit.edit', $data); return View::make('credit.edit', $data);
} }
@ -130,6 +132,7 @@ class CreditController extends \BaseController {
$credit->client_id = Input::get('client'); $credit->client_id = Input::get('client');
$credit->credit_date = Utils::toSqlDate(Input::get('credit_date')); $credit->credit_date = Utils::toSqlDate(Input::get('credit_date'));
$credit->amount = floatval(Input::get('amount')); $credit->amount = floatval(Input::get('amount'));
$credit->currency_id = Input::get('currency_id') ? Input::get('currency_id') : null;
$credit->save(); $credit->save();
$message = $publicId ? 'Successfully updated credit' : 'Successfully created credit'; $message = $publicId ? 'Successfully updated credit' : 'Successfully created credit';

View File

@ -55,8 +55,8 @@ class InvoiceController extends \BaseController {
} }
return $table->addColumn('invoice_date', function($model) { return Utils::fromSqlDate($model->invoice_date); }) return $table->addColumn('invoice_date', function($model) { return Utils::fromSqlDate($model->invoice_date); })
->addColumn('total', function($model) { return '$' . money_format('%i', $model->amount); }) ->addColumn('total', function($model) { return Utils::formatMoney($model->amount, $model->currency_id); })
->addColumn('balance', function($model) { return '$' . money_format('%i', $model->balance); }) ->addColumn('balance', function($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
->addColumn('due_date', function($model) { return Utils::fromSqlDate($model->due_date); }) ->addColumn('due_date', function($model) { return Utils::fromSqlDate($model->due_date); })
->addColumn('invoice_status_name', function($model) { return $model->invoice_status_name; }) ->addColumn('invoice_status_name', function($model) { return $model->invoice_status_name; })
->addColumn('dropdown', function($model) ->addColumn('dropdown', function($model)
@ -94,7 +94,7 @@ class InvoiceController extends \BaseController {
return $table->addColumn('start_date', function($model) { return Utils::fromSqlDate($model->start_date); }) return $table->addColumn('start_date', function($model) { return Utils::fromSqlDate($model->start_date); })
->addColumn('end_date', function($model) { return Utils::fromSqlDate($model->end_date); }) ->addColumn('end_date', function($model) { return Utils::fromSqlDate($model->end_date); })
->addColumn('total', function($model) { return '$' . money_format('%i', $model->amount); }) ->addColumn('total', function($model) { return Utils::formatMoney($model->amount, $model->currency_id); })
->addColumn('dropdown', function($model) ->addColumn('dropdown', function($model)
{ {
return '<div class="btn-group tr-action" style="visibility:hidden;"> return '<div class="btn-group tr-action" style="visibility:hidden;">
@ -352,6 +352,7 @@ class InvoiceController extends \BaseController {
'countries' => Country::orderBy('name')->get(), 'countries' => Country::orderBy('name')->get(),
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
'taxRates' => TaxRate::scope()->orderBy('name')->get(), 'taxRates' => TaxRate::scope()->orderBy('name')->get(),
'currencies' => Currency::orderBy('name')->get(),
'frequencies' => array( 'frequencies' => array(
1 => 'Weekly', 1 => 'Weekly',
2 => 'Two weeks', 2 => 'Two weeks',

View File

@ -19,7 +19,7 @@ class PaymentController extends \BaseController
->where('payments.account_id', '=', Auth::user()->account_id) ->where('payments.account_id', '=', Auth::user()->account_id)
->where('payments.deleted_at', '=', null) ->where('payments.deleted_at', '=', null)
->where('clients.deleted_at', '=', null) ->where('clients.deleted_at', '=', null)
->select('payments.public_id', 'payments.transaction_reference', 'clients.name as client_name', 'clients.public_id as client_public_id', 'payments.amount', 'payments.payment_date', 'invoices.public_id as invoice_public_id', 'invoices.invoice_number'); ->select('payments.public_id', 'payments.transaction_reference', 'clients.name as client_name', 'clients.public_id as client_public_id', 'payments.amount', 'payments.payment_date', 'invoices.public_id as invoice_public_id', 'invoices.invoice_number', 'payments.currency_id');
if ($clientPublicId) { if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId); $query->where('clients.public_id', '=', $clientPublicId);
@ -47,7 +47,7 @@ class PaymentController extends \BaseController
} }
return $table->addColumn('invoice_number', function($model) { return $model->invoice_public_id ? link_to('invoices/' . $model->invoice_public_id . '/edit', $model->invoice_number) : ''; }) return $table->addColumn('invoice_number', function($model) { return $model->invoice_public_id ? link_to('invoices/' . $model->invoice_public_id . '/edit', $model->invoice_number) : ''; })
->addColumn('amount', function($model) { return '$' . $model->amount; }) ->addColumn('amount', function($model) { return Utils::formatMoney($model->amount, $model->currency_id); })
->addColumn('payment_date', function($model) { return Utils::dateToString($model->payment_date); }) ->addColumn('payment_date', function($model) { return Utils::dateToString($model->payment_date); })
->addColumn('dropdown', function($model) ->addColumn('dropdown', function($model)
{ {
@ -83,6 +83,7 @@ class PaymentController extends \BaseController
'method' => 'POST', 'method' => 'POST',
'url' => 'payments', 'url' => 'payments',
'title' => '- New Payment', 'title' => '- New Payment',
'currencies' => Currency::orderBy('name')->get(),
'clients' => Client::scope()->orderBy('name')->get()); 'clients' => Client::scope()->orderBy('name')->get());
return View::make('payments.edit', $data); return View::make('payments.edit', $data);
@ -99,6 +100,7 @@ class PaymentController extends \BaseController
'method' => 'PUT', 'method' => 'PUT',
'url' => 'payments/' . $publicId, 'url' => 'payments/' . $publicId,
'title' => '- Edit Payment', 'title' => '- Edit Payment',
'currencies' => Currency::orderBy('name')->get(),
'clients' => Client::scope()->orderBy('name')->get()); 'clients' => Client::scope()->orderBy('name')->get());
return View::make('payments.edit', $data); return View::make('payments.edit', $data);
} }
@ -137,6 +139,7 @@ class PaymentController extends \BaseController
$payment->client_id = Input::get('client'); $payment->client_id = Input::get('client');
$payment->invoice_id = $invoiceId; $payment->invoice_id = $invoiceId;
$payment->currency_id = Input::get('currency_id') ? Input::get('currency_id') : null;
$payment->payment_date = Utils::toSqlDate(Input::get('payment_date')); $payment->payment_date = Utils::toSqlDate(Input::get('payment_date'));
$payment->amount = floatval(Input::get('amount')); $payment->amount = floatval(Input::get('amount'));
$payment->save(); $payment->save();

View File

@ -9,7 +9,7 @@ class ConfideSetupUsersTable extends Migration {
* @return void * @return void
*/ */
public function up() public function up()
{ {
Schema::dropIfExists('themes'); Schema::dropIfExists('themes');
Schema::dropIfExists('credits'); Schema::dropIfExists('credits');
Schema::dropIfExists('activities'); Schema::dropIfExists('activities');
@ -28,6 +28,7 @@ class ConfideSetupUsersTable extends Migration {
Schema::dropIfExists('client_industries'); Schema::dropIfExists('client_industries');
Schema::dropIfExists('users'); Schema::dropIfExists('users');
Schema::dropIfExists('accounts'); Schema::dropIfExists('accounts');
Schema::dropIfExists('currencies');
Schema::dropIfExists('invoice_statuses'); Schema::dropIfExists('invoice_statuses');
Schema::dropIfExists('countries'); Schema::dropIfExists('countries');
Schema::dropIfExists('timezones'); Schema::dropIfExists('timezones');
@ -81,12 +82,24 @@ class ConfideSetupUsersTable extends Migration {
$t->string('label'); $t->string('label');
}); });
Schema::create('currencies', function($t)
{
$t->increments('id');
$t->string('name');
$t->string('symbol');
$t->string('precision');
$t->string('thousand_separator');
$t->string('decimal_separator');
});
Schema::create('accounts', function($t) Schema::create('accounts', function($t)
{ {
$t->increments('id'); $t->increments('id');
$t->unsignedInteger('timezone_id')->nullable(); $t->unsignedInteger('timezone_id')->nullable();
$t->unsignedInteger('date_format_id')->nullable(); $t->unsignedInteger('date_format_id')->nullable();
$t->unsignedInteger('datetime_format_id')->nullable(); $t->unsignedInteger('datetime_format_id')->nullable();
$t->unsignedInteger('currency_id')->nullable();
$t->timestamps(); $t->timestamps();
$t->softDeletes(); $t->softDeletes();
@ -108,6 +121,7 @@ class ConfideSetupUsersTable extends Migration {
$t->foreign('date_format_id')->references('id')->on('date_formats'); $t->foreign('date_format_id')->references('id')->on('date_formats');
$t->foreign('datetime_format_id')->references('id')->on('datetime_formats'); $t->foreign('datetime_format_id')->references('id')->on('datetime_formats');
$t->foreign('country_id')->references('id')->on('countries'); $t->foreign('country_id')->references('id')->on('countries');
$t->foreign('currency_id')->references('id')->on('currencies');
}); });
Schema::create('gateways', function($t) Schema::create('gateways', function($t)
@ -186,6 +200,7 @@ class ConfideSetupUsersTable extends Migration {
$t->increments('id'); $t->increments('id');
$t->unsignedInteger('user_id'); $t->unsignedInteger('user_id');
$t->unsignedInteger('account_id')->index(); $t->unsignedInteger('account_id')->index();
$t->unsignedInteger('currency_id')->default(1)->nullable();
$t->timestamps(); $t->timestamps();
$t->softDeletes(); $t->softDeletes();
@ -198,8 +213,8 @@ class ConfideSetupUsersTable extends Migration {
$t->unsignedInteger('country_id')->nullable(); $t->unsignedInteger('country_id')->nullable();
$t->string('work_phone'); $t->string('work_phone');
$t->text('notes'); $t->text('notes');
$t->decimal('balance', 10, 2); $t->decimal('balance', 13, 4);
$t->decimal('paid_to_date', 10, 2); $t->decimal('paid_to_date', 13, 4);
$t->timestamp('last_login')->nullable(); $t->timestamp('last_login')->nullable();
$t->string('website'); $t->string('website');
$t->unsignedInteger('client_industry_id')->nullable(); $t->unsignedInteger('client_industry_id')->nullable();
@ -211,7 +226,8 @@ class ConfideSetupUsersTable extends Migration {
$t->foreign('country_id')->references('id')->on('countries'); $t->foreign('country_id')->references('id')->on('countries');
$t->foreign('client_industry_id')->references('id')->on('client_industries'); $t->foreign('client_industry_id')->references('id')->on('client_industries');
$t->foreign('client_size_id')->references('id')->on('client_sizes'); $t->foreign('client_size_id')->references('id')->on('client_sizes');
$t->foreign('currency_id')->references('id')->on('currencies');
$t->unsignedInteger('public_id')->index(); $t->unsignedInteger('public_id')->index();
$t->unique( array('account_id','public_id') ); $t->unique( array('account_id','public_id') );
}); });
@ -259,6 +275,7 @@ class ConfideSetupUsersTable extends Migration {
$t->unsignedInteger('user_id'); $t->unsignedInteger('user_id');
$t->unsignedInteger('account_id')->index(); $t->unsignedInteger('account_id')->index();
$t->unsignedInteger('invoice_status_id')->default(1); $t->unsignedInteger('invoice_status_id')->default(1);
$t->unsignedInteger('currency_id')->default(1);
$t->timestamps(); $t->timestamps();
$t->softDeletes(); $t->softDeletes();
@ -277,13 +294,14 @@ class ConfideSetupUsersTable extends Migration {
$t->unsignedInteger('recurring_invoice_id')->index()->nullable(); $t->unsignedInteger('recurring_invoice_id')->index()->nullable();
$t->decimal('amount', 10, 2); $t->decimal('amount', 13, 4);
$t->decimal('balance', 10, 2); $t->decimal('balance', 13, 4);
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); $t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
$t->foreign('account_id')->references('id')->on('accounts'); $t->foreign('account_id')->references('id')->on('accounts');
$t->foreign('user_id')->references('id')->on('users'); $t->foreign('user_id')->references('id')->on('users');
$t->foreign('invoice_status_id')->references('id')->on('invoice_statuses'); $t->foreign('invoice_status_id')->references('id')->on('invoice_statuses');
$t->foreign('currency_id')->references('id')->on('currencies');
$t->foreign('recurring_invoice_id')->references('id')->on('invoices'); $t->foreign('recurring_invoice_id')->references('id')->on('invoices');
$t->unsignedInteger('public_id')->index(); $t->unsignedInteger('public_id')->index();
@ -322,7 +340,7 @@ class ConfideSetupUsersTable extends Migration {
$t->softDeletes(); $t->softDeletes();
$t->string('name'); $t->string('name');
$t->decimal('rate', 10, 2); $t->decimal('rate', 13, 4);
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users'); $t->foreign('user_id')->references('id')->on('users');
@ -341,8 +359,8 @@ class ConfideSetupUsersTable extends Migration {
$t->string('product_key'); $t->string('product_key');
$t->string('notes'); $t->string('notes');
$t->decimal('cost', 10, 2); $t->decimal('cost', 13, 4);
$t->decimal('qty', 10, 2); $t->decimal('qty', 13, 4);
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users'); $t->foreign('user_id')->references('id')->on('users');
@ -364,11 +382,11 @@ class ConfideSetupUsersTable extends Migration {
$t->string('product_key'); $t->string('product_key');
$t->string('notes'); $t->string('notes');
$t->decimal('cost', 10, 2); $t->decimal('cost', 13, 4);
$t->decimal('qty', 10, 2); $t->decimal('qty', 13, 4);
$t->string('tax_name'); $t->string('tax_name');
$t->decimal('tax_rate', 10, 2); $t->decimal('tax_rate', 13, 4);
$t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade'); $t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
$t->foreign('product_id')->references('id')->on('products'); $t->foreign('product_id')->references('id')->on('products');
@ -387,11 +405,12 @@ class ConfideSetupUsersTable extends Migration {
$t->unsignedInteger('contact_id')->nullable(); $t->unsignedInteger('contact_id')->nullable();
$t->unsignedInteger('invitation_id')->nullable(); $t->unsignedInteger('invitation_id')->nullable();
$t->unsignedInteger('user_id')->nullable(); $t->unsignedInteger('user_id')->nullable();
$t->unsignedInteger('currency_id')->default(1);
$t->timestamps(); $t->timestamps();
$t->softDeletes(); $t->softDeletes();
$t->boolean('is_deleted'); $t->boolean('is_deleted');
$t->decimal('amount', 10, 2); $t->decimal('amount', 13, 4);
$t->date('payment_date'); $t->date('payment_date');
$t->string('transaction_reference'); $t->string('transaction_reference');
$t->string('payer_id'); $t->string('payer_id');
@ -401,7 +420,8 @@ class ConfideSetupUsersTable extends Migration {
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); $t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
$t->foreign('contact_id')->references('id')->on('contacts'); $t->foreign('contact_id')->references('id')->on('contacts');
$t->foreign('user_id')->references('id')->on('users'); $t->foreign('user_id')->references('id')->on('users');
$t->foreign('currency_id')->references('id')->on('currencies');
$t->unsignedInteger('public_id')->index(); $t->unsignedInteger('public_id')->index();
$t->unique( array('account_id','public_id') ); $t->unique( array('account_id','public_id') );
}); });
@ -413,11 +433,12 @@ class ConfideSetupUsersTable extends Migration {
$t->unsignedInteger('user_id'); $t->unsignedInteger('user_id');
$t->unsignedInteger('client_id')->index()->nullable(); $t->unsignedInteger('client_id')->index()->nullable();
$t->unsignedInteger('contact_id')->nullable(); $t->unsignedInteger('contact_id')->nullable();
$t->unsignedInteger('currency_id')->default(1);
$t->timestamps(); $t->timestamps();
$t->softDeletes(); $t->softDeletes();
$t->boolean('is_deleted'); $t->boolean('is_deleted');
$t->decimal('amount', 10, 2); $t->decimal('amount', 13, 4);
$t->date('credit_date')->nullable(); $t->date('credit_date')->nullable();
$t->string('credit_number'); $t->string('credit_number');
@ -425,6 +446,7 @@ class ConfideSetupUsersTable extends Migration {
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); $t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
$t->foreign('contact_id')->references('id')->on('contacts'); $t->foreign('contact_id')->references('id')->on('contacts');
$t->foreign('user_id')->references('id')->on('users'); $t->foreign('user_id')->references('id')->on('users');
$t->foreign('currency_id')->references('id')->on('currencies');
$t->unsignedInteger('public_id')->index(); $t->unsignedInteger('public_id')->index();
$t->unique( array('account_id','public_id') ); $t->unique( array('account_id','public_id') );
@ -443,14 +465,16 @@ class ConfideSetupUsersTable extends Migration {
$t->unsignedInteger('invoice_id'); $t->unsignedInteger('invoice_id');
$t->unsignedInteger('credit_id'); $t->unsignedInteger('credit_id');
$t->unsignedInteger('invitation_id'); $t->unsignedInteger('invitation_id');
$t->unsignedInteger('currency_id')->default(1);
$t->text('message'); $t->text('message');
$t->integer('activity_type_id'); $t->integer('activity_type_id');
$t->decimal('adjustment', 10, 2); $t->decimal('adjustment', 13, 4);
$t->decimal('balance', 10, 2); $t->decimal('balance', 13, 4);
$t->foreign('account_id')->references('id')->on('accounts'); $t->foreign('account_id')->references('id')->on('accounts');
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); $t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
$t->foreign('currency_id')->references('id')->on('currencies');
}); });
} }
@ -479,6 +503,7 @@ class ConfideSetupUsersTable extends Migration {
Schema::dropIfExists('client_industries'); Schema::dropIfExists('client_industries');
Schema::dropIfExists('users'); Schema::dropIfExists('users');
Schema::dropIfExists('accounts'); Schema::dropIfExists('accounts');
Schema::dropIfExists('currencies');
Schema::dropIfExists('invoice_statuses'); Schema::dropIfExists('invoice_statuses');
Schema::dropIfExists('countries'); Schema::dropIfExists('countries');
Schema::dropIfExists('timezones'); Schema::dropIfExists('timezones');

View File

@ -69,6 +69,10 @@ class ConstantsSeeder extends Seeder
ClientSize::create(array('name' => '101 - 500')); ClientSize::create(array('name' => '101 - 500'));
ClientSize::create(array('name' => '500+')); ClientSize::create(array('name' => '500+'));
Currency::create(array('name' => 'US Dollar', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Pound Sterling', 'symbol' => '£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
DatetimeFormat::create(array('format' => 'F j, Y, g:i a', 'label' => 'March 10, 2013, 6:15 pm')); DatetimeFormat::create(array('format' => 'F j, Y, g:i a', 'label' => 'March 10, 2013, 6:15 pm'));
DatetimeFormat::create(array('format' => 'D M jS, Y g:ia', 'label' => 'Mon March 10th, 2013, 6:15 pm')); DatetimeFormat::create(array('format' => 'D M jS, Y g:ia', 'label' => 'Mon March 10th, 2013, 6:15 pm'));

View File

@ -33,5 +33,6 @@ class UserEventHandler
Session::put(SESSION_DATE_FORMAT, $account->date_format ? $account->date_format->format : DEFAULT_DATE_FORMAT); Session::put(SESSION_DATE_FORMAT, $account->date_format ? $account->date_format->format : DEFAULT_DATE_FORMAT);
Session::put(SESSION_DATE_PICKER_FORMAT, $account->date_format ? $account->date_format->picker_format : DEFAULT_DATE_PICKER_FORMAT); Session::put(SESSION_DATE_PICKER_FORMAT, $account->date_format ? $account->date_format->picker_format : DEFAULT_DATE_PICKER_FORMAT);
Session::put(SESSION_DATETIME_FORMAT, $account->datetime_format ? $account->datetime_format->format : DEFAULT_DATETIME_FORMAT); Session::put(SESSION_DATETIME_FORMAT, $account->datetime_format ? $account->datetime_format->format : DEFAULT_DATETIME_FORMAT);
Session::put(SESSION_CURRENCY, $account->currency_id ? $account->currency_id : DEFAULT_CURRENCY);
} }
} }

View File

@ -35,6 +35,15 @@ class Utils
return $phoneNumber; return $phoneNumber;
} }
public static function formatMoney($value, $currencyId)
{
$currency = Currency::find($currencyId);
if (!$currency) {
$currency = Currency::find(1);
}
return $currency->symbol . number_format($value, $currency->precision, $currency->decimal_separator, $currency->thousand_separator);
}
public static function pluralize($string, $count) public static function pluralize($string, $count)
{ {
$string = str_replace('?', $count, $string); $string = str_replace('?', $count, $string);

View File

@ -26,6 +26,12 @@ class Activity extends Eloquent
return $query->whereAccountId(Auth::user()->account_id); return $query->whereAccountId(Auth::user()->account_id);
} }
public function account()
{
return $this->belongsTo('Account');
}
private static function getBlank($entity = false) private static function getBlank($entity = false)
{ {
$activity = new Activity; $activity = new Activity;
@ -75,6 +81,7 @@ class Activity extends Eloquent
$activity = Activity::getBlank($invoice); $activity = Activity::getBlank($invoice);
$activity->invoice_id = $invoice->id; $activity->invoice_id = $invoice->id;
$activity->client_id = $invoice->client_id; $activity->client_id = $invoice->client_id;
$activity->currency_id = $invoice->currency_id;
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_INVOICE; $activity->activity_type_id = ACTIVITY_TYPE_CREATE_INVOICE;
$activity->message = $message; $activity->message = $message;
$activity->save(); $activity->save();
@ -121,6 +128,7 @@ class Activity extends Eloquent
$activity->invoice_id = $payment->invoice_id; $activity->invoice_id = $payment->invoice_id;
} }
$activity->client_id = $payment->client_id; $activity->client_id = $payment->client_id;
$activity->currency_id = $payment->currency_id;
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_PAYMENT; $activity->activity_type_id = ACTIVITY_TYPE_CREATE_PAYMENT;
$activity->save(); $activity->save();
} }
@ -132,6 +140,7 @@ class Activity extends Eloquent
$activity->message = Auth::user()->getFullName() . ' created credit'; $activity->message = Auth::user()->getFullName() . ' created credit';
$activity->credit_id = $credit->id; $activity->credit_id = $credit->id;
$activity->client_id = $credit->client_id; $activity->client_id = $credit->client_id;
$activity->currency_id = $credit->currency_id;
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_CREDIT; $activity->activity_type_id = ACTIVITY_TYPE_CREATE_CREDIT;
$activity->save(); $activity->save();
} }

7
app/models/Currency.php Executable file
View File

@ -0,0 +1,7 @@
<?php
class Currency extends Eloquent
{
public $timestamps = false;
protected $softDelete = false;
}

View File

@ -14,6 +14,7 @@ class AccountRepository
$contacts = \DB::table('clients') $contacts = \DB::table('clients')
->join('contacts', 'contacts.client_id', '=', 'clients.id') ->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('clients.deleted_at', '=', null) ->where('clients.deleted_at', '=', null)
->whereRaw("CONCAT(contacts.first_name, contacts.last_name) <> ''")
->select(\DB::raw("'Contacts' as type, clients.public_id, CONCAT(contacts.first_name, ' ', contacts.last_name, ': ', clients.name) as name, '' as token")); ->select(\DB::raw("'Contacts' as type, clients.public_id, CONCAT(contacts.first_name, ' ', contacts.last_name, ': ', clients.name) as name, '' as token"));
$invoices = \DB::table('clients') $invoices = \DB::table('clients')

View File

@ -30,6 +30,7 @@ class ClientRepository
$client->notes = trim($data['notes']); $client->notes = trim($data['notes']);
$client->client_size_id = $data['client_size_id'] ? $data['client_size_id'] : null; $client->client_size_id = $data['client_size_id'] ? $data['client_size_id'] : null;
$client->client_industry_id = $data['client_industry_id'] ? $data['client_industry_id'] : null; $client->client_industry_id = $data['client_industry_id'] ? $data['client_industry_id'] : null;
$client->currency_id = $data['currency_id'] ? $data['currency_id'] : null;
$client->website = trim($data['website']); $client->website = trim($data['website']);
$client->save(); $client->save();

View File

@ -17,7 +17,7 @@ class InvoiceRepository
->where('invoices.deleted_at', '=', null) ->where('invoices.deleted_at', '=', null)
->where('clients.deleted_at', '=', null) ->where('clients.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false) ->where('invoices.is_recurring', '=', false)
->select('clients.public_id as client_public_id', 'invoice_number', 'clients.name as client_name', 'invoices.public_id', 'amount', 'invoices.balance', 'invoice_date', 'due_date', 'invoice_statuses.name as invoice_status_name'); ->select('clients.public_id as client_public_id', 'invoice_number', 'clients.name as client_name', 'invoices.public_id', 'amount', 'invoices.balance', 'invoice_date', 'due_date', 'invoice_statuses.name as invoice_status_name', 'invoices.currency_id');
if ($clientPublicId) if ($clientPublicId)
{ {
@ -45,7 +45,7 @@ class InvoiceRepository
->where('invoices.account_id', '=', $accountId) ->where('invoices.account_id', '=', $accountId)
->where('invoices.deleted_at', '=', null) ->where('invoices.deleted_at', '=', null)
->where('invoices.is_recurring', '=', true) ->where('invoices.is_recurring', '=', true)
->select('clients.public_id as client_public_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'frequencies.name as frequency', 'start_date', 'end_date'); ->select('clients.public_id as client_public_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'frequencies.name as frequency', 'start_date', 'end_date', 'invoices.currency_id');
if ($clientPublicId) if ($clientPublicId)
{ {
@ -88,7 +88,8 @@ class InvoiceRepository
$invoice->end_date = Utils::toSqlDate($data['end_date']); $invoice->end_date = Utils::toSqlDate($data['end_date']);
$invoice->terms = trim($data['terms']); $invoice->terms = trim($data['terms']);
$invoice->po_number = trim($data['po_number']); $invoice->po_number = trim($data['po_number']);
$invoice->currency_id = $data['currency_id'];
$total = 0; $total = 0;
foreach ($data['invoice_items'] as $item) foreach ($data['invoice_items'] as $item)

View File

@ -170,11 +170,13 @@ define('FREQUENCY_SIX_MONTHS', 6);
define('FREQUENCY_ANNUALLY', 7); define('FREQUENCY_ANNUALLY', 7);
define('SESSION_TIMEZONE', 'timezone'); define('SESSION_TIMEZONE', 'timezone');
define('SESSION_CURRENCY', 'currency');
define('SESSION_DATE_FORMAT', 'dateFormat'); define('SESSION_DATE_FORMAT', 'dateFormat');
define('SESSION_DATE_PICKER_FORMAT', 'datePickerFormat'); define('SESSION_DATE_PICKER_FORMAT', 'datePickerFormat');
define('SESSION_DATETIME_FORMAT', 'datetimeFormat'); define('SESSION_DATETIME_FORMAT', 'datetimeFormat');
define('DEFAULT_TIMEZONE', 'US/Eastern'); define('DEFAULT_TIMEZONE', 'US/Eastern');
define('DEFAULT_CURRENCY', 1); // US Dollar
define('DEFAULT_DATE_FORMAT', 'M j, Y'); define('DEFAULT_DATE_FORMAT', 'M j, Y');
define('DEFAULT_DATE_PICKER_FORMAT', 'yyyy-mm-dd'); define('DEFAULT_DATE_PICKER_FORMAT', 'yyyy-mm-dd');
define('DEFAULT_DATETIME_FORMAT', 'F j, Y, g:i a'); define('DEFAULT_DATETIME_FORMAT', 'F j, Y, g:i a');

View File

@ -39,7 +39,9 @@
@endforeach @endforeach
{{ Former::legend('Date and Time') }} {{ Former::legend('Localization') }}
{{ Former::select('currency_id')->addOption('','')->label('Currency')
->fromQuery($currencies, 'name', 'id')->select($account->currency_id) }}
{{ Former::select('timezone_id')->addOption('','')->label('Timezone') {{ Former::select('timezone_id')->addOption('','')->label('Timezone')
->fromQuery($timezones, 'location', 'id')->select($account->timezone_id) }} ->fromQuery($timezones, 'location', 'id')->select($account->timezone_id) }}
{{ Former::select('date_format_id')->addOption('','')->label('Date Format') {{ Former::select('date_format_id')->addOption('','')->label('Date Format')

View File

@ -64,6 +64,8 @@
</div> </div>
{{ Former::legend('Additional Info') }} {{ Former::legend('Additional Info') }}
{{ Former::select('currency_id')->addOption('','')->label('Currency')
->fromQuery($currencies, 'name', 'id')->select(Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY)) }}
{{ Former::select('client_size_id')->addOption('','')->label('Size') {{ Former::select('client_size_id')->addOption('','')->label('Size')
->fromQuery($clientSizes, 'name', 'id')->select($client ? $client->client_size_id : '') }} ->fromQuery($clientSizes, 'name', 'id')->select($client ? $client->client_size_id : '') }}
{{ Former::select('client_industry_id')->addOption('','')->label('Industry') {{ Former::select('client_industry_id')->addOption('','')->label('Industry')

View File

@ -28,6 +28,8 @@
@endif @endif
{{ Former::select('client')->fromQuery($clients, 'name', 'public_id')->select($client ? $client->public_id : '')->addOption('', '')->addGroupClass('client-select') }} {{ Former::select('client')->fromQuery($clients, 'name', 'public_id')->select($client ? $client->public_id : '')->addOption('', '')->addGroupClass('client-select') }}
{{ Former::select('currency_id')->addOption('','')->label('Currency')
->fromQuery($currencies, 'name', 'id')->select(Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY)) }}
{{ Former::text('amount') }} {{ Former::text('amount') }}
{{ Former::text('credit_date')->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }} {{ Former::text('credit_date')->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }}
@ -50,7 +52,8 @@
var $input = $('select#client'); var $input = $('select#client');
$input.combobox(); $input.combobox();
$('#currency_id').combobox();
$('#credit_date').datepicker({ $('#credit_date').datepicker({
autoclose: true, autoclose: true,
todayHighlight: true todayHighlight: true

View File

@ -36,7 +36,8 @@
// dynamic table // dynamic table
jQuery('.{{ $class }}').dataTable({ jQuery('.{{ $class }}').dataTable({
// Disable sorting on the first column // Disable sorting on the first column
@if (isset($haeCheckboxes) && $hasCheckboxes) "aaSorting": [],
@if (isset($hasCheckboxes) && $hasCheckboxes)
"aoColumnDefs" : [ { "aoColumnDefs" : [ {
'bSortable' : false, 'bSortable' : false,
'aTargets' : [ 0 ] 'aTargets' : [ 0 ]

View File

@ -33,7 +33,9 @@
<link rel="stylesheet" type="text/css" href="{{ asset('css/typeahead.js-bootstrap.css') }}"/> <link rel="stylesheet" type="text/css" href="{{ asset('css/typeahead.js-bootstrap.css') }}"/>
<script src="{{ asset('js/script.js') }}" type="text/javascript"></script> <script src="{{ asset('js/script.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/accounting.js') }}" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="{{ asset('css/style.css') }}"/>
<style type="text/css"> <style type="text/css">
@if (!Auth::check() || Auth::user()->showGreyBackground()) @if (!Auth::check() || Auth::user()->showGreyBackground())
@ -42,191 +44,23 @@
} }
@endif @endif
/*
body > div.container {
min-height: 600px;
}
*/
label.checkbox,
label.control-label {
font-weight: normal !important;
}
div.panel {
padding-left: 0px !important;
padding-right: 0px !important;
}
.form-actions {
margin: 0;
background-color: transparent;
text-align: center;
}
/*
.form-horizontal {
max-width: 750px;
margin: 0 auto;
}
*/
/*
.form-group {
width: 50%;
margin: 0 auto;
}
*/
/* DataTables and BootStrap */
.dataTables_wrapper {
padding-top: 16px;
}
table.table thead > tr > th {
border-bottom-width: 0px;
}
#DataTables_Table_0_length label {
font-weight: normal;
padding-bottom: 10px;
}
div.dataTables_paginate.paging_bootstrap {
margin-top: -30px;
}
/*
table.table tbody tr.odd {
background-color: #f9f9f9;
}
table.table tbody tr:hover {
background-color: #f0f0f0 !important;
}
*/
/* table sorting indicators */
/*table.table thead .sorting { background: url('images/sort_both.png') no-repeat center right; }*/
/*
table.table thead .sorting,
table.table thead .sorting_asc,
table.table thead .sorting_desc,
table.table thead .sorting_asc_disabled,
table.table thead .sorting_desc_disabled {
cursor: pointer;
*cursor: hand;
}
table.table thead .sorting_asc { background: url('images/sort_asc.png') no-repeat center right; }
table.table thead .sorting_desc { background: url('images/sort_desc.png') no-repeat center right; }
table.table thead .sorting_asc_disabled { background: url('images/sort_asc_disabled.png') no-repeat center right; }
table.table thead .sorting_desc_disabled { background: url('images/sort_desc_disabled.png') no-repeat center right; }
*/
/* Hover nav */
.sidebar-nav {
padding: 9px 0;
}
.dropdown-menu .sub-menu {
left: 100%;
position: absolute;
top: 0;
visibility: hidden;
margin-top: -1px;
}
.dropdown-menu li:hover .sub-menu {
visibility: visible;
}
.dropdown:hover .dropdown-menu {
display: block;
}
.nav-tabs .dropdown-menu, .nav-pills .dropdown-menu, .navbar .dropdown-menu {
margin-top: 0;
}
.navbar .sub-menu:before {
border-bottom: 7px solid transparent;
border-left: none;
border-right: 7px solid rgba(0, 0, 0, 0.2);
border-top: 7px solid transparent;
left: -7px;
top: 10px;
}
.navbar .sub-menu:after {
border-top: 6px solid transparent;
border-left: none;
border-right: 6px solid #fff;
border-bottom: 6px solid transparent;
left: 10px;
top: 11px;
left: -6px;
}
.invoice-table {
border-style: none !important;
}
/*
table.invoice-table tbody tr:hover {
background-color: #FFFFFF !important;
}
*/
.invoice-table td {
padding: 2px !important;
}
.invoice-table td input,
.invoice-table td textarea {
border: none !important;
width: 100%;
}
.invoice-table th {
border-top: 1px solid #ddd !important;
}
.invoice-table td.hide-border,
.invoice-table th.hide-border {
border-style: none !important;
}
.invoice-table td.td-icon {
vertical-align: middle !important;
}
.fa-bars {
cursor: move !important;
}
.closer-row {
margin-bottom: 2px;
}
/* Animate col width changes */
.row div {
-webkit-transition: width 0.5s ease, margin 0.5s ease;
-moz-transition: width 0.5s ease, margin 0.5s ease;
-o-transition: width 0.5s ease, margin 0.5s ease;
transition: width 0.5s ease, margin 0.5s ease;
}
</style> </style>
<script type="text/javascript">
var currencies = {{ Currency::remember(120)->get(); }};
var currencyMap = {};
for (var i=0; i<currencies.length; i++) {
var currency = currencies[i];
currencyMap[currency.id] = currency;
}
function formatMoney(value, currency_id, hide_symbol) {
if (!currency_id) currency_id = {{ Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY); }};
var currency = currencyMap[currency_id];
return accounting.formatMoney(value, hide_symbol ? '' : currency.symbol, currency.precision, currency.thousand_separator, currency.decimal_separator);
}
</script>
@stop @stop
@section('body') @section('body')

View File

@ -66,7 +66,7 @@
<div class="col-md-3" id="col_2"> <div class="col-md-3" id="col_2">
{{ Former::text('po_number')->label('PO&nbsp;number')->data_bind("value: po_number, valueUpdate: 'afterkeydown'") }} {{ Former::text('po_number')->label('PO&nbsp;number')->data_bind("value: po_number, valueUpdate: 'afterkeydown'") }}
{{ Former::text('discount')->data_bind("value: discount, valueUpdate: 'afterkeydown'") }} {{ Former::text('discount')->data_bind("value: discount, valueUpdate: 'afterkeydown'") }}
{{ Former::text('currency')->data_bind("value: discount, valueUpdate: 'afterkeydown'") }} {{ Former::select('currency_id')->label('Currency')->fromQuery($currencies, 'name', 'id')->data_bind("value: currency_id") }}
<div class="form-group" style="margin-bottom: 8px"> <div class="form-group" style="margin-bottom: 8px">
<label for="recurring" class="control-label col-lg-4 col-sm-4">Taxes</label> <label for="recurring" class="control-label col-lg-4 col-sm-4">Taxes</label>
@ -241,6 +241,8 @@
</div> </div>
{{ Former::legend('Additional Info') }} {{ Former::legend('Additional Info') }}
{{ Former::select('currency_id')->addOption('','')->label('Currency')->data_bind('value: currency_id')
->fromQuery($currencies, 'name', 'id') }}
{{ Former::select('client_size_id')->addOption('','')->label('Size')->data_bind('value: client_size_id') {{ Former::select('client_size_id')->addOption('','')->label('Size')->data_bind('value: client_size_id')
->fromQuery($clientSizes, 'name', 'id')->select($client ? $client->client_size_id : '') }} ->fromQuery($clientSizes, 'name', 'id')->select($client ? $client->client_size_id : '') }}
{{ Former::select('client_industry_id')->addOption('','')->label('Industry')->data_bind('value: client_industry_id') {{ Former::select('client_industry_id')->addOption('','')->label('Industry')->data_bind('value: client_industry_id')
@ -329,6 +331,10 @@
todayHighlight: true todayHighlight: true
}); });
@if ($client && !$invoice)
$('input[name=client]').val({{ $client->public_id }});
@endif
var $input = $('select#client'); var $input = $('select#client');
$input.combobox(); $input.combobox();
$('.client_select input.form-control').on('change', function(e) { $('.client_select input.form-control').on('change', function(e) {
@ -513,6 +519,7 @@
this.client = new ClientModel(); this.client = new ClientModel();
self.discount = ko.observable(''); self.discount = ko.observable('');
self.frequency_id = ko.observable(''); self.frequency_id = ko.observable('');
self.currency_id = ko.observable({{ Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY) }});
self.terms = ko.observable(''); self.terms = ko.observable('');
self.po_number = ko.observable(''); self.po_number = ko.observable('');
self.invoice_date = ko.observable(''); self.invoice_date = ko.observable('');
@ -636,13 +643,13 @@
this.subtotal = ko.computed(function() { this.subtotal = ko.computed(function() {
var total = self.rawSubtotal(); var total = self.rawSubtotal();
return total > 0 ? formatMoney(total) : ''; return total > 0 ? formatMoney(total, self.currency_id()) : '';
}); });
this.discounted = ko.computed(function() { this.discounted = ko.computed(function() {
var total = self.rawSubtotal() * (self.discount()/100); var total = self.rawSubtotal() * (self.discount()/100);
return formatMoney(total); return formatMoney(total, self.currency_id());
}); });
this.total = ko.computed(function() { this.total = ko.computed(function() {
@ -653,7 +660,7 @@
total = total * ((100 - discount)/100); total = total * ((100 - discount)/100);
} }
return total > 0 ? formatMoney(total) : ''; return total > 0 ? formatMoney(total, self.currency_id()) : '';
}); });
self.onDragged = function(item) { self.onDragged = function(item) {
@ -675,6 +682,7 @@
self.country_id = ko.observable(''); self.country_id = ko.observable('');
self.client_size_id = ko.observable(''); self.client_size_id = ko.observable('');
self.client_industry_id = ko.observable(''); self.client_industry_id = ko.observable('');
self.currency_id = ko.observable('');
self.website = ko.observable(''); self.website = ko.observable('');
self.contacts = ko.observableArray(); self.contacts = ko.observableArray();
@ -778,7 +786,7 @@
this.qty = ko.observable(); this.qty = ko.observable();
this.tax = ko.observable(); this.tax = ko.observable();
this.actionsVisible = ko.observable(false); this.actionsVisible = ko.observable(false);
this.prettyQty = ko.computed({ this.prettyQty = ko.computed({
read: function () { read: function () {
return this.qty() ? parseFloat(this.qty()) : ''; return this.qty() ? parseFloat(this.qty()) : '';
@ -791,6 +799,7 @@
if (data) { if (data) {
ko.mapping.fromJS(data, {}, this); ko.mapping.fromJS(data, {}, this);
if (this.cost()) this.cost(formatMoney(this.cost(), model.currency_id(), true));
} }
for (var i=0; i<model.tax_rates().length; i++) { for (var i=0; i<model.tax_rates().length; i++) {
@ -839,7 +848,7 @@
this.total = ko.computed(function() { this.total = ko.computed(function() {
var total = self.rawTotal(); var total = self.rawTotal();
return total ? formatMoney(total) : ''; return total ? formatMoney(total, model.currency_id()) : '';
}); });
this.hideActions = function() { this.hideActions = function() {

View File

@ -29,6 +29,8 @@
{{ Former::select('client')->addOption('', '')->addGroupClass('client-select') }} {{ Former::select('client')->addOption('', '')->addGroupClass('client-select') }}
{{ Former::select('invoice')->addOption('', '')->addGroupClass('invoice-select') }} {{ Former::select('invoice')->addOption('', '')->addGroupClass('invoice-select') }}
{{ Former::select('currency_id')->addOption('','')->label('Currency')
->fromQuery($currencies, 'name', 'id')->select(Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY)) }}
{{ Former::text('amount') }} {{ Former::text('amount') }}
{{ Former::text('payment_date')->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }} {{ Former::text('payment_date')->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }}
@ -107,7 +109,7 @@
var list = clientId ? (invoiceMap.hasOwnProperty(clientId) ? invoiceMap[clientId] : []) : invoices; var list = clientId ? (invoiceMap.hasOwnProperty(clientId) ? invoiceMap[clientId] : []) : invoices;
for (var i=0; i<list.length; i++) { for (var i=0; i<list.length; i++) {
var invoice = list[i]; var invoice = list[i];
$invoiceCombobox.append(new Option(invoice.invoice_number + ' - ' + invoice.invoice_date + ' - ' + invoice.client.name, invoice.public_id)); $invoiceCombobox.append(new Option(invoice.invoice_number + ' - ' + invoice.client.name, invoice.public_id));
} }
$('select#invoice').combobox('refresh'); $('select#invoice').combobox('refresh');
}).trigger('change'); }).trigger('change');
@ -122,6 +124,8 @@
}); });
$input.combobox(); $input.combobox();
$('#currency_id').combobox();
$('#payment_date').datepicker({ $('#payment_date').datepicker({
autoclose: true, autoclose: true,
todayHighlight: true todayHighlight: true

View File

@ -1,20 +1,20 @@
/* /*
body > div.container { body > div.container {
min-height: 600px; min-height: 600px;
} }
*/ */
label.checkbox, label.checkbox,
label.control-label { label.control-label {
font-weight: normal !important; font-weight: normal !important;
} }
div.panel { div.panel {
padding-left: 0px !important; padding-left: 0px !important;
padding-right: 0px !important; padding-right: 0px !important;
} }
.form-actions { .form-actions {
@ -23,57 +23,52 @@ div.panel {
text-align: center; text-align: center;
} }
.sides-padded {
margin-left: 8px !important;
margin-right: 8px !important;
}
/* /*
.form-horizontal { .form-horizontal {
max-width: 750px; max-width: 750px;
margin: 0 auto; margin: 0 auto;
} }
*/ */
/* /*
.form-group { .form-group {
width: 50%; width: 50%;
margin: 0 auto; margin: 0 auto;
} }
*/ */
/* DataTables and BootStrap */ /* DataTables and BootStrap */
.dataTables_wrapper { .dataTables_wrapper {
padding-top: 16px; padding-top: 16px;
} }
table.table thead > tr > th { table.table thead > tr > th {
border-bottom-width: 0px; border-bottom-width: 0px;
} }
#DataTables_Table_0_length label { #DataTables_Table_0_length label {
font-weight: normal; font-weight: normal;
padding-bottom: 10px; padding-bottom: 10px;
} }
div.dataTables_paginate.paging_bootstrap { div.dataTables_paginate.paging_bootstrap {
margin-top: -30px; margin-top: -30px;
} }
/* /*
table.table tbody tr.odd { table.table tbody tr.odd {
background-color: #f9f9f9; background-color: #f9f9f9;
} }
table.table tbody tr:hover { table.table tbody tr:hover {
background-color: #f0f0f0 !important; background-color: #f0f0f0 !important;
} }
*/ */
/* table sorting indicators */ /* table sorting indicators */
/*table.table thead .sorting { background: url('images/sort_both.png') no-repeat center right; }*/ /*table.table thead .sorting { background: url('images/sort_both.png') no-repeat center right; }*/
/*
table.table thead .sorting, table.table thead .sorting,
table.table thead .sorting_asc, table.table thead .sorting_asc,
table.table thead .sorting_desc, table.table thead .sorting_desc,
@ -83,14 +78,13 @@ table.table thead .sorting_desc_disabled {
*cursor: hand; *cursor: hand;
} }
table.table thead .sorting_asc { background: url('images/sort_asc.png') no-repeat center right; } table.table thead .sorting_asc { background: url('../images/sort_asc.png') no-repeat center right; }
table.table thead .sorting_desc { background: url('images/sort_desc.png') no-repeat center right; } table.table thead .sorting_desc { background: url('../images/sort_desc.png') no-repeat center right; }
table.table thead .sorting_asc_disabled { background: url('images/sort_asc_disabled.png') no-repeat center right; } table.table thead .sorting_asc_disabled { background: url('../images/sort_asc_disabled.png') no-repeat center right; }
table.table thead .sorting_desc_disabled { background: url('images/sort_desc_disabled.png') no-repeat center right; } table.table thead .sorting_desc_disabled { background: url('../images/sort_desc_disabled.png') no-repeat center right; }
*/
/* Hover nav */ /* Hover nav */
.sidebar-nav { .sidebar-nav {
@ -137,44 +131,44 @@ table.table thead .sorting_desc_disabled { background: url('images/sort_desc_dis
.invoice-table { .invoice-table {
border-style: none !important; border-style: none !important;
} }
/* /*
table.invoice-table tbody tr:hover { table.invoice-table tbody tr:hover {
background-color: #FFFFFF !important; background-color: #FFFFFF !important;
} }
*/ */
.invoice-table td { .invoice-table td {
padding: 2px !important; padding: 2px !important;
} }
.invoice-table td input, .invoice-table td input,
.invoice-table td textarea { .invoice-table td textarea {
border: none !important; border: none !important;
width: 100%; width: 100%;
} }
.invoice-table th { .invoice-table th {
border-top: 1px solid #ddd !important; border-top: 1px solid #ddd !important;
} }
.invoice-table td.hide-border, .invoice-table td.hide-border,
.invoice-table th.hide-border { .invoice-table th.hide-border {
border-style: none !important; border-style: none !important;
} }
.invoice-table td.td-icon { .invoice-table td.td-icon {
vertical-align: middle !important; vertical-align: middle !important;
} }
.fa-bars { .fa-bars {
cursor: move !important; cursor: move !important;
} }
.closer-row { .closer-row {
margin-bottom: 2px; margin-bottom: 2px;
} }
@ -183,5 +177,5 @@ table.invoice-table tbody tr:hover {
-webkit-transition: width 0.5s ease, margin 0.5s ease; -webkit-transition: width 0.5s ease, margin 0.5s ease;
-moz-transition: width 0.5s ease, margin 0.5s ease; -moz-transition: width 0.5s ease, margin 0.5s ease;
-o-transition: width 0.5s ease, margin 0.5s ease; -o-transition: width 0.5s ease, margin 0.5s ease;
transition: width 0.5s ease, margin 0.5s ease; transition: width 0.5s ease, margin 0.5s ease;
} }

412
public/js/accounting.js Executable file
View File

@ -0,0 +1,412 @@
/*!
* accounting.js v0.3.2
* Copyright 2011, Joss Crowcroft
*
* Freely distributable under the MIT license.
* Portions of accounting.js are inspired or borrowed from underscore.js
*
* Full details and documentation:
* http://josscrowcroft.github.com/accounting.js/
*/
(function(root, undefined) {
/* --- Setup --- */
// Create the local library object, to be exported or referenced globally later
var lib = {};
// Current version
lib.version = '0.3.2';
/* --- Exposed settings --- */
// The library's settings configuration object. Contains default parameters for
// currency and number formatting
lib.settings = {
currency: {
symbol : "$", // default currency symbol is '$'
format : "%s%v", // controls output: %s = symbol, %v = value (can be object, see docs)
decimal : ".", // decimal point separator
thousand : ",", // thousands separator
precision : 2, // decimal places
grouping : 3 // digit grouping (not implemented yet)
},
number: {
precision : 0, // default precision on numbers is 0
grouping : 3, // digit grouping (not implemented yet)
thousand : ",",
decimal : "."
}
};
/* --- Internal Helper Methods --- */
// Store reference to possibly-available ECMAScript 5 methods for later
var nativeMap = Array.prototype.map,
nativeIsArray = Array.isArray,
toString = Object.prototype.toString;
/**
* Tests whether supplied parameter is a string
* from underscore.js
*/
function isString(obj) {
return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
}
/**
* Tests whether supplied parameter is a string
* from underscore.js, delegates to ECMA5's native Array.isArray
*/
function isArray(obj) {
return nativeIsArray ? nativeIsArray(obj) : toString.call(obj) === '[object Array]';
}
/**
* Tests whether supplied parameter is a true object
*/
function isObject(obj) {
return obj && toString.call(obj) === '[object Object]';
}
/**
* Extends an object with a defaults object, similar to underscore's _.defaults
*
* Used for abstracting parameter handling from API methods
*/
function defaults(object, defs) {
var key;
object = object || {};
defs = defs || {};
// Iterate over object non-prototype properties:
for (key in defs) {
if (defs.hasOwnProperty(key)) {
// Replace values with defaults only if undefined (allow empty/zero values):
if (object[key] == null) object[key] = defs[key];
}
}
return object;
}
/**
* Implementation of `Array.map()` for iteration loops
*
* Returns a new Array as a result of calling `iterator` on each array value.
* Defers to native Array.map if available
*/
function map(obj, iterator, context) {
var results = [], i, j;
if (!obj) return results;
// Use native .map method if it exists:
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
// Fallback for native .map:
for (i = 0, j = obj.length; i < j; i++ ) {
results[i] = iterator.call(context, obj[i], i, obj);
}
return results;
}
/**
* Check and normalise the value of precision (must be positive integer)
*/
function checkPrecision(val, base) {
val = Math.round(Math.abs(val));
return isNaN(val)? base : val;
}
/**
* Parses a format string or object and returns format obj for use in rendering
*
* `format` is either a string with the default (positive) format, or object
* containing `pos` (required), `neg` and `zero` values (or a function returning
* either a string or object)
*
* Either string or format.pos must contain "%v" (value) to be valid
*/
function checkCurrencyFormat(format) {
var defaults = lib.settings.currency.format;
// Allow function as format parameter (should return string or object):
if ( typeof format === "function" ) format = format();
// Format can be a string, in which case `value` ("%v") must be present:
if ( isString( format ) && format.match("%v") ) {
// Create and return positive, negative and zero formats:
return {
pos : format,
neg : format.replace("-", "").replace("%v", "-%v"),
zero : format
};
// If no format, or object is missing valid positive value, use defaults:
} else if ( !format || !format.pos || !format.pos.match("%v") ) {
// If defaults is a string, casts it to an object for faster checking next time:
return ( !isString( defaults ) ) ? defaults : lib.settings.currency.format = {
pos : defaults,
neg : defaults.replace("%v", "-%v"),
zero : defaults
};
}
// Otherwise, assume format was fine:
return format;
}
/* --- API Methods --- */
/**
* Takes a string/array of strings, removes all formatting/cruft and returns the raw float value
* alias: accounting.`parse(string)`
*
* Decimal must be included in the regular expression to match floats (defaults to
* accounting.settings.number.decimal), so if the number uses a non-standard decimal
* separator, provide it as the second argument.
*
* Also matches bracketed negatives (eg. "$ (1.99)" => -1.99)
*
* Doesn't throw any errors (`NaN`s become 0) but this may change in future
*/
var unformat = lib.unformat = lib.parse = function(value, decimal) {
// Recursively unformat arrays:
if (isArray(value)) {
return map(value, function(val) {
return unformat(val, decimal);
});
}
// Fails silently (need decent errors):
value = value || 0;
// Return the value as-is if it's already a number:
if (typeof value === "number") return value;
// Default decimal point comes from settings, but could be set to eg. "," in opts:
decimal = decimal || lib.settings.number.decimal;
// Build regex to strip out everything except digits, decimal point and minus sign:
var regex = new RegExp("[^0-9-" + decimal + "]", ["g"]),
unformatted = parseFloat(
("" + value)
.replace(/\((.*)\)/, "-$1") // replace bracketed values with negatives
.replace(regex, '') // strip out any cruft
.replace(decimal, '.') // make sure decimal point is standard
);
// This will fail silently which may cause trouble, let's wait and see:
return !isNaN(unformatted) ? unformatted : 0;
};
/**
* Implementation of toFixed() that treats floats more like decimals
*
* Fixes binary rounding issues (eg. (0.615).toFixed(2) === "0.61") that present
* problems for accounting- and finance-related software.
*/
var toFixed = lib.toFixed = function(value, precision) {
precision = checkPrecision(precision, lib.settings.number.precision);
var power = Math.pow(10, precision);
// Multiply up by precision, round accurately, then divide and use native toFixed():
return (Math.round(lib.unformat(value) * power) / power).toFixed(precision);
};
/**
* Format a number, with comma-separated thousands and custom precision/decimal places
*
* Localise by overriding the precision and thousand / decimal separators
* 2nd parameter `precision` can be an object matching `settings.number`
*/
var formatNumber = lib.formatNumber = function(number, precision, thousand, decimal) {
// Resursively format arrays:
if (isArray(number)) {
return map(number, function(val) {
return formatNumber(val, precision, thousand, decimal);
});
}
// Clean up number:
number = unformat(number);
// Build options object from second param (if object) or all params, extending defaults:
var opts = defaults(
(isObject(precision) ? precision : {
precision : precision,
thousand : thousand,
decimal : decimal
}),
lib.settings.number
),
// Clean up precision
usePrecision = checkPrecision(opts.precision),
// Do some calc:
negative = number < 0 ? "-" : "",
base = parseInt(toFixed(Math.abs(number || 0), usePrecision), 10) + "",
mod = base.length > 3 ? base.length % 3 : 0;
// Format the number:
return negative + (mod ? base.substr(0, mod) + opts.thousand : "") + base.substr(mod).replace(/(\d{3})(?=\d)/g, "$1" + opts.thousand) + (usePrecision ? opts.decimal + toFixed(Math.abs(number), usePrecision).split('.')[1] : "");
};
/**
* Format a number into currency
*
* Usage: accounting.formatMoney(number, symbol, precision, thousandsSep, decimalSep, format)
* defaults: (0, "$", 2, ",", ".", "%s%v")
*
* Localise by overriding the symbol, precision, thousand / decimal separators and format
* Second param can be an object matching `settings.currency` which is the easiest way.
*
* To do: tidy up the parameters
*/
var formatMoney = lib.formatMoney = function(number, symbol, precision, thousand, decimal, format) {
// Resursively format arrays:
if (isArray(number)) {
return map(number, function(val){
return formatMoney(val, symbol, precision, thousand, decimal, format);
});
}
// Clean up number:
number = unformat(number);
// Build options object from second param (if object) or all params, extending defaults:
var opts = defaults(
(isObject(symbol) ? symbol : {
symbol : symbol,
precision : precision,
thousand : thousand,
decimal : decimal,
format : format
}),
lib.settings.currency
),
// Check format (returns object with pos, neg and zero):
formats = checkCurrencyFormat(opts.format),
// Choose which format to use for this value:
useFormat = number > 0 ? formats.pos : number < 0 ? formats.neg : formats.zero;
// Return with currency symbol added:
return useFormat.replace('%s', opts.symbol).replace('%v', formatNumber(Math.abs(number), checkPrecision(opts.precision), opts.thousand, opts.decimal));
};
/**
* Format a list of numbers into an accounting column, padding with whitespace
* to line up currency symbols, thousand separators and decimals places
*
* List should be an array of numbers
* Second parameter can be an object containing keys that match the params
*
* Returns array of accouting-formatted number strings of same length
*
* NB: `white-space:pre` CSS rule is required on the list container to prevent
* browsers from collapsing the whitespace in the output strings.
*/
lib.formatColumn = function(list, symbol, precision, thousand, decimal, format) {
if (!list) return [];
// Build options object from second param (if object) or all params, extending defaults:
var opts = defaults(
(isObject(symbol) ? symbol : {
symbol : symbol,
precision : precision,
thousand : thousand,
decimal : decimal,
format : format
}),
lib.settings.currency
),
// Check format (returns object with pos, neg and zero), only need pos for now:
formats = checkCurrencyFormat(opts.format),
// Whether to pad at start of string or after currency symbol:
padAfterSymbol = formats.pos.indexOf("%s") < formats.pos.indexOf("%v") ? true : false,
// Store value for the length of the longest string in the column:
maxLength = 0,
// Format the list according to options, store the length of the longest string:
formatted = map(list, function(val, i) {
if (isArray(val)) {
// Recursively format columns if list is a multi-dimensional array:
return lib.formatColumn(val, opts);
} else {
// Clean up the value
val = unformat(val);
// Choose which format to use for this value (pos, neg or zero):
var useFormat = val > 0 ? formats.pos : val < 0 ? formats.neg : formats.zero,
// Format this value, push into formatted list and save the length:
fVal = useFormat.replace('%s', opts.symbol).replace('%v', formatNumber(Math.abs(val), checkPrecision(opts.precision), opts.thousand, opts.decimal));
if (fVal.length > maxLength) maxLength = fVal.length;
return fVal;
}
});
// Pad each number in the list and send back the column of numbers:
return map(formatted, function(val, i) {
// Only if this is a string (not a nested array, which would have already been padded):
if (isString(val) && val.length < maxLength) {
// Depending on symbol position, pad after symbol or at index 0:
return padAfterSymbol ? val.replace(opts.symbol, opts.symbol+(new Array(maxLength - val.length + 1).join(" "))) : (new Array(maxLength - val.length + 1).join(" ")) + val;
}
return val;
});
};
/* --- Module Definition --- */
// Export accounting for CommonJS. If being loaded as an AMD module, define it as such.
// Otherwise, just add `accounting` to the global object
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = lib;
}
exports.accounting = lib;
} else if (typeof define === 'function' && define.amd) {
// Return the library as an AMD module:
define([], function() {
return lib;
});
} else {
// Use accounting.noConflict to restore `accounting` back to its original value.
// Returns a reference to the library's `accounting` object;
// e.g. `var numbers = accounting.noConflict();`
lib.noConflict = (function(oldAccounting) {
return function() {
// Reset the value of the root's `accounting` variable:
root.accounting = oldAccounting;
// Delete the noConflict method:
lib.noConflict = undefined;
// Return reference to the library to re-assign it:
return lib;
};
})(root.accounting);
// Declare `fx` on the root (global/window) object:
root['accounting'] = lib;
}
// Root will be `window` in browser or `global` on the server:
}(this));

View File

@ -1,4 +1,5 @@
function generatePDF(invoice) { function generatePDF(invoice) {
var currencyId = invoice.currency_id;
var invoiceNumber = invoice.invoice_number; var invoiceNumber = invoice.invoice_number;
var issuedOn = invoice.invoice_date ? invoice.invoice_date : ''; var issuedOn = invoice.invoice_date ? invoice.invoice_date : '';
var amount = '$0.00'; var amount = '$0.00';
@ -128,7 +129,7 @@ function generatePDF(invoice) {
for (var i=0; i<invoice.invoice_items.length; i++) { for (var i=0; i<invoice.invoice_items.length; i++) {
var item = invoice.invoice_items[i]; var item = invoice.invoice_items[i];
var cost = formatNumber(item.cost); var cost = formatMoney(item.cost, currencyId, true);
var qty = item.qty ? parseFloat(item.qty) + '' : ''; var qty = item.qty ? parseFloat(item.qty) + '' : '';
var notes = item.notes; var notes = item.notes;
var productKey = item.product_key; var productKey = item.product_key;
@ -151,7 +152,7 @@ function generatePDF(invoice) {
if (lineTotal) { if (lineTotal) {
total += lineTotal; total += lineTotal;
} }
lineTotal = formatNumber(lineTotal); lineTotal = formatMoney(lineTotal, currencyId, true);
var costX = unitCostRight - (doc.getStringUnitWidth(cost) * doc.internal.getFontSize()); var costX = unitCostRight - (doc.getStringUnitWidth(cost) * doc.internal.getFontSize());
var qtyX = qtyRight - (doc.getStringUnitWidth(qty) * doc.internal.getFontSize()); var qtyX = qtyRight - (doc.getStringUnitWidth(qty) * doc.internal.getFontSize());
@ -181,7 +182,7 @@ function generatePDF(invoice) {
x += 16; x += 16;
doc.text(footerLeft, x, 'Subtotal'); doc.text(footerLeft, x, 'Subtotal');
var total = formatNumber(total); var total = formatMoney(total, currencyId, true);
var totalX = headerRight - (doc.getStringUnitWidth(total) * doc.internal.getFontSize()); var totalX = headerRight - (doc.getStringUnitWidth(total) * doc.internal.getFontSize());
doc.text(totalX, x, total); doc.text(totalX, x, total);
@ -189,7 +190,7 @@ function generatePDF(invoice) {
x += 16; x += 16;
doc.text(footerLeft, x, 'Discount'); doc.text(footerLeft, x, 'Discount');
var discount = formatNumber(total * (invoice.discount/100)); var discount = formatMoney(total * (invoice.discount/100), currencyId, true);
total -= discount; total -= discount;
var discountX = headerRight - (doc.getStringUnitWidth(discount) * doc.internal.getFontSize()); var discountX = headerRight - (doc.getStringUnitWidth(discount) * doc.internal.getFontSize());
doc.text(discountX, x, discount); doc.text(discountX, x, discount);
@ -197,7 +198,7 @@ function generatePDF(invoice) {
x += 16; x += 16;
doc.text(footerLeft, x, 'Paid to Date'); doc.text(footerLeft, x, 'Paid to Date');
var paid = formatNumber(0); var paid = formatMoney(0, currencyId, true);
var paidX = headerRight - (doc.getStringUnitWidth(paid) * doc.internal.getFontSize()); var paidX = headerRight - (doc.getStringUnitWidth(paid) * doc.internal.getFontSize());
doc.text(paidX, x, paid); doc.text(paidX, x, paid);
@ -206,7 +207,7 @@ function generatePDF(invoice) {
doc.setFontType("bold"); doc.setFontType("bold");
doc.text(footerLeft, x, 'Total'); doc.text(footerLeft, x, 'Total');
var total = formatMoney(total); var total = formatMoney(total, currencyId);
var totalX = headerRight - (doc.getStringUnitWidth(total) * doc.internal.getFontSize()); var totalX = headerRight - (doc.getStringUnitWidth(total) * doc.internal.getFontSize());
doc.text(totalX, x, total); doc.text(totalX, x, total);
@ -345,26 +346,6 @@ function getQuarter(offset) {
return 'Q' + quarter; return 'Q' + quarter;
} }
function formatMoney(num) {
num = parseFloat(num);
if (!num) return '$0.00';
return '$' + formatNumber(num);
}
function formatNumber(num) {
num = parseFloat(num);
if (!num) num = 0;
var p = num.toFixed(2).split(".");
return p[0].split("").reverse().reduce(function(acc, num, i, orig) {
return num + (i && !(i % 3) ? "," : "") + acc;
}, "") + "." + p[1];
}
/* Set the defaults for DataTables initialisation */ /* Set the defaults for DataTables initialisation */
$.extend( true, $.fn.dataTable.defaults, { $.extend( true, $.fn.dataTable.defaults, {
"sDom": "t<'row-fluid'<'span6'i><'span6'p>>", "sDom": "t<'row-fluid'<'span6'i><'span6'p>>",