Merge pull request #9 from hillelcoren/master

Update from Master
This commit is contained in:
Paul-Vincent Roll 2015-07-28 10:22:49 +02:00
commit c9521da75f
124 changed files with 5429 additions and 3316 deletions

View File

@ -19,3 +19,5 @@ MAIL_USERNAME
MAIL_FROM_ADDRESS
MAIL_FROM_NAME
MAIL_PASSWORD
ALLOW_NEW_ACCOUNTS

23
.gitignore vendored
View File

@ -13,18 +13,19 @@
/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

5
.htaccess Normal file
View File

@ -0,0 +1,5 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule "^.env" - [F,L]
RewriteRule "^storage" - [F,L]
</IfModule>

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/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',
@ -65,7 +75,7 @@ module.exports = function(grunt) {
'public/js/lightbox.min.js',
'public/js/bootstrap-combobox.js',
'public/js/script.js',
'public/js/pdf.pdfmake.js'
'public/js/pdf.pdfmake.js',
],
dest: 'public/js/built.js',
nonull: true
@ -91,7 +101,7 @@ module.exports = function(grunt) {
'public/vendor/datatables/media/css/jquery.dataTables.css',
'public/vendor/datatables-bootstrap3/BS3/assets/css/datatables.css',
'public/vendor/font-awesome/css/font-awesome.min.css',
'public/vendor/bootstrap-datepicker/css/datepicker3.css',
'public/vendor/bootstrap-datepicker/dist/css/bootstrap-datepicker3.css',
'public/vendor/spectrum/spectrum.css',
'public/css/bootstrap-combobox.css',
'public/css/typeahead.js-bootstrap.css',

View File

@ -1,8 +1,10 @@
<?php namespace App\Exceptions;
use Redirect;
use Utils;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class Handler extends ExceptionHandler {
@ -40,6 +42,11 @@ class Handler extends ExceptionHandler {
*/
public function render($request, Exception $e)
{
if ($e instanceof ModelNotFoundException) {
return Redirect::to('/');
}
if (Utils::isNinjaProd()) {
$data = [
'error' => get_class($e),

View File

@ -77,14 +77,17 @@ class AccountController extends BaseController
{
if (Auth::check()) {
return Redirect::to('invoices/create');
} elseif (!Utils::isNinja() && Account::count() > 0) {
}
if (!Utils::isNinja() && !Utils::allowNewAccounts() && Account::count() > 0) {
return Redirect::to('/login');
}
$user = false;
$guestKey = Input::get('guest_key');
$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 ($guestKey) {
if ($guestKey && !$prevUserId) {
$user = User::where('password', '=', $guestKey)->first();
if ($user && $user->registered) {
@ -97,6 +100,11 @@ class AccountController extends BaseController
$user = $account->users()->first();
Session::forget(RECENTLY_VIEWED);
if ($prevUserId) {
$users = $this->accountRepo->associateAccounts($user->id, $prevUserId);
Session::put(SESSION_USER_ACCOUNTS, $users);
}
}
Auth::login($user, true);
@ -152,6 +160,7 @@ class AccountController extends BaseController
'currencies' => Cache::get('currencies'),
'languages' => Cache::get('languages'),
'showUser' => Auth::user()->id === Auth::user()->account->users()->first()->id,
'title' => trans('texts.company_details'),
];
return View::make('accounts.details', $data);
@ -164,26 +173,33 @@ class AccountController extends BaseController
if ($count == 0) {
return Redirect::to('gateways/create');
} else {
return View::make('accounts.payments', ['showAdd' => $count < 3]);
return View::make('accounts.payments', [
'showAdd' => $count < 3,
'title' => trans('texts.online_payments')
]);
}
} elseif ($section == ACCOUNT_NOTIFICATIONS) {
$data = [
'account' => Account::with('users')->findOrFail(Auth::user()->account_id),
'title' => trans('texts.notifications'),
];
return View::make('accounts.notifications', $data);
} elseif ($section == ACCOUNT_IMPORT_EXPORT) {
return View::make('accounts.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';
@ -194,11 +210,17 @@ class AccountController extends BaseController
$client->work_phone = '';
$client->work_email = '';
$invoice->invoice_number = Auth::user()->account->getNextInvoiceNumber();
$invoice->invoice_number = $account->getNextInvoiceNumber();
$invoice->invoice_date = date_create()->format('Y-m-d');
$invoice->account = json_decode(Auth::user()->account->toJson());
$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';
@ -207,20 +229,38 @@ 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($subSection == ACCOUNT_CUSTOMIZE_DESIGN);
$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['title'] = trans('texts.email_templates');
} else if ($subSection == ACCOUNT_USER_MANAGEMENT) {
$data['title'] = trans('texts.users_and_tokens');
}
return View::make("accounts.{$subSection}", $data);
} elseif ($section == ACCOUNT_PRODUCTS) {
$data = [
'account' => Auth::user()->account,
'title' => trans('texts.product_library'),
];
return View::make('accounts.products', $data);
@ -244,6 +284,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();
}
@ -252,6 +294,24 @@ 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;
if (!$account->utf8_invoices) {
$account->utf8_invoices = true;
}
$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()) {
@ -628,6 +688,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::isNinja()) {
$user->dark_mode = Input::get('dark_mode') ? true : false;
}
$user->save();
}
@ -635,17 +698,21 @@ 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' && $account->utf8_invoices) {
$file->move('logo/', $account->account_key . '.jpg');
} else if ($mimeType == 'image/png' && $account->utf8_invoices) {
$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());
Image::canvas($image->width(), $image->height(), '#FFFFFF')
->insert($image)->save('logo/'.$account->account_key.'.jpg');
}
}
@ -659,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'));
@ -702,8 +770,6 @@ class AccountController extends BaseController
if (Utils::isNinja()) {
$this->userMailer->sendConfirmation($user);
} else {
$this->accountRepo->registerUser($user);
}
$activities = Activity::scope()->get();
@ -725,10 +791,16 @@ class AccountController extends BaseController
{
$affiliate = Affiliate::where('affiliate_key', '=', SELF_HOST_AFFILIATE_KEY)->first();
$email = trim(Input::get('email'));
if (!$email || $email == 'user@example.com') {
return '';
}
$license = new License();
$license->first_name = Input::get('first_name');
$license->last_name = Input::get('last_name');
$license->email = Input::get('email');
$license->email = $email;
$license->transaction_reference = Request::getClientIp();
$license->license_key = Utils::generateLicense();
$license->affiliate_id = $affiliate->id;
@ -753,9 +825,11 @@ class AccountController extends BaseController
}
$account = Auth::user()->account;
$this->accountRepo->unlinkAccount($account);
$account->forceDelete();
Auth::logout();
Session::flush();
return Redirect::to('/')->with('clearGuestKey', true);
}

View File

@ -200,7 +200,7 @@ class AccountGatewayController extends BaseController
$fields = $gateway->getFields();
$optional = array_merge(Gateway::$hiddenFields, Gateway::$optionalFields);
if (Utils::isNinja() && $gatewayId == GATEWAY_DWOLLA) {
if ($gatewayId == GATEWAY_DWOLLA) {
$optional = array_merge($optional, ['key', 'secret']);
}
@ -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

@ -13,6 +13,8 @@ use Session;
use Cookie;
use Response;
use App\Models\User;
use App\Models\Account;
use App\Models\Industry;
use App\Ninja\Mailers\Mailer;
use App\Ninja\Repositories\AccountRepository;
use Redirect;
@ -32,24 +34,16 @@ class AppController extends BaseController
public function showSetup()
{
if (Utils::isNinja() || Utils::isDatabaseSetup()) {
if (Utils::isNinja() || (Utils::isDatabaseSetup() && Account::count() > 0)) {
return Redirect::to('/');
}
$view = View::make('setup');
/*
$cookie = Cookie::forget('ninja_session', '/', 'www.ninja.dev');
Cookie::queue($cookie);
return Response::make($view)->withCookie($cookie);
*/
return Response::make($view);
return View::make('setup');
}
public function doSetup()
{
if (Utils::isNinja() || Utils::isDatabaseSetup()) {
if (Utils::isNinja() || (Utils::isDatabaseSetup() && Account::count() > 0)) {
return Redirect::to('/');
}
@ -104,7 +98,9 @@ class AppController extends BaseController
// == DB Migrate & Seed == //
// Artisan::call('migrate:rollback', array('--force' => true)); // Debug Purposes
Artisan::call('migrate', array('--force' => true));
Artisan::call('db:seed', array('--force' => true));
if (Industry::count() == 0) {
Artisan::call('db:seed', array('--force' => true));
}
Artisan::call('optimize', array('--force' => true));
$firstName = trim(Input::get('first_name'));
@ -114,8 +110,6 @@ class AppController extends BaseController
$account = $this->accountRepo->create($firstName, $lastName, $email, $password);
$user = $account->users()->first();
//Auth::login($user, true);
return Redirect::to('/login');
}
@ -168,7 +162,9 @@ class AppController extends BaseController
if (!Utils::isNinja() && !Utils::isDatabaseSetup()) {
try {
Artisan::call('migrate', array('--force' => true));
Artisan::call('db:seed', array('--force' => true));
if (Industry::count() == 0) {
Artisan::call('db:seed', array('--force' => true));
}
Artisan::call('optimize', array('--force' => true));
} catch (Exception $e) {
Response::make($e->getMessage(), 500);

View File

@ -3,10 +3,12 @@
use Auth;
use Event;
use Utils;
use Session;
use Illuminate\Http\Request;
use App\Models\User;
use App\Events\UserLoggedIn;
use App\Http\Controllers\Controller;
use App\Ninja\Repositories\AccountRepository;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\Registrar;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
@ -28,6 +30,7 @@ class AuthController extends Controller {
protected $loginPath = '/login';
protected $redirectTo = '/dashboard';
protected $accountRepo;
/**
* Create a new authentication controller instance.
@ -36,12 +39,13 @@ class AuthController extends Controller {
* @param \Illuminate\Contracts\Auth\Registrar $registrar
* @return void
*/
public function __construct(Guard $auth, Registrar $registrar)
public function __construct(Guard $auth, Registrar $registrar, AccountRepository $repo)
{
$this->auth = $auth;
$this->registrar = $registrar;
$this->accountRepo = $repo;
$this->middleware('guest', ['except' => 'getLogout']);
//$this->middleware('guest', ['except' => 'getLogout']);
}
public function getLoginWrapper()
@ -55,13 +59,50 @@ class AuthController extends Controller {
public function postLoginWrapper(Request $request)
{
$userId = Auth::check() ? Auth::user()->id : null;
$user = User::where('email', '=', $request->input('email'))->first();
if ($user->failed_logins >= 3) {
Session::flash('error', 'These credentials do not match our records.');
return redirect()->to('login');
}
$response = self::postLogin($request);
if (Auth::check()) {
Event::fire(new UserLoggedIn());
$users = false;
// we're linking a new account
if ($userId && Auth::user()->id != $userId) {
$users = $this->accountRepo->associateAccounts($userId, Auth::user()->id);
Session::flash('message', trans('texts.associated_accounts'));
// check if other accounts are linked
} else {
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
}
Session::put(SESSION_USER_ACCOUNTS, $users);
} elseif ($user) {
$user->failed_logins = $user->failed_logins + 1;
$user->save();
}
return $response;
}
public function getLogoutWrapper()
{
if (Auth::check() && !Auth::user()->registered) {
$account = Auth::user()->account;
$this->accountRepo->unlinkAccount($account);
$account->forceDelete();
}
$response = self::getLogout();
Session::flush();
return $response;
}
}

View File

@ -24,8 +24,11 @@ class ClientApiController extends Controller
public function index()
{
$clients = Client::scope()->with('contacts')->orderBy('created_at', 'desc')->get();
$clients = Utils::remapPublicIds($clients->toArray());
$clients = Client::scope()
->with('country', 'contacts', 'industry', 'size', 'currency')
->orderBy('created_at', 'desc')
->get();
$clients = Utils::remapPublicIds($clients);
$response = json_encode($clients, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders(count($clients));
@ -43,9 +46,9 @@ class ClientApiController extends Controller
return Response::make($error, 500, $headers);
} else {
$client = $this->clientRepo->save(false, $data, false);
$client->load('contacts');
$client = Utils::remapPublicIds($client->toArray());
$client = $this->clientRepo->save(isset($data['id']) ? $data['id'] : false, $data, false);
$client = Client::scope($client->public_id)->with('country', 'contacts', 'industry', 'size', 'currency')->first();
$client = Utils::remapPublicIds([$client]);
$response = json_encode($client, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();

View File

@ -44,6 +44,8 @@ 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();
@ -83,6 +85,7 @@ class DashboardController extends BaseController
'activities' => $activities,
'pastDue' => $pastDue,
'upcoming' => $upcoming,
'title' => trans('texts.dashboard'),
];
return View::make('dashboard', $data);

View File

@ -27,10 +27,8 @@ class HomeController extends BaseController
{
Session::reflash();
if (!Utils::isDatabaseSetup()) {
if (!Utils::isNinja() && (!Utils::isDatabaseSetup() || Account::count() == 0)) {
return Redirect::to('/setup');
} elseif (Account::count() == 0) {
return Redirect::to('/invoice_now');
} elseif (Auth::check()) {
return Redirect::to('/dashboard');
} else {
@ -45,6 +43,12 @@ class HomeController extends BaseController
public function invoiceNow()
{
if (Auth::check() && Input::get('new_account')) {
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'));
} else {

View File

@ -26,7 +26,7 @@ class InvoiceApiController extends Controller
public function index()
{
$invoices = Invoice::scope()->with('invitations')->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) {
@ -36,7 +36,7 @@ class InvoiceApiController extends Controller
unset($invoice['invitations']);
}
$invoices = Utils::remapPublicIds($invoices->toArray());
$invoices = Utils::remapPublicIds($invoices);
$response = json_encode($invoices, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders(count($invoices));
@ -99,7 +99,6 @@ class InvoiceApiController extends Controller
$data = self::prepareData($data);
$data['client_id'] = $client->id;
$invoice = $this->invoiceRepo->save(false, $data, false);
$invoice->load('invoice_items');
$invitation = Invitation::createNew();
$invitation->invoice_id = $invoice->id;
@ -112,12 +111,8 @@ class InvoiceApiController extends Controller
}
// prepare the return data
$invoice = $invoice->toArray();
$invoice['link'] = $invitation->getLink();
unset($invoice['account']);
unset($invoice['client']);
$invoice = Utils::remapPublicIds($invoice);
$invoice['client_id'] = $client->public_id;
$invoice = Invoice::scope($invoice->public_id)->with('client', 'invoice_items', 'invitations')->first();
$invoice = Utils::remapPublicIds([$invoice]);
$response = json_encode($invoice, JSON_PRETTY_PRINT);
}

View File

@ -213,6 +213,10 @@ 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;
}
$contact = $invitation->contact;
$contact->setVisible([
'first_name',
@ -224,17 +228,23 @@ class InvoiceController extends BaseController
$paymentTypes = [];
if ($client->getGatewayToken()) {
$paymentTypes[] = [
'url' => URL::to("payment/{$invitation->invitation_key}/".PAYMENT_TYPE_TOKEN), 'label' => trans('texts.use_card_on_file')
'url' => URL::to("payment/{$invitation->invitation_key}/token"), 'label' => trans('texts.use_card_on_file')
];
}
foreach(Gateway::$paymentTypes as $type) {
if ($account->getGatewayByType($type)) {
$typeLink = strtolower(str_replace('PAYMENT_TYPE_', '', $type));
$paymentTypes[] = [
'url' => URL::to("/payment/{$invitation->invitation_key}/{$type}"), 'label' => trans('texts.'.strtolower($type))
'url' => URL::to("/payment/{$invitation->invitation_key}/{$typeLink}"), 'label' => trans('texts.'.strtolower($type))
];
}
}
$paymentURL = '';
if (count($paymentTypes)) {
$paymentURL = $paymentTypes[0]['url'];
}
$data = array(
'isConverted' => $invoice->quote_invoice_id ? true : false,
'showBreadcrumbs' => false,
@ -243,7 +253,8 @@ class InvoiceController extends BaseController
'invitation' => $invitation,
'invoiceLabels' => $account->getInvoiceLabels(),
'contact' => $contact,
'paymentTypes' => $paymentTypes
'paymentTypes' => $paymentTypes,
'paymentURL' => $paymentURL
);
return View::make('invoices.view', $data);
@ -318,7 +329,6 @@ class InvoiceController extends BaseController
$data = array(
'entityType' => $entityType,
'showBreadcrumbs' => $clone,
'account' => $invoice->account,
'invoice' => $invoice,
'data' => false,
'method' => $method,
@ -353,7 +363,6 @@ class InvoiceController extends BaseController
{
$client = null;
$invoiceNumber = Auth::user()->account->getNextInvoiceNumber();
$account = Account::with('country')->findOrFail(Auth::user()->account_id);
if ($clientPublicId) {
$client = Client::scope($clientPublicId)->firstOrFail();
@ -361,15 +370,13 @@ 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);
'client' => $client);
$data = array_merge($data, self::getViewModel());
return View::make('invoices.edit', $data);
@ -389,7 +396,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(),
@ -398,7 +405,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',
@ -410,7 +417,7 @@ class InvoiceController extends BaseController
),
'recurringHelp' => $recurringHelp,
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null,
];
}
@ -504,9 +511,13 @@ class InvoiceController extends BaseController
return $this->convertQuote($publicId);
} elseif ($action == 'email') {
if (Auth::user()->confirmed && !Auth::user()->isDemo()) {
$message = trans("texts.emailed_{$entityType}");
$this->mailer->sendInvoice($invoice);
Session::flash('message', $message);
$response = $this->mailer->sendInvoice($invoice);
if ($response === true) {
$message = trans("texts.emailed_{$entityType}");
Session::flash('message', $message);
} else {
Session::flash('error', $response);
}
} else {
$errorMessage = trans(Auth::user()->registered ? 'texts.confirmation_required' : 'texts.registration_required');
Session::flash('error', $errorMessage);
@ -635,7 +646,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

@ -1,8 +1,10 @@
<?php namespace App\Http\Controllers;
use Input;
use Utils;
use Response;
use App\Models\Payment;
use App\Models\Invoice;
use App\Ninja\Repositories\PaymentRepository;
class PaymentApiController extends Controller
@ -16,8 +18,11 @@ class PaymentApiController extends Controller
public function index()
{
$payments = Payment::scope()->orderBy('created_at', 'desc')->get();
$payments = Utils::remapPublicIds($payments->toArray());
$payments = Payment::scope()
->with('client', 'contact', 'invitation', 'user', 'invoice')
->orderBy('created_at', 'desc')
->get();
$payments = Utils::remapPublicIds($payments);
$response = json_encode($payments, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders(count($payments));
@ -25,15 +30,38 @@ class PaymentApiController extends Controller
return Response::make($response, 200, $headers);
}
/*
public function store()
{
$data = Input::all();
$invoice = $this->invoiceRepo->save(false, $data, false);
$response = json_encode($invoice, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();
return Response::make($response, 200, $headers);
}
*/
public function store()
{
$data = Input::all();
$error = false;
if (isset($data['invoice_id'])) {
$invoice = Invoice::scope($data['invoice_id'])->with('client')->first();
if ($invoice) {
$data['invoice'] = $invoice->public_id;
$data['client'] = $invoice->client->public_id;
} else {
$error = trans('validation.not_in', ['attribute' => 'invoice_id']);
}
} else {
$error = trans('validation.not_in', ['attribute' => 'invoice_id']);
}
if (!isset($data['transaction_reference'])) {
$data['transaction_reference'] = '';
}
if (!$error) {
$payment = $this->paymentRepo->save(false, $data);
$payment = Payment::scope($payment->public_id)->with('client', 'contact', 'user', 'invoice')->first();
$payment = Utils::remapPublicIds([$payment]);
}
$response = json_encode($error ?: $payment, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();
return Response::make($response, 200, $headers);
}
}

View File

@ -233,33 +233,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');
@ -292,7 +300,9 @@ class PaymentController extends BaseController
$account = $client->account;
$useToken = false;
if (!$paymentType) {
if ($paymentType) {
$paymentType = 'PAYMENT_TYPE_' . strtoupper($paymentType);
} else {
$paymentType = Session::get('payment_type', $account->account_gateways[0]->getPaymentType());
}
if ($paymentType == PAYMENT_TYPE_TOKEN) {
@ -328,6 +338,7 @@ class PaymentController extends BaseController
'currencyId' => $client->getCurrencyId(),
'account' => $client->account,
'hideLogo' => $account->isWhiteLabel(),
'showAddress' => $accountGateway->show_address,
];
return View::make('payments.payment', $data);
@ -458,10 +469,11 @@ class PaymentController extends BaseController
$this->contactMailer->sendLicensePaymentConfirmation($name, $license->email, $affiliate->price, $license->license_key, $license->product_id);
if (Session::has('return_url')) {
return Redirect::away(Session::get('return_url')."?license_key={$license->license_key}&product_id=".Session::get('product_id'));
} else {
return View::make('public.license', $data);
$data['redirectTo'] = Session::get('return_url')."?license_key={$license->license_key}&product_id=".Session::get('product_id');
$data['message'] = "Redirecting to " . Session::get('return_url');
}
return View::make('public.license', $data);
} catch (\Exception $e) {
$errorMessage = trans('texts.payment_error');
Session::flash('error', $errorMessage);
@ -495,19 +507,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);
@ -520,22 +543,15 @@ class PaymentController extends BaseController
}
}
$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) {
if ($onSite && $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);
@ -628,8 +644,9 @@ class PaymentController extends BaseController
$invoice = $invitation->invoice;
$accountGateway = $invoice->client->account->getGatewayByType(Session::get('payment_type'));
if ($invoice->account->account_key == NINJA_ACCOUNT_KEY) {
$account = Account::find($invoice->client->public_id);
if ($invoice->account->account_key == NINJA_ACCOUNT_KEY
&& $invoice->amount == PRO_PLAN_PRICE) {
$account = Account::with('users')->find($invoice->client->public_id);
if ($account->pro_plan_paid && $account->pro_plan_paid != '0000-00-00') {
$date = DateTime::createFromFormat('Y-m-d', $account->pro_plan_paid);
$account->pro_plan_paid = $date->modify('+1 year')->format('Y-m-d');
@ -637,6 +654,9 @@ class PaymentController extends BaseController
$account->pro_plan_paid = date_create()->format('Y-m-d');
}
$account->save();
$user = $account->users()->first();
$this->accountRepo->syncAccounts($user->id, $account->pro_plan_paid);
}
$payment = Payment::createNew($invitation);
@ -679,6 +699,14 @@ class PaymentController extends BaseController
$accountGateway = $invoice->client->account->getGatewayByType(Session::get('payment_type'));
$gateway = self::createGateway($accountGateway);
// Check for Dwolla payment error
if ($accountGateway->isGateway(GATEWAY_DWOLLA) && Input::get('error')) {
$errorMessage = trans('texts.payment_error')."\n\n".Input::get('error_description');
Session::flash('error', $errorMessage);
Utils::logError($errorMessage);
return Redirect::to('view/'.$invitation->invitation_key);
}
try {
if (method_exists($gateway, 'completePurchase')) {
$details = self::getPaymentDetails($invitation);
@ -731,14 +759,19 @@ class PaymentController extends BaseController
->withErrors($errors)
->withInput();
} else {
$this->paymentRepo->save($publicId, Input::all());
$payment = $this->paymentRepo->save($publicId, Input::all());
if ($publicId) {
Session::flash('message', trans('texts.updated_payment'));
return Redirect::to('payments/');
} else {
Session::flash('message', trans('texts.created_payment'));
if (Input::get('email_receipt')) {
$this->contactMailer->sendPaymentConfirmation($payment);
Session::flash('message', trans('texts.created_payment_emailed_client'));
} else {
Session::flash('message', trans('texts.created_payment'));
}
return Redirect::to('clients/'.Input::get('client'));
}

View File

@ -16,8 +16,8 @@ class QuoteApiController extends Controller
public function index()
{
$invoices = Invoice::scope()->where('invoices.is_quote', '=', true)->orderBy('created_at', 'desc')->get();
$invoices = Utils::remapPublicIds($invoices->toArray());
$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);
$headers = Utils::getApiHeaders(count($invoices));

View File

@ -156,7 +156,7 @@ class QuoteController extends BaseController
'sizes' => Cache::get('sizes'),
'paymentTerms' => Cache::get('paymentTerms'),
'industries' => Cache::get('industries'),
'invoiceDesigns' => InvoiceDesign::availableDesigns(),
'invoiceDesigns' => InvoiceDesign::getDesigns(),
'invoiceLabels' => Auth::user()->account->getInvoiceLabels()
];
}

View File

@ -1,6 +1,7 @@
<?php namespace App\Http\Controllers;
use Auth;
use Config;
use Input;
use Utils;
use DB;
@ -149,53 +150,90 @@ 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);
$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'),
@ -243,6 +281,7 @@ class ReportController extends BaseController
'reportType' => $reportType,
'enableChart' => $enableChart,
'enableReport' => $enableReport,
'title' => trans('texts.charts_and_reports'),
];
return View::make('reports.chart_builder', $params);

View File

@ -1,5 +1,6 @@
<?php namespace App\Http\Controllers;
use Auth;
use View;
use URL;
use Utils;
@ -8,34 +9,24 @@ use Datatable;
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;
}
/**
@ -45,6 +36,8 @@ class TaskController extends BaseController
*/
public function index()
{
self::checkTimezone();
return View::make('list', array(
'entityType' => ENTITY_TASK,
'title' => trans('texts.tasks'),
@ -64,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->duration == -1 ? 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) {
@ -81,7 +74,7 @@ class TaskController extends BaseController
if ($model->invoice_number) {
$str .= '<li>' . link_to("/invoices/{$model->invoice_public_id}/edit", trans('texts.view_invoice')) . '</li>';
} elseif ($model->duration == -1) {
} elseif ($model->is_running) {
$str .= '<li><a href="javascript:stopTask('.$model->public_id.')">'.trans('texts.stop_task').'</a></li>';
} elseif (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
$str .= '<li><a href="javascript:invoiceTask('.$model->public_id.')">'.trans('texts.invoice_task').'</a></li>';
@ -107,7 +100,7 @@ class TaskController extends BaseController
if ($model->invoice_number) {
$class = 'success';
$label = trans('texts.invoiced');
} elseif ($model->duration == -1) {
} elseif ($model->is_running) {
$class = 'primary';
$label = trans('texts.running');
} else {
@ -135,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());
@ -156,7 +152,31 @@ class TaskController extends BaseController
*/
public function edit($publicId)
{
$task = Task::scope($publicId)->with('client')->firstOrFail();
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.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')];
$actions[] = ['url' => 'javascript:onDeleteClick()', 'label' => trans('texts.delete_task')];
} else {
$actions[] = ['url' => 'javascript:submitAction("restore")', 'label' => trans('texts.restore_task')];
}
$data = [
'task' => $task,
@ -164,7 +184,9 @@ class TaskController extends BaseController
'method' => 'PUT',
'url' => 'tasks/'.$publicId,
'title' => trans('texts.edit_task'),
'duration' => time() - strtotime($task->start_time),
'duration' => $task->is_running ? $task->getCurrentDuration() : $task->getDuration(),
'actions' => $actions,
'minuteOffset' => Utils::getTiemstampOffset(),
];
$data = array_merge($data, self::getViewModel());
@ -192,15 +214,16 @@ class TaskController extends BaseController
private function save($publicId = null)
{
$task = $this->taskRepo->save($publicId, Input::all());
$action = Input::get('action');
if (in_array($action, ['archive', 'delete', 'invoice', 'restore', 'add_to_invoice'])) {
return self::bulk();
}
$task = $this->taskRepo->save($publicId, Input::all());
Session::flash('message', trans($publicId ? 'texts.updated_task' : 'texts.created_task'));
if (Input::get('action') == 'stop') {
return Redirect::to("tasks");
} else {
return Redirect::to("tasks/{$task->public_id}/edit");
}
return Redirect::to("tasks/{$task->public_id}/edit");
}
public function bulk()
@ -212,8 +235,7 @@ 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 = [];
@ -228,7 +250,7 @@ class TaskController extends BaseController
}
}
if ($task->duration == -1) {
if ($task->is_running) {
Session::flash('error', trans('texts.task_error_running'));
return Redirect::to('tasks');
} else if ($task->invoice_id) {
@ -239,12 +261,17 @@ class TaskController extends BaseController
$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);
@ -258,4 +285,12 @@ class TaskController extends BaseController
}
}
}
private function checkTimezone()
{
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]));
}
}
}

View File

@ -7,6 +7,7 @@ use DB;
use Event;
use Input;
use View;
use Request;
use Redirect;
use Session;
use URL;
@ -300,22 +301,23 @@ class UserController extends BaseController
* Log the user out of the application.
*
*/
/*
public function logout()
{
if (Auth::check()) {
if (!Auth::user()->registered) {
$account = Auth::user()->account;
$this->accountRepo->unlinkAccount($account);
$account->forceDelete();
}
}
Session::forget('news_feed_id');
Session::forget('news_feed_message');
Auth::logout();
Session::flush();
return Redirect::to('/')->with('clearGuestKey', true);
}
*/
public function changePassword()
{
@ -342,4 +344,36 @@ class UserController extends BaseController
return RESULT_SUCCESS;
}
public function switchAccount($newUserId)
{
$oldUserId = Auth::user()->id;
$referer = Request::header('referer');
$account = $this->accountRepo->findUserAccounts($newUserId, $oldUserId);
if ($account) {
if ($account->hasUserId($newUserId) && $account->hasUserId($oldUserId)) {
Auth::loginUsingId($newUserId);
Auth::user()->account->loadLocalizationSettings();
// regenerate token to prevent open pages
// from saving under the wrong account
Session::put('_token', str_random(40));
}
}
return Redirect::to($referer);
}
public function unlinkAccount($userAccountId, $userId)
{
$this->accountRepo->unlinkUser($userAccountId, $userId);
$referer = Request::header('referer');
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
Session::put(SESSION_USER_ACCOUNTS, $users);
Session::flash('message', trans('texts.unlinked_account'));
return Redirect::to($referer);
}
}

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')) {
@ -157,6 +158,14 @@ class StartupCheck
}
}
return $next($request);
if (preg_match('/(?i)msie [2-8]/', $_SERVER['HTTP_USER_AGENT'])) {
Session::flash('error', trans('texts.old_browser'));
}
// for security prevent displaying within an iframe
$response = $next($request);
$response->headers->set('X-Frame-Options', 'DENY');
return $response;
}
}

