1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-24 18:27:10 +02:00

Merge remote-tracking branch 'remotes/ninja/master' into ninja

This commit is contained in:
trigras 2015-08-31 15:22:38 +03:00
commit c32f986106
146 changed files with 13894 additions and 5163 deletions

View File

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

24
.gitignore vendored
View File

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

View File

@ -51,7 +51,17 @@ module.exports = function(grunt) {
'public/vendor/knockout-mapping/build/output/knockout.mapping-latest.js',
'public/vendor/knockout-sortable/build/knockout-sortable.min.js',
'public/vendor/underscore/underscore.js',
'public/vendor/bootstrap-datepicker/dist/js/bootstrap-datepicker.js',
'public/vendor/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.de.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.da.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.pt-BR.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.nl.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.fr.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.it.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.lt.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.no.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.es.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.sv.min.js',
'public/vendor/typeahead.js/dist/typeahead.min.js',
'public/vendor/accounting/accounting.min.js',
'public/vendor/spectrum/spectrum.js',

View File

@ -7,6 +7,7 @@ use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\InvoiceRepository;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Models\Invitation;
@ -16,12 +17,14 @@ class SendRecurringInvoices extends Command
protected $name = 'ninja:send-invoices';
protected $description = 'Send recurring invoices';
protected $mailer;
protected $invoiceRepo;
public function __construct(Mailer $mailer)
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo)
{
parent::__construct();
$this->mailer = $mailer;
$this->invoiceRepo = $invoiceRepo;
}
public function fire()
@ -34,74 +37,14 @@ class SendRecurringInvoices extends Command
$this->info(count($invoices).' recurring invoice(s) found');
foreach ($invoices as $recurInvoice) {
if ($recurInvoice->client->deleted_at) {
continue;
$this->info('Processing Invoice '.$recurInvoice->id.' - Should send '.($recurInvoice->shouldSendToday() ? 'YES' : 'NO'));
$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');

View File

@ -40,7 +40,6 @@ use App\Ninja\Repositories\AccountRepository;
use App\Ninja\Mailers\UserMailer;
use App\Ninja\Mailers\ContactMailer;
use App\Events\UserLoggedIn;
use App\Events\UserSettingsChanged;
class AccountController extends BaseController
{
@ -75,18 +74,18 @@ class AccountController extends BaseController
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;
$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
if (Auth::check()) {
return Redirect::to('invoices/create');
}
if (!Utils::isNinja() && (Account::count() > 0 && !$prevUserId)) {
return Redirect::to('/login');
}
if ($guestKey && !$prevUserId) {
$user = User::where('password', '=', $guestKey)->first();
@ -110,7 +109,8 @@ class AccountController extends BaseController
Auth::login($user, true);
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()
@ -149,6 +149,7 @@ class AccountController extends BaseController
public function showSection($section = ACCOUNT_DETAILS, $subSection = false)
{
if ($section == ACCOUNT_DETAILS) {
$primaryUser = Auth::user()->account->users()->orderBy('id')->first();
$data = [
'account' => Account::with('users')->findOrFail(Auth::user()->account_id),
'countries' => Cache::get('countries'),
@ -159,8 +160,9 @@ class AccountController extends BaseController
'datetimeFormats' => Cache::get('datetimeFormats'),
'currencies' => Cache::get('currencies'),
'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'),
'primaryUser' => $primaryUser,
];
return View::make('accounts.details', $data);
@ -188,16 +190,18 @@ class AccountController extends BaseController
} elseif ($section == ACCOUNT_IMPORT_EXPORT) {
return View::make('accounts.import_export', ['title' => trans('texts.import_export')]);
} elseif ($section == ACCOUNT_ADVANCED_SETTINGS) {
$account = Auth::user()->account;
$account = Auth::user()->account->load('country');
$data = [
'account' => $account,
'feature' => $subSection,
'title' => trans('texts.invoice_settings'),
];
if ($subSection == ACCOUNT_INVOICE_DESIGN) {
if ($subSection == ACCOUNT_INVOICE_DESIGN
|| $subSection == ACCOUNT_CUSTOMIZE_DESIGN) {
$invoice = new stdClass();
$client = new stdClass();
$contact = new stdClass();
$invoiceItem = new stdClass();
$client->name = 'Sample Client';
@ -208,11 +212,17 @@ class AccountController extends BaseController
$client->work_phone = '';
$client->work_email = '';
$invoice->invoice_number = Auth::user()->account->getNextInvoiceNumber();
$invoice->invoice_date = date_create()->format('Y-m-d');
$invoice->account = json_decode(Auth::user()->account->toJson());
$invoice->invoice_number = $account->getNextInvoiceNumber();
$invoice->invoice_date = Utils::fromSqlDate(date('Y-m-d'));
$invoice->account = json_decode($account->toJson());
$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->qty = 1;
$invoiceItem->notes = 'Notes';
@ -221,15 +231,28 @@ class AccountController extends BaseController
$invoice->client = $client;
$invoice->invoice_items = [$invoiceItem];
$data['account'] = $account;
$data['invoice'] = $invoice;
$data['invoiceDesigns'] = InvoiceDesign::availableDesigns();
$data['invoiceLabels'] = json_decode($account->invoice_labels) ?: [];
$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) {
$data['invoiceEmail'] = $account->getEmailTemplate(ENTITY_INVOICE);
$data['quoteEmail'] = $account->getEmailTemplate(ENTITY_QUOTE);
$data['paymentEmail'] = $account->getEmailTemplate(ENTITY_PAYMENT);
$data['emailFooter'] = $account->getEmailFooter();
$data['emailFooter'] = $account->getEmailFooter();
$data['title'] = trans('texts.email_templates');
} else if ($subSection == ACCOUNT_USER_MANAGEMENT) {
$data['title'] = trans('texts.users_and_tokens');
@ -263,6 +286,8 @@ class AccountController extends BaseController
return AccountController::saveInvoiceSettings();
} elseif ($subSection == ACCOUNT_INVOICE_DESIGN) {
return AccountController::saveInvoiceDesign();
} elseif ($subSection == ACCOUNT_CUSTOMIZE_DESIGN) {
return AccountController::saveCustomizeDesign();
} elseif ($subSection == ACCOUNT_EMAIL_TEMPLATES) {
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()
{
if (Auth::user()->account->isPro()) {
@ -322,7 +360,6 @@ class AccountController extends BaseController
$account->share_counter = Input::get('share_counter') ? 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;
if (!$account->share_counter) {
@ -596,9 +633,10 @@ class AccountController extends BaseController
{
$rules = array(
'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) {
$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->email = trim(strtolower(Input::get('email')));
$user->phone = trim(Input::get('phone'));
if (Utils::isNinjaDev()) {
$user->dark_mode = Input::get('dark_mode') ? true : false;
}
$user->save();
}
@ -654,21 +695,28 @@ class AccountController extends BaseController
if ($file = Input::file('logo')) {
$path = Input::file('logo')->getRealPath();
File::delete('logo/'.$account->account_key.'.jpg');
File::delete('logo/'.$account->account_key.'.png');
$image = Image::make($path);
$mimeType = $file->getMimeType();
if ($image->width() == 200 && $mimeType == 'image/jpeg') {
if ($mimeType == 'image/jpeg') {
$file->move('logo/', $account->account_key . '.jpg');
} else if ($mimeType == 'image/png') {
$file->move('logo/', $account->account_key . '.png');
} else {
$image->resize(200, 120, function ($constraint) {
$constraint->aspectRatio();
});
Image::canvas($image->width(), $image->height(), '#FFFFFF')->insert($image)->save($account->getLogoPath());
if (extension_loaded('fileinfo')) {
$image = Image::make($path);
$image->resize(200, 120, function ($constraint) {
$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'));
return Redirect::to('company/details');
@ -678,6 +726,7 @@ class AccountController extends BaseController
public function removeLogo()
{
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'));
@ -717,9 +766,9 @@ class AccountController extends BaseController
$user->username = $user->email;
$user->password = bcrypt(trim(Input::get('new_password')));
$user->registered = true;
$user->save();
$user->save();
if (Utils::isNinja()) {
if (Utils::isNinjaProd()) {
$this->userMailer->sendConfirmation($user);
}

View File

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

View File

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

View File

@ -5,6 +5,7 @@ use DB;
use View;
use App\Models\Activity;
use App\Models\Invoice;
use App\Models\Payment;
class DashboardController extends BaseController
{
@ -44,47 +45,89 @@ class DashboardController extends BaseController
->where('accounts.id', '=', Auth::user()->account_id)
->where('clients.is_deleted', '=', false)
->where('invoices.is_deleted', '=', false)
->where('invoices.is_quote', '=', false)
->where('invoices.is_recurring', '=', 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();
$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)
->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) {
$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();
$pastDue = 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'))
->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 = [
'paidToDate' => $paidToDate,
'averageInvoice' => $averageInvoice,
//'billedClients' => $metrics ? $metrics->billed_clients : 0,
'invoicesSent' => $metrics ? $metrics->invoices_sent : 0,
'activeClients' => $metrics ? $metrics->active_clients : 0,
'activities' => $activities,
'pastDue' => $pastDue,
'upcoming' => $upcoming,
'title' => trans('texts.dashboard'),
];
'account' => Auth::user()->account,
'paidToDate' => $paidToDate,
'balances' => $balances,
'averageInvoice' => $averageInvoice,
'invoicesSent' => $metrics ? $metrics->invoices_sent : 0,
'activeClients' => $metrics ? $metrics->active_clients : 0,
'activities' => $activities,
'pastDue' => $pastDue,
'upcoming' => $upcoming,
'payments' => $payments,
'title' => trans('texts.dashboard'),
];
return View::make('dashboard', $data);
}

View File

@ -43,14 +43,15 @@ class HomeController extends BaseController
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);
Auth::user()->clearSession();
Auth::logout();
}
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 {
return View::make('public.header', ['invoiceNow' => true]);
}
@ -72,9 +73,9 @@ class HomeController extends BaseController
$user->news_feed_id = $newsFeedId;
$user->save();
}
Session::forget('news_feed_message');
}
Session::forget('news_feed_message');
return 'success';
}

View File

@ -26,7 +26,11 @@ class InvoiceApiController extends Controller
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
foreach ($invoices as $key => $invoice) {
@ -50,12 +54,14 @@ class InvoiceApiController extends Controller
$error = null;
// 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();
} else {
} else if (isset($data['invoice_number'])) {
$invoice = Invoice::scope()->where('invoice_number', '=', $data['invoice_number'])->first();
if ($invoice) {
$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;
$invoice = $this->invoiceRepo->save(false, $data, false);
$invitation = Invitation::createNew();
$invitation->invoice_id = $invoice->id;
$invitation->contact_id = $client->contacts[0]->id;
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
$invitation->save();
if (!isset($data['id'])) {
$invitation = Invitation::createNew();
$invitation->invoice_id = $invoice->id;
$invitation->contact_id = $client->contacts[0]->id;
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
$invitation->save();
}
if (isset($data['email_invoice']) && $data['email_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']),
];
$recurringInvoices = Invoice::scope()
->where('is_recurring', '=', true);
$recurringInvoices = Invoice::scope()->where('is_recurring', '=', true);
if (Session::get('show_trash:invoice')) {
$recurringInvoices->withTrashed();
@ -86,11 +85,12 @@ class InvoiceController extends BaseController
}
$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 = [
'color' => $color,
'hideLogo' => Session::get('white_label'),
'hideLogo' => $account->isWhiteLabel(),
'title' => trans('texts.invoices'),
'entityType' => ENTITY_INVOICE,
'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('invitation_key', $invitationKey);
Session::set('white_label', $account->isWhiteLabel());
$account->loadLocalizationSettings();
@ -213,6 +212,12 @@ class InvoiceController extends BaseController
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
$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->setVisible([
'first_name',
@ -250,7 +255,7 @@ class InvoiceController extends BaseController
'invoiceLabels' => $account->getInvoiceLabels(),
'contact' => $contact,
'paymentTypes' => $paymentTypes,
'paymentURL' => $paymentURL
'paymentURL' => $paymentURL,
);
return View::make('invoices.view', $data);
@ -277,7 +282,7 @@ class InvoiceController extends BaseController
$method = 'POST';
$url = "{$entityType}s";
} else {
Utils::trackViewed($invoice->invoice_number.' - '.$invoice->client->getDisplayName(), $invoice->getEntityType());
Utils::trackViewed($invoice->getDisplayName().' - '.$invoice->client->getDisplayName(), $invoice->getEntityType());
$method = 'PUT';
$url = "{$entityType}s/{$publicId}";
}
@ -321,19 +326,21 @@ class InvoiceController extends BaseController
$actions[] = ['url' => 'javascript:onArchiveClick()', 'label' => trans("texts.archive_{$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(
'entityType' => $entityType,
'showBreadcrumbs' => $clone,
'account' => $invoice->account,
'invoice' => $invoice,
'data' => false,
'method' => $method,
'invitationContactIds' => $contactIds,
'url' => $url,
'title' => trans("texts.edit_{$entityType}"),
'client' => $invoice->client,
'actions' => $actions);
'client' => $invoice->client,
'isRecurring' => $invoice->is_recurring,
'actions' => $actions,
'lastSent' => $lastSent);
$data = array_merge($data, self::getViewModel());
// Set the invitation link on the client's contacts
@ -356,11 +363,10 @@ class InvoiceController extends BaseController
return View::make('invoices.edit', $data);
}
public function create($clientPublicId = 0)
public function create($clientPublicId = 0, $isRecurring = false)
{
$client = null;
$invoiceNumber = Auth::user()->account->getNextInvoiceNumber();
$account = Account::with('country')->findOrFail(Auth::user()->account_id);
$invoiceNumber = $isRecurring ? microtime(true) : Auth::user()->account->getNextInvoiceNumber();
if ($clientPublicId) {
$client = Client::scope($clientPublicId)->firstOrFail();
@ -368,20 +374,24 @@ class InvoiceController extends BaseController
$data = array(
'entityType' => ENTITY_INVOICE,
'account' => $account,
'invoice' => null,
'data' => Input::old('data'),
'invoiceNumber' => $invoiceNumber,
'method' => 'POST',
'url' => 'invoices',
'title' => trans('texts.new_invoice'),
'client' => $client,
'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null);
'isRecurring' => $isRecurring,
'client' => $client);
$data = array_merge($data, self::getViewModel());
return View::make('invoices.edit', $data);
}
public function createRecurring($clientPublicId = 0)
{
return self::create($clientPublicId, true);
}
private static function getViewModel()
{
$recurringHelp = '';
@ -396,7 +406,7 @@ class InvoiceController extends BaseController
}
return [
'account' => Auth::user()->account,
'account' => Auth::user()->account->load('country'),
'products' => Product::scope()->orderBy('id')->get(array('product_key', 'notes', 'cost', 'qty')),
'countries' => Cache::get('countries'),
'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(),
@ -405,7 +415,7 @@ class InvoiceController extends BaseController
'sizes' => Cache::get('sizes'),
'paymentTerms' => Cache::get('paymentTerms'),
'industries' => Cache::get('industries'),
'invoiceDesigns' => InvoiceDesign::availableDesigns(),
'invoiceDesigns' => InvoiceDesign::getDesigns(),
'frequencies' => array(
1 => 'Weekly',
2 => 'Two weeks',
@ -417,7 +427,7 @@ class InvoiceController extends BaseController
),
'recurringHelp' => $recurringHelp,
'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);
} elseif ($action == 'email') {
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) {
$message = trans("texts.emailed_{$entityType}");
Session::flash('message', $message);
@ -577,7 +596,7 @@ class InvoiceController extends BaseController
}
if ($action == 'restore' && $count == 1) {
return Redirect::to("{$entityType}s/".$ids[0]);
return Redirect::to("{$entityType}s/".Utils::getFirst($ids));
} else {
return Redirect::to("{$entityType}s");
}
@ -646,7 +665,7 @@ class InvoiceController extends BaseController
'invoice' => $invoice,
'versionsJson' => json_encode($versionsJson),
'versionsSelect' => $versionsSelect,
'invoiceDesigns' => InvoiceDesign::availableDesigns(),
'invoiceDesigns' => InvoiceDesign::getDesigns(),
];
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();
$color = $invitation->account->primary_color ? $invitation->account->primary_color : '#0b4d78';
$account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => Session::get('white_label'),
'hideLogo' => $account->isWhiteLabel(),
'entityType' => ENTITY_PAYMENT,
'title' => trans('texts.payments'),
'columns' => Utils::trans(['invoice', 'transaction_reference', 'method', 'payment_amount', 'payment_date'])
@ -233,33 +234,41 @@ class PaymentController extends BaseController
private function convertInputForOmnipay($input)
{
$country = Country::find($input['country_id']);
return [
$data = [
'firstName' => $input['first_name'],
'lastName' => $input['last_name'],
'number' => $input['card_number'],
'expiryMonth' => $input['expiration_month'],
'expiryYear' => $input['expiration_year'],
'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)
{
$invoice = $invitation->invoice;
$account = $invoice->account;
$key = $invoice->account_id.'-'.$invoice->invoice_number;
$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,
'countries' => Cache::get('countries'),
'currencyId' => $client->getCurrencyId(),
'currencyCode' => $client->currency ? $client->currency->code : ($account->currency ? $account->currency->code : 'USD'),
'account' => $client->account,
'hideLogo' => $account->isWhiteLabel(),
'showAddress' => $accountGateway->show_address,
];
return View::make('payments.payment', $data);
@ -378,6 +389,7 @@ class PaymentController extends BaseController
'currencyId' => 1,
'paymentTitle' => $affiliate->payment_title,
'paymentSubtitle' => $affiliate->payment_subtitle,
'showAddress' => true,
];
return View::make('payments.payment', $data);
@ -498,19 +510,30 @@ class PaymentController extends BaseController
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',
'last_name' => 'required',
'card_number' => 'required',
'expiration_month' => 'required',
'expiration_year' => 'required',
'cvv' => 'required',
'address1' => 'required',
'city' => 'required',
'state' => 'required',
'postal_code' => 'required',
'country_id' => 'required',
);
];
if ($accountGateway->show_address) {
$rules = array_merge($rules, [
'address1' => 'required',
'city' => 'required',
'state' => 'required',
'postal_code' => 'required',
'country_id' => 'required',
]);
}
if ($onSite) {
$validator = Validator::make(Input::all(), $rules);
@ -521,25 +544,19 @@ class PaymentController extends BaseController
->withErrors($validator)
->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;
$client = $invoice->client;
$account = $client->account;
$accountGateway = $account->getGatewayByType(Session::get('payment_type'));
/*
if ($onSite) {
$client->address1 = trim(Input::get('address1'));
$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();
if ($accountGateway->update_address) {
$client->address1 = trim(Input::get('address1'));
$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->country_id = Input::get('country_id');
$client->save();
}
}
*/
try {
$gateway = self::createGateway($accountGateway);
$details = self::getPaymentDetails($invitation, ($useToken || !$onSite) ? false : Input::all());

View File

@ -16,7 +16,11 @@ class QuoteApiController extends Controller
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);
$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();
$color = $invitation->account->primary_color ? $invitation->account->primary_color : '#0b4d78';
$account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => Session::get('white_label'),
'hideLogo' => $account->isWhiteLabel(),
'title' => trans('texts.quotes'),
'entityType' => ENTITY_QUOTE,
'columns' => Utils::trans(['quote_number', 'quote_date', 'quote_total', 'due_date']),
@ -156,8 +157,9 @@ class QuoteController extends BaseController
'sizes' => Cache::get('sizes'),
'paymentTerms' => Cache::get('paymentTerms'),
'industries' => Cache::get('industries'),
'invoiceDesigns' => InvoiceDesign::availableDesigns(),
'invoiceLabels' => Auth::user()->account->getInvoiceLabels()
'invoiceDesigns' => InvoiceDesign::getDesigns(),
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
'isRecurring' => false,
];
}
@ -183,7 +185,11 @@ class QuoteController extends BaseController
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)

View File

@ -1,6 +1,7 @@
<?php namespace App\Http\Controllers;
use Auth;
use Config;
use Input;
use Utils;
use DB;
@ -19,7 +20,9 @@ class ReportController extends BaseController
$fileName = storage_path() . '/dataviz_sample.txt';
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();
$clients = $account->clients->toJson();
} elseif (file_exists($fileName)) {
@ -149,53 +152,91 @@ class ReportController extends BaseController
$reportTotals['balance'][$currencyId] += $record->balance;
}
if ($action == 'export') {
if ($action == 'export')
{
self::export($exportData, $reportTotals);
}
}
if ($enableChart) {
foreach ([ENTITY_INVOICE, ENTITY_PAYMENT, ENTITY_CREDIT] as $entityType) {
$records = DB::table($entityType.'s')
->select(DB::raw('sum(amount) as total, concat(YEAR('.$entityType.'_date), '.$groupBy.'('.$entityType.'_date)) 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 ($enableChart)
{
foreach ([ENTITY_INVOICE, ENTITY_PAYMENT, ENTITY_CREDIT] as $entityType)
{
// SQLite does not support the YEAR(), MONTH(), WEEK() and similar functions.
// Let's see if SQLite is being used.
if (Config::get('database.connections.'.Config::get('database.default').'.driver') == 'sqlite')
{
// 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)
->where('is_recurring', '=', false);
}
$totals = $records->lists('total');
$dates = $records->lists($groupBy);
$data = array_combine($dates, $totals);
$dates = $records->lists($groupBy);
$data = array_combine($dates, $totals);
$padding = $groupBy == 'DAYOFYEAR' ? 'day' : ($groupBy == 'WEEK' ? 'week' : 'month');
$endDate->modify('+1 '.$padding);
$interval = new DateInterval('P1'.substr($groupBy, 0, 1));
$period = new DatePeriod($startDate, $interval, $endDate);
$period = new DatePeriod($startDate, $interval, $endDate);
$endDate->modify('-1 '.$padding);
$totals = [];
foreach ($period as $d) {
foreach ($period as $d)
{
$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;
if ($entityType == ENTITY_INVOICE) {
if ($entityType == ENTITY_INVOICE)
{
$labelFormat = $groupBy == 'DAYOFYEAR' ? 'j' : ($groupBy == 'WEEK' ? 'W' : 'F');
$label = $d->format($labelFormat);
$label = $d->format($labelFormat);
$labels[] = $label;
}
}
$max = max($totals);
if ($max > 0) {
if ($max > 0)
{
$datasets[] = [
'totals' => $totals,
'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 Session;
use DropdownButton;
use DateTime;
use DateTimeZone;
use App\Models\Client;
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\InvoiceRepository;
class TaskController extends BaseController
{
protected $taskRepo;
public function __construct(TaskRepository $taskRepo)
public function __construct(TaskRepository $taskRepo, InvoiceRepository $invoiceRepo)
{
parent::__construct();
$this->taskRepo = $taskRepo;
$this->invoiceRepo = $invoiceRepo;
}
/**
@ -47,10 +36,7 @@ class TaskController extends BaseController
*/
public function index()
{
if (!Auth::user()->account->timezone) {
$link = link_to('/company/details', trans('texts.click_here'), ['target' => '_blank']);
Session::flash('warning', trans('texts.timezone_unset', ['link' => $link]));
}
self::checkTimezone();
return View::make('list', array(
'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)) : ''; });
}
return $table->addColumn('start_time', function($model) { return Utils::fromSqlDateTime($model->start_time); })
->addColumn('duration', function($model) { return gmdate('H:i:s', $model->is_running ? time() - strtotime($model->start_time) : $model->duration); })
return $table->addColumn('created_at', function($model) { return Task::calcStartTime($model); })
->addColumn('time_log', function($model) { return gmdate('H:i:s', Task::calcDuration($model)); })
->addColumn('description', function($model) { return $model->description; })
->addColumn('invoice_number', function($model) { return self::getStatusLabel($model); })
->addColumn('dropdown', function ($model) {
@ -142,12 +128,15 @@ class TaskController extends BaseController
*/
public function create($clientPublicId = 0)
{
self::checkTimezone();
$data = [
'task' => null,
'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId,
'method' => 'POST',
'url' => 'tasks',
'title' => trans('texts.new_task'),
'minuteOffset' => Utils::getTiemstampOffset(),
];
$data = array_merge($data, self::getViewModel());
@ -163,14 +152,24 @@ class TaskController extends BaseController
*/
public function edit($publicId)
{
self::checkTimezone();
$task = Task::scope($publicId)->with('client', 'invoice')->firstOrFail();
$actions = [];
if ($task->invoice) {
$actions[] = ['url' => URL::to("inovices/{$task->invoice->public_id}/edit"), 'label' => trans("texts.view_invoice")];
} 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;
if (!$task->trashed()) {
$actions[] = ['url' => 'javascript:submitAction("archive")', 'label' => trans('texts.archive_task')];
@ -178,15 +177,16 @@ class TaskController extends BaseController
} else {
$actions[] = ['url' => 'javascript:submitAction("restore")', 'label' => trans('texts.restore_task')];
}
$data = [
'task' => $task,
'clientPublicId' => $task->client ? $task->client->public_id : 0,
'method' => 'PUT',
'url' => 'tasks/'.$publicId,
'title' => trans('texts.edit_task'),
'duration' => $task->resume_time ? ($task->duration + strtotime('now') - strtotime($task->resume_time)) : (strtotime('now') - strtotime($task->start_time)),
'actions' => $actions
'duration' => $task->is_running ? $task->getCurrentDuration() : $task->getDuration(),
'actions' => $actions,
'minuteOffset' => Utils::getTiemstampOffset(),
];
$data = array_merge($data, self::getViewModel());
@ -216,7 +216,7 @@ class TaskController extends BaseController
{
$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();
}
@ -235,12 +235,11 @@ class TaskController extends BaseController
$this->taskRepo->save($ids, ['action' => $action]);
Session::flash('message', trans('texts.stopped_task'));
return Redirect::to('tasks');
} else if ($action == 'invoice') {
} else if ($action == 'invoice' || $action == 'add_to_invoice') {
$tasks = Task::scope($ids)->with('client')->get();
$clientPublicId = false;
$data = [];
foreach ($tasks as $task) {
if ($task->client) {
if (!$clientPublicId) {
@ -258,16 +257,21 @@ class TaskController extends BaseController
Session::flash('error', trans('texts.task_error_invoiced'));
return Redirect::to('tasks');
}
$data[] = [
'publicId' => $task->public_id,
'description' => $task->description,
'startTime' => Utils::fromSqlDateTime($task->start_time),
'duration' => round($task->duration / (60 * 60), 2)
'startTime' => $task->getStartTime(),
'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 {
$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->save();
Session::flash('message', trans('texts.security.updated_settings'));
Session::flash('message', trans('texts.updated_settings'));
return Redirect::to('/dashboard');
}
@ -132,9 +132,12 @@ class UserController extends BaseController
*/
public function create()
{
if (!Auth::user()->confirmed) {
if (!Auth::user()->registered) {
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');
}
@ -374,6 +377,11 @@ class UserController extends BaseController
Session::put(SESSION_USER_ACCOUNTS, $users);
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',
'paymentTypes' => 'App\Models\PaymentType',
'countries' => 'App\Models\Country',
'invoiceDesigns' => 'App\Models\InvoiceDesign',
];
foreach ($cachedTables as $name => $class) {
if (Input::has('clear_cache')) {
@ -74,7 +75,7 @@ class StartupCheck
$count = Session::get(SESSION_COUNTER, 0);
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;
if (Utils::isNinja()) {
$data = Utils::getNewsFeedResponse();
@ -127,37 +128,39 @@ class StartupCheck
}
// Check if the user is claiming a license (ie, additional invoices, white label, etc.)
$claimingLicense = Utils::startsWith($_SERVER['REQUEST_URI'], '/claim_license');
if (!$claimingLicense && Input::has('license_key') && Input::has('product_id')) {
$licenseKey = Input::get('license_key');
$productId = Input::get('product_id');
if (isset($_SERVER['REQUEST_URI'])) {
$claimingLicense = Utils::startsWith($_SERVER['REQUEST_URI'], '/claim_license');
if (!$claimingLicense && Input::has('license_key') && Input::has('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) {
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();
Session::flash('message', trans('texts.bought_designs'));
}
} 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'));
}
} 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'));
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'));
}

View File

@ -26,6 +26,13 @@ Route::post('setup', 'AppController@doSetup');
Route::get('install', 'AppController@install');
Route::get('update', 'AppController@update');
/*
// Codeception code coverage
Route::get('/c3.php', function () {
include '../c3.php';
});
*/
// Public pages
Route::get('/', 'HomeController@showIndex');
Route::get('terms', 'HomeController@showTerms');
@ -95,6 +102,7 @@ Route::group(['middleware' => 'auth'], function() {
Route::post('users/change_password', 'UserController@changePassword');
Route::get('/switch_account/{user_id}', 'UserController@switchAccount');
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::resource('tokens', 'TokenController');
@ -130,7 +138,6 @@ Route::group(['middleware' => 'auth'], function() {
Route::get('tasks/create/{client_id?}', 'TaskController@create');
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('invoices/invoice_history/{invoice_id}', 'InvoiceController@invoiceHistory');
@ -139,6 +146,7 @@ Route::group(['middleware' => 'auth'], function() {
Route::resource('invoices', 'InvoiceController');
Route::get('api/invoices/{client_id?}', array('as'=>'api.invoices', 'uses'=>'InvoiceController@getDatatable'));
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::post('invoices/bulk', 'InvoiceController@bulk');
@ -211,225 +219,229 @@ Route::get('/forgot_password', function() {
});
define('CONTACT_EMAIL', Config::get('mail.from.address'));
define('CONTACT_NAME', Config::get('mail.from.name'));
define('SITE_URL', Config::get('app.url'));
if (!defined('CONTACT_EMAIL')) {
define('CONTACT_EMAIL', Config::get('mail.from.address'));
define('CONTACT_NAME', Config::get('mail.from.name'));
define('SITE_URL', Config::get('app.url'));
define('ENV_DEVELOPMENT', 'local');
define('ENV_STAGING', 'staging');
define('ENV_PRODUCTION', 'fortrabbit');
define('ENV_DEVELOPMENT', 'local');
define('ENV_STAGING', 'staging');
define('ENV_PRODUCTION', 'fortrabbit');
define('RECENTLY_VIEWED', 'RECENTLY_VIEWED');
define('ENTITY_CLIENT', 'client');
define('ENTITY_INVOICE', 'invoice');
define('ENTITY_RECURRING_INVOICE', 'recurring_invoice');
define('ENTITY_PAYMENT', 'payment');
define('ENTITY_CREDIT', 'credit');
define('ENTITY_QUOTE', 'quote');
define('ENTITY_TASK', 'task');
define('RECENTLY_VIEWED', 'RECENTLY_VIEWED');
define('ENTITY_CLIENT', 'client');
define('ENTITY_INVOICE', 'invoice');
define('ENTITY_RECURRING_INVOICE', 'recurring_invoice');
define('ENTITY_PAYMENT', 'payment');
define('ENTITY_CREDIT', 'credit');
define('ENTITY_QUOTE', 'quote');
define('ENTITY_TASK', 'task');
define('PERSON_CONTACT', 'contact');
define('PERSON_USER', 'user');
define('PERSON_CONTACT', 'contact');
define('PERSON_USER', 'user');
define('ACCOUNT_DETAILS', 'details');
define('ACCOUNT_NOTIFICATIONS', 'notifications');
define('ACCOUNT_IMPORT_EXPORT', 'import_export');
define('ACCOUNT_PAYMENTS', 'payments');
define('ACCOUNT_MAP', 'import_map');
define('ACCOUNT_EXPORT', 'export');
define('ACCOUNT_PRODUCTS', 'products');
define('ACCOUNT_ADVANCED_SETTINGS', 'advanced_settings');
define('ACCOUNT_INVOICE_SETTINGS', 'invoice_settings');
define('ACCOUNT_INVOICE_DESIGN', 'invoice_design');
define('ACCOUNT_CHART_BUILDER', 'chart_builder');
define('ACCOUNT_USER_MANAGEMENT', 'user_management');
define('ACCOUNT_DATA_VISUALIZATIONS', 'data_visualizations');
define('ACCOUNT_EMAIL_TEMPLATES', 'email_templates');
define('ACCOUNT_TOKEN_MANAGEMENT', 'token_management');
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('ACCOUNT_DETAILS', 'details');
define('ACCOUNT_NOTIFICATIONS', 'notifications');
define('ACCOUNT_IMPORT_EXPORT', 'import_export');
define('ACCOUNT_PAYMENTS', 'payments');
define('ACCOUNT_MAP', 'import_map');
define('ACCOUNT_EXPORT', 'export');
define('ACCOUNT_PRODUCTS', 'products');
define('ACCOUNT_ADVANCED_SETTINGS', 'advanced_settings');
define('ACCOUNT_INVOICE_SETTINGS', 'invoice_settings');
define('ACCOUNT_INVOICE_DESIGN', 'invoice_design');
define('ACCOUNT_CHART_BUILDER', 'chart_builder');
define('ACCOUNT_USER_MANAGEMENT', 'user_management');
define('ACCOUNT_DATA_VISUALIZATIONS', 'data_visualizations');
define('ACCOUNT_EMAIL_TEMPLATES', 'email_templates');
define('ACCOUNT_TOKEN_MANAGEMENT', 'token_management');
define('ACCOUNT_CUSTOMIZE_DESIGN', 'customize_design');
define('PAYMENT_LIBRARY_OMNIPAY', 1);
define('PAYMENT_LIBRARY_PHP_PAYMENTS', 2);
define('ACTIVITY_TYPE_CREATE_CLIENT', 1);
define('ACTIVITY_TYPE_ARCHIVE_CLIENT', 2);
define('ACTIVITY_TYPE_DELETE_CLIENT', 3);
define('GATEWAY_AUTHORIZE_NET', 1);
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('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('EVENT_CREATE_CLIENT', 1);
define('EVENT_CREATE_INVOICE', 2);
define('EVENT_CREATE_QUOTE', 3);
define('EVENT_CREATE_PAYMENT', 4);
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('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.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('ACTIVITY_TYPE_CREATE_CREDIT', 14);
define('ACTIVITY_TYPE_UPDATE_CREDIT', 15);
define('ACTIVITY_TYPE_ARCHIVE_CREDIT', 16);
define('ACTIVITY_TYPE_DELETE_CREDIT', 17);
define('COUNT_FREE_DESIGNS', 4);
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('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('PRO_PLAN_PRICE', 50);
define('WHITE_LABEL_PRICE', 20);
define('INVOICE_DESIGNS_PRICE', 10);
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('USER_TYPE_SELF_HOST', 'SELF_HOST');
define('USER_TYPE_CLOUD_HOST', 'CLOUD_HOST');
define('NEW_VERSION_AVAILABLE', 'NEW_VERSION_AVAILABLE');
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('TOKEN_BILLING_DISABLED', 1);
define('TOKEN_BILLING_OPT_IN', 2);
define('TOKEN_BILLING_OPT_OUT', 3);
define('TOKEN_BILLING_ALWAYS', 4);
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_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');
define('PAYMENT_TYPE_CREDIT', 1);
define('CUSTOM_DESIGN', 11);
/*
define('GATEWAY_AMAZON', 30);
define('GATEWAY_BLUEPAY', 31);
define('GATEWAY_BRAINTREE', 32);
define('GATEWAY_GOOGLE', 33);
define('GATEWAY_QUICKBOOKS', 35);
*/
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);
$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('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('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)
{
return ucwords(trans($text));
}
define('DEFAULT_TIMEZONE', 'US/Eastern');
define('DEFAULT_CURRENCY', 1); // US Dollar
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
function otrans($text)
{
$locale = Session::get(SESSION_LOCALE);
define('RESULT_SUCCESS', 'success');
define('RESULT_FAILURE', 'failure');
if ($locale == 'en') {
return trans($text);
} else {
$string = trans($text);
$english = trans($text, [], 'en');
return $string != $english ? $string : '';
define('PAYMENT_LIBRARY_OMNIPAY', 1);
define('PAYMENT_LIBRARY_PHP_PAYMENTS', 2);
define('GATEWAY_AUTHORIZE_NET', 1);
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);
}
*/
*/

View File

@ -3,6 +3,7 @@
use Auth;
use Cache;
use DB;
use App;
use Schema;
use Session;
use Request;
@ -61,7 +62,7 @@ class Utils
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()
@ -69,6 +70,11 @@ class Utils
return Auth::check() && Auth::user()->isPro();
}
public static function isEnglish()
{
return App::getLocale() == 'en';
}
public static function getUserType()
{
if (Utils::isNinja()) {
@ -251,6 +257,10 @@ class Utils
$currency = Currency::find(1);
}
if (!$value) {
$value = 0;
}
Cache::add('currency', $currency, DEFAULT_QUERY_CACHE);
return $currency->symbol.number_format($value, $currency->precision, $currency->decimal_separator, $currency->thousand_separator);
@ -315,16 +325,27 @@ class Utils
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)
{
if (!$date) {
return;
}
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
//$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
$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;
}
@ -335,11 +356,11 @@ class Utils
return '';
}
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
//$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
$dateTime = DateTime::createFromFormat('Y-m-d', $date);
$dateTime->setTimeZone(new DateTimeZone($timezone));
//$dateTime->setTimeZone(new DateTimeZone($timezone));
return $formatResult ? $dateTime->format($format) : $dateTime;
}
@ -352,7 +373,7 @@ class Utils
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
$format = Session::get(SESSION_DATETIME_FORMAT, DEFAULT_DATETIME_FORMAT);
$dateTime = DateTime::createFromFormat('Y-m-d H:i:s', $date);
$dateTime->setTimeZone(new DateTimeZone($timezone));
@ -386,10 +407,12 @@ class Utils
}
$object = new stdClass();
$object->accountId = Auth::user()->account_id;
$object->url = $url;
$object->name = ucwords($type).': '.$name;
$data = [];
$counts = [];
for ($i = 0; $i<count($viewed); $i++) {
$item = $viewed[$i];
@ -398,12 +421,22 @@ class Utils
continue;
}
// temporary fix to check for new property in session
if (!property_exists($item, 'accountId')) {
continue;
}
array_unshift($data, $item);
if (isset($counts[$item->accountId])) {
$counts[$item->accountId]++;
} else {
$counts[$item->accountId] = 1;
}
}
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);
}
@ -677,4 +710,37 @@ class Utils
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)
{
$account = Auth::user()->account;
$account->loadLocalizationSettings();
if (Auth::check()) {
$account = Auth::user()->account;
$account->loadLocalizationSettings();
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
Session::put(SESSION_USER_ACCOUNTS, $users);
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
Session::put(SESSION_USER_ACCOUNTS, $users);
}
}
}

View File

@ -4,7 +4,9 @@ use Eloquent;
use Utils;
use Session;
use DateTime;
use Event;
use App;
use App\Events\UserSettingsChanged;
use Illuminate\Database\Eloquent\SoftDeletes;
class Account extends Eloquent
@ -12,6 +14,12 @@ class Account extends Eloquent
use SoftDeletes;
protected $dates = ['deleted_at'];
/*
protected $casts = [
'hide_quantity' => 'boolean',
];
*/
public function users()
{
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()
{
if ($this->name) {
@ -142,7 +155,9 @@ class Account extends Eloquent
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()
@ -171,34 +186,36 @@ class Account extends Eloquent
{
$counter = $isQuote && !$this->share_counter ? $this->quote_number_counter : $this->invoice_number_counter;
$prefix .= $isQuote ? $this->quote_number_prefix : $this->invoice_number_prefix;
$counterOffset = 0;
// confirm the invoice number isn't already taken
do {
$number = $prefix.str_pad($counter, 4, "0", STR_PAD_LEFT);
$check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first();
$counter++;
$counterOffset++;
} 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;
}
public function incrementCounter($invoiceNumber, $isQuote = false, $isRecurring)
public function incrementCounter($isQuote = false)
{
// check if the user modified the invoice number
if (!$isRecurring && $invoiceNumber != $this->getNextInvoiceNumber($isQuote)) {
$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
if ($isQuote && !$this->share_counter) {
$this->quote_number_counter += 1;
} else {
if ($isQuote && !$this->share_counter) {
$this->quote_number_counter += 1;
} else {
$this->invoice_number_counter += 1;
}
$this->invoice_number_counter += 1;
}
$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_CURRENCY, $this->currency_id ? $this->currency_id : DEFAULT_CURRENCY);
Session::put(SESSION_LOCALE, $this->language_id ? $this->language->locale : DEFAULT_LOCALE);
App::setLocale(session(SESSION_LOCALE));
}
public function getInvoiceLabels()
@ -258,13 +277,18 @@ class Account extends Eloquent
'rate',
'hours',
'balance',
'from',
'to',
'invoice_to',
'details',
'invoice_no',
];
foreach ($fields as $field) {
if (isset($custom[$field]) && $custom[$field]) {
$data[$field] = $custom[$field];
} 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()
{
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)
@ -335,6 +359,8 @@ class Account extends Eloquent
'invoice_status_id',
'invoice_items',
'created_at',
'is_recurring',
'is_quote',
]);
foreach ($invoice->invoice_items as $invoiceItem) {
@ -407,9 +433,6 @@ class Account extends Eloquent
}
}
Account::updating(function ($account) {
// Lithuanian requires UTF8 support
if (!Utils::isPro()) {
$account->utf8_invoices = ($account->language_id == 13) ? 1 : 0;
}
Account::updated(function ($account) {
Event::fire(new UserSettingsChanged());
});

View File

@ -214,6 +214,8 @@ class Activity extends Eloquent
if ($invoice->isPaid() && $invoice->balance > 0) {
$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;
}
public function getDisplayName()
{
if ($this->name) {
return $this->name;
}
$this->load('contacts');
$contact = $this->contacts()->first();
return $contact->getDisplayName();
@ -152,11 +150,15 @@ class Client extends EntityModel
public function getCurrencyId()
{
if ($this->currency_id) {
return $this->currency_id;
}
if (!$this->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()
{
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;
}
public function getDisplayName()
{
return $this->getName();
}
// Remap ids to public_ids and show name
public function toPublicArray()
{

View File

@ -48,6 +48,11 @@ class Invoice extends EntityModel
return $this->belongsTo('App\Models\Invoice');
}
public function recurring_invoices()
{
return $this->hasMany('App\Models\Invoice', 'recurring_invoice_id');
}
public function invitations()
{
return $this->hasMany('App\Models\Invitation')->orderBy('invitations.contact_id');
@ -55,7 +60,7 @@ class Invoice extends EntityModel
public function getName()
{
return $this->invoice_number;
return $this->is_recurring ? trans('texts.recurring') : $this->invoice_number;
}
public function getFileName()
@ -69,9 +74,14 @@ class Invoice extends EntityModel
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()
{
return link_to('invoices/'.$this->public_id, $this->invoice_number);
return self::calcLink($this);
}
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->account->incrementCounter($invoice->invoice_number, $invoice->is_quote, $invoice->recurring_invoice_id);
Activity::createInvoice($invoice);
});
@ -267,4 +282,4 @@ Invoice::deleting(function ($invoice) {
Invoice::restoring(function ($invoice) {
Activity::restoreInvoice($invoice);
});
});

View File

@ -2,22 +2,35 @@
use Eloquent;
use Auth;
use Cache;
use App\Models\InvoiceDesign;
class InvoiceDesign extends Eloquent
{
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) {
$fileName = public_path(strtolower("js/templates/{$design->name}.js"));
if (Auth::user()->account->utf8_invoices && file_exists($fileName)) {
$design->javascript = file_get_contents($fileName);
foreach ($designs as $design) {
if ($design->id > Auth::user()->maxInvoiceDesignId()) {
$designs->pull($design->id);
}
$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;
}
}
}

View File

@ -1,7 +1,7 @@
<?php namespace App\Models;
use DB;
use Utils;
use Illuminate\Database\Eloquent\SoftDeletes;
class Task extends EntityModel
@ -22,6 +22,66 @@ class Task extends EntityModel
{
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) {

View File

@ -2,7 +2,9 @@
use Session;
use Auth;
use Event;
use App\Libraries\Utils;
use App\Events\UserSettingsChanged;
use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
@ -100,7 +102,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
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()
@ -213,3 +215,8 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
User::updating(function ($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);
$invoiceAmount = Utils::formatMoney($invoice->getRequestedAmount(), $invoice->client->getCurrencyId());
$this->initClosure($invoice);
foreach ($invoice->invitations as $invitation) {
if (!$invitation->user || !$invitation->user->email || $invitation->user->trashed()) {
return false;
@ -40,7 +42,8 @@ class ContactMailer extends Mailer
'$client' => $invoice->client->getDisplayName(),
'$account' => $accountName,
'$contact' => $invitation->contact->getDisplayName(),
'$amount' => $invoiceAmount
'$amount' => $invoiceAmount,
'$advancedRawInvoice->' => '$'
];
// 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'] = preg_replace_callback('/\{\{\$?(.*)\}\}/', $this->advancedTemplateHandler, $data['body']);
$data['link'] = $invitation->getLink();
$data['entityType'] = $entityType;
$data['invoice_id'] = $invoice->id;
@ -69,6 +73,8 @@ class ContactMailer extends Mailer
}
Event::fire(new InvoiceSent($invoice));
return $response;
}
public function sendPaymentConfirmation(Payment $payment)
@ -123,4 +129,22 @@ class ContactMailer extends Mailer
$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 {
Mail::send($views, $data, function ($message) use ($toEmail, $fromEmail, $fromName, $subject, $data) {
$toEmail = strtolower($toEmail);
$replyEmail = $fromEmail;
$fromEmail = CONTACT_EMAIL;
if(isset($data['invoice_id'])) {
if (isset($data['invoice_id'])) {
$invoice = Invoice::with('account')->where('id', '=', $data['invoice_id'])->get()->first();
if($invoice->account->pdf_email_attachment && file_exists($invoice->getPDFPath())) {
$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;
} catch (Exception $e) {
$response = $e->getResponse()->getBody()->getContents();
$response = json_decode($response);
return nl2br($response->Message);
} catch (Exception $exception) {
if (isset($_ENV['POSTMARK_API_TOKEN'])) {
$response = $exception->getResponse()->getBody()->getContents();
$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(),
'accountName' => $invoice->account->getDisplayName(),
'userName' => $user->getDisplayName(),
'invoiceAmount' => Utils::formatMoney($invoice->amount, $invoice->client->getCurrencyId()),
'invoiceAmount' => Utils::formatMoney($invoice->getRequestedAmount(), $invoice->client->getCurrencyId()),
'invoiceNumber' => $invoice->invoice_number,
'invoiceLink' => SITE_URL."/{$entityType}s/{$invoice->public_id}",
];

View File

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

View File

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

View File

@ -1,11 +1,12 @@
<?php namespace App\Ninja\Repositories;
use Carbon;
use Utils;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Models\Invitation;
use App\Models\Product;
use App\Models\Task;
use Utils;
class InvoiceRepository
{
@ -266,15 +267,18 @@ class InvoiceRepository
$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->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->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;
if (!$publicId) {
$invoice->client_id = $data['client_id'];
$invoice->is_recurring = $data['is_recurring'] && !Utils::isDemo() ? true : false;
}
@ -311,6 +315,7 @@ class InvoiceRepository
}
$total = 0;
$itemTax = 0;
foreach ($data['invoice_items'] as $item) {
$item = (array) $item;
@ -320,15 +325,29 @@ class InvoiceRepository
$invoiceItemCost = round(Utils::parseFloat($item['cost']), 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;
$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) {
@ -354,6 +373,7 @@ class InvoiceRepository
$total += $total * $invoice->tax_rate / 100;
$total = round($total, 2);
$total += $itemTax;
// custom fields not charged taxes
if ($invoice->custom_value1 && !$invoice->custom_taxes1) {
@ -387,20 +407,19 @@ class InvoiceRepository
$task->invoice_id = $invoice->id;
$task->client_id = $invoice->client_id;
$task->save();
} else if ($item['product_key']) {
} else if ($item['product_key'] && !$invoice->has_tasks) {
$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 (!$product) {
$product = Product::createNew();
$product->product_key = trim($item['product_key']);
}
$product->notes = $item['notes'];
$product->cost = $item['cost'];
$product->save();
}
$product->save();
}
$invoiceItem = InvoiceItem::createNew();
@ -536,4 +555,89 @@ class InvoiceRepository
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('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) {
$query->where('clients.public_id', '=', $clientPublicId);
@ -46,7 +46,7 @@ class TaskRepository
}
public function save($publicId, $data)
{
{
if ($publicId) {
$task = Task::scope($publicId)->firstOrFail();
} else {
@ -60,36 +60,26 @@ class TaskRepository
$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') {
$task->start_time = Carbon::now()->toDateTimeString();
$task->is_running = true;
$timeLog[] = [strtotime('now'), false];
} 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;
$timeLog[] = [strtotime('now'), false];
} else if ($data['action'] == 'stop' && $task->is_running) {
if ($task->resume_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');
$timeLog[count($timeLog)-1][1] = time();
$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->save();
return $task;

View File

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

View File

@ -20,7 +20,8 @@
"d3": "3.4.11",
"handsontable": "*",
"pdfmake": "*",
"moment": "*"
"moment": "*",
"jsoneditor": "*"
},
"resolutions": {
"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",
"laravelcollective/html": "~5.0",
"wildbit/laravel-postmark-provider": "dev-master",
"Dwolla/omnipay-dwolla": "dev-master"
"Dwolla/omnipay-dwolla": "dev-master"
},
"require-dev": {
"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": {
"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)
{
$table->boolean('utf8_invoices')->default(false);
$table->boolean('utf8_invoices')->default(true);
$table->boolean('auto_wrap')->default(false);
$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();
$this->call('UserTableSeeder');
$this->call('ConstantsSeeder');
$this->command->info('Seeded the constants');
$this->call('CountriesSeeder');
$this->command->info('Seeded the countries!');
$this->command->info('Seeded the countries');
$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\DateFormat;
use App\Models\DatetimeFormat;
use App\Models\InvoiceDesign;
class PaymentLibrariesSeeder extends Seeder
{
@ -16,6 +17,7 @@ class PaymentLibrariesSeeder extends Seeder
$this->createPaymentTerms();
$this->createDateFormats();
$this->createDatetimeFormats();
$this->createInvoiceDesigns();
}
private function createGateways() {
@ -62,7 +64,7 @@ class PaymentLibrariesSeeder extends Seeder
['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' => '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' => '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' => '.'],
@ -80,10 +82,16 @@ class PaymentLibrariesSeeder extends Seeder
['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' => '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) {
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);
}
}
@ -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
use App\Models\User;
use App\Models\Account;
class UserTableSeeder extends Seeder
{
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
RewriteRule ^ index.php [L]
# Enable this to enforce https access
# RewriteCond %{SERVER_PORT} 80
# RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R,L]
</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 {
vertical-align: middle;
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;
}
table.dataTable.no-footer {
@ -2837,15 +2839,12 @@ background-clip: padding-box;
.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 {
border-left: none;
background-color: #fbfbfb;
border-bottom: 1px solid #dfe0e1;
}
.dashboard table.table thead > tr > th {
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 {
vertical-align: middle;
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;
}
table.dataTable.no-footer {
@ -487,15 +489,12 @@ background-clip: padding-box;
.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 {
border-left: none;
background-color: #fbfbfb;
border-bottom: 1px solid #dfe0e1;
}
.dashboard table.table thead > tr > th {
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 || {};
function GetPdfMake(invoice, javascript, callback) {
var account = invoice.account;
var baseDD = {
pageMargins: [40, 40, 40, 40],
styles: {
bold: {
bold: true
},
cost: {
alignment: 'right'
},
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;
},
NINJA.TEMPLATES = {
CLEAN: "1",
BOLD:"2",
MODERN: "3",
NORMAL:"4",
BUSINESS:"5",
CREATIVE:"6",
ELEGANT:"7",
HIPSTER:"8",
PLAYFUL:"9",
PHOTO:"10"
};
};
function GetPdfMake(invoice, javascript, callback) {
eval(javascript);
dd = $.extend(true, baseDD, dd);
javascript = NINJA.decodeJavascript(invoice, javascript);
/*
pdfMake.fonts = {
wqy: {
normal: 'wqy.ttf',
bold: 'wqy.ttf',
italics: 'wqy.ttf',
bolditalics: 'wqy.ttf'
function jsonCallBack(key, val) {
if ((val+'').indexOf('$firstAndLast') === 0) {
var parts = val.split(':');
return function (i, node) {
return (i === 0 || i === node.table.body.length) ? parseFloat(parts[1]) : 0;
};
} 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 = {
NotoSansCJKsc: {
normal: 'NotoSansCJKsc-Regular.ttf',
bold: 'NotoSansCJKsc-Medium.ttf',
italics: 'NotoSansCJKsc-Italic.ttf',
bolditalics: 'NotoSansCJKsc-Italic.ttf'
},
var fonts = {
Roboto: {
normal: 'Roboto-Regular.ttf',
bold: 'Roboto-Medium.ttf',
@ -83,56 +76,152 @@ function GetPdfMake(invoice, javascript, callback) {
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)
{
var text = [];
var data = [];
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) {
text.push({text:invoiceLabels.terms, style:'termsLabel'});
text.push({text:processVariables(invoice.terms), style:'terms'});
data.push({text:invoiceLabels.terms, style: ['termsLabel']});
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) {
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 shownItem = false;
var currencyId = invoice && invoice.client ? invoice.client.currency_id : 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++) {
var row = [];
var item = invoice.invoice_items[i];
var cost = formatMoney(item.cost, currencyId, true);
var qty = NINJA.parseFloat(item.qty) ? roundToTwo(NINJA.parseFloat(item.qty)) + '' : '';
var notes = item.notes;
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
if (shownItem && (!cost || cost == '0.00') && !notes && !productKey) {
continue;
}
shownItem = true;
// process date variables
@ -142,148 +231,179 @@ NINJA.invoiceLines = function(invoice) {
}
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);
rowStyle = i%2===0?'odd':'even';
row[0] = {style:["productKey", rowStyle], text:productKey};
row[1] = {style:["notes", rowStyle], text:notes};
row[2] = {style:["cost", rowStyle], text:cost};
row[3] = {style:["quantity", rowStyle], text:qty};
row[4] = {style:["tax", rowStyle], text:""+tax};
row[5] = {style:["lineTotal", rowStyle], text:lineTotal};
rowStyle = (i % 2 == 0) ? 'odd' : 'even';
row.push({style:["productKey", rowStyle], text:productKey || ' '}); // product key can be blank when selecting from a datalist
row.push({style:["notes", rowStyle], text:notes || ' '});
row.push({style:["cost", rowStyle], text:cost});
if (!hideQuantity) {
row.push({style:["quantity", rowStyle], text:qty || ' '});
}
row.push({style:["lineTotal", rowStyle], text:lineTotal || ' '});
grid.push(row);
}
return grid;
return NINJA.prepareDataTable(grid, 'invoiceItems');
}
NINJA.subtotals = function(invoice)
NINJA.subtotals = function(invoice, hideBalance)
{
if (!invoice) {
return;
}
var data = [
[invoiceLabels.subtotal, formatMoney(invoice.subtotal_amount, invoice.client.currency_id)],
];
var account = invoice.account;
var data = [];
data.push([{text: invoiceLabels.subtotal}, {text: formatMoney(invoice.subtotal_amount, invoice.client.currency_id)}]);
if(invoice.discount_amount != 0) {
data.push([invoiceLabels.discount, formatMoney(invoice.discount_amount, invoice.client.currency_id)]);
if (invoice.discount_amount != 0) {
data.push([{text: invoiceLabels.discount}, {text: formatMoney(invoice.discount_amount, invoice.client.currency_id)}]);
}
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') {
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) {
data.push([invoiceLabels.tax, formatMoney(invoice.tax_amount, invoice.client.currency_id)]);
for (var key in invoice.item_taxes) {
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') {
data.push([invoiceLabels.custom_invoice_label1, formatMoney(invoice.custom_value1, invoice.client.currency_id)]);
if (invoice.tax && invoice.tax.name || invoice.tax_name) {
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') {
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;
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'},
{text:formatMoney(invoice.balance_amount, invoice.client.currency_id), style:'balanceDueValue'}]);
return data;
if (!hideBalance) {
var isPartial = NINJA.parseFloat(invoice.partial);
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) {
var data = [];
if(account.name) data.push({text:account.name, style:'accountName'});
if(account.id_number) data.push({text:account.id_number, style:'accountDetails'});
if(account.vat_number) data.push({text:account.vat_number, style:'accountDetails'});
if(account.work_email) data.push({text:account.work_email, style:'accountDetails'});
if(account.work_phone) data.push({text:account.work_phone, style:'accountDetails'});
return data;
NINJA.accountDetails = function(invoice) {
var account = invoice.account;
var data = [
{text:account.name, style: ['accountName']},
{text:account.id_number},
{text:account.vat_number},
{text:account.work_email},
{text:account.work_phone}
];
return NINJA.prepareDataList(data, 'accountDetails');
}
NINJA.accountAddress = function(account) {
var address = '';
NINJA.accountAddress = function(invoice) {
var account = invoice.account;
var cityStatePostal = '';
if (account.city || account.state || account.postal_code) {
address = ((account.city ? account.city + ', ' : '') + account.state + ' ' + account.postal_code).trim();
}
var data = [];
if(account.address1) data.push({text:account.address1, style:'accountDetails'});
if(account.address2) data.push({text:account.address2, style:'accountDetails'});
if(address) data.push({text:address, style:'accountDetails'});
if(account.country) data.push({text:account.country.name, style: 'accountDetails'});
if(account.custom_label1 && account.custom_value1) data.push({text:account.custom_label1 +' '+ account.custom_value1, style: 'accountDetails'});
if(account.custom_label2 && account.custom_value2) data.push({text:account.custom_label2 +' '+ account.custom_value2, style: 'accountDetails'});
return data;
cityStatePostal = ((account.city ? account.city + ', ' : '') + account.state + ' ' + account.postal_code).trim();
}
var data = [
{text: account.address1},
{text: account.address2},
{text: cityStatePostal},
{text: account.country ? account.country.name : ''},
{text: invoice.account.custom_value1 ? invoice.account.custom_label1 + ' ' + invoice.account.custom_value1 : false},
{text: invoice.account.custom_value2 ? invoice.account.custom_label2 + ' ' + invoice.account.custom_value2 : false}
];
return NINJA.prepareDataList(data, 'accountAddress');
}
NINJA.invoiceDetails = function(invoice) {
var data = [
[
invoice.is_quote ? invoiceLabels.quote_number : invoiceLabels.invoice_number,
{style: 'bold', text: invoice.invoice_number},
],
[
invoice.is_quote ? invoiceLabels.quote_date : invoiceLabels.invoice_date,
invoice.invoice_date,
],
[
invoice.is_quote ? invoiceLabels.total : invoiceLabels.balance_due,
formatMoney(invoice.balance_amount, invoice.client.currency_id),
],
[
{text: (invoice.is_quote ? invoiceLabels.quote_number : invoiceLabels.invoice_number), style: ['invoiceNumberLabel']},
{text: invoice.invoice_number, style: ['invoiceNumber']}
],
[
{text: invoiceLabels.po_number},
{text: invoice.po_number}
],
[
{text: (invoice.is_quote ? invoiceLabels.quote_date : invoiceLabels.invoice_date)},
{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 data;
if (!client) {
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 = [
getClientDisplayName(client),
client.id_number,
client.vat_number,
concatStrings(client.address1, client.address2),
concatStrings(client.city, client.state, client.postal_code),
client.country ? client.country.name : false,
invoice.contact && getClientDisplayName(client) != invoice.contact.email ? invoice.contact.email : 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,
data = [
{text:clientName || ' ', style: ['clientName']},
{text:client.address1},
{text:concatStrings(client.city, client.state, client.postal_code)},
{text:client.country ? client.country.name : ''},
{text:clientEmail},
{text: invoice.client.custom_value1 ? invoice.account.custom_client_label1 + ' ' + invoice.client.custom_value1 : false},
{text: invoice.client.custom_value2 ? invoice.account.custom_client_label2 + ' ' + invoice.client.custom_value2 : false}
];
var data = [];
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;
return NINJA.prepareDataList(data, 'clientDetails');
}
NINJA.getPrimaryColor = function(defaultColor) {
return NINJA.primaryColor ? NINJA.primaryColor : defaultColor;
}
@ -292,6 +412,62 @@ NINJA.getSecondaryColor = function(defaultColor) {
return NINJA.primaryColor ? NINJA.secondaryColor : defaultColor;
}
NINJA.getEntityLabel = function(invoice) {
return invoice.is_quote ? invoiceLabels.quote : invoiceLabels.invoice;
// remove blanks and add section style to all elements
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 refreshTimer;
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);
var a = copyInvoice(invoice);
var b = copyInvoice(invoiceOld);
var a = copyObject(invoice);
var b = copyObject(invoiceOld);
if (!force && _.isEqual(a, b)) {
return;
}
pdfmakeMarker = "//pdfmake";
invoiceOld = invoice;
report_id = invoice.invoice_design_id;
pdfmakeMarker = "{";
if(javascript.slice(0, pdfmakeMarker.length) === pdfmakeMarker) {
doc = GetPdfMake(invoice, javascript, cb);
//doc.getDataUrl(cb);
} else {
doc = GetPdf(invoice, javascript);
doc.getDataUrl = function(cb) {
cb( this.output("datauristring"));
};
}
if (cb) {
doc.getDataUrl(cb);
}
return doc;
}
function copyInvoice(orig) {
function copyObject(orig) {
if (!orig) return false;
var copy = JSON.stringify(orig);
var copy = JSON.parse(copy);
return copy;
return JSON.parse(JSON.stringify(orig));
}
@ -58,12 +76,14 @@ function GetPdf(invoice, javascript){
lineTotalRight: 550
};
/*
if (invoice.has_taxes)
{
layout.descriptionLeft -= 20;
layout.unitCostRight -= 40;
layout.qtyRight -= 40;
}
*/
/*
@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];
} else if (key === 'tax' && invoice.tax_name) {
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)) {
key = invoiceLabels[key] + ' ' + parseFloat(invoice.discount) + '%';
} else {
@ -913,22 +930,49 @@ function displayNotesAndTerms(doc, layout, invoice, y)
function calculateAmounts(invoice) {
var total = 0;
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++) {
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)) {
tax = parseFloat(item.tax.rate);
taxRate = parseFloat(item.tax.rate);
taxName = item.tax.name;
} 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));
if (tax) {
lineTotal += roundToTwo(lineTotal * tax / 100);
if (invoice.discount != 0) {
if (parseInt(invoice.is_amount_discount)) {
lineTotal -= roundToTwo((lineTotal/total) * invoice.discount);
} else {
lineTotal -= roundToTwo(lineTotal * (invoice.discount/100));
}
}
if (lineTotal) {
total += lineTotal;
var taxAmount = roundToTwo(lineTotal * taxRate / 100);
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) {
@ -968,6 +1012,12 @@ function calculateAmounts(invoice) {
total = parseFloat(total) + parseFloat(tax);
}
for (var key in taxes) {
if (taxes.hasOwnProperty(key)) {
total += taxes[key].amount;
}
}
// custom fields w/o with taxes
if (NINJA.parseFloat(invoice.custom_value1) && invoice.custom_taxes1 != '1') {
total += roundToTwo(invoice.custom_value1);
@ -979,8 +1029,8 @@ function calculateAmounts(invoice) {
invoice.total_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance));
invoice.discount_amount = discount;
invoice.tax_amount = tax;
invoice.has_taxes = hasTaxes;
invoice.item_taxes = taxes;
if (NINJA.parseFloat(invoice.partial)) {
invoice.balance_amount = roundToTwo(invoice.partial);
} else {
@ -1024,11 +1074,12 @@ function displayInvoiceHeader(doc, invoice, layout) {
}
doc.text(totalX, layout.tableTop, invoiceLabels.line_total);
/*
if (invoice.has_taxes)
{
doc.text(taxX, layout.tableTop, invoiceLabels.tax);
}
*/
}
function displayInvoiceItems(doc, invoice, layout) {
@ -1211,10 +1262,11 @@ function displayInvoiceItems(doc, invoice, layout) {
doc.line(qtyX-45, y-16,qtyX-45, y+55);
/*
if (invoice.has_taxes) {
doc.line(taxX-15, y-16,taxX-15, 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.unitCostRight-55, topX,layout.unitCostRight-55, y);
doc.line(layout.qtyRight-50, topX,layout.qtyRight-50, y);
/*
if (invoice.has_taxes) {
doc.line(layout.taxRight-28, topX,layout.taxRight-28, y);
}
*/
doc.line(totalX-25, topX,totalX-25, y+90);
doc.line(totalX+45, topX,totalX+45, y+90);
}
@ -1574,4 +1628,20 @@ function twoDigits(value) {
return '0' + 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.
### 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).
@ -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)
* [Sigitas Limontas](https://lt.linkedin.com/in/sigitaslimontas)
### Documentation
* [Self Host](https://www.invoiceninja.com/knowledgebase/self-host/)
### Documentation
* [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/)
* [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/)
### 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
* [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
* [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',
'invoice_date' => 'Rechnungsdatum',
'due_date' => 'Fällig am',
'invoice_number' => 'Rechungsnummer',
'invoice_number' => 'Rechnungsnummer',
'invoice_number_short' => 'Rechnung #',
'po_number' => 'Bestellnummer',
'po_number_short' => 'BN #',
@ -46,7 +46,7 @@ return array(
'line_total' => 'Summe',
'subtotal' => 'Zwischensumme',
'paid_to_date' => 'Bereits gezahlt',
'balance_due' => 'Rechnungsbetrag',
'balance_due' => 'Geschuldeter Betrag',
'invoice_design_id' => 'Design',
'terms' => 'Bedingungen',
'your_invoice' => 'Ihre Rechnung',
@ -96,7 +96,8 @@ return array(
'import' => 'Importieren',
'download' => 'Downloaden',
'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',
'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>Beispiel zu dynamischen Rechnungs-Variabeln:</p>
<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>"Vorschusszahlung für :QUARTER+1" => "Vorschusszahlung für Q2"</li>
</ul>',
@ -205,7 +206,7 @@ return array(
'import_to' => 'Importieren nach',
'client_will_create' => 'Kunde wird 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',
// application messages
@ -218,7 +219,7 @@ return array(
'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.',
'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',
'created_client' => 'Kunde erfolgreich erstellt',
@ -251,16 +252,17 @@ return array(
'deleted_credits' => ':count Guthaben erfolgreich gelöscht',
// Emails
'confirmation_subject' => 'Invoice Ninja Kontobestätigung',
'confirmation_subject' => 'InvoiceNinja Kontobestätigung',
'confirmation_header' => 'Kontobestätigung',
'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_message' => 'Vielen Dank für Ihre Zahlung von :amount.',
'email_salutation' => 'Sehr geehrte/r :name,',
'email_signature' => 'Mit freundlichen Grüßen,',
'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:',
'notification_invoice_paid_subject' => 'Die Rechnung :invoice wurde von :client bezahlt',
'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_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_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
'secure_payment' => 'Sichere Zahlung',
'card_number' => 'Kartennummer',
@ -281,7 +284,7 @@ return array(
// Security alerts
'security' => array(
'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!',
'wrong_confirmation' => 'Falscher Bestätigungscode.',
'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' => [
'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',
],
'logout' => 'Ausloggen',
'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',
'email_taken' => 'Diese E-Mail Adresse ist bereits registriert',
'email_taken' => 'Diese E-Mail-Adresse ist bereits registriert',
'working' => 'Wird bearbeitet',
'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.',
'password' => 'Passwort',
'invoice_subject' => 'Neue Rechnung :invoice von :account',
'close' => 'Schließen',
'pro_plan_product' => '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 für den Beitritt! Sobald die Rechnung bezahlt wurde,Beim Auswählen eines Produktes werden beginnt deine Pro Plan Mitgliedschaft.',
'pro_plan_success' => 'Danke, dass Sie Invoice Ninja\'s Pro gewählt haben!<p/>&nbsp;<br/>
<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',
'custom_fields' => 'Benutzerdefinierte Felder',
@ -320,6 +327,7 @@ return array(
'field_label' => 'Feldbezeichnung',
'field_value' => 'Feldwert',
'edit' => 'Bearbeiten',
'set_name' => 'Den Firmennamen setzen',
'view_as_recipient' => 'Als Empfänger betrachten',
// product management
@ -336,7 +344,7 @@ return array(
'created_product' => 'Produkt erfolgreich erstellt',
'archived_product' => 'Produkt erfolgreich archiviert',
'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',
'pro_plan_advanced_settings' => ':link um durch eine Pro-Mitgliedschaft erweiterte Einstellungen zu aktivieren',
@ -346,7 +354,7 @@ return array(
'chart_builder' => 'Diagrammersteller',
'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
'quote' => 'Angebot',
@ -394,19 +402,19 @@ return array(
'invoice_fields' => 'Rechnungsfelder',
'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_paid_to_date' => 'Bereits gezahlt ausblenden',
'hide_paid_to_date_help' => 'Bereits gezahlt nur anzeigen, wenn eine Zahlung eingegangen ist.',
'hide_paid_to_date' => '"Bereits gezahlt" ausblenden',
'hide_paid_to_date_help' => '"Bereits gezahlt" nur anzeigen, wenn eine Zahlung eingegangen ist.',
'charge_taxes' => 'Steuern erheben',
'user_management' => 'Benutzerverwaltung',
'add_user' => 'Add User',
'add_user' => 'Benutzer hinzufügen',
'send_invite' => 'Einladung senden',
'sent_invite' => 'Einladung erfolgreich gesendet',
'updated_user' => 'Benutzer erfolgreich aktualisiert',
'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',
'edit_user' => 'Benutzer bearbeiten',
'delete_user' => 'Benutzer löschen',
@ -435,7 +443,7 @@ return array(
'quote_number_counter' => 'Zähler für Angebotsnummer',
'share_invoice_counter' => 'Zähler der Rechnung teilen',
'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',
'gateway_help_1' => ':link um sich bei Authorize.net anzumelden.',
@ -452,15 +460,15 @@ return array(
'more_designs_self_host_text' => '',
'buy' => 'Kaufen',
'bought_designs' => 'Die zusätzliche Rechnungsvorlagen wurden erfolgreich hinzugefügt',
'sent' => 'gesendet',
'timesheets' => 'Timesheets',
'vat_number' => 'USt-IdNr.',
'timesheets' => 'Zeittabellen',
'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_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.',
'vat_number' => 'USt-IdNr.',
'id_number' => 'ID-Nummer',
'white_label_link' => 'Branding entfernen',
@ -506,7 +514,7 @@ return array(
'approve' => 'Zustimmen',
'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_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',
@ -519,8 +527,8 @@ return array(
'token_billing_secure' => 'Die Daten werden sicher von :stripe_link gespeichert.',
'support' => 'Support',
'contact_information' => 'Kontakt Informationen',
'256_encryption' => '256-Bit Verschlüsselung',
'contact_information' => 'Kontakt-Informationen',
'256_encryption' => '256-Bit-Verschlüsselung',
'amount_due' => 'Fälliger Betrag',
'billing_address' => 'Rechnungsadresse',
'billing_method' => 'Abrechnungsmethode',
@ -528,9 +536,9 @@ return array(
'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.',
'default_invoice_footer' => 'Standard Fußzeile festlegen',
'default_invoice_footer' => 'Standard-Fußzeile festlegen',
'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',
'tokens' => 'Token',
@ -567,7 +575,7 @@ return array(
'forgot_password' => 'Passwort vergessen?',
'email_address' => 'E-Mail-Adresse',
'lets_go' => "Auf geht's",
'password_recovery' => 'Passwort Wiederherstellung',
'password_recovery' => 'Passwort-Wiederherstellung',
'send_email' => 'E-Mail verschicken',
'set_password' => 'Passwort festlegen',
'converted' => 'Umgewandelt',
@ -578,25 +586,24 @@ return array(
'resend_confirmation' => 'Bestätigungsmail erneut senden',
'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_paypal' => 'PayPal',
'payment_type_bitcoin' => 'Bitcoin',
'knowledge_base' => 'Wissensdatenbank',
'partial' => 'Parziell',
'knowledge_base' => 'FAQ',
'partial' => 'Partiell',
'partial_remaining' => ':partial von :balance',
'more_fields' => 'Weitere Felder',
'less_fields' => 'Weniger Felder',
'client_name' => 'Kundenname',
'pdf_settings' => 'PDF Einstellungen',
'utf8_invoices' => 'Cyrillic Unterstützung <sup>Beta</sup>',
'product_settings' => 'Produkt Einstellungen',
'auto_wrap' => 'Automatischer Zeilenumbruch',
'duplicate_post' => 'Achtung: Die vorherige Seite wurde zweimal abgeschickt. Das zweite Abschicken wurde ignoriert.',
'view_documentation' => 'Dokumentation anzeigen',
'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',
'www' => 'www',
@ -642,7 +649,7 @@ return array(
'minutes' => 'Minuten',
'hour' => 'Stunde',
'hours' => 'Stunden',
'task_details' => 'Aufgaben Details',
'task_details' => 'Aufgaben-Details',
'duration' => 'Dauer',
'end_time' => 'Endzeit',
'end' => 'Ende',
@ -665,16 +672,16 @@ return array(
'counter' => 'Zähler',
'payment_type_dwolla' => 'Dwolla',
'gateway_help_43' => ':link to sign up for Dwolla.',
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
'gateway_help_43' => ':link zum Registrieren auf Dwolla.',
'partial_value' => 'Muss größer als Null und kleiner als der Gesamtbetrag sein',
'more_actions' => 'Weitere Aktionen',
'pro_plan_title' => 'NINJA PRO',
'pro_plan_call_to_action' => 'Jetzt Upgraden!',
'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_feature4' => '"Created by Invoice Ninja" entfernen',
'pro_plan_feature4' => '"Erstellt durch Invoice Ninja" entfernen',
'pro_plan_feature5' => 'Multi-Benutzer Zugriff & Aktivitätstracking',
'pro_plan_feature6' => 'Angebote & pro-forma Rechnungen erstellen',
'pro_plan_feature7' => 'Rechungstitelfelder und Nummerierung anpassen',
@ -689,13 +696,63 @@ return array(
'email_receipt' => 'Zahlungsbestätigung an Kunden per E-Mail senden',
'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',
'new_account' => 'Neues Konto',
'new_company' => 'Neues Konto',
'associated_accounts' => 'Konten erfolgreich verlinkt',
'unlinked_account' => 'Konten erfolgreich getrennt',
'login' => 'Login',
'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',
'tax' => 'Tax',
'item' => 'Item',
'description' => 'Description',
'unit_cost' => 'Unit Cost',
'description' => 'Description',
'unit_cost' => 'Cost',
'quantity' => 'Quantity',
'line_total' => 'Line Total',
'subtotal' => 'Subtotal',
@ -119,8 +119,8 @@ return array(
'active_client' => 'active client',
'active_clients' => 'active clients',
'invoices_past_due' => 'Invoices Past Due',
'upcoming_invoices' => 'Upcoming invoices',
'average_invoice' => 'Average invoice',
'upcoming_invoices' => 'Upcoming Invoices',
'average_invoice' => 'Average Invoice',
// list pages
'archive' => 'Archive',
@ -276,9 +276,9 @@ return array(
// Payment page
'secure_payment' => 'Secure Payment',
'card_number' => 'Card number',
'expiration_month' => 'Expiration month',
'expiration_year' => 'Expiration year',
'card_number' => 'Card Number',
'expiration_month' => 'Expiration Month',
'expiration_year' => 'Expiration Year',
'cvv' => 'CVV',
// Security alerts
@ -401,9 +401,9 @@ return array(
'invoice_fields' => 'Invoice Fields',
'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_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.',
'charge_taxes' => 'Charge taxes',
@ -526,11 +526,11 @@ return array(
'token_billing_secure' => 'The data is stored securely by :stripe_link',
'support' => 'Support',
'contact_information' => 'Contact information',
'contact_information' => 'Contact Information',
'256_encryption' => '256-Bit Encryption',
'amount_due' => 'Amount due',
'billing_address' => 'Billing address',
'billing_method' => 'Billing method',
'billing_address' => 'Billing Address',
'billing_method' => 'Billing Method',
'order_overview' => 'Order overview',
'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.',
@ -573,7 +573,8 @@ return array(
'recover_password' => 'Recover your password',
'forgot_password' => 'Forgot your password?',
'email_address' => 'Email address',
'lets_go' => 'Lets go',
'lets_go' => 'Let\'s go',
//'lets_go' => 'Login',
'password_recovery' => 'Password Recovery',
'send_email' => 'Send email',
'set_password' => 'Set Password',
@ -597,7 +598,6 @@ return array(
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap',
'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',
'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account',
'add_company' => 'Add Company',
'untitled' => 'Untitled',
'new_account' => 'New Account',
'new_company' => 'New Company',
'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login',
'or' => 'or',
'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.',
'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

@ -417,264 +417,321 @@ return array(
'invalid_counter' => 'Para evitar posibles conflictos, por favor crea un prefijo de facturación y de cotización.',
'mark_sent' => 'Marcar como enviado',
'gateway_help_1' => ':link to sign up for Authorize.net.',
'gateway_help_2' => ':link to sign up for Authorize.net.',
'gateway_help_17' => ':link to get your PayPal API signature.',
'gateway_help_23' => 'Note: use your secret API key, not your publishable API key.',
'gateway_help_27' => ':link to sign up for TwoCheckout.',
'gateway_help_1' => ':link para registrarse con Authorize.net.',
'gateway_help_2' => ':link para registrarse con Authorize.net.',
'gateway_help_17' => ':link para obtener su firma del API de PayPal.',
'gateway_help_23' => 'Nota: use use llave secreta del API, no la llave pública.',
'gateway_help_27' => ':link para registrarse con TwoCheckout.',
'more_designs' => 'More designs',
'more_designs_title' => 'Additional Invoice Designs',
'more_designs_cloud_header' => 'Go Pro for more invoice designs',
'more_designs' => 'Más diseños',
'more_designs_title' => 'Diseños Adicionales de Facturas',
'more_designs_cloud_header' => 'Vete Pro para más diseños de facturas',
'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' => '',
'buy' => 'Buy',
'bought_designs' => 'Successfully added additional invoice designs',
'buy' => 'Comprar',
'bought_designs' => 'Diseños adicionales de facturas agregados con éxito',
'sent' => 'sent',
'timesheets' => 'Timesheets',
'sent' => 'enviado',
'timesheets' => 'Hohas de Tiempo',
'payment_title' => 'Enter Your Billing Address and Credit Card information',
'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_footer2' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'vat_number' => 'Vat Number',
'payment_title' => 'Ingrese la Dirección de Facturación de su Tareta de Crédito',
'payment_cvv' => '*Este es el número de 3-4 dígitos en la parte posterior de su tarjeta de crédito',
'payment_footer1' => '*La dirección debe coincidir con la dirección asociada a la tarjeta de crédito.',
'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' => 'Número de Impuesto',
'id_number' => 'ID Number',
'white_label_link' => 'White label',
'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_header' => 'White Label',
'bought_white_label' => 'Successfully enabled white label license',
'white_labeled' => 'White labeled',
'white_label_link' => 'Etiqueta Blanca',
'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' => 'Etiqueta Blanca',
'bought_white_label' => 'Licencia de etiqueta blanca habilitada con éxito',
'white_labeled' => 'Etiqueta Blanca',
'restore' => 'Restore',
'restore_invoice' => 'Restore Invoice',
'restore_quote' => 'Restore Quote',
'restore_client' => 'Restore Client',
'restore_credit' => 'Restore Credit',
'restore_payment' => 'Restore Payment',
'restore' => 'Restaurar',
'restore_invoice' => 'Restaurar Invoice',
'restore_quote' => 'Restaurar Quote',
'restore_client' => 'Restaurar Client',
'restore_credit' => 'Restaurar Credit',
'restore_payment' => 'Restaurar Payment',
'restored_invoice' => 'Successfully restored invoice',
'restored_quote' => 'Successfully restored quote',
'restored_client' => 'Successfully restored client',
'restored_payment' => 'Successfully restored payment',
'restored_credit' => 'Successfully restored credit',
'restored_invoice' => 'Factura restaurada con éxito',
'restored_quote' => 'Cotización restaurada con éxito',
'restored_client' => 'Cliente restaurado con éxito',
'restored_payment' => 'Pago restaurado con éxito',
'restored_credit' => 'Crédito restaurado con éxito',
'reason_for_canceling' => 'Help us improve our site by telling us why you\'re leaving.',
'discount_percent' => 'Percent',
'discount_amount' => 'Amount',
'reason_for_canceling' => 'Ayúdenos a mejorar contándonos porqué se va.',
'discount_percent' => 'Porcentaje',
'discount_amount' => 'Cantidad',
'invoice_history' => 'Invoice History',
'quote_history' => 'Quote History',
'current_version' => 'Current version',
'select_versiony' => 'Select version',
'view_history' => 'View History',
'invoice_history' => 'Facturar Historial',
'quote_history' => 'Cotizar Historial',
'current_version' => 'Versión actual',
'select_versiony' => 'Seleccionar versión',
'view_history' => 'Ver Historial',
'edit_payment' => 'Edit Payment',
'updated_payment' => 'Successfully updated payment',
'deleted' => 'Deleted',
'restore_user' => 'Restore User',
'restored_user' => 'Successfully restored user',
'show_deleted_users' => 'Show deleted users',
'email_templates' => 'Email Templates',
'invoice_email' => 'Invoice Email',
'payment_email' => 'Payment Email',
'quote_email' => 'Quote Email',
'reset_all' => 'Reset All',
'approve' => 'Approve',
'edit_payment' => 'Editar Pago',
'updated_payment' => 'Pago actualizado con éxito',
'deleted' => 'Eliminado',
'restore_user' => 'Restaurar Usuario',
'restored_user' => 'Usuario restaurado con éxito',
'show_deleted_users' => 'Mostrar usuarios eliminados',
'email_templates' => 'Plantillas de Correos',
'invoice_email' => 'Correo de Factura',
'payment_email' => 'Correo de Pago',
'quote_email' => 'Correo de Cotizacion',
'reset_all' => 'Reiniciar Todos',
'approve' => 'Aprobar',
'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_1' => 'Disabled',
'token_billing_2' => 'Opt-in - checkbox is shown but not selected',
'token_billing_3' => 'Opt-out - checkbox is shown and selected',
'token_billing_4' => 'Always',
'token_billing_checkbox' => 'Store credit card details',
'view_in_stripe' => 'View in Stripe',
'use_card_on_file' => 'Use card on file',
'edit_payment_details' => 'Edit payment details',
'token_billing' => 'Save card details',
'token_billing_secure' => 'The data is stored securely by :stripe_link',
'token_billing_type_id' => 'Token de Facturación',
'token_billing_help' => 'Permite almacenar tarjetas de crédito con su gateway de pagos, y facturar en una fecha posterior.',
'token_billing_1' => 'Deshabilitado',
'token_billing_2' => 'Opt-in - el checkbox es mostrado pero no seleccionado',
'token_billing_3' => 'Opt-out - el checkbox es mostrado y seleccionado',
'token_billing_4' => 'Siempre',
'token_billing_checkbox' => 'Almacenar detalles de la tarjeta de crédito',
'view_in_stripe' => 'Ver en Stripe',
'use_card_on_file' => 'Usar la tarjeta en el archivo',
'edit_payment_details' => 'Editar detalles del pago',
'token_billing' => 'Guardar detalles de la tarjeta',
'token_billing_secure' => 'La información es almacenada de manera segura por :stripe_link',
'support' => 'Support',
'contact_information' => 'Contact information',
'256_encryption' => '256-Bit Encryption',
'amount_due' => 'Amount due',
'billing_address' => 'Billing address',
'billing_method' => 'Billing method',
'order_overview' => 'Order overview',
'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.',
'support' => 'Soporte',
'contact_information' => 'Información de Contacto',
'256_encryption' => 'Encripción de 256-Bit',
'amount_due' => 'Valor por cobrar',
'billing_address' => 'Dirección de facturación',
'billing_method' => 'Método de facturación',
'order_overview' => 'Resumen de la orden',
'match_address' => '*La dirección debe coincidir con la dirección asociada a la tarjeta de crédito.',
'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',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
'default_invoice_footer' => 'Asignar pié de página por defecto para la factura',
'invoice_footer' => 'Pié de págia de la factura',
'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',
'add_token' => 'Add Token',
'show_deleted_tokens' => 'Show deleted tokens',
'deleted_token' => 'Successfully deleted token',
'created_token' => 'Successfully created token',
'updated_token' => 'Successfully updated token',
'edit_token' => 'Edit Token',
'delete_token' => 'Delete Token',
'add_token' => 'Agregar Token',
'show_deleted_tokens' => 'Mostrar los tokens eliminados',
'deleted_token' => 'Token eliminado con éxito',
'created_token' => 'Token creado con éxito',
'updated_token' => 'Token actualizado con éxito',
'edit_token' => 'Editar Token',
'delete_token' => 'Eliminar Token',
'token' => 'Token',
'add_gateway' => 'Add Gateway',
'delete_gateway' => 'Delete Gateway',
'edit_gateway' => 'Edit Gateway',
'updated_gateway' => 'Successfully updated gateway',
'created_gateway' => 'Successfully created gateway',
'deleted_gateway' => 'Successfully deleted gateway',
'add_gateway' => 'Agregar Gateway',
'delete_gateway' => 'Eliminar Gateway',
'edit_gateway' => 'Editar Gateway',
'updated_gateway' => 'Gateway actualizado con éxito',
'created_gateway' => 'Gateway creado con éxito',
'deleted_gateway' => 'Gateway eliminado con éxito',
'pay_with_paypal' => 'PayPal',
'pay_with_card' => 'Credit card',
'pay_with_card' => 'Tarjeta de Crédito',
'change_password' => 'Change password',
'current_password' => 'Current password',
'new_password' => 'New password',
'confirm_password' => 'Confirm password',
'password_error_incorrect' => 'The current password is incorrect.',
'password_error_invalid' => 'The new password is invalid.',
'updated_password' => 'Successfully updated password',
'change_password' => 'Cambiar contraseña',
'current_password' => 'contraseña actual',
'new_password' => 'Nueva contraseña',
'confirm_password' => 'Confirmar contraseña',
'password_error_incorrect' => 'La contraseña actual es incorrecta.',
'password_error_invalid' => 'La nueva contraseña es inválida.',
'updated_password' => 'Contraseñaactualizada con éxito',
'api_tokens' => 'API Tokens',
'users_and_tokens' => 'Users & Tokens',
'account_login' => 'Account Login',
'recover_password' => 'Recover your password',
'forgot_password' => 'Forgot your password?',
'email_address' => 'Email address',
'lets_go' => 'Lets go',
'password_recovery' => 'Password Recovery',
'send_email' => 'Send email',
'set_password' => 'Set Password',
'converted' => 'Converted',
'users_and_tokens' => 'Usuarios y Tokens',
'account_login' => 'Ingreso',
'recover_password' => 'Recupere su contraseña',
'forgot_password' => 'Olvidó su Contraseña?',
'email_address' => 'Correo electrónico',
'lets_go' => 'Vamos',
'password_recovery' => 'Recuperación de Contraseña',
'send_email' => 'Enviar correo',
'set_password' => 'Asignar Contraseña',
'converted' => 'Convertido',
'email_approved' => 'Email me when a quote is <b>approved</b>',
'notification_quote_approved_subject' => 'Quote :invoice was approved by :client',
'notification_quote_approved' => 'The following client :client approved Quote :invoice for :amount.',
'resend_confirmation' => 'Resend confirmation email',
'confirmation_resent' => 'The confirmation email was resent',
'email_approved' => 'Enviarme un correo cuando una cotización sea <b>aprobada</b>',
'notification_quote_approved_subject' => 'Cotización :invoice fue aprobada por :client',
'notification_quote_approved' => 'El cliente :client ha aprobado la cotización :invoice por el valor :amount.',
'resend_confirmation' => 'Reenviar correo de confirmación',
'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.',
'payment_type_credit_card' => 'Credit card',
'gateway_help_42' => ':link para registrarse en BitPay.<br/>Nota: use una llave del API legacy, no un token API.',
'payment_type_credit_card' => 'Tarjeta de Crédito',
'payment_type_paypal' => 'PayPal',
'payment_type_bitcoin' => 'Bitcoin',
'knowledge_base' => 'Knowledge Base',
'partial' => 'Partial',
'partial_remaining' => ':partial of :balance',
'knowledge_base' => 'Base de Conocimiento',
'partial' => 'Parcial',
'partial_remaining' => ':partial de :balance',
'more_fields' => 'More Fields',
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap',
'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.',
'view_documentation' => 'View Documentation',
'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.',
'more_fields' => ' Más Campos',
'less_fields' => 'Menos Campos',
'client_name' => 'Nombre del Cliente',
'pdf_settings' => 'Configuración de PDF',
'product_settings' => 'Configuración del Producto',
'auto_wrap' => 'Ajuste Automático de Linea',
'duplicate_post' => 'Advertencia: la página anterior fue enviada dos veces. El segundo envío ha sido ignorado.',
'view_documentation' => 'Ver Documentación',
'app_title' => 'Facturación Open-Source Gratuita',
'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',
'rows' => 'rows',
'rows' => 'filas',
'www' => 'www',
'logo' => 'Logo',
'subdomain' => 'Subdomain',
'provide_name_or_email' => 'Please provide a contact name or email',
'charts_and_reports' => 'Charts & Reports',
'chart' => 'Chart',
'report' => 'Report',
'group_by' => 'Group by',
'paid' => 'Paid',
'enable_report' => 'Report',
'enable_chart' => 'Chart',
'totals' => 'Totals',
'run' => 'Run',
'export' => 'Export',
'documentation' => 'Documentation',
'subdomain' => 'Subdominio',
'provide_name_or_email' => 'Por favor provea un nombre o correo electrónico de contacto',
'charts_and_reports' => 'Gráficas y Reportes',
'chart' => 'Gráfica',
'report' => 'Reporte',
'group_by' => 'Agrupar por',
'paid' => 'Pagado',
'enable_report' => 'Reportes',
'enable_chart' => 'Gráficas',
'totals' => 'Totales',
'run' => 'Ejecutar',
'export' => 'Exportar',
'documentation' => 'Documentación',
'zapier' => 'Zapier <sup>Beta</sup>',
'recurring' => 'Recurring',
'last_invoice_sent' => 'Last invoice sent :date',
'recurring' => 'Recurrente',
'last_invoice_sent' => 'Ultima factura enviada en :date',
'processed_updates' => 'Successfully completed update',
'tasks' => 'Tasks',
'new_task' => 'New Task',
'start_time' => 'Start Time',
'created_task' => 'Successfully created task',
'updated_task' => 'Successfully updated task',
'edit_task' => 'Edit Task',
'archive_task' => 'Archive Task',
'restore_task' => 'Restore Task',
'delete_task' => 'Delete Task',
'stop_task' => 'Stop Task',
'time' => 'Time',
'start' => 'Start',
'stop' => 'Stop',
'now' => 'Now',
'processed_updates' => 'Actualización completada con éxito',
'tasks' => 'Tareas',
'new_task' => 'Nueva Tarea',
'start_time' => 'Tiempo de Inicio',
'created_task' => 'Tarea creada con éxito',
'updated_task' => 'Tarea actualizada con éxito',
'edit_task' => 'Editar Tarea',
'archive_task' => 'Archivar Tarea',
'restore_task' => 'Restaurar Tarea',
'delete_task' => 'Eliminar Tarea',
'stop_task' => 'Detener Tarea',
'time' => 'Tiempo',
'start' => 'Iniciar',
'stop' => 'Detener',
'now' => 'Ahora',
'timer' => 'Timer',
'manual' => 'Manual',
'date_and_time' => 'Date & Time',
'second' => 'second',
'seconds' => 'seconds',
'minute' => 'minute',
'minutes' => 'minutes',
'hour' => 'hour',
'hours' => 'hours',
'task_details' => 'Task Details',
'duration' => 'Duration',
'end_time' => 'End Time',
'end' => 'End',
'invoiced' => 'Invoiced',
'logged' => 'Logged',
'running' => 'Running',
'task_error_multiple_clients' => 'The tasks can\'t belong to different clients',
'task_error_running' => 'Please stop running tasks first',
'task_error_invoiced' => 'Tasks have already been invoiced',
'restored_task' => 'Successfully restored task',
'archived_task' => 'Successfully archived task',
'archived_tasks' => 'Successfully archived :count tasks',
'deleted_task' => 'Successfully deleted task',
'deleted_tasks' => 'Successfully deleted :count tasks',
'create_task' => 'Create Task',
'stopped_task' => 'Successfully stopped task',
'invoice_task' => 'Invoice Task',
'invoice_labels' => 'Invoice Labels',
'prefix' => 'Prefix',
'counter' => 'Counter',
'date_and_time' => 'Fecha y Hora',
'second' => 'segundo',
'seconds' => 'segundos',
'minute' => 'minuto',
'minutes' => 'minutos',
'hour' => 'hora',
'hours' => 'horas',
'task_details' => 'Detalles de la Tarea',
'duration' => 'Duración',
'end_time' => 'Tiempo Final',
'end' => 'Fin',
'invoiced' => 'Facturado',
'logged' => 'Registrado',
'running' => 'Corriendo',
'task_error_multiple_clients' => 'Las tareas no pueden pertenecer a diferentes clientes',
'task_error_running' => 'Por favor primero detenga las tareas que se estén ejecutando',
'task_error_invoiced' => 'Las tareas ya han sido facturadas',
'restored_task' => 'Tarea restaurada con éxito',
'archived_task' => 'Tarea archivada con éxito',
'archived_tasks' => ':count tareas archivadas con éxito',
'deleted_task' => 'Tarea eliminada con éxito',
'deleted_tasks' => ':count tareas eliminadas con éxito',
'create_task' => 'Crear Tarea',
'stopped_task' => 'Tarea detenida con éxito',
'invoice_task' => 'Tarea de Factura',
'invoice_labels' => 'Etiquetas',
'prefix' => 'Prefijo',
'counter' => 'Contador',
'payment_type_dwolla' => 'Dwolla',
'gateway_help_43' => ':link to sign up for Dwolla.',
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
'gateway_help_43' => ':link para registrarse con Dwolla.',
'partial_value' => 'Debe ser mayor que cero y menor que el total',
'more_actions' => 'Más Acciones',
'pro_plan_title' => 'NINJA PRO',
'pro_plan_call_to_action' => 'Upgrade Now!',
'pro_plan_feature1' => 'Create Unlimited Clients',
'pro_plan_feature2' => 'Access to 10 Beautiful Invoice Designs',
'pro_plan_feature3' => 'Custom URLs - "YourBrand.InvoiceNinja.com"',
'pro_plan_feature4' => 'Remove "Created by Invoice Ninja"',
'pro_plan_feature5' => 'Multi-user Access & Activity Tracking',
'pro_plan_feature6' => 'Create Quotes & Pro-forma Invoices',
'pro_plan_feature7' => 'Customize Invoice Field Titles & Numbering',
'pro_plan_feature8' => 'Option to Attach PDFs to Client Emails',
'pro_plan_call_to_action' => 'Actualícese Ahora!',
'pro_plan_feature1' => 'Cree Clientes Ilimitados',
'pro_plan_feature2' => 'Acceda a 10 hermosos diseños de factura',
'pro_plan_feature3' => 'Custom URLs - "SuMarca.InvoiceNinja.com"',
'pro_plan_feature4' => 'Remove "Creado por Invoice Ninja"',
'pro_plan_feature5' => 'Acceso Multi-usuario y seguimiento de actividades',
'pro_plan_feature6' => 'Cree Cotizaciones y facturas Pro-forma',
'pro_plan_feature7' => 'Personalice los Títulos de los Campos y Numeración de las Cuentas de Cobro',
'pro_plan_feature8' => 'Opción para adjuntarle documentos PDF a los correos dirigidos a los clientes',
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Edit Details',
'work' => 'Work',
'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here',
'resume' => 'Continuar',
'break_duration' => 'Descanso',
'edit_details' => 'Editar Detalles',
'work' => 'Trabajo',
'timezone_unset' => 'Por favor :link para configurar su Uso Horario',
'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',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap',
'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',
'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account',
'add_company' => 'Add Company',
'untitled' => 'Untitled',
'new_account' => 'New Account',
'new_company' => 'New Company',
'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login',
'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(
@ -35,11 +35,11 @@ return array(
'invoice_number_short' => 'Facture #',
'po_number' => 'Numéro du bon de commande',
'po_number_short' => 'Bon de commande #',
'frequency_id' => 'Fréquence',
'frequency_id' => 'Fréquence',
'discount' => 'Remise',
'taxes' => 'Taxes',
'tax' => 'Taxe',
'item' => 'Article',
'item' => 'Article',
'description' => 'Description',
'unit_cost' => 'Coût unitaire',
'quantity' => 'Quantité',
@ -117,11 +117,11 @@ return array(
'billed_client' => 'client facturé',
'billed_clients' => 'clients facturés',
'active_client' => 'client actif',
'active_clients' => 'clients actifs',
'active_clients' => 'clients actifs',
'invoices_past_due' => 'Date limite de paiement dépassée',
'upcoming_invoices' => 'Factures à venir',
'average_invoice' => 'Moyenne de facturation',
// list pages
'archive' => 'Archiver',
'delete' => 'Supprimer',
@ -276,7 +276,7 @@ return array(
// Payment page
'secure_payment' => 'Paiement sécurisé',
'card_number' => 'Numéro de carte',
'expiration_month' => 'Mois d\'expiration',
'expiration_month' => 'Mois d\'expiration',
'expiration_year' => 'Année d\'expiration',
'cvv' => 'CVV',
@ -297,8 +297,8 @@ return array(
'remove_logo_link' => 'Cliquez ici',
],
'logout' => 'Se déconnecter',
'sign_up_to_save' => 'Connectez vous pour sauvegarder votre travail',
'logout' => 'Se déconnecter',
'sign_up_to_save' => 'Connectez vous pour sauvegarder votre travail',
'agree_to_terms' =>'J\'accepte les conditions d\'utilisation d\'Invoice ninja :terms',
'terms_of_service' => 'Conditions d\'utilisation',
'email_taken' => 'L\'adresse courriel existe déjà',
@ -319,7 +319,7 @@ return array(
'field_label' => 'Nom du champ',
'field_value' => 'Valeur du champ',
'edit' => 'Éditer',
'view_as_recipient' => 'Voir en tant que destinataire',
'view_as_recipient' => 'Voir en tant que destinataire',
// product management
'product_library' => 'Inventaire',
@ -387,7 +387,7 @@ return array(
'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_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é.',
@ -426,7 +426,7 @@ return array(
'sample_data' => 'Données fictives présentées',
'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',
'invoice_settings' => 'Paramètres des factures',
'invoice_number_prefix' => 'Préfixe du numéro de facture',
@ -436,7 +436,7 @@ return array(
'share_invoice_counter' => 'Partager le compteur de facture',
'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',
'mark_sent' => 'Marquer comme envoyé',
'mark_sent' => 'Marquer comme envoyé',
'gateway_help_1' => ':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' => '',
'buy' => 'Acheter',
'bought_designs' => 'Les nouveaux modèles ont été ajoutés avec succès',
'sent' => 'envoyé',
'timesheets' => 'Feuilles de temps',
@ -460,7 +460,7 @@ return array(
'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_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',
'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.',
'discount_percent' => 'Pourcent',
'discount_amount' => 'Montant',
'invoice_history' => 'Historique des factures',
'quote_history' => 'Historique des devis',
'current_version' => 'Version courante',
@ -503,7 +503,7 @@ return array(
'payment_email' => 'Email de paiement',
'quote_email' => 'Email de déclaration',
'reset_all' => 'Réinitialiser',
'approve' => 'Accepter',
'approve' => 'Accepter',
'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.',
@ -527,7 +527,7 @@ return array(
'order_overview' => 'Order overview',
'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.',
'default_invoice_footer' => 'Définir par défaut',
'invoice_footer' => 'Pied de facture',
'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.',
'resend_confirmation' => 'Resend confirmation email',
'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.',
'payment_type_credit_card' => 'Carte de crédit',
'payment_type_paypal' => 'PayPal',
@ -585,19 +585,18 @@ return array(
'knowledge_base' => 'Base de connaissances',
'partial' => 'Partiel',
'partial_remaining' => ':partial de :balance',
'more_fields' => 'Plus de champs',
'less_fields' => 'Moins de champs',
'client_name' => 'Nom du client',
'pdf_settings' => 'Réglages PDF',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Réglages du produit',
'auto_wrap' => 'Auto Line Wrap',
'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.',
'view_documentation' => 'Voir documentation',
'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.',
'rows' => 'lignes',
'www' => 'www',
'logo' => 'Logo',
@ -628,7 +627,7 @@ return array(
'archive_task' => 'Archiver tâche',
'restore_task' => 'Restaurer tâche',
'delete_task' => 'Supprimer tâche',
'stop_task' => 'Arrêter tâcher',
'stop_task' => 'Arrêter tâche',
'time' => 'Temps',
'start' => 'Début',
'stop' => 'Fin',
@ -682,21 +681,71 @@ return array(
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Editer détails',
'edit_details' => 'Modifier',
'work' => 'Travail',
'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'cliquer ici',
'email_receipt' => 'Email payment receipt to the 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',
'new_account' => 'Nouveau compte',
'new_company' => 'Nouveau compte',
'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Connexion',
'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(
@ -35,11 +35,11 @@ return array(
'invoice_number_short' => 'Facture #',
'po_number' => 'Numéro du bon de commande',
'po_number_short' => 'Bon de commande #',
'frequency_id' => 'Fréquence',
'frequency_id' => 'Fréquence',
'discount' => 'Remise',
'taxes' => 'Taxes',
'tax' => 'Taxe',
'item' => 'Article',
'item' => 'Article',
'description' => 'Description',
'unit_cost' => 'Coût unitaire',
'quantity' => 'Quantité',
@ -117,11 +117,11 @@ return array(
'billed_client' => 'client facturé',
'billed_clients' => 'clients facturés',
'active_client' => 'client actif',
'active_clients' => 'clients actifs',
'active_clients' => 'clients actifs',
'invoices_past_due' => 'Date limite de paiement dépassée',
'upcoming_invoices' => 'Factures à venir',
'average_invoice' => 'Moyenne de facturation',
// list pages
'archive' => 'Archiver',
'delete' => 'Supprimer',
@ -276,7 +276,7 @@ return array(
// Payment page
'secure_payment' => 'Paiement sécurisé',
'card_number' => 'Numéro de carte',
'expiration_month' => 'Mois d\'expiration',
'expiration_month' => 'Mois d\'expiration',
'expiration_year' => 'Année d\'expiration',
'cvv' => 'CVV',
@ -297,8 +297,8 @@ return array(
'remove_logo_link' => 'Cliquez ici',
],
'logout' => 'Se déconnecter',
'sign_up_to_save' => 'Connectez vous pour sauvegarder votre travail',
'logout' => 'Se déconnecter',
'sign_up_to_save' => 'Connectez vous pour sauvegarder votre travail',
'agree_to_terms' =>'J\'accepte les conditions d\'utilisation d\'Invoice ninja :terms',
'terms_of_service' => 'Conditions d\'utilisation',
'email_taken' => 'L\'adresse courriel existe déjà',
@ -319,7 +319,7 @@ return array(
'field_label' => 'Nom du champ',
'field_value' => 'Valeur du champ',
'edit' => 'Éditer',
'view_as_recipient' => 'Voir en tant que destinataire',
'view_as_recipient' => 'Voir en tant que destinataire',
// product management
'product_library' => 'Inventaire',
@ -387,7 +387,7 @@ return array(
'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_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é.',
@ -426,7 +426,7 @@ return array(
'sample_data' => 'Données fictives présentées',
'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',
'invoice_settings' => 'Paramètres des factures',
'invoice_number_prefix' => 'Préfixe du numéro de facture',
@ -436,7 +436,7 @@ return array(
'share_invoice_counter' => 'Partager le compteur de facture',
'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',
'mark_sent' => 'Marquer comme envoyé',
'mark_sent' => 'Marquer comme envoyé',
'gateway_help_1' => ':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' => '',
'buy' => 'Acheter',
'bought_designs' => 'Les nouveaux modèles ont été ajoutés avec succès',
'sent' => 'envoyé',
'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_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.',
'vat_number' => 'Numéro de TVA',
'vat_number' => 'Numéro de TVA',
'id_number' => 'Numéro ID',
'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.',
'discount_percent' => 'Pourcent',
'discount_amount' => 'Montant',
'invoice_history' => 'Historique des factures',
'quote_history' => 'Historique des devis',
'current_version' => 'Version courante',
@ -503,7 +503,7 @@ return array(
'payment_email' => 'Courriel de paiement',
'quote_email' => 'Courriel de devis',
'reset_all' => 'Tout remettre à zéro',
'approve' => 'Approuver',
'approve' => 'Approuver',
'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.',
@ -527,7 +527,7 @@ return array(
'order_overview' => 'Order overview',
'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.',
'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
@ -571,13 +571,13 @@ return array(
'send_email' => 'Send email',
'set_password' => 'Set Password',
'converted' => 'Converted',
'email_approved' => 'Email me when a quote is <b>approved</b>',
'notification_quote_approved_subject' => 'Quote :invoice was approved by :client',
'notification_quote_approved' => 'The following client :client approved Quote :invoice for :amount.',
'resend_confirmation' => 'Resend confirmation email',
'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.',
'payment_type_credit_card' => 'Credit card',
'payment_type_paypal' => 'PayPal',
@ -590,14 +590,13 @@ return array(
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap',
'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.',
'view_documentation' => 'View Documentation',
'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.',
'rows' => 'rows',
'www' => 'www',
'logo' => 'Logo',
@ -619,50 +618,50 @@ return array(
'last_invoice_sent' => 'Last invoice sent :date',
'processed_updates' => 'Successfully completed update',
'tasks' => 'Tasks',
'new_task' => 'New Task',
'start_time' => 'Start Time',
'created_task' => 'Successfully created task',
'updated_task' => 'Successfully updated task',
'tasks' => 'Tâches',
'new_task' => 'Nouvelle Tâche',
'start_time' => 'Démarrée à',
'created_task' => 'Tâche créée avec succès',
'updated_task' => 'Tâche modifiée avec succès',
'edit_task' => 'Edit Task',
'archive_task' => 'Archive Task',
'restore_task' => 'Restore Task',
'delete_task' => 'Delete Task',
'stop_task' => 'Stop Task',
'archive_task' => 'Archiver la Tâche',
'restore_task' => 'Restaurer la Tâche',
'delete_task' => 'Supprimer la Tâche',
'stop_task' => 'Arrêter la Tâche',
'time' => 'Time',
'start' => 'Start',
'stop' => 'Stop',
'start' => 'Démarrer',
'stop' => 'Arrêter',
'now' => 'Now',
'timer' => 'Timer',
'manual' => 'Manual',
'date_and_time' => 'Date & Time',
'second' => 'second',
'seconds' => 'seconds',
'second' => 'seconde',
'seconds' => 'secondes',
'minute' => 'minute',
'minutes' => 'minutes',
'hour' => 'hour',
'hours' => 'hours',
'task_details' => 'Task Details',
'duration' => 'Duration',
'end_time' => 'End Time',
'hour' => 'heure',
'hours' => 'heures',
'task_details' => 'Détails de la Tâche',
'duration' => 'Durée',
'end_time' => 'Arrêtée à',
'end' => 'End',
'invoiced' => 'Invoiced',
'logged' => 'Logged',
'running' => 'Running',
'task_error_multiple_clients' => 'The tasks can\'t belong to different clients',
'task_error_running' => 'Please stop running tasks first',
'task_error_invoiced' => 'Tasks have already been invoiced',
'restored_task' => 'Successfully restored task',
'archived_task' => 'Successfully archived task',
'archived_tasks' => 'Successfully archived :count tasks',
'deleted_task' => 'Successfully deleted task',
'deleted_tasks' => 'Successfully deleted :count tasks',
'create_task' => 'Create Task',
'stopped_task' => 'Successfully stopped task',
'invoice_task' => 'Invoice Task',
'task_error_multiple_clients' => 'Une tâche ne peut appartenir à plusieurs clients',
'task_error_running' => 'Merci d\'arrêter les tâches en cours',
'task_error_invoiced' => 'Ces tâches ont déjà été facturées',
'restored_task' => 'Tâche restaurée avec succès',
'archived_task' => 'Tâche archivée avec succès',
'archived_tasks' => ':count tâches archivées avec succès',
'deleted_task' => 'Tâche supprimée avec succès',
'deleted_tasks' => ':count tâches supprimées avec succès',
'create_task' => 'Créer une Tâche',
'stopped_task' => 'Tâche arrêtée avec succès',
'invoice_task' => 'Facturer Tâche',
'invoice_labels' => 'Invoice Labels',
'prefix' => 'Prefix',
'counter' => 'Counter',
'prefix' => 'Préfixe',
'counter' => 'Compteur',
'payment_type_dwolla' => '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_feature8' => 'Option to Attach PDFs to Client Emails',
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Edit Details',
'work' => 'Work',
'resume' => 'Continuer',
'break_duration' => 'Pause',
'edit_details' => 'Modifier',
'work' => 'Travail',
'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',
'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account',
'add_company' => 'Add Company',
'untitled' => 'Untitled',
'new_account' => 'New Account',
'new_company' => 'New Company',
'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login',
'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',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap',
'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',
'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account',
'add_company' => 'Add Company',
'untitled' => 'Untitled',
'new_account' => 'New Account',
'new_company' => 'New Company',
'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login',
'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,20 +1,19 @@
<?php
<?php
return array(
return [
/*
|--------------------------------------------------------------------------
| Pagination Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used by the paginator library to build
| the simple pagination links. You are free to change them to anything
| you want to customize your views to better match your application.
|
*/
/*
|--------------------------------------------------------------------------
| Puslapiavimo kalbos eilutės
|--------------------------------------------------------------------------
|
| Šios kalbos eilutės yra naudojamas puslapiavimo bibliotekos kurti
| paprastas puslapiavimo nuorodas. Jūs galite laisvai keisti jas
| į bet kokias kitas labiau tinkančias Jūsų programai.
|
*/
'previous' => '&laquo; Previous',
'previous' => '&laquo; Ankstesnis',
'next' => 'Sekantis &raquo;',
'next' => 'Next &raquo;',
);
];

View File

@ -1,24 +1,21 @@
<?php
return array(
return [
/*
|--------------------------------------------------------------------------
| Password Reminder Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are the default lines which match reasons
| that are given by the password broker for a password update attempt
| has failed, such as for an invalid token or invalid new password.
|
*/
/*
|--------------------------------------------------------------------------
| Slaptažodžio priminimo kalbos eilutės
|--------------------------------------------------------------------------
|
| Sekančios kalbos eilutės yra numatytos elutės, atitinkančios priežastims,
| pateikiamoms slatažodžių tarpininko, kai nepavyksta slaptažodžio atnaujinimo
| bandymas, tokioms kaip negaliojanti žymė ar neteisingas naujas slaptažodis..
|
*/
"password" => "Passwords must be at least six characters and match the confirmation.",
"user" => "We can't find a user with that e-mail address.",
"token" => "This password reset token is invalid.",
"sent" => "Password reminder sent!",
);
"password" => "Slaptažodis turi būti bent šešių simbolių ir sutapti su patvirtinimu.",
"user" => "Vartotojas su tokiu el. pašu nerastas.",
"token" => "Šis slaptažodžio raktas yra neteisingas.",
"sent" => "Naujo slaptažodžio nustatymo nuoroda išsiųsta",
"reset" => "Nustatytas naujas slaptažodis!",
];

View File

@ -600,7 +600,6 @@ return array(
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap',
'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',
'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account',
'add_company' => 'Add Company',
'untitled' => 'Untitled',
'new_account' => 'New Account',
'new_company' => 'New Company',
'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login',
'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,103 +1,108 @@
<?php
return array(
return [
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| such as the size rules. Feel free to tweak each of these messages.
|
*/
/*
|--------------------------------------------------------------------------
| Patvirtinimo kalbos eilutės
|--------------------------------------------------------------------------
|
| Sekančios kalbos eilutėse yra numatyti klaidos pranešimai naudojami
| patvirtinimo klasėje. Kai kurios šių eilučių turi keletą versijų
| tokių kaip dydžio taisyklės. Galite laisvai pataisyti bet kuriuos pranešimus.
|
*/
"accepted" => "The :attribute must be accepted.",
"active_url" => "The :attribute is not a valid URL.",
"after" => "The :attribute must be a date after :date.",
"alpha" => "The :attribute may only contain letters.",
"alpha_dash" => "The :attribute may only contain letters, numbers, and dashes.",
"alpha_num" => "The :attribute may only contain letters and numbers.",
"array" => "The :attribute must be an array.",
"before" => "The :attribute must be a date before :date.",
"between" => array(
"numeric" => "The :attribute must be between :min - :max.",
"file" => "The :attribute must be between :min - :max kilobytes.",
"string" => "The :attribute must be between :min - :max characters.",
"array" => "The :attribute must have between :min - :max items.",
),
"confirmed" => "The :attribute confirmation does not match.",
"date" => "The :attribute is not a valid date.",
"date_format" => "The :attribute does not match the format :format.",
"different" => "The :attribute and :other must be different.",
"digits" => "The :attribute must be :digits digits.",
"digits_between" => "The :attribute must be between :min and :max digits.",
"email" => "The :attribute format is invalid.",
"exists" => "The selected :attribute is invalid.",
"image" => "The :attribute must be an image.",
"in" => "The selected :attribute is invalid.",
"integer" => "The :attribute must be an integer.",
"ip" => "The :attribute must be a valid IP address.",
"max" => array(
"numeric" => "The :attribute may not be greater than :max.",
"file" => "The :attribute may not be greater than :max kilobytes.",
"string" => "The :attribute may not be greater than :max characters.",
"array" => "The :attribute may not have more than :max items.",
),
"mimes" => "The :attribute must be a file of type: :values.",
"min" => array(
"numeric" => "The :attribute must be at least :min.",
"file" => "The :attribute must be at least :min kilobytes.",
"string" => "The :attribute must be at least :min characters.",
"array" => "The :attribute must have at least :min items.",
),
"not_in" => "The selected :attribute is invalid.",
"numeric" => "The :attribute must be a number.",
"regex" => "The :attribute format is invalid.",
"required" => "The :attribute field is required.",
"required_if" => "The :attribute field is required when :other is :value.",
"required_with" => "The :attribute field is required when :values is present.",
"required_without" => "The :attribute field is required when :values is not present.",
"same" => "The :attribute and :other must match.",
"size" => array(
"numeric" => "The :attribute must be :size.",
"file" => "The :attribute must be :size kilobytes.",
"string" => "The :attribute must be :size characters.",
"array" => "The :attribute must contain :size items.",
),
"unique" => "The :attribute has already been taken.",
"url" => "The :attribute format is invalid.",
"positive" => "The :attribute must be greater than zero.",
"has_credit" => "The client does not have enough credit.",
"notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
"accepted" => "Laukas :attribute turi būti priimtas.",
"active_url" => "Laukas :attribute nėra galiojantis internetinis adresas.",
"after" => "Laukelyje :attribute turi būti data po :date.",
"alpha" => "Laukas :attribute gali turėti tik raides.",
"alpha_dash" => "Laukas :attribute gali turėti tik raides, skaičius ir brūkšnelius.",
"alpha_num" => "Laukas :attribute gali turėti tik raides ir skaičius.",
"array" => "Laukas :attribute turi būti masyvas.",
"before" => "Laukas :attribute turi būti data prieš :date.",
"between" => [
"numeric" => "Lauko :attribute reikšmė turi būti tarp :min ir :max.",
"file" => "Failo dydis lauke :attribute turi būti tarp :min ir :max kilobaitų.",
"string" => "Simbolių skaičius lauke :attribute turi būti tarp :min ir :max.",
"array" => "Elementų skaičius lauke :attribute turi turėti nuo :min iki :max.",
],
"boolean" => "Lauko reikšmė :attribute turi būti 'taip' arba 'ne'.",
"confirmed" => "Lauko :attribute patvirtinimas nesutampa.",
"date" => "Lauko :attribute reikšmė nėra galiojanti data.",
"date_format" => "Lauko :attribute reikšmė neatitinka formato :format.",
"different" => "Laukų :attribute ir :other reikšmės turi skirtis.",
"digits" => "Laukas :attribute turi būti sudarytas iš :digits skaitmenų.",
"digits_between" => "Laukas :attribute tuti turėti nuo :min iki :max skaitmenų.",
"email" => "Lauko :attribute reikšmė turi būti galiojantis el. pašto adresas.",
"filled" => "Laukas :attribute turi būti užpildytas.",
"exists" => "Pasirinkta negaliojanti :attribute reikšmė.",
"image" => "Lauko :attribute reikšmė turi būti paveikslėlis.",
"in" => "Pasirinkta negaliojanti :attribute reikšmė.",
"integer" => "Lauko :attribute reikšmė turi būti veikasis skaičius.",
"ip" => "Lauko :attribute reikšmė turi būti galiojantis IP adresas.",
"max" => [
"numeric" => "Lauko :attribute reikšmė negali būti didesnė nei :max.",
"file" => "Failo dydis lauke :attribute reikšmė negali būti didesnė nei :max kilobaitų.",
"string" => "Simbolių kiekis lauke :attribute reikšmė negali būti didesnė nei :max simbolių.",
"array" => "Elementų kiekis lauke :attribute negali turėti daugiau nei :max elementų.",
],
"mimes" => "Lauko reikšmė :attribute turi būti failas vieno iš sekančių tipų: :values.",
"min" => [
"numeric" => "Lauko :attribute reikšmė turi būti ne mažesnė nei :min.",
"file" => "Failo dydis lauke :attribute turi būti ne mažesnis nei :min kilobaitų.",
"string" => "Simbolių kiekis lauke :attribute turi būti ne mažiau nei :min.",
"array" => "Elementų kiekis lauke :attribute turi būti ne mažiau nei :min.",
],
"not_in" => "Pasirinkta negaliojanti reikšmė :attribute.",
"numeric" => "Lauko :attribute reikšmė turi būti skaičius.",
"regex" => "Negaliojantis lauko :attribute formatas.",
"required" => "Privaloma užpildyti lauką :attribute.",
"required_if" => "Privaloma užpildyti lauką :attribute kai :other yra :value.",
"required_with" => "Privaloma užpildyti lauką :attribute kai pateikta :values.",
"required_with_all" => "Privaloma užpildyti lauką :attribute kai pateikta :values.",
"required_without" => "Privaloma užpildyti lauką :attribute kai nepateikta :values.",
"required_without_all" => "Privaloma užpildyti lauką :attribute kai nepateikta nei viena iš reikšmių :values.",
"same" => "Laukai :attribute ir :other turi sutapti.",
"size" => [
"numeric" => "Lauko :attribute reikšmė turi būti :size.",
"file" => "Failo dydis lauke :attribute turi būti :size kilobaitai.",
"string" => "Simbolių skaičius lauke :attribute turi būti :size.",
"array" => "Elementų kiekis lauke :attribute turi būti :size.",
],
"string" => "The :attribute must be a string.",
"timezone" => "Lauko :attribute reikšmė turi būti galiojanti laiko zona.",
"unique" => "Tokia :attribute reikšmė jau pasirinkta.",
"url" => "Negaliojantis lauko :attribute formatas.",
'custom' => array(),
/*
|--------------------------------------------------------------------------
| Pasirinktiniai patvirtinimo kalbos eilutės
|--------------------------------------------------------------------------
|
| Čia galite nurodyti pasirinktinius patvirtinimo pranešimus, naudodami
| konvenciją "attribute.rule" eilučių pavadinimams. Tai leidžia greitai
| nurodyti konkrečią pasirinktinę kalbos eilutę tam tikrai atributo taisyklei.
|
*/
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap attribute place-holders
| with something more reader friendly such as E-Mail Address instead
| of "email". This simply helps us make messages a little cleaner.
|
*/
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
'attributes' => array(),
/*
|--------------------------------------------------------------------------
| Pasirinktiniai patvirtinimo atributai
|--------------------------------------------------------------------------
|
| Sekančios kalbos eilutės naudojamos pakeisti vietos žymes
| kuo nors labiau priimtinu skaitytojui (pvz. "El.Pašto Adresas" vietoj
| "email". TTai tiesiog padeda mums padaryti žinutes truputi aiškesnėmis.
|
*/
);
'attributes' => [],
];

View File

@ -598,7 +598,6 @@ return array(
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap',
'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',
'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account',
'add_company' => 'Add Company',
'untitled' => 'Untitled',
'new_account' => 'New Account',
'new_company' => 'New Company',
'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login',
'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.',
'unsaved_changes' => 'Je hebt niet bewaarde wijzigingen',
'custom_fields' => 'Custom fields',
'company_fields' => 'Company Fields',
'client_fields' => 'Client Fields',
'field_label' => 'Field Label',
'field_value' => 'Field Value',
'custom_fields' => 'Aangepaste velden',
'company_fields' => 'Velden Bedrijf',
'client_fields' => 'Velden Klant',
'field_label' => 'Label Veld',
'field_value' => 'Waarde Veld',
'edit' => 'Bewerk',
'view_invoice' => 'Bekijk factuur',
'view_as_recipient' => 'Bekijk als ontvanger',
// product management
'product_library' => 'Product Library',
'product_library' => 'Product Bibliotheek',
'product' => 'Product',
'products' => 'Products',
'fill_products' => 'Auto-fill products',
'fill_products_help' => 'Selecting a product will automatically <b>set the description and cost</b>',
'update_products' => 'Auto-update products',
'update_products_help' => 'Updating an invoice will automatically <b>update the products</b>',
'create_product' => 'Create Product',
'edit_product' => 'Edit Product',
'archive_product' => 'Archive Product',
'updated_product' => 'Successfully updated product',
'created_product' => 'Successfully created product',
'archived_product' => 'Successfully archived product',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan',
'products' => 'Producten',
'fill_products' => 'Producten Automatisch aanvullen',
'fill_products_help' => 'Een product selecteren zal automatisch <b>de beschrijving en kost instellen</b>',
'update_products' => 'Producten automatisch aanpassen',
'update_products_help' => 'Aanpassen van een factuur zal automatisch <b>de producten aanpassen</b>',
'create_product' => 'Product maken',
'edit_product' => 'Product aanpassen',
'archive_product' => 'Product Archiveren',
'updated_product' => 'Product Succesvol aangepast',
'created_product' => 'Product Succesvol aangemaakt',
'archived_product' => 'Product Succesvol gearchiveerd',
'pro_plan_custom_fields' => ':link om aangepaste velden in te schakelen door het Pro Plan te nemen',
'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',
'specify_colors' => 'Kies kleuren',
'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!',
'go_pro' => 'Go Pro',
@ -395,9 +395,9 @@ return array(
'invoice_fields' => 'Factuur Velden',
'invoice_options' => 'Factuur Opties',
'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_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_quantity_help' => 'Als de artikel-aantallen altijd 1 zijn, kunt u uw facturen netter maken door dit veld te verbergen.',
'hide_paid_to_date' => 'Verberg "Reeds betaald"',
'hide_paid_to_date_help' => 'Toon alleen het "Reeds betaald" gebied op je facturen als er een betaling gemaakt is.',
'charge_taxes' => 'Charge taxes',
'user_management' => 'Gebruikersbeheer',
@ -439,15 +439,15 @@ return array(
'mark_sent' => 'Markeer als verzonden',
'gateway_help_1' => ':link to sign up for Authorize.net.',
'gateway_help_2' => ':link to sign up for Authorize.net.',
'gateway_help_17' => ':link to get your PayPal API signature.',
'gateway_help_23' => 'Note: use your secret API key, not your publishable API key.',
'gateway_help_27' => ':link to sign up for TwoCheckout.',
'gateway_help_1' => ':link om in te schrijven voor Authorize.net.',
'gateway_help_2' => ':link om in te schrijven voor Authorize.net.',
'gateway_help_17' => ':link om je PayPal API signature te krijgen.',
'gateway_help_23' => 'Opmerking: gebruik je gehieme API key, niet je publiceerbare API key.',
'gateway_help_27' => ':link om in te schrijven voor TwoCheckout.',
'more_designs' => 'Meer 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_self_host_header' => 'Krijg 6 extra factuurontwerpen voor maar $'.INVOICE_DESIGNS_PRICE,
'more_designs_self_host_text' => '',
@ -458,12 +458,12 @@ return array(
'sent' => 'verzonden',
'timesheets' => 'Timesheets',
'payment_title' => 'Enter Your Billing Address and Credit Card information',
'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_footer2' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'vat_number' => 'Vat Number',
'id_number' => 'ID Number',
'payment_title' => 'Geef je betalingsadres en kredietkaart gegevens op',
'payment_cvv' => '*Dit is de code van 3-4 tekens op de achterkant van je kaart',
'payment_footer1' => '*Betalingsadres moet overeenkomen met het adres dat aan je kaart gekoppekd is.',
'payment_footer2' => '*Klik alstublieft slechts 1 keer op "PAY NOW" - verwerking kan tot 1 minuut duren.',
'vat_number' => 'BTW Nummer',
'id_number' => 'ID Nummer',
'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.',
@ -508,18 +508,18 @@ return array(
'reset_all' => 'Reset Alles',
'approve' => 'Goedkeuren',
'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_1' => 'Disabled',
'token_billing_2' => 'Opt-in - checkbox is shown but not selected',
'token_billing_3' => 'Opt-out - checkbox is shown and selected',
'token_billing_4' => 'Always',
'token_billing_checkbox' => 'Store credit card details',
'view_in_stripe' => 'View in Stripe',
'token_billing_type_id' => 'Betalingstoken',
'token_billing_help' => 'Laat je toe om kredietkaart gegevens bij je gateway op te slaan en ze later te gebruiken.',
'token_billing_1' => 'Inactief',
'token_billing_2' => 'Opt-in - checkbox is getoond maar niet geselecteerd',
'token_billing_3' => 'Opt-out - checkbox is getoond en geselecteerd',
'token_billing_4' => 'Altijd',
'token_billing_checkbox' => 'Sla kredietkaart gegevens op',
'view_in_stripe' => 'In Stripe bekijken',
'use_card_on_file' => 'Use card on file',
'edit_payment_details' => 'Edit payment details',
'token_billing' => 'Save card details',
'token_billing_secure' => 'The data is stored securely by :stripe_link',
'edit_payment_details' => 'Betalingsdetails aanpassen',
'token_billing' => 'Kaartgegevens opslaan',
'token_billing_secure' => 'De gegevens zijn succesvol veilig opgeslaan door :stripe_link',
'support' => 'Ondersteuning',
'contact_information' => 'Contact informatie',
@ -546,14 +546,14 @@ return array(
'delete_token' => 'Verwijder Token',
'token' => 'Token',
'add_gateway' => 'Add Gateway',
'delete_gateway' => 'Delete Gateway',
'edit_gateway' => 'Edit Gateway',
'updated_gateway' => 'Successfully updated gateway',
'created_gateway' => 'Successfully created gateway',
'deleted_gateway' => 'Successfully deleted gateway',
'add_gateway' => 'Gateway Toevoegen',
'delete_gateway' => 'Gateway Verwijderen',
'edit_gateway' => 'Gateway Aanpassen',
'updated_gateway' => 'Gateway Succesvol aangepast',
'created_gateway' => 'Gateway Succesvol aangemaakt',
'deleted_gateway' => 'Gateway Succesvol verwijderd',
'pay_with_paypal' => 'PayPal',
'pay_with_card' => 'Credit card',
'pay_with_card' => 'Kredietkaart',
'change_password' => 'Verander wachtwoord',
'current_password' => 'Huidig wachtwoord',
@ -575,131 +575,180 @@ return array(
'set_password' => 'Stel wachtwoord in',
'converted' => 'Omgezet',
'email_approved' => 'Email me when a quote is <b>approved</b>',
'notification_quote_approved_subject' => 'Quote :invoice was approved by :client',
'notification_quote_approved' => 'The following client :client approved Quote :invoice for :amount.',
'resend_confirmation' => 'Resend confirmation email',
'confirmation_resent' => 'The confirmation email was resent',
'email_approved' => 'Email me wanneer een offerte is <b>goedgekeurd</b>',
'notification_quote_approved_subject' => 'Offerte :invoice is goedgekeurd door :client',
'notification_quote_approved' => 'De volgende Klant :client heeft Offerte :invoice goedgekeurd voor :amount.',
'resend_confirmation' => 'Verstuurd bevestingsmail opnieuw',
'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.',
'payment_type_credit_card' => 'Credit card',
'gateway_help_42' => ':link om te registreren voor BitPay.<br/>Opmerking: gebruik een Legacy API Key, niet een API token.',
'payment_type_credit_card' => 'Kredietkaart',
'payment_type_paypal' => 'PayPal',
'payment_type_bitcoin' => 'Bitcoin',
'knowledge_base' => 'Knowledge Base',
'partial' => 'Partial',
'partial_remaining' => ':partial of :balance',
'knowledge_base' => 'Kennis databank',
'partial' => 'Gedeeld',
'partial_remaining' => ':partial / :balance',
'more_fields' => 'More Fields',
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap',
'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.',
'view_documentation' => 'View Documentation',
'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.',
'more_fields' => 'Meer velden',
'less_fields' => 'Minder velden',
'client_name' => 'Klant Naam',
'pdf_settings' => 'PDF Instellingen',
'product_settings' => 'Product Instellingen',
'auto_wrap' => 'Automatisch Lijn afbreken',
'duplicate_post' => 'Opgelet: de volgende pagina is twee keer doorgestuurd. De tweede verzending is genegeerd.',
'view_documentation' => 'Bekijk Documentatie',
'app_title' => 'Gratis Open-Source Online Facturatie',
'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.',
'rows' => 'rows',
'rows' => 'rijen',
'www' => 'www',
'logo' => 'Logo',
'subdomain' => 'Subdomain',
'provide_name_or_email' => 'Please provide a contact name or email',
'charts_and_reports' => 'Charts & Reports',
'chart' => 'Chart',
'report' => 'Report',
'group_by' => 'Group by',
'paid' => 'Paid',
'enable_report' => 'Report',
'enable_chart' => 'Chart',
'totals' => 'Totals',
'run' => 'Run',
'export' => 'Export',
'documentation' => 'Documentation',
'subdomain' => 'Subdomein',
'provide_name_or_email' => 'Geef aub een contact naam en email op',
'charts_and_reports' => 'Grafieken & Rapporten',
'chart' => 'Grafiek',
'report' => 'Rapport',
'group_by' => 'Groepeer per',
'paid' => 'Betaald',
'enable_report' => 'Rapport',
'enable_chart' => 'Grafiek',
'totals' => 'Totalen',
'run' => 'Uitvoeren',
'export' => 'Exporteer',
'documentation' => 'Documentatie',
'zapier' => 'Zapier <sup>Beta</sup>',
'recurring' => 'Recurring',
'last_invoice_sent' => 'Last invoice sent :date',
'recurring' => 'Terugkerend',
'last_invoice_sent' => 'Laatste factuur verzonden :date',
'processed_updates' => 'Successfully completed update',
'tasks' => 'Tasks',
'new_task' => 'New Task',
'start_time' => 'Start Time',
'created_task' => 'Successfully created task',
'updated_task' => 'Successfully updated task',
'edit_task' => 'Edit Task',
'archive_task' => 'Archive Task',
'restore_task' => 'Restore Task',
'delete_task' => 'Delete Task',
'stop_task' => 'Stop Task',
'time' => 'Time',
'processed_updates' => 'Update succesvol uitgevoerd',
'tasks' => 'Taken',
'new_task' => 'Nieuwe Taak',
'start_time' => 'Start Tijd',
'created_task' => 'Taak succesvol aangemaakt',
'updated_task' => 'Taak succesvol aangepast',
'edit_task' => 'Pas Taak aan',
'archive_task' => 'Archiveer Taak',
'restore_task' => 'Taak herstellen',
'delete_task' => 'Verwijder Taak',
'stop_task' => 'Stop Taak',
'time' => 'Tijd',
'start' => 'Start',
'stop' => 'Stop',
'now' => 'Now',
'now' => 'Nu',
'timer' => 'Timer',
'manual' => 'Manual',
'date_and_time' => 'Date & Time',
'manual' => 'Manueel',
'date_and_time' => 'Datum & Tijd',
'second' => 'second',
'seconds' => 'seconds',
'minute' => 'minute',
'minutes' => 'minutes',
'hour' => 'hour',
'hours' => 'hours',
'task_details' => 'Task Details',
'duration' => 'Duration',
'end_time' => 'End Time',
'end' => 'End',
'invoiced' => 'Invoiced',
'logged' => 'Logged',
'running' => 'Running',
'task_error_multiple_clients' => 'The tasks can\'t belong to different clients',
'task_error_running' => 'Please stop running tasks first',
'task_error_invoiced' => 'Tasks have already been invoiced',
'restored_task' => 'Successfully restored task',
'archived_task' => 'Successfully archived task',
'archived_tasks' => 'Successfully archived :count tasks',
'deleted_task' => 'Successfully deleted task',
'deleted_tasks' => 'Successfully deleted :count tasks',
'create_task' => 'Create Task',
'stopped_task' => 'Successfully stopped task',
'invoice_task' => 'Invoice Task',
'invoice_labels' => 'Invoice Labels',
'prefix' => 'Prefix',
'counter' => 'Counter',
'seconds' => 'seconden',
'minute' => 'minuut',
'minutes' => 'minuten',
'hour' => 'uur',
'hours' => 'uren',
'task_details' => 'Taak Details',
'duration' => 'Duur',
'end_time' => 'Eind Tijd',
'end' => 'Einde',
'invoiced' => 'Gefactureerd',
'logged' => 'Gelogd',
'running' => 'Lopend',
'task_error_multiple_clients' => 'Taken kunnen niet tot meerdere klanten behoren',
'task_error_running' => 'Stop aub de lopende taken eerst',
'task_error_invoiced' => 'Deze taken zijn al gefactureerd',
'restored_task' => 'Taak succesvol hersteld',
'archived_task' => 'Taak succesvol gearchiveerd',
'archived_tasks' => ':count taken succesvol gearchiveerd',
'deleted_task' => 'Taak succesvol verwijderd',
'deleted_tasks' => ':count taken succesvol verwijderd',
'create_task' => 'Taak aanmaken',
'stopped_task' => 'Taak succesvol gestopt',
'invoice_task' => 'Factuur taak',
'invoice_labels' => 'Factuur labels',
'prefix' => 'Voorvoegsel',
'counter' => 'Teller',
'payment_type_dwolla' => 'Dwolla',
'gateway_help_43' => ':link to sign up for Dwolla.',
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
'gateway_help_43' => ':link om in te schrijven voor Dwolla.',
'partial_value' => 'Moet groter zijn dan nul en minder dan het totaal',
'more_actions' => 'Meer acties',
'pro_plan_title' => 'NINJA PRO',
'pro_plan_call_to_action' => 'Upgrade Now!',
'pro_plan_feature1' => 'Create Unlimited Clients',
'pro_plan_feature2' => 'Access to 10 Beautiful Invoice Designs',
'pro_plan_feature3' => 'Custom URLs - "YourBrand.InvoiceNinja.com"',
'pro_plan_feature4' => 'Remove "Created by Invoice Ninja"',
'pro_plan_feature5' => 'Multi-user Access & Activity Tracking',
'pro_plan_feature6' => 'Create Quotes & Pro-forma Invoices',
'pro_plan_feature7' => 'Customize Invoice Field Titles & Numbering',
'pro_plan_feature8' => 'Option to Attach PDFs to Client Emails',
'pro_plan_call_to_action' => 'Nu upgraden!',
'pro_plan_feature1' => 'Maak ongelimiteerd klanten aan',
'pro_plan_feature2' => 'Toegang tot 10 mooie factuur ontwerpen',
'pro_plan_feature3' => 'Aangepaste URLs - "YourBrand.InvoiceNinja.com"',
'pro_plan_feature4' => 'Verwijder "Aangemaakt door Invoice Ninja"',
'pro_plan_feature5' => 'Multi-user toegang & Activeit Tracking',
'pro_plan_feature6' => 'Maak offertes & Pro-forma facturen aan',
'pro_plan_feature7' => 'Pas factuur veld titels & nummering aan',
'pro_plan_feature8' => 'Optie om PDFs toe te voegen aan de emails naar klanten',
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Edit Details',
'work' => 'Work',
'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here',
'resume' => 'Doorgaan',
'break_duration' => 'Pauze',
'edit_details' => 'Details aanpassen',
'work' => 'Werk',
'timezone_unset' => ':link om je tijdszone aan te passen',
'click_here' => 'Klik hier',
'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',
'email_receipt' => 'Mail betalingsbewijs naar de klant',
'created_payment_emailed_client' => 'Betaling succesvol toegevoegd en gemaild naar de klant',
'add_company' => 'Bedrijf toevoegen',
'untitled' => 'Zonder titel',
'new_company' => 'Nieuw bedrijf',
'associated_accounts' => 'Accounts succesvol gekoppeld',
'unlinked_account' => 'Accounts succesvol losgekoppeld',
'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.",
"has_credit" => "De klant heeft niet voldoende krediet.",
"notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
"notmasked" => "De waarden zijn verborgen",
"less_than" => 'Het :attribute moet minder zijn dan :value',
/*
|--------------------------------------------------------------------------

View File

@ -593,7 +593,6 @@ return array(
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap',
'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',
'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account',
'add_company' => 'Add Company',
'untitled' => 'Untitled',
'new_account' => 'New Account',
'new_company' => 'New Company',
'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login',
'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',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings',
'auto_wrap' => 'Auto Line Wrap',
'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',
'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account',
'add_company' => 'Add Company',
'untitled' => 'Untitled',
'new_account' => 'New Account',
'new_company' => 'New Company',
'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login',
'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">
@if ($accountGateway)
{!! Former::populateField('payment_type_id', $paymentTypeId) !!}
{!! 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)
@foreach ($accountGateway->fields as $field => $junk)
@if (in_array($field, $hiddenFields))
@ -28,6 +31,8 @@
@endif
@else
{!! Former::populateField('gateway_id', GATEWAY_STRIPE) !!}
{!! Former::populateField('show_address', 1) !!}
{!! Former::populateField('update_address', 1) !!}
@endif
{!! Former::select('payment_type_id')
@ -77,6 +82,15 @@
@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[]')
->label('Accepted Credit Cards')
->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() {
setPaymentType();
@if ($accountGateway)
$('.payment-type-option').hide();
@endif
$('#show_address').change(enableUpdateAddress);
enableUpdateAddress();
})
</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) }}
@if ($showUser)
{{ Former::populateField('first_name', $account->users()->first()->first_name) }}
{{ Former::populateField('last_name', $account->users()->first()->last_name) }}
{{ Former::populateField('email', $account->users()->first()->email) }}
{{ Former::populateField('phone', $account->users()->first()->phone) }}
{{ Former::populateField('first_name', $primaryUser->first_name) }}
{{ Former::populateField('last_name', $primaryUser->last_name) }}
{{ Former::populateField('email', $primaryUser->email) }}
{{ Former::populateField('phone', $primaryUser->phone) }}
@if (Utils::isNinjaDev())
{{ Former::populateField('dark_mode', intval($primaryUser->dark_mode)) }}
@endif
@endif
<div class="row">
@ -48,7 +51,7 @@
@if (file_exists($account->getLogoPath()))
<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>
</center><br/>
@endif
@ -81,13 +84,17 @@
@if ($showUser)
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.users') !!}</h3>
<h3 class="panel-title">{!! trans('texts.primary_user') !!}</h3>
</div>
<div class="panel-body">
{!! Former::text('first_name') !!}
{!! Former::text('last_name') !!}
{!! Former::text('email') !!}
{!! Former::text('phone') !!}
@if (Utils::isNinjaDev())
{!! Former::checkbox('dark_mode')->text(trans('texts.dark_mode_help')) !!}
@endif
@if (Auth::user()->confirmed)
{!! Former::actions( Button::primary(trans('texts.change_password'))->small()->withAttributes(['onclick'=>'showChangePassword()'])) !!}
@elseif (Auth::user()->registered)

View File

@ -108,7 +108,7 @@
}
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
@foreach (\App\Models\Gateway::getPaymentTypeLinks() as $type)

View File

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

View File

@ -23,8 +23,6 @@
{{ Former::populateField('custom_invoice_taxes2', intval($account->custom_invoice_taxes2)) }}
{{ Former::populateField('share_counter', intval($account->share_counter)) }}
{{ 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="col-md-6">
@ -98,10 +96,6 @@
</div>
<div class="panel-body">
{!! 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>

View File

@ -8,7 +8,7 @@
{!! HTML::nav_link('company/products', 'product_library') !!}
{!! HTML::nav_link('company/notifications', 'notifications') !!}
{!! 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>
<br/>

View File

@ -1,6 +1,6 @@
<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_settings', 'invoice_settings') !!}
{!! 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/user_management', 'users_and_tokens') !!}

View File

@ -20,7 +20,7 @@
@endif
{!! Former::text('product_key')->label('texts.product') !!}
{!! Former::textarea('notes')->data_bind("value: wrapped_notes, valueUpdate: 'afterkeydown'") !!}
{!! Former::textarea('notes') !!}
{!! Former::text('cost') !!}
</div>
@ -35,29 +35,6 @@
<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() {
$('#product_key').focus();
});

View File

@ -13,7 +13,7 @@
<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())
{!! Button::normal(trans('texts.zapier'))->asLinkTo(ZAPIER_URL)->withAttributes(['target' => '_blank']) !!}
@endif

View File

@ -79,11 +79,11 @@
{!! Former::hidden('remember')->raw() !!}
</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>
<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

View File

@ -6,7 +6,10 @@
<div class="col-md-4">
<div class="panel panel-default">
<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">
@if (count($paidToDate))
@foreach ($paidToDate as $item)
@ -16,9 +19,6 @@
{{ Utils::formatMoney(0) }}
@endif
</div>
<div class="in-thin">
{{ trans('texts.in_total_revenue') }}
</div>
</div>
</div>
</div>
@ -44,12 +44,18 @@
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-body">
<img src="{{ asset('images/totalinvoices.png') }}" class="in-image"/>
<div class="in-bold">
{{ $invoicesSent }}
</div>
<img src="{{ asset('images/totalincome.png') }}" class="in-image"/>
<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>
@ -61,13 +67,16 @@
<div class="row">
<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">
<h3 class="panel-title in-bold-white">
<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>
</div>
<ul class="panel-body list-group">
<ul class="panel-body list-group" style="height:276px;overflow-y:auto;">
@foreach ($activities as $activity)
<li class="list-group-item">
<span style="color:#888;font-style:italic">{{ Utils::timestampToDateString(strtotime($activity->created_at)) }}:</span>
@ -76,15 +85,43 @@
@endforeach
</ul>
</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 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">
<h3 class="panel-title in-bold-white">
<i class="glyphicon glyphicon-time"></i> {{ trans('texts.invoices_past_due') }}
</h3>
</div>
<div class="panel-body">
<div class="panel-body" style="height:274px;overflow-y:auto;">
<table class="table table-striped">
<thead>
<th>{{ trans('texts.invoice_number_short') }}</th>
@ -95,23 +132,23 @@
<tbody>
@foreach ($pastDue as $invoice)
<tr>
<td>{!! $invoice->getLink() !!}</td>
<td>{{ $invoice->client->getDisplayName() }}</td>
<td>{!! \App\Models\Invoice::calcLink($invoice) !!}</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::formatMoney($invoice->balance, $invoice->client->getCurrencyId()) }}</td>
<td>{{ Utils::formatMoney($invoice->balance, $invoice->currency_id ?: ($account->currency_id ?: DEFAULT_CURRENCY)) }}</td>
</tr>
@endforeach
</tbody>
</table>
</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;">
<h3 class="panel-title" style="color: black !important">
<i class="glyphicon glyphicon-time"></i> {{ trans('texts.upcoming_invoices') }}
</h3>
</div>
<div class="panel-body">
<div class="panel-body" style="height:274px;overflow-y:auto;">
<table class="table table-striped">
<thead>
<th>{{ trans('texts.invoice_number_short') }}</th>
@ -122,10 +159,10 @@
<tbody>
@foreach ($upcoming as $invoice)
<tr>
<td>{!! $invoice->getLink() !!}</td>
<td>{{ $invoice->client->getDisplayName() }}</td>
<td>{!! \App\Models\Invoice::calcLink($invoice) !!}</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::formatMoney($invoice->balance, $invoice->client->getCurrencyId()) }}</td>
<td>{{ Utils::formatMoney($invoice->balance, $invoice->currency_id ?: ($account->currency_id ?: DEFAULT_CURRENCY)) }}</td>
</tr>
@endforeach
</tbody>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,18 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<html lang="{{ App::getLocale() }}">
<head>
<meta charset="utf-8">
</head>
<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/>
{{ $account }}
</body>
{{ trans('texts.email_signature') }}<br/>
{{ $account }}
</body>
</html>

View File

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

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