1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-13 22:54:25 +01:00

Merge branch 'ninja'

This commit is contained in:
trigras 2015-08-31 15:24:40 +03:00
commit d1e0518fb7
141 changed files with 13731 additions and 3250 deletions

View File

@ -2,7 +2,7 @@ APP_ENV=production
APP_DEBUG=false APP_DEBUG=false
APP_URL=http://ninja.dev APP_URL=http://ninja.dev
APP_CIPHER=rijndael-128 APP_CIPHER=rijndael-128
APP_KEY APP_KEY=SomeRandomString
APP_TIMEZONE APP_TIMEZONE
DB_TYPE=mysql DB_TYPE=mysql
@ -19,5 +19,3 @@ MAIL_USERNAME
MAIL_FROM_ADDRESS MAIL_FROM_ADDRESS
MAIL_FROM_NAME MAIL_FROM_NAME
MAIL_PASSWORD MAIL_PASSWORD
ALLOW_NEW_ACCOUNTS

24
.gitignore vendored
View File

@ -13,18 +13,20 @@
/bootstrap/environment.php /bootstrap/environment.php
/vendor /vendor
/node_modules /node_modules
.env
/.DS_Store /.DS_Store
/Thumbs.db /Thumbs.db
.env.development.php /.env
.env.php /.env.development.php
.idea /.env.php
.project
error_log /error_log
public/error_log /auth.json
/public/error_log
/ninja.sublime-project /ninja.sublime-project
/ninja.sublime-workspace /ninja.sublime-workspace
auth.json /.phpstorm.meta.php
/_ide_helper.php
.phpstorm.meta.php /.idea
_ide_helper.php /.project
tests/_output/

View File

@ -7,6 +7,7 @@ use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use App\Ninja\Mailers\ContactMailer as Mailer; use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\InvoiceRepository;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\InvoiceItem; use App\Models\InvoiceItem;
use App\Models\Invitation; use App\Models\Invitation;
@ -16,12 +17,14 @@ class SendRecurringInvoices extends Command
protected $name = 'ninja:send-invoices'; protected $name = 'ninja:send-invoices';
protected $description = 'Send recurring invoices'; protected $description = 'Send recurring invoices';
protected $mailer; protected $mailer;
protected $invoiceRepo;
public function __construct(Mailer $mailer) public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo)
{ {
parent::__construct(); parent::__construct();
$this->mailer = $mailer; $this->mailer = $mailer;
$this->invoiceRepo = $invoiceRepo;
} }
public function fire() public function fire()
@ -34,74 +37,14 @@ class SendRecurringInvoices extends Command
$this->info(count($invoices).' recurring invoice(s) found'); $this->info(count($invoices).' recurring invoice(s) found');
foreach ($invoices as $recurInvoice) { foreach ($invoices as $recurInvoice) {
if ($recurInvoice->client->deleted_at) { $this->info('Processing Invoice '.$recurInvoice->id.' - Should send '.($recurInvoice->shouldSendToday() ? 'YES' : 'NO'));
continue;
$invoice = $this->invoiceRepo->createRecurringInvoice($recurInvoice);
if ($invoice) {
$recurInvoice->account->loadLocalizationSettings();
$this->mailer->sendInvoice($invoice);
} }
if (!$recurInvoice->user->confirmed) {
continue;
}
$this->info('Processing Invoice '.$recurInvoice->id.' - Should send '.($recurInvoice->shouldSendToday() ? 'YES' : 'NO'));
if (!$recurInvoice->shouldSendToday()) {
continue;
}
$invoice = Invoice::createNew($recurInvoice);
$invoice->client_id = $recurInvoice->client_id;
$invoice->recurring_invoice_id = $recurInvoice->id;
$invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber(false, 'R');
$invoice->amount = $recurInvoice->amount;
$invoice->balance = $recurInvoice->amount;
$invoice->invoice_date = date_create()->format('Y-m-d');
$invoice->discount = $recurInvoice->discount;
$invoice->po_number = $recurInvoice->po_number;
$invoice->public_notes = Utils::processVariables($recurInvoice->public_notes);
$invoice->terms = Utils::processVariables($recurInvoice->terms);
$invoice->invoice_footer = Utils::processVariables($recurInvoice->invoice_footer);
$invoice->tax_name = $recurInvoice->tax_name;
$invoice->tax_rate = $recurInvoice->tax_rate;
$invoice->invoice_design_id = $recurInvoice->invoice_design_id;
$invoice->custom_value1 = $recurInvoice->custom_value1;
$invoice->custom_value2 = $recurInvoice->custom_value2;
$invoice->custom_taxes1 = $recurInvoice->custom_taxes1;
$invoice->custom_taxes2 = $recurInvoice->custom_taxes2;
$invoice->is_amount_discount = $recurInvoice->is_amount_discount;
if ($invoice->client->payment_terms != 0) {
$days = $invoice->client->payment_terms;
if ($days == -1) {
$days = 0;
}
$invoice->due_date = date_create()->modify($days.' day')->format('Y-m-d');
}
$invoice->save();
foreach ($recurInvoice->invoice_items as $recurItem) {
$item = InvoiceItem::createNew($recurItem);
$item->product_id = $recurItem->product_id;
$item->qty = $recurItem->qty;
$item->cost = $recurItem->cost;
$item->notes = Utils::processVariables($recurItem->notes);
$item->product_key = Utils::processVariables($recurItem->product_key);
$item->tax_name = $recurItem->tax_name;
$item->tax_rate = $recurItem->tax_rate;
$invoice->invoice_items()->save($item);
}
foreach ($recurInvoice->invitations as $recurInvitation) {
$invitation = Invitation::createNew($recurInvitation);
$invitation->contact_id = $recurInvitation->contact_id;
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
$invoice->invitations()->save($invitation);
}
$this->mailer->sendInvoice($invoice);
$recurInvoice->last_sent_date = Carbon::now()->toDateTimeString();
$recurInvoice->save();
} }
$this->info('Done'); $this->info('Done');

View File

@ -40,7 +40,6 @@ use App\Ninja\Repositories\AccountRepository;
use App\Ninja\Mailers\UserMailer; use App\Ninja\Mailers\UserMailer;
use App\Ninja\Mailers\ContactMailer; use App\Ninja\Mailers\ContactMailer;
use App\Events\UserLoggedIn; use App\Events\UserLoggedIn;
use App\Events\UserSettingsChanged;
class AccountController extends BaseController class AccountController extends BaseController
{ {
@ -75,18 +74,18 @@ class AccountController extends BaseController
public function getStarted() public function getStarted()
{ {
if (Auth::check()) {
return Redirect::to('invoices/create');
}
if (!Utils::isNinja() && !Utils::allowNewAccounts() && Account::count() > 0) {
return Redirect::to('/login');
}
$user = false; $user = false;
$guestKey = Input::get('guest_key'); // local storage key to login until registered $guestKey = Input::get('guest_key'); // local storage key to login until registered
$prevUserId = Session::pull(PREV_USER_ID); // last user id used to link to new account $prevUserId = Session::pull(PREV_USER_ID); // last user id used to link to new account
if (Auth::check()) {
return Redirect::to('invoices/create');
}
if (!Utils::isNinja() && (Account::count() > 0 && !$prevUserId)) {
return Redirect::to('/login');
}
if ($guestKey && !$prevUserId) { if ($guestKey && !$prevUserId) {
$user = User::where('password', '=', $guestKey)->first(); $user = User::where('password', '=', $guestKey)->first();
@ -110,7 +109,8 @@ class AccountController extends BaseController
Auth::login($user, true); Auth::login($user, true);
Event::fire(new UserLoggedIn()); Event::fire(new UserLoggedIn());
return Redirect::to('invoices/create')->with('sign_up', Input::get('sign_up')); $redirectTo = Input::get('redirect_to', 'invoices/create');
return Redirect::to($redirectTo)->with('sign_up', Input::get('sign_up'));
} }
public function enableProPlan() public function enableProPlan()
@ -149,6 +149,7 @@ class AccountController extends BaseController
public function showSection($section = ACCOUNT_DETAILS, $subSection = false) public function showSection($section = ACCOUNT_DETAILS, $subSection = false)
{ {
if ($section == ACCOUNT_DETAILS) { if ($section == ACCOUNT_DETAILS) {
$primaryUser = Auth::user()->account->users()->orderBy('id')->first();
$data = [ $data = [
'account' => Account::with('users')->findOrFail(Auth::user()->account_id), 'account' => Account::with('users')->findOrFail(Auth::user()->account_id),
'countries' => Cache::get('countries'), 'countries' => Cache::get('countries'),
@ -159,8 +160,9 @@ class AccountController extends BaseController
'datetimeFormats' => Cache::get('datetimeFormats'), 'datetimeFormats' => Cache::get('datetimeFormats'),
'currencies' => Cache::get('currencies'), 'currencies' => Cache::get('currencies'),
'languages' => Cache::get('languages'), 'languages' => Cache::get('languages'),
'showUser' => Auth::user()->id === Auth::user()->account->users()->first()->id, 'showUser' => Auth::user()->id === $primaryUser->id,
'title' => trans('texts.company_details'), 'title' => trans('texts.company_details'),
'primaryUser' => $primaryUser,
]; ];
return View::make('accounts.details', $data); return View::make('accounts.details', $data);
@ -188,16 +190,18 @@ class AccountController extends BaseController
} elseif ($section == ACCOUNT_IMPORT_EXPORT) { } elseif ($section == ACCOUNT_IMPORT_EXPORT) {
return View::make('accounts.import_export', ['title' => trans('texts.import_export')]); return View::make('accounts.import_export', ['title' => trans('texts.import_export')]);
} elseif ($section == ACCOUNT_ADVANCED_SETTINGS) { } elseif ($section == ACCOUNT_ADVANCED_SETTINGS) {
$account = Auth::user()->account; $account = Auth::user()->account->load('country');
$data = [ $data = [
'account' => $account, 'account' => $account,
'feature' => $subSection, 'feature' => $subSection,
'title' => trans('texts.invoice_settings'), 'title' => trans('texts.invoice_settings'),
]; ];
if ($subSection == ACCOUNT_INVOICE_DESIGN) { if ($subSection == ACCOUNT_INVOICE_DESIGN
|| $subSection == ACCOUNT_CUSTOMIZE_DESIGN) {
$invoice = new stdClass(); $invoice = new stdClass();
$client = new stdClass(); $client = new stdClass();
$contact = new stdClass();
$invoiceItem = new stdClass(); $invoiceItem = new stdClass();
$client->name = 'Sample Client'; $client->name = 'Sample Client';
@ -208,11 +212,17 @@ class AccountController extends BaseController
$client->work_phone = ''; $client->work_phone = '';
$client->work_email = ''; $client->work_email = '';
$invoice->invoice_number = Auth::user()->account->getNextInvoiceNumber(); $invoice->invoice_number = $account->getNextInvoiceNumber();
$invoice->invoice_date = date_create()->format('Y-m-d'); $invoice->invoice_date = Utils::fromSqlDate(date('Y-m-d'));
$invoice->account = json_decode(Auth::user()->account->toJson()); $invoice->account = json_decode($account->toJson());
$invoice->amount = $invoice->balance = 100; $invoice->amount = $invoice->balance = 100;
$invoice->terms = trim($account->invoice_terms);
$invoice->invoice_footer = trim($account->invoice_footer);
$contact->email = 'contact@gmail.com';
$client->contacts = [$contact];
$invoiceItem->cost = 100; $invoiceItem->cost = 100;
$invoiceItem->qty = 1; $invoiceItem->qty = 1;
$invoiceItem->notes = 'Notes'; $invoiceItem->notes = 'Notes';
@ -221,15 +231,28 @@ class AccountController extends BaseController
$invoice->client = $client; $invoice->client = $client;
$invoice->invoice_items = [$invoiceItem]; $invoice->invoice_items = [$invoiceItem];
$data['account'] = $account;
$data['invoice'] = $invoice; $data['invoice'] = $invoice;
$data['invoiceDesigns'] = InvoiceDesign::availableDesigns();
$data['invoiceLabels'] = json_decode($account->invoice_labels) ?: []; $data['invoiceLabels'] = json_decode($account->invoice_labels) ?: [];
$data['title'] = trans('texts.invoice_design'); $data['title'] = trans('texts.invoice_design');
$data['invoiceDesigns'] = InvoiceDesign::getDesigns();
$design = false;
foreach ($data['invoiceDesigns'] as $item) {
if ($item->id == $account->invoice_design_id) {
$design = $item->javascript;
break;
}
}
if ($subSection == ACCOUNT_CUSTOMIZE_DESIGN) {
$data['customDesign'] = ($account->custom_design && !$design) ? $account->custom_design : $design;
}
} else if ($subSection == ACCOUNT_EMAIL_TEMPLATES) { } else if ($subSection == ACCOUNT_EMAIL_TEMPLATES) {
$data['invoiceEmail'] = $account->getEmailTemplate(ENTITY_INVOICE); $data['invoiceEmail'] = $account->getEmailTemplate(ENTITY_INVOICE);
$data['quoteEmail'] = $account->getEmailTemplate(ENTITY_QUOTE); $data['quoteEmail'] = $account->getEmailTemplate(ENTITY_QUOTE);
$data['paymentEmail'] = $account->getEmailTemplate(ENTITY_PAYMENT); $data['paymentEmail'] = $account->getEmailTemplate(ENTITY_PAYMENT);
$data['emailFooter'] = $account->getEmailFooter(); $data['emailFooter'] = $account->getEmailFooter();
$data['title'] = trans('texts.email_templates'); $data['title'] = trans('texts.email_templates');
} else if ($subSection == ACCOUNT_USER_MANAGEMENT) { } else if ($subSection == ACCOUNT_USER_MANAGEMENT) {
$data['title'] = trans('texts.users_and_tokens'); $data['title'] = trans('texts.users_and_tokens');
@ -263,6 +286,8 @@ class AccountController extends BaseController
return AccountController::saveInvoiceSettings(); return AccountController::saveInvoiceSettings();
} elseif ($subSection == ACCOUNT_INVOICE_DESIGN) { } elseif ($subSection == ACCOUNT_INVOICE_DESIGN) {
return AccountController::saveInvoiceDesign(); return AccountController::saveInvoiceDesign();
} elseif ($subSection == ACCOUNT_CUSTOMIZE_DESIGN) {
return AccountController::saveCustomizeDesign();
} elseif ($subSection == ACCOUNT_EMAIL_TEMPLATES) { } elseif ($subSection == ACCOUNT_EMAIL_TEMPLATES) {
return AccountController::saveEmailTemplates(); return AccountController::saveEmailTemplates();
} }
@ -271,6 +296,19 @@ class AccountController extends BaseController
} }
} }
private function saveCustomizeDesign() {
if (Auth::user()->account->isPro()) {
$account = Auth::user()->account;
$account->custom_design = Input::get('custom_design');
$account->invoice_design_id = CUSTOM_DESIGN;
$account->save();
Session::flash('message', trans('texts.updated_settings'));
}
return Redirect::to('company/advanced_settings/customize_design');
}
private function saveEmailTemplates() private function saveEmailTemplates()
{ {
if (Auth::user()->account->isPro()) { if (Auth::user()->account->isPro()) {
@ -322,7 +360,6 @@ class AccountController extends BaseController
$account->share_counter = Input::get('share_counter') ? true : false; $account->share_counter = Input::get('share_counter') ? true : false;
$account->pdf_email_attachment = Input::get('pdf_email_attachment') ? true : false; $account->pdf_email_attachment = Input::get('pdf_email_attachment') ? true : false;
$account->utf8_invoices = Input::get('utf8_invoices') ? true : false;
$account->auto_wrap = Input::get('auto_wrap') ? true : false; $account->auto_wrap = Input::get('auto_wrap') ? true : false;
if (!$account->share_counter) { if (!$account->share_counter) {
@ -596,9 +633,10 @@ class AccountController extends BaseController
{ {
$rules = array( $rules = array(
'name' => 'required', 'name' => 'required',
'logo' => 'sometimes|max:1024|mimes:jpeg,gif,png',
); );
$user = Auth::user()->account->users()->first(); $user = Auth::user()->account->users()->orderBy('id')->first();
if (Auth::user()->id === $user->id) { if (Auth::user()->id === $user->id) {
$rules['email'] = 'email|required|unique:users,email,'.$user->id.',id'; $rules['email'] = 'email|required|unique:users,email,'.$user->id.',id';
@ -647,6 +685,9 @@ class AccountController extends BaseController
$user->username = trim(Input::get('email')); $user->username = trim(Input::get('email'));
$user->email = trim(strtolower(Input::get('email'))); $user->email = trim(strtolower(Input::get('email')));
$user->phone = trim(Input::get('phone')); $user->phone = trim(Input::get('phone'));
if (Utils::isNinjaDev()) {
$user->dark_mode = Input::get('dark_mode') ? true : false;
}
$user->save(); $user->save();
} }
@ -654,21 +695,28 @@ class AccountController extends BaseController
if ($file = Input::file('logo')) { if ($file = Input::file('logo')) {
$path = Input::file('logo')->getRealPath(); $path = Input::file('logo')->getRealPath();
File::delete('logo/'.$account->account_key.'.jpg'); File::delete('logo/'.$account->account_key.'.jpg');
File::delete('logo/'.$account->account_key.'.png');
$image = Image::make($path);
$mimeType = $file->getMimeType(); $mimeType = $file->getMimeType();
if ($image->width() == 200 && $mimeType == 'image/jpeg') { if ($mimeType == 'image/jpeg') {
$file->move('logo/', $account->account_key . '.jpg'); $file->move('logo/', $account->account_key . '.jpg');
} else if ($mimeType == 'image/png') {
$file->move('logo/', $account->account_key . '.png');
} else { } else {
$image->resize(200, 120, function ($constraint) { if (extension_loaded('fileinfo')) {
$constraint->aspectRatio(); $image = Image::make($path);
}); $image->resize(200, 120, function ($constraint) {
Image::canvas($image->width(), $image->height(), '#FFFFFF')->insert($image)->save($account->getLogoPath()); $constraint->aspectRatio();
});
Image::canvas($image->width(), $image->height(), '#FFFFFF')
->insert($image)->save('logo/'.$account->account_key.'.jpg');
} else {
Session::flash('warning', 'Warning: To support gifs the fileinfo PHP extension needs to be enabled.');
}
} }
} }
Event::fire(new UserSettingsChanged());
Session::flash('message', trans('texts.updated_settings')); Session::flash('message', trans('texts.updated_settings'));
return Redirect::to('company/details'); return Redirect::to('company/details');
@ -678,6 +726,7 @@ class AccountController extends BaseController
public function removeLogo() public function removeLogo()
{ {
File::delete('logo/'.Auth::user()->account->account_key.'.jpg'); File::delete('logo/'.Auth::user()->account->account_key.'.jpg');
File::delete('logo/'.Auth::user()->account->account_key.'.png');
Session::flash('message', trans('texts.removed_logo')); Session::flash('message', trans('texts.removed_logo'));
@ -717,9 +766,9 @@ class AccountController extends BaseController
$user->username = $user->email; $user->username = $user->email;
$user->password = bcrypt(trim(Input::get('new_password'))); $user->password = bcrypt(trim(Input::get('new_password')));
$user->registered = true; $user->registered = true;
$user->save(); $user->save();
if (Utils::isNinja()) { if (Utils::isNinjaProd()) {
$this->userMailer->sendConfirmation($user); $this->userMailer->sendConfirmation($user);
} }

View File

@ -257,6 +257,8 @@ class AccountGatewayController extends BaseController
} }
$accountGateway->accepted_credit_cards = $cardCount; $accountGateway->accepted_credit_cards = $cardCount;
$accountGateway->show_address = Input::get('show_address') ? true : false;
$accountGateway->update_address = Input::get('update_address') ? true : false;
$accountGateway->config = json_encode($config); $accountGateway->config = json_encode($config);
if ($accountGatewayPublicId) { if ($accountGatewayPublicId) {
@ -278,7 +280,7 @@ class AccountGatewayController extends BaseController
Session::flash('message', $message); Session::flash('message', $message);
return Redirect::to('company/payments'); return Redirect::to("gateways/{$accountGateway->public_id}/edit");
} }
} }

View File

@ -34,7 +34,7 @@ class AppController extends BaseController
public function showSetup() public function showSetup()
{ {
if (Utils::isNinja() || (Utils::isDatabaseSetup() && Account::count() > 0)) { if (Utils::isNinjaProd() || (Utils::isDatabaseSetup() && Account::count() > 0)) {
return Redirect::to('/'); return Redirect::to('/');
} }
@ -43,7 +43,7 @@ class AppController extends BaseController
public function doSetup() public function doSetup()
{ {
if (Utils::isNinja() || (Utils::isDatabaseSetup() && Account::count() > 0)) { if (Utils::isNinjaProd() || (Utils::isDatabaseSetup() && Account::count() > 0)) {
return Redirect::to('/'); return Redirect::to('/');
} }
@ -88,7 +88,7 @@ class AppController extends BaseController
"MAIL_HOST={$mail['host']}\n". "MAIL_HOST={$mail['host']}\n".
"MAIL_USERNAME={$mail['username']}\n". "MAIL_USERNAME={$mail['username']}\n".
"MAIL_FROM_NAME={$mail['from']['name']}\n". "MAIL_FROM_NAME={$mail['from']['name']}\n".
"MAIL_PASSWORD={$mail['password']}\n"; "MAIL_PASSWORD={$mail['password']}";
// Write Config Settings // Write Config Settings
$fp = fopen(base_path()."/.env", 'w'); $fp = fopen(base_path()."/.env", 'w');
@ -101,6 +101,7 @@ class AppController extends BaseController
if (Industry::count() == 0) { if (Industry::count() == 0) {
Artisan::call('db:seed', array('--force' => true)); Artisan::call('db:seed', array('--force' => true));
} }
Cache::flush();
Artisan::call('optimize', array('--force' => true)); Artisan::call('optimize', array('--force' => true));
$firstName = trim(Input::get('first_name')); $firstName = trim(Input::get('first_name'));
@ -159,7 +160,7 @@ class AppController extends BaseController
public function install() public function install()
{ {
if (!Utils::isNinja() && !Utils::isDatabaseSetup()) { if (!Utils::isNinjaProd() && !Utils::isDatabaseSetup()) {
try { try {
Artisan::call('migrate', array('--force' => true)); Artisan::call('migrate', array('--force' => true));
if (Industry::count() == 0) { if (Industry::count() == 0) {
@ -176,7 +177,7 @@ class AppController extends BaseController
public function update() public function update()
{ {
if (!Utils::isNinja()) { if (!Utils::isNinjaProd()) {
try { try {
Artisan::call('migrate', array('--force' => true)); Artisan::call('migrate', array('--force' => true));
Artisan::call('db:seed', array('--force' => true, '--class' => 'PaymentLibrariesSeeder')); Artisan::call('db:seed', array('--force' => true, '--class' => 'PaymentLibrariesSeeder'));

View File

@ -62,7 +62,7 @@ class AuthController extends Controller {
$userId = Auth::check() ? Auth::user()->id : null; $userId = Auth::check() ? Auth::user()->id : null;
$user = User::where('email', '=', $request->input('email'))->first(); $user = User::where('email', '=', $request->input('email'))->first();
if ($user->failed_logins >= 3) { if ($user && $user->failed_logins >= 3) {
Session::flash('error', 'These credentials do not match our records.'); Session::flash('error', 'These credentials do not match our records.');
return redirect()->to('login'); return redirect()->to('login');
} }

View File

@ -268,7 +268,7 @@ class ClientController extends BaseController
$record = Contact::createNew(); $record = Contact::createNew();
} }
$record->email = trim(strtolower($contact->email)); $record->email = trim($contact->email);
$record->first_name = trim($contact->first_name); $record->first_name = trim($contact->first_name);
$record->last_name = trim($contact->last_name); $record->last_name = trim($contact->last_name);
$record->phone = trim($contact->phone); $record->phone = trim($contact->phone);
@ -306,7 +306,7 @@ class ClientController extends BaseController
Session::flash('message', $message); Session::flash('message', $message);
if ($action == 'restore' && $count == 1) { if ($action == 'restore' && $count == 1) {
return Redirect::to('clients/'.$ids[0]); return Redirect::to('clients/'.Utils::getFirst($ids));
} else { } else {
return Redirect::to('clients'); return Redirect::to('clients');
} }

View File

@ -5,6 +5,7 @@ use DB;
use View; use View;
use App\Models\Activity; use App\Models\Activity;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Payment;
class DashboardController extends BaseController class DashboardController extends BaseController
{ {
@ -44,47 +45,89 @@ class DashboardController extends BaseController
->where('accounts.id', '=', Auth::user()->account_id) ->where('accounts.id', '=', Auth::user()->account_id)
->where('clients.is_deleted', '=', false) ->where('clients.is_deleted', '=', false)
->where('invoices.is_deleted', '=', false) ->where('invoices.is_deleted', '=', false)
->where('invoices.is_quote', '=', false)
->where('invoices.is_recurring', '=', false)
->groupBy('accounts.id') ->groupBy('accounts.id')
->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')) ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END'))
->get(); ->get();
$select = DB::raw('SUM(clients.balance) as value, clients.currency_id as currency_id');
$balances = DB::table('accounts')
->select($select)
->leftJoin('clients', 'accounts.id', '=', 'clients.account_id')
->where('accounts.id', '=', Auth::user()->account_id)
->where('clients.is_deleted', '=', false)
->groupBy('accounts.id')
->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END'))
->get();
$activities = Activity::where('activities.account_id', '=', Auth::user()->account_id) $activities = Activity::where('activities.account_id', '=', Auth::user()->account_id)
->where('activity_type_id', '>', 0) ->where('activity_type_id', '>', 0)
->orderBy('created_at', 'desc')->take(14)->get(); ->orderBy('created_at', 'desc')
->take(50)
->get();
$pastDue = Invoice::scope()->whereHas('client', function($query) { $pastDue = DB::table('invoices')
$query->where('deleted_at', '=', null); ->leftJoin('clients', 'clients.id', '=', 'invoices.client_id')
}) ->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
->where('due_date', '<', date('Y-m-d')) ->where('invoices.account_id', '=', Auth::user()->account_id)
->where('balance', '>', 0) ->where('clients.deleted_at', '=', null)
->where('is_recurring', '=', false) ->where('contacts.deleted_at', '=', null)
->where('is_quote', '=', false) ->where('invoices.is_recurring', '=', false)
->where('is_deleted', '=', false) ->where('invoices.is_quote', '=', false)
->orderBy('due_date', 'asc')->take(6)->get(); ->where('invoices.balance', '>', 0)
->where('invoices.is_deleted', '=', false)
->where('contacts.is_primary', '=', true)
->where('invoices.due_date', '<', date('Y-m-d'))
->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id'])
->orderBy('invoices.due_date', 'asc')
->take(50)
->get();
$upcoming = DB::table('invoices')
->leftJoin('clients', 'clients.id', '=', 'invoices.client_id')
->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
->where('invoices.account_id', '=', Auth::user()->account_id)
->where('clients.deleted_at', '=', null)
->where('contacts.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false)
->where('invoices.is_quote', '=', false)
->where('invoices.balance', '>', 0)
->where('invoices.is_deleted', '=', false)
->where('contacts.is_primary', '=', true)
->where('invoices.due_date', '>=', date('Y-m-d'))
->orderBy('invoices.due_date', 'asc')
->take(50)
->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id'])
->get();
$payments = DB::table('payments')
->leftJoin('clients', 'clients.id', '=', 'payments.client_id')
->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
->leftJoin('invoices', 'invoices.id', '=', 'payments.invoice_id')
->where('payments.account_id', '=', Auth::user()->account_id)
->where('clients.deleted_at', '=', null)
->where('contacts.deleted_at', '=', null)
->where('contacts.is_primary', '=', true)
->select(['payments.payment_date', 'payments.amount', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id'])
->orderBy('payments.id', 'desc')
->take(50)
->get();
$upcoming = Invoice::scope()->whereHas('client', function($query) {
$query->where('deleted_at', '=', null);
})
->where('due_date', '>=', date('Y-m-d'))
->where('balance', '>', 0)
->where('is_recurring', '=', false)
->where('is_quote', '=', false)
->where('is_deleted', '=', false)
->orderBy('due_date', 'asc')->take(6)->get();
$data = [ $data = [
'paidToDate' => $paidToDate, 'account' => Auth::user()->account,
'averageInvoice' => $averageInvoice, 'paidToDate' => $paidToDate,
//'billedClients' => $metrics ? $metrics->billed_clients : 0, 'balances' => $balances,
'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, 'averageInvoice' => $averageInvoice,
'activeClients' => $metrics ? $metrics->active_clients : 0, 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0,
'activities' => $activities, 'activeClients' => $metrics ? $metrics->active_clients : 0,
'pastDue' => $pastDue, 'activities' => $activities,
'upcoming' => $upcoming, 'pastDue' => $pastDue,
'title' => trans('texts.dashboard'), 'upcoming' => $upcoming,
]; 'payments' => $payments,
'title' => trans('texts.dashboard'),
];
return View::make('dashboard', $data); return View::make('dashboard', $data);
} }

View File

@ -43,14 +43,15 @@ class HomeController extends BaseController
public function invoiceNow() public function invoiceNow()
{ {
if (Auth::check() && Input::get('new_account')) { if (Auth::check() && Input::get('new_company')) {
Session::put(PREV_USER_ID, Auth::user()->id); Session::put(PREV_USER_ID, Auth::user()->id);
Auth::user()->clearSession(); Auth::user()->clearSession();
Auth::logout(); Auth::logout();
} }
if (Auth::check()) { if (Auth::check()) {
return Redirect::to('invoices/create')->with('sign_up', Input::get('sign_up')); $redirectTo = Input::get('redirect_to', 'invoices/create');
return Redirect::to($redirectTo)->with('sign_up', Input::get('sign_up'));
} else { } else {
return View::make('public.header', ['invoiceNow' => true]); return View::make('public.header', ['invoiceNow' => true]);
} }
@ -72,9 +73,9 @@ class HomeController extends BaseController
$user->news_feed_id = $newsFeedId; $user->news_feed_id = $newsFeedId;
$user->save(); $user->save();
} }
Session::forget('news_feed_message');
} }
Session::forget('news_feed_message');
return 'success'; return 'success';
} }

View File

@ -26,7 +26,11 @@ class InvoiceApiController extends Controller
public function index() public function index()
{ {
$invoices = Invoice::scope()->with('client', 'invitations.account')->where('invoices.is_quote', '=', false)->orderBy('created_at', 'desc')->get(); $invoices = Invoice::scope()
->with('client', 'invitations.account')
->where('invoices.is_quote', '=', false)
->orderBy('created_at', 'desc')
->get();
// Add the first invitation link to the data // Add the first invitation link to the data
foreach ($invoices as $key => $invoice) { foreach ($invoices as $key => $invoice) {
@ -50,12 +54,14 @@ class InvoiceApiController extends Controller
$error = null; $error = null;
// check if the invoice number is set and unique // check if the invoice number is set and unique
if (!isset($data['invoice_number'])) { if (!isset($data['invoice_number']) && !isset($data['id'])) {
$data['invoice_number'] = Auth::user()->account->getNextInvoiceNumber(); $data['invoice_number'] = Auth::user()->account->getNextInvoiceNumber();
} else { } else if (isset($data['invoice_number'])) {
$invoice = Invoice::scope()->where('invoice_number', '=', $data['invoice_number'])->first(); $invoice = Invoice::scope()->where('invoice_number', '=', $data['invoice_number'])->first();
if ($invoice) { if ($invoice) {
$error = trans('validation.unique', ['attribute' => 'texts.invoice_number']); $error = trans('validation.unique', ['attribute' => 'texts.invoice_number']);
} else {
$data['id'] = $invoice->public_id;
} }
} }
@ -100,11 +106,13 @@ class InvoiceApiController extends Controller
$data['client_id'] = $client->id; $data['client_id'] = $client->id;
$invoice = $this->invoiceRepo->save(false, $data, false); $invoice = $this->invoiceRepo->save(false, $data, false);
$invitation = Invitation::createNew(); if (!isset($data['id'])) {
$invitation->invoice_id = $invoice->id; $invitation = Invitation::createNew();
$invitation->contact_id = $client->contacts[0]->id; $invitation->invoice_id = $invoice->id;
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH); $invitation->contact_id = $client->contacts[0]->id;
$invitation->save(); $invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
$invitation->save();
}
if (isset($data['email_invoice']) && $data['email_invoice']) { if (isset($data['email_invoice']) && $data['email_invoice']) {
$this->mailer->sendInvoice($invoice); $this->mailer->sendInvoice($invoice);

View File

@ -60,8 +60,7 @@ class InvoiceController extends BaseController
'columns' => Utils::trans(['checkbox', 'invoice_number', 'client', 'invoice_date', 'invoice_total', 'balance_due', 'due_date', 'status', 'action']), 'columns' => Utils::trans(['checkbox', 'invoice_number', 'client', 'invoice_date', 'invoice_total', 'balance_due', 'due_date', 'status', 'action']),
]; ];
$recurringInvoices = Invoice::scope() $recurringInvoices = Invoice::scope()->where('is_recurring', '=', true);
->where('is_recurring', '=', true);
if (Session::get('show_trash:invoice')) { if (Session::get('show_trash:invoice')) {
$recurringInvoices->withTrashed(); $recurringInvoices->withTrashed();
@ -86,11 +85,12 @@ class InvoiceController extends BaseController
} }
$invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first(); $invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first();
$color = $invitation->account->primary_color ? $invitation->account->primary_color : '#0b4d78'; $account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [ $data = [
'color' => $color, 'color' => $color,
'hideLogo' => Session::get('white_label'), 'hideLogo' => $account->isWhiteLabel(),
'title' => trans('texts.invoices'), 'title' => trans('texts.invoices'),
'entityType' => ENTITY_INVOICE, 'entityType' => ENTITY_INVOICE,
'columns' => Utils::trans(['invoice_number', 'invoice_date', 'invoice_total', 'balance_due', 'due_date']), 'columns' => Utils::trans(['invoice_number', 'invoice_date', 'invoice_total', 'balance_due', 'due_date']),
@ -205,7 +205,6 @@ class InvoiceController extends BaseController
Session::set($invitationKey, true); Session::set($invitationKey, true);
Session::set('invitation_key', $invitationKey); Session::set('invitation_key', $invitationKey);
Session::set('white_label', $account->isWhiteLabel());
$account->loadLocalizationSettings(); $account->loadLocalizationSettings();
@ -213,6 +212,12 @@ class InvoiceController extends BaseController
$invoice->due_date = Utils::fromSqlDate($invoice->due_date); $invoice->due_date = Utils::fromSqlDate($invoice->due_date);
$invoice->is_pro = $account->isPro(); $invoice->is_pro = $account->isPro();
if ($invoice->invoice_design_id == CUSTOM_DESIGN) {
$invoice->invoice_design->javascript = $account->custom_design;
} else {
$invoice->invoice_design->javascript = $invoice->invoice_design->pdfmake;
}
$contact = $invitation->contact; $contact = $invitation->contact;
$contact->setVisible([ $contact->setVisible([
'first_name', 'first_name',
@ -250,7 +255,7 @@ class InvoiceController extends BaseController
'invoiceLabels' => $account->getInvoiceLabels(), 'invoiceLabels' => $account->getInvoiceLabels(),
'contact' => $contact, 'contact' => $contact,
'paymentTypes' => $paymentTypes, 'paymentTypes' => $paymentTypes,
'paymentURL' => $paymentURL 'paymentURL' => $paymentURL,
); );
return View::make('invoices.view', $data); return View::make('invoices.view', $data);
@ -277,7 +282,7 @@ class InvoiceController extends BaseController
$method = 'POST'; $method = 'POST';
$url = "{$entityType}s"; $url = "{$entityType}s";
} else { } else {
Utils::trackViewed($invoice->invoice_number.' - '.$invoice->client->getDisplayName(), $invoice->getEntityType()); Utils::trackViewed($invoice->getDisplayName().' - '.$invoice->client->getDisplayName(), $invoice->getEntityType());
$method = 'PUT'; $method = 'PUT';
$url = "{$entityType}s/{$publicId}"; $url = "{$entityType}s/{$publicId}";
} }
@ -321,19 +326,21 @@ class InvoiceController extends BaseController
$actions[] = ['url' => 'javascript:onArchiveClick()', 'label' => trans("texts.archive_{$entityType}")]; $actions[] = ['url' => 'javascript:onArchiveClick()', 'label' => trans("texts.archive_{$entityType}")];
$actions[] = ['url' => 'javascript:onDeleteClick()', 'label' => trans("texts.delete_{$entityType}")]; $actions[] = ['url' => 'javascript:onDeleteClick()', 'label' => trans("texts.delete_{$entityType}")];
$lastSent = ($invoice->is_recurring && $invoice->last_sent_date) ? $invoice->recurring_invoices->last() : null;
$data = array( $data = array(
'entityType' => $entityType, 'entityType' => $entityType,
'showBreadcrumbs' => $clone, 'showBreadcrumbs' => $clone,
'account' => $invoice->account,
'invoice' => $invoice, 'invoice' => $invoice,
'data' => false, 'data' => false,
'method' => $method, 'method' => $method,
'invitationContactIds' => $contactIds, 'invitationContactIds' => $contactIds,
'url' => $url, 'url' => $url,
'title' => trans("texts.edit_{$entityType}"), 'title' => trans("texts.edit_{$entityType}"),
'client' => $invoice->client, 'client' => $invoice->client,
'actions' => $actions); 'isRecurring' => $invoice->is_recurring,
'actions' => $actions,
'lastSent' => $lastSent);
$data = array_merge($data, self::getViewModel()); $data = array_merge($data, self::getViewModel());
// Set the invitation link on the client's contacts // Set the invitation link on the client's contacts
@ -356,11 +363,10 @@ class InvoiceController extends BaseController
return View::make('invoices.edit', $data); return View::make('invoices.edit', $data);
} }
public function create($clientPublicId = 0) public function create($clientPublicId = 0, $isRecurring = false)
{ {
$client = null; $client = null;
$invoiceNumber = Auth::user()->account->getNextInvoiceNumber(); $invoiceNumber = $isRecurring ? microtime(true) : Auth::user()->account->getNextInvoiceNumber();
$account = Account::with('country')->findOrFail(Auth::user()->account_id);
if ($clientPublicId) { if ($clientPublicId) {
$client = Client::scope($clientPublicId)->firstOrFail(); $client = Client::scope($clientPublicId)->firstOrFail();
@ -368,20 +374,24 @@ class InvoiceController extends BaseController
$data = array( $data = array(
'entityType' => ENTITY_INVOICE, 'entityType' => ENTITY_INVOICE,
'account' => $account,
'invoice' => null, 'invoice' => null,
'data' => Input::old('data'), 'data' => Input::old('data'),
'invoiceNumber' => $invoiceNumber, 'invoiceNumber' => $invoiceNumber,
'method' => 'POST', 'method' => 'POST',
'url' => 'invoices', 'url' => 'invoices',
'title' => trans('texts.new_invoice'), 'title' => trans('texts.new_invoice'),
'client' => $client, 'isRecurring' => $isRecurring,
'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null); 'client' => $client);
$data = array_merge($data, self::getViewModel()); $data = array_merge($data, self::getViewModel());
return View::make('invoices.edit', $data); return View::make('invoices.edit', $data);
} }
public function createRecurring($clientPublicId = 0)
{
return self::create($clientPublicId, true);
}
private static function getViewModel() private static function getViewModel()
{ {
$recurringHelp = ''; $recurringHelp = '';
@ -396,7 +406,7 @@ class InvoiceController extends BaseController
} }
return [ return [
'account' => Auth::user()->account, 'account' => Auth::user()->account->load('country'),
'products' => Product::scope()->orderBy('id')->get(array('product_key', 'notes', 'cost', 'qty')), 'products' => Product::scope()->orderBy('id')->get(array('product_key', 'notes', 'cost', 'qty')),
'countries' => Cache::get('countries'), 'countries' => Cache::get('countries'),
'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(), 'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(),
@ -405,7 +415,7 @@ class InvoiceController extends BaseController
'sizes' => Cache::get('sizes'), 'sizes' => Cache::get('sizes'),
'paymentTerms' => Cache::get('paymentTerms'), 'paymentTerms' => Cache::get('paymentTerms'),
'industries' => Cache::get('industries'), 'industries' => Cache::get('industries'),
'invoiceDesigns' => InvoiceDesign::availableDesigns(), 'invoiceDesigns' => InvoiceDesign::getDesigns(),
'frequencies' => array( 'frequencies' => array(
1 => 'Weekly', 1 => 'Weekly',
2 => 'Two weeks', 2 => 'Two weeks',
@ -417,7 +427,7 @@ class InvoiceController extends BaseController
), ),
'recurringHelp' => $recurringHelp, 'recurringHelp' => $recurringHelp,
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(), 'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null,
]; ];
} }
@ -511,7 +521,16 @@ class InvoiceController extends BaseController
return $this->convertQuote($publicId); return $this->convertQuote($publicId);
} elseif ($action == 'email') { } elseif ($action == 'email') {
if (Auth::user()->confirmed && !Auth::user()->isDemo()) { if (Auth::user()->confirmed && !Auth::user()->isDemo()) {
$response = $this->mailer->sendInvoice($invoice); if ($invoice->is_recurring) {
if ($invoice->shouldSendToday()) {
$invoice = $this->invoiceRepo->createRecurringInvoice($invoice);
$response = $this->mailer->sendInvoice($invoice);
} else {
$response = trans('texts.recurring_too_soon');
}
} else {
$response = $this->mailer->sendInvoice($invoice);
}
if ($response === true) { if ($response === true) {
$message = trans("texts.emailed_{$entityType}"); $message = trans("texts.emailed_{$entityType}");
Session::flash('message', $message); Session::flash('message', $message);
@ -577,7 +596,7 @@ class InvoiceController extends BaseController
} }
if ($action == 'restore' && $count == 1) { if ($action == 'restore' && $count == 1) {
return Redirect::to("{$entityType}s/".$ids[0]); return Redirect::to("{$entityType}s/".Utils::getFirst($ids));
} else { } else {
return Redirect::to("{$entityType}s"); return Redirect::to("{$entityType}s");
} }
@ -646,7 +665,7 @@ class InvoiceController extends BaseController
'invoice' => $invoice, 'invoice' => $invoice,
'versionsJson' => json_encode($versionsJson), 'versionsJson' => json_encode($versionsJson),
'versionsSelect' => $versionsSelect, 'versionsSelect' => $versionsSelect,
'invoiceDesigns' => InvoiceDesign::availableDesigns(), 'invoiceDesigns' => InvoiceDesign::getDesigns(),
]; ];
return View::make('invoices.history', $data); return View::make('invoices.history', $data);

View File

@ -61,11 +61,12 @@ class PaymentController extends BaseController
} }
$invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first(); $invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first();
$color = $invitation->account->primary_color ? $invitation->account->primary_color : '#0b4d78'; $account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [ $data = [
'color' => $color, 'color' => $color,
'hideLogo' => Session::get('white_label'), 'hideLogo' => $account->isWhiteLabel(),
'entityType' => ENTITY_PAYMENT, 'entityType' => ENTITY_PAYMENT,
'title' => trans('texts.payments'), 'title' => trans('texts.payments'),
'columns' => Utils::trans(['invoice', 'transaction_reference', 'method', 'payment_amount', 'payment_date']) 'columns' => Utils::trans(['invoice', 'transaction_reference', 'method', 'payment_amount', 'payment_date'])
@ -233,33 +234,41 @@ class PaymentController extends BaseController
private function convertInputForOmnipay($input) private function convertInputForOmnipay($input)
{ {
$country = Country::find($input['country_id']); $data = [
return [
'firstName' => $input['first_name'], 'firstName' => $input['first_name'],
'lastName' => $input['last_name'], 'lastName' => $input['last_name'],
'number' => $input['card_number'], 'number' => $input['card_number'],
'expiryMonth' => $input['expiration_month'], 'expiryMonth' => $input['expiration_month'],
'expiryYear' => $input['expiration_year'], 'expiryYear' => $input['expiration_year'],
'cvv' => $input['cvv'], 'cvv' => $input['cvv'],
'billingAddress1' => $input['address1'],
'billingAddress2' => $input['address2'],
'billingCity' => $input['city'],
'billingState' => $input['state'],
'billingPostcode' => $input['postal_code'],
'billingCountry' => $country->iso_3166_2,
'shippingAddress1' => $input['address1'],
'shippingAddress2' => $input['address2'],
'shippingCity' => $input['city'],
'shippingState' => $input['state'],
'shippingPostcode' => $input['postal_code'],
'shippingCountry' => $country->iso_3166_2
]; ];
if (isset($input['country_id'])) {
$country = Country::find($input['country_id']);
$data = array_merge($data, [
'billingAddress1' => $input['address1'],
'billingAddress2' => $input['address2'],
'billingCity' => $input['city'],
'billingState' => $input['state'],
'billingPostcode' => $input['postal_code'],
'billingCountry' => $country->iso_3166_2,
'shippingAddress1' => $input['address1'],
'shippingAddress2' => $input['address2'],
'shippingCity' => $input['city'],
'shippingState' => $input['state'],
'shippingPostcode' => $input['postal_code'],
'shippingCountry' => $country->iso_3166_2
]);
}
return $data;
} }
private function getPaymentDetails($invitation, $input = null) private function getPaymentDetails($invitation, $input = null)
{ {
$invoice = $invitation->invoice; $invoice = $invitation->invoice;
$account = $invoice->account;
$key = $invoice->account_id.'-'.$invoice->invoice_number; $key = $invoice->account_id.'-'.$invoice->invoice_number;
$currencyCode = $invoice->client->currency ? $invoice->client->currency->code : ($invoice->account->currency ? $invoice->account->currency->code : 'USD'); $currencyCode = $invoice->client->currency ? $invoice->client->currency->code : ($invoice->account->currency ? $invoice->account->currency->code : 'USD');
@ -328,8 +337,10 @@ class PaymentController extends BaseController
'acceptedCreditCardTypes' => $acceptedCreditCardTypes, 'acceptedCreditCardTypes' => $acceptedCreditCardTypes,
'countries' => Cache::get('countries'), 'countries' => Cache::get('countries'),
'currencyId' => $client->getCurrencyId(), 'currencyId' => $client->getCurrencyId(),
'currencyCode' => $client->currency ? $client->currency->code : ($account->currency ? $account->currency->code : 'USD'),
'account' => $client->account, 'account' => $client->account,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->isWhiteLabel(),
'showAddress' => $accountGateway->show_address,
]; ];
return View::make('payments.payment', $data); return View::make('payments.payment', $data);
@ -378,6 +389,7 @@ class PaymentController extends BaseController
'currencyId' => 1, 'currencyId' => 1,
'paymentTitle' => $affiliate->payment_title, 'paymentTitle' => $affiliate->payment_title,
'paymentSubtitle' => $affiliate->payment_subtitle, 'paymentSubtitle' => $affiliate->payment_subtitle,
'showAddress' => true,
]; ];
return View::make('payments.payment', $data); return View::make('payments.payment', $data);
@ -498,19 +510,30 @@ class PaymentController extends BaseController
public function do_payment($invitationKey, $onSite = true, $useToken = false) public function do_payment($invitationKey, $onSite = true, $useToken = false)
{ {
$rules = array( $invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.currency', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail();
$invoice = $invitation->invoice;
$client = $invoice->client;
$account = $client->account;
$accountGateway = $account->getGatewayByType(Session::get('payment_type'));
$rules = [
'first_name' => 'required', 'first_name' => 'required',
'last_name' => 'required', 'last_name' => 'required',
'card_number' => 'required', 'card_number' => 'required',
'expiration_month' => 'required', 'expiration_month' => 'required',
'expiration_year' => 'required', 'expiration_year' => 'required',
'cvv' => 'required', 'cvv' => 'required',
'address1' => 'required', ];
'city' => 'required',
'state' => 'required', if ($accountGateway->show_address) {
'postal_code' => 'required', $rules = array_merge($rules, [
'country_id' => 'required', 'address1' => 'required',
); 'city' => 'required',
'state' => 'required',
'postal_code' => 'required',
'country_id' => 'required',
]);
}
if ($onSite) { if ($onSite) {
$validator = Validator::make(Input::all(), $rules); $validator = Validator::make(Input::all(), $rules);
@ -521,25 +544,19 @@ class PaymentController extends BaseController
->withErrors($validator) ->withErrors($validator)
->withInput(); ->withInput();
} }
}
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.currency', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail();
$invoice = $invitation->invoice; if ($accountGateway->update_address) {
$client = $invoice->client; $client->address1 = trim(Input::get('address1'));
$account = $client->account; $client->address2 = trim(Input::get('address2'));
$accountGateway = $account->getGatewayByType(Session::get('payment_type')); $client->city = trim(Input::get('city'));
$client->state = trim(Input::get('state'));
/* $client->postal_code = trim(Input::get('postal_code'));
if ($onSite) { $client->country_id = Input::get('country_id');
$client->address1 = trim(Input::get('address1')); $client->save();
$client->address2 = trim(Input::get('address2')); }
$client->city = trim(Input::get('city'));
$client->state = trim(Input::get('state'));
$client->postal_code = trim(Input::get('postal_code'));
$client->save();
} }
*/
try { try {
$gateway = self::createGateway($accountGateway); $gateway = self::createGateway($accountGateway);
$details = self::getPaymentDetails($invitation, ($useToken || !$onSite) ? false : Input::all()); $details = self::getPaymentDetails($invitation, ($useToken || !$onSite) ? false : Input::all());

View File

@ -16,7 +16,11 @@ class QuoteApiController extends Controller
public function index() public function index()
{ {
$invoices = Invoice::scope()->with('client', 'user')->where('invoices.is_quote', '=', true)->orderBy('created_at', 'desc')->get(); $invoices = Invoice::scope()
->with('client', 'user')
->where('invoices.is_quote', '=', true)
->orderBy('created_at', 'desc')
->get();
$invoices = Utils::remapPublicIds($invoices); $invoices = Utils::remapPublicIds($invoices);
$response = json_encode($invoices, JSON_PRETTY_PRINT); $response = json_encode($invoices, JSON_PRETTY_PRINT);

View File

@ -75,11 +75,12 @@ class QuoteController extends BaseController
} }
$invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first(); $invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first();
$color = $invitation->account->primary_color ? $invitation->account->primary_color : '#0b4d78'; $account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [ $data = [
'color' => $color, 'color' => $color,
'hideLogo' => Session::get('white_label'), 'hideLogo' => $account->isWhiteLabel(),
'title' => trans('texts.quotes'), 'title' => trans('texts.quotes'),
'entityType' => ENTITY_QUOTE, 'entityType' => ENTITY_QUOTE,
'columns' => Utils::trans(['quote_number', 'quote_date', 'quote_total', 'due_date']), 'columns' => Utils::trans(['quote_number', 'quote_date', 'quote_total', 'due_date']),
@ -156,8 +157,9 @@ class QuoteController extends BaseController
'sizes' => Cache::get('sizes'), 'sizes' => Cache::get('sizes'),
'paymentTerms' => Cache::get('paymentTerms'), 'paymentTerms' => Cache::get('paymentTerms'),
'industries' => Cache::get('industries'), 'industries' => Cache::get('industries'),
'invoiceDesigns' => InvoiceDesign::availableDesigns(), 'invoiceDesigns' => InvoiceDesign::getDesigns(),
'invoiceLabels' => Auth::user()->account->getInvoiceLabels() 'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
'isRecurring' => false,
]; ];
} }
@ -183,7 +185,11 @@ class QuoteController extends BaseController
Session::flash('message', $message); Session::flash('message', $message);
} }
return Redirect::to('quotes'); if ($action == 'restore' && $count == 1) {
return Redirect::to("quotes/".Utils::getFirst($ids));
} else {
return Redirect::to("quotes");
}
} }
public function approve($invitationKey) public function approve($invitationKey)

View File

@ -1,6 +1,7 @@
<?php namespace App\Http\Controllers; <?php namespace App\Http\Controllers;
use Auth; use Auth;
use Config;
use Input; use Input;
use Utils; use Utils;
use DB; use DB;
@ -19,7 +20,9 @@ class ReportController extends BaseController
$fileName = storage_path() . '/dataviz_sample.txt'; $fileName = storage_path() . '/dataviz_sample.txt';
if (Auth::user()->account->isPro()) { if (Auth::user()->account->isPro()) {
$account = Account::where('id', '=', Auth::user()->account->id)->with(['clients.invoices.invoice_items', 'clients.contacts'])->first(); $account = Account::where('id', '=', Auth::user()->account->id)
->with(['clients.invoices.invoice_items', 'clients.contacts'])
->first();
$account = $account->hideFieldsForViz(); $account = $account->hideFieldsForViz();
$clients = $account->clients->toJson(); $clients = $account->clients->toJson();
} elseif (file_exists($fileName)) { } elseif (file_exists($fileName)) {
@ -149,53 +152,91 @@ class ReportController extends BaseController
$reportTotals['balance'][$currencyId] += $record->balance; $reportTotals['balance'][$currencyId] += $record->balance;
} }
if ($action == 'export') { if ($action == 'export')
{
self::export($exportData, $reportTotals); self::export($exportData, $reportTotals);
} }
} }
if ($enableChart) { if ($enableChart)
foreach ([ENTITY_INVOICE, ENTITY_PAYMENT, ENTITY_CREDIT] as $entityType) { {
$records = DB::table($entityType.'s') foreach ([ENTITY_INVOICE, ENTITY_PAYMENT, ENTITY_CREDIT] as $entityType)
->select(DB::raw('sum(amount) as total, concat(YEAR('.$entityType.'_date), '.$groupBy.'('.$entityType.'_date)) as '.$groupBy)) {
->where('account_id', '=', Auth::user()->account_id) // SQLite does not support the YEAR(), MONTH(), WEEK() and similar functions.
->where($entityType.'s.is_deleted', '=', false) // Let's see if SQLite is being used.
->where($entityType.'s.'.$entityType.'_date', '>=', $startDate->format('Y-m-d')) if (Config::get('database.connections.'.Config::get('database.default').'.driver') == 'sqlite')
->where($entityType.'s.'.$entityType.'_date', '<=', $endDate->format('Y-m-d')) {
->groupBy($groupBy); // Replace the unsupported function with it's date format counterpart
switch ($groupBy)
{
case 'MONTH':
$dateFormat = '%m'; // returns 01-12
break;
case 'WEEK':
$dateFormat = '%W'; // returns 00-53
break;
case 'DAYOFYEAR':
$dateFormat = '%j'; // returns 001-366
break;
default:
$dateFormat = '%m'; // MONTH by default
break;
}
if ($entityType == ENTITY_INVOICE) { // Concatenate the year and the chosen timeframe (Month, Week or Day)
$timeframe = 'strftime("%Y", '.$entityType.'_date) || strftime("'.$dateFormat.'", '.$entityType.'_date)';
}
else
{
// Supported by Laravel's other DBMS drivers (MySQL, MSSQL and PostgreSQL)
$timeframe = 'concat(YEAR('.$entityType.'_date), '.$groupBy.'('.$entityType.'_date))';
}
$records = DB::table($entityType.'s')
->select(DB::raw('sum(amount) as total, '.$timeframe.' as '.$groupBy))
->where('account_id', '=', Auth::user()->account_id)
->where($entityType.'s.is_deleted', '=', false)
->where($entityType.'s.'.$entityType.'_date', '>=', $startDate->format('Y-m-d'))
->where($entityType.'s.'.$entityType.'_date', '<=', $endDate->format('Y-m-d'))
->groupBy($groupBy);
if ($entityType == ENTITY_INVOICE)
{
$records->where('is_quote', '=', false) $records->where('is_quote', '=', false)
->where('is_recurring', '=', false); ->where('is_recurring', '=', false);
} }
$totals = $records->lists('total'); $totals = $records->lists('total');
$dates = $records->lists($groupBy); $dates = $records->lists($groupBy);
$data = array_combine($dates, $totals); $data = array_combine($dates, $totals);
$padding = $groupBy == 'DAYOFYEAR' ? 'day' : ($groupBy == 'WEEK' ? 'week' : 'month'); $padding = $groupBy == 'DAYOFYEAR' ? 'day' : ($groupBy == 'WEEK' ? 'week' : 'month');
$endDate->modify('+1 '.$padding); $endDate->modify('+1 '.$padding);
$interval = new DateInterval('P1'.substr($groupBy, 0, 1)); $interval = new DateInterval('P1'.substr($groupBy, 0, 1));
$period = new DatePeriod($startDate, $interval, $endDate); $period = new DatePeriod($startDate, $interval, $endDate);
$endDate->modify('-1 '.$padding); $endDate->modify('-1 '.$padding);
$totals = []; $totals = [];
foreach ($period as $d) { foreach ($period as $d)
{
$dateFormat = $groupBy == 'DAYOFYEAR' ? 'z' : ($groupBy == 'WEEK' ? 'W' : 'n'); $dateFormat = $groupBy == 'DAYOFYEAR' ? 'z' : ($groupBy == 'WEEK' ? 'W' : 'n');
$date = $d->format('Y'.$dateFormat); // MySQL returns 1-366 for DAYOFYEAR, whereas PHP returns 0-365
$date = $groupBy == 'DAYOFYEAR' ? $d->format('Y') . ($d->format($dateFormat) + 1) : $d->format('Y'.$dateFormat);
$totals[] = isset($data[$date]) ? $data[$date] : 0; $totals[] = isset($data[$date]) ? $data[$date] : 0;
if ($entityType == ENTITY_INVOICE) { if ($entityType == ENTITY_INVOICE)
{
$labelFormat = $groupBy == 'DAYOFYEAR' ? 'j' : ($groupBy == 'WEEK' ? 'W' : 'F'); $labelFormat = $groupBy == 'DAYOFYEAR' ? 'j' : ($groupBy == 'WEEK' ? 'W' : 'F');
$label = $d->format($labelFormat); $label = $d->format($labelFormat);
$labels[] = $label; $labels[] = $label;
} }
} }
$max = max($totals); $max = max($totals);
if ($max > 0) { if ($max > 0)
{
$datasets[] = [ $datasets[] = [
'totals' => $totals, 'totals' => $totals,
'colors' => $entityType == ENTITY_INVOICE ? '78,205,196' : ($entityType == ENTITY_CREDIT ? '199,244,100' : '255,107,107'), 'colors' => $entityType == ENTITY_INVOICE ? '78,205,196' : ($entityType == ENTITY_CREDIT ? '199,244,100' : '255,107,107'),

View File

@ -10,34 +10,23 @@ use Validator;
use Redirect; use Redirect;
use Session; use Session;
use DropdownButton; use DropdownButton;
use DateTime;
use DateTimeZone;
use App\Models\Client; use App\Models\Client;
use App\Models\Task; use App\Models\Task;
/*
use Auth;
use Cache;
use App\Models\Activity;
use App\Models\Contact;
use App\Models\Invoice;
use App\Models\Size;
use App\Models\PaymentTerm;
use App\Models\Industry;
use App\Models\Currency;
use App\Models\Country;
*/
use App\Ninja\Repositories\TaskRepository; use App\Ninja\Repositories\TaskRepository;
use App\Ninja\Repositories\InvoiceRepository;
class TaskController extends BaseController class TaskController extends BaseController
{ {
protected $taskRepo; protected $taskRepo;
public function __construct(TaskRepository $taskRepo) public function __construct(TaskRepository $taskRepo, InvoiceRepository $invoiceRepo)
{ {
parent::__construct(); parent::__construct();
$this->taskRepo = $taskRepo; $this->taskRepo = $taskRepo;
$this->invoiceRepo = $invoiceRepo;
} }
/** /**
@ -47,10 +36,7 @@ class TaskController extends BaseController
*/ */
public function index() public function index()
{ {
if (!Auth::user()->account->timezone) { self::checkTimezone();
$link = link_to('/company/details', trans('texts.click_here'), ['target' => '_blank']);
Session::flash('warning', trans('texts.timezone_unset', ['link' => $link]));
}
return View::make('list', array( return View::make('list', array(
'entityType' => ENTITY_TASK, 'entityType' => ENTITY_TASK,
@ -71,8 +57,8 @@ class TaskController extends BaseController
->addColumn('client_name', function ($model) { return $model->client_public_id ? link_to('clients/'.$model->client_public_id, Utils::getClientDisplayName($model)) : ''; }); ->addColumn('client_name', function ($model) { return $model->client_public_id ? link_to('clients/'.$model->client_public_id, Utils::getClientDisplayName($model)) : ''; });
} }
return $table->addColumn('start_time', function($model) { return Utils::fromSqlDateTime($model->start_time); }) return $table->addColumn('created_at', function($model) { return Task::calcStartTime($model); })
->addColumn('duration', function($model) { return gmdate('H:i:s', $model->is_running ? time() - strtotime($model->start_time) : $model->duration); }) ->addColumn('time_log', function($model) { return gmdate('H:i:s', Task::calcDuration($model)); })
->addColumn('description', function($model) { return $model->description; }) ->addColumn('description', function($model) { return $model->description; })
->addColumn('invoice_number', function($model) { return self::getStatusLabel($model); }) ->addColumn('invoice_number', function($model) { return self::getStatusLabel($model); })
->addColumn('dropdown', function ($model) { ->addColumn('dropdown', function ($model) {
@ -142,12 +128,15 @@ class TaskController extends BaseController
*/ */
public function create($clientPublicId = 0) public function create($clientPublicId = 0)
{ {
self::checkTimezone();
$data = [ $data = [
'task' => null, 'task' => null,
'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId, 'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId,
'method' => 'POST', 'method' => 'POST',
'url' => 'tasks', 'url' => 'tasks',
'title' => trans('texts.new_task'), 'title' => trans('texts.new_task'),
'minuteOffset' => Utils::getTiemstampOffset(),
]; ];
$data = array_merge($data, self::getViewModel()); $data = array_merge($data, self::getViewModel());
@ -163,14 +152,24 @@ class TaskController extends BaseController
*/ */
public function edit($publicId) public function edit($publicId)
{ {
self::checkTimezone();
$task = Task::scope($publicId)->with('client', 'invoice')->firstOrFail(); $task = Task::scope($publicId)->with('client', 'invoice')->firstOrFail();
$actions = []; $actions = [];
if ($task->invoice) { if ($task->invoice) {
$actions[] = ['url' => URL::to("inovices/{$task->invoice->public_id}/edit"), 'label' => trans("texts.view_invoice")]; $actions[] = ['url' => URL::to("inovices/{$task->invoice->public_id}/edit"), 'label' => trans("texts.view_invoice")];
} else { } else {
$actions[] = ['url' => 'javascript:submitAction("invoice")', 'label' => trans("texts.invoice_task")]; $actions[] = ['url' => 'javascript:submitAction("invoice")', 'label' => trans("texts.create_invoice")];
// check for any open invoices
$invoices = $task->client_id ? $this->invoiceRepo->findOpenInvoices($task->client_id) : [];
foreach ($invoices as $invoice) {
$actions[] = ['url' => 'javascript:submitAction("add_to_invoice", '.$invoice->public_id.')', 'label' => trans("texts.add_to_invoice", ["invoice" => $invoice->invoice_number])];
}
} }
$actions[] = DropdownButton::DIVIDER; $actions[] = DropdownButton::DIVIDER;
if (!$task->trashed()) { if (!$task->trashed()) {
$actions[] = ['url' => 'javascript:submitAction("archive")', 'label' => trans('texts.archive_task')]; $actions[] = ['url' => 'javascript:submitAction("archive")', 'label' => trans('texts.archive_task')];
@ -178,15 +177,16 @@ class TaskController extends BaseController
} else { } else {
$actions[] = ['url' => 'javascript:submitAction("restore")', 'label' => trans('texts.restore_task')]; $actions[] = ['url' => 'javascript:submitAction("restore")', 'label' => trans('texts.restore_task')];
} }
$data = [ $data = [
'task' => $task, 'task' => $task,
'clientPublicId' => $task->client ? $task->client->public_id : 0, 'clientPublicId' => $task->client ? $task->client->public_id : 0,
'method' => 'PUT', 'method' => 'PUT',
'url' => 'tasks/'.$publicId, 'url' => 'tasks/'.$publicId,
'title' => trans('texts.edit_task'), 'title' => trans('texts.edit_task'),
'duration' => $task->resume_time ? ($task->duration + strtotime('now') - strtotime($task->resume_time)) : (strtotime('now') - strtotime($task->start_time)), 'duration' => $task->is_running ? $task->getCurrentDuration() : $task->getDuration(),
'actions' => $actions 'actions' => $actions,
'minuteOffset' => Utils::getTiemstampOffset(),
]; ];
$data = array_merge($data, self::getViewModel()); $data = array_merge($data, self::getViewModel());
@ -216,7 +216,7 @@ class TaskController extends BaseController
{ {
$action = Input::get('action'); $action = Input::get('action');
if (in_array($action, ['archive', 'delete', 'invoice', 'restore'])) { if (in_array($action, ['archive', 'delete', 'invoice', 'restore', 'add_to_invoice'])) {
return self::bulk(); return self::bulk();
} }
@ -235,12 +235,11 @@ class TaskController extends BaseController
$this->taskRepo->save($ids, ['action' => $action]); $this->taskRepo->save($ids, ['action' => $action]);
Session::flash('message', trans('texts.stopped_task')); Session::flash('message', trans('texts.stopped_task'));
return Redirect::to('tasks'); return Redirect::to('tasks');
} else if ($action == 'invoice') { } else if ($action == 'invoice' || $action == 'add_to_invoice') {
$tasks = Task::scope($ids)->with('client')->get(); $tasks = Task::scope($ids)->with('client')->get();
$clientPublicId = false; $clientPublicId = false;
$data = []; $data = [];
foreach ($tasks as $task) { foreach ($tasks as $task) {
if ($task->client) { if ($task->client) {
if (!$clientPublicId) { if (!$clientPublicId) {
@ -258,16 +257,21 @@ class TaskController extends BaseController
Session::flash('error', trans('texts.task_error_invoiced')); Session::flash('error', trans('texts.task_error_invoiced'));
return Redirect::to('tasks'); return Redirect::to('tasks');
} }
$data[] = [ $data[] = [
'publicId' => $task->public_id, 'publicId' => $task->public_id,
'description' => $task->description, 'description' => $task->description,
'startTime' => Utils::fromSqlDateTime($task->start_time), 'startTime' => $task->getStartTime(),
'duration' => round($task->duration / (60 * 60), 2) 'duration' => $task->getHours(),
]; ];
} }
return Redirect::to("invoices/create/{$clientPublicId}")->with('tasks', $data); if ($action == 'invoice') {
return Redirect::to("invoices/create/{$clientPublicId}")->with('tasks', $data);
} else {
$invoiceId = Input::get('invoice_id');
return Redirect::to("invoices/{$invoiceId}/edit")->with('tasks', $data);
}
} else { } else {
$count = $this->taskRepo->bulk($ids, $action); $count = $this->taskRepo->bulk($ids, $action);
@ -281,4 +285,12 @@ class TaskController extends BaseController
} }
} }
} }
private function checkTimezone()
{
if (!Auth::user()->account->timezone) {
$link = link_to('/company/details?focus=timezone_id', trans('texts.click_here'), ['target' => '_blank']);
Session::flash('warning', trans('texts.timezone_unset', ['link' => $link]));
}
}
} }

View File

@ -95,7 +95,7 @@ class UserController extends BaseController
$user->force_pdfjs = true; $user->force_pdfjs = true;
$user->save(); $user->save();
Session::flash('message', trans('texts.security.updated_settings')); Session::flash('message', trans('texts.updated_settings'));
return Redirect::to('/dashboard'); return Redirect::to('/dashboard');
} }
@ -132,9 +132,12 @@ class UserController extends BaseController
*/ */
public function create() public function create()
{ {
if (!Auth::user()->confirmed) { if (!Auth::user()->registered) {
Session::flash('error', trans('texts.register_to_add_user')); Session::flash('error', trans('texts.register_to_add_user'));
return Redirect::to('company/advanced_settings/user_management');
}
if (!Auth::user()->confirmed) {
Session::flash('error', trans('texts.confirmation_required'));
return Redirect::to('company/advanced_settings/user_management'); return Redirect::to('company/advanced_settings/user_management');
} }
@ -374,6 +377,11 @@ class UserController extends BaseController
Session::put(SESSION_USER_ACCOUNTS, $users); Session::put(SESSION_USER_ACCOUNTS, $users);
Session::flash('message', trans('texts.unlinked_account')); Session::flash('message', trans('texts.unlinked_account'));
return Redirect::to($referer); return Redirect::to('/dashboard');
}
public function manageCompanies()
{
return View::make('users.account_management');
} }
} }

View File

@ -49,6 +49,7 @@ class StartupCheck
'paymentTerms' => 'App\Models\PaymentTerm', 'paymentTerms' => 'App\Models\PaymentTerm',
'paymentTypes' => 'App\Models\PaymentType', 'paymentTypes' => 'App\Models\PaymentType',
'countries' => 'App\Models\Country', 'countries' => 'App\Models\Country',
'invoiceDesigns' => 'App\Models\InvoiceDesign',
]; ];
foreach ($cachedTables as $name => $class) { foreach ($cachedTables as $name => $class) {
if (Input::has('clear_cache')) { if (Input::has('clear_cache')) {
@ -74,7 +75,7 @@ class StartupCheck
$count = Session::get(SESSION_COUNTER, 0); $count = Session::get(SESSION_COUNTER, 0);
Session::put(SESSION_COUNTER, ++$count); Session::put(SESSION_COUNTER, ++$count);
if (!Utils::startsWith($_SERVER['REQUEST_URI'], '/news_feed') && !Session::has('news_feed_id')) { if (isset($_SERVER['REQUEST_URI']) && !Utils::startsWith($_SERVER['REQUEST_URI'], '/news_feed') && !Session::has('news_feed_id')) {
$data = false; $data = false;
if (Utils::isNinja()) { if (Utils::isNinja()) {
$data = Utils::getNewsFeedResponse(); $data = Utils::getNewsFeedResponse();
@ -127,37 +128,39 @@ class StartupCheck
} }
// Check if the user is claiming a license (ie, additional invoices, white label, etc.) // Check if the user is claiming a license (ie, additional invoices, white label, etc.)
$claimingLicense = Utils::startsWith($_SERVER['REQUEST_URI'], '/claim_license'); if (isset($_SERVER['REQUEST_URI'])) {
if (!$claimingLicense && Input::has('license_key') && Input::has('product_id')) { $claimingLicense = Utils::startsWith($_SERVER['REQUEST_URI'], '/claim_license');
$licenseKey = Input::get('license_key'); if (!$claimingLicense && Input::has('license_key') && Input::has('product_id')) {
$productId = Input::get('product_id'); $licenseKey = Input::get('license_key');
$productId = Input::get('product_id');
$data = trim(file_get_contents((Utils::isNinjaDev() ? 'http://www.ninja.dev' : NINJA_APP_URL)."/claim_license?license_key={$licenseKey}&product_id={$productId}")); $data = trim(file_get_contents((Utils::isNinjaDev() ? SITE_URL : NINJA_APP_URL)."/claim_license?license_key={$licenseKey}&product_id={$productId}"));
if ($productId == PRODUCT_INVOICE_DESIGNS) {
if ($data = json_decode($data)) {
foreach ($data as $item) {
$design = new InvoiceDesign();
$design->id = $item->id;
$design->name = $item->name;
$design->javascript = $item->javascript;
$design->save();
}
if ($productId == PRODUCT_INVOICE_DESIGNS) { Session::flash('message', trans('texts.bought_designs'));
if ($data = json_decode($data)) {
foreach ($data as $item) {
$design = new InvoiceDesign();
$design->id = $item->id;
$design->name = $item->name;
$design->javascript = $item->javascript;
$design->save();
} }
} elseif ($productId == PRODUCT_WHITE_LABEL) {
if ($data == 'valid') {
$account = Auth::user()->account;
$account->pro_plan_paid = NINJA_DATE;
$account->save();
Session::flash('message', trans('texts.bought_designs')); Session::flash('message', trans('texts.bought_white_label'));
} }
} elseif ($productId == PRODUCT_WHITE_LABEL) {
if ($data == 'valid') {
$account = Auth::user()->account;
$account->pro_plan_paid = NINJA_DATE;
$account->save();
Session::flash('message', trans('texts.bought_white_label'));
} }
} }
} }
if (preg_match('/(?i)msie [2-8]/', $_SERVER['HTTP_USER_AGENT'])) { if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/(?i)msie [2-8]/', $_SERVER['HTTP_USER_AGENT'])) {
Session::flash('error', trans('texts.old_browser')); Session::flash('error', trans('texts.old_browser'));
} }

View File

@ -26,6 +26,13 @@ Route::post('setup', 'AppController@doSetup');
Route::get('install', 'AppController@install'); Route::get('install', 'AppController@install');
Route::get('update', 'AppController@update'); Route::get('update', 'AppController@update');
/*
// Codeception code coverage
Route::get('/c3.php', function () {
include '../c3.php';
});
*/
// Public pages // Public pages
Route::get('/', 'HomeController@showIndex'); Route::get('/', 'HomeController@showIndex');
Route::get('terms', 'HomeController@showTerms'); Route::get('terms', 'HomeController@showTerms');
@ -95,6 +102,7 @@ Route::group(['middleware' => 'auth'], function() {
Route::post('users/change_password', 'UserController@changePassword'); Route::post('users/change_password', 'UserController@changePassword');
Route::get('/switch_account/{user_id}', 'UserController@switchAccount'); Route::get('/switch_account/{user_id}', 'UserController@switchAccount');
Route::get('/unlink_account/{user_account_id}/{user_id}', 'UserController@unlinkAccount'); Route::get('/unlink_account/{user_account_id}/{user_id}', 'UserController@unlinkAccount');
Route::get('/manage_companies', 'UserController@manageCompanies');
Route::get('api/tokens', array('as'=>'api.tokens', 'uses'=>'TokenController@getDatatable')); Route::get('api/tokens', array('as'=>'api.tokens', 'uses'=>'TokenController@getDatatable'));
Route::resource('tokens', 'TokenController'); Route::resource('tokens', 'TokenController');
@ -130,7 +138,6 @@ Route::group(['middleware' => 'auth'], function() {
Route::get('tasks/create/{client_id?}', 'TaskController@create'); Route::get('tasks/create/{client_id?}', 'TaskController@create');
Route::post('tasks/bulk', 'TaskController@bulk'); Route::post('tasks/bulk', 'TaskController@bulk');
Route::get('recurring_invoices', 'InvoiceController@recurringIndex');
Route::get('api/recurring_invoices/{client_id?}', array('as'=>'api.recurring_invoices', 'uses'=>'InvoiceController@getRecurringDatatable')); Route::get('api/recurring_invoices/{client_id?}', array('as'=>'api.recurring_invoices', 'uses'=>'InvoiceController@getRecurringDatatable'));
Route::get('invoices/invoice_history/{invoice_id}', 'InvoiceController@invoiceHistory'); Route::get('invoices/invoice_history/{invoice_id}', 'InvoiceController@invoiceHistory');
@ -139,6 +146,7 @@ Route::group(['middleware' => 'auth'], function() {
Route::resource('invoices', 'InvoiceController'); Route::resource('invoices', 'InvoiceController');
Route::get('api/invoices/{client_id?}', array('as'=>'api.invoices', 'uses'=>'InvoiceController@getDatatable')); Route::get('api/invoices/{client_id?}', array('as'=>'api.invoices', 'uses'=>'InvoiceController@getDatatable'));
Route::get('invoices/create/{client_id?}', 'InvoiceController@create'); Route::get('invoices/create/{client_id?}', 'InvoiceController@create');
Route::get('recurring_invoices/create/{client_id?}', 'InvoiceController@createRecurring');
Route::get('invoices/{public_id}/clone', 'InvoiceController@cloneInvoice'); Route::get('invoices/{public_id}/clone', 'InvoiceController@cloneInvoice');
Route::post('invoices/bulk', 'InvoiceController@bulk'); Route::post('invoices/bulk', 'InvoiceController@bulk');
@ -211,225 +219,229 @@ Route::get('/forgot_password', function() {
}); });
define('CONTACT_EMAIL', Config::get('mail.from.address')); if (!defined('CONTACT_EMAIL')) {
define('CONTACT_NAME', Config::get('mail.from.name')); define('CONTACT_EMAIL', Config::get('mail.from.address'));
define('SITE_URL', Config::get('app.url')); define('CONTACT_NAME', Config::get('mail.from.name'));
define('SITE_URL', Config::get('app.url'));
define('ENV_DEVELOPMENT', 'local'); define('ENV_DEVELOPMENT', 'local');
define('ENV_STAGING', 'staging'); define('ENV_STAGING', 'staging');
define('ENV_PRODUCTION', 'fortrabbit'); define('ENV_PRODUCTION', 'fortrabbit');
define('RECENTLY_VIEWED', 'RECENTLY_VIEWED'); define('RECENTLY_VIEWED', 'RECENTLY_VIEWED');
define('ENTITY_CLIENT', 'client'); define('ENTITY_CLIENT', 'client');
define('ENTITY_INVOICE', 'invoice'); define('ENTITY_INVOICE', 'invoice');
define('ENTITY_RECURRING_INVOICE', 'recurring_invoice'); define('ENTITY_RECURRING_INVOICE', 'recurring_invoice');
define('ENTITY_PAYMENT', 'payment'); define('ENTITY_PAYMENT', 'payment');
define('ENTITY_CREDIT', 'credit'); define('ENTITY_CREDIT', 'credit');
define('ENTITY_QUOTE', 'quote'); define('ENTITY_QUOTE', 'quote');
define('ENTITY_TASK', 'task'); define('ENTITY_TASK', 'task');
define('PERSON_CONTACT', 'contact'); define('PERSON_CONTACT', 'contact');
define('PERSON_USER', 'user'); define('PERSON_USER', 'user');
define('ACCOUNT_DETAILS', 'details'); define('ACCOUNT_DETAILS', 'details');
define('ACCOUNT_NOTIFICATIONS', 'notifications'); define('ACCOUNT_NOTIFICATIONS', 'notifications');
define('ACCOUNT_IMPORT_EXPORT', 'import_export'); define('ACCOUNT_IMPORT_EXPORT', 'import_export');
define('ACCOUNT_PAYMENTS', 'payments'); define('ACCOUNT_PAYMENTS', 'payments');
define('ACCOUNT_MAP', 'import_map'); define('ACCOUNT_MAP', 'import_map');
define('ACCOUNT_EXPORT', 'export'); define('ACCOUNT_EXPORT', 'export');
define('ACCOUNT_PRODUCTS', 'products'); define('ACCOUNT_PRODUCTS', 'products');
define('ACCOUNT_ADVANCED_SETTINGS', 'advanced_settings'); define('ACCOUNT_ADVANCED_SETTINGS', 'advanced_settings');
define('ACCOUNT_INVOICE_SETTINGS', 'invoice_settings'); define('ACCOUNT_INVOICE_SETTINGS', 'invoice_settings');
define('ACCOUNT_INVOICE_DESIGN', 'invoice_design'); define('ACCOUNT_INVOICE_DESIGN', 'invoice_design');
define('ACCOUNT_CHART_BUILDER', 'chart_builder'); define('ACCOUNT_CHART_BUILDER', 'chart_builder');
define('ACCOUNT_USER_MANAGEMENT', 'user_management'); define('ACCOUNT_USER_MANAGEMENT', 'user_management');
define('ACCOUNT_DATA_VISUALIZATIONS', 'data_visualizations'); define('ACCOUNT_DATA_VISUALIZATIONS', 'data_visualizations');
define('ACCOUNT_EMAIL_TEMPLATES', 'email_templates'); define('ACCOUNT_EMAIL_TEMPLATES', 'email_templates');
define('ACCOUNT_TOKEN_MANAGEMENT', 'token_management'); define('ACCOUNT_TOKEN_MANAGEMENT', 'token_management');
define('ACCOUNT_CUSTOMIZE_DESIGN', 'customize_design');
define('ACTIVITY_TYPE_CREATE_CLIENT', 1);
define('ACTIVITY_TYPE_ARCHIVE_CLIENT', 2);
define('ACTIVITY_TYPE_DELETE_CLIENT', 3);
define('ACTIVITY_TYPE_CREATE_INVOICE', 4);
define('ACTIVITY_TYPE_UPDATE_INVOICE', 5);
define('ACTIVITY_TYPE_EMAIL_INVOICE', 6);
define('ACTIVITY_TYPE_VIEW_INVOICE', 7);
define('ACTIVITY_TYPE_ARCHIVE_INVOICE', 8);
define('ACTIVITY_TYPE_DELETE_INVOICE', 9);
define('ACTIVITY_TYPE_CREATE_PAYMENT', 10);
define('ACTIVITY_TYPE_UPDATE_PAYMENT', 11);
define('ACTIVITY_TYPE_ARCHIVE_PAYMENT', 12);
define('ACTIVITY_TYPE_DELETE_PAYMENT', 13);
define('ACTIVITY_TYPE_CREATE_CREDIT', 14);
define('ACTIVITY_TYPE_UPDATE_CREDIT', 15);
define('ACTIVITY_TYPE_ARCHIVE_CREDIT', 16);
define('ACTIVITY_TYPE_DELETE_CREDIT', 17);
define('ACTIVITY_TYPE_CREATE_QUOTE', 18);
define('ACTIVITY_TYPE_UPDATE_QUOTE', 19);
define('ACTIVITY_TYPE_EMAIL_QUOTE', 20);
define('ACTIVITY_TYPE_VIEW_QUOTE', 21);
define('ACTIVITY_TYPE_ARCHIVE_QUOTE', 22);
define('ACTIVITY_TYPE_DELETE_QUOTE', 23);
define('ACTIVITY_TYPE_RESTORE_QUOTE', 24);
define('ACTIVITY_TYPE_RESTORE_INVOICE', 25);
define('ACTIVITY_TYPE_RESTORE_CLIENT', 26);
define('ACTIVITY_TYPE_RESTORE_PAYMENT', 27);
define('ACTIVITY_TYPE_RESTORE_CREDIT', 28);
define('ACTIVITY_TYPE_APPROVE_QUOTE', 29);
define('DEFAULT_INVOICE_NUMBER', '0001');
define('RECENTLY_VIEWED_LIMIT', 8);
define('LOGGED_ERROR_LIMIT', 100);
define('RANDOM_KEY_LENGTH', 32);
define('MAX_NUM_CLIENTS', 500);
define('MAX_NUM_CLIENTS_PRO', 20000);
define('MAX_NUM_USERS', 20);
define('MAX_SUBDOMAIN_LENGTH', 30);
define('DEFAULT_FONT_SIZE', 9);
define('INVOICE_STATUS_DRAFT', 1);
define('INVOICE_STATUS_SENT', 2);
define('INVOICE_STATUS_VIEWED', 3);
define('INVOICE_STATUS_PARTIAL', 4);
define('INVOICE_STATUS_PAID', 5);
define('PAYMENT_TYPE_CREDIT', 1);
define('FREQUENCY_WEEKLY', 1);
define('FREQUENCY_TWO_WEEKS', 2);
define('FREQUENCY_FOUR_WEEKS', 3);
define('FREQUENCY_MONTHLY', 4);
define('FREQUENCY_THREE_MONTHS', 5);
define('FREQUENCY_SIX_MONTHS', 6);
define('FREQUENCY_ANNUALLY', 7);
define('SESSION_TIMEZONE', 'timezone');
define('SESSION_CURRENCY', 'currency');
define('SESSION_DATE_FORMAT', 'dateFormat');
define('SESSION_DATE_PICKER_FORMAT', 'datePickerFormat');
define('SESSION_DATETIME_FORMAT', 'datetimeFormat');
define('SESSION_COUNTER', 'sessionCounter');
define('SESSION_LOCALE', 'sessionLocale');
define('SESSION_USER_ACCOUNTS', 'userAccounts');
define('SESSION_LAST_REQUEST_PAGE', 'SESSION_LAST_REQUEST_PAGE');
define('SESSION_LAST_REQUEST_TIME', 'SESSION_LAST_REQUEST_TIME');
define('DEFAULT_TIMEZONE', 'US/Eastern');
define('DEFAULT_CURRENCY', 1); // US Dollar
define('DEFAULT_DATE_FORMAT', 'M j, Y');
define('DEFAULT_DATE_PICKER_FORMAT', 'M d, yyyy');
define('DEFAULT_DATETIME_FORMAT', 'F j, Y, g:i a');
define('DEFAULT_QUERY_CACHE', 120); // minutes
define('DEFAULT_LOCALE', 'en');
define('RESULT_SUCCESS', 'success');
define('RESULT_FAILURE', 'failure');
define('PAYMENT_LIBRARY_OMNIPAY', 1); define('ACTIVITY_TYPE_CREATE_CLIENT', 1);
define('PAYMENT_LIBRARY_PHP_PAYMENTS', 2); define('ACTIVITY_TYPE_ARCHIVE_CLIENT', 2);
define('ACTIVITY_TYPE_DELETE_CLIENT', 3);
define('GATEWAY_AUTHORIZE_NET', 1); define('ACTIVITY_TYPE_CREATE_INVOICE', 4);
define('GATEWAY_AUTHORIZE_NET_SIM', 2); define('ACTIVITY_TYPE_UPDATE_INVOICE', 5);
define('GATEWAY_PAYPAL_EXPRESS', 17); define('ACTIVITY_TYPE_EMAIL_INVOICE', 6);
define('GATEWAY_PAYPAL_PRO', 18); define('ACTIVITY_TYPE_VIEW_INVOICE', 7);
define('GATEWAY_STRIPE', 23); define('ACTIVITY_TYPE_ARCHIVE_INVOICE', 8);
define('GATEWAY_TWO_CHECKOUT', 27); define('ACTIVITY_TYPE_DELETE_INVOICE', 9);
define('GATEWAY_BEANSTREAM', 29);
define('GATEWAY_PSIGATE', 30);
define('GATEWAY_MOOLAH', 31);
define('GATEWAY_BITPAY', 42);
define('GATEWAY_DWOLLA', 43);
define('EVENT_CREATE_CLIENT', 1); define('ACTIVITY_TYPE_CREATE_PAYMENT', 10);
define('EVENT_CREATE_INVOICE', 2); define('ACTIVITY_TYPE_UPDATE_PAYMENT', 11);
define('EVENT_CREATE_QUOTE', 3); define('ACTIVITY_TYPE_ARCHIVE_PAYMENT', 12);
define('EVENT_CREATE_PAYMENT', 4); define('ACTIVITY_TYPE_DELETE_PAYMENT', 13);
define('REQUESTED_PRO_PLAN', 'REQUESTED_PRO_PLAN'); define('ACTIVITY_TYPE_CREATE_CREDIT', 14);
define('DEMO_ACCOUNT_ID', 'DEMO_ACCOUNT_ID'); define('ACTIVITY_TYPE_UPDATE_CREDIT', 15);
define('PREV_USER_ID', 'PREV_USER_ID'); define('ACTIVITY_TYPE_ARCHIVE_CREDIT', 16);
define('NINJA_ACCOUNT_KEY', 'zg4ylmzDkdkPOT8yoKQw9LTWaoZJx79h'); define('ACTIVITY_TYPE_DELETE_CREDIT', 17);
define('NINJA_GATEWAY_ID', GATEWAY_STRIPE);
define('NINJA_GATEWAY_CONFIG', '');
define('NINJA_WEB_URL', 'https://www.invoiceninja.com');
define('NINJA_APP_URL', 'https://app.invoiceninja.com');
define('NINJA_VERSION', '2.2.2');
define('NINJA_DATE', '2000-01-01');
define('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com');
define('RELEASES_URL', 'https://github.com/hillelcoren/invoice-ninja/releases/');
define('ZAPIER_URL', 'https://zapier.com/developer/invite/11276/85cf0ee4beae8e802c6c579eb4e351f1/');
define('OUTDATE_BROWSER_URL', 'http://browsehappy.com/');
define('COUNT_FREE_DESIGNS', 4); define('ACTIVITY_TYPE_CREATE_QUOTE', 18);
define('PRODUCT_ONE_CLICK_INSTALL', 1); define('ACTIVITY_TYPE_UPDATE_QUOTE', 19);
define('PRODUCT_INVOICE_DESIGNS', 2); define('ACTIVITY_TYPE_EMAIL_QUOTE', 20);
define('PRODUCT_WHITE_LABEL', 3); define('ACTIVITY_TYPE_VIEW_QUOTE', 21);
define('PRODUCT_SELF_HOST', 4); define('ACTIVITY_TYPE_ARCHIVE_QUOTE', 22);
define('WHITE_LABEL_AFFILIATE_KEY', '92D2J5'); define('ACTIVITY_TYPE_DELETE_QUOTE', 23);
define('INVOICE_DESIGNS_AFFILIATE_KEY', 'T3RS74');
define('SELF_HOST_AFFILIATE_KEY', '8S69AD');
define('PRO_PLAN_PRICE', 50); define('ACTIVITY_TYPE_RESTORE_QUOTE', 24);
define('WHITE_LABEL_PRICE', 20); define('ACTIVITY_TYPE_RESTORE_INVOICE', 25);
define('INVOICE_DESIGNS_PRICE', 10); define('ACTIVITY_TYPE_RESTORE_CLIENT', 26);
define('ACTIVITY_TYPE_RESTORE_PAYMENT', 27);
define('ACTIVITY_TYPE_RESTORE_CREDIT', 28);
define('ACTIVITY_TYPE_APPROVE_QUOTE', 29);
define('USER_TYPE_SELF_HOST', 'SELF_HOST'); define('DEFAULT_INVOICE_NUMBER', '0001');
define('USER_TYPE_CLOUD_HOST', 'CLOUD_HOST'); define('RECENTLY_VIEWED_LIMIT', 8);
define('NEW_VERSION_AVAILABLE', 'NEW_VERSION_AVAILABLE'); define('LOGGED_ERROR_LIMIT', 100);
define('RANDOM_KEY_LENGTH', 32);
define('MAX_NUM_CLIENTS', 500);
define('MAX_NUM_CLIENTS_PRO', 20000);
define('MAX_NUM_USERS', 20);
define('MAX_SUBDOMAIN_LENGTH', 30);
define('DEFAULT_FONT_SIZE', 9);
define('TOKEN_BILLING_DISABLED', 1); define('INVOICE_STATUS_DRAFT', 1);
define('TOKEN_BILLING_OPT_IN', 2); define('INVOICE_STATUS_SENT', 2);
define('TOKEN_BILLING_OPT_OUT', 3); define('INVOICE_STATUS_VIEWED', 3);
define('TOKEN_BILLING_ALWAYS', 4); define('INVOICE_STATUS_PARTIAL', 4);
define('INVOICE_STATUS_PAID', 5);
define('PAYMENT_TYPE_PAYPAL', 'PAYMENT_TYPE_PAYPAL'); define('PAYMENT_TYPE_CREDIT', 1);
define('PAYMENT_TYPE_CREDIT_CARD', 'PAYMENT_TYPE_CREDIT_CARD'); define('CUSTOM_DESIGN', 11);
define('PAYMENT_TYPE_BITCOIN', 'PAYMENT_TYPE_BITCOIN');
define('PAYMENT_TYPE_DWOLLA', 'PAYMENT_TYPE_DWOLLA');
define('PAYMENT_TYPE_TOKEN', 'PAYMENT_TYPE_TOKEN');
define('PAYMENT_TYPE_ANY', 'PAYMENT_TYPE_ANY');
/* define('FREQUENCY_WEEKLY', 1);
define('GATEWAY_AMAZON', 30); define('FREQUENCY_TWO_WEEKS', 2);
define('GATEWAY_BLUEPAY', 31); define('FREQUENCY_FOUR_WEEKS', 3);
define('GATEWAY_BRAINTREE', 32); define('FREQUENCY_MONTHLY', 4);
define('GATEWAY_GOOGLE', 33); define('FREQUENCY_THREE_MONTHS', 5);
define('GATEWAY_QUICKBOOKS', 35); define('FREQUENCY_SIX_MONTHS', 6);
*/ define('FREQUENCY_ANNUALLY', 7);
$creditCards = [ define('SESSION_TIMEZONE', 'timezone');
1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'], define('SESSION_CURRENCY', 'currency');
2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'], define('SESSION_DATE_FORMAT', 'dateFormat');
4 => ['card' => 'images/credit_cards/Test-AmericanExpress-Icon.png', 'text' => 'American Express'], define('SESSION_DATE_PICKER_FORMAT', 'datePickerFormat');
8 => ['card' => 'images/credit_cards/Test-Diners-Icon.png', 'text' => 'Diners'], define('SESSION_DATETIME_FORMAT', 'datetimeFormat');
16 => ['card' => 'images/credit_cards/Test-Discover-Icon.png', 'text' => 'Discover'] define('SESSION_COUNTER', 'sessionCounter');
]; define('SESSION_LOCALE', 'sessionLocale');
define('SESSION_USER_ACCOUNTS', 'userAccounts');
define('CREDIT_CARDS', serialize($creditCards)); define('SESSION_LAST_REQUEST_PAGE', 'SESSION_LAST_REQUEST_PAGE');
define('SESSION_LAST_REQUEST_TIME', 'SESSION_LAST_REQUEST_TIME');
function uctrans($text) define('DEFAULT_TIMEZONE', 'US/Eastern');
{ define('DEFAULT_CURRENCY', 1); // US Dollar
return ucwords(trans($text)); define('DEFAULT_LANGUAGE', 1); // English
} define('DEFAULT_DATE_FORMAT', 'M j, Y');
define('DEFAULT_DATE_PICKER_FORMAT', 'M d, yyyy');
define('DEFAULT_DATETIME_FORMAT', 'F j, Y, g:i a');
define('DEFAULT_QUERY_CACHE', 120); // minutes
define('DEFAULT_LOCALE', 'en');
// optional trans: only return the string if it's translated define('RESULT_SUCCESS', 'success');
function otrans($text) define('RESULT_FAILURE', 'failure');
{
$locale = Session::get(SESSION_LOCALE);
if ($locale == 'en') {
return trans($text); define('PAYMENT_LIBRARY_OMNIPAY', 1);
} else { define('PAYMENT_LIBRARY_PHP_PAYMENTS', 2);
$string = trans($text);
$english = trans($text, [], 'en'); define('GATEWAY_AUTHORIZE_NET', 1);
return $string != $english ? $string : ''; define('GATEWAY_AUTHORIZE_NET_SIM', 2);
define('GATEWAY_PAYPAL_EXPRESS', 17);
define('GATEWAY_PAYPAL_PRO', 18);
define('GATEWAY_STRIPE', 23);
define('GATEWAY_TWO_CHECKOUT', 27);
define('GATEWAY_BEANSTREAM', 29);
define('GATEWAY_PSIGATE', 30);
define('GATEWAY_MOOLAH', 31);
define('GATEWAY_BITPAY', 42);
define('GATEWAY_DWOLLA', 43);
define('EVENT_CREATE_CLIENT', 1);
define('EVENT_CREATE_INVOICE', 2);
define('EVENT_CREATE_QUOTE', 3);
define('EVENT_CREATE_PAYMENT', 4);
define('REQUESTED_PRO_PLAN', 'REQUESTED_PRO_PLAN');
define('DEMO_ACCOUNT_ID', 'DEMO_ACCOUNT_ID');
define('PREV_USER_ID', 'PREV_USER_ID');
define('NINJA_ACCOUNT_KEY', 'zg4ylmzDkdkPOT8yoKQw9LTWaoZJx79h');
define('NINJA_GATEWAY_ID', GATEWAY_STRIPE);
define('NINJA_GATEWAY_CONFIG', '');
define('NINJA_WEB_URL', 'https://www.invoiceninja.com');
define('NINJA_APP_URL', 'https://app.invoiceninja.com');
define('NINJA_VERSION', '2.3.4');
define('NINJA_DATE', '2000-01-01');
define('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com');
define('RELEASES_URL', 'https://github.com/hillelcoren/invoice-ninja/releases/');
define('ZAPIER_URL', 'https://zapier.com/developer/invite/11276/85cf0ee4beae8e802c6c579eb4e351f1/');
define('OUTDATE_BROWSER_URL', 'http://browsehappy.com/');
define('PDFMAKE_DOCS', 'http://pdfmake.org/playground.html');
define('COUNT_FREE_DESIGNS', 4);
define('COUNT_FREE_DESIGNS_SELF_HOST', 5); // include the custom design
define('PRODUCT_ONE_CLICK_INSTALL', 1);
define('PRODUCT_INVOICE_DESIGNS', 2);
define('PRODUCT_WHITE_LABEL', 3);
define('PRODUCT_SELF_HOST', 4);
define('WHITE_LABEL_AFFILIATE_KEY', '92D2J5');
define('INVOICE_DESIGNS_AFFILIATE_KEY', 'T3RS74');
define('SELF_HOST_AFFILIATE_KEY', '8S69AD');
define('PRO_PLAN_PRICE', 50);
define('WHITE_LABEL_PRICE', 20);
define('INVOICE_DESIGNS_PRICE', 10);
define('USER_TYPE_SELF_HOST', 'SELF_HOST');
define('USER_TYPE_CLOUD_HOST', 'CLOUD_HOST');
define('NEW_VERSION_AVAILABLE', 'NEW_VERSION_AVAILABLE');
define('TEST_USERNAME', 'user@example.com');
define('TEST_PASSWORD', 'password');
define('TOKEN_BILLING_DISABLED', 1);
define('TOKEN_BILLING_OPT_IN', 2);
define('TOKEN_BILLING_OPT_OUT', 3);
define('TOKEN_BILLING_ALWAYS', 4);
define('PAYMENT_TYPE_PAYPAL', 'PAYMENT_TYPE_PAYPAL');
define('PAYMENT_TYPE_CREDIT_CARD', 'PAYMENT_TYPE_CREDIT_CARD');
define('PAYMENT_TYPE_BITCOIN', 'PAYMENT_TYPE_BITCOIN');
define('PAYMENT_TYPE_DWOLLA', 'PAYMENT_TYPE_DWOLLA');
define('PAYMENT_TYPE_TOKEN', 'PAYMENT_TYPE_TOKEN');
define('PAYMENT_TYPE_ANY', 'PAYMENT_TYPE_ANY');
$creditCards = [
1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'],
2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'],
4 => ['card' => 'images/credit_cards/Test-AmericanExpress-Icon.png', 'text' => 'American Express'],
8 => ['card' => 'images/credit_cards/Test-Diners-Icon.png', 'text' => 'Diners'],
16 => ['card' => 'images/credit_cards/Test-Discover-Icon.png', 'text' => 'Discover']
];
define('CREDIT_CARDS', serialize($creditCards));
function uctrans($text)
{
return ucwords(trans($text));
}
// optional trans: only return the string if it's translated
function otrans($text)
{
$locale = Session::get(SESSION_LOCALE);
if ($locale == 'en') {
return trans($text);
} else {
$string = trans($text);
$english = trans($text, [], 'en');
return $string != $english ? $string : '';
}
} }
} }
@ -465,4 +477,4 @@ if (Auth::check() && Auth::user()->id === 1)
{ {
Auth::loginUsingId(1); Auth::loginUsingId(1);
} }
*/ */

View File

@ -3,6 +3,7 @@
use Auth; use Auth;
use Cache; use Cache;
use DB; use DB;
use App;
use Schema; use Schema;
use Session; use Session;
use Request; use Request;
@ -61,7 +62,7 @@ class Utils
public static function allowNewAccounts() public static function allowNewAccounts()
{ {
return Utils::isNinja() || (isset($_ENV['ALLOW_NEW_ACCOUNTS']) && $_ENV['ALLOW_NEW_ACCOUNTS'] == 'true'); return Utils::isNinja() || Auth::check();
} }
public static function isPro() public static function isPro()
@ -69,6 +70,11 @@ class Utils
return Auth::check() && Auth::user()->isPro(); return Auth::check() && Auth::user()->isPro();
} }
public static function isEnglish()
{
return App::getLocale() == 'en';
}
public static function getUserType() public static function getUserType()
{ {
if (Utils::isNinja()) { if (Utils::isNinja()) {
@ -251,6 +257,10 @@ class Utils
$currency = Currency::find(1); $currency = Currency::find(1);
} }
if (!$value) {
$value = 0;
}
Cache::add('currency', $currency, DEFAULT_QUERY_CACHE); Cache::add('currency', $currency, DEFAULT_QUERY_CACHE);
return $currency->symbol.number_format($value, $currency->precision, $currency->decimal_separator, $currency->thousand_separator); return $currency->symbol.number_format($value, $currency->precision, $currency->decimal_separator, $currency->thousand_separator);
@ -315,16 +325,27 @@ class Utils
return $date->format($format); return $date->format($format);
} }
public static function getTiemstampOffset()
{
$timezone = new DateTimeZone(Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE));
$datetime = new DateTime('now', $timezone);
$offset = $timezone->getOffset($datetime);
$minutes = $offset / 60;
return $minutes;
}
public static function toSqlDate($date, $formatResult = true) public static function toSqlDate($date, $formatResult = true)
{ {
if (!$date) { if (!$date) {
return; return;
} }
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE); //$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT); $format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
$dateTime = DateTime::createFromFormat($format, $date, new DateTimeZone($timezone)); //$dateTime = DateTime::createFromFormat($format, $date, new DateTimeZone($timezone));
$dateTime = DateTime::createFromFormat($format, $date);
return $formatResult ? $dateTime->format('Y-m-d') : $dateTime; return $formatResult ? $dateTime->format('Y-m-d') : $dateTime;
} }
@ -335,11 +356,11 @@ class Utils
return ''; return '';
} }
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE); //$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT); $format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
$dateTime = DateTime::createFromFormat('Y-m-d', $date); $dateTime = DateTime::createFromFormat('Y-m-d', $date);
$dateTime->setTimeZone(new DateTimeZone($timezone)); //$dateTime->setTimeZone(new DateTimeZone($timezone));
return $formatResult ? $dateTime->format($format) : $dateTime; return $formatResult ? $dateTime->format($format) : $dateTime;
} }
@ -352,7 +373,7 @@ class Utils
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE); $timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
$format = Session::get(SESSION_DATETIME_FORMAT, DEFAULT_DATETIME_FORMAT); $format = Session::get(SESSION_DATETIME_FORMAT, DEFAULT_DATETIME_FORMAT);
$dateTime = DateTime::createFromFormat('Y-m-d H:i:s', $date); $dateTime = DateTime::createFromFormat('Y-m-d H:i:s', $date);
$dateTime->setTimeZone(new DateTimeZone($timezone)); $dateTime->setTimeZone(new DateTimeZone($timezone));
@ -386,10 +407,12 @@ class Utils
} }
$object = new stdClass(); $object = new stdClass();
$object->accountId = Auth::user()->account_id;
$object->url = $url; $object->url = $url;
$object->name = ucwords($type).': '.$name; $object->name = ucwords($type).': '.$name;
$data = []; $data = [];
$counts = [];
for ($i = 0; $i<count($viewed); $i++) { for ($i = 0; $i<count($viewed); $i++) {
$item = $viewed[$i]; $item = $viewed[$i];
@ -398,12 +421,22 @@ class Utils
continue; continue;
} }
// temporary fix to check for new property in session
if (!property_exists($item, 'accountId')) {
continue;
}
array_unshift($data, $item); array_unshift($data, $item);
if (isset($counts[$item->accountId])) {
$counts[$item->accountId]++;
} else {
$counts[$item->accountId] = 1;
}
} }
array_unshift($data, $object); array_unshift($data, $object);
if (count($data) > RECENTLY_VIEWED_LIMIT) { if (isset($counts[Auth::user()->account_id]) && $counts[Auth::user()->account_id] > RECENTLY_VIEWED_LIMIT) {
array_pop($data); array_pop($data);
} }
@ -677,4 +710,37 @@ class Utils
fwrite($output, "\n"); fwrite($output, "\n");
} }
public static function stringToObjectResolution($baseObject, $rawPath)
{
$val = '';
if (!is_object($baseObject)) {
return $val;
}
$path = preg_split('/->/', $rawPath);
$node = $baseObject;
while (($prop = array_shift($path)) !== null) {
if (property_exists($node, $prop)) {
$val = $node->$prop;
$node = $node->$prop;
} else if (is_object($node) && isset($node->$prop)) {
$node = $node->{$prop};
} else if ( method_exists($node, $prop)) {
$val = call_user_func(array($node, $prop));
}
}
return $val;
}
public static function getFirst($values) {
if (is_array($values)) {
return count($values) ? $values[0] : false;
} else {
return $values;
}
}
} }

View File

@ -27,11 +27,13 @@ class HandleUserSettingsChanged {
*/ */
public function handle(UserSettingsChanged $event) public function handle(UserSettingsChanged $event)
{ {
$account = Auth::user()->account; if (Auth::check()) {
$account->loadLocalizationSettings(); $account = Auth::user()->account;
$account->loadLocalizationSettings();
$users = $this->accountRepo->loadAccounts(Auth::user()->id); $users = $this->accountRepo->loadAccounts(Auth::user()->id);
Session::put(SESSION_USER_ACCOUNTS, $users); Session::put(SESSION_USER_ACCOUNTS, $users);
}
} }
} }

View File

@ -4,7 +4,9 @@ use Eloquent;
use Utils; use Utils;
use Session; use Session;
use DateTime; use DateTime;
use Event;
use App;
use App\Events\UserSettingsChanged;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class Account extends Eloquent class Account extends Eloquent
@ -12,6 +14,12 @@ class Account extends Eloquent
use SoftDeletes; use SoftDeletes;
protected $dates = ['deleted_at']; protected $dates = ['deleted_at'];
/*
protected $casts = [
'hide_quantity' => 'boolean',
];
*/
public function users() public function users()
{ {
return $this->hasMany('App\Models\User'); return $this->hasMany('App\Models\User');
@ -88,6 +96,11 @@ class Account extends Eloquent
} }
} }
public function isEnglish()
{
return !$this->language_id || $this->language_id == DEFAULT_LANGUAGE;
}
public function getDisplayName() public function getDisplayName()
{ {
if ($this->name) { if ($this->name) {
@ -142,7 +155,9 @@ class Account extends Eloquent
public function getLogoPath() public function getLogoPath()
{ {
return 'logo/'.$this->account_key.'.jpg'; $fileName = 'logo/' . $this->account_key;
return file_exists($fileName.'.png') ? $fileName.'.png' : $fileName.'.jpg';
} }
public function getLogoWidth() public function getLogoWidth()
@ -171,34 +186,36 @@ class Account extends Eloquent
{ {
$counter = $isQuote && !$this->share_counter ? $this->quote_number_counter : $this->invoice_number_counter; $counter = $isQuote && !$this->share_counter ? $this->quote_number_counter : $this->invoice_number_counter;
$prefix .= $isQuote ? $this->quote_number_prefix : $this->invoice_number_prefix; $prefix .= $isQuote ? $this->quote_number_prefix : $this->invoice_number_prefix;
$counterOffset = 0;
// confirm the invoice number isn't already taken // confirm the invoice number isn't already taken
do { do {
$number = $prefix.str_pad($counter, 4, "0", STR_PAD_LEFT); $number = $prefix.str_pad($counter, 4, "0", STR_PAD_LEFT);
$check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first(); $check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first();
$counter++; $counter++;
$counterOffset++;
} while ($check); } while ($check);
// update the invoice counter to be caught up
if ($counterOffset > 1) {
if ($isQuote && !$this->share_counter) {
$this->quote_number_counter += $counterOffset - 1;
} else {
$this->invoice_number_counter += $counterOffset - 1;
}
$this->save();
}
return $number; return $number;
} }
public function incrementCounter($invoiceNumber, $isQuote = false, $isRecurring) public function incrementCounter($isQuote = false)
{ {
// check if the user modified the invoice number if ($isQuote && !$this->share_counter) {
if (!$isRecurring && $invoiceNumber != $this->getNextInvoiceNumber($isQuote)) { $this->quote_number_counter += 1;
$number = intval(preg_replace('/[^0-9]/', '', $invoiceNumber));
if ($isQuote && !$this->share_counter) {
$this->quote_number_counter = $number + 1;
} else {
$this->invoice_number_counter = $number + 1;
}
// otherwise, just increment the counter
} else { } else {
if ($isQuote && !$this->share_counter) { $this->invoice_number_counter += 1;
$this->quote_number_counter += 1;
} else {
$this->invoice_number_counter += 1;
}
} }
$this->save(); $this->save();
@ -221,6 +238,8 @@ class Account extends Eloquent
Session::put(SESSION_DATETIME_FORMAT, $this->datetime_format ? $this->datetime_format->format : DEFAULT_DATETIME_FORMAT); Session::put(SESSION_DATETIME_FORMAT, $this->datetime_format ? $this->datetime_format->format : DEFAULT_DATETIME_FORMAT);
Session::put(SESSION_CURRENCY, $this->currency_id ? $this->currency_id : DEFAULT_CURRENCY); Session::put(SESSION_CURRENCY, $this->currency_id ? $this->currency_id : DEFAULT_CURRENCY);
Session::put(SESSION_LOCALE, $this->language_id ? $this->language->locale : DEFAULT_LOCALE); Session::put(SESSION_LOCALE, $this->language_id ? $this->language->locale : DEFAULT_LOCALE);
App::setLocale(session(SESSION_LOCALE));
} }
public function getInvoiceLabels() public function getInvoiceLabels()
@ -258,13 +277,18 @@ class Account extends Eloquent
'rate', 'rate',
'hours', 'hours',
'balance', 'balance',
'from',
'to',
'invoice_to',
'details',
'invoice_no',
]; ];
foreach ($fields as $field) { foreach ($fields as $field) {
if (isset($custom[$field]) && $custom[$field]) { if (isset($custom[$field]) && $custom[$field]) {
$data[$field] = $custom[$field]; $data[$field] = $custom[$field];
} else { } else {
$data[$field] = uctrans("texts.$field"); $data[$field] = $this->isEnglish() ? uctrans("texts.$field") : trans("texts.$field");
} }
} }
@ -303,10 +327,10 @@ class Account extends Eloquent
public function isWhiteLabel() public function isWhiteLabel()
{ {
if (Utils::isNinjaProd()) { if (Utils::isNinjaProd()) {
return false; return self::isPro() && $this->pro_plan_paid != NINJA_DATE;
} else {
return $this->pro_plan_paid == NINJA_DATE;
} }
return $this->pro_plan_paid == NINJA_DATE;
} }
public function getSubscription($eventId) public function getSubscription($eventId)
@ -335,6 +359,8 @@ class Account extends Eloquent
'invoice_status_id', 'invoice_status_id',
'invoice_items', 'invoice_items',
'created_at', 'created_at',
'is_recurring',
'is_quote',
]); ]);
foreach ($invoice->invoice_items as $invoiceItem) { foreach ($invoice->invoice_items as $invoiceItem) {
@ -407,9 +433,6 @@ class Account extends Eloquent
} }
} }
Account::updating(function ($account) { Account::updated(function ($account) {
// Lithuanian requires UTF8 support Event::fire(new UserSettingsChanged());
if (!Utils::isPro()) {
$account->utf8_invoices = ($account->language_id == 13) ? 1 : 0;
}
}); });

View File

@ -214,6 +214,8 @@ class Activity extends Eloquent
if ($invoice->isPaid() && $invoice->balance > 0) { if ($invoice->isPaid() && $invoice->balance > 0) {
$invoice->invoice_status_id = INVOICE_STATUS_PARTIAL; $invoice->invoice_status_id = INVOICE_STATUS_PARTIAL;
} elseif ($invoice->invoice_status_id && $invoice->balance == 0) {
$invoice->invoice_status_id = INVOICE_STATUS_PAID;
} }
} }
} }

View File

@ -76,15 +76,13 @@ class Client extends EntityModel
{ {
return $this->name; return $this->name;
} }
public function getDisplayName() public function getDisplayName()
{ {
if ($this->name) { if ($this->name) {
return $this->name; return $this->name;
} }
$this->load('contacts');
$contact = $this->contacts()->first(); $contact = $this->contacts()->first();
return $contact->getDisplayName(); return $contact->getDisplayName();
@ -152,11 +150,15 @@ class Client extends EntityModel
public function getCurrencyId() public function getCurrencyId()
{ {
if ($this->currency_id) {
return $this->currency_id;
}
if (!$this->account) { if (!$this->account) {
$this->load('account'); $this->load('account');
} }
return $this->currency_id ?: ($this->account->currency_id ?: DEFAULT_CURRENCY); return $this->account->currency_id ?: DEFAULT_CURRENCY;
} }
} }

View File

@ -44,7 +44,7 @@ class EntityModel extends Eloquent
public function getActivityKey() public function getActivityKey()
{ {
return '[' . $this->getEntityType().':'.$this->public_id.':'.$this->getName() . ']'; return '[' . $this->getEntityType().':'.$this->public_id.':'.$this->getDisplayName() . ']';
} }
/* /*
@ -83,6 +83,11 @@ class EntityModel extends Eloquent
return $this->public_id; return $this->public_id;
} }
public function getDisplayName()
{
return $this->getName();
}
// Remap ids to public_ids and show name // Remap ids to public_ids and show name
public function toPublicArray() public function toPublicArray()
{ {

View File

@ -48,6 +48,11 @@ class Invoice extends EntityModel
return $this->belongsTo('App\Models\Invoice'); return $this->belongsTo('App\Models\Invoice');
} }
public function recurring_invoices()
{
return $this->hasMany('App\Models\Invoice', 'recurring_invoice_id');
}
public function invitations() public function invitations()
{ {
return $this->hasMany('App\Models\Invitation')->orderBy('invitations.contact_id'); return $this->hasMany('App\Models\Invitation')->orderBy('invitations.contact_id');
@ -55,7 +60,7 @@ class Invoice extends EntityModel
public function getName() public function getName()
{ {
return $this->invoice_number; return $this->is_recurring ? trans('texts.recurring') : $this->invoice_number;
} }
public function getFileName() public function getFileName()
@ -69,9 +74,14 @@ class Invoice extends EntityModel
return storage_path() . '/pdfcache/cache-' . $this->id . '.pdf'; return storage_path() . '/pdfcache/cache-' . $this->id . '.pdf';
} }
public static function calcLink($invoice)
{
return link_to('invoices/' . $invoice->public_id, $invoice->invoice_number);
}
public function getLink() public function getLink()
{ {
return link_to('invoices/'.$this->public_id, $this->invoice_number); return self::calcLink($this);
} }
public function getEntityType() public function getEntityType()
@ -252,8 +262,13 @@ class Invoice extends EntityModel
} }
} }
Invoice::creating(function ($invoice) {
if (!$invoice->is_recurring) {
$invoice->account->incrementCounter($invoice->is_quote);
}
});
Invoice::created(function ($invoice) { Invoice::created(function ($invoice) {
$invoice->account->incrementCounter($invoice->invoice_number, $invoice->is_quote, $invoice->recurring_invoice_id);
Activity::createInvoice($invoice); Activity::createInvoice($invoice);
}); });
@ -267,4 +282,4 @@ Invoice::deleting(function ($invoice) {
Invoice::restoring(function ($invoice) { Invoice::restoring(function ($invoice) {
Activity::restoreInvoice($invoice); Activity::restoreInvoice($invoice);
}); });

View File

@ -2,22 +2,35 @@
use Eloquent; use Eloquent;
use Auth; use Auth;
use Cache;
use App\Models\InvoiceDesign;
class InvoiceDesign extends Eloquent class InvoiceDesign extends Eloquent
{ {
public $timestamps = false; public $timestamps = false;
public function scopeAvailableDesigns($query) public static function getDesigns()
{ {
$designs = $query->where('id', '<=', \Auth::user()->maxInvoiceDesignId())->orderBy('id')->get(); $account = Auth::user()->account;
$designs = Cache::get('invoiceDesigns');
foreach ($designs as $design) { foreach ($designs as $design) {
$fileName = public_path(strtolower("js/templates/{$design->name}.js")); if ($design->id > Auth::user()->maxInvoiceDesignId()) {
if (Auth::user()->account->utf8_invoices && file_exists($fileName)) { $designs->pull($design->id);
$design->javascript = file_get_contents($fileName); }
$design->javascript = $design->pdfmake;
$design->pdfmake = null;
if ($design->id == CUSTOM_DESIGN) {
if ($account->custom_design) {
$design->javascript = $account->custom_design;
} else {
$designs->pop();
}
} }
} }
return $designs; return $designs;
} }
} }

View File

@ -1,7 +1,7 @@
<?php namespace App\Models; <?php namespace App\Models;
use DB; use DB;
use Utils;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class Task extends EntityModel class Task extends EntityModel
@ -22,6 +22,66 @@ class Task extends EntityModel
{ {
return $this->belongsTo('App\Models\Client')->withTrashed(); return $this->belongsTo('App\Models\Client')->withTrashed();
} }
public static function calcStartTime($task)
{
$parts = json_decode($task->time_log) ?: [];
if (count($parts)) {
return Utils::timestampToDateTimeString($parts[0][0]);
} else {
return '';
}
}
public function getStartTime()
{
return self::calcStartTime($this);
}
public static function calcDuration($task)
{
$duration = 0;
$parts = json_decode($task->time_log) ?: [];
foreach ($parts as $part) {
if (count($part) == 1 || !$part[1]) {
$duration += time() - $part[0];
} else {
$duration += $part[1] - $part[0];
}
}
return $duration;
}
public function getDuration()
{
return self::calcDuration($this);
}
public function getCurrentDuration()
{
$parts = json_decode($this->time_log) ?: [];
$part = $parts[count($parts)-1];
if (count($part) == 1 || !$part[1]) {
return time() - $part[0];
} else {
return 0;
}
}
public function hasPreviousDuration()
{
$parts = json_decode($this->time_log) ?: [];
return count($parts) && (count($parts[0]) && $parts[0][1]);
}
public function getHours()
{
return round($this->getDuration() / (60 * 60), 2);
}
} }
Task::created(function ($task) { Task::created(function ($task) {

View File

@ -2,7 +2,9 @@
use Session; use Session;
use Auth; use Auth;
use Event;
use App\Libraries\Utils; use App\Libraries\Utils;
use App\Events\UserSettingsChanged;
use Illuminate\Auth\Authenticatable; use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Auth\Passwords\CanResetPassword;
@ -100,7 +102,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
public function maxInvoiceDesignId() public function maxInvoiceDesignId()
{ {
return $this->isPro() ? 10 : COUNT_FREE_DESIGNS; return $this->isPro() ? 11 : (Utils::isNinja() ? COUNT_FREE_DESIGNS : COUNT_FREE_DESIGNS_SELF_HOST);
} }
public function getDisplayName() public function getDisplayName()
@ -213,3 +215,8 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
User::updating(function ($user) { User::updating(function ($user) {
User::updateUser($user); User::updateUser($user);
}); });
User::updated(function ($user) {
Event::fire(new UserSettingsChanged());
});

View File

@ -23,6 +23,8 @@ class ContactMailer extends Mailer
$emailTemplate = $invoice->account->getEmailTemplate($entityType); $emailTemplate = $invoice->account->getEmailTemplate($entityType);
$invoiceAmount = Utils::formatMoney($invoice->getRequestedAmount(), $invoice->client->getCurrencyId()); $invoiceAmount = Utils::formatMoney($invoice->getRequestedAmount(), $invoice->client->getCurrencyId());
$this->initClosure($invoice);
foreach ($invoice->invitations as $invitation) { foreach ($invoice->invitations as $invitation) {
if (!$invitation->user || !$invitation->user->email || $invitation->user->trashed()) { if (!$invitation->user || !$invitation->user->email || $invitation->user->trashed()) {
return false; return false;
@ -40,7 +42,8 @@ class ContactMailer extends Mailer
'$client' => $invoice->client->getDisplayName(), '$client' => $invoice->client->getDisplayName(),
'$account' => $accountName, '$account' => $accountName,
'$contact' => $invitation->contact->getDisplayName(), '$contact' => $invitation->contact->getDisplayName(),
'$amount' => $invoiceAmount '$amount' => $invoiceAmount,
'$advancedRawInvoice->' => '$'
]; ];
// Add variables for available payment types // Add variables for available payment types
@ -49,6 +52,7 @@ class ContactMailer extends Mailer
} }
$data['body'] = str_replace(array_keys($variables), array_values($variables), $emailTemplate); $data['body'] = str_replace(array_keys($variables), array_values($variables), $emailTemplate);
$data['body'] = preg_replace_callback('/\{\{\$?(.*)\}\}/', $this->advancedTemplateHandler, $data['body']);
$data['link'] = $invitation->getLink(); $data['link'] = $invitation->getLink();
$data['entityType'] = $entityType; $data['entityType'] = $entityType;
$data['invoice_id'] = $invoice->id; $data['invoice_id'] = $invoice->id;
@ -69,6 +73,8 @@ class ContactMailer extends Mailer
} }
Event::fire(new InvoiceSent($invoice)); Event::fire(new InvoiceSent($invoice));
return $response;
} }
public function sendPaymentConfirmation(Payment $payment) public function sendPaymentConfirmation(Payment $payment)
@ -123,4 +129,22 @@ class ContactMailer extends Mailer
$this->sendTo($email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data); $this->sendTo($email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
} }
private function initClosure($object)
{
$this->advancedTemplateHandler = function($match) use ($object) {
for ($i = 1; $i < count($match); $i++) {
$blobConversion = $match[$i];
if (isset($$blobConversion)) {
return $$blobConversion;
} else if (preg_match('/trans\(([\w\.]+)\)/', $blobConversion, $regexTranslation)) {
return trans($regexTranslation[1]);
} else if (strpos($blobConversion, '->') !== false) {
return Utils::stringToObjectResolution($object, $blobConversion);
}
}
};
}
} }

View File

@ -17,10 +17,11 @@ class Mailer
try { try {
Mail::send($views, $data, function ($message) use ($toEmail, $fromEmail, $fromName, $subject, $data) { Mail::send($views, $data, function ($message) use ($toEmail, $fromEmail, $fromName, $subject, $data) {
$toEmail = strtolower($toEmail);
$replyEmail = $fromEmail; $replyEmail = $fromEmail;
$fromEmail = CONTACT_EMAIL; $fromEmail = CONTACT_EMAIL;
if(isset($data['invoice_id'])) { if (isset($data['invoice_id'])) {
$invoice = Invoice::with('account')->where('id', '=', $data['invoice_id'])->get()->first(); $invoice = Invoice::with('account')->where('id', '=', $data['invoice_id'])->get()->first();
if($invoice->account->pdf_email_attachment && file_exists($invoice->getPDFPath())) { if($invoice->account->pdf_email_attachment && file_exists($invoice->getPDFPath())) {
$message->attach( $message->attach(
@ -30,14 +31,22 @@ class Mailer
} }
} }
$message->to($toEmail)->from($fromEmail, $fromName)->replyTo($replyEmail, $fromName)->subject($subject); $message->to($toEmail)
->from($fromEmail, $fromName)
->replyTo($replyEmail, $fromName)
->subject($subject);
}); });
return true; return true;
} catch (Exception $e) { } catch (Exception $exception) {
$response = $e->getResponse()->getBody()->getContents(); if (isset($_ENV['POSTMARK_API_TOKEN'])) {
$response = json_decode($response); $response = $exception->getResponse()->getBody()->getContents();
return nl2br($response->Message); $response = json_decode($response);
return nl2br($response->Message);
} else {
return $exception->getMessage();
}
} }
} }
} }

View File

@ -47,7 +47,7 @@ class UserMailer extends Mailer
'clientName' => $invoice->client->getDisplayName(), 'clientName' => $invoice->client->getDisplayName(),
'accountName' => $invoice->account->getDisplayName(), 'accountName' => $invoice->account->getDisplayName(),
'userName' => $user->getDisplayName(), 'userName' => $user->getDisplayName(),
'invoiceAmount' => Utils::formatMoney($invoice->amount, $invoice->client->getCurrencyId()), 'invoiceAmount' => Utils::formatMoney($invoice->getRequestedAmount(), $invoice->client->getCurrencyId()),
'invoiceNumber' => $invoice->invoice_number, 'invoiceNumber' => $invoice->invoice_number,
'invoiceLink' => SITE_URL."/{$entityType}s/{$invoice->public_id}", 'invoiceLink' => SITE_URL."/{$entityType}s/{$invoice->public_id}",
]; ];

View File

@ -6,7 +6,7 @@ use Session;
use Utils; use Utils;
use DB; use DB;
use stdClass; use stdClass;
use Schema;
use App\Models\AccountGateway; use App\Models\AccountGateway;
use App\Models\Invitation; use App\Models\Invitation;
use App\Models\Invoice; use App\Models\Invoice;
@ -250,6 +250,10 @@ class AccountRepository
public function findUserAccounts($userId1, $userId2 = false) public function findUserAccounts($userId1, $userId2 = false)
{ {
if (!Schema::hasTable('user_accounts')) {
return false;
}
$query = UserAccount::where('user_id1', '=', $userId1) $query = UserAccount::where('user_id1', '=', $userId1)
->orWhere('user_id2', '=', $userId1) ->orWhere('user_id2', '=', $userId1)
->orWhere('user_id3', '=', $userId1) ->orWhere('user_id3', '=', $userId1)
@ -268,7 +272,6 @@ class AccountRepository
} }
public function prepareUsersData($record) { public function prepareUsersData($record) {
if (!$record) { if (!$record) {
return false; return false;
} }
@ -294,7 +297,7 @@ class AccountRepository
$item->account_id = $user->account->id; $item->account_id = $user->account->id;
$item->account_name = $user->account->getDisplayName(); $item->account_name = $user->account->getDisplayName();
$item->pro_plan_paid = $user->account->pro_plan_paid; $item->pro_plan_paid = $user->account->pro_plan_paid;
$item->account_key = file_exists($user->account->getLogoPath()) ? $user->account->account_key : null; $item->logo_path = file_exists($user->account->getLogoPath()) ? $user->account->getLogoPath() : null;
$data[] = $item; $data[] = $item;
} }
@ -312,6 +315,9 @@ class AccountRepository
} }
public function syncUserAccounts($users, $proPlanPaid = false) { public function syncUserAccounts($users, $proPlanPaid = false) {
if (!$users) {
return;
}
if (!$proPlanPaid) { if (!$proPlanPaid) {
foreach ($users as $user) { foreach ($users as $user) {
@ -374,7 +380,6 @@ class AccountRepository
} }
public function unlinkUser($userAccountId, $userId) { public function unlinkUser($userAccountId, $userId) {
$userAccount = UserAccount::whereId($userAccountId)->first(); $userAccount = UserAccount::whereId($userAccountId)->first();
if ($userAccount->hasUserId($userId)) { if ($userAccount->hasUserId($userId)) {
$userAccount->removeUserId($userId); $userAccount->removeUserId($userId);

View File

@ -120,7 +120,7 @@ class ClientRepository
if (isset($data['contact'])) { if (isset($data['contact'])) {
$info = $data['contact']; $info = $data['contact'];
if (isset($info['email'])) { if (isset($info['email'])) {
$contact->email = trim(strtolower($info['email'])); $contact->email = trim($info['email']);
} }
if (isset($info['first_name'])) { if (isset($info['first_name'])) {
$contact->first_name = trim($info['first_name']); $contact->first_name = trim($info['first_name']);
@ -145,7 +145,7 @@ class ClientRepository
} }
if (isset($record['email'])) { if (isset($record['email'])) {
$contact->email = trim(strtolower($record['email'])); $contact->email = trim($record['email']);
} }
if (isset($record['first_name'])) { if (isset($record['first_name'])) {
$contact->first_name = trim($record['first_name']); $contact->first_name = trim($record['first_name']);

View File

@ -1,11 +1,12 @@
<?php namespace App\Ninja\Repositories; <?php namespace App\Ninja\Repositories;
use Carbon;
use Utils;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\InvoiceItem; use App\Models\InvoiceItem;
use App\Models\Invitation; use App\Models\Invitation;
use App\Models\Product; use App\Models\Product;
use App\Models\Task; use App\Models\Task;
use Utils;
class InvoiceRepository class InvoiceRepository
{ {
@ -266,15 +267,18 @@ class InvoiceRepository
$account->save(); $account->save();
} }
$invoice->client_id = $data['client_id']; if (isset($data['invoice_number'])) {
$invoice->invoice_number = trim($data['invoice_number']);
}
$invoice->discount = round(Utils::parseFloat($data['discount']), 2); $invoice->discount = round(Utils::parseFloat($data['discount']), 2);
$invoice->is_amount_discount = $data['is_amount_discount'] ? true : false; $invoice->is_amount_discount = $data['is_amount_discount'] ? true : false;
$invoice->invoice_number = trim($data['invoice_number']);
$invoice->partial = round(Utils::parseFloat($data['partial']), 2); $invoice->partial = round(Utils::parseFloat($data['partial']), 2);
$invoice->invoice_date = isset($data['invoice_date_sql']) ? $data['invoice_date_sql'] : Utils::toSqlDate($data['invoice_date']); $invoice->invoice_date = isset($data['invoice_date_sql']) ? $data['invoice_date_sql'] : Utils::toSqlDate($data['invoice_date']);
$invoice->has_tasks = isset($data['has_tasks']) ? $data['has_tasks'] : false; $invoice->has_tasks = isset($data['has_tasks']) ? $data['has_tasks'] : false;
if (!$publicId) { if (!$publicId) {
$invoice->client_id = $data['client_id'];
$invoice->is_recurring = $data['is_recurring'] && !Utils::isDemo() ? true : false; $invoice->is_recurring = $data['is_recurring'] && !Utils::isDemo() ? true : false;
} }
@ -311,6 +315,7 @@ class InvoiceRepository
} }
$total = 0; $total = 0;
$itemTax = 0;
foreach ($data['invoice_items'] as $item) { foreach ($data['invoice_items'] as $item) {
$item = (array) $item; $item = (array) $item;
@ -320,15 +325,29 @@ class InvoiceRepository
$invoiceItemCost = round(Utils::parseFloat($item['cost']), 2); $invoiceItemCost = round(Utils::parseFloat($item['cost']), 2);
$invoiceItemQty = round(Utils::parseFloat($item['qty']), 2); $invoiceItemQty = round(Utils::parseFloat($item['qty']), 2);
$invoiceItemTaxRate = 0;
if (isset($item['tax_rate']) && Utils::parseFloat($item['tax_rate']) > 0) {
$invoiceItemTaxRate = Utils::parseFloat($item['tax_rate']);
}
$lineTotal = $invoiceItemCost * $invoiceItemQty; $lineTotal = $invoiceItemCost * $invoiceItemQty;
$total += round($lineTotal, 2);
}
$total += round($lineTotal + ($lineTotal * $invoiceItemTaxRate / 100), 2); foreach ($data['invoice_items'] as $item) {
$item = (array) $item;
if (isset($item['tax_rate']) && Utils::parseFloat($item['tax_rate']) > 0) {
$invoiceItemCost = round(Utils::parseFloat($item['cost']), 2);
$invoiceItemQty = round(Utils::parseFloat($item['qty']), 2);
$invoiceItemTaxRate = Utils::parseFloat($item['tax_rate']);
$lineTotal = $invoiceItemCost * $invoiceItemQty;
if ($invoice->discount > 0) {
if ($invoice->is_amount_discount) {
$lineTotal -= round(($lineTotal/$total) * $invoice->discount, 2);
} else {
$lineTotal -= round($lineTotal * ($invoice->discount/100), 2);
}
}
$itemTax += round($lineTotal * $invoiceItemTaxRate / 100, 2);
}
} }
if ($invoice->discount > 0) { if ($invoice->discount > 0) {
@ -354,6 +373,7 @@ class InvoiceRepository
$total += $total * $invoice->tax_rate / 100; $total += $total * $invoice->tax_rate / 100;
$total = round($total, 2); $total = round($total, 2);
$total += $itemTax;
// custom fields not charged taxes // custom fields not charged taxes
if ($invoice->custom_value1 && !$invoice->custom_taxes1) { if ($invoice->custom_value1 && !$invoice->custom_taxes1) {
@ -387,20 +407,19 @@ class InvoiceRepository
$task->invoice_id = $invoice->id; $task->invoice_id = $invoice->id;
$task->client_id = $invoice->client_id; $task->client_id = $invoice->client_id;
$task->save(); $task->save();
} else if ($item['product_key']) { } else if ($item['product_key'] && !$invoice->has_tasks) {
$product = Product::findProductByKey(trim($item['product_key'])); $product = Product::findProductByKey(trim($item['product_key']));
if (!$product) {
$product = Product::createNew();
$product->product_key = trim($item['product_key']);
}
if (\Auth::user()->account->update_products) { if (\Auth::user()->account->update_products) {
if (!$product) {
$product = Product::createNew();
$product->product_key = trim($item['product_key']);
}
$product->notes = $item['notes']; $product->notes = $item['notes'];
$product->cost = $item['cost']; $product->cost = $item['cost'];
$product->save();
} }
$product->save();
} }
$invoiceItem = InvoiceItem::createNew(); $invoiceItem = InvoiceItem::createNew();
@ -536,4 +555,89 @@ class InvoiceRepository
return count($invoices); return count($invoices);
} }
public function findOpenInvoices($clientId)
{
return Invoice::scope()
->whereClientId($clientId)
->whereIsQuote(false)
->whereIsRecurring(false)
->whereDeletedAt(null)
->whereHasTasks(true)
->where('invoice_status_id', '<', 5)
->select(['public_id', 'invoice_number'])
->get();
}
public function createRecurringInvoice($recurInvoice)
{
$recurInvoice->load('account.timezone', 'invoice_items', 'client', 'user');
if ($recurInvoice->client->deleted_at) {
return false;
}
if (!$recurInvoice->user->confirmed) {
return false;
}
if (!$recurInvoice->shouldSendToday()) {
return false;
}
$invoice = Invoice::createNew($recurInvoice);
$invoice->client_id = $recurInvoice->client_id;
$invoice->recurring_invoice_id = $recurInvoice->id;
$invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber(false, 'R');
$invoice->amount = $recurInvoice->amount;
$invoice->balance = $recurInvoice->amount;
$invoice->invoice_date = date_create()->format('Y-m-d');
$invoice->discount = $recurInvoice->discount;
$invoice->po_number = $recurInvoice->po_number;
$invoice->public_notes = Utils::processVariables($recurInvoice->public_notes);
$invoice->terms = Utils::processVariables($recurInvoice->terms);
$invoice->invoice_footer = Utils::processVariables($recurInvoice->invoice_footer);
$invoice->tax_name = $recurInvoice->tax_name;
$invoice->tax_rate = $recurInvoice->tax_rate;
$invoice->invoice_design_id = $recurInvoice->invoice_design_id;
$invoice->custom_value1 = $recurInvoice->custom_value1;
$invoice->custom_value2 = $recurInvoice->custom_value2;
$invoice->custom_taxes1 = $recurInvoice->custom_taxes1;
$invoice->custom_taxes2 = $recurInvoice->custom_taxes2;
$invoice->is_amount_discount = $recurInvoice->is_amount_discount;
if ($invoice->client->payment_terms != 0) {
$days = $invoice->client->payment_terms;
if ($days == -1) {
$days = 0;
}
$invoice->due_date = date_create()->modify($days.' day')->format('Y-m-d');
}
$invoice->save();
foreach ($recurInvoice->invoice_items as $recurItem) {
$item = InvoiceItem::createNew($recurItem);
$item->product_id = $recurItem->product_id;
$item->qty = $recurItem->qty;
$item->cost = $recurItem->cost;
$item->notes = Utils::processVariables($recurItem->notes);
$item->product_key = Utils::processVariables($recurItem->product_key);
$item->tax_name = $recurItem->tax_name;
$item->tax_rate = $recurItem->tax_rate;
$invoice->invoice_items()->save($item);
}
foreach ($recurInvoice->invitations as $recurInvitation) {
$invitation = Invitation::createNew($recurInvitation);
$invitation->contact_id = $recurInvitation->contact_id;
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
$invoice->invitations()->save($invitation);
}
$recurInvoice->last_sent_date = Carbon::now()->toDateTimeString();
$recurInvoice->save();
return $invoice;
}
} }

View File

@ -23,7 +23,7 @@ class TaskRepository
}) })
->where('contacts.deleted_at', '=', null) ->where('contacts.deleted_at', '=', null)
->where('clients.deleted_at', '=', null) ->where('clients.deleted_at', '=', null)
->select('tasks.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', 'contacts.first_name', 'contacts.email', 'contacts.last_name', 'invoices.invoice_status_id', 'tasks.start_time', 'tasks.description', 'tasks.duration', 'tasks.is_deleted', 'tasks.deleted_at', 'invoices.invoice_number', 'invoices.public_id as invoice_public_id', 'tasks.is_running'); ->select('tasks.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', 'contacts.first_name', 'contacts.email', 'contacts.last_name', 'invoices.invoice_status_id', 'tasks.description', 'tasks.is_deleted', 'tasks.deleted_at', 'invoices.invoice_number', 'invoices.public_id as invoice_public_id', 'tasks.is_running', 'tasks.time_log', 'tasks.created_at');
if ($clientPublicId) { if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId); $query->where('clients.public_id', '=', $clientPublicId);
@ -46,7 +46,7 @@ class TaskRepository
} }
public function save($publicId, $data) public function save($publicId, $data)
{ {
if ($publicId) { if ($publicId) {
$task = Task::scope($publicId)->firstOrFail(); $task = Task::scope($publicId)->firstOrFail();
} else { } else {
@ -60,36 +60,26 @@ class TaskRepository
$task->description = trim($data['description']); $task->description = trim($data['description']);
} }
$timeLog = $task->time_log ? json_decode($task->time_log, true) : []; if (isset($data['time_log'])) {
$timeLog = json_decode($data['time_log']);
} elseif ($task->time_log) {
$timeLog = json_decode($task->time_log);
} else {
$timeLog = [];
}
if ($data['action'] == 'start') { if ($data['action'] == 'start') {
$task->start_time = Carbon::now()->toDateTimeString();
$task->is_running = true; $task->is_running = true;
$timeLog[] = [strtotime('now'), false]; $timeLog[] = [strtotime('now'), false];
} else if ($data['action'] == 'resume') { } else if ($data['action'] == 'resume') {
$task->break_duration = strtotime('now') - strtotime($task->start_time) + $task->duration;
$task->resume_time = Carbon::now()->toDateTimeString();
$task->is_running = true; $task->is_running = true;
$timeLog[] = [strtotime('now'), false]; $timeLog[] = [strtotime('now'), false];
} else if ($data['action'] == 'stop' && $task->is_running) { } else if ($data['action'] == 'stop' && $task->is_running) {
if ($task->resume_time) { $timeLog[count($timeLog)-1][1] = time();
$task->duration = $task->duration + strtotime('now') - strtotime($task->resume_time);
$task->resume_time = null;
} else {
$task->duration = strtotime('now') - strtotime($task->start_time);
}
$timeLog[count($timeLog)-1][1] = strtotime('now');
$task->is_running = false; $task->is_running = false;
} else if ($data['action'] == 'save' && !$task->is_running) {
$task->start_time = $data['start_time'];
$task->duration = $data['duration'];
$task->break_duration = $data['break_duration'];
} }
$task->duration = max($task->duration, 0);
$task->break_duration = max($task->break_duration, 0);
$task->time_log = json_encode($timeLog); $task->time_log = json_encode($timeLog);
$task->save(); $task->save();
return $task; return $task;

View File

@ -37,13 +37,19 @@ class AppServiceProvider extends ServiceProvider {
$str = '<li class="dropdown '.$class.'"> $str = '<li class="dropdown '.$class.'">
<a href="'.URL::to($types).'" class="dropdown-toggle">'.trans("texts.$types").'</a> <a href="'.URL::to($types).'" class="dropdown-toggle">'.trans("texts.$types").'</a>
<ul class="dropdown-menu" id="menu1"> <ul class="dropdown-menu" id="menu1">';
<li><a href="'.URL::to($types.'/create').'">'.trans("texts.new_$type").'</a></li>';
if ($type == ENTITY_INVOICE && Auth::user()->isPro()) { if ($type != ENTITY_TASK || Auth::user()->account->timezone_id) {
$str .= '<li class="divider"></li> $str .= '<li><a href="'.URL::to($types.'/create').'">'.trans("texts.new_$type").'</a></li>';
}
if ($type == ENTITY_INVOICE) {
$str .= '<li><a href="'.URL::to('recurring_invoices/create').'">'.trans("texts.new_recurring_invoice").'</a></li>';
if (Auth::user()->isPro()) {
$str .= '<li class="divider"></li>
<li><a href="'.URL::to('quotes').'">'.trans("texts.quotes").'</a></li> <li><a href="'.URL::to('quotes').'">'.trans("texts.quotes").'</a></li>
<li><a href="'.URL::to('quotes/create').'">'.trans("texts.new_quote").'</a></li>'; <li><a href="'.URL::to('quotes/create').'">'.trans("texts.new_quote").'</a></li>';
}
} else if ($type == ENTITY_CLIENT) { } else if ($type == ENTITY_CLIENT) {
$str .= '<li class="divider"></li> $str .= '<li class="divider"></li>
<li><a href="'.URL::to('credits').'">'.trans("texts.credits").'</a></li> <li><a href="'.URL::to('credits').'">'.trans("texts.credits").'</a></li>
@ -66,7 +72,7 @@ class AppServiceProvider extends ServiceProvider {
// Get the breadcrumbs by exploding the current path. // Get the breadcrumbs by exploding the current path.
$basePath = Utils::basePath(); $basePath = Utils::basePath();
$parts = explode('?', $_SERVER['REQUEST_URI']); $parts = explode('?', isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '');
$path = $parts[0]; $path = $parts[0];
if ($basePath != '/') { if ($basePath != '/') {

View File

@ -20,7 +20,8 @@
"d3": "3.4.11", "d3": "3.4.11",
"handsontable": "*", "handsontable": "*",
"pdfmake": "*", "pdfmake": "*",
"moment": "*" "moment": "*",
"jsoneditor": "*"
}, },
"resolutions": { "resolutions": {
"jquery": "~1.11" "jquery": "~1.11"

240
c3.php Executable file
View File

@ -0,0 +1,240 @@
<?php
// @codingStandardsIgnoreFile
// @codeCoverageIgnoreStart
/**
* C3 - Codeception Code Coverage
*
* @author tiger
*/
// $_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_DEBUG'] = 1;
if (isset($_COOKIE['CODECEPTION_CODECOVERAGE'])) {
$cookie = json_decode($_COOKIE['CODECEPTION_CODECOVERAGE'], true);
// fix for improperly encoded JSON in Code Coverage cookie with WebDriver.
// @see https://github.com/Codeception/Codeception/issues/874
if (!is_array($cookie)) {
$cookie = json_decode($cookie, true);
}
if ($cookie) {
foreach ($cookie as $key => $value) {
$_SERVER["HTTP_X_CODECEPTION_".strtoupper($key)] = $value;
}
}
}
if (!array_key_exists('HTTP_X_CODECEPTION_CODECOVERAGE', $_SERVER)) {
return;
}
if (!function_exists('__c3_error')) {
function __c3_error($message)
{
file_put_contents(C3_CODECOVERAGE_MEDIATE_STORAGE . DIRECTORY_SEPARATOR . 'error.txt', $message);
if (!headers_sent()) {
header('X-Codeception-CodeCoverage-Error: ' . str_replace("\n", ' ', $message), true, 500);
}
setcookie('CODECEPTION_CODECOVERAGE_ERROR', $message);
}
}
// Autoload Codeception classes
if (!class_exists('\\Codeception\\Codecept')) {
if (stream_resolve_include_path(__DIR__ . '/vendor/autoload.php')) {
require_once __DIR__ . '/vendor/autoload.php';
} elseif (file_exists(__DIR__ . '/codecept.phar')) {
require_once 'phar://'.__DIR__ . '/codecept.phar/autoload.php';
} elseif (stream_resolve_include_path('Codeception/autoload.php')) {
require_once 'Codeception/autoload.php';
} else {
__c3_error('Codeception is not loaded. Please check that either PHAR or Composer or PEAR package can be used');
}
}
// Load Codeception Config
$config_file = realpath(__DIR__) . DIRECTORY_SEPARATOR . 'codeception.yml';
if (isset($_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_CONFIG'])) {
$config_file = realpath(__DIR__) . DIRECTORY_SEPARATOR . $_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_CONFIG'];
}
if (!file_exists($config_file)) {
__c3_error(sprintf("Codeception config file '%s' not found", $config_file));
}
try {
\Codeception\Configuration::config($config_file);
} catch (\Exception $e) {
__c3_error($e->getMessage());
}
if (!defined('C3_CODECOVERAGE_MEDIATE_STORAGE')) {
// workaround for 'zend_mm_heap corrupted' problem
gc_disable();
if ((integer)ini_get('memory_limit') < 384) {
ini_set('memory_limit', '384M');
}
define('C3_CODECOVERAGE_MEDIATE_STORAGE', Codeception\Configuration::logDir() . 'c3tmp');
define('C3_CODECOVERAGE_PROJECT_ROOT', Codeception\Configuration::projectDir());
define('C3_CODECOVERAGE_TESTNAME', $_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE']);
function __c3_build_html_report(PHP_CodeCoverage $codeCoverage, $path)
{
$writer = new PHP_CodeCoverage_Report_HTML();
$writer->process($codeCoverage, $path . 'html');
if (file_exists($path . '.tar')) {
unlink($path . '.tar');
}
$phar = new PharData($path . '.tar');
$phar->setSignatureAlgorithm(Phar::SHA1);
$files = $phar->buildFromDirectory($path . 'html');
array_map('unlink', $files);
if (in_array('GZ', Phar::getSupportedCompression())) {
if (file_exists($path . '.tar.gz')) {
unlink($path . '.tar.gz');
}
$phar->compress(\Phar::GZ);
// close the file so that we can rename it
unset($phar);
unlink($path . '.tar');
rename($path . '.tar.gz', $path . '.tar');
}
return $path . '.tar';
}
function __c3_build_clover_report(PHP_CodeCoverage $codeCoverage, $path)
{
$writer = new PHP_CodeCoverage_Report_Clover();
$writer->process($codeCoverage, $path . '.clover.xml');
return $path . '.clover.xml';
}
function __c3_send_file($filename)
{
if (!headers_sent()) {
readfile($filename);
}
return __c3_exit();
}
/**
* @param $filename
* @return null|PHP_CodeCoverage
*/
function __c3_factory($filename)
{
$phpCoverage = is_readable($filename)
? unserialize(file_get_contents($filename))
: new PHP_CodeCoverage();
if (isset($_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_SUITE'])) {
$suite = $_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_SUITE'];
try {
$settings = \Codeception\Configuration::suiteSettings($suite, \Codeception\Configuration::config());
} catch (Exception $e) {
__c3_error($e->getMessage());
}
} else {
$settings = \Codeception\Configuration::config();
}
try {
\Codeception\Coverage\Filter::setup($phpCoverage)
->whiteList($settings)
->blackList($settings);
} catch (Exception $e) {
__c3_error($e->getMessage());
}
return $phpCoverage;
}
function __c3_exit()
{
if (!isset($_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_DEBUG'])) {
exit;
}
return null;
}
function __c3_clear()
{
\Codeception\Util\FileSystem::doEmptyDir(C3_CODECOVERAGE_MEDIATE_STORAGE);
}
}
if (!is_dir(C3_CODECOVERAGE_MEDIATE_STORAGE)) {
if (mkdir(C3_CODECOVERAGE_MEDIATE_STORAGE, 0777, true) === false) {
__c3_error('Failed to create directory "' . C3_CODECOVERAGE_MEDIATE_STORAGE . '"');
}
}
// evaluate base path for c3-related files
$path = realpath(C3_CODECOVERAGE_MEDIATE_STORAGE) . DIRECTORY_SEPARATOR . 'codecoverage';
$requested_c3_report = (strpos($_SERVER['REQUEST_URI'], 'c3/report') !== false);
$complete_report = $current_report = $path . '.serialized';
if ($requested_c3_report) {
set_time_limit(0);
$route = ltrim(strrchr($_SERVER['REQUEST_URI'], '/'), '/');
if ($route == 'clear') {
__c3_clear();
return __c3_exit();
}
$codeCoverage = __c3_factory($complete_report);
switch ($route) {
case 'html':
try {
__c3_send_file(__c3_build_html_report($codeCoverage, $path));
} catch (Exception $e) {
__c3_error($e->getMessage());
}
return __c3_exit();
case 'clover':
try {
__c3_send_file(__c3_build_clover_report($codeCoverage, $path));
} catch (Exception $e) {
__c3_error($e->getMessage());
}
return __c3_exit();
case 'serialized':
try {
__c3_send_file($complete_report);
} catch (Exception $e) {
__c3_error($e->getMessage());
}
return __c3_exit();
}
} else {
$codeCoverage = __c3_factory($current_report);
$codeCoverage->start(C3_CODECOVERAGE_TESTNAME);
if (!array_key_exists('HTTP_X_CODECEPTION_CODECOVERAGE_DEBUG', $_SERVER)) {
register_shutdown_function(
function () use ($codeCoverage, $current_report) {
$codeCoverage->stop();
file_put_contents($current_report, serialize($codeCoverage));
}
);
}
}
// @codeCoverageIgnoreEnd

27
codeception.yml Normal file
View File

@ -0,0 +1,27 @@
actor: Tester
paths:
tests: tests
log: tests/_output
data: tests/_data
support: tests/_support
envs: tests/_envs
settings:
bootstrap: _bootstrap.php
colors: true
memory_limit: 1024M
extensions:
enabled:
- Codeception\Extension\RunFailed
- Codeception\Extension\Recorder
config:
Codeception\Extension\Recorder:
delete_successful: false
modules:
config:
Db:
dsn: 'mysql:dbname=ninja;host=localhost;'
user: 'ninja'
password: 'ninja'
dump: tests/_data/dump.sql
populate: true
cleanup: false

View File

@ -37,11 +37,14 @@
"guzzlehttp/guzzle": "~5.0", "guzzlehttp/guzzle": "~5.0",
"laravelcollective/html": "~5.0", "laravelcollective/html": "~5.0",
"wildbit/laravel-postmark-provider": "dev-master", "wildbit/laravel-postmark-provider": "dev-master",
"Dwolla/omnipay-dwolla": "dev-master" "Dwolla/omnipay-dwolla": "dev-master"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "~4.0", "phpunit/phpunit": "~4.0",
"phpspec/phpspec": "~2.1" "phpspec/phpspec": "~2.1",
"codeception/codeception": "~2.0",
"codeception/c3": "~2.0",
"fzaninotto/faker": "^1.5"
}, },
"autoload": { "autoload": {
"classmap": [ "classmap": [

1081
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -119,6 +119,6 @@ return [
| |
*/ */
'pretend' => false, 'pretend' => env('MAIL_PRETEND'),
]; ];

View File

@ -19,7 +19,7 @@ class AddPartialAmountToInvoices extends Migration {
Schema::table('accounts', function($table) Schema::table('accounts', function($table)
{ {
$table->boolean('utf8_invoices')->default(false); $table->boolean('utf8_invoices')->default(true);
$table->boolean('auto_wrap')->default(false); $table->boolean('auto_wrap')->default(false);
$table->string('subdomain')->nullable(); $table->string('subdomain')->nullable();
}); });

View File

@ -0,0 +1,73 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class SimplifyTasks extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$tasks = \App\Models\Task::all();
foreach ($tasks as $task) {
$startTime = strtotime($task->start_time);
if (!$task->time_log || !count(json_decode($task->time_log))) {
$task->time_log = json_encode([[$startTime, $startTime + $task->duration]]);
$task->save();
} elseif ($task->getDuration() != intval($task->duration)) {
$task->time_log = json_encode([[$startTime, $startTime + $task->duration]]);
$task->save();
}
}
Schema::table('tasks', function($table)
{
$table->dropColumn('start_time');
$table->dropColumn('duration');
$table->dropColumn('break_duration');
$table->dropColumn('resume_time');
});
Schema::table('users', function($table)
{
$table->boolean('dark_mode')->default(false)->nullable();
});
Schema::table('users', function($table)
{
$table->dropColumn('theme_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('tasks', function($table)
{
$table->timestamp('start_time')->nullable();
$table->integer('duration')->nullable();
$table->timestamp('resume_time')->nullable();
$table->integer('break_duration')->nullable();
});
Schema::table('users', function($table)
{
$table->dropColumn('dark_mode');
});
Schema::table('users', function($table)
{
$table->integer('theme_id')->nullable();
});
}
}

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddCustomDesign extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function($table)
{
$table->text('custom_design')->nullable();
});
DB::table('invoice_designs')->insert(['id' => CUSTOM_DESIGN, 'name' => 'Custom']);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function($table)
{
$table->dropColumn('custom_design');
});
}
}

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddPdfmakeSupport extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('invoice_designs', function($table)
{
$table->text('pdfmake')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('invoice_designs', function($table)
{
$table->dropColumn('pdfmake');
});
}
}

View File

@ -13,14 +13,14 @@ class DatabaseSeeder extends Seeder {
Eloquent::unguard(); Eloquent::unguard();
$this->call('UserTableSeeder');
$this->call('ConstantsSeeder'); $this->call('ConstantsSeeder');
$this->command->info('Seeded the constants');
$this->call('CountriesSeeder'); $this->call('CountriesSeeder');
$this->command->info('Seeded the countries!'); $this->command->info('Seeded the countries');
$this->call('PaymentLibrariesSeeder'); $this->call('PaymentLibrariesSeeder');
$this->command->info('Seeded the Payment Libraries!'); $this->command->info('Seeded the Payment Libraries');
} }
} }

View File

@ -5,6 +5,7 @@ use App\Models\PaymentTerm;
use App\Models\Currency; use App\Models\Currency;
use App\Models\DateFormat; use App\Models\DateFormat;
use App\Models\DatetimeFormat; use App\Models\DatetimeFormat;
use App\Models\InvoiceDesign;
class PaymentLibrariesSeeder extends Seeder class PaymentLibrariesSeeder extends Seeder
{ {
@ -16,6 +17,7 @@ class PaymentLibrariesSeeder extends Seeder
$this->createPaymentTerms(); $this->createPaymentTerms();
$this->createDateFormats(); $this->createDateFormats();
$this->createDatetimeFormats(); $this->createDatetimeFormats();
$this->createInvoiceDesigns();
} }
private function createGateways() { private function createGateways() {
@ -62,7 +64,7 @@ class PaymentLibrariesSeeder extends Seeder
['name' => 'US Dollar', 'code' => 'USD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'US Dollar', 'code' => 'USD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Pound Sterling', 'code' => 'GBP', 'symbol' => '£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Pound Sterling', 'code' => 'GBP', 'symbol' => '£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Euro', 'code' => 'EUR', 'symbol' => '€', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Euro', 'code' => 'EUR', 'symbol' => '€', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Rand', 'code' => 'ZAR', 'symbol' => 'R', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'South African Rand', 'code' => 'ZAR', 'symbol' => 'R', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Danish Krone', 'code' => 'DKK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Danish Krone', 'code' => 'DKK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Israeli Shekel', 'code' => 'ILS', 'symbol' => 'NIS ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Israeli Shekel', 'code' => 'ILS', 'symbol' => 'NIS ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Swedish Krona', 'code' => 'SEK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Swedish Krona', 'code' => 'SEK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
@ -80,10 +82,16 @@ class PaymentLibrariesSeeder extends Seeder
['name' => 'Malaysian Ringgit', 'code' => 'MYR', 'symbol' => 'RM', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Malaysian Ringgit', 'code' => 'MYR', 'symbol' => 'RM', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Brazilian Real', 'code' => 'BRL', 'symbol' => 'R$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Brazilian Real', 'code' => 'BRL', 'symbol' => 'R$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Thai baht', 'code' => 'THB', 'symbol' => 'THB ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Thai baht', 'code' => 'THB', 'symbol' => 'THB ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Nigerian Naira', 'code' => 'NGN', 'symbol' => 'NGN ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Argentine Peso', 'code' => 'ARS', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
]; ];
foreach ($currencies as $currency) { foreach ($currencies as $currency) {
if (!DB::table('currencies')->whereName($currency['name'])->get()) { $record = Currency::whereCode($currency['code'])->first();
if ($record) {
$record->name = $currency['name'];
$record->save();
} else {
Currency::create($currency); Currency::create($currency);
} }
} }
@ -131,4 +139,37 @@ class PaymentLibrariesSeeder extends Seeder
} }
} }
private function createInvoiceDesigns() {
$designs = [
'Clean',
'Bold',
'Modern',
'Plain',
'Business',
'Creative',
'Elegant',
'Hipster',
'Playful',
'Photo',
];
for ($i=0; $i<count($designs); $i++) {
$design = $designs[$i];
$fileName = storage_path() . '/templates/' . strtolower($design) . '.js';
if (file_exists($fileName)) {
$pdfmake = file_get_contents($fileName);
if ($pdfmake) {
$record = InvoiceDesign::whereName($design)->first();
if (!$record) {
$record = new InvoiceDesign;
$record->id = $i + 1;
$record->name = $design;
}
$record->pdfmake = $pdfmake;
$record->save();
}
}
}
}
} }

View File

@ -1,11 +1,31 @@
<?php <?php
use App\Models\User;
use App\Models\Account;
class UserTableSeeder extends Seeder class UserTableSeeder extends Seeder
{ {
public function run() public function run()
{ {
$this->command->info('Running UserTableSeeder');
Eloquent::unguard();
$account = Account::create([
'name' => 'Test Account',
'account_key' => str_random(16),
'timezone_id' => 1,
]);
User::create([
'email' => TEST_USERNAME,
'username' => TEST_USERNAME,
'account_id' => $account->id,
'password' => Hash::make(TEST_PASSWORD),
'registered' => true,
'confirmed' => true,
]);
} }
} }

View File

@ -13,4 +13,7 @@
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L] RewriteRule ^ index.php [L]
# Enable this to enforce https access
# RewriteCond %{SERVER_PORT} 80
# RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R,L]
</IfModule> </IfModule>

View File

@ -2433,6 +2433,8 @@ th {border-left: 1px solid #d26b26; }
.table>thead>tr>th, .table>tbody>tr>th, .table>tfoot>tr>th, .table>thead>tr>td, .table>tbody>tr>td, .table>tfoot>tr>td { .table>thead>tr>th, .table>tbody>tr>th, .table>tfoot>tr>th, .table>thead>tr>td, .table>tbody>tr>td, .table>tfoot>tr>td {
vertical-align: middle; vertical-align: middle;
border-top: none; border-top: none;
}
table.invoice-table>thead>tr>th, table.invoice-table>tbody>tr>th, table.invoice-table>tfoot>tr>th, table.invoice-table>thead>tr>td, table.invoice-table>tbody>tr>td, table.invoice-table>tfoot>tr>td {
border-bottom: 1px solid #dfe0e1; border-bottom: 1px solid #dfe0e1;
} }
table.dataTable.no-footer { table.dataTable.no-footer {
@ -2837,15 +2839,12 @@ background-clip: padding-box;
.dashboard .panel-body {padding: 0;} .dashboard .panel-body {padding: 0;}
.dashboard .table-striped>tbody>tr>td, .table-striped>tbody>tr>th { background-color: #fbfbfb;}
.dashboard .table-striped>tbody>tr:nth-child(odd)>tr, .table-striped>tbody>tr:nth-child(odd)>th {
background-color: #fff;
}
.dashboard th { .dashboard th {
border-left: none; border-left: none;
background-color: #fbfbfb; background-color: #fbfbfb;
border-bottom: 1px solid #dfe0e1; border-bottom: 1px solid #dfe0e1;
} }
.dashboard table.table thead > tr > th { .dashboard table.table thead > tr > th {
border-bottom-width: 1px; border-bottom-width: 1px;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

1
public/css/jsoneditor.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -83,6 +83,8 @@ th {border-left: 1px solid #d26b26; }
.table>thead>tr>th, .table>tbody>tr>th, .table>tfoot>tr>th, .table>thead>tr>td, .table>tbody>tr>td, .table>tfoot>tr>td { .table>thead>tr>th, .table>tbody>tr>th, .table>tfoot>tr>th, .table>thead>tr>td, .table>tbody>tr>td, .table>tfoot>tr>td {
vertical-align: middle; vertical-align: middle;
border-top: none; border-top: none;
}
table.invoice-table>thead>tr>th, table.invoice-table>tbody>tr>th, table.invoice-table>tfoot>tr>th, table.invoice-table>thead>tr>td, table.invoice-table>tbody>tr>td, table.invoice-table>tfoot>tr>td {
border-bottom: 1px solid #dfe0e1; border-bottom: 1px solid #dfe0e1;
} }
table.dataTable.no-footer { table.dataTable.no-footer {
@ -487,15 +489,12 @@ background-clip: padding-box;
.dashboard .panel-body {padding: 0;} .dashboard .panel-body {padding: 0;}
.dashboard .table-striped>tbody>tr>td, .table-striped>tbody>tr>th { background-color: #fbfbfb;}
.dashboard .table-striped>tbody>tr:nth-child(odd)>tr, .table-striped>tbody>tr:nth-child(odd)>th {
background-color: #fff;
}
.dashboard th { .dashboard th {
border-left: none; border-left: none;
background-color: #fbfbfb; background-color: #fbfbfb;
border-bottom: 1px solid #dfe0e1; border-bottom: 1px solid #dfe0e1;
} }
.dashboard table.table thead > tr > th { .dashboard table.table thead > tr > th {
border-bottom-width: 1px; border-bottom-width: 1px;
} }

File diff suppressed because one or more lines are too long

44
public/js/jsoneditor.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,72 +1,65 @@
var NINJA = NINJA || {}; var NINJA = NINJA || {};
function GetPdfMake(invoice, javascript, callback) { NINJA.TEMPLATES = {
var account = invoice.account; CLEAN: "1",
var baseDD = { BOLD:"2",
pageMargins: [40, 40, 40, 40], MODERN: "3",
styles: { NORMAL:"4",
bold: { BUSINESS:"5",
bold: true CREATIVE:"6",
}, ELEGANT:"7",
cost: { HIPSTER:"8",
alignment: 'right' PLAYFUL:"9",
}, PHOTO:"10"
quantity: { };
alignment: 'right'
},
tax: {
alignment: 'right'
},
lineTotal: {
alignment: 'right'
},
right: {
alignment: 'right'
},
subtotals: {
alignment: 'right'
},
termsLabel: {
bold: true,
margin: [0, 10, 0, 4]
}
},
footer: function(){
f = [{ text:invoice.invoice_footer?processVariables(invoice.invoice_footer):"", margin: [40, 0]}]
if (!invoice.is_pro && logoImages.imageLogo1) {
f.push({
image: logoImages.imageLogo1,
width: 150,
margin: [40,0]
});
}
return f;
},
}; function GetPdfMake(invoice, javascript, callback) {
eval(javascript); javascript = NINJA.decodeJavascript(invoice, javascript);
dd = $.extend(true, baseDD, dd);
/* function jsonCallBack(key, val) {
pdfMake.fonts = { if ((val+'').indexOf('$firstAndLast') === 0) {
wqy: { var parts = val.split(':');
normal: 'wqy.ttf', return function (i, node) {
bold: 'wqy.ttf', return (i === 0 || i === node.table.body.length) ? parseFloat(parts[1]) : 0;
italics: 'wqy.ttf', };
bolditalics: 'wqy.ttf' } else if ((val+'').indexOf('$none') === 0) {
return function (i, node) {
return 0;
};
} else if ((val+'').indexOf('$notFirst') === 0) {
var parts = val.split(':');
return function (i, node) {
return i === 0 ? 0 : parseFloat(parts[1]);
};
} else if ((val+'').indexOf('$amount') === 0) {
var parts = val.split(':');
return function (i, node) {
return parseFloat(parts[1]);
};
} else if ((val+'').indexOf('$primaryColor') === 0) {
var parts = val.split(':');
return NINJA.primaryColor || parts[1];
} else if ((val+'').indexOf('$secondaryColor') === 0) {
var parts = val.split(':');
return NINJA.secondaryColor || parts[1];
} }
};
*/ return val;
}
//console.log(javascript);
var dd = JSON.parse(javascript, jsonCallBack);
if (!invoice.is_pro && dd.hasOwnProperty('footer') && dd.footer.hasOwnProperty('columns')) {
dd.footer.columns.push({image: logoImages.imageLogo1, alignment: 'right', width: 130})
}
//console.log(JSON.stringify(dd));
/* /*
pdfMake.fonts = { var fonts = {
NotoSansCJKsc: {
normal: 'NotoSansCJKsc-Regular.ttf',
bold: 'NotoSansCJKsc-Medium.ttf',
italics: 'NotoSansCJKsc-Italic.ttf',
bolditalics: 'NotoSansCJKsc-Italic.ttf'
},
Roboto: { Roboto: {
normal: 'Roboto-Regular.ttf', normal: 'Roboto-Regular.ttf',
bold: 'Roboto-Medium.ttf', bold: 'Roboto-Medium.ttf',
@ -83,56 +76,152 @@ function GetPdfMake(invoice, javascript, callback) {
return doc; return doc;
} }
NINJA.decodeJavascript = function(invoice, javascript)
{
var account = invoice.account;
var blankImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=';
// search/replace variables
var json = {
'accountName': account.name || ' ',
'accountLogo': window.accountLogo || blankImage,
'accountDetails': NINJA.accountDetails(invoice),
'accountAddress': NINJA.accountAddress(invoice),
'invoiceDetails': NINJA.invoiceDetails(invoice),
'invoiceDetailsHeight': NINJA.invoiceDetails(invoice).length * 22,
'invoiceLineItems': NINJA.invoiceLines(invoice),
'invoiceLineItemColumns': NINJA.invoiceColumns(invoice),
'clientDetails': NINJA.clientDetails(invoice),
'notesAndTerms': NINJA.notesAndTerms(invoice),
'subtotals': NINJA.subtotals(invoice),
'subtotalsHeight': NINJA.subtotals(invoice).length * 22,
'subtotalsWithoutBalance': NINJA.subtotals(invoice, true),
'balanceDue': formatMoney(invoice.balance_amount, invoice.client.currency_id),
'invoiceFooter': invoice.invoice_footer || ' ',
'invoiceNumber': invoice.invoice_number || ' ',
'entityType': invoice.is_quote ? invoiceLabels.quote : invoiceLabels.invoice,
'entityTypeUC': (invoice.is_quote ? invoiceLabels.quote : invoiceLabels.invoice).toUpperCase(),
'fontSize': NINJA.fontSize,
'fontSizeLarger': NINJA.fontSize + 1,
'fontSizeLargest': NINJA.fontSize + 2,
}
for (var key in json) {
var regExp = new RegExp('"\\$'+key+'"', 'g');
var val = JSON.stringify(json[key]);
val = doubleDollarSign(val);
javascript = javascript.replace(regExp, val);
}
// search/replace labels
var regExp = new RegExp('"\\$\\\w*?Label(UC)?(:)?(\\\?)?"', 'g');
var matches = javascript.match(regExp);
if (matches) {
for (var i=0; i<matches.length; i++) {
var match = matches[i];
field = match.substring(2, match.indexOf('Label'));
field = toSnakeCase(field);
var value = getDescendantProp(invoice, field);
if (match.indexOf('?') < 0 || value) {
if (invoice.partial && field == 'balance_due') {
field = 'amount_due';
}
var label = invoiceLabels[field];
if (match.indexOf('UC') >= 0) {
if (!label) console.log('match: ' + field);
label = label.toUpperCase();
}
if (match.indexOf(':') >= 0) {
label = label + ':';
}
} else {
label = ' ';
}
javascript = javascript.replace(match, '"'+label+'"');
}
}
// search/replace values
var regExp = new RegExp('"\\$[\\\w\\\.]*?Value"', 'g');
var matches = javascript.match(regExp);
if (matches) {
for (var i=0; i<matches.length; i++) {
var match = matches[i];
field = match.substring(2, match.indexOf('Value'));
field = toSnakeCase(field);
var value = getDescendantProp(invoice, field) || ' ';
value = doubleDollarSign(value);
if (field.toLowerCase().indexOf('date') >= 0 && value != ' ') {
value = moment(value, 'YYYY-MM-DD').format('MMM D YYYY');
}
javascript = javascript.replace(match, '"'+value+'"');
}
}
return javascript;
}
NINJA.notesAndTerms = function(invoice) NINJA.notesAndTerms = function(invoice)
{ {
var text = []; var data = [];
if (invoice.public_notes) { if (invoice.public_notes) {
text.push({text:processVariables(invoice.public_notes), style:'notes'}); data.push({text:invoice.public_notes, style: ['notes']});
data.push({text:' '});
} }
if (invoice.terms) { if (invoice.terms) {
text.push({text:invoiceLabels.terms, style:'termsLabel'}); data.push({text:invoiceLabels.terms, style: ['termsLabel']});
text.push({text:processVariables(invoice.terms), style:'terms'}); data.push({text:invoice.terms, style: ['terms']});
} }
return text; return NINJA.prepareDataList(data, 'notesAndTerms');
}
NINJA.invoiceColumns = function(invoice)
{
if (invoice.account.hide_quantity == '1') {
return ["15%", "*", "10%", "15%"];
} else {
return ["15%", "*", "10%", "auto", "15%"];
}
} }
NINJA.invoiceLines = function(invoice) { NINJA.invoiceLines = function(invoice) {
var grid = [
[
{text: invoiceLabels.item, style: 'tableHeader'},
{text: invoiceLabels.description, style: 'tableHeader'},
{text: invoiceLabels.unit_cost, style: 'tableHeader'},
{text: invoiceLabels.quantity, style: 'tableHeader'},
{text: invoice.has_taxes?invoiceLabels.tax:'', style: 'tableHeader'},
{text: invoiceLabels.line_total, style: 'tableHeader'}
]
];
var total = 0; var total = 0;
var shownItem = false; var shownItem = false;
var currencyId = invoice && invoice.client ? invoice.client.currency_id : 1; var currencyId = invoice && invoice.client ? invoice.client.currency_id : 1;
var hideQuantity = invoice.account.hide_quantity == '1'; var hideQuantity = invoice.account.hide_quantity == '1';
var grid = [[
{text: invoiceLabels.item, style: ['tableHeader', 'itemTableHeader']},
{text: invoiceLabels.description, style: ['tableHeader', 'descriptionTableHeader']},
{text: invoiceLabels.unit_cost, style: ['tableHeader', 'costTableHeader']}
]];
if (!hideQuantity) {
grid[0].push({text: invoiceLabels.quantity, style: ['tableHeader', 'qtyTableHeader']});
}
grid[0].push({text: invoiceLabels.line_total, style: ['tableHeader', 'lineTotalTableHeader']});
for (var i = 0; i < invoice.invoice_items.length; i++) { for (var i = 0; i < invoice.invoice_items.length; i++) {
var row = []; var row = [];
var item = invoice.invoice_items[i]; var item = invoice.invoice_items[i];
var cost = formatMoney(item.cost, currencyId, true); var cost = formatMoney(item.cost, currencyId, true);
var qty = NINJA.parseFloat(item.qty) ? roundToTwo(NINJA.parseFloat(item.qty)) + '' : ''; var qty = NINJA.parseFloat(item.qty) ? roundToTwo(NINJA.parseFloat(item.qty)) + '' : '';
var notes = item.notes; var notes = item.notes;
var productKey = item.product_key; var productKey = item.product_key;
var tax = "";
if (item.tax && parseFloat(item.tax.rate)) {
tax = parseFloat(item.tax.rate);
} else if (item.tax_rate && parseFloat(item.tax_rate)) {
tax = parseFloat(item.tax_rate);
}
// show at most one blank line // show at most one blank line
if (shownItem && (!cost || cost == '0.00') && !notes && !productKey) { if (shownItem && (!cost || cost == '0.00') && !notes && !productKey) {
continue; continue;
} }
shownItem = true; shownItem = true;
// process date variables // process date variables
@ -142,148 +231,179 @@ NINJA.invoiceLines = function(invoice) {
} }
var lineTotal = roundToTwo(NINJA.parseFloat(item.cost)) * roundToTwo(NINJA.parseFloat(item.qty)); var lineTotal = roundToTwo(NINJA.parseFloat(item.cost)) * roundToTwo(NINJA.parseFloat(item.qty));
if (tax) {
lineTotal += lineTotal * tax / 100;
}
if (lineTotal) {
total += lineTotal;
}
lineTotal = formatMoney(lineTotal, currencyId); lineTotal = formatMoney(lineTotal, currencyId);
rowStyle = i%2===0?'odd':'even'; rowStyle = (i % 2 == 0) ? 'odd' : 'even';
row[0] = {style:["productKey", rowStyle], text:productKey}; row.push({style:["productKey", rowStyle], text:productKey || ' '}); // product key can be blank when selecting from a datalist
row[1] = {style:["notes", rowStyle], text:notes}; row.push({style:["notes", rowStyle], text:notes || ' '});
row[2] = {style:["cost", rowStyle], text:cost}; row.push({style:["cost", rowStyle], text:cost});
row[3] = {style:["quantity", rowStyle], text:qty}; if (!hideQuantity) {
row[4] = {style:["tax", rowStyle], text:""+tax}; row.push({style:["quantity", rowStyle], text:qty || ' '});
row[5] = {style:["lineTotal", rowStyle], text:lineTotal}; }
row.push({style:["lineTotal", rowStyle], text:lineTotal || ' '});
grid.push(row); grid.push(row);
} }
return grid; return NINJA.prepareDataTable(grid, 'invoiceItems');
} }
NINJA.subtotals = function(invoice) NINJA.subtotals = function(invoice, hideBalance)
{ {
if (!invoice) { if (!invoice) {
return; return;
} }
var data = [ var account = invoice.account;
[invoiceLabels.subtotal, formatMoney(invoice.subtotal_amount, invoice.client.currency_id)], var data = [];
]; data.push([{text: invoiceLabels.subtotal}, {text: formatMoney(invoice.subtotal_amount, invoice.client.currency_id)}]);
if(invoice.discount_amount != 0) { if (invoice.discount_amount != 0) {
data.push([invoiceLabels.discount, formatMoney(invoice.discount_amount, invoice.client.currency_id)]); data.push([{text: invoiceLabels.discount}, {text: formatMoney(invoice.discount_amount, invoice.client.currency_id)}]);
} }
if (NINJA.parseFloat(invoice.custom_value1) && invoice.custom_taxes1 == '1') { if (NINJA.parseFloat(invoice.custom_value1) && invoice.custom_taxes1 == '1') {
data.push([invoiceLabels.custom_invoice_label1, formatMoney(invoice.custom_value1, invoice.client.currency_id)]); data.push([{text: account.custom_invoice_label1}, {text: formatMoney(invoice.custom_value1, invoice.client.currency_id)}]);
} }
if (NINJA.parseFloat(invoice.custom_value2) && invoice.custom_taxes2 == '1') { if (NINJA.parseFloat(invoice.custom_value2) && invoice.custom_taxes2 == '1') {
data.push([invoiceLabels.custom_invoice_label2, formatMoney(invoice.custom_value2, invoice.client.currency_id)]); data.push([{text: account.custom_invoice_label2}, {text: formatMoney(invoice.custom_value2, invoice.client.currency_id)}]);
} }
if(invoice.tax && invoice.tax.name || invoice.tax_name) { for (var key in invoice.item_taxes) {
data.push([invoiceLabels.tax, formatMoney(invoice.tax_amount, invoice.client.currency_id)]); if (invoice.item_taxes.hasOwnProperty(key)) {
var taxRate = invoice.item_taxes[key];
var taxStr = taxRate.name + ' ' + (taxRate.rate*1).toString() + '%';
data.push([{text: taxStr}, {text: formatMoney(taxRate.amount, invoice.client.currency_id)}]);
}
} }
if (NINJA.parseFloat(invoice.custom_value1) && invoice.custom_taxes1 != '1') { if (invoice.tax && invoice.tax.name || invoice.tax_name) {
data.push([invoiceLabels.custom_invoice_label1, formatMoney(invoice.custom_value1, invoice.client.currency_id)]); var taxStr = invoice.tax_name + ' ' + (invoice.tax_rate*1).toString() + '%';
data.push([{text: taxStr}, {text: formatMoney(invoice.tax_amount, invoice.client.currency_id)}]);
}
if (NINJA.parseFloat(invoice.custom_value1) && invoice.custom_taxes1 != '1') {
data.push([{text: account.custom_invoice_label1}, {text: formatMoney(invoice.custom_value1, invoice.client.currency_id)}]);
} }
if (NINJA.parseFloat(invoice.custom_value2) && invoice.custom_taxes2 != '1') { if (NINJA.parseFloat(invoice.custom_value2) && invoice.custom_taxes2 != '1') {
data.push([invoiceLabels.custom_invoice_label2, formatMoney(invoice.custom_value2, invoice.client.currency_id)]); data.push([{text: account.custom_invoice_label2}, {text: formatMoney(invoice.custom_value2, invoice.client.currency_id)}]);
} }
var paid = invoice.amount - invoice.balance; var paid = invoice.amount - invoice.balance;
if (invoice.account.hide_paid_to_date != '1' || paid) { if (invoice.account.hide_paid_to_date != '1' || paid) {
data.push([invoiceLabels.paid_to_date, formatMoney(paid, invoice.client.currency_id)]); data.push([{text:invoiceLabels.paid_to_date}, {text:formatMoney(paid, invoice.client.currency_id)}]);
} }
data.push([{text:invoice.is_quote ? invoiceLabels.total : invoiceLabels.balance_due, style:'balanceDueLabel'}, if (!hideBalance) {
{text:formatMoney(invoice.balance_amount, invoice.client.currency_id), style:'balanceDueValue'}]); var isPartial = NINJA.parseFloat(invoice.partial);
return data; data.push([
{text: isPartial ? invoiceLabels.amount_due : invoiceLabels.balance_due, style:['balanceDueLabel']},
{text: formatMoney(invoice.balance_amount, invoice.client.currency_id), style:['balanceDue']}
]);
}
return NINJA.prepareDataPairs(data, 'subtotals');
} }
NINJA.accountDetails = function(account) { NINJA.accountDetails = function(invoice) {
var data = []; var account = invoice.account;
if(account.name) data.push({text:account.name, style:'accountName'}); var data = [
if(account.id_number) data.push({text:account.id_number, style:'accountDetails'}); {text:account.name, style: ['accountName']},
if(account.vat_number) data.push({text:account.vat_number, style:'accountDetails'}); {text:account.id_number},
if(account.work_email) data.push({text:account.work_email, style:'accountDetails'}); {text:account.vat_number},
if(account.work_phone) data.push({text:account.work_phone, style:'accountDetails'}); {text:account.work_email},
return data; {text:account.work_phone}
];
return NINJA.prepareDataList(data, 'accountDetails');
} }
NINJA.accountAddress = function(account) { NINJA.accountAddress = function(invoice) {
var address = ''; var account = invoice.account;
var cityStatePostal = '';
if (account.city || account.state || account.postal_code) { if (account.city || account.state || account.postal_code) {
address = ((account.city ? account.city + ', ' : '') + account.state + ' ' + account.postal_code).trim(); cityStatePostal = ((account.city ? account.city + ', ' : '') + account.state + ' ' + account.postal_code).trim();
} }
var data = [];
if(account.address1) data.push({text:account.address1, style:'accountDetails'}); var data = [
if(account.address2) data.push({text:account.address2, style:'accountDetails'}); {text: account.address1},
if(address) data.push({text:address, style:'accountDetails'}); {text: account.address2},
if(account.country) data.push({text:account.country.name, style: 'accountDetails'}); {text: cityStatePostal},
if(account.custom_label1 && account.custom_value1) data.push({text:account.custom_label1 +' '+ account.custom_value1, style: 'accountDetails'}); {text: account.country ? account.country.name : ''},
if(account.custom_label2 && account.custom_value2) data.push({text:account.custom_label2 +' '+ account.custom_value2, style: 'accountDetails'}); {text: invoice.account.custom_value1 ? invoice.account.custom_label1 + ' ' + invoice.account.custom_value1 : false},
return data; {text: invoice.account.custom_value2 ? invoice.account.custom_label2 + ' ' + invoice.account.custom_value2 : false}
];
return NINJA.prepareDataList(data, 'accountAddress');
} }
NINJA.invoiceDetails = function(invoice) { NINJA.invoiceDetails = function(invoice) {
var data = [ var data = [
[ [
invoice.is_quote ? invoiceLabels.quote_number : invoiceLabels.invoice_number, {text: (invoice.is_quote ? invoiceLabels.quote_number : invoiceLabels.invoice_number), style: ['invoiceNumberLabel']},
{style: 'bold', text: invoice.invoice_number}, {text: invoice.invoice_number, style: ['invoiceNumber']}
], ],
[ [
invoice.is_quote ? invoiceLabels.quote_date : invoiceLabels.invoice_date, {text: invoiceLabels.po_number},
invoice.invoice_date, {text: invoice.po_number}
], ],
[ [
invoice.is_quote ? invoiceLabels.total : invoiceLabels.balance_due, {text: (invoice.is_quote ? invoiceLabels.quote_date : invoiceLabels.invoice_date)},
formatMoney(invoice.balance_amount, invoice.client.currency_id), {text: invoice.invoice_date}
], ],
[
{text: invoiceLabels.due_date},
{text: invoice.due_date}
]
]; ];
return data;
var isPartial = NINJA.parseFloat(invoice.partial);
if (NINJA.parseFloat(invoice.balance) < NINJA.parseFloat(invoice.amount)) {
data.push([
{text: invoiceLabels.total},
{text: formatMoney(invoice.amount, invoice.client.currency_id)}
]);
} else if (isPartial) {
data.push([
{text: invoiceLabels.total},
{text: formatMoney(invoice.total_amount, invoice.client.currency_id)}
]);
}
data.push([
{text: isPartial ? invoiceLabels.amount_due : invoiceLabels.balance_due, style: ['invoiceDetailBalanceDueLabel']},
{text: formatMoney(invoice.balance_amount, invoice.client.currency_id), style: ['invoiceDetailBalanceDue']}
])
return NINJA.prepareDataPairs(data, 'invoiceDetails');
} }
NINJA.clientDetails = function(invoice) { NINJA.clientDetails = function(invoice) {
var client = invoice.client; var client = invoice.client;
var data;
if (!client) { if (!client) {
return; return;
} }
var contact = client.contacts[0];
var clientName = client.name || (contact.first_name || contact.last_name ? (contact.first_name + ' ' + contact.last_name) : contact.email);
var clientEmail = client.contacts[0].email == clientName ? '' : client.contacts[0].email;
var fields = [ data = [
getClientDisplayName(client), {text:clientName || ' ', style: ['clientName']},
client.id_number, {text:client.address1},
client.vat_number, {text:concatStrings(client.city, client.state, client.postal_code)},
concatStrings(client.address1, client.address2), {text:client.country ? client.country.name : ''},
concatStrings(client.city, client.state, client.postal_code), {text:clientEmail},
client.country ? client.country.name : false, {text: invoice.client.custom_value1 ? invoice.account.custom_client_label1 + ' ' + invoice.client.custom_value1 : false},
invoice.contact && getClientDisplayName(client) != invoice.contact.email ? invoice.contact.email : false, {text: invoice.client.custom_value2 ? invoice.account.custom_client_label2 + ' ' + invoice.client.custom_value2 : false}
invoice.client.custom_value1 ? invoice.account['custom_client_label1'] + ' ' + invoice.client.custom_value1 : false,
invoice.client.custom_value2 ? invoice.account['custom_client_label2'] + ' ' + invoice.client.custom_value2 : false,
]; ];
var data = []; return NINJA.prepareDataList(data, 'clientDetails');
for (var i=0; i<fields.length; i++) {
var field = fields[i];
if (!field) {
continue;
}
data.push([field]);
}
if (!data.length) {
data.push(['']);
}
return data;
} }
NINJA.getPrimaryColor = function(defaultColor) { NINJA.getPrimaryColor = function(defaultColor) {
return NINJA.primaryColor ? NINJA.primaryColor : defaultColor; return NINJA.primaryColor ? NINJA.primaryColor : defaultColor;
} }
@ -292,6 +412,62 @@ NINJA.getSecondaryColor = function(defaultColor) {
return NINJA.primaryColor ? NINJA.secondaryColor : defaultColor; return NINJA.primaryColor ? NINJA.secondaryColor : defaultColor;
} }
NINJA.getEntityLabel = function(invoice) { // remove blanks and add section style to all elements
return invoice.is_quote ? invoiceLabels.quote : invoiceLabels.invoice; NINJA.prepareDataList = function(oldData, section) {
var newData = [];
for (var i=0; i<oldData.length; i++) {
var item = NINJA.processItem(oldData[i], section);
if (item.text) {
newData.push(item);
}
}
return newData;
}
NINJA.prepareDataTable = function(oldData, section) {
var newData = [];
for (var i=0; i<oldData.length; i++) {
var row = oldData[i];
var newRow = [];
for (var j=0; j<row.length; j++) {
var item = NINJA.processItem(row[j], section);
if (item.text) {
newRow.push(item);
}
}
if (newRow.length) {
newData.push(newRow);
}
}
return newData;
}
NINJA.prepareDataPairs = function(oldData, section) {
var newData = [];
for (var i=0; i<oldData.length; i++) {
var row = oldData[i];
var isBlank = false;
for (var j=0; j<row.length; j++) {
var item = NINJA.processItem(row[j], section);
if (!item.text) {
isBlank = true;
}
if (j == 1) {
NINJA.processItem(row[j], section + "Value");
}
}
if (!isBlank) {
newData.push(oldData[i]);
}
}
return newData;
}
NINJA.processItem = function(item, section) {
if (item.style && item.style instanceof Array) {
item.style.push(section);
} else {
item.style = [section];
}
return item;
} }

View File

@ -8,33 +8,51 @@ var isIE = /*@cc_on!@*/false || !!document.documentMode; // At least IE6
var invoiceOld; var invoiceOld;
var refreshTimer;
function generatePDF(invoice, javascript, force, cb) { function generatePDF(invoice, javascript, force, cb) {
if (!invoice || !javascript) {
return;
}
//console.log('== generatePDF - force: %s', force);
if (force || !invoiceOld) {
refreshTimer = null;
} else {
if (refreshTimer) {
clearTimeout(refreshTimer);
}
refreshTimer = setTimeout(function() {
generatePDF(invoice, javascript, true, cb);
}, 500);
return;
}
invoice = calculateAmounts(invoice); invoice = calculateAmounts(invoice);
var a = copyInvoice(invoice); var a = copyObject(invoice);
var b = copyInvoice(invoiceOld); var b = copyObject(invoiceOld);
if (!force && _.isEqual(a, b)) { if (!force && _.isEqual(a, b)) {
return; return;
} }
pdfmakeMarker = "//pdfmake";
invoiceOld = invoice; invoiceOld = invoice;
report_id = invoice.invoice_design_id; pdfmakeMarker = "{";
if(javascript.slice(0, pdfmakeMarker.length) === pdfmakeMarker) { if(javascript.slice(0, pdfmakeMarker.length) === pdfmakeMarker) {
doc = GetPdfMake(invoice, javascript, cb); doc = GetPdfMake(invoice, javascript, cb);
//doc.getDataUrl(cb);
} else { } else {
doc = GetPdf(invoice, javascript); doc = GetPdf(invoice, javascript);
doc.getDataUrl = function(cb) { doc.getDataUrl = function(cb) {
cb( this.output("datauristring")); cb( this.output("datauristring"));
}; };
} }
if (cb) {
doc.getDataUrl(cb);
}
return doc; return doc;
} }
function copyInvoice(orig) { function copyObject(orig) {
if (!orig) return false; if (!orig) return false;
var copy = JSON.stringify(orig); return JSON.parse(JSON.stringify(orig));
var copy = JSON.parse(copy);
return copy;
} }
@ -58,12 +76,14 @@ function GetPdf(invoice, javascript){
lineTotalRight: 550 lineTotalRight: 550
}; };
/*
if (invoice.has_taxes) if (invoice.has_taxes)
{ {
layout.descriptionLeft -= 20; layout.descriptionLeft -= 20;
layout.unitCostRight -= 40; layout.unitCostRight -= 40;
layout.qtyRight -= 40; layout.qtyRight -= 40;
} }
*/
/* /*
@param orientation One of "portrait" or "landscape" (or shortcuts "p" (Default), "l") @param orientation One of "portrait" or "landscape" (or shortcuts "p" (Default), "l")
@ -859,9 +879,6 @@ function displayGrid(doc, invoice, data, x, y, layout, options) {
key = invoice.account[key]; key = invoice.account[key];
} else if (key === 'tax' && invoice.tax_name) { } else if (key === 'tax' && invoice.tax_name) {
key = invoice.tax_name + ' ' + (invoice.tax_rate*1).toString() + '%'; key = invoice.tax_name + ' ' + (invoice.tax_rate*1).toString() + '%';
if (invoice.tax_name.toLowerCase().indexOf(invoiceLabels['tax'].toLowerCase()) == -1) {
key = invoiceLabels['tax'] + ': ' + key;
}
} else if (key === 'discount' && NINJA.parseFloat(invoice.discount) && !parseInt(invoice.is_amount_discount)) { } else if (key === 'discount' && NINJA.parseFloat(invoice.discount) && !parseInt(invoice.is_amount_discount)) {
key = invoiceLabels[key] + ' ' + parseFloat(invoice.discount) + '%'; key = invoiceLabels[key] + ' ' + parseFloat(invoice.discount) + '%';
} else { } else {
@ -913,22 +930,49 @@ function displayNotesAndTerms(doc, layout, invoice, y)
function calculateAmounts(invoice) { function calculateAmounts(invoice) {
var total = 0; var total = 0;
var hasTaxes = false; var hasTaxes = false;
var taxes = {};
// sum line item
for (var i=0; i<invoice.invoice_items.length; i++) {
var item = invoice.invoice_items[i];
var lineTotal = roundToTwo(NINJA.parseFloat(item.cost)) * roundToTwo(NINJA.parseFloat(item.qty));
if (lineTotal) {
total += lineTotal;
}
}
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 tax = 0; var taxRate = 0;
var taxName = '';
// the object structure differs if it's read from the db or created by knockoutJS
if (item.tax && parseFloat(item.tax.rate)) { if (item.tax && parseFloat(item.tax.rate)) {
tax = parseFloat(item.tax.rate); taxRate = parseFloat(item.tax.rate);
taxName = item.tax.name;
} else if (item.tax_rate && parseFloat(item.tax_rate)) { } else if (item.tax_rate && parseFloat(item.tax_rate)) {
tax = parseFloat(item.tax_rate); taxRate = parseFloat(item.tax_rate);
taxName = item.tax_name;
} }
// calculate line item tax
var lineTotal = roundToTwo(NINJA.parseFloat(item.cost)) * roundToTwo(NINJA.parseFloat(item.qty)); var lineTotal = roundToTwo(NINJA.parseFloat(item.cost)) * roundToTwo(NINJA.parseFloat(item.qty));
if (tax) { if (invoice.discount != 0) {
lineTotal += roundToTwo(lineTotal * tax / 100); if (parseInt(invoice.is_amount_discount)) {
lineTotal -= roundToTwo((lineTotal/total) * invoice.discount);
} else {
lineTotal -= roundToTwo(lineTotal * (invoice.discount/100));
}
} }
if (lineTotal) { var taxAmount = roundToTwo(lineTotal * taxRate / 100);
total += lineTotal;
if (taxRate) {
var key = taxName + taxRate;
if (taxes.hasOwnProperty(key)) {
taxes[key].amount += taxAmount;
} else {
taxes[key] = {name: taxName, rate:taxRate, amount:taxAmount};
}
} }
if ((item.tax && item.tax.name) || item.tax_name) { if ((item.tax && item.tax.name) || item.tax_name) {
@ -968,6 +1012,12 @@ function calculateAmounts(invoice) {
total = parseFloat(total) + parseFloat(tax); total = parseFloat(total) + parseFloat(tax);
} }
for (var key in taxes) {
if (taxes.hasOwnProperty(key)) {
total += taxes[key].amount;
}
}
// custom fields w/o with taxes // custom fields w/o with taxes
if (NINJA.parseFloat(invoice.custom_value1) && invoice.custom_taxes1 != '1') { if (NINJA.parseFloat(invoice.custom_value1) && invoice.custom_taxes1 != '1') {
total += roundToTwo(invoice.custom_value1); total += roundToTwo(invoice.custom_value1);
@ -979,8 +1029,8 @@ function calculateAmounts(invoice) {
invoice.total_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance)); invoice.total_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance));
invoice.discount_amount = discount; invoice.discount_amount = discount;
invoice.tax_amount = tax; invoice.tax_amount = tax;
invoice.has_taxes = hasTaxes; invoice.item_taxes = taxes;
if (NINJA.parseFloat(invoice.partial)) { if (NINJA.parseFloat(invoice.partial)) {
invoice.balance_amount = roundToTwo(invoice.partial); invoice.balance_amount = roundToTwo(invoice.partial);
} else { } else {
@ -1024,11 +1074,12 @@ function displayInvoiceHeader(doc, invoice, layout) {
} }
doc.text(totalX, layout.tableTop, invoiceLabels.line_total); doc.text(totalX, layout.tableTop, invoiceLabels.line_total);
/*
if (invoice.has_taxes) if (invoice.has_taxes)
{ {
doc.text(taxX, layout.tableTop, invoiceLabels.tax); doc.text(taxX, layout.tableTop, invoiceLabels.tax);
} }
*/
} }
function displayInvoiceItems(doc, invoice, layout) { function displayInvoiceItems(doc, invoice, layout) {
@ -1211,10 +1262,11 @@ function displayInvoiceItems(doc, invoice, layout) {
doc.line(qtyX-45, y-16,qtyX-45, y+55); doc.line(qtyX-45, y-16,qtyX-45, y+55);
/*
if (invoice.has_taxes) { if (invoice.has_taxes) {
doc.line(taxX-15, y-16,taxX-15, y+55); doc.line(taxX-15, y-16,taxX-15, y+55);
} }
*/
doc.line(totalX-27, y-16,totalX-27, y+55); doc.line(totalX-27, y-16,totalX-27, y+55);
} }
@ -1277,9 +1329,11 @@ function displayInvoiceItems(doc, invoice, layout) {
doc.line(layout.descriptionLeft-8, topX,layout.descriptionLeft-8, y); doc.line(layout.descriptionLeft-8, topX,layout.descriptionLeft-8, y);
doc.line(layout.unitCostRight-55, topX,layout.unitCostRight-55, y); doc.line(layout.unitCostRight-55, topX,layout.unitCostRight-55, y);
doc.line(layout.qtyRight-50, topX,layout.qtyRight-50, y); doc.line(layout.qtyRight-50, topX,layout.qtyRight-50, y);
/*
if (invoice.has_taxes) { if (invoice.has_taxes) {
doc.line(layout.taxRight-28, topX,layout.taxRight-28, y); doc.line(layout.taxRight-28, topX,layout.taxRight-28, y);
} }
*/
doc.line(totalX-25, topX,totalX-25, y+90); doc.line(totalX-25, topX,totalX-25, y+90);
doc.line(totalX+45, topX,totalX+45, y+90); doc.line(totalX+45, topX,totalX+45, y+90);
} }
@ -1574,4 +1628,20 @@ function twoDigits(value) {
return '0' + value; return '0' + value;
} }
return value; return value;
}
function toSnakeCase(str) {
if (!str) return '';
return str.replace(/([A-Z])/g, function($1){return "_"+$1.toLowerCase();});
}
function getDescendantProp(obj, desc) {
var arr = desc.split(".");
while(arr.length && (obj = obj[arr.shift()]));
return obj;
}
function doubleDollarSign(str) {
if (!str) return '';
return str.replace(/\$/g, '\$\$\$');
} }

View File

@ -1,162 +0,0 @@
//pdfmake
/*
var dd = {
content: 'wqy中文wqy',
defaultStyle: {
font: 'wqy'
}
};
*/
var dd = {
content: [
{
columns: [
[
invoice.image?
{
image: invoice.image,
fit: [150, 80]
}:""
],
{
stack: NINJA.accountDetails(account)
},
{
stack: NINJA.accountAddress(account)
}
]
},
{
text:(NINJA.getEntityLabel(invoice)).toUpperCase(),
margin: [8, 70, 8, 16],
style: 'primaryColor',
fontSize: NINJA.fontSize + 2
},
{
table: {
headerRows: 1,
widths: ['auto', 'auto', '*'],
body: [
[
{
table: {
body: NINJA.invoiceDetails(invoice),
},
layout: 'noBorders',
},
{
table: {
body: NINJA.clientDetails(invoice),
},
layout: 'noBorders',
},
''
]
]
},
layout: {
hLineWidth: function (i, node) {
return (i === 0 || i === node.table.body.length) ? .5 : 0;
},
vLineWidth: function (i, node) {
return 0;
},
hLineColor: function (i, node) {
return '#D8D8D8';
},
paddingLeft: function(i, node) { return 8; },
paddingRight: function(i, node) { return 8; },
paddingTop: function(i, node) { return 4; },
paddingBottom: function(i, node) { return 4; }
}
},
'\n',
{
table: {
headerRows: 1,
widths: ['15%', '*', 'auto', 'auto', 'auto', 'auto'],
body: NINJA.invoiceLines(invoice),
},
layout: {
hLineWidth: function (i, node) {
return i === 0 ? 0 : .5;
},
vLineWidth: function (i, node) {
return 0;
},
hLineColor: function (i, node) {
return '#D8D8D8';
},
paddingLeft: function(i, node) { return 8; },
paddingRight: function(i, node) { return 8; },
paddingTop: function(i, node) { return 8; },
paddingBottom: function(i, node) { return 8; }
},
},
'\n',
{
columns: [
NINJA.notesAndTerms(invoice),
{
style: 'subtotals',
table: {
widths: ['*', '*'],
body: NINJA.subtotals(invoice),
},
layout: {
hLineWidth: function (i, node) {
return 0;
},
vLineWidth: function (i, node) {
return 0;
},
paddingLeft: function(i, node) { return 8; },
paddingRight: function(i, node) { return 8; },
paddingTop: function(i, node) { return 4; },
paddingBottom: function(i, node) { return 4; }
},
}
]
},
],
defaultStyle: {
//font: 'arialuni',
fontSize: NINJA.fontSize,
margin: [8, 4, 8, 4]
},
styles: {
primaryColor:{
color: NINJA.getPrimaryColor('#299CC2')
},
accountName: {
margin: [4, 2, 4, 2],
color: NINJA.getPrimaryColor('#299CC2')
},
accountDetails: {
margin: [4, 2, 4, 2],
color: '#AAA9A9'
},
even: {
},
odd: {
fillColor:'#F4F4F4'
},
productKey: {
color: NINJA.getPrimaryColor('#299CC2')
},
tableHeader: {
bold: true
},
balanceDueLabel: {
fontSize: NINJA.fontSize + 2
},
balanceDueValue: {
fontSize: NINJA.fontSize + 2,
color: NINJA.getPrimaryColor('#299CC2')
},
},
pageMargins: [40, 40, 40, 40],
};

View File

@ -3,9 +3,13 @@
If you'd like to use our code to sell your own invoicing app email us for details about our affiliate program. If you'd like to use our code to sell your own invoicing app email us for details about our affiliate program.
### Getting Started ### Installation Options
To setup the site you can either use the [zip file](https://www.invoiceninja.com/knowledgebase/self-host/) (easier to run) or checkout the code from GitHub (easier to make changes). * [Zip - Free](https://www.invoiceninja.com/knowledgebase/self-host/)
* [Bitnami - Free](https://bitnami.com/stack/invoice-ninja)
* [Softaculous - $30](https://www.softaculous.com/apps/ecommerce/Invoice_Ninja)
### Getting Started
If you have any questions or comments please use our [support forum](https://www.invoiceninja.com/forums/forum/support/). For updates follow [@invoiceninja](https://twitter.com/invoiceninja) or join the [Facebook Group](https://www.facebook.com/invoiceninja). If you have any questions or comments please use our [support forum](https://www.invoiceninja.com/forums/forum/support/). For updates follow [@invoiceninja](https://twitter.com/invoiceninja) or join the [Facebook Group](https://www.facebook.com/invoiceninja).
@ -31,12 +35,11 @@ If you'd like to translate the site please use [caouecs/Laravel4-long](https://g
* [Jeramy Simpson](https://github.com/JeramyMywork) - [MyWork](https://www.mywork.com.au) * [Jeramy Simpson](https://github.com/JeramyMywork) - [MyWork](https://www.mywork.com.au)
* [Sigitas Limontas](https://lt.linkedin.com/in/sigitaslimontas) * [Sigitas Limontas](https://lt.linkedin.com/in/sigitaslimontas)
### Documentation ### Documentation
* [Self Host](https://www.invoiceninja.com/knowledgebase/self-host/)
* [Ubuntu and Apache](http://blog.technerdservices.com/index.php/2015/04/techpop-how-to-install-invoice-ninja-on-ubuntu-14-04/) * [Ubuntu and Apache](http://blog.technerdservices.com/index.php/2015/04/techpop-how-to-install-invoice-ninja-on-ubuntu-14-04/)
* [Debian and Nginx](https://www.rosehosting.com/blog/install-invoice-ninja-on-a-debian-7-vps/) * [Debian and Nginx](https://www.rosehosting.com/blog/install-invoice-ninja-on-a-debian-7-vps/)
* [API Documentation](https://www.invoiceninja.com/knowledgebase/api-documentation/) * [API Documentation](https://www.invoiceninja.com/knowledgebase/api-documentation/)
* [User Guide](https://www.invoiceninja.com/user-guide/)
* [Developer Guide](https://www.invoiceninja.com/knowledgebase/developer-guide/) * [Developer Guide](https://www.invoiceninja.com/knowledgebase/developer-guide/)
### Frameworks/Libraries ### Frameworks/Libraries
@ -67,4 +70,5 @@ If you'd like to translate the site please use [caouecs/Laravel4-long](https://g
* [jashkenas/underscore](https://github.com/jashkenas/underscore) - JavaScript's utility _ belt * [jashkenas/underscore](https://github.com/jashkenas/underscore) - JavaScript's utility _ belt
* [caouecs/Laravel4-long](https://github.com/caouecs/Laravel4-lang) - List of languages for Laravel4 * [caouecs/Laravel4-long](https://github.com/caouecs/Laravel4-lang) - List of languages for Laravel4
* [bgrins/spectrum](https://github.com/bgrins/spectrum) - The No Hassle JavaScript Colorpicker * [bgrins/spectrum](https://github.com/bgrins/spectrum) - The No Hassle JavaScript Colorpicker
* [lokesh/lightbox2](https://github.com/lokesh/lightbox2/) - The original lightbox script * [lokesh/lightbox2](https://github.com/lokesh/lightbox2/) - The original lightbox script
* [josdejong/jsoneditor](https://github.com/josdejong/jsoneditor/) - A web-based tool to view, edit and format JSON

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@ return array(
'client' => 'Kunde', 'client' => 'Kunde',
'invoice_date' => 'Rechnungsdatum', 'invoice_date' => 'Rechnungsdatum',
'due_date' => 'Fällig am', 'due_date' => 'Fällig am',
'invoice_number' => 'Rechungsnummer', 'invoice_number' => 'Rechnungsnummer',
'invoice_number_short' => 'Rechnung #', 'invoice_number_short' => 'Rechnung #',
'po_number' => 'Bestellnummer', 'po_number' => 'Bestellnummer',
'po_number_short' => 'BN #', 'po_number_short' => 'BN #',
@ -46,7 +46,7 @@ return array(
'line_total' => 'Summe', 'line_total' => 'Summe',
'subtotal' => 'Zwischensumme', 'subtotal' => 'Zwischensumme',
'paid_to_date' => 'Bereits gezahlt', 'paid_to_date' => 'Bereits gezahlt',
'balance_due' => 'Rechnungsbetrag', 'balance_due' => 'Geschuldeter Betrag',
'invoice_design_id' => 'Design', 'invoice_design_id' => 'Design',
'terms' => 'Bedingungen', 'terms' => 'Bedingungen',
'your_invoice' => 'Ihre Rechnung', 'your_invoice' => 'Ihre Rechnung',
@ -96,7 +96,8 @@ return array(
'import' => 'Importieren', 'import' => 'Importieren',
'download' => 'Downloaden', 'download' => 'Downloaden',
'cancel' => 'Abbrechen', 'cancel' => 'Abbrechen',
'provide_email' => 'Bitte gib eine gültige E-Mail Adresse an', 'close' => 'Schließen',
'provide_email' => 'Bitte gib eine gültige E-Mail-Adresse an',
'powered_by' => 'Powered by', 'powered_by' => 'Powered by',
'no_items' => 'Keine Objekte', 'no_items' => 'Keine Objekte',
@ -106,7 +107,7 @@ return array(
<p>Benutze :MONTH, :QUARTER oder :YEAR für ein dynamisches Datum. Grundlegende Mathematik funktioniert genauso gut, zum Beispiel :MONTH-1.</p> <p>Benutze :MONTH, :QUARTER oder :YEAR für ein dynamisches Datum. Grundlegende Mathematik funktioniert genauso gut, zum Beispiel :MONTH-1.</p>
<p>Beispiel zu dynamischen Rechnungs-Variabeln:</p> <p>Beispiel zu dynamischen Rechnungs-Variabeln:</p>
<ul> <ul>
<li>"Fitnessstudio Mitgliedschaft für den Monat :MONTH" => "Fitnessstudio Mitgliedschaft für den Monat Juli"</li> <li>"Fitnessstudio-Mitgliedschaft für den Monat :MONTH" => "Fitnessstudio-Mitgliedschaft für den Monat Juli"</li>
<li>":YEAR+1 Jahresbeitrag" => "2015 Jahresbeitrag"</li> <li>":YEAR+1 Jahresbeitrag" => "2015 Jahresbeitrag"</li>
<li>"Vorschusszahlung für :QUARTER+1" => "Vorschusszahlung für Q2"</li> <li>"Vorschusszahlung für :QUARTER+1" => "Vorschusszahlung für Q2"</li>
</ul>', </ul>',
@ -205,7 +206,7 @@ return array(
'import_to' => 'Importieren nach', 'import_to' => 'Importieren nach',
'client_will_create' => 'Kunde wird erstellt', 'client_will_create' => 'Kunde wird erstellt',
'clients_will_create' => 'Kunden werden erstellt', 'clients_will_create' => 'Kunden werden erstellt',
'email_settings' => 'E-Mail Einstellungen', 'email_settings' => 'E-Mail-Einstellungen',
'pdf_email_attachment' => 'PDF an E-Mails anhängen', 'pdf_email_attachment' => 'PDF an E-Mails anhängen',
// application messages // application messages
@ -218,7 +219,7 @@ return array(
'limit_clients' => 'Entschuldige, das überschreitet das Limit von :count Kunden', 'limit_clients' => 'Entschuldige, das überschreitet das Limit von :count Kunden',
'payment_error' => 'Es ist ein Fehler während der Zahlung aufgetreten. Bitte versuche es später noch einmal.', 'payment_error' => 'Es ist ein Fehler während der Zahlung aufgetreten. Bitte versuche es später noch einmal.',
'registration_required' => 'Bitte melde dich an um eine Rechnung zu versenden', 'registration_required' => 'Bitte melde dich an um eine Rechnung zu versenden',
'confirmation_required' => 'Bitte bestätige deine E-Mail Adresse', 'confirmation_required' => 'Bitte bestätige deine E-Mail-Adresse',
'updated_client' => 'Kunde erfolgreich aktualisiert', 'updated_client' => 'Kunde erfolgreich aktualisiert',
'created_client' => 'Kunde erfolgreich erstellt', 'created_client' => 'Kunde erfolgreich erstellt',
@ -251,16 +252,17 @@ return array(
'deleted_credits' => ':count Guthaben erfolgreich gelöscht', 'deleted_credits' => ':count Guthaben erfolgreich gelöscht',
// Emails // Emails
'confirmation_subject' => 'Invoice Ninja Kontobestätigung', 'confirmation_subject' => 'InvoiceNinja Kontobestätigung',
'confirmation_header' => 'Kontobestätigung', 'confirmation_header' => 'Kontobestätigung',
'confirmation_message' => 'Bitte klicke auf den folgenden Link um dein Konto zu bestätigen.', 'confirmation_message' => 'Bitte klicke auf den folgenden Link um dein Konto zu bestätigen.',
'invoice_message' => 'Um Ihre Rechnung über :amount einzusehen, klicken Sie bitte auf den folgenden Link.', 'invoice_subject' => 'Neue Rechnung :invoice von :account',
'invoice_message' => 'Um Ihre Rechnung über :amount einzusehen, klicken Sie bitte auf den folgenden Link:',
'payment_subject' => 'Zahlungseingang', 'payment_subject' => 'Zahlungseingang',
'payment_message' => 'Vielen Dank für Ihre Zahlung von :amount.', 'payment_message' => 'Vielen Dank für Ihre Zahlung von :amount.',
'email_salutation' => 'Sehr geehrte/r :name,', 'email_salutation' => 'Sehr geehrte/r :name,',
'email_signature' => 'Mit freundlichen Grüßen,', 'email_signature' => 'Mit freundlichen Grüßen,',
'email_from' => 'Das InvoiceNinja Team', 'email_from' => 'Das InvoiceNinja Team',
'user_email_footer' => 'Um deine E-Mail Benachrichtigungen anzupassen besuche bitte '.SITE_URL.'/company/notifications', 'user_email_footer' => 'Um deine E-Mail-Benachrichtigungen anzupassen besuche bitte '.SITE_URL.'/company/notifications',
'invoice_link_message' => 'Um deine Kundenrechnung anzuschauen, klicke auf den folgenden Link:', 'invoice_link_message' => 'Um deine Kundenrechnung anzuschauen, klicke auf den folgenden Link:',
'notification_invoice_paid_subject' => 'Die Rechnung :invoice wurde von :client bezahlt', 'notification_invoice_paid_subject' => 'Die Rechnung :invoice wurde von :client bezahlt',
'notification_invoice_sent_subject' => 'Die Rechnung :invoice wurde an :client versendet', 'notification_invoice_sent_subject' => 'Die Rechnung :invoice wurde an :client versendet',
@ -269,8 +271,9 @@ return array(
'notification_invoice_sent' => 'Dem folgenden Kunden :client wurde die Rechnung :invoice über :amount zugesendet.', 'notification_invoice_sent' => 'Dem folgenden Kunden :client wurde die Rechnung :invoice über :amount zugesendet.',
'notification_invoice_viewed' => 'Der folgende Kunde :client hat sich Rechnung :invoice über :amount angesehen.', 'notification_invoice_viewed' => 'Der folgende Kunde :client hat sich Rechnung :invoice über :amount angesehen.',
'reset_password' => 'Du kannst dein Passwort zurücksetzen, indem du auf den folgenden Link klickst:', 'reset_password' => 'Du kannst dein Passwort zurücksetzen, indem du auf den folgenden Link klickst:',
'reset_password_footer' => 'Wenn du das Zurücksetzen des Passworts nicht beantragt hast benachrichtige bitte unseren Support: ' . CONTACT_EMAIL, 'reset_password_footer' => 'Wenn du das Zurücksetzen des Passworts nicht beantragt hast, benachrichtige bitte unseren Support: ' . CONTACT_EMAIL,
// Payment page // Payment page
'secure_payment' => 'Sichere Zahlung', 'secure_payment' => 'Sichere Zahlung',
'card_number' => 'Kartennummer', 'card_number' => 'Kartennummer',
@ -281,7 +284,7 @@ return array(
// Security alerts // Security alerts
'security' => array( 'security' => array(
'too_many_attempts' => 'Zu viele Versuche. Bitte probiere es in ein paar Minuten erneut.', 'too_many_attempts' => 'Zu viele Versuche. Bitte probiere es in ein paar Minuten erneut.',
'wrong_credentials' => 'Falsche E-Mail Adresse oder falsches Passwort.', 'wrong_credentials' => 'Falsche E-Mail-Adresse oder falsches Passwort.',
'confirmation' => 'Dein Konto wurde bestätigt!', 'confirmation' => 'Dein Konto wurde bestätigt!',
'wrong_confirmation' => 'Falscher Bestätigungscode.', 'wrong_confirmation' => 'Falscher Bestätigungscode.',
'password_forgot' => 'Weitere Informationen um das Passwort zurückzusetzen wurden dir per E-Mail zugeschickt.', 'password_forgot' => 'Weitere Informationen um das Passwort zurückzusetzen wurden dir per E-Mail zugeschickt.',
@ -291,27 +294,31 @@ return array(
// Pro Plan // Pro Plan
'pro_plan' => [ 'pro_plan' => [
'remove_logo' => ':link, um das Invoice Ninja Logo zu entfernen, indem du dem Pro Plan beitrittst', 'remove_logo' => ':link, um das InvoiceNinja-Logo zu entfernen, indem du dem Pro Plan beitrittst',
'remove_logo_link' => 'Klicke hier', 'remove_logo_link' => 'Klicke hier',
], ],
'logout' => 'Ausloggen', 'logout' => 'Ausloggen',
'sign_up_to_save' => 'Melde dich an, um deine Arbeit zu speichern', 'sign_up_to_save' => 'Melde dich an, um deine Arbeit zu speichern',
'agree_to_terms' =>'Ich akzeptiere die Invoice Ninja :terms', 'agree_to_terms' =>'Ich akzeptiere die InvoiceNinja :terms',
'terms_of_service' => 'Service-Bedingungen', 'terms_of_service' => 'Service-Bedingungen',
'email_taken' => 'Diese E-Mail Adresse ist bereits registriert', 'email_taken' => 'Diese E-Mail-Adresse ist bereits registriert',
'working' => 'Wird bearbeitet', 'working' => 'Wird bearbeitet',
'success' => 'Erfolg', 'success' => 'Erfolg',
'success_message' => 'Du hast dich erfolgreich registriert. Bitte besuche den Link in deiner Bestätigungsmail um deine E-Mail Adresse zu verifizieren.', 'success_message' => 'Du hast dich erfolgreich registriert. Bitte besuche den Link in deiner Bestätigungsmail um deine E-Mail-Adresse zu verifizieren.',
'erase_data' => 'Diese Aktion wird deine Daten dauerhaft löschen.', 'erase_data' => 'Diese Aktion wird deine Daten dauerhaft löschen.',
'password' => 'Passwort', 'password' => 'Passwort',
'invoice_subject' => 'Neue Rechnung :invoice von :account',
'close' => 'Schließen', 'close' => 'Schließen',
'pro_plan_product' => 'Pro Plan', 'pro_plan_product' => 'Pro Plan',
'pro_plan_description' => 'Jahresmitgliedschaft beim Invoice Ninja Pro Plan.', 'pro_plan_description' => 'Jahresmitgliedschaft beim Invoice Ninja Pro Plan.',
'pro_plan_success' => 'Danke für den Beitritt! Sobald die Rechnung bezahlt wurde, beginnt deine Pro Plan Mitgliedschaft.', 'pro_plan_success' => 'Danke, dass Sie Invoice Ninja\'s Pro gewählt haben!<p/>&nbsp;<br/>
'pro_plan_success' => 'Danke für den Beitritt! Sobald die Rechnung bezahlt wurde,Beim Auswählen eines Produktes werden beginnt deine Pro Plan Mitgliedschaft.', <b>Nächste Schritte</b>Eine bezahlbare Rechnung wurde an die Mailadresse,
welche mit Ihrem Account verbunden ist, geschickt. Um alle der umfangreichen
Pro Funktionen freizuschalten, folgen Sie bitte den Anweisungen in der Rechnung um ein Jahr
die Pro Funktionen zu nutzen.
Sie finden die Rechnung nicht? Sie benötigen weitere Hilfe? Wir helfen gerne
-- schicken Sie uns doch eine Email an contact@invoice-ninja.com',
'unsaved_changes' => 'Es liegen ungespeicherte Änderungen vor', 'unsaved_changes' => 'Es liegen ungespeicherte Änderungen vor',
'custom_fields' => 'Benutzerdefinierte Felder', 'custom_fields' => 'Benutzerdefinierte Felder',
@ -320,6 +327,7 @@ return array(
'field_label' => 'Feldbezeichnung', 'field_label' => 'Feldbezeichnung',
'field_value' => 'Feldwert', 'field_value' => 'Feldwert',
'edit' => 'Bearbeiten', 'edit' => 'Bearbeiten',
'set_name' => 'Den Firmennamen setzen',
'view_as_recipient' => 'Als Empfänger betrachten', 'view_as_recipient' => 'Als Empfänger betrachten',
// product management // product management
@ -336,7 +344,7 @@ return array(
'created_product' => 'Produkt erfolgreich erstellt', 'created_product' => 'Produkt erfolgreich erstellt',
'archived_product' => 'Produkt erfolgreich archiviert', 'archived_product' => 'Produkt erfolgreich archiviert',
'product_library' => 'Produktbibliothek', 'product_library' => 'Produktbibliothek',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan', 'pro_plan_custom_fields' => ':link um durch eine Pro-Mitgliedschaft erweiterte Felder zu aktivieren',
'advanced_settings' => 'Erweiterte Einstellungen', 'advanced_settings' => 'Erweiterte Einstellungen',
'pro_plan_advanced_settings' => ':link um durch eine Pro-Mitgliedschaft erweiterte Einstellungen zu aktivieren', 'pro_plan_advanced_settings' => ':link um durch eine Pro-Mitgliedschaft erweiterte Einstellungen zu aktivieren',
@ -346,7 +354,7 @@ return array(
'chart_builder' => 'Diagrammersteller', 'chart_builder' => 'Diagrammersteller',
'ninja_email_footer' => 'Nutze :site um Kunden Rechnungen zu stellen und online bezahlt zu werden, kostenlos!', 'ninja_email_footer' => 'Nutze :site um Kunden Rechnungen zu stellen und online bezahlt zu werden, kostenlos!',
'go_pro' => 'Werde Pro-Mitglied', 'go_pro' => 'Go Pro',
// Quotes // Quotes
'quote' => 'Angebot', 'quote' => 'Angebot',
@ -394,19 +402,19 @@ return array(
'invoice_fields' => 'Rechnungsfelder', 'invoice_fields' => 'Rechnungsfelder',
'invoice_options' => 'Rechnungsoptionen', 'invoice_options' => 'Rechnungsoptionen',
'hide_quantity' => 'Anzahl verbergen', 'hide_quantity' => 'Menge verbergen',
'hide_quantity_help' => 'Wenn deine Menge immer 1 beträgt, kannst du deine Rechnung einfach halten, indem du dieses Feld entfernst.', 'hide_quantity_help' => 'Wenn deine Menge immer 1 beträgt, kannst du deine Rechnung einfach halten, indem du dieses Feld entfernst.',
'hide_paid_to_date' => 'Bereits gezahlt ausblenden', 'hide_paid_to_date' => '"Bereits gezahlt" ausblenden',
'hide_paid_to_date_help' => 'Bereits gezahlt nur anzeigen, wenn eine Zahlung eingegangen ist.', 'hide_paid_to_date_help' => '"Bereits gezahlt" nur anzeigen, wenn eine Zahlung eingegangen ist.',
'charge_taxes' => 'Steuern erheben', 'charge_taxes' => 'Steuern erheben',
'user_management' => 'Benutzerverwaltung', 'user_management' => 'Benutzerverwaltung',
'add_user' => 'Add User', 'add_user' => 'Benutzer hinzufügen',
'send_invite' => 'Einladung senden', 'send_invite' => 'Einladung senden',
'sent_invite' => 'Einladung erfolgreich gesendet', 'sent_invite' => 'Einladung erfolgreich gesendet',
'updated_user' => 'Benutzer erfolgreich aktualisiert', 'updated_user' => 'Benutzer erfolgreich aktualisiert',
'invitation_message' => 'Du wurdest von :invitor eingeladen.', 'invitation_message' => 'Du wurdest von :invitor eingeladen.',
'register_to_add_user' => 'Bitte registrieren um einen Benutzer hinzuzufügen', 'register_to_add_user' => 'Bitte registrieren, um einen Benutzer hinzuzufügen',
'user_state' => 'Status', 'user_state' => 'Status',
'edit_user' => 'Benutzer bearbeiten', 'edit_user' => 'Benutzer bearbeiten',
'delete_user' => 'Benutzer löschen', 'delete_user' => 'Benutzer löschen',
@ -435,7 +443,7 @@ return array(
'quote_number_counter' => 'Zähler für Angebotsnummer', 'quote_number_counter' => 'Zähler für Angebotsnummer',
'share_invoice_counter' => 'Zähler der Rechnung teilen', 'share_invoice_counter' => 'Zähler der Rechnung teilen',
'invoice_issued_to' => 'Rechnung ausgestellt für', 'invoice_issued_to' => 'Rechnung ausgestellt für',
'invalid_counter' => 'Bitte setze, um Probleme zu vermeiden, entweder ein Rechnungs-oder Angebotspräfix.', 'invalid_counter' => 'Bitte setze, um Probleme zu vermeiden, entweder ein Rechnungs- oder Angebotspräfix.',
'mark_sent' => 'Als gesendet markieren', 'mark_sent' => 'Als gesendet markieren',
'gateway_help_1' => ':link um sich bei Authorize.net anzumelden.', 'gateway_help_1' => ':link um sich bei Authorize.net anzumelden.',
@ -452,15 +460,15 @@ return array(
'more_designs_self_host_text' => '', 'more_designs_self_host_text' => '',
'buy' => 'Kaufen', 'buy' => 'Kaufen',
'bought_designs' => 'Die zusätzliche Rechnungsvorlagen wurden erfolgreich hinzugefügt', 'bought_designs' => 'Die zusätzliche Rechnungsvorlagen wurden erfolgreich hinzugefügt',
'sent' => 'gesendet', 'sent' => 'gesendet',
'timesheets' => 'Timesheets',
'vat_number' => 'USt-IdNr.',
'timesheets' => 'Zeittabellen',
'payment_title' => 'Geben Sie Ihre Rechnungsadresse und Ihre Kreditkarteninformationen ein', 'payment_title' => 'Geben Sie Ihre Rechnungsadresse und Ihre Kreditkarteninformationen ein',
'payment_cvv' => '*Dies ist die 3-4-stellige Nummer auf der Rückseite Ihrer Kreditkarte', 'payment_cvv' => '*Dies ist die 3-4-stellige Nummer auf der Rückseite Ihrer Kreditkarte',
'payment_footer1' => '*Die Rechnungsadresse muss mit der Adresse der Kreditkarte übereinstimmen.', 'payment_footer1' => '*Die Rechnungsadresse muss mit der Adresse der Kreditkarte übereinstimmen.',
'payment_footer2' => '*Bitte drücken Sie nur einmal auf "Jetzt bezahlen" - die Verarbeitung der Transaktion kann bis zu einer Minute dauern.', 'payment_footer2' => '*Bitte drücken Sie nur einmal auf "Jetzt bezahlen" - die Verarbeitung der Transaktion kann bis zu einer Minute dauern.',
'vat_number' => 'USt-IdNr.',
'id_number' => 'ID-Nummer', 'id_number' => 'ID-Nummer',
'white_label_link' => 'Branding entfernen', 'white_label_link' => 'Branding entfernen',
@ -506,7 +514,7 @@ return array(
'approve' => 'Zustimmen', 'approve' => 'Zustimmen',
'token_billing_type_id' => 'Token Billing', 'token_billing_type_id' => 'Token Billing',
'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', 'token_billing_help' => 'Ermöglicht Ihnen, Kreditkarten mit Ihrem Gateway zu speichern und diese zu einem späteren Zeitpunkt zu belasten.',
'token_billing_1' => 'Deaktiviert', 'token_billing_1' => 'Deaktiviert',
'token_billing_2' => 'Opt-in - Kontrollkästchen wird angezeigt ist aber nicht ausgewählt', 'token_billing_2' => 'Opt-in - Kontrollkästchen wird angezeigt ist aber nicht ausgewählt',
'token_billing_3' => 'Opt-out - Kontrollkästchen wird angezeigt und ist ausgewählt', 'token_billing_3' => 'Opt-out - Kontrollkästchen wird angezeigt und ist ausgewählt',
@ -519,8 +527,8 @@ return array(
'token_billing_secure' => 'Die Daten werden sicher von :stripe_link gespeichert.', 'token_billing_secure' => 'Die Daten werden sicher von :stripe_link gespeichert.',
'support' => 'Support', 'support' => 'Support',
'contact_information' => 'Kontakt Informationen', 'contact_information' => 'Kontakt-Informationen',
'256_encryption' => '256-Bit Verschlüsselung', '256_encryption' => '256-Bit-Verschlüsselung',
'amount_due' => 'Fälliger Betrag', 'amount_due' => 'Fälliger Betrag',
'billing_address' => 'Rechnungsadresse', 'billing_address' => 'Rechnungsadresse',
'billing_method' => 'Abrechnungsmethode', 'billing_method' => 'Abrechnungsmethode',
@ -528,9 +536,9 @@ return array(
'match_address' => '*Die Rechnungsadresse muss mit der Adresse der Kreditkarte übereinstimmen.', 'match_address' => '*Die Rechnungsadresse muss mit der Adresse der Kreditkarte übereinstimmen.',
'click_once' => '*Bitte drücken Sie nur einmal auf "Jetzt bezahlen" - die Verarbeitung der Transaktion kann bis zu einer Minute dauern.', 'click_once' => '*Bitte drücken Sie nur einmal auf "Jetzt bezahlen" - die Verarbeitung der Transaktion kann bis zu einer Minute dauern.',
'default_invoice_footer' => 'Standard Fußzeile festlegen', 'default_invoice_footer' => 'Standard-Fußzeile festlegen',
'invoice_footer' => 'Fußzeile', 'invoice_footer' => 'Fußzeile',
'save_as_default_footer' => 'Als Standard Fußzeile speichern', 'save_as_default_footer' => 'Als Standard-Fußzeile speichern',
'token_management' => 'Token Verwaltung', 'token_management' => 'Token Verwaltung',
'tokens' => 'Token', 'tokens' => 'Token',
@ -567,7 +575,7 @@ return array(
'forgot_password' => 'Passwort vergessen?', 'forgot_password' => 'Passwort vergessen?',
'email_address' => 'E-Mail-Adresse', 'email_address' => 'E-Mail-Adresse',
'lets_go' => "Auf geht's", 'lets_go' => "Auf geht's",
'password_recovery' => 'Passwort Wiederherstellung', 'password_recovery' => 'Passwort-Wiederherstellung',
'send_email' => 'E-Mail verschicken', 'send_email' => 'E-Mail verschicken',
'set_password' => 'Passwort festlegen', 'set_password' => 'Passwort festlegen',
'converted' => 'Umgewandelt', 'converted' => 'Umgewandelt',
@ -578,25 +586,24 @@ return array(
'resend_confirmation' => 'Bestätigungsmail erneut senden', 'resend_confirmation' => 'Bestätigungsmail erneut senden',
'confirmation_resent' => 'Bestätigungsemail wurde erneut gesendet', 'confirmation_resent' => 'Bestätigungsemail wurde erneut gesendet',
'gateway_help_42' => ':link to sign up for BitPay.<br/>Note: use a Legacy API Key, not an API token.', 'gateway_help_42' => ':link zum Registrieren auf BitPay.<br/>Hinweis: benutze einen Legacy API Key, keinen API token.',
'payment_type_credit_card' => 'Kreditkarte', 'payment_type_credit_card' => 'Kreditkarte',
'payment_type_paypal' => 'PayPal', 'payment_type_paypal' => 'PayPal',
'payment_type_bitcoin' => 'Bitcoin', 'payment_type_bitcoin' => 'Bitcoin',
'knowledge_base' => 'Wissensdatenbank', 'knowledge_base' => 'FAQ',
'partial' => 'Parziell', 'partial' => 'Partiell',
'partial_remaining' => ':partial von :balance', 'partial_remaining' => ':partial von :balance',
'more_fields' => 'Weitere Felder', 'more_fields' => 'Weitere Felder',
'less_fields' => 'Weniger Felder', 'less_fields' => 'Weniger Felder',
'client_name' => 'Kundenname', 'client_name' => 'Kundenname',
'pdf_settings' => 'PDF Einstellungen', 'pdf_settings' => 'PDF Einstellungen',
'utf8_invoices' => 'Cyrillic Unterstützung <sup>Beta</sup>',
'product_settings' => 'Produkt Einstellungen', 'product_settings' => 'Produkt Einstellungen',
'auto_wrap' => 'Automatischer Zeilenumbruch', 'auto_wrap' => 'Automatischer Zeilenumbruch',
'duplicate_post' => 'Achtung: Die vorherige Seite wurde zweimal abgeschickt. Das zweite Abschicken wurde ignoriert.', 'duplicate_post' => 'Achtung: Die vorherige Seite wurde zweimal abgeschickt. Das zweite Abschicken wurde ignoriert.',
'view_documentation' => 'Dokumentation anzeigen', 'view_documentation' => 'Dokumentation anzeigen',
'app_title' => 'Kostenlose Online Open-Source Rechnungsausstellung', 'app_title' => 'Kostenlose Online Open-Source Rechnungsausstellung',
'app_description' => 'Invoice Ninja is a free, open-source solution for invoicing and billing customers. With Invoice Ninja, you can easily build and send beautiful invoices from any device that has access to the web. Your clients can print your invoices, download them as pdf files, and even pay you online from within the system.', 'app_description' => 'InvoiceNinja ist eine kostenlose, quelloffene Lösung für die Rechnungsstellung und Abrechnung von Kunden. Mit Invoice Ninja kannst du einfach schöne Rechnungen erstellen und verschicken, von jedem Gerät mit Internetzugang. Deine Kunden können die Rechnungen drucken, als PDF Datei herunterladen und sogar online im System bezahlen.',
'rows' => 'Zeilen', 'rows' => 'Zeilen',
'www' => 'www', 'www' => 'www',
@ -642,7 +649,7 @@ return array(
'minutes' => 'Minuten', 'minutes' => 'Minuten',
'hour' => 'Stunde', 'hour' => 'Stunde',
'hours' => 'Stunden', 'hours' => 'Stunden',
'task_details' => 'Aufgaben Details', 'task_details' => 'Aufgaben-Details',
'duration' => 'Dauer', 'duration' => 'Dauer',
'end_time' => 'Endzeit', 'end_time' => 'Endzeit',
'end' => 'Ende', 'end' => 'Ende',
@ -665,16 +672,16 @@ return array(
'counter' => 'Zähler', 'counter' => 'Zähler',
'payment_type_dwolla' => 'Dwolla', 'payment_type_dwolla' => 'Dwolla',
'gateway_help_43' => ':link to sign up for Dwolla.', 'gateway_help_43' => ':link zum Registrieren auf Dwolla.',
'partial_value' => 'Must be greater than zero and less than the total', 'partial_value' => 'Muss größer als Null und kleiner als der Gesamtbetrag sein',
'more_actions' => 'More Actions', 'more_actions' => 'Weitere Aktionen',
'pro_plan_title' => 'NINJA PRO', 'pro_plan_title' => 'NINJA PRO',
'pro_plan_call_to_action' => 'Jetzt Upgraden!', 'pro_plan_call_to_action' => 'Jetzt Upgraden!',
'pro_plan_feature1' => 'Unlimitierte Anzahl Kunden erstellen', 'pro_plan_feature1' => 'Unlimitierte Anzahl Kunden erstellen',
'pro_plan_feature2' => 'Zugriff ui 10 schönen Rechnungsdesigns', 'pro_plan_feature2' => 'Zugriff auf 10 schöne Rechnungsdesigns',
'pro_plan_feature3' => 'Benutzerdefinierte URLs - "DeineFirma.InvoiceNinja.com"', 'pro_plan_feature3' => 'Benutzerdefinierte URLs - "DeineFirma.InvoiceNinja.com"',
'pro_plan_feature4' => '"Created by Invoice Ninja" entfernen', 'pro_plan_feature4' => '"Erstellt durch Invoice Ninja" entfernen',
'pro_plan_feature5' => 'Multi-Benutzer Zugriff & Aktivitätstracking', 'pro_plan_feature5' => 'Multi-Benutzer Zugriff & Aktivitätstracking',
'pro_plan_feature6' => 'Angebote & pro-forma Rechnungen erstellen', 'pro_plan_feature6' => 'Angebote & pro-forma Rechnungen erstellen',
'pro_plan_feature7' => 'Rechungstitelfelder und Nummerierung anpassen', 'pro_plan_feature7' => 'Rechungstitelfelder und Nummerierung anpassen',
@ -689,13 +696,63 @@ return array(
'email_receipt' => 'Zahlungsbestätigung an Kunden per E-Mail senden', 'email_receipt' => 'Zahlungsbestätigung an Kunden per E-Mail senden',
'created_payment_emailed_client' => 'Zahlung erfolgreich erstellt und Kunde per E-Mail benachrichtigt', 'created_payment_emailed_client' => 'Zahlung erfolgreich erstellt und Kunde per E-Mail benachrichtigt',
'add_account' => 'Konto hinzufügen', 'add_company' => 'Konto hinzufügen',
'untitled' => 'Unbenannt', 'untitled' => 'Unbenannt',
'new_account' => 'Neues Konto', 'new_company' => 'Neues Konto',
'associated_accounts' => 'Konten erfolgreich verlinkt', 'associated_accounts' => 'Konten erfolgreich verlinkt',
'unlinked_account' => 'Konten erfolgreich getrennt', 'unlinked_account' => 'Konten erfolgreich getrennt',
'login' => 'Login', 'login' => 'Login',
'or' => 'oder', 'or' => 'oder',
'email_error' => 'Es gab ein Problem beim Senden dieses E-Mails.',
'confirm_recurring_timing' => 'Beachten Sie: E-Mails werden zu Beginn der Stunde gesendet.',
'old_browser' => 'Bitte verwenden Sie einen <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">neueren Browser</a>',
'payment_terms_help' => 'Setzt das Standardfälligkeitsdatum',
'unlink_account' => 'Konten Trennen',
'unlink' => 'Trennen',
'show_address' => 'Adresse Anzeigen',
'show_address_help' => 'Verlange von Kunden ihre Rechnungsadresse anzugeben',
'update_address' => 'Adresse Aktualisieren',
'update_address_help' => 'Kundenadresse mit den gemachten Angaben aktualisieren',
'times' => 'Zeiten',
'set_now' => 'Jetzt setzen',
'dark_mode' => 'Dunkler Modus',
'dark_mode_help' => 'Weißer Text auf schwarzem Hintergrund anzeigen',
'add_to_invoice' => 'Zur Rechnung :invoice hinzufügen',
'create_new_invoice' => 'Neue Rechnung erstellen',
'task_errors' => 'Bitte korrigieren Sie alle überlappenden Zeiten',
'from' => 'Von',
'to' => 'An',
'font_size' => 'Schriftgröße',
'primary_color' => 'Primäre Farbe',
'secondary_color' => 'Sekundäre Farbe',
'customize_design' => 'Design Anpassen',
); 'content' => 'Inhalt',
'styles' => 'Stile',
'defaults' => 'Standards',
'margins' => 'Außenabstände',
'header' => 'Kopfzeile',
'footer' => 'Fußzeile',
'custom' => 'Benutzerdefiniert',
'invoice_to' => 'Rechnung an',
'invoice_no' => 'Rechnung Nr.',
'recent_payments' => 'Kürzliche Zahlungen',
'outstanding' => 'Ausstehend',
'manage_companies' => 'Unternehmen verwalten',
'total_revenue' => 'Gesamteinnahmen',
'current_user' => 'Aktueller Benutzer',
'new_recurring_invoice' => 'Neue wiederkehrende Rechnung',
'recurring_invoice' => 'Wiederkehrende Rechnung',
'recurring_too_soon' => 'Es ist zu früh, um die nächste wiederkehrende Rechnung zu erstellen',
'created_by_invoice' => 'Erstellt durch :invoice',
'primary_user' => 'Primärer Benutzer',
'help' => 'Hilfe',
'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p>
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>'
);

View File

@ -40,8 +40,8 @@ return array(
'taxes' => 'Taxes', 'taxes' => 'Taxes',
'tax' => 'Tax', 'tax' => 'Tax',
'item' => 'Item', 'item' => 'Item',
'description' => 'Description', 'description' => 'Description',
'unit_cost' => 'Unit Cost', 'unit_cost' => 'Cost',
'quantity' => 'Quantity', 'quantity' => 'Quantity',
'line_total' => 'Line Total', 'line_total' => 'Line Total',
'subtotal' => 'Subtotal', 'subtotal' => 'Subtotal',
@ -119,8 +119,8 @@ return array(
'active_client' => 'active client', 'active_client' => 'active client',
'active_clients' => 'active clients', 'active_clients' => 'active clients',
'invoices_past_due' => 'Invoices Past Due', 'invoices_past_due' => 'Invoices Past Due',
'upcoming_invoices' => 'Upcoming invoices', 'upcoming_invoices' => 'Upcoming Invoices',
'average_invoice' => 'Average invoice', 'average_invoice' => 'Average Invoice',
// list pages // list pages
'archive' => 'Archive', 'archive' => 'Archive',
@ -276,9 +276,9 @@ return array(
// Payment page // Payment page
'secure_payment' => 'Secure Payment', 'secure_payment' => 'Secure Payment',
'card_number' => 'Card number', 'card_number' => 'Card Number',
'expiration_month' => 'Expiration month', 'expiration_month' => 'Expiration Month',
'expiration_year' => 'Expiration year', 'expiration_year' => 'Expiration Year',
'cvv' => 'CVV', 'cvv' => 'CVV',
// Security alerts // Security alerts
@ -401,9 +401,9 @@ return array(
'invoice_fields' => 'Invoice Fields', 'invoice_fields' => 'Invoice Fields',
'invoice_options' => 'Invoice Options', 'invoice_options' => 'Invoice Options',
'hide_quantity' => 'Hide quantity', 'hide_quantity' => 'Hide Quantity',
'hide_quantity_help' => 'If your line items quantities are always 1, then you can declutter invoices by no longer displaying this field.', 'hide_quantity_help' => 'If your line items quantities are always 1, then you can declutter invoices by no longer displaying this field.',
'hide_paid_to_date' => 'Hide paid to date', 'hide_paid_to_date' => 'Hide Paid to Date',
'hide_paid_to_date_help' => 'Only display the "Paid to Date" area on your invoices once a payment has been received.', 'hide_paid_to_date_help' => 'Only display the "Paid to Date" area on your invoices once a payment has been received.',
'charge_taxes' => 'Charge taxes', 'charge_taxes' => 'Charge taxes',
@ -526,11 +526,11 @@ return array(
'token_billing_secure' => 'The data is stored securely by :stripe_link', 'token_billing_secure' => 'The data is stored securely by :stripe_link',
'support' => 'Support', 'support' => 'Support',
'contact_information' => 'Contact information', 'contact_information' => 'Contact Information',
'256_encryption' => '256-Bit Encryption', '256_encryption' => '256-Bit Encryption',
'amount_due' => 'Amount due', 'amount_due' => 'Amount due',
'billing_address' => 'Billing address', 'billing_address' => 'Billing Address',
'billing_method' => 'Billing method', 'billing_method' => 'Billing Method',
'order_overview' => 'Order overview', 'order_overview' => 'Order overview',
'match_address' => '*Address must match address associated with credit card.', 'match_address' => '*Address must match address associated with credit card.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.', 'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
@ -573,7 +573,8 @@ return array(
'recover_password' => 'Recover your password', 'recover_password' => 'Recover your password',
'forgot_password' => 'Forgot your password?', 'forgot_password' => 'Forgot your password?',
'email_address' => 'Email address', 'email_address' => 'Email address',
'lets_go' => 'Lets go', 'lets_go' => 'Let\'s go',
//'lets_go' => 'Login',
'password_recovery' => 'Password Recovery', 'password_recovery' => 'Password Recovery',
'send_email' => 'Send email', 'send_email' => 'Send email',
'set_password' => 'Set Password', 'set_password' => 'Set Password',
@ -597,7 +598,6 @@ return array(
'less_fields' => 'Less Fields', 'less_fields' => 'Less Fields',
'client_name' => 'Client Name', 'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings', 'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings', 'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap', 'auto_wrap' => 'Auto Line Wrap',
'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.', 'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.',
@ -696,20 +696,64 @@ return array(
'email_receipt' => 'Email payment receipt to the client', 'email_receipt' => 'Email payment receipt to the client',
'created_payment_emailed_client' => 'Successfully created payment and emailed client', 'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account', 'add_company' => 'Add Company',
'untitled' => 'Untitled', 'untitled' => 'Untitled',
'new_account' => 'New Account', 'new_company' => 'New Company',
'associated_accounts' => 'Successfully linked accounts', 'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts', 'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login', 'login' => 'Login',
'or' => 'or', 'or' => 'or',
'email_error' => 'There was a problem sending the email', 'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.', 'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>', 'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date', 'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account', 'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink', 'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
'from' => 'From',
'to' => 'To',
'font_size' => 'Font Size',
'primary_color' => 'Primary Color',
'secondary_color' => 'Secondary Color',
'customize_design' => 'Customize Design',
'content' => 'Content',
'styles' => 'Styles',
'defaults' => 'Defaults',
'margins' => 'Margins',
'header' => 'Header',
'footer' => 'Footer',
'custom' => 'Custom',
'invoice_to' => 'Invoice to',
'invoice_no' => 'Invoice No.',
'recent_payments' => 'Recent Payments',
'outstanding' => 'Outstanding',
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
'primary_user' => 'Primary User',
'help' => 'Help',
'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p>
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>'
); );

View File

@ -417,264 +417,321 @@ return array(
'invalid_counter' => 'Para evitar posibles conflictos, por favor crea un prefijo de facturación y de cotización.', 'invalid_counter' => 'Para evitar posibles conflictos, por favor crea un prefijo de facturación y de cotización.',
'mark_sent' => 'Marcar como enviado', 'mark_sent' => 'Marcar como enviado',
'gateway_help_1' => ':link to sign up for Authorize.net.', 'gateway_help_1' => ':link para registrarse con Authorize.net.',
'gateway_help_2' => ':link to sign up for Authorize.net.', 'gateway_help_2' => ':link para registrarse con Authorize.net.',
'gateway_help_17' => ':link to get your PayPal API signature.', 'gateway_help_17' => ':link para obtener su firma del API de PayPal.',
'gateway_help_23' => 'Note: use your secret API key, not your publishable API key.', 'gateway_help_23' => 'Nota: use use llave secreta del API, no la llave pública.',
'gateway_help_27' => ':link to sign up for TwoCheckout.', 'gateway_help_27' => ':link para registrarse con TwoCheckout.',
'more_designs' => 'More designs', 'more_designs' => 'Más diseños',
'more_designs_title' => 'Additional Invoice Designs', 'more_designs_title' => 'Diseños Adicionales de Facturas',
'more_designs_cloud_header' => 'Go Pro for more invoice designs', 'more_designs_cloud_header' => 'Vete Pro para más diseños de facturas',
'more_designs_cloud_text' => '', 'more_designs_cloud_text' => '',
'more_designs_self_host_header' => 'Get 6 more invoice designs for just $'.INVOICE_DESIGNS_PRICE, 'more_designs_self_host_header' => 'Adquiera 6 diseños adicionales de facturas por solo $'.INVOICE_DESIGNS_PRICE,
'more_designs_self_host_text' => '', 'more_designs_self_host_text' => '',
'buy' => 'Buy', 'buy' => 'Comprar',
'bought_designs' => 'Successfully added additional invoice designs', 'bought_designs' => 'Diseños adicionales de facturas agregados con éxito',
'sent' => 'sent', 'sent' => 'enviado',
'timesheets' => 'Timesheets', 'timesheets' => 'Hohas de Tiempo',
'payment_title' => 'Enter Your Billing Address and Credit Card information', 'payment_title' => 'Ingrese la Dirección de Facturación de su Tareta de Crédito',
'payment_cvv' => '*This is the 3-4 digit number onthe back of your card', 'payment_cvv' => '*Este es el número de 3-4 dígitos en la parte posterior de su tarjeta de crédito',
'payment_footer1' => '*Billing address must match address associated with credit card.', 'payment_footer1' => '*La dirección debe coincidir con la dirección asociada a la tarjeta de crédito.',
'payment_footer2' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.', 'payment_footer2' => '*Por favor haga clic en "PAGAR AHORA" sólo una vez - la transacción puede demorarse hasta un minuto en ser procesada.',
'vat_number' => 'Vat Number', 'vat_number' => 'Número de Impuesto',
'id_number' => 'ID Number', 'id_number' => 'ID Number',
'white_label_link' => 'White label', 'white_label_link' => 'Etiqueta Blanca',
'white_label_text' => 'Purchase a white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the top of the client pages.', 'white_label_text' => 'Adquiera una licencia de etiqueta blanca por $'.WHITE_LABEL_PRICE.' para eliminar la marca de Invoice Ninja branding de la parte superior de las páginas de los clientes.',
'white_label_header' => 'White Label', 'white_label_header' => 'Etiqueta Blanca',
'bought_white_label' => 'Successfully enabled white label license', 'bought_white_label' => 'Licencia de etiqueta blanca habilitada con éxito',
'white_labeled' => 'White labeled', 'white_labeled' => 'Etiqueta Blanca',
'restore' => 'Restore', 'restore' => 'Restaurar',
'restore_invoice' => 'Restore Invoice', 'restore_invoice' => 'Restaurar Invoice',
'restore_quote' => 'Restore Quote', 'restore_quote' => 'Restaurar Quote',
'restore_client' => 'Restore Client', 'restore_client' => 'Restaurar Client',
'restore_credit' => 'Restore Credit', 'restore_credit' => 'Restaurar Credit',
'restore_payment' => 'Restore Payment', 'restore_payment' => 'Restaurar Payment',
'restored_invoice' => 'Successfully restored invoice', 'restored_invoice' => 'Factura restaurada con éxito',
'restored_quote' => 'Successfully restored quote', 'restored_quote' => 'Cotización restaurada con éxito',
'restored_client' => 'Successfully restored client', 'restored_client' => 'Cliente restaurado con éxito',
'restored_payment' => 'Successfully restored payment', 'restored_payment' => 'Pago restaurado con éxito',
'restored_credit' => 'Successfully restored credit', 'restored_credit' => 'Crédito restaurado con éxito',
'reason_for_canceling' => 'Help us improve our site by telling us why you\'re leaving.', 'reason_for_canceling' => 'Ayúdenos a mejorar contándonos porqué se va.',
'discount_percent' => 'Percent', 'discount_percent' => 'Porcentaje',
'discount_amount' => 'Amount', 'discount_amount' => 'Cantidad',
'invoice_history' => 'Invoice History', 'invoice_history' => 'Facturar Historial',
'quote_history' => 'Quote History', 'quote_history' => 'Cotizar Historial',
'current_version' => 'Current version', 'current_version' => 'Versión actual',
'select_versiony' => 'Select version', 'select_versiony' => 'Seleccionar versión',
'view_history' => 'View History', 'view_history' => 'Ver Historial',
'edit_payment' => 'Edit Payment', 'edit_payment' => 'Editar Pago',
'updated_payment' => 'Successfully updated payment', 'updated_payment' => 'Pago actualizado con éxito',
'deleted' => 'Deleted', 'deleted' => 'Eliminado',
'restore_user' => 'Restore User', 'restore_user' => 'Restaurar Usuario',
'restored_user' => 'Successfully restored user', 'restored_user' => 'Usuario restaurado con éxito',
'show_deleted_users' => 'Show deleted users', 'show_deleted_users' => 'Mostrar usuarios eliminados',
'email_templates' => 'Email Templates', 'email_templates' => 'Plantillas de Correos',
'invoice_email' => 'Invoice Email', 'invoice_email' => 'Correo de Factura',
'payment_email' => 'Payment Email', 'payment_email' => 'Correo de Pago',
'quote_email' => 'Quote Email', 'quote_email' => 'Correo de Cotizacion',
'reset_all' => 'Reset All', 'reset_all' => 'Reiniciar Todos',
'approve' => 'Approve', 'approve' => 'Aprobar',
'token_billing_type_id' => 'Token Billing', 'token_billing_type_id' => 'Token de Facturación',
'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', 'token_billing_help' => 'Permite almacenar tarjetas de crédito con su gateway de pagos, y facturar en una fecha posterior.',
'token_billing_1' => 'Disabled', 'token_billing_1' => 'Deshabilitado',
'token_billing_2' => 'Opt-in - checkbox is shown but not selected', 'token_billing_2' => 'Opt-in - el checkbox es mostrado pero no seleccionado',
'token_billing_3' => 'Opt-out - checkbox is shown and selected', 'token_billing_3' => 'Opt-out - el checkbox es mostrado y seleccionado',
'token_billing_4' => 'Always', 'token_billing_4' => 'Siempre',
'token_billing_checkbox' => 'Store credit card details', 'token_billing_checkbox' => 'Almacenar detalles de la tarjeta de crédito',
'view_in_stripe' => 'View in Stripe', 'view_in_stripe' => 'Ver en Stripe',
'use_card_on_file' => 'Use card on file', 'use_card_on_file' => 'Usar la tarjeta en el archivo',
'edit_payment_details' => 'Edit payment details', 'edit_payment_details' => 'Editar detalles del pago',
'token_billing' => 'Save card details', 'token_billing' => 'Guardar detalles de la tarjeta',
'token_billing_secure' => 'The data is stored securely by :stripe_link', 'token_billing_secure' => 'La información es almacenada de manera segura por :stripe_link',
'support' => 'Support', 'support' => 'Soporte',
'contact_information' => 'Contact information', 'contact_information' => 'Información de Contacto',
'256_encryption' => '256-Bit Encryption', '256_encryption' => 'Encripción de 256-Bit',
'amount_due' => 'Amount due', 'amount_due' => 'Valor por cobrar',
'billing_address' => 'Billing address', 'billing_address' => 'Dirección de facturación',
'billing_method' => 'Billing method', 'billing_method' => 'Método de facturación',
'order_overview' => 'Order overview', 'order_overview' => 'Resumen de la orden',
'match_address' => '*Address must match address associated with credit card.', 'match_address' => '*La dirección debe coincidir con la dirección asociada a la tarjeta de crédito.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.', 'click_once' => '*Por favor haga clic en "PAGAR AHORA" sólo una vez - la transacción puede demorarse hasta un minuto en ser procesada.',
'default_invoice_footer' => 'Set default invoice footer', 'default_invoice_footer' => 'Asignar pié de página por defecto para la factura',
'invoice_footer' => 'Invoice footer', 'invoice_footer' => 'Pié de págia de la factura',
'save_as_default_footer' => 'Save as default footer', 'save_as_default_footer' => 'Guardar como el pié de página por defecto',
'token_management' => 'Token Management', 'token_management' => 'Administración de Tokens',
'tokens' => 'Tokens', 'tokens' => 'Tokens',
'add_token' => 'Add Token', 'add_token' => 'Agregar Token',
'show_deleted_tokens' => 'Show deleted tokens', 'show_deleted_tokens' => 'Mostrar los tokens eliminados',
'deleted_token' => 'Successfully deleted token', 'deleted_token' => 'Token eliminado con éxito',
'created_token' => 'Successfully created token', 'created_token' => 'Token creado con éxito',
'updated_token' => 'Successfully updated token', 'updated_token' => 'Token actualizado con éxito',
'edit_token' => 'Edit Token', 'edit_token' => 'Editar Token',
'delete_token' => 'Delete Token', 'delete_token' => 'Eliminar Token',
'token' => 'Token', 'token' => 'Token',
'add_gateway' => 'Add Gateway', 'add_gateway' => 'Agregar Gateway',
'delete_gateway' => 'Delete Gateway', 'delete_gateway' => 'Eliminar Gateway',
'edit_gateway' => 'Edit Gateway', 'edit_gateway' => 'Editar Gateway',
'updated_gateway' => 'Successfully updated gateway', 'updated_gateway' => 'Gateway actualizado con éxito',
'created_gateway' => 'Successfully created gateway', 'created_gateway' => 'Gateway creado con éxito',
'deleted_gateway' => 'Successfully deleted gateway', 'deleted_gateway' => 'Gateway eliminado con éxito',
'pay_with_paypal' => 'PayPal', 'pay_with_paypal' => 'PayPal',
'pay_with_card' => 'Credit card', 'pay_with_card' => 'Tarjeta de Crédito',
'change_password' => 'Change password', 'change_password' => 'Cambiar contraseña',
'current_password' => 'Current password', 'current_password' => 'contraseña actual',
'new_password' => 'New password', 'new_password' => 'Nueva contraseña',
'confirm_password' => 'Confirm password', 'confirm_password' => 'Confirmar contraseña',
'password_error_incorrect' => 'The current password is incorrect.', 'password_error_incorrect' => 'La contraseña actual es incorrecta.',
'password_error_invalid' => 'The new password is invalid.', 'password_error_invalid' => 'La nueva contraseña es inválida.',
'updated_password' => 'Successfully updated password', 'updated_password' => 'Contraseñaactualizada con éxito',
'api_tokens' => 'API Tokens', 'api_tokens' => 'API Tokens',
'users_and_tokens' => 'Users & Tokens', 'users_and_tokens' => 'Usuarios y Tokens',
'account_login' => 'Account Login', 'account_login' => 'Ingreso',
'recover_password' => 'Recover your password', 'recover_password' => 'Recupere su contraseña',
'forgot_password' => 'Forgot your password?', 'forgot_password' => 'Olvidó su Contraseña?',
'email_address' => 'Email address', 'email_address' => 'Correo electrónico',
'lets_go' => 'Lets go', 'lets_go' => 'Vamos',
'password_recovery' => 'Password Recovery', 'password_recovery' => 'Recuperación de Contraseña',
'send_email' => 'Send email', 'send_email' => 'Enviar correo',
'set_password' => 'Set Password', 'set_password' => 'Asignar Contraseña',
'converted' => 'Converted', 'converted' => 'Convertido',
'email_approved' => 'Email me when a quote is <b>approved</b>', 'email_approved' => 'Enviarme un correo cuando una cotización sea <b>aprobada</b>',
'notification_quote_approved_subject' => 'Quote :invoice was approved by :client', 'notification_quote_approved_subject' => 'Cotización :invoice fue aprobada por :client',
'notification_quote_approved' => 'The following client :client approved Quote :invoice for :amount.', 'notification_quote_approved' => 'El cliente :client ha aprobado la cotización :invoice por el valor :amount.',
'resend_confirmation' => 'Resend confirmation email', 'resend_confirmation' => 'Reenviar correo de confirmación',
'confirmation_resent' => 'The confirmation email was resent', 'confirmation_resent' => 'El correo de confirmación fue reenviado',
'gateway_help_42' => ':link to sign up for BitPay.<br/>Note: use a Legacy API Key, not an API token.', 'gateway_help_42' => ':link para registrarse en BitPay.<br/>Nota: use una llave del API legacy, no un token API.',
'payment_type_credit_card' => 'Credit card', 'payment_type_credit_card' => 'Tarjeta de Crédito',
'payment_type_paypal' => 'PayPal', 'payment_type_paypal' => 'PayPal',
'payment_type_bitcoin' => 'Bitcoin', 'payment_type_bitcoin' => 'Bitcoin',
'knowledge_base' => 'Knowledge Base', 'knowledge_base' => 'Base de Conocimiento',
'partial' => 'Partial', 'partial' => 'Parcial',
'partial_remaining' => ':partial of :balance', 'partial_remaining' => ':partial de :balance',
'more_fields' => 'More Fields', 'more_fields' => ' Más Campos',
'less_fields' => 'Less Fields', 'less_fields' => 'Menos Campos',
'client_name' => 'Client Name', 'client_name' => 'Nombre del Cliente',
'pdf_settings' => 'PDF Settings', 'pdf_settings' => 'Configuración de PDF',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>', 'product_settings' => 'Configuración del Producto',
'product_settings' => 'Product Settings', 'auto_wrap' => 'Ajuste Automático de Linea',
'auto_wrap' => 'Auto Line Wrap', 'duplicate_post' => 'Advertencia: la página anterior fue enviada dos veces. El segundo envío ha sido ignorado.',
'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.', 'view_documentation' => 'Ver Documentación',
'view_documentation' => 'View Documentation', 'app_title' => 'Facturación Open-Source Gratuita',
'app_title' => 'Free Open-Source Online Invoicing', 'app_description' => 'Invoice Ninja es una solución open-source gratuita para manejar la facturación de sus clientes. Con Invoice Ninja, se pueden crear y enviar hermosas facturas desde cualquier dispositivo que tenga acceso a Internet. Sus clientes pueden imprimir sus facturas, descargarlas en formato PDF o inclusive pagarlas en linea desde esta misma plataforma',
'app_description' => 'Invoice Ninja is a free, open-source solution for invoicing and billing customers. With Invoice Ninja, you can easily build and send beautiful invoices from any device that has access to the web. Your clients can print your invoices, download them as pdf files, and even pay you online from within the system.',
'rows' => 'rows', 'rows' => 'filas',
'www' => 'www', 'www' => 'www',
'logo' => 'Logo', 'logo' => 'Logo',
'subdomain' => 'Subdomain', 'subdomain' => 'Subdominio',
'provide_name_or_email' => 'Please provide a contact name or email', 'provide_name_or_email' => 'Por favor provea un nombre o correo electrónico de contacto',
'charts_and_reports' => 'Charts & Reports', 'charts_and_reports' => 'Gráficas y Reportes',
'chart' => 'Chart', 'chart' => 'Gráfica',
'report' => 'Report', 'report' => 'Reporte',
'group_by' => 'Group by', 'group_by' => 'Agrupar por',
'paid' => 'Paid', 'paid' => 'Pagado',
'enable_report' => 'Report', 'enable_report' => 'Reportes',
'enable_chart' => 'Chart', 'enable_chart' => 'Gráficas',
'totals' => 'Totals', 'totals' => 'Totales',
'run' => 'Run', 'run' => 'Ejecutar',
'export' => 'Export', 'export' => 'Exportar',
'documentation' => 'Documentation', 'documentation' => 'Documentación',
'zapier' => 'Zapier <sup>Beta</sup>', 'zapier' => 'Zapier <sup>Beta</sup>',
'recurring' => 'Recurring', 'recurring' => 'Recurrente',
'last_invoice_sent' => 'Last invoice sent :date', 'last_invoice_sent' => 'Ultima factura enviada en :date',
'processed_updates' => 'Successfully completed update', 'processed_updates' => 'Actualización completada con éxito',
'tasks' => 'Tasks', 'tasks' => 'Tareas',
'new_task' => 'New Task', 'new_task' => 'Nueva Tarea',
'start_time' => 'Start Time', 'start_time' => 'Tiempo de Inicio',
'created_task' => 'Successfully created task', 'created_task' => 'Tarea creada con éxito',
'updated_task' => 'Successfully updated task', 'updated_task' => 'Tarea actualizada con éxito',
'edit_task' => 'Edit Task', 'edit_task' => 'Editar Tarea',
'archive_task' => 'Archive Task', 'archive_task' => 'Archivar Tarea',
'restore_task' => 'Restore Task', 'restore_task' => 'Restaurar Tarea',
'delete_task' => 'Delete Task', 'delete_task' => 'Eliminar Tarea',
'stop_task' => 'Stop Task', 'stop_task' => 'Detener Tarea',
'time' => 'Time', 'time' => 'Tiempo',
'start' => 'Start', 'start' => 'Iniciar',
'stop' => 'Stop', 'stop' => 'Detener',
'now' => 'Now', 'now' => 'Ahora',
'timer' => 'Timer', 'timer' => 'Timer',
'manual' => 'Manual', 'manual' => 'Manual',
'date_and_time' => 'Date & Time', 'date_and_time' => 'Fecha y Hora',
'second' => 'second', 'second' => 'segundo',
'seconds' => 'seconds', 'seconds' => 'segundos',
'minute' => 'minute', 'minute' => 'minuto',
'minutes' => 'minutes', 'minutes' => 'minutos',
'hour' => 'hour', 'hour' => 'hora',
'hours' => 'hours', 'hours' => 'horas',
'task_details' => 'Task Details', 'task_details' => 'Detalles de la Tarea',
'duration' => 'Duration', 'duration' => 'Duración',
'end_time' => 'End Time', 'end_time' => 'Tiempo Final',
'end' => 'End', 'end' => 'Fin',
'invoiced' => 'Invoiced', 'invoiced' => 'Facturado',
'logged' => 'Logged', 'logged' => 'Registrado',
'running' => 'Running', 'running' => 'Corriendo',
'task_error_multiple_clients' => 'The tasks can\'t belong to different clients', 'task_error_multiple_clients' => 'Las tareas no pueden pertenecer a diferentes clientes',
'task_error_running' => 'Please stop running tasks first', 'task_error_running' => 'Por favor primero detenga las tareas que se estén ejecutando',
'task_error_invoiced' => 'Tasks have already been invoiced', 'task_error_invoiced' => 'Las tareas ya han sido facturadas',
'restored_task' => 'Successfully restored task', 'restored_task' => 'Tarea restaurada con éxito',
'archived_task' => 'Successfully archived task', 'archived_task' => 'Tarea archivada con éxito',
'archived_tasks' => 'Successfully archived :count tasks', 'archived_tasks' => ':count tareas archivadas con éxito',
'deleted_task' => 'Successfully deleted task', 'deleted_task' => 'Tarea eliminada con éxito',
'deleted_tasks' => 'Successfully deleted :count tasks', 'deleted_tasks' => ':count tareas eliminadas con éxito',
'create_task' => 'Create Task', 'create_task' => 'Crear Tarea',
'stopped_task' => 'Successfully stopped task', 'stopped_task' => 'Tarea detenida con éxito',
'invoice_task' => 'Invoice Task', 'invoice_task' => 'Tarea de Factura',
'invoice_labels' => 'Invoice Labels', 'invoice_labels' => 'Etiquetas',
'prefix' => 'Prefix', 'prefix' => 'Prefijo',
'counter' => 'Counter', 'counter' => 'Contador',
'payment_type_dwolla' => 'Dwolla', 'payment_type_dwolla' => 'Dwolla',
'gateway_help_43' => ':link to sign up for Dwolla.', 'gateway_help_43' => ':link para registrarse con Dwolla.',
'partial_value' => 'Must be greater than zero and less than the total', 'partial_value' => 'Debe ser mayor que cero y menor que el total',
'more_actions' => 'More Actions', 'more_actions' => 'Más Acciones',
'pro_plan_title' => 'NINJA PRO', 'pro_plan_title' => 'NINJA PRO',
'pro_plan_call_to_action' => 'Upgrade Now!', 'pro_plan_call_to_action' => 'Actualícese Ahora!',
'pro_plan_feature1' => 'Create Unlimited Clients', 'pro_plan_feature1' => 'Cree Clientes Ilimitados',
'pro_plan_feature2' => 'Access to 10 Beautiful Invoice Designs', 'pro_plan_feature2' => 'Acceda a 10 hermosos diseños de factura',
'pro_plan_feature3' => 'Custom URLs - "YourBrand.InvoiceNinja.com"', 'pro_plan_feature3' => 'Custom URLs - "SuMarca.InvoiceNinja.com"',
'pro_plan_feature4' => 'Remove "Created by Invoice Ninja"', 'pro_plan_feature4' => 'Remove "Creado por Invoice Ninja"',
'pro_plan_feature5' => 'Multi-user Access & Activity Tracking', 'pro_plan_feature5' => 'Acceso Multi-usuario y seguimiento de actividades',
'pro_plan_feature6' => 'Create Quotes & Pro-forma Invoices', 'pro_plan_feature6' => 'Cree Cotizaciones y facturas Pro-forma',
'pro_plan_feature7' => 'Customize Invoice Field Titles & Numbering', 'pro_plan_feature7' => 'Personalice los Títulos de los Campos y Numeración de las Cuentas de Cobro',
'pro_plan_feature8' => 'Option to Attach PDFs to Client Emails', 'pro_plan_feature8' => 'Opción para adjuntarle documentos PDF a los correos dirigidos a los clientes',
'resume' => 'Resume', 'resume' => 'Continuar',
'break_duration' => 'Break', 'break_duration' => 'Descanso',
'edit_details' => 'Edit Details', 'edit_details' => 'Editar Detalles',
'work' => 'Work', 'work' => 'Trabajo',
'timezone_unset' => 'Please :link to set your timezone', 'timezone_unset' => 'Por favor :link para configurar su Uso Horario',
'click_here' => 'click here', 'click_here' => 'haga clic aquí',
'resume' => 'Continuar',
'break_duration' => 'Descanso',
'edit_details' => 'Editar Detalles',
'work' => 'Tranajo',
'timezone_unset' => 'Por favor :link para configurar su Uso Horario',
'click_here' => 'haga clic aquí',
'email_receipt' => 'Enviar por correo electrónico el recibo de pago al cliente',
'created_payment_emailed_client' => 'Pago creado y enviado al cliente con éxito',
'add_company' => 'Agregar Compañía',
'untitled' => 'Sin Título',
'new_company' => 'Nueva Compañia',
'associated_accounts' => 'Cuentas conectadas con éxito',
'unlinked_account' => 'Cuentas desconectadas con éxito',
'login' => 'Ingresar',
'or' => 'o',
'email_error' => 'Hubo un problema enviando el correo',
'confirm_recurring_timing' => 'Nota: los correos son enviados al inicio de la hora.',
'old_browser' => 'Por favor utilice <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">más reciente</a>',
'payment_terms_help' => 'Asigna la fecha de vencimiento por defecto de la factura',
'unlink_account' => 'Desconectar Cuenta',
'unlink' => 'Desconectar',
'show_address' => 'Actualizar Dirección',
'show_address_help' => 'Requerir que el cliente provea su dirección de facturación',
'update_address' => 'Actualizar Dirección',
'update_address_help' => 'Actualizar la dirección del cliente con los detalles proporcionados',
'times' => 'Tiempos',
'set_now' => 'Asignar ahora',
'dark_mode' => 'Modo Oscuro',
'dark_mode_help' => 'Mostrar texto blanco sobre fondo negro',
'add_to_invoice' => 'Agregar a cuenta :invoice',
'create_new_invoice' => 'Crear Nueva Cuenta',
'task_errors' => 'Por favor corrija cualquier tiempo que se sobreponga con otro',
'from' => 'De',
'to' => 'Para',
'font_size' => 'Tamaño de Letra',
'primary_color' => 'Color Primario',
'secondary_color' => 'Color Secundario',
'customize_design' => 'Personalizar el Diseño',
'content' => 'Contenido',
'styles' => 'Estílos',
'defaults' => 'Valores por Defecto',
'margins' => 'Márgenes',
'header' => 'encabezado',
'footer' => 'Pié de Página',
'custom' => 'Personalizado',
'invoice_to' => 'Factura para',
'invoice_no' => 'Factura No.',
'recent_payments' => 'Pagos Recientes',
'outstanding' => 'Sobresaliente',
'manage_companies' => 'Administrar Compañías',
'total_revenue' => 'Ingresos Totales',
'current_user' => 'Usuario Actual',
'new_recurring_invoice' => 'Nueva Factura Recurrente',
'recurring_invoice' => 'Factura Recurrente',
'recurring_too_soon' => 'Es my pronto para crear la siguiente factura recurrente',
'created_by_invoice' => 'Creado por :invoice',
'primary_user' => 'Usuario Primario',
'help' => 'Ayuda',
'customize_help' => '<p>Nosotros usamos <a href="http://pdfmake.org/" target="_blank">pdfmake</a> para definir los diseños de las cuentas de cobro de manera declarativa. El <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> de pdfmake es una excelente manera de ver a la librería en acción.</p>
<p>Puede acceder cualquier campo de una factura agregando <code>Value</code> al final. Por ejemplo, <code>$invoiceNumberValue</code> muestra el número de factura.</p>
<p>Para acceder a una propiedad hija usando notación de punto.Por ejemplo, para mostrar el nombre de un cliente se puede usar <code>$client.nameValue</code>.</p>
<p>Si necesita ayuda entendiendo algo puede preguntar en nuestro <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">foro de soporte</a>.</p>'
'email_receipt' => 'Email payment receipt to the client',
'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account',
'untitled' => 'Untitled',
'new_account' => 'New Account',
'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login',
'or' => 'or',
); );

View File

@ -598,7 +598,6 @@ return array(
'less_fields' => 'Less Fields', 'less_fields' => 'Less Fields',
'client_name' => 'Client Name', 'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings', 'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings', 'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap', 'auto_wrap' => 'Auto Line Wrap',
'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.', 'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.',
@ -697,14 +696,64 @@ return array(
'email_receipt' => 'Email payment receipt to the client', 'email_receipt' => 'Email payment receipt to the client',
'created_payment_emailed_client' => 'Successfully created payment and emailed client', 'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account', 'add_company' => 'Add Company',
'untitled' => 'Untitled', 'untitled' => 'Untitled',
'new_account' => 'New Account', 'new_company' => 'New Company',
'associated_accounts' => 'Successfully linked accounts', 'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts', 'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login', 'login' => 'Login',
'or' => 'or', 'or' => 'or',
'email_error' => 'There was a problem sending the email',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
'from' => 'From',
'to' => 'To',
'font_size' => 'Font Size',
'primary_color' => 'Primary Color',
'secondary_color' => 'Secondary Color',
'customize_design' => 'Customize Design',
'content' => 'Content',
'styles' => 'Styles',
'defaults' => 'Defaults',
'margins' => 'Margins',
'header' => 'Header',
'footer' => 'Footer',
'custom' => 'Custom',
'invoice_to' => 'Invoice to',
'invoice_no' => 'Invoice No.',
'recent_payments' => 'Recent Payments',
'outstanding' => 'Outstanding',
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
'primary_user' => 'Primary User',
'help' => 'Help',
'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p>
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>'
); );

View File

@ -1,4 +1,4 @@
<?php <?php
return array( return array(
@ -35,11 +35,11 @@ return array(
'invoice_number_short' => 'Facture #', 'invoice_number_short' => 'Facture #',
'po_number' => 'Numéro du bon de commande', 'po_number' => 'Numéro du bon de commande',
'po_number_short' => 'Bon de commande #', 'po_number_short' => 'Bon de commande #',
'frequency_id' => 'Fréquence', 'frequency_id' => 'Fréquence',
'discount' => 'Remise', 'discount' => 'Remise',
'taxes' => 'Taxes', 'taxes' => 'Taxes',
'tax' => 'Taxe', 'tax' => 'Taxe',
'item' => 'Article', 'item' => 'Article',
'description' => 'Description', 'description' => 'Description',
'unit_cost' => 'Coût unitaire', 'unit_cost' => 'Coût unitaire',
'quantity' => 'Quantité', 'quantity' => 'Quantité',
@ -117,11 +117,11 @@ return array(
'billed_client' => 'client facturé', 'billed_client' => 'client facturé',
'billed_clients' => 'clients facturés', 'billed_clients' => 'clients facturés',
'active_client' => 'client actif', 'active_client' => 'client actif',
'active_clients' => 'clients actifs', 'active_clients' => 'clients actifs',
'invoices_past_due' => 'Date limite de paiement dépassée', 'invoices_past_due' => 'Date limite de paiement dépassée',
'upcoming_invoices' => 'Factures à venir', 'upcoming_invoices' => 'Factures à venir',
'average_invoice' => 'Moyenne de facturation', 'average_invoice' => 'Moyenne de facturation',
// list pages // list pages
'archive' => 'Archiver', 'archive' => 'Archiver',
'delete' => 'Supprimer', 'delete' => 'Supprimer',
@ -276,7 +276,7 @@ return array(
// Payment page // Payment page
'secure_payment' => 'Paiement sécurisé', 'secure_payment' => 'Paiement sécurisé',
'card_number' => 'Numéro de carte', 'card_number' => 'Numéro de carte',
'expiration_month' => 'Mois d\'expiration', 'expiration_month' => 'Mois d\'expiration',
'expiration_year' => 'Année d\'expiration', 'expiration_year' => 'Année d\'expiration',
'cvv' => 'CVV', 'cvv' => 'CVV',
@ -297,8 +297,8 @@ return array(
'remove_logo_link' => 'Cliquez ici', 'remove_logo_link' => 'Cliquez ici',
], ],
'logout' => 'Se déconnecter', 'logout' => 'Se déconnecter',
'sign_up_to_save' => 'Connectez vous pour sauvegarder votre travail', 'sign_up_to_save' => 'Connectez vous pour sauvegarder votre travail',
'agree_to_terms' =>'J\'accepte les conditions d\'utilisation d\'Invoice ninja :terms', 'agree_to_terms' =>'J\'accepte les conditions d\'utilisation d\'Invoice ninja :terms',
'terms_of_service' => 'Conditions d\'utilisation', 'terms_of_service' => 'Conditions d\'utilisation',
'email_taken' => 'L\'adresse courriel existe déjà', 'email_taken' => 'L\'adresse courriel existe déjà',
@ -319,7 +319,7 @@ return array(
'field_label' => 'Nom du champ', 'field_label' => 'Nom du champ',
'field_value' => 'Valeur du champ', 'field_value' => 'Valeur du champ',
'edit' => 'Éditer', 'edit' => 'Éditer',
'view_as_recipient' => 'Voir en tant que destinataire', 'view_as_recipient' => 'Voir en tant que destinataire',
// product management // product management
'product_library' => 'Inventaire', 'product_library' => 'Inventaire',
@ -387,7 +387,7 @@ return array(
'notification_quote_sent_subject' => 'Le devis :invoice a été envoyé à :client', 'notification_quote_sent_subject' => 'Le devis :invoice a été envoyé à :client',
'notification_quote_viewed_subject' => 'Le devis :invoice a été visionné par :client', 'notification_quote_viewed_subject' => 'Le devis :invoice a été visionné par :client',
'notification_quote_sent' => 'Le devis :invoice de :amount a été envoyé au client :client.', 'notification_quote_sent' => 'Le devis :invoice de :amount a été envoyé au client :client.',
'notification_quote_viewed' => 'Le devis :invoice de :amount a été visioné par le client :client.', 'notification_quote_viewed' => 'Le devis :invoice de :amount a été visioné par le client :client.',
'session_expired' => 'Votre session a expiré.', 'session_expired' => 'Votre session a expiré.',
@ -426,7 +426,7 @@ return array(
'sample_data' => 'Données fictives présentées', 'sample_data' => 'Données fictives présentées',
'hide' => 'Cacher', 'hide' => 'Cacher',
'new_version_available' => 'Une nouvelle version de :releases_link est disponible. Vous utilisez v:user_version, la plus récente est v:latest_version', 'new_version_available' => 'Une nouvelle version de :releases_link est disponible. Vous utilisez v:user_version, la plus récente est v:latest_version',
'invoice_settings' => 'Paramètres des factures', 'invoice_settings' => 'Paramètres des factures',
'invoice_number_prefix' => 'Préfixe du numéro de facture', 'invoice_number_prefix' => 'Préfixe du numéro de facture',
@ -436,7 +436,7 @@ return array(
'share_invoice_counter' => 'Partager le compteur de facture', 'share_invoice_counter' => 'Partager le compteur de facture',
'invoice_issued_to' => 'Facture destinée à', 'invoice_issued_to' => 'Facture destinée à',
'invalid_counter' => 'Pour éviter un éventuel conflit, merci de définir un préfixe pour le numéro de facture ou pour le numéro de devis', 'invalid_counter' => 'Pour éviter un éventuel conflit, merci de définir un préfixe pour le numéro de facture ou pour le numéro de devis',
'mark_sent' => 'Marquer comme envoyé', 'mark_sent' => 'Marquer comme envoyé',
'gateway_help_1' => ':link to sign up for Authorize.net.', 'gateway_help_1' => ':link to sign up for Authorize.net.',
'gateway_help_2' => ':link to sign up for Authorize.net.', 'gateway_help_2' => ':link to sign up for Authorize.net.',
@ -452,7 +452,7 @@ return array(
'more_designs_self_host_text' => '', 'more_designs_self_host_text' => '',
'buy' => 'Acheter', 'buy' => 'Acheter',
'bought_designs' => 'Les nouveaux modèles ont été ajoutés avec succès', 'bought_designs' => 'Les nouveaux modèles ont été ajoutés avec succès',
'sent' => 'envoyé', 'sent' => 'envoyé',
'timesheets' => 'Feuilles de temps', 'timesheets' => 'Feuilles de temps',
@ -460,7 +460,7 @@ return array(
'payment_cvv' => '*Numéro à 3 ou 4 chiffres au dos de votre carte', 'payment_cvv' => '*Numéro à 3 ou 4 chiffres au dos de votre carte',
'payment_footer1' => '*L\'adresse de facturation doit correspondre à celle enregistrée avec votre carte bancaire', 'payment_footer1' => '*L\'adresse de facturation doit correspondre à celle enregistrée avec votre carte bancaire',
'payment_footer2' => '*Merci de cliquer sur "Payer maintenant" une seule fois. Le processus peut prendre jusqu\'à 1 minute.', 'payment_footer2' => '*Merci de cliquer sur "Payer maintenant" une seule fois. Le processus peut prendre jusqu\'à 1 minute.',
'vat_number' => 'Numéro de TVA', 'vat_number' => 'Numéro de TVA',
'id_number' => 'Numéro ID', 'id_number' => 'Numéro ID',
'white_label_link' => 'Marque blanche', 'white_label_link' => 'Marque blanche',
@ -485,7 +485,7 @@ return array(
'reason_for_canceling' => 'Aidez nous à améliorer notre site en nous disant pourquoi vous partez.', 'reason_for_canceling' => 'Aidez nous à améliorer notre site en nous disant pourquoi vous partez.',
'discount_percent' => 'Pourcent', 'discount_percent' => 'Pourcent',
'discount_amount' => 'Montant', 'discount_amount' => 'Montant',
'invoice_history' => 'Historique des factures', 'invoice_history' => 'Historique des factures',
'quote_history' => 'Historique des devis', 'quote_history' => 'Historique des devis',
'current_version' => 'Version courante', 'current_version' => 'Version courante',
@ -503,7 +503,7 @@ return array(
'payment_email' => 'Email de paiement', 'payment_email' => 'Email de paiement',
'quote_email' => 'Email de déclaration', 'quote_email' => 'Email de déclaration',
'reset_all' => 'Réinitialiser', 'reset_all' => 'Réinitialiser',
'approve' => 'Accepter', 'approve' => 'Accepter',
'token_billing_type_id' => 'Token Billing', 'token_billing_type_id' => 'Token Billing',
'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.',
@ -527,7 +527,7 @@ return array(
'order_overview' => 'Order overview', 'order_overview' => 'Order overview',
'match_address' => '*Address must match address associated with credit card.', 'match_address' => '*Address must match address associated with credit card.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.', 'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'default_invoice_footer' => 'Définir par défaut', 'default_invoice_footer' => 'Définir par défaut',
'invoice_footer' => 'Pied de facture', 'invoice_footer' => 'Pied de facture',
'save_as_default_footer' => 'Définir comme pied de facture par défatu', 'save_as_default_footer' => 'Définir comme pied de facture par défatu',
@ -577,7 +577,7 @@ return array(
'notification_quote_approved' => 'The following client :client approved Quote :invoice for :amount.', 'notification_quote_approved' => 'The following client :client approved Quote :invoice for :amount.',
'resend_confirmation' => 'Resend confirmation email', 'resend_confirmation' => 'Resend confirmation email',
'confirmation_resent' => 'The confirmation email was resent', 'confirmation_resent' => 'The confirmation email was resent',
'gateway_help_42' => ':link to sign up for BitPay.<br/>Note: use a Legacy API Key, not an API token.', 'gateway_help_42' => ':link to sign up for BitPay.<br/>Note: use a Legacy API Key, not an API token.',
'payment_type_credit_card' => 'Carte de crédit', 'payment_type_credit_card' => 'Carte de crédit',
'payment_type_paypal' => 'PayPal', 'payment_type_paypal' => 'PayPal',
@ -585,19 +585,18 @@ return array(
'knowledge_base' => 'Base de connaissances', 'knowledge_base' => 'Base de connaissances',
'partial' => 'Partiel', 'partial' => 'Partiel',
'partial_remaining' => ':partial de :balance', 'partial_remaining' => ':partial de :balance',
'more_fields' => 'Plus de champs', 'more_fields' => 'Plus de champs',
'less_fields' => 'Moins de champs', 'less_fields' => 'Moins de champs',
'client_name' => 'Nom du client', 'client_name' => 'Nom du client',
'pdf_settings' => 'Réglages PDF', 'pdf_settings' => 'Réglages PDF',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Réglages du produit', 'product_settings' => 'Réglages du produit',
'auto_wrap' => 'Auto Line Wrap', 'auto_wrap' => 'Auto Line Wrap',
'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.', 'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.',
'view_documentation' => 'Voir documentation', 'view_documentation' => 'Voir documentation',
'app_title' => 'Free Open-Source Online Invoicing', 'app_title' => 'Free Open-Source Online Invoicing',
'app_description' => 'Invoice Ninja is a free, open-source solution for invoicing and billing customers. With Invoice Ninja, you can easily build and send beautiful invoices from any device that has access to the web. Your clients can print your invoices, download them as pdf files, and even pay you online from within the system.', 'app_description' => 'Invoice Ninja is a free, open-source solution for invoicing and billing customers. With Invoice Ninja, you can easily build and send beautiful invoices from any device that has access to the web. Your clients can print your invoices, download them as pdf files, and even pay you online from within the system.',
'rows' => 'lignes', 'rows' => 'lignes',
'www' => 'www', 'www' => 'www',
'logo' => 'Logo', 'logo' => 'Logo',
@ -628,7 +627,7 @@ return array(
'archive_task' => 'Archiver tâche', 'archive_task' => 'Archiver tâche',
'restore_task' => 'Restaurer tâche', 'restore_task' => 'Restaurer tâche',
'delete_task' => 'Supprimer tâche', 'delete_task' => 'Supprimer tâche',
'stop_task' => 'Arrêter tâcher', 'stop_task' => 'Arrêter tâche',
'time' => 'Temps', 'time' => 'Temps',
'start' => 'Début', 'start' => 'Début',
'stop' => 'Fin', 'stop' => 'Fin',
@ -682,21 +681,71 @@ return array(
'resume' => 'Resume', 'resume' => 'Resume',
'break_duration' => 'Break', 'break_duration' => 'Break',
'edit_details' => 'Editer détails', 'edit_details' => 'Modifier',
'work' => 'Travail', 'work' => 'Travail',
'timezone_unset' => 'Please :link to set your timezone', 'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'cliquer ici', 'click_here' => 'cliquer ici',
'email_receipt' => 'Email payment receipt to the client', 'email_receipt' => 'Email payment receipt to the client',
'created_payment_emailed_client' => 'Paiement crée avec succès et envoyé au client', 'created_payment_emailed_client' => 'Paiement crée avec succès et envoyé au client',
'add_account' => 'Ajouter compte', 'add_company' => 'Ajouter compte',
'untitled' => 'Sans titre', 'untitled' => 'Sans titre',
'new_account' => 'Nouveau compte', 'new_company' => 'Nouveau compte',
'associated_accounts' => 'Successfully linked accounts', 'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts', 'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Connexion', 'login' => 'Connexion',
'or' => 'ou', 'or' => 'ou',
'email_error' => 'There was a problem sending the email',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
'from' => 'From',
'to' => 'To',
'font_size' => 'Font Size',
'primary_color' => 'Primary Color',
'secondary_color' => 'Secondary Color',
'customize_design' => 'Customize Design',
'content' => 'Content',
'styles' => 'Styles',
'defaults' => 'Defaults',
'margins' => 'Margins',
'header' => 'Header',
'footer' => 'Footer',
'custom' => 'Custom',
'invoice_to' => 'Invoice to',
'invoice_no' => 'Invoice No.',
'recent_payments' => 'Recent Payments',
'outstanding' => 'Outstanding',
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
'primary_user' => 'Primary User',
'help' => 'Help',
'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p>
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>'
); );

View File

@ -1,4 +1,4 @@
<?php <?php
return array( return array(
@ -35,11 +35,11 @@ return array(
'invoice_number_short' => 'Facture #', 'invoice_number_short' => 'Facture #',
'po_number' => 'Numéro du bon de commande', 'po_number' => 'Numéro du bon de commande',
'po_number_short' => 'Bon de commande #', 'po_number_short' => 'Bon de commande #',
'frequency_id' => 'Fréquence', 'frequency_id' => 'Fréquence',
'discount' => 'Remise', 'discount' => 'Remise',
'taxes' => 'Taxes', 'taxes' => 'Taxes',
'tax' => 'Taxe', 'tax' => 'Taxe',
'item' => 'Article', 'item' => 'Article',
'description' => 'Description', 'description' => 'Description',
'unit_cost' => 'Coût unitaire', 'unit_cost' => 'Coût unitaire',
'quantity' => 'Quantité', 'quantity' => 'Quantité',
@ -117,11 +117,11 @@ return array(
'billed_client' => 'client facturé', 'billed_client' => 'client facturé',
'billed_clients' => 'clients facturés', 'billed_clients' => 'clients facturés',
'active_client' => 'client actif', 'active_client' => 'client actif',
'active_clients' => 'clients actifs', 'active_clients' => 'clients actifs',
'invoices_past_due' => 'Date limite de paiement dépassée', 'invoices_past_due' => 'Date limite de paiement dépassée',
'upcoming_invoices' => 'Factures à venir', 'upcoming_invoices' => 'Factures à venir',
'average_invoice' => 'Moyenne de facturation', 'average_invoice' => 'Moyenne de facturation',
// list pages // list pages
'archive' => 'Archiver', 'archive' => 'Archiver',
'delete' => 'Supprimer', 'delete' => 'Supprimer',
@ -276,7 +276,7 @@ return array(
// Payment page // Payment page
'secure_payment' => 'Paiement sécurisé', 'secure_payment' => 'Paiement sécurisé',
'card_number' => 'Numéro de carte', 'card_number' => 'Numéro de carte',
'expiration_month' => 'Mois d\'expiration', 'expiration_month' => 'Mois d\'expiration',
'expiration_year' => 'Année d\'expiration', 'expiration_year' => 'Année d\'expiration',
'cvv' => 'CVV', 'cvv' => 'CVV',
@ -297,8 +297,8 @@ return array(
'remove_logo_link' => 'Cliquez ici', 'remove_logo_link' => 'Cliquez ici',
], ],
'logout' => 'Se déconnecter', 'logout' => 'Se déconnecter',
'sign_up_to_save' => 'Connectez vous pour sauvegarder votre travail', 'sign_up_to_save' => 'Connectez vous pour sauvegarder votre travail',
'agree_to_terms' =>'J\'accepte les conditions d\'utilisation d\'Invoice ninja :terms', 'agree_to_terms' =>'J\'accepte les conditions d\'utilisation d\'Invoice ninja :terms',
'terms_of_service' => 'Conditions d\'utilisation', 'terms_of_service' => 'Conditions d\'utilisation',
'email_taken' => 'L\'adresse courriel existe déjà', 'email_taken' => 'L\'adresse courriel existe déjà',
@ -319,7 +319,7 @@ return array(
'field_label' => 'Nom du champ', 'field_label' => 'Nom du champ',
'field_value' => 'Valeur du champ', 'field_value' => 'Valeur du champ',
'edit' => 'Éditer', 'edit' => 'Éditer',
'view_as_recipient' => 'Voir en tant que destinataire', 'view_as_recipient' => 'Voir en tant que destinataire',
// product management // product management
'product_library' => 'Inventaire', 'product_library' => 'Inventaire',
@ -387,7 +387,7 @@ return array(
'notification_quote_sent_subject' => 'Le devis :invoice a été envoyé à :client', 'notification_quote_sent_subject' => 'Le devis :invoice a été envoyé à :client',
'notification_quote_viewed_subject' => 'Le devis :invoice a été visionné par :client', 'notification_quote_viewed_subject' => 'Le devis :invoice a été visionné par :client',
'notification_quote_sent' => 'Le devis :invoice de :amount a été envoyé au client :client.', 'notification_quote_sent' => 'Le devis :invoice de :amount a été envoyé au client :client.',
'notification_quote_viewed' => 'Le devis :invoice de :amount a été visioné par le client :client.', 'notification_quote_viewed' => 'Le devis :invoice de :amount a été visioné par le client :client.',
'session_expired' => 'Votre session a expiré.', 'session_expired' => 'Votre session a expiré.',
@ -426,7 +426,7 @@ return array(
'sample_data' => 'Données fictives présentées', 'sample_data' => 'Données fictives présentées',
'hide' => 'Cacher', 'hide' => 'Cacher',
'new_version_available' => 'Une nouvelle version de :releases_link est disponible. Vous utilisez v:user_version, la plus récente est v:latest_version', 'new_version_available' => 'Une nouvelle version de :releases_link est disponible. Vous utilisez v:user_version, la plus récente est v:latest_version',
'invoice_settings' => 'Paramètres des factures', 'invoice_settings' => 'Paramètres des factures',
'invoice_number_prefix' => 'Préfixe du numéro de facture', 'invoice_number_prefix' => 'Préfixe du numéro de facture',
@ -436,7 +436,7 @@ return array(
'share_invoice_counter' => 'Partager le compteur de facture', 'share_invoice_counter' => 'Partager le compteur de facture',
'invoice_issued_to' => 'Facture destinée à', 'invoice_issued_to' => 'Facture destinée à',
'invalid_counter' => 'Pour éviter un éventuel conflit, merci de définir un préfixe pour le numéro de facture ou pour le numéro de devis', 'invalid_counter' => 'Pour éviter un éventuel conflit, merci de définir un préfixe pour le numéro de facture ou pour le numéro de devis',
'mark_sent' => 'Marquer comme envoyé', 'mark_sent' => 'Marquer comme envoyé',
'gateway_help_1' => ':link to sign up for Authorize.net.', 'gateway_help_1' => ':link to sign up for Authorize.net.',
'gateway_help_2' => ':link to sign up for Authorize.net.', 'gateway_help_2' => ':link to sign up for Authorize.net.',
@ -452,7 +452,7 @@ return array(
'more_designs_self_host_text' => '', 'more_designs_self_host_text' => '',
'buy' => 'Acheter', 'buy' => 'Acheter',
'bought_designs' => 'Les nouveaux modèles ont été ajoutés avec succès', 'bought_designs' => 'Les nouveaux modèles ont été ajoutés avec succès',
'sent' => 'envoyé', 'sent' => 'envoyé',
'timesheets' => 'Feuilles de temps', 'timesheets' => 'Feuilles de temps',
@ -460,7 +460,7 @@ return array(
'payment_cvv' => '*This is the 3-4 digit number onthe back of your card', 'payment_cvv' => '*This is the 3-4 digit number onthe back of your card',
'payment_footer1' => '*Billing address must match address associated with credit card.', 'payment_footer1' => '*Billing address must match address associated with credit card.',
'payment_footer2' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.', 'payment_footer2' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'vat_number' => 'Numéro de TVA', 'vat_number' => 'Numéro de TVA',
'id_number' => 'Numéro ID', 'id_number' => 'Numéro ID',
'white_label_link' => 'Marque blanche', 'white_label_link' => 'Marque blanche',
@ -485,7 +485,7 @@ return array(
'reason_for_canceling' => 'Aidez nous à améliorer notre site en nous disant pourquoi vous partez.', 'reason_for_canceling' => 'Aidez nous à améliorer notre site en nous disant pourquoi vous partez.',
'discount_percent' => 'Pourcent', 'discount_percent' => 'Pourcent',
'discount_amount' => 'Montant', 'discount_amount' => 'Montant',
'invoice_history' => 'Historique des factures', 'invoice_history' => 'Historique des factures',
'quote_history' => 'Historique des devis', 'quote_history' => 'Historique des devis',
'current_version' => 'Version courante', 'current_version' => 'Version courante',
@ -503,7 +503,7 @@ return array(
'payment_email' => 'Courriel de paiement', 'payment_email' => 'Courriel de paiement',
'quote_email' => 'Courriel de devis', 'quote_email' => 'Courriel de devis',
'reset_all' => 'Tout remettre à zéro', 'reset_all' => 'Tout remettre à zéro',
'approve' => 'Approuver', 'approve' => 'Approuver',
'token_billing_type_id' => 'Token Billing', 'token_billing_type_id' => 'Token Billing',
'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.',
@ -527,7 +527,7 @@ return array(
'order_overview' => 'Order overview', 'order_overview' => 'Order overview',
'match_address' => '*Address must match address associated with credit card.', 'match_address' => '*Address must match address associated with credit card.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.', 'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'default_invoice_footer' => 'Set default invoice footer', 'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer', 'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer', 'save_as_default_footer' => 'Save as default footer',
@ -571,13 +571,13 @@ return array(
'send_email' => 'Send email', 'send_email' => 'Send email',
'set_password' => 'Set Password', 'set_password' => 'Set Password',
'converted' => 'Converted', 'converted' => 'Converted',
'email_approved' => 'Email me when a quote is <b>approved</b>', 'email_approved' => 'Email me when a quote is <b>approved</b>',
'notification_quote_approved_subject' => 'Quote :invoice was approved by :client', 'notification_quote_approved_subject' => 'Quote :invoice was approved by :client',
'notification_quote_approved' => 'The following client :client approved Quote :invoice for :amount.', 'notification_quote_approved' => 'The following client :client approved Quote :invoice for :amount.',
'resend_confirmation' => 'Resend confirmation email', 'resend_confirmation' => 'Resend confirmation email',
'confirmation_resent' => 'The confirmation email was resent', 'confirmation_resent' => 'The confirmation email was resent',
'gateway_help_42' => ':link to sign up for BitPay.<br/>Note: use a Legacy API Key, not an API token.', 'gateway_help_42' => ':link to sign up for BitPay.<br/>Note: use a Legacy API Key, not an API token.',
'payment_type_credit_card' => 'Credit card', 'payment_type_credit_card' => 'Credit card',
'payment_type_paypal' => 'PayPal', 'payment_type_paypal' => 'PayPal',
@ -590,14 +590,13 @@ return array(
'less_fields' => 'Less Fields', 'less_fields' => 'Less Fields',
'client_name' => 'Client Name', 'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings', 'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings', 'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap', 'auto_wrap' => 'Auto Line Wrap',
'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.', 'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.',
'view_documentation' => 'View Documentation', 'view_documentation' => 'View Documentation',
'app_title' => 'Free Open-Source Online Invoicing', 'app_title' => 'Free Open-Source Online Invoicing',
'app_description' => 'Invoice Ninja is a free, open-source solution for invoicing and billing customers. With Invoice Ninja, you can easily build and send beautiful invoices from any device that has access to the web. Your clients can print your invoices, download them as pdf files, and even pay you online from within the system.', 'app_description' => 'Invoice Ninja is a free, open-source solution for invoicing and billing customers. With Invoice Ninja, you can easily build and send beautiful invoices from any device that has access to the web. Your clients can print your invoices, download them as pdf files, and even pay you online from within the system.',
'rows' => 'rows', 'rows' => 'rows',
'www' => 'www', 'www' => 'www',
'logo' => 'Logo', 'logo' => 'Logo',
@ -619,50 +618,50 @@ return array(
'last_invoice_sent' => 'Last invoice sent :date', 'last_invoice_sent' => 'Last invoice sent :date',
'processed_updates' => 'Successfully completed update', 'processed_updates' => 'Successfully completed update',
'tasks' => 'Tasks', 'tasks' => 'Tâches',
'new_task' => 'New Task', 'new_task' => 'Nouvelle Tâche',
'start_time' => 'Start Time', 'start_time' => 'Démarrée à',
'created_task' => 'Successfully created task', 'created_task' => 'Tâche créée avec succès',
'updated_task' => 'Successfully updated task', 'updated_task' => 'Tâche modifiée avec succès',
'edit_task' => 'Edit Task', 'edit_task' => 'Edit Task',
'archive_task' => 'Archive Task', 'archive_task' => 'Archiver la Tâche',
'restore_task' => 'Restore Task', 'restore_task' => 'Restaurer la Tâche',
'delete_task' => 'Delete Task', 'delete_task' => 'Supprimer la Tâche',
'stop_task' => 'Stop Task', 'stop_task' => 'Arrêter la Tâche',
'time' => 'Time', 'time' => 'Time',
'start' => 'Start', 'start' => 'Démarrer',
'stop' => 'Stop', 'stop' => 'Arrêter',
'now' => 'Now', 'now' => 'Now',
'timer' => 'Timer', 'timer' => 'Timer',
'manual' => 'Manual', 'manual' => 'Manual',
'date_and_time' => 'Date & Time', 'date_and_time' => 'Date & Time',
'second' => 'second', 'second' => 'seconde',
'seconds' => 'seconds', 'seconds' => 'secondes',
'minute' => 'minute', 'minute' => 'minute',
'minutes' => 'minutes', 'minutes' => 'minutes',
'hour' => 'hour', 'hour' => 'heure',
'hours' => 'hours', 'hours' => 'heures',
'task_details' => 'Task Details', 'task_details' => 'Détails de la Tâche',
'duration' => 'Duration', 'duration' => 'Durée',
'end_time' => 'End Time', 'end_time' => 'Arrêtée à',
'end' => 'End', 'end' => 'End',
'invoiced' => 'Invoiced', 'invoiced' => 'Invoiced',
'logged' => 'Logged', 'logged' => 'Logged',
'running' => 'Running', 'running' => 'Running',
'task_error_multiple_clients' => 'The tasks can\'t belong to different clients', 'task_error_multiple_clients' => 'Une tâche ne peut appartenir à plusieurs clients',
'task_error_running' => 'Please stop running tasks first', 'task_error_running' => 'Merci d\'arrêter les tâches en cours',
'task_error_invoiced' => 'Tasks have already been invoiced', 'task_error_invoiced' => 'Ces tâches ont déjà été facturées',
'restored_task' => 'Successfully restored task', 'restored_task' => 'Tâche restaurée avec succès',
'archived_task' => 'Successfully archived task', 'archived_task' => 'Tâche archivée avec succès',
'archived_tasks' => 'Successfully archived :count tasks', 'archived_tasks' => ':count tâches archivées avec succès',
'deleted_task' => 'Successfully deleted task', 'deleted_task' => 'Tâche supprimée avec succès',
'deleted_tasks' => 'Successfully deleted :count tasks', 'deleted_tasks' => ':count tâches supprimées avec succès',
'create_task' => 'Create Task', 'create_task' => 'Créer une Tâche',
'stopped_task' => 'Successfully stopped task', 'stopped_task' => 'Tâche arrêtée avec succès',
'invoice_task' => 'Invoice Task', 'invoice_task' => 'Facturer Tâche',
'invoice_labels' => 'Invoice Labels', 'invoice_labels' => 'Invoice Labels',
'prefix' => 'Prefix', 'prefix' => 'Préfixe',
'counter' => 'Counter', 'counter' => 'Compteur',
'payment_type_dwolla' => 'Dwolla', 'payment_type_dwolla' => 'Dwolla',
'gateway_help_43' => ':link to sign up for Dwolla.', 'gateway_help_43' => ':link to sign up for Dwolla.',
@ -681,22 +680,73 @@ return array(
'pro_plan_feature7' => 'Customize Invoice Field Titles & Numbering', 'pro_plan_feature7' => 'Customize Invoice Field Titles & Numbering',
'pro_plan_feature8' => 'Option to Attach PDFs to Client Emails', 'pro_plan_feature8' => 'Option to Attach PDFs to Client Emails',
'resume' => 'Resume', 'resume' => 'Continuer',
'break_duration' => 'Break', 'break_duration' => 'Pause',
'edit_details' => 'Edit Details', 'edit_details' => 'Modifier',
'work' => 'Work', 'work' => 'Travail',
'timezone_unset' => 'Please :link to set your timezone', 'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here', 'click_here' => 'cliquer içi',
'email_receipt' => 'Email payment receipt to the client', 'email_receipt' => 'Email payment receipt to the client',
'created_payment_emailed_client' => 'Successfully created payment and emailed client', 'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account', 'add_company' => 'Add Company',
'untitled' => 'Untitled', 'untitled' => 'Untitled',
'new_account' => 'New Account', 'new_company' => 'New Company',
'associated_accounts' => 'Successfully linked accounts', 'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts', 'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login', 'login' => 'Login',
'or' => 'or', 'or' => 'or',
'email_error' => 'There was a problem sending the email',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
'from' => 'From',
'to' => 'To',
'font_size' => 'Font Size',
'primary_color' => 'Primary Color',
'secondary_color' => 'Secondary Color',
'customize_design' => 'Customize Design',
'content' => 'Content',
'styles' => 'Styles',
'defaults' => 'Defaults',
'margins' => 'Margins',
'header' => 'Header',
'footer' => 'Footer',
'custom' => 'Custom',
'invoice_to' => 'Invoice to',
'invoice_no' => 'Invoice No.',
'recent_payments' => 'Recent Payments',
'outstanding' => 'Outstanding',
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
'primary_user' => 'Primary User',
'help' => 'Help',
'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p>
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>'
); );

View File

@ -592,7 +592,6 @@ return array(
'less_fields' => 'Less Fields', 'less_fields' => 'Less Fields',
'client_name' => 'Client Name', 'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings', 'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings', 'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap', 'auto_wrap' => 'Auto Line Wrap',
'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.', 'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.',
@ -692,14 +691,64 @@ return array(
'email_receipt' => 'Email payment receipt to the client', 'email_receipt' => 'Email payment receipt to the client',
'created_payment_emailed_client' => 'Successfully created payment and emailed client', 'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account', 'add_company' => 'Add Company',
'untitled' => 'Untitled', 'untitled' => 'Untitled',
'new_account' => 'New Account', 'new_company' => 'New Company',
'associated_accounts' => 'Successfully linked accounts', 'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts', 'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login', 'login' => 'Login',
'or' => 'or', 'or' => 'or',
'email_error' => 'There was a problem sending the email',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
'from' => 'From',
'to' => 'To',
'font_size' => 'Font Size',
'primary_color' => 'Primary Color',
'secondary_color' => 'Secondary Color',
'customize_design' => 'Customize Design',
'content' => 'Content',
'styles' => 'Styles',
'defaults' => 'Defaults',
'margins' => 'Margins',
'header' => 'Header',
'footer' => 'Footer',
'custom' => 'Custom',
'invoice_to' => 'Invoice to',
'invoice_no' => 'Invoice No.',
'recent_payments' => 'Recent Payments',
'outstanding' => 'Outstanding',
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
'primary_user' => 'Primary User',
'help' => 'Help',
'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p>
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>'
); );

View File

@ -600,7 +600,6 @@ return array(
'less_fields' => 'Less Fields', 'less_fields' => 'Less Fields',
'client_name' => 'Client Name', 'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings', 'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings', 'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap', 'auto_wrap' => 'Auto Line Wrap',
'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.', 'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.',
@ -699,14 +698,65 @@ return array(
'email_receipt' => 'Email payment receipt to the client', 'email_receipt' => 'Email payment receipt to the client',
'created_payment_emailed_client' => 'Successfully created payment and emailed client', 'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account', 'add_company' => 'Add Company',
'untitled' => 'Untitled', 'untitled' => 'Untitled',
'new_account' => 'New Account', 'new_company' => 'New Company',
'associated_accounts' => 'Successfully linked accounts', 'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts', 'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login', 'login' => 'Login',
'or' => 'or', 'or' => 'or',
'email_error' => 'There was a problem sending the email',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
'from' => 'From',
'to' => 'To',
'font_size' => 'Font Size',
'primary_color' => 'Primary Color',
'secondary_color' => 'Secondary Color',
'customize_design' => 'Customize Design',
'content' => 'Content',
'styles' => 'Styles',
'defaults' => 'Defaults',
'margins' => 'Margins',
'header' => 'Header',
'footer' => 'Footer',
'custom' => 'Custom',
'invoice_to' => 'Invoice to',
'invoice_no' => 'Invoice No.',
'recent_payments' => 'Recent Payments',
'outstanding' => 'Outstanding',
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
'primary_user' => 'Primary User',
'help' => 'Help',
'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p>
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>'
); );

View File

@ -598,7 +598,6 @@ return array(
'less_fields' => 'Less Fields', 'less_fields' => 'Less Fields',
'client_name' => 'Client Name', 'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings', 'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings', 'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap', 'auto_wrap' => 'Auto Line Wrap',
'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.', 'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.',
@ -697,14 +696,64 @@ return array(
'email_receipt' => 'Email payment receipt to the client', 'email_receipt' => 'Email payment receipt to the client',
'created_payment_emailed_client' => 'Successfully created payment and emailed client', 'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account', 'add_company' => 'Add Company',
'untitled' => 'Untitled', 'untitled' => 'Untitled',
'new_account' => 'New Account', 'new_company' => 'New Company',
'associated_accounts' => 'Successfully linked accounts', 'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts', 'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login', 'login' => 'Login',
'or' => 'or', 'or' => 'or',
'email_error' => 'There was a problem sending the email',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
'from' => 'From',
'to' => 'To',
'font_size' => 'Font Size',
'primary_color' => 'Primary Color',
'secondary_color' => 'Secondary Color',
'customize_design' => 'Customize Design',
'content' => 'Content',
'styles' => 'Styles',
'defaults' => 'Defaults',
'margins' => 'Margins',
'header' => 'Header',
'footer' => 'Footer',
'custom' => 'Custom',
'invoice_to' => 'Invoice to',
'invoice_no' => 'Invoice No.',
'recent_payments' => 'Recent Payments',
'outstanding' => 'Outstanding',
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
'primary_user' => 'Primary User',
'help' => 'Help',
'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p>
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>'
); );

View File

@ -313,38 +313,38 @@ return array(
'pro_plan_success' => 'Bedankt voor het aanmelden! Zodra je factuur betaald is zal je Pro Plan lidmaatschap beginnen.', 'pro_plan_success' => 'Bedankt voor het aanmelden! Zodra je factuur betaald is zal je Pro Plan lidmaatschap beginnen.',
'unsaved_changes' => 'Je hebt niet bewaarde wijzigingen', 'unsaved_changes' => 'Je hebt niet bewaarde wijzigingen',
'custom_fields' => 'Custom fields', 'custom_fields' => 'Aangepaste velden',
'company_fields' => 'Company Fields', 'company_fields' => 'Velden Bedrijf',
'client_fields' => 'Client Fields', 'client_fields' => 'Velden Klant',
'field_label' => 'Field Label', 'field_label' => 'Label Veld',
'field_value' => 'Field Value', 'field_value' => 'Waarde Veld',
'edit' => 'Bewerk', 'edit' => 'Bewerk',
'view_invoice' => 'Bekijk factuur', 'view_invoice' => 'Bekijk factuur',
'view_as_recipient' => 'Bekijk als ontvanger', 'view_as_recipient' => 'Bekijk als ontvanger',
// product management // product management
'product_library' => 'Product Library', 'product_library' => 'Product Bibliotheek',
'product' => 'Product', 'product' => 'Product',
'products' => 'Products', 'products' => 'Producten',
'fill_products' => 'Auto-fill products', 'fill_products' => 'Producten Automatisch aanvullen',
'fill_products_help' => 'Selecting a product will automatically <b>set the description and cost</b>', 'fill_products_help' => 'Een product selecteren zal automatisch <b>de beschrijving en kost instellen</b>',
'update_products' => 'Auto-update products', 'update_products' => 'Producten automatisch aanpassen',
'update_products_help' => 'Updating an invoice will automatically <b>update the products</b>', 'update_products_help' => 'Aanpassen van een factuur zal automatisch <b>de producten aanpassen</b>',
'create_product' => 'Create Product', 'create_product' => 'Product maken',
'edit_product' => 'Edit Product', 'edit_product' => 'Product aanpassen',
'archive_product' => 'Archive Product', 'archive_product' => 'Product Archiveren',
'updated_product' => 'Successfully updated product', 'updated_product' => 'Product Succesvol aangepast',
'created_product' => 'Successfully created product', 'created_product' => 'Product Succesvol aangemaakt',
'archived_product' => 'Successfully archived product', 'archived_product' => 'Product Succesvol gearchiveerd',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan', 'pro_plan_custom_fields' => ':link om aangepaste velden in te schakelen door het Pro Plan te nemen',
'advanced_settings' => 'Geavanceerde instellingen', 'advanced_settings' => 'Geavanceerde instellingen',
'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan', 'pro_plan_advanced_settings' => ':link om de geavanceerde instellingen te activeren door het Pro Plan te nemen',
'invoice_design' => 'Factuur ontwerp', 'invoice_design' => 'Factuur ontwerp',
'specify_colors' => 'Kies kleuren', 'specify_colors' => 'Kies kleuren',
'specify_colors_label' => 'Kies de kleuren die in de factuur gebruikt worden', 'specify_colors_label' => 'Kies de kleuren die in de factuur gebruikt worden',
'chart_builder' => 'Chart Builder', 'chart_builder' => 'Grafiek bouwer',
'ninja_email_footer' => 'Gebruik :site om uw klanten gratis te factureren en betalingen te ontvangen!', 'ninja_email_footer' => 'Gebruik :site om uw klanten gratis te factureren en betalingen te ontvangen!',
'go_pro' => 'Go Pro', 'go_pro' => 'Go Pro',
@ -395,9 +395,9 @@ return array(
'invoice_fields' => 'Factuur Velden', 'invoice_fields' => 'Factuur Velden',
'invoice_options' => 'Factuur Opties', 'invoice_options' => 'Factuur Opties',
'hide_quantity' => 'Verberg aantallen', 'hide_quantity' => 'Verberg aantallen',
'hide_quantity_help' => 'Als us artikel-aantallen altijd 1 zijn, kunt u uw facturen er netter uit laten zien door dit veld te verbergen.', 'hide_quantity_help' => 'Als de artikel-aantallen altijd 1 zijn, kunt u uw facturen netter maken door dit veld te verbergen.',
'hide_paid_to_date' => 'Hide paid to date', 'hide_paid_to_date' => 'Verberg "Reeds betaald"',
'hide_paid_to_date_help' => 'Only display the "Paid to Date" area on your invoices once a payment has been received.', 'hide_paid_to_date_help' => 'Toon alleen het "Reeds betaald" gebied op je facturen als er een betaling gemaakt is.',
'charge_taxes' => 'Charge taxes', 'charge_taxes' => 'Charge taxes',
'user_management' => 'Gebruikersbeheer', 'user_management' => 'Gebruikersbeheer',
@ -439,15 +439,15 @@ return array(
'mark_sent' => 'Markeer als verzonden', 'mark_sent' => 'Markeer als verzonden',
'gateway_help_1' => ':link to sign up for Authorize.net.', 'gateway_help_1' => ':link om in te schrijven voor Authorize.net.',
'gateway_help_2' => ':link to sign up for Authorize.net.', 'gateway_help_2' => ':link om in te schrijven voor Authorize.net.',
'gateway_help_17' => ':link to get your PayPal API signature.', 'gateway_help_17' => ':link om je PayPal API signature te krijgen.',
'gateway_help_23' => 'Note: use your secret API key, not your publishable API key.', 'gateway_help_23' => 'Opmerking: gebruik je gehieme API key, niet je publiceerbare API key.',
'gateway_help_27' => ':link to sign up for TwoCheckout.', 'gateway_help_27' => ':link om in te schrijven voor TwoCheckout.',
'more_designs' => 'Meer ontwerpen', 'more_designs' => 'Meer ontwerpen',
'more_designs_title' => 'Aanvullende Factuur Ontwerpen', 'more_designs_title' => 'Aanvullende Factuur Ontwerpen',
'more_designs_cloud_header' => 'Go Pro for more invoice designs', 'more_designs_cloud_header' => 'Neem Pro Plan voor meer factuur ontwerpen',
'more_designs_cloud_text' => '', 'more_designs_cloud_text' => '',
'more_designs_self_host_header' => 'Krijg 6 extra factuurontwerpen voor maar $'.INVOICE_DESIGNS_PRICE, 'more_designs_self_host_header' => 'Krijg 6 extra factuurontwerpen voor maar $'.INVOICE_DESIGNS_PRICE,
'more_designs_self_host_text' => '', 'more_designs_self_host_text' => '',
@ -458,12 +458,12 @@ return array(
'sent' => 'verzonden', 'sent' => 'verzonden',
'timesheets' => 'Timesheets', 'timesheets' => 'Timesheets',
'payment_title' => 'Enter Your Billing Address and Credit Card information', 'payment_title' => 'Geef je betalingsadres en kredietkaart gegevens op',
'payment_cvv' => '*This is the 3-4 digit number onthe back of your card', 'payment_cvv' => '*Dit is de code van 3-4 tekens op de achterkant van je kaart',
'payment_footer1' => '*Billing address must match address associated with credit card.', 'payment_footer1' => '*Betalingsadres moet overeenkomen met het adres dat aan je kaart gekoppekd is.',
'payment_footer2' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.', 'payment_footer2' => '*Klik alstublieft slechts 1 keer op "PAY NOW" - verwerking kan tot 1 minuut duren.',
'vat_number' => 'Vat Number', 'vat_number' => 'BTW Nummer',
'id_number' => 'ID Number', 'id_number' => 'ID Nummer',
'white_label_link' => 'White label', 'white_label_link' => 'White label',
'white_label_text' => 'Koop een white label licentie voor $'.WHITE_LABEL_PRICE.' om de Invoice Ninja merknaam te verwijderen uit de bovenkant van de klantenpagina\'s.', 'white_label_text' => 'Koop een white label licentie voor $'.WHITE_LABEL_PRICE.' om de Invoice Ninja merknaam te verwijderen uit de bovenkant van de klantenpagina\'s.',
@ -508,18 +508,18 @@ return array(
'reset_all' => 'Reset Alles', 'reset_all' => 'Reset Alles',
'approve' => 'Goedkeuren', 'approve' => 'Goedkeuren',
'token_billing_type_id' => 'Token Billing', 'token_billing_type_id' => 'Betalingstoken',
'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', 'token_billing_help' => 'Laat je toe om kredietkaart gegevens bij je gateway op te slaan en ze later te gebruiken.',
'token_billing_1' => 'Disabled', 'token_billing_1' => 'Inactief',
'token_billing_2' => 'Opt-in - checkbox is shown but not selected', 'token_billing_2' => 'Opt-in - checkbox is getoond maar niet geselecteerd',
'token_billing_3' => 'Opt-out - checkbox is shown and selected', 'token_billing_3' => 'Opt-out - checkbox is getoond en geselecteerd',
'token_billing_4' => 'Always', 'token_billing_4' => 'Altijd',
'token_billing_checkbox' => 'Store credit card details', 'token_billing_checkbox' => 'Sla kredietkaart gegevens op',
'view_in_stripe' => 'View in Stripe', 'view_in_stripe' => 'In Stripe bekijken',
'use_card_on_file' => 'Use card on file', 'use_card_on_file' => 'Use card on file',
'edit_payment_details' => 'Edit payment details', 'edit_payment_details' => 'Betalingsdetails aanpassen',
'token_billing' => 'Save card details', 'token_billing' => 'Kaartgegevens opslaan',
'token_billing_secure' => 'The data is stored securely by :stripe_link', 'token_billing_secure' => 'De gegevens zijn succesvol veilig opgeslaan door :stripe_link',
'support' => 'Ondersteuning', 'support' => 'Ondersteuning',
'contact_information' => 'Contact informatie', 'contact_information' => 'Contact informatie',
@ -546,14 +546,14 @@ return array(
'delete_token' => 'Verwijder Token', 'delete_token' => 'Verwijder Token',
'token' => 'Token', 'token' => 'Token',
'add_gateway' => 'Add Gateway', 'add_gateway' => 'Gateway Toevoegen',
'delete_gateway' => 'Delete Gateway', 'delete_gateway' => 'Gateway Verwijderen',
'edit_gateway' => 'Edit Gateway', 'edit_gateway' => 'Gateway Aanpassen',
'updated_gateway' => 'Successfully updated gateway', 'updated_gateway' => 'Gateway Succesvol aangepast',
'created_gateway' => 'Successfully created gateway', 'created_gateway' => 'Gateway Succesvol aangemaakt',
'deleted_gateway' => 'Successfully deleted gateway', 'deleted_gateway' => 'Gateway Succesvol verwijderd',
'pay_with_paypal' => 'PayPal', 'pay_with_paypal' => 'PayPal',
'pay_with_card' => 'Credit card', 'pay_with_card' => 'Kredietkaart',
'change_password' => 'Verander wachtwoord', 'change_password' => 'Verander wachtwoord',
'current_password' => 'Huidig wachtwoord', 'current_password' => 'Huidig wachtwoord',
@ -575,131 +575,180 @@ return array(
'set_password' => 'Stel wachtwoord in', 'set_password' => 'Stel wachtwoord in',
'converted' => 'Omgezet', 'converted' => 'Omgezet',
'email_approved' => 'Email me when a quote is <b>approved</b>', 'email_approved' => 'Email me wanneer een offerte is <b>goedgekeurd</b>',
'notification_quote_approved_subject' => 'Quote :invoice was approved by :client', 'notification_quote_approved_subject' => 'Offerte :invoice is goedgekeurd door :client',
'notification_quote_approved' => 'The following client :client approved Quote :invoice for :amount.', 'notification_quote_approved' => 'De volgende Klant :client heeft Offerte :invoice goedgekeurd voor :amount.',
'resend_confirmation' => 'Resend confirmation email', 'resend_confirmation' => 'Verstuurd bevestingsmail opnieuw',
'confirmation_resent' => 'The confirmation email was resent', 'confirmation_resent' => 'De bevestigingsmail is opnieuw verstuurd',
'gateway_help_42' => ':link to sign up for BitPay.<br/>Note: use a Legacy API Key, not an API token.', 'gateway_help_42' => ':link om te registreren voor BitPay.<br/>Opmerking: gebruik een Legacy API Key, niet een API token.',
'payment_type_credit_card' => 'Credit card', 'payment_type_credit_card' => 'Kredietkaart',
'payment_type_paypal' => 'PayPal', 'payment_type_paypal' => 'PayPal',
'payment_type_bitcoin' => 'Bitcoin', 'payment_type_bitcoin' => 'Bitcoin',
'knowledge_base' => 'Knowledge Base', 'knowledge_base' => 'Kennis databank',
'partial' => 'Partial', 'partial' => 'Gedeeld',
'partial_remaining' => ':partial of :balance', 'partial_remaining' => ':partial / :balance',
'more_fields' => 'More Fields', 'more_fields' => 'Meer velden',
'less_fields' => 'Less Fields', 'less_fields' => 'Minder velden',
'client_name' => 'Client Name', 'client_name' => 'Klant Naam',
'pdf_settings' => 'PDF Settings', 'pdf_settings' => 'PDF Instellingen',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>', 'product_settings' => 'Product Instellingen',
'product_settings' => 'Product Settings', 'auto_wrap' => 'Automatisch Lijn afbreken',
'auto_wrap' => 'Auto Line Wrap', 'duplicate_post' => 'Opgelet: de volgende pagina is twee keer doorgestuurd. De tweede verzending is genegeerd.',
'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.', 'view_documentation' => 'Bekijk Documentatie',
'view_documentation' => 'View Documentation', 'app_title' => 'Gratis Open-Source Online Facturatie',
'app_title' => 'Free Open-Source Online Invoicing', 'app_description' => 'Invoice Ninja is een gratis, open-source oplossing voor het aanmkaen en versturen van facturen aan klanten. Met Invoice Ninja, kan je gemakkelijk mooie facturen aanmaken en verzenden van om het even welk toestel met internettoegang. Je klanten kunnen je facturen afprinten, downloaden als pdf bestanden, en je zelfs online betalen vanuit het systeem.',
'app_description' => 'Invoice Ninja is a free, open-source solution for invoicing and billing customers. With Invoice Ninja, you can easily build and send beautiful invoices from any device that has access to the web. Your clients can print your invoices, download them as pdf files, and even pay you online from within the system.',
'rows' => 'rows', 'rows' => 'rijen',
'www' => 'www', 'www' => 'www',
'logo' => 'Logo', 'logo' => 'Logo',
'subdomain' => 'Subdomain', 'subdomain' => 'Subdomein',
'provide_name_or_email' => 'Please provide a contact name or email', 'provide_name_or_email' => 'Geef aub een contact naam en email op',
'charts_and_reports' => 'Charts & Reports', 'charts_and_reports' => 'Grafieken & Rapporten',
'chart' => 'Chart', 'chart' => 'Grafiek',
'report' => 'Report', 'report' => 'Rapport',
'group_by' => 'Group by', 'group_by' => 'Groepeer per',
'paid' => 'Paid', 'paid' => 'Betaald',
'enable_report' => 'Report', 'enable_report' => 'Rapport',
'enable_chart' => 'Chart', 'enable_chart' => 'Grafiek',
'totals' => 'Totals', 'totals' => 'Totalen',
'run' => 'Run', 'run' => 'Uitvoeren',
'export' => 'Export', 'export' => 'Exporteer',
'documentation' => 'Documentation', 'documentation' => 'Documentatie',
'zapier' => 'Zapier <sup>Beta</sup>', 'zapier' => 'Zapier <sup>Beta</sup>',
'recurring' => 'Recurring', 'recurring' => 'Terugkerend',
'last_invoice_sent' => 'Last invoice sent :date', 'last_invoice_sent' => 'Laatste factuur verzonden :date',
'processed_updates' => 'Successfully completed update', 'processed_updates' => 'Update succesvol uitgevoerd',
'tasks' => 'Tasks', 'tasks' => 'Taken',
'new_task' => 'New Task', 'new_task' => 'Nieuwe Taak',
'start_time' => 'Start Time', 'start_time' => 'Start Tijd',
'created_task' => 'Successfully created task', 'created_task' => 'Taak succesvol aangemaakt',
'updated_task' => 'Successfully updated task', 'updated_task' => 'Taak succesvol aangepast',
'edit_task' => 'Edit Task', 'edit_task' => 'Pas Taak aan',
'archive_task' => 'Archive Task', 'archive_task' => 'Archiveer Taak',
'restore_task' => 'Restore Task', 'restore_task' => 'Taak herstellen',
'delete_task' => 'Delete Task', 'delete_task' => 'Verwijder Taak',
'stop_task' => 'Stop Task', 'stop_task' => 'Stop Taak',
'time' => 'Time', 'time' => 'Tijd',
'start' => 'Start', 'start' => 'Start',
'stop' => 'Stop', 'stop' => 'Stop',
'now' => 'Now', 'now' => 'Nu',
'timer' => 'Timer', 'timer' => 'Timer',
'manual' => 'Manual', 'manual' => 'Manueel',
'date_and_time' => 'Date & Time', 'date_and_time' => 'Datum & Tijd',
'second' => 'second', 'second' => 'second',
'seconds' => 'seconds', 'seconds' => 'seconden',
'minute' => 'minute', 'minute' => 'minuut',
'minutes' => 'minutes', 'minutes' => 'minuten',
'hour' => 'hour', 'hour' => 'uur',
'hours' => 'hours', 'hours' => 'uren',
'task_details' => 'Task Details', 'task_details' => 'Taak Details',
'duration' => 'Duration', 'duration' => 'Duur',
'end_time' => 'End Time', 'end_time' => 'Eind Tijd',
'end' => 'End', 'end' => 'Einde',
'invoiced' => 'Invoiced', 'invoiced' => 'Gefactureerd',
'logged' => 'Logged', 'logged' => 'Gelogd',
'running' => 'Running', 'running' => 'Lopend',
'task_error_multiple_clients' => 'The tasks can\'t belong to different clients', 'task_error_multiple_clients' => 'Taken kunnen niet tot meerdere klanten behoren',
'task_error_running' => 'Please stop running tasks first', 'task_error_running' => 'Stop aub de lopende taken eerst',
'task_error_invoiced' => 'Tasks have already been invoiced', 'task_error_invoiced' => 'Deze taken zijn al gefactureerd',
'restored_task' => 'Successfully restored task', 'restored_task' => 'Taak succesvol hersteld',
'archived_task' => 'Successfully archived task', 'archived_task' => 'Taak succesvol gearchiveerd',
'archived_tasks' => 'Successfully archived :count tasks', 'archived_tasks' => ':count taken succesvol gearchiveerd',
'deleted_task' => 'Successfully deleted task', 'deleted_task' => 'Taak succesvol verwijderd',
'deleted_tasks' => 'Successfully deleted :count tasks', 'deleted_tasks' => ':count taken succesvol verwijderd',
'create_task' => 'Create Task', 'create_task' => 'Taak aanmaken',
'stopped_task' => 'Successfully stopped task', 'stopped_task' => 'Taak succesvol gestopt',
'invoice_task' => 'Invoice Task', 'invoice_task' => 'Factuur taak',
'invoice_labels' => 'Invoice Labels', 'invoice_labels' => 'Factuur labels',
'prefix' => 'Prefix', 'prefix' => 'Voorvoegsel',
'counter' => 'Counter', 'counter' => 'Teller',
'payment_type_dwolla' => 'Dwolla', 'payment_type_dwolla' => 'Dwolla',
'gateway_help_43' => ':link to sign up for Dwolla.', 'gateway_help_43' => ':link om in te schrijven voor Dwolla.',
'partial_value' => 'Must be greater than zero and less than the total', 'partial_value' => 'Moet groter zijn dan nul en minder dan het totaal',
'more_actions' => 'More Actions', 'more_actions' => 'Meer acties',
'pro_plan_title' => 'NINJA PRO', 'pro_plan_title' => 'NINJA PRO',
'pro_plan_call_to_action' => 'Upgrade Now!', 'pro_plan_call_to_action' => 'Nu upgraden!',
'pro_plan_feature1' => 'Create Unlimited Clients', 'pro_plan_feature1' => 'Maak ongelimiteerd klanten aan',
'pro_plan_feature2' => 'Access to 10 Beautiful Invoice Designs', 'pro_plan_feature2' => 'Toegang tot 10 mooie factuur ontwerpen',
'pro_plan_feature3' => 'Custom URLs - "YourBrand.InvoiceNinja.com"', 'pro_plan_feature3' => 'Aangepaste URLs - "YourBrand.InvoiceNinja.com"',
'pro_plan_feature4' => 'Remove "Created by Invoice Ninja"', 'pro_plan_feature4' => 'Verwijder "Aangemaakt door Invoice Ninja"',
'pro_plan_feature5' => 'Multi-user Access & Activity Tracking', 'pro_plan_feature5' => 'Multi-user toegang & Activeit Tracking',
'pro_plan_feature6' => 'Create Quotes & Pro-forma Invoices', 'pro_plan_feature6' => 'Maak offertes & Pro-forma facturen aan',
'pro_plan_feature7' => 'Customize Invoice Field Titles & Numbering', 'pro_plan_feature7' => 'Pas factuur veld titels & nummering aan',
'pro_plan_feature8' => 'Option to Attach PDFs to Client Emails', 'pro_plan_feature8' => 'Optie om PDFs toe te voegen aan de emails naar klanten',
'resume' => 'Resume', 'resume' => 'Doorgaan',
'break_duration' => 'Break', 'break_duration' => 'Pauze',
'edit_details' => 'Edit Details', 'edit_details' => 'Details aanpassen',
'work' => 'Work', 'work' => 'Werk',
'timezone_unset' => 'Please :link to set your timezone', 'timezone_unset' => ':link om je tijdszone aan te passen',
'click_here' => 'click here', 'click_here' => 'Klik hier',
'email_receipt' => 'Email payment receipt to the client', 'email_receipt' => 'Mail betalingsbewijs naar de klant',
'created_payment_emailed_client' => 'Successfully created payment and emailed client', 'created_payment_emailed_client' => 'Betaling succesvol toegevoegd en gemaild naar de klant',
'add_account' => 'Add Account', 'add_company' => 'Bedrijf toevoegen',
'untitled' => 'Untitled', 'untitled' => 'Zonder titel',
'new_account' => 'New Account', 'new_company' => 'Nieuw bedrijf',
'associated_accounts' => 'Successfully linked accounts', 'associated_accounts' => 'Accounts succesvol gekoppeld',
'unlinked_account' => 'Successfully unlinked accounts', 'unlinked_account' => 'Accounts succesvol losgekoppeld',
'login' => 'Login', 'login' => 'Login',
'or' => 'or', 'or' => 'of',
'email_error' => 'Er was een probleem om de email te verzenden',
'confirm_recurring_timing' => 'Opmerking: emails worden aan het begin van het uur verzonden.',
'old_browser' => 'Gebruik aub een <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">nieuwere browser</a>',
'payment_terms_help' => 'Stel de standaard factuur vervaldatum in',
'unlink_account' => 'Koppel account los',
'unlink' => 'Koppel los',
'show_address' => 'Toon Adres',
'show_address_help' => 'Verplicht de klant om zijn factuur adres op te geven',
'update_address' => 'Adres aanpassen',
'update_address_help' => 'Pas het adres van de klant aan met de ingevulde gegevens',
'times' => 'Tijden',
'set_now' => 'Start nu',
'dark_mode' => 'Donkere modus',
'dark_mode_help' => 'Toon witte tekst op een donkere achtergrond',
'add_to_invoice' => 'Toevoegen aan factuur :invoice',
'create_new_invoice' => 'Maak een nieuwe factuur',
'task_errors' => 'Pas overlappende tijden aan aub.',
'from' => 'Van',
'to' => 'Aan',
'font_size' => 'Tekstgrootte',
'primary_color' => 'Primaire kleur',
'secondary_color' => 'Secundaire kleur',
'customize_design' => 'Pas design aan',
'content' => 'Inhoud',
'styles' => 'Stijlen',
'defaults' => 'Standaardwaarden',
'margins' => 'Marges',
'header' => 'Header',
'footer' => 'Footer',
'custom' => 'Aangepast',
'invoice_to' => 'Factuur aan',
'invoice_no' => 'Factuur Nr.',
'recent_payments' => 'Recente betalingen',
'outstanding' => 'Uitstaand',
'manage_companies' => 'Beheer bedrijven',
'total_revenue' => 'Totale opbrengst',
'current_user' => 'Huidige gebruiker',
'new_recurring_invoice' => 'Nieuwe wederkerende factuur',
'recurring_invoice' => 'Wederkerende factuur',
'recurring_too_soon' => 'Het is te vroeg om de volgende wederkerende factuur aan te maken',
'created_by_invoice' => 'Aangemaakt door :invoice',
'primary_user' => 'Primaire gebruiker',
'help' => 'Help',
'customize_help' => '<p>We gebruiken <a href="http://pdfmake.org/" target="_blank">pdfmake</a> om de factuur ontwerpen declaratief te definieren. De pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> is een interessante manier om de library in actie te zien.</p>
<p>Je kan elk factuur veld gebruiken door <code>Veld</code> toe te voegen op het einde. Bijvoorbeeld <code>$invoiceNumberValue</code> toont de factuur nummer.</p>
<p>Gebruik dot notatie om een "kind eigenschap" te gebruiken. Bijvoorbeeld voor de klant naam te tonen gebruik je <code>$client.nameValue</code>.</p>
<p>Als je ergens hulp bij nodig hebt, post dan een vraag op ons <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>'
); );

View File

@ -73,8 +73,8 @@ return array(
"positive" => ":attribute moet groter zijn dan nul.", "positive" => ":attribute moet groter zijn dan nul.",
"has_credit" => "De klant heeft niet voldoende krediet.", "has_credit" => "De klant heeft niet voldoende krediet.",
"notmasked" => "The values are masked", "notmasked" => "De waarden zijn verborgen",
"less_than" => 'The :attribute must be less than :value', "less_than" => 'Het :attribute moet minder zijn dan :value',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -593,7 +593,6 @@ return array(
'less_fields' => 'Less Fields', 'less_fields' => 'Less Fields',
'client_name' => 'Client Name', 'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings', 'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings', 'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap', 'auto_wrap' => 'Auto Line Wrap',
'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.', 'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.',
@ -692,13 +691,64 @@ return array(
'email_receipt' => 'Email payment receipt to the client', 'email_receipt' => 'Email payment receipt to the client',
'created_payment_emailed_client' => 'Successfully created payment and emailed client', 'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account', 'add_company' => 'Add Company',
'untitled' => 'Untitled', 'untitled' => 'Untitled',
'new_account' => 'New Account', 'new_company' => 'New Company',
'associated_accounts' => 'Successfully linked accounts', 'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts', 'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login', 'login' => 'Login',
'or' => 'or', 'or' => 'or',
'email_error' => 'There was a problem sending the email',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
'from' => 'From',
'to' => 'To',
'font_size' => 'Font Size',
'primary_color' => 'Primary Color',
'secondary_color' => 'Secondary Color',
'customize_design' => 'Customize Design',
'content' => 'Content',
'styles' => 'Styles',
'defaults' => 'Defaults',
'margins' => 'Margins',
'header' => 'Header',
'footer' => 'Footer',
'custom' => 'Custom',
'invoice_to' => 'Invoice to',
'invoice_no' => 'Invoice No.',
'recent_payments' => 'Recent Payments',
'outstanding' => 'Outstanding',
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
'primary_user' => 'Primary User',
'help' => 'Help',
'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p>
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>'
); );

View File

@ -596,7 +596,6 @@ return array(
'less_fields' => 'Less Fields', 'less_fields' => 'Less Fields',
'client_name' => 'Client Name', 'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings', 'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings', 'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap', 'auto_wrap' => 'Auto Line Wrap',
'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.', 'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.',
@ -695,14 +694,65 @@ return array(
'email_receipt' => 'Email payment receipt to the client', 'email_receipt' => 'Email payment receipt to the client',
'created_payment_emailed_client' => 'Successfully created payment and emailed client', 'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account', 'add_company' => 'Add Company',
'untitled' => 'Untitled', 'untitled' => 'Untitled',
'new_account' => 'New Account', 'new_company' => 'New Company',
'associated_accounts' => 'Successfully linked accounts', 'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts', 'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login', 'login' => 'Login',
'or' => 'or', 'or' => 'or',
'email_error' => 'There was a problem sending the email',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
'from' => 'From',
'to' => 'To',
'font_size' => 'Font Size',
'primary_color' => 'Primary Color',
'secondary_color' => 'Secondary Color',
'customize_design' => 'Customize Design',
'content' => 'Content',
'styles' => 'Styles',
'defaults' => 'Defaults',
'margins' => 'Margins',
'header' => 'Header',
'footer' => 'Footer',
'custom' => 'Custom',
'invoice_to' => 'Invoice to',
'invoice_no' => 'Invoice No.',
'recent_payments' => 'Recent Payments',
'outstanding' => 'Outstanding',
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
'primary_user' => 'Primary User',
'help' => 'Help',
'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p>
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>'
); );

View File

@ -14,9 +14,12 @@
<div class="panel-body"> <div class="panel-body">
@if ($accountGateway) @if ($accountGateway)
{!! Former::populateField('payment_type_id', $paymentTypeId) !!}
{!! Former::populateField('gateway_id', $accountGateway->gateway_id) !!} {!! Former::populateField('gateway_id', $accountGateway->gateway_id) !!}
{!! Former::populateField('recommendedGateway_id', $accountGateway->gateway_id) !!} {!! Former::populateField('payment_type_id', $paymentTypeId) !!}
{!! Former::populateField('recommendedGateway_id', $accountGateway->gateway_id) !!}
{!! Former::populateField('show_address', intval($accountGateway->show_address)) !!}
{!! Former::populateField('update_address', intval($accountGateway->update_address)) !!}
@if ($config) @if ($config)
@foreach ($accountGateway->fields as $field => $junk) @foreach ($accountGateway->fields as $field => $junk)
@if (in_array($field, $hiddenFields)) @if (in_array($field, $hiddenFields))
@ -28,6 +31,8 @@
@endif @endif
@else @else
{!! Former::populateField('gateway_id', GATEWAY_STRIPE) !!} {!! Former::populateField('gateway_id', GATEWAY_STRIPE) !!}
{!! Former::populateField('show_address', 1) !!}
{!! Former::populateField('update_address', 1) !!}
@endif @endif
{!! Former::select('payment_type_id') {!! Former::select('payment_type_id')
@ -77,6 +82,15 @@
@endforeach @endforeach
{!! Former::checkbox('show_address')
->label(trans('texts.billing_address'))
->text(trans('texts.show_address_help'))
->addGroupClass('gateway-option') !!}
{!! Former::checkbox('update_address')
->label(' ')
->text(trans('texts.update_address_help'))
->addGroupClass('gateway-option') !!}
{!! Former::checkboxes('creditCardTypes[]') {!! Former::checkboxes('creditCardTypes[]')
->label('Accepted Credit Cards') ->label('Accepted Credit Cards')
->checkboxes($creditCardTypes) ->checkboxes($creditCardTypes)
@ -131,11 +145,25 @@
} }
} }
function enableUpdateAddress(event) {
var disabled = !$('#show_address').is(':checked');
$('#update_address').prop('disabled', disabled);
$('label[for=update_address]').css('color', disabled ? '#888' : '#000');
if (disabled) {
$('#update_address').prop('checked', false);
} else if (event) {
$('#update_address').prop('checked', true);
}
}
$(function() { $(function() {
setPaymentType(); setPaymentType();
@if ($accountGateway) @if ($accountGateway)
$('.payment-type-option').hide(); $('.payment-type-option').hide();
@endif @endif
$('#show_address').change(enableUpdateAddress);
enableUpdateAddress();
}) })
</script> </script>

View File

@ -0,0 +1,211 @@
@extends('accounts.nav')
@section('head')
@parent
<script src="{{ asset('js/pdf_viewer.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/compatibility.js') }}" type="text/javascript"></script>
<link href="{{ asset('css/jsoneditor.min.css') }}" rel="stylesheet" type="text/css">
<script src="{{ asset('js/jsoneditor.min.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/pdfmake.min.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/vfs_fonts.js') }}" type="text/javascript"></script>
<style type="text/css">
select.form-control {
background: #FFFFFF !important;
margin-right: 12px;
}
table {
background: #FFFFFF !important;
}
</style>
@stop
@section('content')
@parent
@include('accounts.nav_advanced')
<script>
var invoiceDesigns = {!! $invoiceDesigns !!};
var invoice = {!! json_encode($invoice) !!};
var sections = ['content', 'styles', 'defaultStyle', 'pageMargins', 'header', 'footer'];
var customDesign = origCustomDesign = {!! $customDesign ?: 'JSON.parse(invoiceDesigns[0].javascript);' !!};
function getPDFString(cb, force) {
invoice.is_pro = {!! Auth::user()->isPro() ? 'true' : 'false' !!};
invoice.account.hide_quantity = {!! Auth::user()->account->hide_quantity ? 'true' : 'false' !!};
invoice.account.hide_paid_to_date = {!! Auth::user()->account->hide_paid_to_date ? 'true' : 'false' !!};
invoice.invoice_design_id = {!! Auth::user()->account->invoice_design_id !!};
NINJA.primaryColor = '{!! Auth::user()->account->primary_color !!}';
NINJA.secondaryColor = '{!! Auth::user()->account->secondary_color !!}';
NINJA.fontSize = {!! Auth::user()->account->font_size !!};
generatePDF(invoice, getDesignJavascript(), force, cb);
}
function getDesignJavascript() {
var id = $('#invoice_design_id').val();
if (id == '-1') {
showMoreDesigns();
$('#invoice_design_id').val(1);
return invoiceDesigns[0].javascript;
} else if (customDesign) {
return JSON.stringify(customDesign);
} else {
return invoiceDesigns[0].javascript;
}
}
function loadEditor(section)
{
editorSection = section;
editor.set(customDesign[section]);
// the function throws an error if the editor is in code view
try {
editor.expandAll();
} catch(err) {}
}
function saveEditor(data)
{
setTimeout(function() {
customDesign[editorSection] = editor.get();
refreshPDF();
}, 100)
}
function onSelectChange()
{
var id = $('#invoice_design_id').val();
if (parseInt(id)) {
var design = _.find(invoiceDesigns, function(design){ return design.id == id});
customDesign = JSON.parse(design.javascript);
} else {
customDesign = origCustomDesign;
}
loadEditor(editorSection);
refreshPDF(true);
}
function submitForm()
{
$('#custom_design').val(JSON.stringify(customDesign));
$('form.warn-on-exit').submit();
}
$(function() {
refreshPDF(true);
var container = document.getElementById("jsoneditor");
var options = {
mode: 'form',
modes: ['form', 'code'],
change: function() {
saveEditor();
}
};
window.editor = new JSONEditor(container, options);
loadEditor('content');
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var target = $(e.target).attr("href") // activated tab
target = target.substring(1); // strip leading #
loadEditor(target);
});
});
</script>
<div class="row">
<div class="col-md-6">
{!! Former::open()->addClass('warn-on-exit') !!}
{!! Former::populateField('invoice_design_id', $account->invoice_design_id) !!}
<div style="display:none">
{!! Former::text('custom_design') !!}
</div>
<div role="tabpanel">
<ul class="nav nav-tabs" role="tablist" style="border: none">
<li role="presentation" class="active"><a href="#content" aria-controls="content" role="tab" data-toggle="tab">{{ trans('texts.content') }}</a></li>
<li role="presentation"><a href="#styles" aria-controls="styles" role="tab" data-toggle="tab">{{ trans('texts.styles') }}</a></li>
<li role="presentation"><a href="#defaultStyle" aria-controls="defaultStyle" role="tab" data-toggle="tab">{{ trans('texts.defaults') }}</a></li>
<li role="presentation"><a href="#pageMargins" aria-controls="margins" role="tab" data-toggle="tab">{{ trans('texts.margins') }}</a></li>
<li role="presentation"><a href="#header" aria-controls="header" role="tab" data-toggle="tab">{{ trans('texts.header') }}</a></li>
<li role="presentation"><a href="#footer" aria-controls="footer" role="tab" data-toggle="tab">{{ trans('texts.footer') }}</a></li>
</ul>
</div>
<div id="jsoneditor" style="width: 550px; height: 743px;"></div>
<p>&nbsp;</p>
<div>
{!! Former::select('invoice_design_id')->style('display:inline;width:120px')->fromQuery($invoiceDesigns, 'name', 'id')->onchange('onSelectChange()')->raw() !!}
<div class="pull-right">
{!! Button::normal(trans('texts.help'))->withAttributes(['onclick' => 'showHelp()'])->appendIcon(Icon::create('question-sign')) !!}
{!! Button::normal(trans('texts.cancel'))->asLinkTo(URL::to('/company/advanced_settings/invoice_design'))->appendIcon(Icon::create('remove-circle')) !!}
@if (Auth::user()->isPro())
{!! Button::success(trans('texts.save'))->withAttributes(['onclick' => 'submitForm()'])->appendIcon(Icon::create('floppy-disk')) !!}
@endif
</div>
</div>
<script>
@if (!Auth::user()->isPro())
$(function() {
$('form.warn-on-exit input').prop('disabled', true);
});
@endif
function showHelp() {
$('#helpModal').modal('show');
}
</script>
{!! Former::close() !!}
<div class="modal fade" id="helpModal" tabindex="-1" role="dialog" aria-labelledby="helpModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="helpModalLabel">{{ trans('texts.help') }}</h4>
</div>
<div class="panel-body" style="background-color: #fff">
{!! trans('texts.customize_help') !!}
</div>
<div class="modal-footer" style="margin-top: 0px">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.close') }}</button>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
@include('invoices.pdf', ['account' => Auth::user()->account, 'pdfHeight' => 800])
</div>
</div>
@stop

View File

@ -18,10 +18,13 @@
{{ Former::populate($account) }} {{ Former::populate($account) }}
@if ($showUser) @if ($showUser)
{{ Former::populateField('first_name', $account->users()->first()->first_name) }} {{ Former::populateField('first_name', $primaryUser->first_name) }}
{{ Former::populateField('last_name', $account->users()->first()->last_name) }} {{ Former::populateField('last_name', $primaryUser->last_name) }}
{{ Former::populateField('email', $account->users()->first()->email) }} {{ Former::populateField('email', $primaryUser->email) }}
{{ Former::populateField('phone', $account->users()->first()->phone) }} {{ Former::populateField('phone', $primaryUser->phone) }}
@if (Utils::isNinjaDev())
{{ Former::populateField('dark_mode', intval($primaryUser->dark_mode)) }}
@endif
@endif @endif
<div class="row"> <div class="row">
@ -48,7 +51,7 @@
@if (file_exists($account->getLogoPath())) @if (file_exists($account->getLogoPath()))
<center> <center>
{!! HTML::image($account->getLogoPath().'?no_cache='.time(), "Logo") !!} &nbsp; {!! HTML::image($account->getLogoPath().'?no_cache='.time(), 'Logo', ['width' => 200]) !!} &nbsp;
<a href="#" onclick="deleteLogo()">{{ trans('texts.remove_logo') }}</a> <a href="#" onclick="deleteLogo()">{{ trans('texts.remove_logo') }}</a>
</center><br/> </center><br/>
@endif @endif
@ -81,13 +84,17 @@
@if ($showUser) @if ($showUser)
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.users') !!}</h3> <h3 class="panel-title">{!! trans('texts.primary_user') !!}</h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
{!! Former::text('first_name') !!} {!! Former::text('first_name') !!}
{!! Former::text('last_name') !!} {!! Former::text('last_name') !!}
{!! Former::text('email') !!} {!! Former::text('email') !!}
{!! Former::text('phone') !!} {!! Former::text('phone') !!}
@if (Utils::isNinjaDev())
{!! Former::checkbox('dark_mode')->text(trans('texts.dark_mode_help')) !!}
@endif
@if (Auth::user()->confirmed) @if (Auth::user()->confirmed)
{!! Former::actions( Button::primary(trans('texts.change_password'))->small()->withAttributes(['onclick'=>'showChangePassword()'])) !!} {!! Former::actions( Button::primary(trans('texts.change_password'))->small()->withAttributes(['onclick'=>'showChangePassword()'])) !!}
@elseif (Auth::user()->registered) @elseif (Auth::user()->registered)

View File

@ -108,7 +108,7 @@
} }
keys = ['footer', 'account', 'client', 'amount', 'link', 'contact']; keys = ['footer', 'account', 'client', 'amount', 'link', 'contact'];
vals = [{!! json_encode($emailFooter) !!}, '{!! Auth::user()->account->getDisplayName() !!}', 'Client Name', formatMoney(100), '{!! NINJA_WEB_URL !!}', 'Contact Name']; vals = [{!! json_encode($emailFooter) !!}, '{!! Auth::user()->account->getDisplayName() !!}', 'Client Name', formatMoney(100), '{!! SITE_URL . '/view/...' !!}', 'Contact Name'];
// Add any available payment method links // Add any available payment method links
@foreach (\App\Models\Gateway::getPaymentTypeLinks() as $type) @foreach (\App\Models\Gateway::getPaymentTypeLinks() as $type)

View File

@ -3,13 +3,10 @@
@section('head') @section('head')
@parent @parent
<script src="{!! asset('js/pdf_viewer.js') !!}" type="text/javascript"></script> <script src="{{ asset('js/pdf_viewer.js') }}" type="text/javascript"></script>
<script src="{!! asset('js/compatibility.js') !!}" type="text/javascript"></script> <script src="{{ asset('js/compatibility.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/pdfmake.min.js') }}" type="text/javascript"></script>
@if (Auth::user()->account->utf8_invoices) <script src="{{ asset('js/vfs_fonts.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/pdfmake.min.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/vfs_fonts.js') }}" type="text/javascript"></script>
@endif
@stop @stop
@ -27,8 +24,9 @@
showMoreDesigns(); showMoreDesigns();
$('#invoice_design_id').val(1); $('#invoice_design_id').val(1);
return invoiceDesigns[0].javascript; return invoiceDesigns[0].javascript;
} else { } else {
return invoiceDesigns[id-1].javascript; var design = _.find(invoiceDesigns, function(design){ return design.id == id});
return design ? design.javascript : '';
} }
} }
@ -55,8 +53,7 @@
} }
} }
doc = generatePDF(invoice, getDesignJavascript(), true); generatePDF(invoice, getDesignJavascript(), true, cb);
doc.getDataUrl(cb);
} }
$(function() { $(function() {
@ -96,19 +93,20 @@
<div class="panel-body"> <div class="panel-body">
@if (!Utils::isPro() || \App\Models\InvoiceDesign::count() == COUNT_FREE_DESIGNS) @if (!Utils::isPro() || \App\Models\InvoiceDesign::count() == COUNT_FREE_DESIGNS_SELF_HOST)
{!! Former::select('invoice_design_id')->style('display:inline;width:120px')->fromQuery($invoiceDesigns, 'name', 'id')->addOption(trans('texts.more_designs') . '...', '-1') !!} {!! Former::select('invoice_design_id')->style('display:inline;width:120px')->fromQuery($invoiceDesigns, 'name', 'id')->addOption(trans('texts.more_designs') . '...', '-1') !!}
@else @else
{!! Former::select('invoice_design_id')->style('display:inline;width:120px')->fromQuery($invoiceDesigns, 'name', 'id') !!} {!! Former::select('invoice_design_id')->style('display:inline;width:120px')->fromQuery($invoiceDesigns, 'name', 'id') !!}
@endif @endif
@if (Auth::user()->account->utf8_invoices) {!! Former::text('font_size')->type('number')->min('0')->step('1')->style('width:120px') !!}
{!! Former::text('font_size')->type('number')->min('0')->step('1')->style('width:120px') !!}
@endif
{!! Former::text('primary_color') !!} {!! Former::text('primary_color') !!}
{!! Former::text('secondary_color') !!} {!! Former::text('secondary_color') !!}
{!! Former::actions(
Button::primary(trans('texts.customize_design'))->small()->asLinkTo(URL::to('/company/advanced_settings/customize_design'))
) !!}
</div> </div>
</div> </div>

View File

@ -23,8 +23,6 @@
{{ Former::populateField('custom_invoice_taxes2', intval($account->custom_invoice_taxes2)) }} {{ Former::populateField('custom_invoice_taxes2', intval($account->custom_invoice_taxes2)) }}
{{ Former::populateField('share_counter', intval($account->share_counter)) }} {{ Former::populateField('share_counter', intval($account->share_counter)) }}
{{ Former::populateField('pdf_email_attachment', intval($account->pdf_email_attachment)) }} {{ Former::populateField('pdf_email_attachment', intval($account->pdf_email_attachment)) }}
{{ Former::populateField('utf8_invoices', intval($account->utf8_invoices)) }}
{{ Former::populateField('auto_wrap', intval($account->auto_wrap)) }}
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
@ -98,10 +96,6 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
{!! Former::checkbox('pdf_email_attachment')->text(trans('texts.enable')) !!} {!! Former::checkbox('pdf_email_attachment')->text(trans('texts.enable')) !!}
{!! Former::checkbox('utf8_invoices')->text(trans('texts.enable')) !!}
<div style="display:none">
{!! Former::checkbox('auto_wrap')->text(trans('texts.enable')) !!}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -8,7 +8,7 @@
{!! HTML::nav_link('company/products', 'product_library') !!} {!! HTML::nav_link('company/products', 'product_library') !!}
{!! HTML::nav_link('company/notifications', 'notifications') !!} {!! HTML::nav_link('company/notifications', 'notifications') !!}
{!! HTML::nav_link('company/import_export', 'import_export', 'company/import_map') !!} {!! HTML::nav_link('company/import_export', 'import_export', 'company/import_map') !!}
{!! HTML::nav_link('company/advanced_settings/invoice_settings', 'advanced_settings', '*/advanced_settings/*') !!} {!! HTML::nav_link('company/advanced_settings/invoice_design', 'advanced_settings', '*/advanced_settings/*') !!}
</ul> </ul>
<br/> <br/>

View File

@ -1,6 +1,6 @@
<ul class="nav nav-tabs nav nav-justified"> <ul class="nav nav-tabs nav nav-justified">
{!! HTML::nav_link('company/advanced_settings/invoice_settings', 'invoice_settings') !!}
{!! HTML::nav_link('company/advanced_settings/invoice_design', 'invoice_design') !!} {!! HTML::nav_link('company/advanced_settings/invoice_design', 'invoice_design') !!}
{!! HTML::nav_link('company/advanced_settings/invoice_settings', 'invoice_settings') !!}
{!! HTML::nav_link('company/advanced_settings/email_templates', 'email_templates') !!} {!! HTML::nav_link('company/advanced_settings/email_templates', 'email_templates') !!}
{!! HTML::nav_link('company/advanced_settings/charts_and_reports', 'charts_and_reports') !!} {!! HTML::nav_link('company/advanced_settings/charts_and_reports', 'charts_and_reports') !!}
{!! HTML::nav_link('company/advanced_settings/user_management', 'users_and_tokens') !!} {!! HTML::nav_link('company/advanced_settings/user_management', 'users_and_tokens') !!}

View File

@ -20,7 +20,7 @@
@endif @endif
{!! Former::text('product_key')->label('texts.product') !!} {!! Former::text('product_key')->label('texts.product') !!}
{!! Former::textarea('notes')->data_bind("value: wrapped_notes, valueUpdate: 'afterkeydown'") !!} {!! Former::textarea('notes') !!}
{!! Former::text('cost') !!} {!! Former::text('cost') !!}
</div> </div>
@ -35,29 +35,6 @@
<script type="text/javascript"> <script type="text/javascript">
function ViewModel(data) {
var self = this;
@if ($product)
self.notes = ko.observable(wordWrapText('{{ str_replace(["\r\n","\r","\n"], '\n', addslashes($product->notes)) }}', 300));
@else
self.notes = ko.observable('');
@endif
self.wrapped_notes = ko.computed({
read: function() {
return self.notes();
},
write: function(value) {
value = wordWrapText(value, 235);
self.notes(value);
},
owner: this
});
}
window.model = new ViewModel();
ko.applyBindings(model);
$(function() { $(function() {
$('#product_key').focus(); $('#product_key').focus();
}); });

View File

@ -13,7 +13,7 @@
<div class="pull-right"> <div class="pull-right">
{!! Button::normal(trans('texts.documentation'))->asLinkTo(NINJA_WEB_URL.'/knowledgebase/api-documentation/')->withAttributes(['target' => '_blank']) !!} {!! Button::normal(trans('texts.documentation'))->asLinkTo(NINJA_WEB_URL.'/knowledgebase/api-documentation/')->withAttributes(['target' => '_blank'])->appendIcon(Icon::create('info-sign')) !!}
@if (Utils::isNinja()) @if (Utils::isNinja())
{!! Button::normal(trans('texts.zapier'))->asLinkTo(ZAPIER_URL)->withAttributes(['target' => '_blank']) !!} {!! Button::normal(trans('texts.zapier'))->asLinkTo(ZAPIER_URL)->withAttributes(['target' => '_blank']) !!}
@endif @endif

View File

@ -79,11 +79,11 @@
{!! Former::hidden('remember')->raw() !!} {!! Former::hidden('remember')->raw() !!}
</p> </p>
<p>{!! Button::success(trans(Input::get('new_account') && Utils::allowNewAccounts() ? 'texts.login' : 'texts.lets_go'))->large()->submit()->block() !!}</p> <p>{!! Button::success(trans(Input::get('new_company') ? 'texts.login' : 'texts.lets_go'))->large()->submit()->block() !!}</p>
@if (Input::get('new_account') && Utils::allowNewAccounts()) @if (Input::get('new_company') && Utils::allowNewAccounts())
<center><p>- {{ trans('texts.or') }} -</p></center> <center><p>- {{ trans('texts.or') }} -</p></center>
<p>{!! Button::primary(trans('texts.new_account'))->asLinkTo(URL::to('/invoice_now?new_account=true'))->large()->submit()->block() !!}</p> <p>{!! Button::primary(trans('texts.new_company'))->asLinkTo(URL::to('/invoice_now?new_company=true&sign_up=true'))->large()->submit()->block() !!}</p>
@endif @endif

View File

@ -6,7 +6,10 @@
<div class="col-md-4"> <div class="col-md-4">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-body"> <div class="panel-body">
<img src="{{ asset('images/totalincome.png') }}" class="in-image"/> <img src="{{ asset('images/totalinvoices.png') }}" class="in-image"/>
<div class="in-thin">
{{ trans('texts.total_revenue') }}
</div>
<div class="in-bold"> <div class="in-bold">
@if (count($paidToDate)) @if (count($paidToDate))
@foreach ($paidToDate as $item) @foreach ($paidToDate as $item)
@ -16,9 +19,6 @@
{{ Utils::formatMoney(0) }} {{ Utils::formatMoney(0) }}
@endif @endif
</div> </div>
<div class="in-thin">
{{ trans('texts.in_total_revenue') }}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -44,12 +44,18 @@
<div class="col-md-4"> <div class="col-md-4">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-body"> <div class="panel-body">
<img src="{{ asset('images/totalinvoices.png') }}" class="in-image"/> <img src="{{ asset('images/totalincome.png') }}" class="in-image"/>
<div class="in-bold">
{{ $invoicesSent }}
</div>
<div class="in-thin"> <div class="in-thin">
{{ Utils::pluralize('invoice', $invoicesSent) }} {{ trans('texts.sent') }} {{ trans('texts.outstanding') }}
</div>
<div class="in-bold">
@if (count($balances))
@foreach ($balances as $item)
{{ Utils::formatMoney($item->value, $item->currency_id) }}<br/>
@endforeach
@else
{{ Utils::formatMoney(0) }}
@endif
</div> </div>
</div> </div>
</div> </div>
@ -61,13 +67,16 @@
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div class="panel panel-default dashboard" style="min-height:660px"> <div class="panel panel-default dashboard" style="height:320px">
<div class="panel-heading" style="background-color:#0b4d78 !important"> <div class="panel-heading" style="background-color:#0b4d78 !important">
<h3 class="panel-title in-bold-white"> <h3 class="panel-title in-bold-white">
<i class="glyphicon glyphicon-exclamation-sign"></i> {{ trans('texts.notifications') }} <i class="glyphicon glyphicon-exclamation-sign"></i> {{ trans('texts.notifications') }}
<div class="pull-right" style="font-size:14px;padding-top:4px">
{{ $invoicesSent }} {{ Utils::pluralize('invoice', $invoicesSent) }} {{ trans('texts.sent') }}
</div>
</h3> </h3>
</div> </div>
<ul class="panel-body list-group"> <ul class="panel-body list-group" style="height:276px;overflow-y:auto;">
@foreach ($activities as $activity) @foreach ($activities as $activity)
<li class="list-group-item"> <li class="list-group-item">
<span style="color:#888;font-style:italic">{{ Utils::timestampToDateString(strtotime($activity->created_at)) }}:</span> <span style="color:#888;font-style:italic">{{ Utils::timestampToDateString(strtotime($activity->created_at)) }}:</span>
@ -76,15 +85,43 @@
@endforeach @endforeach
</ul> </ul>
</div> </div>
<div class="panel panel-default dashboard" style="height:320px;">
<div class="panel-heading" style="margin:0; background-color: #f5f5f5 !important;">
<h3 class="panel-title" style="color: black !important">
<i class="glyphicon glyphicon-ok-sign"></i> {{ trans('texts.recent_payments') }}
</h3>
</div>
<div class="panel-body" style="height:274px;overflow-y:auto;">
<table class="table table-striped">
<thead>
<th>{{ trans('texts.invoice_number_short') }}</th>
<th>{{ trans('texts.client') }}</th>
<th>{{ trans('texts.payment_date') }}</th>
<th>{{ trans('texts.amount') }}</th>
</thead>
<tbody>
@foreach ($payments as $payment)
<tr>
<td>{!! \App\Models\Invoice::calcLink($payment) !!}</td>
<td>{!! link_to('/clients/'.$payment->client_public_id, trim($payment->client_name) ?: (trim($payment->first_name . ' ' . $payment->last_name) ?: $payment->email)) !!}</td>
<td>{{ Utils::fromSqlDate($payment->payment_date) }}</td>
<td>{{ Utils::formatMoney($payment->amount, $payment->currency_id ?: ($account->currency_id ?: DEFAULT_CURRENCY)) }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="panel panel-default dashboard" style="min-height:320px"> <div class="panel panel-default dashboard" style="height:320px">
<div class="panel-heading" style="background-color:#e37329 !important"> <div class="panel-heading" style="background-color:#e37329 !important">
<h3 class="panel-title in-bold-white"> <h3 class="panel-title in-bold-white">
<i class="glyphicon glyphicon-time"></i> {{ trans('texts.invoices_past_due') }} <i class="glyphicon glyphicon-time"></i> {{ trans('texts.invoices_past_due') }}
</h3> </h3>
</div> </div>
<div class="panel-body"> <div class="panel-body" style="height:274px;overflow-y:auto;">
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<th>{{ trans('texts.invoice_number_short') }}</th> <th>{{ trans('texts.invoice_number_short') }}</th>
@ -95,23 +132,23 @@
<tbody> <tbody>
@foreach ($pastDue as $invoice) @foreach ($pastDue as $invoice)
<tr> <tr>
<td>{!! $invoice->getLink() !!}</td> <td>{!! \App\Models\Invoice::calcLink($invoice) !!}</td>
<td>{{ $invoice->client->getDisplayName() }}</td> <td>{!! link_to('/clients/'.$invoice->client_public_id, trim($invoice->client_name) ?: (trim($invoice->first_name . ' ' . $invoice->last_name) ?: $invoice->email)) !!}</td>
<td>{{ Utils::fromSqlDate($invoice->due_date) }}</td> <td>{{ Utils::fromSqlDate($invoice->due_date) }}</td>
<td>{{ Utils::formatMoney($invoice->balance, $invoice->client->getCurrencyId()) }}</td> <td>{{ Utils::formatMoney($invoice->balance, $invoice->currency_id ?: ($account->currency_id ?: DEFAULT_CURRENCY)) }}</td>
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<div class="panel panel-default dashboard" style="min-height:320px;"> <div class="panel panel-default dashboard" style="height:320px;">
<div class="panel-heading" style="margin:0; background-color: #f5f5f5 !important;"> <div class="panel-heading" style="margin:0; background-color: #f5f5f5 !important;">
<h3 class="panel-title" style="color: black !important"> <h3 class="panel-title" style="color: black !important">
<i class="glyphicon glyphicon-time"></i> {{ trans('texts.upcoming_invoices') }} <i class="glyphicon glyphicon-time"></i> {{ trans('texts.upcoming_invoices') }}
</h3> </h3>
</div> </div>
<div class="panel-body"> <div class="panel-body" style="height:274px;overflow-y:auto;">
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<th>{{ trans('texts.invoice_number_short') }}</th> <th>{{ trans('texts.invoice_number_short') }}</th>
@ -122,10 +159,10 @@
<tbody> <tbody>
@foreach ($upcoming as $invoice) @foreach ($upcoming as $invoice)
<tr> <tr>
<td>{!! $invoice->getLink() !!}</td> <td>{!! \App\Models\Invoice::calcLink($invoice) !!}</td>
<td>{{ $invoice->client->getDisplayName() }}</td> <td>{!! link_to('/clients/'.$invoice->client_public_id, trim($invoice->client_name) ?: (trim($invoice->first_name . ' ' . $invoice->last_name) ?: $invoice->email)) !!}</td>
<td>{{ Utils::fromSqlDate($invoice->due_date) }}</td> <td>{{ Utils::fromSqlDate($invoice->due_date) }}</td>
<td>{{ Utils::formatMoney($invoice->balance, $invoice->client->getCurrencyId()) }}</td> <td>{{ Utils::formatMoney($invoice->balance, $invoice->currency_id ?: ($account->currency_id ?: DEFAULT_CURRENCY)) }}</td>
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>

View File

@ -1,4 +1,8 @@
<html> <!DOCTYPE html>
<html lang="{{ App::getLocale() }}">
<head>
<meta charset="utf-8">
</head>
<body> <body>
@if (false && !$invitationMessage) @if (false && !$invitationMessage)
@include('emails.confirm_action', ['user' => $user]) @include('emails.confirm_action', ['user' => $user])

View File

@ -1,4 +1,8 @@
<html> <!DOCTYPE html>
<html lang="{{ App::getLocale() }}">
<head>
<meta charset="utf-8">
</head>
<body> <body>
@if (false) @if (false)
@include('emails.view_action', ['link' => $link, 'entityType' => $entityType]) @include('emails.view_action', ['link' => $link, 'entityType' => $entityType])

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="{{ App::getLocale() }}">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
</head> </head>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="{{ App::getLocale() }}">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
</head> </head>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{App::getLocale()}}"> <html lang="{{ App::getLocale() }}">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
</head> </head>

View File

@ -1,18 +1,18 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="{{ App::getLocale() }}">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
</head> </head>
<body> <body>
{{ $client }},<p/> {{ $client }},<p/>
{{ trans('texts.payment_message', ['amount' => $amount]) }}<p/> {{ trans('texts.payment_message', ['amount' => $amount]) }}<p/>
{{ $license }}<p/> {{ $license }}<p/>
{{ trans('texts.email_signature') }}<br/> {{ trans('texts.email_signature') }}<br/>
{{ $account }} {{ $account }}
</body> </body>
</html> </html>

View File

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="{{ App::getLocale() }}">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
</head> </head>
<body>{!! $body !!}</body> <body>{!! $body !!}</body>
</html> </html>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="{{ App::getLocale() }}">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
</head> </head>

View File

@ -25,6 +25,23 @@
} }
} }
@if (Auth::check() && Auth::user()->dark_mode)
body {
background: #000 !important;
color: white !important;
}
.panel-body {
background: #272822 !important;
/*background: #e6e6e6 !important;*/
}
.panel-default {
border-color: #444;
}
@endif
</style> </style>
@include('script') @include('script')
@ -186,19 +203,6 @@
}); });
} }
function showUnlink(userAccountId, userId) {
NINJA.unlink = {
'userAccountId': userAccountId,
'userId': userId
};
$('#unlinkModal').modal('show');
return false;
}
function unlinkAccount() {
window.location = '{{ URL::to('/unlink_account') }}' + '/' + NINJA.unlink.userAccountId + '/' + NINJA.unlink.userId;
}
function wordWrapText(value, width) function wordWrapText(value, width)
{ {
@if (Auth::user()->account->auto_wrap) @if (Auth::user()->account->auto_wrap)
@ -249,12 +253,12 @@
}, 2000); }, 2000);
$('#search').blur(function(){ $('#search').blur(function(){
$('#search').css('width', '150px'); $('#search').css('width', '{{ Utils::isEnglish() ? 150 : 110 }}px');
$('ul.navbar-right').show(); $('ul.navbar-right').show();
}); });
$('#search').focus(function(){ $('#search').focus(function(){
$('#search').css('width', '256px'); $('#search').css('width', '{{ Utils::isEnglish() ? 256 : 216 }}px');
$('ul.navbar-right').hide(); $('ul.navbar-right').hide();
if (!window.hasOwnProperty('searchData')) { if (!window.hasOwnProperty('searchData')) {
$.get('{{ URL::route('getSearchData') }}', function(data) { $.get('{{ URL::route('getSearchData') }}', function(data) {
@ -309,8 +313,21 @@
showSignUp(); showSignUp();
@endif @endif
$('ul.navbar-settings, ul.navbar-history').hover(function () {
//$('.user-accounts').find('li').hide();
//$('.user-accounts').css({display: 'none'});
//console.log($('.user-accounts').dropdown(''))
if ($('.user-accounts').css('display') == 'block') {
$('.user-accounts').dropdown('toggle');
}
});
@yield('onReady') @yield('onReady')
@if (Input::has('focus'))
$('#{{ Input::get('focus') }}').focus();
@endif
}); });
</script> </script>
@ -363,7 +380,7 @@
<span class="caret"></span> <span class="caret"></span>
</div> </div>
</button> </button>
<ul class="dropdown-menu user-accounts" role="menu"> <ul class="dropdown-menu user-accounts">
@if (session(SESSION_USER_ACCOUNTS)) @if (session(SESSION_USER_ACCOUNTS))
@foreach (session(SESSION_USER_ACCOUNTS) as $item) @foreach (session(SESSION_USER_ACCOUNTS) as $item)
@if ($item->user_id == Auth::user()->id) @if ($item->user_id == Auth::user()->id)
@ -372,9 +389,8 @@
'user_id' => $item->user_id, 'user_id' => $item->user_id,
'account_name' => $item->account_name, 'account_name' => $item->account_name,
'user_name' => $item->user_name, 'user_name' => $item->user_name,
'account_key' => $item->account_key, 'logo_path' => isset($item->logo_path) ? $item->logo_path : "",
'selected' => true, 'selected' => true,
'show_remove' => count(session(SESSION_USER_ACCOUNTS)) > 1,
]) ])
@endif @endif
@endforeach @endforeach
@ -385,9 +401,8 @@
'user_id' => $item->user_id, 'user_id' => $item->user_id,
'account_name' => $item->account_name, 'account_name' => $item->account_name,
'user_name' => $item->user_name, 'user_name' => $item->user_name,
'account_key' => $item->account_key, 'logo_path' => isset($item->logo_path) ? $item->logo_path : "",
'selected' => false, 'selected' => false,
'show_remove' => count(session(SESSION_USER_ACCOUNTS)) > 1,
]) ])
@endif @endif
@endforeach @endforeach
@ -395,13 +410,15 @@
@include('user_account', [ @include('user_account', [
'account_name' => Auth::user()->account->name ?: trans('texts.untitled'), 'account_name' => Auth::user()->account->name ?: trans('texts.untitled'),
'user_name' => Auth::user()->getDisplayName(), 'user_name' => Auth::user()->getDisplayName(),
'account_key' => Auth::user()->account->account_key, 'logo_path' => Auth::user()->account->getLogoPath(),
'selected' => true, 'selected' => true,
]) ])
@endif @endif
<li class="divider"></li> <li class="divider"></li>
@if (!session(SESSION_USER_ACCOUNTS) || count(session(SESSION_USER_ACCOUNTS)) < 5) @if (count(session(SESSION_USER_ACCOUNTS)) > 1)
<li>{!! link_to('/login?new_account=true', trans('texts.add_account')) !!}</li> <li>{!! link_to('/manage_companies', trans('texts.manage_companies')) !!}</li>
@elseif (!session(SESSION_USER_ACCOUNTS) || count(session(SESSION_USER_ACCOUNTS)) < 5)
<li>{!! link_to('/login?new_company=true', trans('texts.add_company')) !!}</li>
@endif @endif
<li>{!! link_to('#', trans('texts.logout'), array('onclick'=>'logout()')) !!}</li> <li>{!! link_to('#', trans('texts.logout'), array('onclick'=>'logout()')) !!}</li>
</ul> </ul>
@ -409,7 +426,7 @@
</div> </div>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right navbar-settings">
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span class="glyphicon glyphicon-cog" title="{{ trans('texts.settings') }}"/> <span class="glyphicon glyphicon-cog" title="{{ trans('texts.settings') }}"/>
@ -420,13 +437,13 @@
<li>{!! link_to('company/products', uctrans('texts.product_library')) !!}</li> <li>{!! link_to('company/products', uctrans('texts.product_library')) !!}</li>
<li>{!! link_to('company/notifications', uctrans('texts.notifications')) !!}</li> <li>{!! link_to('company/notifications', uctrans('texts.notifications')) !!}</li>
<li>{!! link_to('company/import_export', uctrans('texts.import_export')) !!}</li> <li>{!! link_to('company/import_export', uctrans('texts.import_export')) !!}</li>
<li><a href="{{ url('company/advanced_settings/invoice_settings') }}">{!! uctrans('texts.advanced_settings') . Utils::getProLabel(ACCOUNT_ADVANCED_SETTINGS) !!}</a></li> <li><a href="{{ url('company/advanced_settings/invoice_design') }}">{!! uctrans('texts.advanced_settings') . Utils::getProLabel(ACCOUNT_ADVANCED_SETTINGS) !!}</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right navbar-history">
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span class="glyphicon glyphicon-time" title="{{ trans('texts.history') }}"/> <span class="glyphicon glyphicon-time" title="{{ trans('texts.history') }}"/>
@ -436,7 +453,9 @@
<li><a href="#">{{ trans('texts.no_items') }}</a></li> <li><a href="#">{{ trans('texts.no_items') }}</a></li>
@else @else
@foreach (Session::get(RECENTLY_VIEWED) as $link) @foreach (Session::get(RECENTLY_VIEWED) as $link)
<li><a href="{{ $link->url }}">{{ $link->name }}</a></li> @if (property_exists($link, 'accountId') && $link->accountId == Auth::user()->account_id)
<li><a href="{{ $link->url }}">{{ $link->name }}</a></li>
@endif
@endforeach @endforeach
@endif @endif
</ul> </ul>
@ -445,7 +464,7 @@
<form class="navbar-form navbar-right" role="search"> <form class="navbar-form navbar-right" role="search">
<div class="form-group"> <div class="form-group">
<input type="text" id="search" style="width: 150px" <input type="text" id="search" style="width: {{ Utils::isEnglish() ? 150 : 110 }}px"
class="form-control" placeholder="{{ trans('texts.search') }}"> class="form-control" placeholder="{{ trans('texts.search') }}">
</div> </div>
</form> </form>
@ -575,28 +594,6 @@
</div> </div>
@endif @endif
@if (Auth::check() && session(SESSION_USER_ACCOUNTS) && count(session(SESSION_USER_ACCOUNTS)))
<div class="modal fade" id="unlinkModal" tabindex="-1" role="dialog" aria-labelledby="unlinkModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="myModalLabel">{{ trans('texts.unlink_account') }}</h4>
</div>
<div class="container">
<h3>{{ trans('texts.are_you_sure') }}</h3>
</div>
<div class="modal-footer" id="signUpFooter">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.cancel') }}</button>
<button type="button" class="btn btn-primary" onclick="unlinkAccount()">{{ trans('texts.unlink') }}</button>
</div>
</div>
</div>
</div>
@endif
@if (Auth::check() && !Auth::user()->isPro()) @if (Auth::check() && !Auth::user()->isPro())
<div class="modal fade" id="proPlanModal" tabindex="-1" role="dialog" aria-labelledby="proPlanModalLabel" aria-hidden="true"> <div class="modal fade" id="proPlanModal" tabindex="-1" role="dialog" aria-labelledby="proPlanModalLabel" aria-hidden="true">
<div class="modal-dialog large-dialog"> <div class="modal-dialog large-dialog">
@ -637,7 +634,8 @@
@endif @endif
{{-- Per our license, please do not remove or modify this section. --}} {{-- Per our license, please do not remove or modify this section. --}}
@if (!Utils::isNinja()) @if (!Utils::isNinjaProd())
<p>&nbsp;</p>
<div class="container"> <div class="container">
{{ trans('texts.powered_by') }} <a href="https://www.invoiceninja.com/?utm_source=powered_by" target="_blank">InvoiceNinja.com</a> | {{ trans('texts.powered_by') }} <a href="https://www.invoiceninja.com/?utm_source=powered_by" target="_blank">InvoiceNinja.com</a> |
@if (Auth::user()->account->isWhiteLabel()) @if (Auth::user()->account->isWhiteLabel())

View File

@ -5,11 +5,8 @@
<script src="{{ asset('js/pdf_viewer.js') }}" type="text/javascript"></script> <script src="{{ asset('js/pdf_viewer.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/compatibility.js') }}" type="text/javascript"></script> <script src="{{ asset('js/compatibility.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/pdfmake.min.js') }}" type="text/javascript"></script>
@if (Auth::user()->account->utf8_invoices) <script src="{{ asset('js/vfs_fonts.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/pdfmake.min.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/vfs_fonts.js') }}" type="text/javascript"></script>
@endif
@stop @stop
@ -17,8 +14,12 @@
@if ($invoice && $invoice->id) @if ($invoice && $invoice->id)
<ol class="breadcrumb"> <ol class="breadcrumb">
<li>{!! link_to(($entityType == ENTITY_QUOTE ? 'quotes' : 'invoices'), trans('texts.' . ($entityType == ENTITY_QUOTE ? 'quotes' : 'invoices'))) !!}</li> @if ($isRecurring)
<li class='active'>{{ $invoice->invoice_number }}</li> <li>{!! link_to('invoices', trans('texts.recurring_invoice')) !!}</li>
@else
<li>{!! link_to(($entityType == ENTITY_QUOTE ? 'quotes' : 'invoices'), trans('texts.' . ($entityType == ENTITY_QUOTE ? 'quotes' : 'invoices'))) !!}</li>
<li class='active'>{{ $invoice->invoice_number }}</li>
@endif
</ol> </ol>
@endif @endif
@ -53,7 +54,7 @@
<div class="form-group" style="margin-bottom: 8px"> <div class="form-group" style="margin-bottom: 8px">
<div class="col-lg-8 col-sm-8 col-lg-offset-4 col-sm-offset-4"> <div class="col-lg-8 col-sm-8 col-lg-offset-4 col-sm-offset-4">
<a id="createClientLink" class="pointer" data-bind="click: $root.showClientForm, text: $root.clientLinkText"></a> <a id="createClientLink" class="pointer" data-bind="click: $root.showClientForm, html: $root.clientLinkText"></a>
<span data-bind="visible: $root.invoice().client().public_id() > 0">| <span data-bind="visible: $root.invoice().client().public_id() > 0">|
<a data-bind="attr: {href: '{{ url('/clients') }}/' + $root.invoice().client().public_id()}" target="_blank">{{ trans('texts.view_client') }}</a> <a data-bind="attr: {href: '{{ url('/clients') }}/' + $root.invoice().client().public_id()}" target="_blank">{{ trans('texts.view_client') }}</a>
</span> </span>
@ -67,7 +68,7 @@
<div data-bind="with: client"> <div data-bind="with: client">
<div style="display:none" class="form-group" data-bind="visible: contacts().length > 0 &amp;&amp; (contacts()[0].email() || contacts()[0].first_name()), foreach: contacts"> <div style="display:none" class="form-group" data-bind="visible: contacts().length > 0 &amp;&amp; (contacts()[0].email() || contacts()[0].first_name()), foreach: contacts">
<div class="col-lg-8 col-lg-offset-4"> <div class="col-lg-8 col-lg-offset-4">
<label class="checkbox" data-bind="attr: {for: $index() + '_check'}" onclick="refreshPDF()"> <label class="checkbox" data-bind="attr: {for: $index() + '_check'}" onclick="refreshPDF(true)">
<input type="checkbox" value="1" data-bind="checked: send_invoice, attr: {id: $index() + '_check'}"> <input type="checkbox" value="1" data-bind="checked: send_invoice, attr: {id: $index() + '_check'}">
<span data-bind="html: email.display"/> <span data-bind="html: email.display"/>
</label> </label>
@ -79,60 +80,47 @@
<div class="col-md-4" id="col_2"> <div class="col-md-4" id="col_2">
<div data-bind="visible: !is_recurring()"> <div data-bind="visible: !is_recurring()">
{!! Former::text('invoice_date')->data_bind("datePicker: invoice_date, valueUpdate: 'afterkeydown'")->label(trans("texts.{$entityType}_date")) {!! Former::text('invoice_date')->data_bind("datePicker: invoice_date, valueUpdate: 'afterkeydown'")->label(trans("texts.{$entityType}_date"))
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'invoice_date\')"></i>') !!} ->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->appendIcon('calendar')->addGroupClass('invoice_date') !!}
{!! Former::text('due_date')->data_bind("datePicker: due_date, valueUpdate: 'afterkeydown'") {!! Former::text('due_date')->data_bind("datePicker: due_date, valueUpdate: 'afterkeydown'")
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'due_date\')"></i>') !!} ->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->appendIcon('calendar')->addGroupClass('due_date') !!}
{!! Former::text('partial')->data_bind("value: partial, valueUpdate: 'afterkeydown'")->onchange('onPartialChange()') {!! Former::text('partial')->data_bind("value: partial, valueUpdate: 'afterkeydown'")->onchange('onPartialChange()')
->rel('tooltip')->data_toggle('tooltip')->data_placement('bottom')->title(trans('texts.partial_value')) !!} ->rel('tooltip')->data_toggle('tooltip')->data_placement('bottom')->title(trans('texts.partial_value')) !!}
</div> </div>
@if ($entityType == ENTITY_INVOICE) @if ($entityType == ENTITY_INVOICE)
<div data-bind="visible: is_recurring" style="display: none"> <div data-bind="visible: is_recurring" style="display: none">
{!! Former::select('frequency_id')->options($frequencies)->data_bind("value: frequency_id") !!} {!! Former::select('frequency_id')->options($frequencies)->data_bind("value: frequency_id")
->appendIcon('question-sign')->addGroupClass('frequency_id') !!}
{!! Former::text('start_date')->data_bind("datePicker: start_date, valueUpdate: 'afterkeydown'") {!! Former::text('start_date')->data_bind("datePicker: start_date, valueUpdate: 'afterkeydown'")
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'start_date\')"></i>') !!} ->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->appendIcon('calendar')->addGroupClass('start_date') !!}
{!! Former::text('end_date')->data_bind("datePicker: end_date, valueUpdate: 'afterkeydown'") {!! Former::text('end_date')->data_bind("datePicker: end_date, valueUpdate: 'afterkeydown'")
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'end_date\')"></i>') !!} ->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->appendIcon('calendar')->addGroupClass('end_date') !!}
</div> </div>
@if ($invoice && $invoice->recurring_invoice_id) @if ($invoice && $invoice->recurring_invoice)
<div class="pull-right" style="padding-top: 6px"> <div class="pull-right" style="padding-top: 6px">
{!! trans('texts.created_by_recurring', ['invoice' => link_to('/invoices/'.$invoice->recurring_invoice->public_id, $invoice->recurring_invoice->invoice_number)]) !!} {!! trans('texts.created_by_invoice', ['invoice' => link_to('/invoices/'.$invoice->recurring_invoice->public_id, trans('texts.recurring_invoice'))]) !!}
</div> </div>
@else @elseif ($invoice && isset($lastSent) && $lastSent)
<div data-bind="visible: invoice_status_id() === 0"> <div class="pull-right" style="padding-top: 6px">
<div class="form-group"> {!! trans('texts.last_invoice_sent', [
<label for="" class="control-label col-lg-4 col-sm-4"> 'date' => link_to('/invoices/'.$lastSent->public_id, Utils::dateToString($invoice->last_sent_date), ['id' => 'lastInvoiceSent'])
{{ trans('texts.recurring') }} ]) !!}
</label>
<div class="col-lg-8 col-sm-8">
<div class="checkbox">
<label for="recurring" class="">
<input onclick="onRecurringEnabled()" data-bind="checked: is_recurring" id="recurring" type="checkbox" name="recurring" value="1">{{ trans('texts.enable') }} &nbsp;&nbsp;
<a href="#" onclick="showLearnMore()"><i class="glyphicon glyphicon-question-sign"></i> {{ trans('texts.learn_more') }}</a>
</label>
</div>
</div>
</div> </div>
@if ($invoice && $invoice->last_sent_date) @endif
<div class="pull-right">
{{ trans('texts.last_invoice_sent', ['date' => Utils::dateToString($invoice->last_sent_date)]) }}
</div>
@endif
</div>
@endif
@endif @endif
</div> </div>
<div class="col-md-4" id="col_2"> <div class="col-md-4" id="col_2">
{!! Former::text('invoice_number')->label(trans("texts.{$entityType}_number_short"))->data_bind("value: invoice_number, valueUpdate: 'afterkeydown'") !!} <span data-bind="visible: !is_recurring()">
{!! Former::text('invoice_number')->label(trans("texts.{$entityType}_number_short"))->data_bind("value: invoice_number, valueUpdate: 'afterkeydown'") !!}
</span>
{!! Former::text('po_number')->label(trans('texts.po_number_short'))->data_bind("value: po_number, valueUpdate: 'afterkeydown'") !!} {!! Former::text('po_number')->label(trans('texts.po_number_short'))->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'")
->addGroupClass('discount-group')->type('number')->min('0')->step('any')->append( ->addGroupClass('discount-group')->type('number')->min('0')->step('any')->append(
Former::select('is_amount_discount')->addOption(trans('texts.discount_percent'), '0') Former::select('is_amount_discount')->addOption(trans('texts.discount_percent'), '0')
->addOption(trans('texts.discount_amount'), '1')->data_bind("value: is_amount_discount")->raw() ->addOption(trans('texts.discount_amount'), '1')->data_bind("value: is_amount_discount")->raw()
) !!} ) !!}
{{-- Former::select('currency_id')->addOption('', '')->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="taxes" class="control-label col-lg-4 col-sm-4">{{ trans('texts.taxes') }}</label> <label for="taxes" class="control-label col-lg-4 col-sm-4">{{ trans('texts.taxes') }}</label>
@ -147,7 +135,7 @@
<p>&nbsp;</p> <p>&nbsp;</p>
<div class="table-responsive"> <div class="table-responsive">
<table class="table invoice-table" style="margin-bottom: 0px !important"> <table class="table invoice-table">
<thead> <thead>
<tr> <tr>
<th style="min-width:32px;" class="hide-border"></th> <th style="min-width:32px;" class="hide-border"></th>
@ -163,9 +151,11 @@
<tbody data-bind="sortable: { data: invoice_items, afterMove: onDragged }"> <tbody data-bind="sortable: { data: invoice_items, afterMove: onDragged }">
<tr data-bind="event: { mouseover: showActions, mouseout: hideActions }" class="sortable-row"> <tr data-bind="event: { mouseover: showActions, mouseout: hideActions }" class="sortable-row">
<td class="hide-border td-icon"> <td class="hide-border td-icon">
<i style="display:none" data-bind="visible: actionsVisible() &amp;&amp; $parent.invoice_items().length > 1" class="fa fa-sort"></i> <i style="display:none" data-bind="visible: actionsVisible() &amp;&amp;
$index() < ($parent.invoice_items().length - 1) &amp;&amp;
$parent.invoice_items().length > 1" class="fa fa-sort"></i>
</td> </td>
<td> <td>
{!! Former::text('product_key')->useDatalist($products->toArray(), 'product_key')->onkeyup('onItemChange()') {!! Former::text('product_key')->useDatalist($products->toArray(), 'product_key')->onkeyup('onItemChange()')
->raw()->data_bind("value: product_key, valueUpdate: 'afterkeydown'")->addClass('datalist') !!} ->raw()->data_bind("value: product_key, valueUpdate: 'afterkeydown'")->addClass('datalist') !!}
</td> </td>
@ -184,8 +174,10 @@
<td style="text-align:right;padding-top:9px !important"> <td style="text-align:right;padding-top:9px !important">
<div class="line-total" data-bind="text: totals.total"></div> <div class="line-total" data-bind="text: totals.total"></div>
</td> </td>
<td style="cursor:pointer" class="hide-border td-icon"> <td style="cursor:pointer" class="hide-border td-icon"> &nbsp;
&nbsp;<i style="display:none" data-bind="click: $parent.removeItem, visible: actionsVisible() &amp;&amp; $parent.invoice_items().length > 1" class="fa fa-minus-circle redlink" title="Remove item"/> <i style="display:none" data-bind="click: $parent.removeItem, visible: actionsVisible() &amp;&amp;
$index() < ($parent.invoice_items().length - 1) &amp;&amp;
$parent.invoice_items().length > 1" class="fa fa-minus-circle redlink" title="Remove item"/>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -255,6 +247,15 @@
</tr> </tr>
@endif @endif
<tr style="display:none" data-bind="visible: $root.invoice_item_taxes.show &amp;&amp; totals.hasItemTaxes">
<td class="hide-border" colspan="4"/>
@if (!$account->hide_quantity)
<td>{{ trans('texts.tax') }}</td>
@endif
<td style="min-width:120px"><span data-bind="html: totals.itemTaxRates"/></td>
<td style="text-align: right"><span data-bind="html: totals.itemTaxAmounts"/></td>
</tr>
<tr style="display:none" data-bind="visible: $root.invoice_taxes.show"> <tr style="display:none" data-bind="visible: $root.invoice_taxes.show">
<td class="hide-border" colspan="3"/> <td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/> <td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
@ -324,10 +325,10 @@
</div> </div>
@if (!Utils::isPro() || \App\Models\InvoiceDesign::count() == COUNT_FREE_DESIGNS) @if (!Utils::isPro() || \App\Models\InvoiceDesign::count() == COUNT_FREE_DESIGNS_SELF_HOST)
{!! Former::select('invoice_design_id')->style('display:'.($account->utf8_invoices ? 'none' : 'inline').';width:150px;background-color:white !important')->raw()->fromQuery($invoiceDesigns, 'name', 'id')->data_bind("value: invoice_design_id")->addOption(trans('texts.more_designs') . '...', '-1') !!} {!! Former::select('invoice_design_id')->style('display:inline;width:150px;background-color:white !important')->raw()->fromQuery($invoiceDesigns, 'name', 'id')->data_bind("value: invoice_design_id")->addOption(trans('texts.more_designs') . '...', '-1') !!}
@else @else
{!! Former::select('invoice_design_id')->style('display:'.($account->utf8_invoices ? 'none' : 'inline').';width:150px;background-color:white !important')->raw()->fromQuery($invoiceDesigns, 'name', 'id')->data_bind("value: invoice_design_id") !!} {!! Former::select('invoice_design_id')->style('display:inline;width:150px;background-color:white !important')->raw()->fromQuery($invoiceDesigns, 'name', 'id')->data_bind("value: invoice_design_id") !!}
@endif @endif
{!! Button::primary(trans('texts.download_pdf'))->withAttributes(array('onclick' => 'onDownloadClick()'))->appendIcon(Icon::create('download-alt')) !!} {!! Button::primary(trans('texts.download_pdf'))->withAttributes(array('onclick' => 'onDownloadClick()'))->appendIcon(Icon::create('download-alt')) !!}
@ -335,10 +336,7 @@
@if (!$invoice || (!$invoice->trashed() && !$invoice->client->trashed())) @if (!$invoice || (!$invoice->trashed() && !$invoice->client->trashed()))
{!! Button::success(trans("texts.save_{$entityType}"))->withAttributes(array('id' => 'saveButton', 'onclick' => 'onSaveClick()'))->appendIcon(Icon::create('floppy-disk')) !!} {!! Button::success(trans("texts.save_{$entityType}"))->withAttributes(array('id' => 'saveButton', 'onclick' => 'onSaveClick()'))->appendIcon(Icon::create('floppy-disk')) !!}
{!! Button::info(trans("texts.email_{$entityType}"))->withAttributes(array('id' => 'emailButton', 'onclick' => 'onEmailClick()'))->appendIcon(Icon::create('send')) !!}
@if (!$invoice || ($invoice && !$invoice->is_recurring))
{!! Button::info(trans("texts.email_{$entityType}"))->withAttributes(array('id' => 'email_button', 'onclick' => 'onEmailClick()'))->appendIcon(Icon::create('send')) !!}
@endif
@if ($invoice && $invoice->id) @if ($invoice && $invoice->id)
{!! DropdownButton::normal(trans('texts.more_actions')) {!! DropdownButton::normal(trans('texts.more_actions'))
@ -485,10 +483,10 @@
<tr data-bind="event: { mouseover: showActions, mouseout: hideActions }"> <tr data-bind="event: { mouseover: showActions, mouseout: hideActions }">
<td style="width:30px" class="hide-border"></td> <td style="width:30px" class="hide-border"></td>
<td style="width:60px"> <td style="width:60px">
<input onkeyup="onTaxRateChange()" data-bind="value: name, valueUpdate: 'afterkeydown'" class="form-control" onchange="refreshPDF()"//> <input onkeyup="onTaxRateChange()" data-bind="value: name, valueUpdate: 'afterkeydown'" class="form-control" onchange="refreshPDF(true)"//>
</td> </td>
<td style="width:60px"> <td style="width:60px">
<input onkeyup="onTaxRateChange()" data-bind="value: prettyRate, valueUpdate: 'afterkeydown'" style="text-align: right" class="form-control" onchange="refreshPDF()"//> <input onkeyup="onTaxRateChange()" data-bind="value: prettyRate, valueUpdate: 'afterkeydown'" style="text-align: right" class="form-control" onchange="refreshPDF(true)"//>
</td> </td>
<td style="width:30px; cursor:pointer" class="hide-border td-icon"> <td style="width:30px; cursor:pointer" class="hide-border td-icon">
&nbsp;<i style="width:12px;" data-bind="click: $root.removeTaxRate, visible: actionsVisible() &amp;&amp; !isEmpty()" class="fa fa-minus-circle redlink" title="Remove item"/> &nbsp;<i style="width:12px;" data-bind="click: $root.removeTaxRate, visible: actionsVisible() &amp;&amp; !isEmpty()" class="fa fa-minus-circle redlink" title="Remove item"/>
@ -588,8 +586,8 @@
} else { } else {
model.loadClient($.parseJSON(ko.toJSON(new ClientModel()))); model.loadClient($.parseJSON(ko.toJSON(new ClientModel())));
model.invoice().client().country = false; model.invoice().client().country = false;
} }
refreshPDF(); refreshPDF(true);
}); });
// If no clients exists show the client form when clicking on the client select input // If no clients exists show the client form when clicking on the client select input
@ -600,11 +598,25 @@
} }
$('#invoice_footer, #terms, #public_notes, #invoice_number, #invoice_date, #due_date, #po_number, #discount, #currency_id, #invoice_design_id, #recurring, #is_amount_discount, #partial').change(function() { $('#invoice_footer, #terms, #public_notes, #invoice_number, #invoice_date, #due_date, #po_number, #discount, #currency_id, #invoice_design_id, #recurring, #is_amount_discount, #partial').change(function() {
setTimeout(function() { setTimeout(function() {
refreshPDF(); refreshPDF(true);
}, 1); }, 1);
}); });
$('.frequency_id .input-group-addon').click(function() {
showLearnMore();
});
var fields = ['invoice_date', 'due_date', 'start_date', 'end_date'];
for (var i=0; i<fields.length; i++) {
var field = fields[i];
(function (_field) {
$('.' + _field + ' .input-group-addon').click(function() {
toggleDatePicker(_field);
});
})(field);
}
@if ($client || $invoice || count($clients) == 0) @if ($client || $invoice || count($clients) == 0)
$('#invoice_number').focus(); $('#invoice_number').focus();
@else @else
@ -616,7 +628,7 @@
}).on('hidden.bs.modal', function () { }).on('hidden.bs.modal', function () {
if (model.clientBackup) { if (model.clientBackup) {
model.loadClient(model.clientBackup); model.loadClient(model.clientBackup);
refreshPDF(); refreshPDF(true);
} }
}) })
@ -639,19 +651,23 @@
@if ($client) @if ($client)
$input.trigger('change'); $input.trigger('change');
@else @else
refreshPDF(); refreshPDF(true);
@endif @endif
var client = model.invoice().client(); var client = model.invoice().client();
setComboboxValue($('.client_select'), setComboboxValue($('.client_select'),
client.public_id(), client.public_id(),
client.name.display()); client.name.display());
@if (isset($tasks) && $tasks)
NINJA.formIsChanged = true;
@endif
}); });
function applyComboboxListeners() { function applyComboboxListeners() {
var selectorStr = '.invoice-table input, .invoice-table select, .invoice-table textarea'; var selectorStr = '.invoice-table input, .invoice-table select, .invoice-table textarea';
$(selectorStr).off('blur').on('blur', function() { $(selectorStr).off('blur').on('blur', function() {
refreshPDF(); refreshPDF(true);
}); });
$('textarea').on('keyup focus', function(e) { $('textarea').on('keyup focus', function(e) {
@ -667,9 +683,15 @@
var product = products[i]; var product = products[i];
if (product.product_key == key) { if (product.product_key == key) {
var model = ko.dataFor(this); var model = ko.dataFor(this);
model.notes(product.notes); if (!model.notes()) {
model.cost(accounting.toFixed(product.cost,2)); model.notes(product.notes);
model.qty(1); }
if (!model.cost()) {
model.cost(accounting.toFixed(product.cost,2));
}
if (!model.qty()) {
model.qty(1);
}
break; break;
} }
} }
@ -685,6 +707,10 @@
invoice.is_quote = {{ $entityType == ENTITY_QUOTE ? 'true' : 'false' }}; invoice.is_quote = {{ $entityType == ENTITY_QUOTE ? 'true' : 'false' }};
invoice.contact = _.findWhere(invoice.client.contacts, {send_invoice: true}); invoice.contact = _.findWhere(invoice.client.contacts, {send_invoice: true});
if (invoice.is_recurring) {
invoice.invoice_number = '0000';
}
@if (!$invoice) @if (!$invoice)
if (!invoice.terms) { if (!invoice.terms) {
invoice.terms = wordWrapText('{!! str_replace(["\r\n","\r","\n"], '\n', addslashes($account->invoice_terms)) !!}', 300); invoice.terms = wordWrapText('{!! str_replace(["\r\n","\r","\n"], '\n', addslashes($account->invoice_terms)) !!}', 300);
@ -707,14 +733,11 @@
return invoice; return invoice;
} }
function getPDFString(cb) { function getPDFString(cb, force) {
var invoice = createInvoiceModel(); var invoice = createInvoiceModel();
var design = getDesignJavascript(); var design = getDesignJavascript();
if (!design) return; if (!design) return;
doc = generatePDF(invoice, design, false); generatePDF(invoice, design, force, cb);
if (!doc) return;
//return doc.output('datauristring');
doc.getDataUrl(cb);
} }
function getDesignJavascript() { function getDesignJavascript() {
@ -724,7 +747,8 @@
model.invoice().invoice_design_id(1); model.invoice().invoice_design_id(1);
return invoiceDesigns[0].javascript; return invoiceDesigns[0].javascript;
} else { } else {
return invoiceDesigns[id-1].javascript; var design = _.find(invoiceDesigns, function(design){ return design.id == id});
return design ? design.javascript : '';
} }
} }
@ -916,7 +940,6 @@
var paymentTerms = parseInt(self.invoice().client().payment_terms()); var paymentTerms = parseInt(self.invoice().client().payment_terms());
if (paymentTerms && paymentTerms != 0 && !self.invoice().due_date()) if (paymentTerms && paymentTerms != 0 && !self.invoice().due_date())
{ {
console.log("here");
if (paymentTerms == -1) paymentTerms = 0; if (paymentTerms == -1) paymentTerms = 0;
var dueDate = $('#invoice_date').datepicker('getDate'); var dueDate = $('#invoice_date').datepicker('getDate');
dueDate.setDate(dueDate.getDate() + paymentTerms); dueDate.setDate(dueDate.getDate() + paymentTerms);
@ -1087,7 +1110,7 @@
$('#emailError').css( "display", "none" ); $('#emailError').css( "display", "none" );
//$('.client_select input.form-control').focus(); //$('.client_select input.form-control').focus();
refreshPDF(); refreshPDF(true);
model.clientBackup = false; model.clientBackup = false;
$('#clientModal').modal('hide'); $('#clientModal').modal('hide');
} }
@ -1115,10 +1138,10 @@
var self = this; var self = this;
this.client = ko.observable(data ? false : new ClientModel()); this.client = ko.observable(data ? false : new ClientModel());
self.account = {!! $account !!}; self.account = {!! $account !!};
this.id = ko.observable(''); self.id = ko.observable('');
self.discount = ko.observable(''); self.discount = ko.observable('');
self.is_amount_discount = ko.observable(0); self.is_amount_discount = ko.observable(0);
self.frequency_id = ko.observable(''); self.frequency_id = ko.observable(4); // default to monthly
self.terms = ko.observable(''); self.terms = ko.observable('');
self.default_terms = ko.observable({{ !$invoice && $account->invoice_terms ? 'true' : 'false' }} ? wordWrapText('{!! str_replace(["\r\n","\r","\n"], '\n', addslashes($account->invoice_terms)) !!}', 300) : ''); self.default_terms = ko.observable({{ !$invoice && $account->invoice_terms ? 'true' : 'false' }} ? wordWrapText('{!! str_replace(["\r\n","\r","\n"], '\n', addslashes($account->invoice_terms)) !!}', 300) : '');
self.set_default_terms = ko.observable(false); self.set_default_terms = ko.observable(false);
@ -1134,12 +1157,12 @@
self.end_date = ko.observable(''); self.end_date = ko.observable('');
self.tax_name = ko.observable(); self.tax_name = ko.observable();
self.tax_rate = ko.observable(); self.tax_rate = ko.observable();
self.is_recurring = ko.observable(false); self.is_recurring = ko.observable({{ $isRecurring ? 'true' : 'false' }});
self.invoice_status_id = ko.observable(0); self.invoice_status_id = ko.observable(0);
self.invoice_items = ko.observableArray(); self.invoice_items = ko.observableArray();
self.amount = ko.observable(0); self.amount = ko.observable(0);
self.balance = ko.observable(0); self.balance = ko.observable(0);
self.invoice_design_id = ko.observable({{ $account->utf8_invoices ? 1 : $account->invoice_design_id }}); self.invoice_design_id = ko.observable({{ $account->invoice_design_id }});
self.partial = ko.observable(0); self.partial = ko.observable(0);
self.has_tasks = ko.observable(false); self.has_tasks = ko.observable(false);
@ -1248,7 +1271,7 @@
self.removeItem = function(item) { self.removeItem = function(item) {
self.invoice_items.remove(item); self.invoice_items.remove(item);
refreshPDF(); refreshPDF(true);
} }
@ -1285,13 +1308,6 @@
var discount = self.totals.rawDiscounted(); var discount = self.totals.rawDiscounted();
total -= discount; total -= discount;
/*
var discount = parseFloat(self.discount());
if (discount > 0) {
total = roundToTwo(total * ((100 - discount)/100));
}
*/
var customValue1 = roundToTwo(self.custom_value1()); var customValue1 = roundToTwo(self.custom_value1());
var customValue2 = roundToTwo(self.custom_value2()); var customValue2 = roundToTwo(self.custom_value2());
var customTaxes1 = self.custom_taxes1() == 1; var customTaxes1 = self.custom_taxes1() == 1;
@ -1313,6 +1329,65 @@
} }
}); });
self.totals.itemTaxes = ko.computed(function() {
var taxes = {};
var total = self.totals.rawSubtotal();
for(var i=0; i<self.invoice_items().length; i++) {
var item = self.invoice_items()[i];
var lineTotal = item.totals.rawTotal();
if (self.discount()) {
if (parseInt(self.is_amount_discount())) {
lineTotal -= roundToTwo((lineTotal/total) * self.discount());
} else {
lineTotal -= roundToTwo(lineTotal * (self.discount()/100));
}
}
var taxAmount = roundToTwo(lineTotal * item.tax_rate() / 100);
if (taxAmount) {
var key = item.tax_name() + item.tax_rate();
if (taxes.hasOwnProperty(key)) {
taxes[key].amount += taxAmount;
} else {
taxes[key] = {name:item.tax_name(), rate:item.tax_rate(), amount:taxAmount};
}
}
}
return taxes;
});
self.totals.hasItemTaxes = ko.computed(function() {
var count = 0;
var taxes = self.totals.itemTaxes();
for (var key in taxes) {
if (taxes.hasOwnProperty(key)) {
count++;
}
}
return count > 0;
});
self.totals.itemTaxRates = ko.computed(function() {
var taxes = self.totals.itemTaxes();
var parts = [];
for (var key in taxes) {
if (taxes.hasOwnProperty(key)) {
parts.push(taxes[key].name + ' ' + (taxes[key].rate*1) + '%');
}
}
return parts.join('<br/>');
});
self.totals.itemTaxAmounts = ko.computed(function() {
var taxes = self.totals.itemTaxes();
var parts = [];
for (var key in taxes) {
if (taxes.hasOwnProperty(key)) {
parts.push(formatMoney(taxes[key].amount, self.client().currency_id()));
}
}
return parts.join('<br/>');
});
self.totals.rawPaidToDate = ko.computed(function() { self.totals.rawPaidToDate = ko.computed(function() {
return accounting.toFixed(self.amount(),2) - accounting.toFixed(self.balance(),2); return accounting.toFixed(self.amount(),2) - accounting.toFixed(self.balance(),2);
}); });
@ -1339,10 +1414,17 @@
total = NINJA.parseFloat(total) + customValue2; total = NINJA.parseFloat(total) + customValue2;
} }
var taxRate = parseFloat(self.tax_rate()); var taxRate = parseFloat(self.tax_rate());
if (taxRate > 0) { if (taxRate > 0) {
total = NINJA.parseFloat(total) + roundToTwo((total * (taxRate/100))); total = NINJA.parseFloat(total) + roundToTwo((total * (taxRate/100)));
} }
var taxes = self.totals.itemTaxes();
for (var key in taxes) {
if (taxes.hasOwnProperty(key)) {
total += taxes[key].amount;
}
}
if (customValue1 && !customTaxes1) { if (customValue1 && !customTaxes1) {
total = NINJA.parseFloat(total) + customValue1; total = NINJA.parseFloat(total) + customValue1;
@ -1364,7 +1446,7 @@
}); });
self.onDragged = function(item) { self.onDragged = function(item) {
refreshPDF(); refreshPDF(true);
} }
} }
@ -1517,8 +1599,8 @@
self.displayName = ko.computed({ self.displayName = ko.computed({
read: function () { read: function () {
var name = self.name() ? self.name() : ''; var name = self.name() ? self.name() : '';
var rate = self.rate() ? parseFloat(self.rate()) + '% ' : ''; var rate = self.rate() ? parseFloat(self.rate()) + '%' : '';
return rate + name; return name + ' ' + rate;
}, },
write: function (value) { write: function (value) {
// do nothing // do nothing
@ -1592,7 +1674,6 @@
if (data) { if (data) {
ko.mapping.fromJS(data, self.mapping, this); ko.mapping.fromJS(data, self.mapping, this);
//if (this.cost()) this.cost(formatMoney(this.cost(), model ? model.invoice().currency_id() : 1, true));
} }
self.wrapped_notes = ko.computed({ self.wrapped_notes = ko.computed({
@ -1612,11 +1693,7 @@
this.totals.rawTotal = ko.computed(function() { this.totals.rawTotal = ko.computed(function() {
var cost = roundToTwo(NINJA.parseFloat(self.cost())); var cost = roundToTwo(NINJA.parseFloat(self.cost()));
var qty = roundToTwo(NINJA.parseFloat(self.qty())); var qty = roundToTwo(NINJA.parseFloat(self.qty()));
var taxRate = NINJA.parseFloat(self.tax_rate()); var value = cost * qty;
var value = cost * qty;
if (taxRate > 0) {
value += value * (taxRate/100);
}
return value ? roundToTwo(value) : 0; return value ? roundToTwo(value) : 0;
}); });
@ -1700,10 +1777,10 @@
function onRecurringEnabled() function onRecurringEnabled()
{ {
if ($('#recurring').prop('checked')) { if ($('#recurring').prop('checked')) {
$('#email_button').attr('disabled', true); $('#emailButton').attr('disabled', true);
model.invoice().partial(''); model.invoice().partial('');
} else { } else {
$('#email_button').removeAttr('disabled'); $('#emailButton').removeAttr('disabled');
} }
} }
@ -1755,23 +1832,24 @@
//} //}
model.invoice().custom_taxes1({{ $account->custom_invoice_taxes1 ? 'true' : 'false' }}); model.invoice().custom_taxes1({{ $account->custom_invoice_taxes1 ? 'true' : 'false' }});
model.invoice().custom_taxes2({{ $account->custom_invoice_taxes2 ? 'true' : 'false' }}); model.invoice().custom_taxes2({{ $account->custom_invoice_taxes2 ? 'true' : 'false' }});
@if (isset($tasks) && $tasks)
// move the blank invoice line item to the end
var blank = model.invoice().invoice_items.pop();
var tasks = {!! $tasks !!};
for (var i=0; i<tasks.length; i++) {
var task = tasks[i];
var item = model.invoice().addItem();
item.notes(task.description);
item.product_key(task.startTime);
item.qty(task.duration);
item.task_public_id(task.publicId);
}
model.invoice().invoice_items.push(blank);
model.invoice().has_tasks(true);
@endif
@endif @endif
@if (isset($tasks) && $tasks)
// move the blank invoice line item to the end
var blank = model.invoice().invoice_items.pop();
var tasks = {!! $tasks !!};
for (var i=0; i<tasks.length; i++) {
var task = tasks[i];
var item = model.invoice().addItem();
item.notes(task.description);
item.product_key(task.startTime);
item.qty(task.duration);
item.task_public_id(task.publicId);
}
model.invoice().invoice_items.push(blank);
model.invoice().has_tasks(true);
@endif
@endif @endif
model.invoice().tax(model.getTaxRate(model.invoice().tax_name(), model.invoice().tax_rate())); model.invoice().tax(model.getTaxRate(model.invoice().tax_name(), model.invoice().tax_rate()));

View File

@ -5,12 +5,8 @@
<script src="{{ asset('js/pdf_viewer.js') }}" type="text/javascript"></script> <script src="{{ asset('js/pdf_viewer.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/compatibility.js') }}" type="text/javascript"></script> <script src="{{ asset('js/compatibility.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/pdfmake.min.js') }}" type="text/javascript"></script>
@if (Auth::user()->account->utf8_invoices) <script src="{{ asset('js/vfs_fonts.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/pdfmake.min.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/vfs_fonts.js') }}" type="text/javascript"></script>
@endif
<script> <script>
@ -37,8 +33,7 @@
invoiceDesign = invoiceDesigns[0]; invoiceDesign = invoiceDesigns[0];
} }
doc = generatePDF(invoice, invoiceDesign.javascript, true); generatePDF(invoice, invoiceDesign.javascript, true, cb);
doc.getDataUrl(cb);
} }
$(function() { $(function() {

View File

@ -63,18 +63,18 @@
logoImages.imageLogoHeight3 = 81/2; logoImages.imageLogoHeight3 = 81/2;
@if (file_exists($account->getLogoPath())) @if (file_exists($account->getLogoPath()))
window.accountLogo = "{{ HTML::image_data($account->getLogoPath()) }}";
if (window.invoice) { if (window.invoice) {
invoice.image = "{{ HTML::image_data($account->getLogoPath()) }}"; invoice.image = window.accountLogo;
invoice.imageWidth = {{ $account->getLogoWidth() }}; invoice.imageWidth = {{ $account->getLogoWidth() }};
invoice.imageHeight = {{ $account->getLogoHeight() }}; invoice.imageHeight = {{ $account->getLogoHeight() }};
} else {
window.accountLogo = "{{ HTML::image_data($account->getLogoPath()) }}";
} }
@endif @endif
var NINJA = NINJA || {}; var NINJA = NINJA || {};
NINJA.primaryColor = "{{ $account->primary_color }}"; NINJA.primaryColor = "{{ $account->primary_color }}";
NINJA.secondaryColor = "{{ $account->secondary_color }}"; NINJA.secondaryColor = "{{ $account->secondary_color }}";
NINJA.fontSize = {{ $account->font_size }};
var invoiceLabels = {!! json_encode($account->getInvoiceLabels()) !!}; var invoiceLabels = {!! json_encode($account->getInvoiceLabels()) !!};
@ -87,8 +87,8 @@
var isRefreshing = false; var isRefreshing = false;
var needsRefresh = false; var needsRefresh = false;
function refreshPDF() { function refreshPDF(force) {
getPDFString(refreshPDFCB); getPDFString(refreshPDFCB, force);
} }
function refreshPDFCB(string) { function refreshPDFCB(string) {
@ -98,7 +98,7 @@
$('#theFrame').attr('src', string).show(); $('#theFrame').attr('src', string).show();
} else { } else {
if (isRefreshing) { if (isRefreshing) {
needsRefresh = true; //needsRefresh = true;
return; return;
} }
isRefreshing = true; isRefreshing = true;

Some files were not shown because too many files have changed in this diff Show More