View File

@ -67,7 +67,7 @@ get('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@getRegiste
post('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@postRegister'));
get('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@getLoginWrapper'));
post('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@postLoginWrapper'));
get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogout'));
get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogoutWrapper'));
get('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail'));
post('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail'));
get('/password/reset/{token}', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset'));
@ -93,6 +93,8 @@ Route::group(['middleware' => 'auth'], function() {
Route::get('send_confirmation/{user_id}', 'UserController@sendConfirmation');
Route::get('restore_user/{user_id}', 'UserController@restoreUser');
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('api/tokens', array('as'=>'api.tokens', 'uses'=>'TokenController@getDatatable'));
Route::resource('tokens', 'TokenController');
@ -204,6 +206,9 @@ Route::get('/testimonials', function() {
Route::get('/compare-online-invoicing{sites?}', function() {
return Redirect::to(NINJA_WEB_URL, 301);
});
Route::get('/forgot_password', function() {
return Redirect::to(NINJA_APP_URL.'/forgot', 301);
});
define('CONTACT_EMAIL', Config::get('mail.from.address'));
@ -241,6 +246,8 @@ 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('ACTIVITY_TYPE_CREATE_CLIENT', 1);
define('ACTIVITY_TYPE_ARCHIVE_CLIENT', 2);
@ -294,6 +301,7 @@ define('INVOICE_STATUS_PARTIAL', 4);
define('INVOICE_STATUS_PAID', 5);
define('PAYMENT_TYPE_CREDIT', 1);
define('CUSTOM_DESIGN', 11);
define('FREQUENCY_WEEKLY', 1);
define('FREQUENCY_TWO_WEEKS', 2);
@ -310,6 +318,7 @@ 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');
@ -348,18 +357,23 @@ 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.2.0');
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('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);
@ -452,6 +466,7 @@ Event::listen('illuminate.query', function($query, $bindings, $time, $name)
});
*/
/*
if (Auth::check() && Auth::user()->id === 1)
{

View File

@ -51,12 +51,17 @@ class Utils
public static function isNinjaProd()
{
return isset($_ENV['NINJA_PROD']) && $_ENV['NINJA_PROD'];
return isset($_ENV['NINJA_PROD']) && $_ENV['NINJA_PROD'] == 'true';
}
public static function isNinjaDev()
{
return isset($_ENV['NINJA_DEV']) && $_ENV['NINJA_DEV'];
return isset($_ENV['NINJA_DEV']) && $_ENV['NINJA_DEV'] == 'true';
}
public static function allowNewAccounts()
{
return Utils::isNinja() || (isset($_ENV['ALLOW_NEW_ACCOUNTS']) && $_ENV['ALLOW_NEW_ACCOUNTS'] == 'true');
}
public static function isPro()
@ -246,6 +251,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);
@ -310,6 +319,16 @@ 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) {
@ -563,14 +582,15 @@ class Utils
{
$curl = curl_init();
$jsonEncodedData = json_encode($data->toJson());
$jsonEncodedData = json_encode($data->toPublicArray());
$opts = [
CURLOPT_URL => $subscription->target_url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $jsonEncodedData,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Content-Length: '.strlen($jsonEncodedData)],
CURLOPT_URL => $subscription->target_url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $jsonEncodedData,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Content-Length: '.strlen($jsonEncodedData)],
];
curl_setopt_array($curl, $opts);
@ -586,27 +606,39 @@ class Utils
}
public static function remapPublicIds(array $data)
public static function remapPublicIds($items)
{
$return = [];
foreach ($data as $key => $val) {
if ($key === 'public_id') {
$key = 'id';
} elseif (strpos($key, '_id')) {
continue;
}
if (is_array($val)) {
$val = Utils::remapPublicIds($val);
}
$return[$key] = $val;
foreach ($items as $item) {
$return[] = $item->toPublicArray();
}
return $return;
}
public static function hideIds($data)
{
$publicId = null;
foreach ($data as $key => $val) {
if (is_array($val)) {
$data[$key] = Utils::hideIds($val);
} else if ($key == 'id' || strpos($key, '_id')) {
if ($key == 'public_id') {
$publicId = $val;
}
unset($data[$key]);
}
}
if ($publicId) {
$data['id'] = $publicId;
}
return $data;
}
public static function getApiHeaders($count = 0)
{
return [
@ -659,4 +691,29 @@ 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;
}
}

View File

@ -3,6 +3,7 @@
use Utils;
use Auth;
use Carbon;
use Session;
use App\Events\UserLoggedIn;
use App\Ninja\Repositories\AccountRepository;
use Illuminate\Queue\InteractsWithQueue;
@ -32,13 +33,16 @@ class HandleUserLoggedIn {
{
$account = Auth::user()->account;
if (!Utils::isNinja() && empty($account->last_login)) {
if (!Utils::isNinja() && Auth::user()->id == 1 && empty($account->last_login)) {
$this->accountRepo->registerUser(Auth::user());
}
$account->last_login = Carbon::now()->toDateTimeString();
$account->save();
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
Session::put(SESSION_USER_ACCOUNTS, $users);
$account->loadLocalizationSettings();
}

View File

@ -1,9 +1,9 @@
<?php namespace App\Listeners;
use Auth;
use Session;
use App\Events\UserSettingsChanged;
use App\Ninja\Repositories\AccountRepository;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldBeQueued;
@ -14,9 +14,9 @@ class HandleUserSettingsChanged {
*
* @return void
*/
public function __construct()
public function __construct(AccountRepository $accountRepo)
{
//
$this->accountRepo = $accountRepo;
}
/**
@ -29,6 +29,9 @@ class HandleUserSettingsChanged {
{
$account = Auth::user()->account;
$account->loadLocalizationSettings();
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
Session::put(SESSION_USER_ACCOUNTS, $users);
}
}

View File

@ -12,6 +12,10 @@ class Account extends Eloquent
use SoftDeletes;
protected $dates = ['deleted_at'];
protected $casts = [
'utf8_invoice' => 'boolean',
];
public function users()
{
return $this->hasMany('App\Models\User');
@ -133,9 +137,17 @@ class Account extends Eloquent
return false;
}
/*
public function hasLogo()
{
file_exists($this->getLogoPath());
}
*/
public function getLogoPath()
{
return 'logo/'.$this->account_key.'.jpg';
$fileName = 'logo/' . $this->account_key;
return file_exists($fileName.'.png') ? $fileName.'.png' : $fileName.'.jpg';
}
public function getLogoWidth()
@ -179,11 +191,14 @@ class Account extends Eloquent
{
// check if the user modified the invoice number
if (!$isRecurring && $invoiceNumber != $this->getNextInvoiceNumber($isQuote)) {
$number = intval(preg_replace('/[^0-9]/', '', $invoiceNumber));
// remove the prefix
$prefix = $isQuote ? $this->quote_number_prefix : $this->invoice_number_prefix;
$invoiceNumber = preg_replace('/^'.$prefix.'/', '', $invoiceNumber);
$invoiceNumber = intval(preg_replace('/[^0-9]/', '', $invoiceNumber));
if ($isQuote && !$this->share_counter) {
$this->quote_number_counter = $number + 1;
$this->quote_number_counter = $invoiceNumber + 1;
} else {
$this->invoice_number_counter = $number + 1;
$this->invoice_number_counter = $invoiceNumber + 1;
}
// otherwise, just increment the counter
} else {
@ -250,6 +265,12 @@ class Account extends Eloquent
'date',
'rate',
'hours',
'balance',
'from',
'to',
'invoice_to',
'details',
'invoice_no',
];
foreach ($fields as $field) {
@ -401,7 +422,7 @@ class Account extends Eloquent
Account::updating(function ($account) {
// Lithuanian requires UTF8 support
if (!Utils::isPro()) {
$account->utf8_invoices = ($account->language_id == 13) ? 1 : 0;
if (!Utils::isPro() && $account->language_id == 13) {
$account->utf8_invoices = true;
}
});

View File

@ -34,5 +34,9 @@ class AccountGateway extends EntityModel
public function isPaymentType($type) {
return $this->getPaymentType() == $type;
}
public function isGateway($gatewayId) {
return $this->gateway_id == $gatewayId;
}
}

View File

@ -74,7 +74,7 @@ class Client extends EntityModel
public function getName()
{
return $this->getDisplayName();
return $this->name;
}
public function getDisplayName()
@ -84,6 +84,7 @@ class Client extends EntityModel
}
$this->load('contacts');
$contact = $this->contacts()->first();
return $contact->getDisplayName();

View File

@ -38,6 +38,11 @@ class Contact extends EntityModel
}
*/
public function getName()
{
return $this->getDisplayName();
}
public function getDisplayName()
{
if ($this->getFullName()) {

View File

@ -7,4 +7,9 @@ class Country extends Eloquent
public $timestamps = false;
protected $visible = ['id', 'name'];
public function getName()
{
return $this->name;
}
}

View File

@ -5,4 +5,9 @@ use Eloquent;
class Currency extends Eloquent
{
public $timestamps = false;
public function getName()
{
return $this->name;
}
}

View File

@ -77,4 +77,34 @@ class EntityModel extends Eloquent
return $query;
}
public function getName()
{
return $this->public_id;
}
// Remap ids to public_ids and show name
public function toPublicArray()
{
$data = $this->toArray();
foreach ($this->attributes as $key => $val) {
if (strpos($key, '_id')) {
list($field, $id) = explode('_', $key);
if ($field == 'account') {
// do nothing
} else {
$entity = @$this->$field;
if ($entity) {
$data["{$field}_name"] = $entity->getName();
}
}
}
}
$data = Utils::hideIds($data);
return $data;
}
}

View File

@ -39,6 +39,14 @@ class Gateway extends Eloquent
return '/images/gateways/logo_'.$this->provider.'.png';
}
public static function getPaymentTypeLinks() {
$data = [];
foreach (self::$paymentTypes as $type) {
$data[] = strtolower(str_replace('PAYMENT_TYPE_', '', $type));
}
return $data;
}
public function getHelp()
{
$link = '';

View File

@ -5,4 +5,9 @@ use Eloquent;
class Industry extends Eloquent
{
public $timestamps = false;
public function getName()
{
return $this->name;
}
}

View File

@ -29,7 +29,10 @@ class Invitation extends EntityModel
public function getLink()
{
$this->load('account');
if (!$this->account) {
$this->load('account');
}
$url = SITE_URL;
if ($this->account->subdomain) {
@ -41,4 +44,9 @@ class Invitation extends EntityModel
return "{$url}/view/{$this->invitation_key}";
}
public function getName()
{
return $this->invitation_key;
}
}

View File

@ -43,6 +43,11 @@ class Invoice extends EntityModel
return $this->belongsTo('App\Models\InvoiceDesign');
}
public function recurring_invoice()
{
return $this->belongsTo('App\Models\Invoice');
}
public function invitations()
{
return $this->hasMany('App\Models\Invitation')->orderBy('invitations.contact_id');
@ -247,8 +252,11 @@ class Invoice extends EntityModel
}
}
Invoice::created(function ($invoice) {
Invoice::creating(function ($invoice) {
$invoice->account->incrementCounter($invoice->invoice_number, $invoice->is_quote, $invoice->recurring_invoice_id);
});
Invoice::created(function ($invoice) {
Activity::createInvoice($invoice);
});

View File

@ -2,19 +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($forceUtf8 = false)
{
$designs = $query->where('id', '<=', \Auth::user()->maxInvoiceDesignId())->orderBy('id')->get();
$account = Auth::user()->account;
$designs = Cache::get('invoiceDesigns');
$utf8 = $forceUtf8 || $account->utf8_invoices;
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);
if ($design->id > Auth::user()->maxInvoiceDesignId()) {
$designs->pull($design->id);
}
if ($utf8) {
$design->javascript = $design->pdfmake;
}
$design->pdfmake = null;
if ($design->id == CUSTOM_DESIGN) {
if ($utf8 && $account->custom_design) {
$design->javascript = $account->custom_design;
} else {
$designs->pop();
}
}
}

View File

@ -22,6 +22,11 @@ class Payment extends EntityModel
return $this->belongsTo('App\Models\Client')->withTrashed();
}
public function user()
{
return $this->belongsTo('App\Models\User')->withTrashed();
}
public function account()
{
return $this->belongsTo('App\Models\Account');

View File

@ -5,4 +5,9 @@ use Eloquent;
class Size extends Eloquent
{
public $timestamps = false;
public function getName()
{
return $this->name;
}
}

View File

@ -1,7 +1,7 @@
<?php namespace App\Models;
use DB;
use Utils;
use Illuminate\Database\Eloquent\SoftDeletes;
class Task extends EntityModel
@ -13,10 +13,75 @@ class Task extends EntityModel
return $this->belongsTo('App\Models\Account');
}
public function invoice()
{
return $this->belongsTo('App\Models\Invoice');
}
public function client()
{
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

@ -48,6 +48,11 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return $this->belongsTo('App\Models\Theme');
}
public function getName()
{
return $this->getDisplayName();
}
public function getPersonType()
{
return PERSON_USER;
@ -95,7 +100,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()
@ -128,6 +133,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return Session::get(SESSION_COUNTER, 0);
}
/*
public function getPopOverText()
{
if (!Utils::isNinja() || !Auth::check() || Session::has('error')) {
@ -146,6 +152,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return false;
}
*/
public function afterSave($success = true, $forced = false)
{
@ -176,4 +183,33 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return 'remember_token';
}
public function clearSession()
{
$keys = [
RECENTLY_VIEWED,
SESSION_USER_ACCOUNTS,
SESSION_TIMEZONE,
SESSION_DATE_FORMAT,
SESSION_DATE_PICKER_FORMAT,
SESSION_DATETIME_FORMAT,
SESSION_CURRENCY,
SESSION_LOCALE,
];
foreach ($keys as $key) {
Session::forget($key);
}
}
public static function updateUser($user)
{
if ($user->password != !$user->getOriginal('password')) {
$user->failed_logins = 0;
}
}
}
User::updating(function ($user) {
User::updateUser($user);
});

View File

@ -0,0 +1,52 @@
<?php namespace App\Models;
use Eloquent;
class UserAccount extends Eloquent
{
public $timestamps = false;
public function hasUserId($userId)
{
if (!$userId) {
return false;
}
for ($i=1; $i<=5; $i++) {
$field = "user_id{$i}";
if ($this->$field && $this->$field == $userId) {
return true;
}
}
return false;
}
public function setUserId($userId)
{
if (self::hasUserId($userId)) {
return;
}
for ($i=1; $i<=5; $i++) {
$field = "user_id{$i}";
if (!$this->$field) {
$this->$field = $userId;
break;
}
}
}
public function removeUserId($userId)
{
if (!$userId || !self::hasUserId($userId)) {
return;
}
for ($i=1; $i<=5; $i++) {
$field = "user_id{$i}";
if ($this->$field && $this->$field == $userId) {
$this->$field = null;
}
}
}
}

View File

@ -7,6 +7,7 @@ use URL;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Activity;
use App\Models\Gateway;
use App\Events\InvoiceSent;
class ContactMailer extends Mailer
@ -22,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;
@ -39,28 +42,27 @@ 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
foreach([PAYMENT_TYPE_CREDIT_CARD, PAYMENT_TYPE_PAYPAL, PAYMENT_TYPE_BITCOIN] as $type) {
if ($invoice->account->getGatewayByType($type)) {
// Changes "PAYMENT_TYPE_CREDIT_CARD" to "$credit_card_link"
$gateway_slug = '$'.strtolower(str_replace('PAYMENT_TYPE_', '', $type)).'_link';
$variables[$gateway_slug] = URL::to("/payment/{$invitation->invitation_key}/{$type}");
}
foreach (Gateway::getPaymentTypeLinks() as $type) {
$variables["\${$type}_link"] = URL::to("/payment/{$invitation->invitation_key}/{$type}");
}
$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;
$fromEmail = $invitation->user->email;
$this->sendTo($invitation->contact->email, $fromEmail, $accountName, $subject, $view, $data);
$response = $this->sendTo($invitation->contact->email, $fromEmail, $accountName, $subject, $view, $data);
if ($response !== true) {
return $response;
}
Activity::emailInvoice($invitation);
}
@ -71,6 +73,8 @@ class ContactMailer extends Mailer
}
Event::fire(new InvoiceSent($invoice));
return $response;
}
public function sendPaymentConfirmation(Payment $payment)
@ -90,8 +94,17 @@ class ContactMailer extends Mailer
$data = ['body' => str_replace(array_keys($variables), array_values($variables), $emailTemplate)];
$user = $payment->invitation->user;
$this->sendTo($payment->contact->email, $user->email, $accountName, $subject, $view, $data);
if ($payment->invitation) {
$user = $payment->invitation->user;
$contact = $payment->contact;
} else {
$user = $payment->user;
$contact = $payment->client->contacts[0];
}
if ($user->email && $contact->email) {
$this->sendTo($contact->email, $user->email, $accountName, $subject, $view, $data);
}
}
public function sendLicensePaymentConfirmation($name, $email, $amount, $license, $productId)
@ -116,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

@ -1,5 +1,6 @@
<?php namespace App\Ninja\Mailers;
use Exception;
use Mail;
use Utils;
use App\Models\Invoice;
@ -13,26 +14,38 @@ class Mailer
'emails.'.$view.'_text',
];
Mail::send($views, $data, function ($message) use ($toEmail, $fromEmail, $fromName, $subject, $data) {
$replyEmail = $fromEmail;
try {
Mail::send($views, $data, function ($message) use ($toEmail, $fromEmail, $fromName, $subject, $data) {
// http://stackoverflow.com/questions/2421234/gmail-appearing-to-ignore-reply-to
if (Utils::isNinja() && $toEmail != CONTACT_EMAIL) {
$fromEmail = NINJA_FROM_EMAIL;
}
$replyEmail = $fromEmail;
$fromEmail = CONTACT_EMAIL;
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(
$invoice->getPDFPath(),
array('as' => $invoice->getFileName(), 'mime' => 'application/pdf')
);
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(
$invoice->getPDFPath(),
array('as' => $invoice->getFileName(), 'mime' => 'application/pdf')
);
}
}
}
//$message->setEncoder(\Swift_Encoding::get8BitEncoding());
$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 $exception) {
if (method_exists($exception, 'getResponse')) {
$response = $exception->getResponse()->getBody()->getContents();
$response = json_decode($response);
return nl2br($response->Message);
} else {
return $exception->getMessage();
}
}
}
}

View File

@ -4,6 +4,8 @@ use Auth;
use Request;
use Session;
use Utils;
use DB;
use stdClass;
use App\Models\AccountGateway;
use App\Models\Invitation;
@ -14,6 +16,7 @@ use App\Models\Language;
use App\Models\Contact;
use App\Models\Account;
use App\Models\User;
use App\Models\UserAccount;
class AccountRepository
{
@ -244,4 +247,138 @@ class AccountRepository
curl_exec($ch);
curl_close($ch);
}
public function findUserAccounts($userId1, $userId2 = false)
{
$query = UserAccount::where('user_id1', '=', $userId1)
->orWhere('user_id2', '=', $userId1)
->orWhere('user_id3', '=', $userId1)
->orWhere('user_id4', '=', $userId1)
->orWhere('user_id5', '=', $userId1);
if ($userId2) {
$query->orWhere('user_id1', '=', $userId2)
->orWhere('user_id2', '=', $userId2)
->orWhere('user_id3', '=', $userId2)
->orWhere('user_id4', '=', $userId2)
->orWhere('user_id5', '=', $userId2);
}
return $query->first(['id', 'user_id1', 'user_id2', 'user_id3', 'user_id4', 'user_id5']);
}
public function prepareUsersData($record) {
if (!$record) {
return false;
}
$userIds = [];
for ($i=1; $i<=5; $i++) {
$field = "user_id$i";
if ($record->$field) {
$userIds[] = $record->$field;
}
}
$users = User::with('account')
->whereIn('id', $userIds)
->get();
$data = [];
foreach ($users as $user) {
$item = new stdClass();
$item->id = $record->id;
$item->user_id = $user->id;
$item->user_name = $user->getDisplayName();
$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;
$data[] = $item;
}
return $data;
}
public function loadAccounts($userId) {
$record = self::findUserAccounts($userId);
return self::prepareUsersData($record);
}
public function syncAccounts($userId, $proPlanPaid) {
$users = self::loadAccounts($userId);
self::syncUserAccounts($users, $proPlanPaid);
}
public function syncUserAccounts($users, $proPlanPaid = false) {
if (!$proPlanPaid) {
foreach ($users as $user) {
if ($user->pro_plan_paid && $user->pro_plan_paid != '0000-00-00') {
$proPlanPaid = $user->pro_plan_paid;
break;
}
}
}
if (!$proPlanPaid) {
return;
}
$accountIds = [];
foreach ($users as $user) {
if ($user->pro_plan_paid != $proPlanPaid) {
$accountIds[] = $user->account_id;
}
}
if (count($accountIds)) {
DB::table('accounts')
->whereIn('id', $accountIds)
->update(['pro_plan_paid' => $proPlanPaid]);
}
}
public function associateAccounts($userId1, $userId2) {
$record = self::findUserAccounts($userId1, $userId2);
if ($record) {
foreach ([$userId1, $userId2] as $userId) {
if (!$record->hasUserId($userId)) {
$record->setUserId($userId);
}
}
} else {
$record = new UserAccount();
$record->user_id1 = $userId1;
$record->user_id2 = $userId2;
}
$record->save();
$users = self::prepareUsersData($record);
self::syncUserAccounts($users);
return $users;
}
public function unlinkAccount($account) {
foreach ($account->users as $user) {
if ($userAccount = self::findUserAccounts($user->id)) {
$userAccount->removeUserId($user->id);
$userAccount->save();
}
}
}
public function unlinkUser($userAccountId, $userId) {
$userAccount = UserAccount::whereId($userAccountId)->first();
if ($userAccount->hasUserId($userId)) {
$userAccount->removeUserId($userId);
$userAccount->save();
}
}
}

View File

@ -536,4 +536,16 @@ class InvoiceRepository
return count($invoices);
}
public function findOpenInvoices($clientId)
{
return Invoice::scope()
->whereClientId($clientId)
->whereIsQuote(false)
->whereIsRecurring(false)
->whereHasTasks(true)
->where('balance', '>', 0)
->select(['public_id', 'invoice_number'])
->get();
}
}

View File

@ -108,7 +108,14 @@ class PaymentRepository
$payment->payment_type_id = $paymentTypeId;
}
$payment->payment_date = Utils::toSqlDate($input['payment_date']);
if (isset($input['payment_date_sql'])) {
$payment->payment_date = $input['payment_date_sql'];
} elseif (isset($input['payment_date'])) {
$payment->payment_date = Utils::toSqlDate($input['payment_date']);
} else {
$payment->payment_date = date('Y-m-d');
}
$payment->transaction_reference = trim($input['transaction_reference']);
if (!$publicId) {

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');
->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);
@ -60,18 +60,20 @@ class TaskRepository
$task->description = trim($data['description']);
}
//$timeLog = $task->time_log ? json_decode($task->time_log, true) : [];
$timeLog = isset($data['time_log']) ? json_decode($data['time_log']) : [];
if ($data['action'] == 'start') {
$task->start_time = Carbon::now()->toDateTimeString();
$task->duration = -1;
} else if ($data['action'] == 'stop' && $task->duration == -1) {
$task->duration = strtotime('now') - strtotime($task->start_time);
} else if ($data['action'] == 'save' && $task->duration != -1) {
$task->start_time = $data['start_time'];
$task->duration = $data['duration'];
$task->is_running = true;
$timeLog[] = [strtotime('now'), false];
} else if ($data['action'] == 'resume') {
$task->is_running = true;
$timeLog[] = [strtotime('now'), false];
} else if ($data['action'] == 'stop' && $task->is_running) {
$timeLog[count($timeLog)-1][1] = time();
$task->is_running = false;
}
$task->duration = max($task->duration, -1);
$task->time_log = json_encode($timeLog);
$task->save();
return $task;

View File

@ -44,6 +44,10 @@ class AppServiceProvider extends ServiceProvider {
$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>
<li><a href="'.URL::to('credits/create').'">'.trans("texts.new_credit").'</a></li>';
}
$str .= '</ul>

View File

@ -2,25 +2,26 @@
"name": "hillelcoren/invoice-ninja",
"version": "0.9.0",
"dependencies": {
"jquery": "~1.11",
"bootstrap": "~3.*",
"jquery-ui": "~1.*",
"jquery": "1.11.3",
"bootstrap": "3.3.1",
"jquery-ui": "1.11.2",
"datatables": "1.10.4",
"datatables-bootstrap3": "*",
"knockout.js": "~3.*",
"knockout-mapping": "*",
"knockout-sortable": "*",
"knockout.js": "3.1.0",
"knockout-mapping": "2.4.1",
"knockout-sortable": "0.9.3",
"font-awesome": "~4.*",
"underscore": "~1.*",
"jspdf": "*",
"bootstrap-datepicker": "~1.*",
"typeahead.js": "~0.9.3",
"accounting": "~0.*",
"spectrum": "~1.3.4",
"d3": "~3.4.11",
"underscore": "1.7.0",
"jspdf": "1.0.272",
"bootstrap-datepicker": "1.4.0",
"typeahead.js": "0.9.3",
"accounting": "0.3.2",
"spectrum": "1.3.4",
"d3": "3.4.11",
"handsontable": "*",
"pdfmake": "*",
"moment": "*"
"moment": "*",
"jsoneditor": "*"
},
"resolutions": {
"jquery": "~1.11"

View File

@ -21,7 +21,7 @@ class AddTasks extends Migration {
$table->timestamps();
$table->softDeletes();
$table->timestamp('start_time');
$table->timestamp('start_time')->nullable();
$table->integer('duration')->nullable();
$table->string('description')->nullable();
$table->boolean('is_deleted')->default(false);

View File

@ -0,0 +1,58 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class EnableResumingTasks extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('tasks', function($table)
{
$table->boolean('is_running')->default(false);
$table->integer('break_duration')->nullable();
$table->timestamp('resume_time')->nullable();
$table->text('time_log')->nullable();
});
$tasks = DB::table('tasks')
->where('duration', '=', -1)
->select('id', 'duration', 'start_time')
->get();
foreach ($tasks as $task) {
$data = [
'is_running' => true,
'duration' => 0,
];
DB::table('tasks')
->where('id', $task->id)
->update($data);
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('tasks', function($table)
{
$table->dropColumn('is_running');
$table->dropColumn('resume_time');
$table->dropColumn('break_duration');
$table->dropColumn('time_log');
});
}
}

View File

@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class MultiCompanySupport extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_accounts', function($table)
{
$table->increments('id');
$table->unsignedInteger('user_id1')->nullable();
$table->unsignedInteger('user_id2')->nullable();
$table->unsignedInteger('user_id3')->nullable();
$table->unsignedInteger('user_id4')->nullable();
$table->unsignedInteger('user_id5')->nullable();
$table->foreign('user_id1')->references('id')->on('users');
$table->foreign('user_id2')->references('id')->on('users');
$table->foreign('user_id3')->references('id')->on('users');
$table->foreign('user_id4')->references('id')->on('users');
$table->foreign('user_id5')->references('id')->on('users');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_accounts');
}
}

View File

@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class SupportLockingAccount extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function($table)
{
$table->smallInteger('failed_logins')->nullable();
});
Schema::table('account_gateways', function($table)
{
$table->boolean('show_address')->default(true)->nullable();
$table->boolean('update_address')->default(true)->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function($table)
{
$table->dropColumn('failed_logins');
});
Schema::table('account_gateways', function($table)
{
$table->dropColumn('show_address');
$table->dropColumn('update_address');
});
}
}

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

@ -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() {
@ -131,4 +133,33 @@ class PaymentLibrariesSeeder extends Seeder
}
}
private function createInvoiceDesigns() {
$designs = [
'Clean',
'Bold',
'Modern',
'Plain',
'Business',
'Creative',
'Elegant',
'Hipster',
'Playful',
'Photo',
];
foreach ($designs as $design) {
$fileName = storage_path() . '/templates/' . strtolower($design) . '.js';
$pdfmake = file_get_contents($fileName);
if ($pdfmake) {
$record = InvoiceDesign::whereName($design)->first();
if (!$record) {
$record = new InvoiceDesign;
$record->name = $design;
}
$record->pdfmake = $pdfmake;
$record->save();
}
}
}
}

154
public/css/built.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

94
public/css/style.css vendored
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 {
@ -884,3 +886,95 @@ a .glyphicon,
button .glyphicon {
padding-left: 8px;
}
.pro-plan-modal {
background-color: #4b4b4b;
padding-bottom: 40px;
padding-right: 25px;
opacity:0.95 !important;
}
.pro-plan-modal .left-side {
margin-top: 50px;
}
.pro-plan-modal h2 {
color: #36c157;
font-size: 71px;
font-weight: 800;
}
.pro-plan-modal img.price {
height: 90px;
}
.pro-plan-modal a.button {
font-family: 'roboto_slabregular', Georgia, Times, serif;
background: #f38c4f;
background: -moz-linear-gradient(top, #f38c4f 0%, #db7134 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f38c4f), color-stop(100%,#db7134));
background: -webkit-linear-gradient(top, #f38c4f 0%,#db7134 100%);
background: -o-linear-gradient(top, #f38c4f 0%,#db7134 100%);
background: -ms-linear-gradient(top, #f38c4f 0%,#db7134 100%);
background: linear-gradient(to bottom, #f38c4f 0%,#db7134 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f38c4f', endColorstr='#db7134',GradientType=0 );
text-shadow: 1px 1px 1px rgba(0, 0, 0, .25);
width: 68%;
margin-top: 20px;
font-size: 28px;
color: #fff;
border-radius: 10px;
padding: 20px 0;
display: inline-block;
text-decoration: none;
}
.pro-plan-modal a.button:hover {
background: #db7134; /* Old browsers */
background: -moz-linear-gradient(top, #db7134 0%, #f38c4f 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#db7134), color-stop(100%,#f38c4f)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #db7134 0%,#f38c4f 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #db7134 0%,#f38c4f 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, #db7134 0%,#f38c4f 100%); /* IE10+ */
background: linear-gradient(to bottom, #db7134 0%,#f38c4f 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#db7134', endColorstr='#f38c4f',GradientType=0 ); /* IE6-9 */
}
.pro-plan-modal ul {
color: #fff;
list-style: none;
padding: 0 0 30px 0;
text-align: left;
white-space: pre-line;
margin: 0;
}
.pro-plan-modal ul li {
font-family: 'roboto_slabregular', Georgia, Times, serif;
background: url('../images/pro_plan/check.png') no-repeat 0px 12px;
display: inline-block;
font-size: 17px;
line-height: 36px;
padding: 0 0 0 19px;
}
.pro-plan-modal img.close {
width: 35px;
margin-top: 20px;
}
ul.user-accounts div.account {
font-size: large;
}
ul.user-accounts div.remove {
padding-top: 14px;
color: #BBB;
visibility: hidden;
}
ul.user-accounts a:hover div.remove {
visibility: visible;
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

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 || {};
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) {
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;
},
};
javascript = NINJA.decodeJavascript(invoice, javascript);
eval(javascript);
dd = $.extend(true, baseDD, dd);
/*
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,30 +76,123 @@ 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': account.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]);
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) {
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) || ' ';
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.has_taxes) {
return ["15%", "*", "auto", "auto", "auto", "15%"];
} else {
return ["15%", "*", "auto", "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'}
{text: invoiceLabels.item, style: ['tableHeader', 'itemTableHeader']},
{text: invoiceLabels.description, style: ['tableHeader', 'descriptionTableHeader']},
{text: invoiceLabels.unit_cost, style: ['tableHeader', 'costTableHeader']},
{text: invoiceLabels.quantity, style: ['tableHeader', 'qtyTableHeader']},
{text: invoice.has_taxes ? invoiceLabels.tax : '', style: ['tableHeader', 'taxTableHeader']},
{text: invoiceLabels.line_total, style: ['tableHeader', 'lineTotalTableHeader']}
]
];
@ -116,13 +202,15 @@ NINJA.invoiceLines = function(invoice) {
var hideQuantity = invoice.account.hide_quantity == '1';
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 = "";
var tax = '';
if (item.tax && parseFloat(item.tax.rate)) {
tax = parseFloat(item.tax.rate);
} else if (item.tax_rate && parseFloat(item.tax_rate)) {
@ -133,6 +221,7 @@ NINJA.invoiceLines = function(invoice) {
if (shownItem && (!cost || cost == '0.00') && !notes && !productKey) {
continue;
}
shownItem = true;
// process date variables
@ -150,140 +239,165 @@ NINJA.invoiceLines = function(invoice) {
}
lineTotal = formatMoney(lineTotal, currencyId);
rowStyle = i%2===0?'odd':'even';
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};
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});
row.push({style:["quantity", rowStyle], text:qty || ' '});
if (invoice.has_taxes) {
row.push({style:["tax", rowStyle], text: tax+'' || ''});
}
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, removeBalance)
{
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)]);
if (invoice.tax && invoice.tax.name || invoice.tax_name) {
data.push([{text: invoiceLabels.tax}, {text: formatMoney(invoice.tax_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)}]);
}
var paid = invoice.amount - invoice.balance;
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 (!removeBalance) {
data.push([
{text:invoice.is_quote ? invoiceLabels.balance_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();
cityStatePostal = ((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;
var data = [
{text: account.address1},
{text: account.address2},
{text: cityStatePostal},
{text: account.country ? account.country.name : ''}
];
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},
{text: (invoice.is_quote ? invoiceLabels.quote_number : invoiceLabels.invoice_number), style: ['invoiceNumberLabel']},
{text: invoice.invoice_number, style: ['invoiceNumber']}
],
[
invoice.is_quote ? invoiceLabels.quote_date : invoiceLabels.invoice_date,
invoice.invoice_date,
{text: invoiceLabels.po_number},
{text: invoice.po_number}
],
[
invoice.is_quote ? invoiceLabels.total : invoiceLabels.balance_due,
formatMoney(invoice.balance_amount, invoice.client.currency_id),
{text: invoiceLabels.invoice_date},
{text: invoice.invoice_date}
],
[
{text: invoiceLabels.due_date},
{text: invoice.due_date}
]
];
return data;
if (NINJA.parseFloat(invoice.balance) < NINJA.parseFloat(invoice.amount)) {
data.push([
{text: invoiceLabels.total},
{text: formatMoney(invoice.amount, invoice.client.currency_id)}
]);
}
if (NINJA.parseFloat(invoice.partial)) {
data.push([
{text: invoiceLabels.balance},
{text: formatMoney(invoice.total_amount, invoice.client.currency_id)}
]);
}
data.push([
{text: 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) {
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}
];
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 +406,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;
}

File diff suppressed because one or more lines are too long

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));
}
@ -705,8 +723,12 @@ function getInvoiceDetails(invoice) {
{'due_date': invoice.due_date},
];
if (NINJA.parseFloat(invoice.balance) < NINJA.parseFloat(invoice.amount)) {
fields.push({'total': formatMoney(invoice.amount, invoice.client.currency_id)});
}
if (NINJA.parseFloat(invoice.partial)) {
fields.push({'total': formatMoney(invoice.total_amount, invoice.client.currency_id)});
fields.push({'balance': formatMoney(invoice.total_amount, invoice.client.currency_id)});
}
fields.push({'balance_due': formatMoney(invoice.balance_amount, invoice.client.currency_id)})
@ -762,12 +784,16 @@ function displaySubtotals(doc, layout, invoice, y, rightAlignTitleX)
}
var paid = invoice.amount - invoice.balance;
if (paid) {
data.push({'total': formatMoney(invoice.amount, invoice.client.currency_id)});
}
if (invoice.account.hide_paid_to_date != '1' || paid) {
data.push({'paid_to_date': formatMoney(paid, invoice.client.currency_id)});
}
if (NINJA.parseFloat(invoice.partial) && invoice.total_amount != invoice.subtotal_amount) {
data.push({'total': formatMoney(invoice.total_amount, invoice.client.currency_id)});
data.push({'balance': formatMoney(invoice.total_amount, invoice.client.currency_id)});
}
var options = {
@ -1053,7 +1079,7 @@ function displayInvoiceItems(doc, invoice, layout) {
}
shownItem = true;
var numLines = doc.splitTextToSize(item.notes, 200).length + 2;
var numLines = Math.max(doc.splitTextToSize(item.notes, 200).length, doc.splitTextToSize(item.product_key, 60).length) + 2;
//console.log('num lines %s', numLines);
var y = tableTop + (line * layout.tableRowHeight) + (2 * layout.tablePadding);
@ -1567,3 +1593,14 @@ function twoDigits(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;
}

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

@ -1,18 +1,20 @@
# Invoice Ninja
### [https://www.invoiceninja.com](https://www.invoiceninja.com)
If you'd like to use our code to sell your own invoicing app we have an affiliate program. Get in touch for more details.
If you'd like to use our code to sell your own invoicing app email us for details about our affiliate program.
### Introduction
### 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)
For updates follow [@invoiceninja](https://twitter.com/invoiceninja) or join the [Facebook Group](https://www.facebook.com/invoiceninja). For discussion of the app please use our [new forum](http://www.invoiceninja.com/forums).
### Getting Started
If you have any questions or comments please use our [support forum](https://www.invoiceninja.com/forums/forum/support/). For updates follow [@invoiceninja](https://twitter.com/invoiceninja) or join the [Facebook Group](https://www.facebook.com/invoiceninja).
If you'd like to translate the site please use [caouecs/Laravel4-long](https://github.com/caouecs/Laravel4-lang) for the starter files.
Developed by [@hillelcoren](https://twitter.com/hillelcoren) | Designed by [kantorp-wegl.in](http://kantorp-wegl.in/).
### Features
* Built using Laravel 5
@ -34,11 +36,10 @@ Developed by [@hillelcoren](https://twitter.com/hillelcoren) | Designed by [kant
* [Sigitas Limontas](https://lt.linkedin.com/in/sigitaslimontas)
### Documentation
* [Self Host](https://www.invoiceninja.com/knowledgebase/self-host/)
* [Ubuntu and Apache](http://blog.technerdservices.com/index.php/2015/04/techpop-how-to-install-invoice-ninja-on-ubuntu-14-04/)
* [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
@ -70,3 +71,4 @@ Developed by [@hillelcoren](https://twitter.com/hillelcoren) | Designed by [kant
* [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
* [josdejong/jsoneditor](https://github.com/josdejong/jsoneditor/) - A web-based tool to view, edit and format JSON

View File

@ -599,7 +599,7 @@ return array(
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'utf8_invoices' => 'New PDF Engine <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.',
@ -678,4 +678,67 @@ return array(
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
'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',
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Edit Details',
'work' => 'Work',
'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here',
'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',
'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',
);

View File

@ -419,7 +419,7 @@ return array(
'confirm_email_quote' => 'Bist du sicher, dass du dieses Angebot per E-Mail versenden möchtest',
'confirm_recurring_email_invoice' => 'Wiederkehrende Rechnung ist aktiv. Bis du sicher, dass du diese Rechnung weiterhin als E-Mail verschicken möchtest?',
'cancel_account' => 'Account Kündigen',
'cancel_account' => 'Konto Kündigen',
'cancel_account_message' => 'Warnung: Alle Daten werden unwiderruflich und vollständig gelöscht, es gibt kein zurück.',
'go_back' => 'Zurück',
@ -562,7 +562,7 @@ return array(
'api_tokens' => 'API Token',
'users_and_tokens' => 'Benutzer & Token',
'account_login' => 'Account Login',
'account_login' => 'Konto Login',
'recover_password' => 'Passwort wiederherstellen',
'forgot_password' => 'Passwort vergessen?',
'email_address' => 'E-Mail-Adresse',
@ -590,7 +590,6 @@ return array(
'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.',
@ -660,7 +659,7 @@ return array(
'create_task' => 'Aufgabe erstellen',
'stopped_task' => 'Aufgabe erfolgreich angehalten',
'invoice_task' => 'Aufgabe in Rechnung stellen',
'invoice_labels' => 'Rechnung Etiketten',
'invoice_labels' => 'Rechnung Spaltenüberschriften',
'prefix' => 'Präfix',
'counter' => 'Zähler',
@ -669,4 +668,68 @@ return array(
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
'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_feature3' => 'Benutzerdefinierte URLs - "DeineFirma.InvoiceNinja.com"',
'pro_plan_feature4' => '"Created by 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',
'pro_plan_feature8' => 'PDFs an E-Mails zu Kunden anhängen',
'resume' => 'Fortfahren',
'break_duration' => 'Pause',
'edit_details' => 'Details bearbeiten',
'work' => 'Arbeiten',
'timezone_unset' => 'Bitte :link um deine Zeitzone zu setzen',
'click_here' => 'hier klicken',
'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',
'untitled' => 'Unbenannt',
'new_account' => 'Neues Konto',
'associated_accounts' => 'Konten erfolgreich verlinkt',
'unlinked_account' => 'Konten erfolgreich getrennt',
'login' => 'Login',
'or' => 'oder',
'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',
'utf8_invoices' => 'New PDF Engine <sup>Beta</sup>',
'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',
);

View File

@ -41,7 +41,7 @@ return array(
'tax' => 'Tax',
'item' => 'Item',
'description' => 'Description',
'unit_cost' => 'Unit Cost',
'unit_cost' => 'Cost',
'quantity' => 'Quantity',
'line_total' => 'Line Total',
'subtotal' => 'Subtotal',
@ -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.',
@ -597,7 +597,7 @@ return array(
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'utf8_invoices' => 'New PDF Engine <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.',
@ -676,4 +676,68 @@ return array(
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
'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',
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Edit Details',
'work' => 'Work',
'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here',
'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',
'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.',
);

View File

@ -569,7 +569,7 @@ return array(
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'utf8_invoices' => 'New PDF Engine <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.',
@ -648,4 +648,66 @@ return array(
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
'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',
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Edit Details',
'work' => 'Work',
'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here',
'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',
'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',
);

View File

@ -598,7 +598,7 @@ return array(
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'utf8_invoices' => 'New PDF Engine <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.',
@ -677,4 +677,67 @@ return array(
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
'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',
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Edit Details',
'work' => 'Work',
'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here',
'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',
'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',
);

View File

@ -12,9 +12,9 @@ return array(
'address2' => 'Appt/Bâtiment',
'city' => 'Ville',
'state' => 'Région/Département',
'postal_code' => 'Code Postal',
'postal_code' => 'Code postal',
'country_id' => 'Pays',
'contacts' => 'Informations de contact', //if you speak about contact details
'contacts' => 'Informations de contact',
'first_name' => 'Prénom',
'last_name' => 'Nom',
'phone' => 'Téléphone',
@ -23,7 +23,7 @@ return array(
'payment_terms' => 'Conditions de paiement',
'currency_id' => 'Devise',
'size_id' => 'Taille',
'industry_id' => 'Secteur', // literal translation : Industrie
'industry_id' => 'Secteur',
'private_notes' => 'Note personnelle',
// invoice
@ -45,11 +45,11 @@ return array(
'quantity' => 'Quantité',
'line_total' => 'Total',
'subtotal' => 'Total',
'paid_to_date' => 'Versé à ce jour',//this one is not very used in France
'balance_due' => 'Montant total',//can be "Montant à verser" or "Somme totale"
'invoice_design_id' => 'Design', //if you speak about invoice's design -> "Modèle"
'paid_to_date' => 'Versé à ce jour',
'balance_due' => 'Montant total',
'invoice_design_id' => 'Design',
'terms' => 'Conditions',
'your_invoice' => 'Votre Facture',
'your_invoice' => 'Votre facture',
'remove_contact' => 'Supprimer un contact',
'add_contact' => 'Ajouter un contact',
@ -133,17 +133,17 @@ return array(
'delete_credit' => 'Supprimer ce crédit',
'show_archived_deleted' => 'Afficher archivés/supprimés',
'filter' => 'Filtrer',
'new_client' => 'Nouveau Client',
'new_invoice' => 'Nouvelle Facture',
'new_payment' => 'Nouveau Paiement',
'new_credit' => 'Nouveau Crédit',
'new_client' => 'Nouveau client',
'new_invoice' => 'Nouvelle facture',
'new_payment' => 'Nouveau paiement',
'new_credit' => 'Nouveau crédit',
'contact' => 'Contact',
'date_created' => 'Date de création',
'last_login' => 'Dernière connexion',
'balance' => 'Solde',
'action' => 'Action',
'status' => 'Statut',
'invoice_total' => 'Montant Total',
'invoice_total' => 'Montant total',
'frequency' => 'Fréquence',
'start_date' => 'Date de début',
'end_date' => 'Date de fin',
@ -156,8 +156,8 @@ return array(
'credit_date' => 'Date de crédit',
'empty_table' => 'Aucune donnée disponible dans la table',
'select' => 'Sélectionner',
'edit_client' => 'Éditer le Client',
'edit_invoice' => 'Éditer la Facture',
'edit_client' => 'Éditer le client',
'edit_invoice' => 'Éditer la facture',
// client view page
'create_invoice' => 'Créer une facture',
@ -206,13 +206,13 @@ return array(
'import_to' => 'Importer en tant que',
'client_will_create' => 'client sera créé',
'clients_will_create' => 'clients seront créés',
'email_settings' => 'Email Settings',
'pdf_email_attachment' => 'Attach PDF to Emails',
'email_settings' => 'Paramètres mail',
'pdf_email_attachment' => 'Joindre PDF aux emails',
// application messages
'created_client' => 'Client créé avec succès',
'created_clients' => ':count clients créés ave csuccès',
'updated_settings' => 'paramètres mis à jour avec succès',
'created_clients' => ':count clients créés avec succès',
'updated_settings' => 'Paramètres mis à jour avec succès',
'removed_logo' => 'Logo supprimé avec succès',
'sent_message' => 'Message envoyé avec succès',
'invoice_error' => 'Veuillez vous assurer de sélectionner un client et de corriger les erreurs',
@ -252,7 +252,7 @@ return array(
'deleted_credits' => ':count crédits supprimés avec succès',
// Emails
'confirmation_subject' => 'Validation du compte invoice ninja',
'confirmation_subject' => 'Validation du compte Invoice Ninja',
'confirmation_header' => 'Validation du compte',
'confirmation_message' => 'Veuillez cliquer sur le lien ci-après pour valider votre compte.',
'invoice_subject' => 'Nouvelle facture :invoice en provenance de :account',
@ -261,7 +261,7 @@ return array(
'payment_message' => 'Merci pour votre paiement d\'un montant de :amount',
'email_salutation' => 'Cher :name,',
'email_signature' => 'Cordialement,',
'email_from' => 'L\'équipe InvoiceNinja',
'email_from' => 'L\'équipe Invoice Ninja',
'user_email_footer' => 'Pour modifier vos paramètres de notification par courriel, veuillez visiter '.SITE_URL.'/company/notifications',
'invoice_link_message' => 'Pour voir la facture de votre client cliquez sur le lien ci-après :',
'notification_invoice_paid_subject' => 'La facture :invoice a été payée par le client :client',
@ -293,7 +293,7 @@ return array(
// Pro Plan
'pro_plan' => [
'remove_logo' => ':link pour supprimer le logo Invoice Ninja en souscrivant au plan pro',
'remove_logo' => ':link pour supprimer le logo Invoice Ninja en souscrivant au Plan Pro',
'remove_logo_link' => 'Cliquez ici',
],
@ -330,7 +330,7 @@ return array(
'update_products' => 'Mise à jour auto des produits',
'update_products_help' => 'La mise à jour d\'une facture entraîne la <b>mise à jour des produits</b>',
'create_product' => 'Nouveau produit',
'edit_product' => 'Éditer Produit',
'edit_product' => 'Éditer produit',
'archive_product' => 'Archiver Produit',
'updated_product' => 'Produit mis à jour',
'created_product' => 'Produit créé',
@ -456,10 +456,10 @@ return array(
'sent' => 'envoyé',
'timesheets' => 'Feuilles de temps',
'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.',
'payment_title' => 'Entrez votre adresse de facturation et vos informations bancaires',
'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',
'id_number' => 'Numéro ID',
@ -492,18 +492,18 @@ return array(
'select_versiony' => 'Choix de la verison',
'view_history' => 'Consulter l\'historique',
'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' => 'Editer le paiement',
'updated_payment' => 'Paiement édité avec succès',
'deleted' => 'Supprimé',
'restore_user' => 'Restaurer l\'utilisateur',
'restored_user' => 'Restaurer la commande',
'show_deleted_users' => 'Voir les utilisateurs supprimés',
'email_templates' => 'Templates de mail',
'invoice_email' => 'Templates de facture',
'payment_email' => 'Email de paiement',
'quote_email' => 'Email de déclaration',
'reset_all' => 'Réinitialiser',
'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.',
@ -519,18 +519,18 @@ return array(
'token_billing_secure' => 'The data is stored securely by :stripe_link',
'support' => 'Support',
'contact_information' => 'Contact information',
'contact_information' => 'Information de contact',
'256_encryption' => '256-Bit Encryption',
'amount_due' => 'Amount due',
'amount_due' => 'Montant dû',
'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.',
'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
'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',
'token_management' => 'Token Management',
'tokens' => 'Tokens',
@ -543,30 +543,30 @@ return array(
'delete_token' => 'Delete 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' => 'Ajouter passerelle',
'delete_gateway' => 'Supprimer passerelle',
'edit_gateway' => 'Editer passerelle',
'updated_gateway' => 'Passerelle mise à jour avec succès',
'created_gateway' => 'Passerelle crée avec succès',
'deleted_gateway' => 'Passerelle supprimée avec succès',
'pay_with_paypal' => 'PayPal',
'pay_with_card' => 'Credit card',
'pay_with_card' => 'Carte bancaire',
'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' => 'Changer de pot de passe',
'current_password' => 'Mot de passe actuel',
'new_password' => 'Nouveau mot de passe',
'confirm_password' => 'Confirmer le mot de passe',
'password_error_incorrect' => 'Le mot de passe actuel est incorrect.',
'password_error_invalid' => 'Le nouveau mot de passe est invalide',
'updated_password' => 'Mot de passe mis à jour avec succès',
'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',
'forgot_password' => 'Mot de passe oublié ?',
'email_address' => 'Adresse email',
'lets_go' => 'Allons-y !',
'password_recovery' => 'Password Recovery',
'send_email' => 'Send email',
'set_password' => 'Set Password',
@ -579,94 +579,157 @@ return array(
'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_credit_card' => 'Carte de crédit',
'payment_type_paypal' => 'PayPal',
'payment_type_bitcoin' => 'Bitcoin',
'knowledge_base' => 'Knowledge Base',
'partial' => 'Partial',
'partial_remaining' => ':partial of :balance',
'knowledge_base' => 'Base de connaissances',
'partial' => 'Partiel',
'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',
'more_fields' => 'Plus de champs',
'less_fields' => 'Moins de champs',
'client_name' => 'Nom du client',
'pdf_settings' => 'Réglages PDF',
'utf8_invoices' => 'New PDF Engine <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' => 'View Documentation',
'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' => 'rows',
'rows' => 'lignes',
'www' => 'www',
'logo' => 'Logo',
'subdomain' => 'Subdomain',
'provide_name_or_email' => 'Please provide a contact name or email',
'subdomain' => 'Sous domaine',
'provide_name_or_email' => 'Merci d\'indiquer un nom ou une adresse email',
'charts_and_reports' => 'Charts & Reports',
'chart' => 'Chart',
'report' => 'Report',
'group_by' => 'Group by',
'paid' => 'Paid',
'group_by' => 'Grouper par',
'paid' => 'Pa',
'enable_report' => 'Report',
'enable_chart' => 'Chart',
'totals' => 'Totals',
'run' => 'Run',
'export' => 'Export',
'export' => 'Exporter',
'documentation' => 'Documentation',
'zapier' => 'Zapier <sup>Beta</sup>',
'recurring' => 'Recurring',
'last_invoice_sent' => 'Last invoice sent :date',
'recurring' => 'Récurrent',
'last_invoice_sent' => 'Dernière facture envoyée le :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',
'timer' => 'Timer',
'manual' => 'Manual',
'date_and_time' => 'Date & Time',
'second' => 'second',
'seconds' => 'seconds',
'processed_updates' => 'Mise à jour effectuée avec succès',
'tasks' => 'Tâches',
'new_task' => 'Nouvelle tâche',
'start_time' => 'Début',
'created_task' => 'Tâche crée avec succès',
'updated_task' => 'Tâche mise à jour avec succès',
'edit_task' => 'Editer la tâche',
'archive_task' => 'Archiver tâche',
'restore_task' => 'Restaurer tâche',
'delete_task' => 'Supprimer tâche',
'stop_task' => 'Arrêter tâche',
'time' => 'Temps',
'start' => 'Début',
'stop' => 'Fin',
'now' => 'Maintenant',
'timer' => 'Compteur',
'manual' => 'Manuel',
'date_and_time' => 'Date & heure',
'second' => 'seconde',
'seconds' => 'secondes',
'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',
'hour' => 'heure',
'hours' => 'heures',
'task_details' => 'Détails tâche',
'duration' => 'Durée',
'end_time' => 'Heure de fin',
'end' => 'Fin',
'invoiced' => 'Facturé',
'logged' => 'Connecté',
'running' => 'En cours',
'task_error_multiple_clients' => 'Cette tâche ne peut appartenir à plusieurs clients',
'task_error_running' => 'Merci d\'arrêter les tâches en cours',
'task_error_invoiced' => 'Tâches déjà 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 tâche',
'stopped_task' => 'Tâche stoppée avec succès',
'invoice_task' => 'Tâche facturation',
'invoice_labels' => 'Champs facture',
'prefix' => 'Préfixe',
'counter' => 'Compteur',
'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',
'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',
'resume' => 'Resume',
'break_duration' => 'Break',
'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',
'untitled' => 'Sans titre',
'new_account' => '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',
'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',
);

View File

@ -590,7 +590,7 @@ return array(
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'utf8_invoices' => 'New PDF Engine <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.',
@ -619,54 +619,117 @@ 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.',
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
'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',
'resume' => 'Continuer',
'break_duration' => 'Pause',
'edit_details' => 'Modifier',
'work' => 'Travail',
'timezone_unset' => 'Please :link to set your timezone',
'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',
'untitled' => 'Untitled',
'new_account' => 'New Account',
'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',
);

View File

@ -592,7 +592,7 @@ return array(
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'utf8_invoices' => 'New PDF Engine <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.',
@ -671,4 +671,68 @@ return array(
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
'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',
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Edit Details',
'work' => 'Work',
'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here',
'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',
'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',
);

View File

@ -1,20 +1,19 @@
<?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,7 @@ return array(
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'utf8_invoices' => 'New PDF Engine <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.',
@ -679,5 +679,68 @@ return array(
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
'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',
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Edit Details',
'work' => 'Work',
'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here',
'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',
'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',
);

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.",
"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.",
"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',
/*
|--------------------------------------------------------------------------
| 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 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.
|
*/
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
'custom' => 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.
|
*/
/*
|--------------------------------------------------------------------------
| 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.
|
*/
'attributes' => [],
'attributes' => array(),
);
];

View File

@ -598,7 +598,7 @@ return array(
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'utf8_invoices' => 'New PDF Engine <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.',
@ -677,4 +677,67 @@ return array(
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
'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',
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Edit Details',
'work' => 'Work',
'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here',
'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',
'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',
);

View File

@ -593,7 +593,7 @@ return array(
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'utf8_invoices' => 'New PDF Engine <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.',
@ -672,4 +672,67 @@ return array(
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
'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',
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Edit Details',
'work' => 'Work',
'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here',
'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',
'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',
);

View File

@ -593,7 +593,7 @@ return array(
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'utf8_invoices' => 'New PDF Engine <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.',
@ -672,4 +672,66 @@ return array(
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
'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',
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Edit Details',
'work' => 'Work',
'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here',
'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',
'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',
);

View File

@ -596,7 +596,7 @@ return array(
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'utf8_invoices' => 'New PDF Engine <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.',
@ -675,4 +675,67 @@ return array(
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
'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',
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Edit Details',
'work' => 'Work',
'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here',
'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',
'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',
);

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('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,183 @@
@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.documentation'))->asLinkTo(PDFMAKE_DOCS)->withAttributes(['target' => '_blank'])->appendIcon(Icon::create('info-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>
@if (!Auth::user()->isPro())
<script>
$(function() {
$('form.warn-on-exit input').prop('disabled', true);
});
</script>
@endif
{!! Former::close() !!}
</div>
<div class="col-md-6">
@include('invoices.pdf', ['account' => Auth::user()->account, 'pdfHeight' => 800])
</div>
</div>
@stop

View File

@ -22,6 +22,9 @@
{{ Former::populateField('last_name', $account->users()->first()->last_name) }}
{{ Former::populateField('email', $account->users()->first()->email) }}
{{ Former::populateField('phone', $account->users()->first()->phone) }}
@if (Utils::isNinja())
{{ Former::populateField('dark_mode', intval($account->users()->first()->dark_mode)) }}
@endif
@endif
<div class="row">
@ -88,6 +91,10 @@
{!! Former::text('last_name') !!}
{!! Former::text('email') !!}
{!! Former::text('phone') !!}
@if (Utils::isNinja())
{!! 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

@ -111,20 +111,10 @@
vals = [{!! json_encode($emailFooter) !!}, '{!! Auth::user()->account->getDisplayName() !!}', 'Client Name', formatMoney(100), '{!! NINJA_WEB_URL !!}', 'Contact Name'];
// Add any available payment method links
<?php
foreach([PAYMENT_TYPE_CREDIT_CARD, PAYMENT_TYPE_PAYPAL, PAYMENT_TYPE_BITCOIN] as $type) {
if (Auth::user()->account->getGatewayByType($type)) {
// Changes "PAYMENT_TYPE_CREDIT_CARD" to "credit_card"
$gateway_slug = strtolower(str_replace('PAYMENT_TYPE_', '', $type)).'_link';
echo "keys.push('$gateway_slug'); ";
echo "vals.push('".URL::to("/payment/xxxxxx/{$type}")."'); ";
echo "\n";
}
}
?>
@foreach (\App\Models\Gateway::getPaymentTypeLinks() as $type)
{!! "keys.push('" . $type.'_link' . "');" !!}
{!! "vals.push('" . URL::to("/payment/xxxxxx/{$type}") . "');" !!}
@endforeach
for (var i=0; i<keys.length; i++) {
var regExp = new RegExp('\\$'+keys[i], 'g');

View File

@ -28,7 +28,8 @@
$('#invoice_design_id').val(1);
return invoiceDesigns[0].javascript;
} else {
return invoiceDesigns[id-1].javascript;
var design = _.find(invoiceDesigns, function(design){ return design.id == id});
return design ? design.javascript : '';
}
}
@ -55,8 +56,7 @@
}
}
doc = generatePDF(invoice, getDesignJavascript(), true);
doc.getDataUrl(cb);
generatePDF(invoice, getDesignJavascript(), true, cb);
}
$(function() {
@ -96,7 +96,7 @@
<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') !!}
@ -109,6 +109,10 @@
{!! 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

@ -24,7 +24,6 @@
{{ 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">
@ -99,9 +98,6 @@
<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') !!}
@ -9,7 +9,7 @@
@if (!Auth::user()->account->isPro())
<center>
<div style="font-size:larger;" class="col-md-8 col-md-offset-2">{!! trans('texts.pro_plan_advanced_settings', ['link'=>'<a href="#" onclick="submitProPlan(\''.$feature.'\')">'.trans('texts.pro_plan.remove_logo_link').'</a>']) !!}</div>
<div style="font-size:larger;" class="col-md-8 col-md-offset-2">{!! trans('texts.pro_plan_advanced_settings', ['link'=>'<a href="#" onclick="showProPlan(\''.$feature.'\')">'.trans('texts.pro_plan.remove_logo_link').'</a>']) !!}</div>
&nbsp;<p/>&nbsp;
</center>
@endif

View File

@ -4,7 +4,7 @@
@parent
{!! Former::open($url)->method($method)
->rules(['product_key' => 'required|max:20'])
->rules(['product_key' => 'required|max:255'])
->addClass('col-md-8 col-md-offset-2 warn-on-exit') !!}

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