1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-08 20:22:42 +01:00
This commit is contained in:
David Bomba 2015-10-27 10:01:13 +11:00
commit 7cdbb56284
134 changed files with 14945 additions and 12783 deletions

View File

@ -20,6 +20,6 @@ MAIL_FROM_ADDRESS
MAIL_FROM_NAME
MAIL_PASSWORD
#PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'
PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'
LOG=single

2
.gitignore vendored
View File

@ -14,7 +14,7 @@
/vendor
/node_modules
/.DS_Store
/Thumbs.db
Thumbs.db
/.env
/.env.development.php
/.env.php

View File

@ -130,6 +130,16 @@ module.exports = function(grunt) {
options: {
process: false
}
},
js_pdf: {
src: [
'public/js/pdf_viewer.js',
'public/js/compatibility.js',
'public/js/pdfmake.min.js',
'public/js/vfs_fonts.js',
],
dest: 'public/js/pdf.built.js',
nonull: true
}
}
});

View File

@ -235,7 +235,7 @@ class CheckData extends Command {
'updated_at' => new Carbon,
'account_id' => $client->account_id,
'client_id' => $client->id,
'message' => 'Recovered update to invoice [<a href="https://github.com/hillelcoren/invoice-ninja/releases/tag/v1.7.1" target="_blank">details</a>]',
'message' => 'Corrected client balance',
'adjustment' => $client->actual_balance - $activity->balance,
'balance' => $client->actual_balance,
]);

View File

@ -59,7 +59,7 @@ class CreateRandomData extends Command {
}
$invoice = Invoice::createNew($user);
$invoice->invoice_number = $user->account->getNextInvoiceNumber();
$invoice->invoice_number = $user->account->getNextInvoiceNumber($invoice);
$invoice->amount = $invoice->balance = $price;
$invoice->created_at = date('Y-m-d', strtotime(date("Y-m-d") . ' - ' . rand(1, 100) . ' days'));
$client->invoices()->save($invoice);

View File

@ -33,15 +33,22 @@ class SendRecurringInvoices extends Command
$today = new DateTime();
$invoices = Invoice::with('account.timezone', 'invoice_items', 'client', 'user')
->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', array($today, $today))->get();
->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND frequency_id > 0 AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', array($today, $today))
->orderBy('id', 'asc')
->get();
$this->info(count($invoices).' recurring invoice(s) found');
foreach ($invoices as $recurInvoice) {
if (!$recurInvoice->user->confirmed) {
continue;
}
$recurInvoice->account->loadLocalizationSettings($recurInvoice->client);
$this->info('Processing Invoice '.$recurInvoice->id.' - Should send '.($recurInvoice->shouldSendToday() ? 'YES' : 'NO'));
$invoice = $this->invoiceRepo->createRecurringInvoice($recurInvoice);
if ($invoice && !$invoice->isPaid()) {
$recurInvoice->account->loadLocalizationSettings($invoice->client);
$this->info('Sending Invoice');
$this->mailer->sendInvoice($invoice);
}
}

View File

@ -15,7 +15,6 @@ use Cache;
use Response;
use parseCSV;
use Request;
use App\Models\Affiliate;
use App\Models\License;
use App\Models\User;
@ -37,6 +36,7 @@ use App\Models\Gateway;
use App\Models\Timezone;
use App\Models\Industry;
use App\Models\InvoiceDesign;
use App\Models\TaxRate;
use App\Ninja\Repositories\AccountRepository;
use App\Ninja\Mailers\UserMailer;
use App\Ninja\Mailers\ContactMailer;
@ -129,9 +129,9 @@ class AccountController extends BaseController
Session::put("show_trash:{$entityType}", $visible == 'true');
if ($entityType == 'user') {
return Redirect::to('company/'.ACCOUNT_ADVANCED_SETTINGS.'/'.ACCOUNT_USER_MANAGEMENT);
return Redirect::to('settings/'.ACCOUNT_USER_MANAGEMENT);
} elseif ($entityType == 'token') {
return Redirect::to('company/'.ACCOUNT_ADVANCED_SETTINGS.'/'.ACCOUNT_TOKEN_MANAGEMENT);
return Redirect::to('settings/'.ACCOUNT_API_TOKENS);
} else {
return Redirect::to("{$entityType}s");
}
@ -143,10 +143,80 @@ class AccountController extends BaseController
return Response::json($data);
}
public function showSection($section = ACCOUNT_DETAILS, $subSection = false)
public function showSection($section = false)
{
if ($section == ACCOUNT_DETAILS) {
if (!$section) {
return Redirect::to('/settings/' . ACCOUNT_COMPANY_DETAILS, 301);
}
if ($section == ACCOUNT_COMPANY_DETAILS) {
return self::showCompanyDetails();
} elseif ($section == ACCOUNT_USER_DETAILS) {
return self::showUserDetails();
} elseif ($section == ACCOUNT_LOCALIZATION) {
return self::showLocalization();
} elseif ($section == ACCOUNT_PAYMENTS) {
return self::showOnlinePayments();
} elseif ($section == ACCOUNT_INVOICE_SETTINGS) {
return self::showInvoiceSettings();
} elseif ($section == ACCOUNT_IMPORT_EXPORT) {
return View::make('accounts.import_export', ['title' => trans('texts.import_export')]);
} elseif ($section == ACCOUNT_INVOICE_DESIGN || $section == ACCOUNT_CUSTOMIZE_DESIGN) {
return self::showInvoiceDesign($section);
} elseif ($section === ACCOUNT_TEMPLATES_AND_REMINDERS) {
return self::showTemplates();
} elseif ($section === ACCOUNT_PRODUCTS) {
return self::showProducts();
} elseif ($section === ACCOUNT_TAX_RATES) {
return self::showTaxRates();
} else {
$data = [
'account' => Account::with('users')->findOrFail(Auth::user()->account_id),
'title' => trans("texts.{$section}"),
'section' => $section
];
return View::make("accounts.{$section}", $data);
}
}
private function showInvoiceSettings()
{
$account = Auth::user()->account;
$recurringHours = [];
for ($i=0; $i<24; $i++) {
if ($account->military_time) {
$format = 'H:i';
} else {
$format = 'g:i a';
}
$recurringHours[$i] = date($format, strtotime("{$i}:00"));
}
$data = [
'account' => Account::with('users')->findOrFail(Auth::user()->account_id),
'title' => trans("texts.invoice_settings"),
'section' => ACCOUNT_INVOICE_SETTINGS,
'recurringHours' => $recurringHours
];
return View::make("accounts.invoice_settings", $data);
}
private function showCompanyDetails()
{
$data = [
'account' => Account::with('users')->findOrFail(Auth::user()->account_id),
'countries' => Cache::get('countries'),
'sizes' => Cache::get('sizes'),
'industries' => Cache::get('industries'),
'title' => trans('texts.company_details'),
];
return View::make('accounts.details', $data);
}
private function showUserDetails()
{
$oauthLoginUrls = [];
foreach (AuthService::$providers as $provider) {
$oauthLoginUrls[] = ['label' => $provider, 'url' => '/auth/' . strtolower($provider)];
@ -154,23 +224,32 @@ class AccountController extends BaseController
$data = [
'account' => Account::with('users')->findOrFail(Auth::user()->account_id),
'countries' => Cache::get('countries'),
'sizes' => Cache::get('sizes'),
'industries' => Cache::get('industries'),
'timezones' => Cache::get('timezones'),
'dateFormats' => Cache::get('dateFormats'),
'datetimeFormats' => Cache::get('datetimeFormats'),
'currencies' => Cache::get('currencies'),
'languages' => Cache::get('languages'),
'title' => trans('texts.company_details'),
'title' => trans('texts.user_details'),
'user' => Auth::user(),
'oauthProviderName' => AuthService::getProviderName(Auth::user()->oauth_provider_id),
'oauthLoginUrls' => $oauthLoginUrls,
];
return View::make('accounts.details', $data);
} elseif ($section == ACCOUNT_PAYMENTS) {
return View::make('accounts.user_details', $data);
}
private function showLocalization()
{
$data = [
'account' => Account::with('users')->findOrFail(Auth::user()->account_id),
'timezones' => Cache::get('timezones'),
'dateFormats' => Cache::get('dateFormats'),
'datetimeFormats' => Cache::get('datetimeFormats'),
'currencies' => Cache::get('currencies'),
'languages' => Cache::get('languages'),
'title' => trans('texts.localization'),
];
return View::make('accounts.localization', $data);
}
private function showOnlinePayments()
{
$account = Auth::user()->account;
$account->load('account_gateways');
$count = count($account->account_gateways);
@ -183,25 +262,39 @@ class AccountController extends BaseController
'title' => trans('texts.online_payments')
]);
}
} elseif ($section == ACCOUNT_NOTIFICATIONS) {
}
private function showProducts()
{
$columns = ['product', 'description', 'unit_cost'];
if (Auth::user()->account->invoice_item_taxes) {
$columns[] = 'tax_rate';
}
$columns[] = 'action';
$data = [
'account' => Account::with('users')->findOrFail(Auth::user()->account_id),
'title' => trans('texts.notifications'),
'account' => Auth::user()->account,
'title' => trans('texts.product_library'),
'columns' => Utils::trans($columns),
];
return View::make('accounts.notifications', $data);
} elseif ($section == ACCOUNT_IMPORT_EXPORT) {
return View::make('accounts.import_export', ['title' => trans('texts.import_export')]);
} elseif ($section == ACCOUNT_ADVANCED_SETTINGS) {
return View::make('accounts.products', $data);
}
private function showTaxRates()
{
$data = [
'account' => Auth::user()->account,
'title' => trans('texts.tax_rates'),
'taxRates' => TaxRate::scope()->get(['id', 'name', 'rate']),
];
return View::make('accounts.tax_rates', $data);
}
private function showInvoiceDesign($section)
{
$account = Auth::user()->account->load('country');
$data = [
'account' => $account,
'feature' => $subSection,
'title' => trans('texts.invoice_settings'),
];
if ($subSection == ACCOUNT_INVOICE_DESIGN
|| $subSection == ACCOUNT_CUSTOMIZE_DESIGN) {
$invoice = new stdClass();
$client = new stdClass();
$contact = new stdClass();
@ -215,7 +308,7 @@ class AccountController extends BaseController
$client->work_phone = '';
$client->work_email = '';
$invoice->invoice_number = $account->getNextInvoiceNumber();
$invoice->invoice_number = '0000';
$invoice->invoice_date = Utils::fromSqlDate(date('Y-m-d'));
$invoice->account = json_decode($account->toJson());
$invoice->amount = $invoice->balance = 100;
@ -239,6 +332,7 @@ class AccountController extends BaseController
$data['invoiceLabels'] = json_decode($account->invoice_labels) ?: [];
$data['title'] = trans('texts.invoice_design');
$data['invoiceDesigns'] = InvoiceDesign::getDesigns();
$data['section'] = $section;
$design = false;
foreach ($data['invoiceDesigns'] as $item) {
@ -248,10 +342,16 @@ class AccountController extends BaseController
}
}
if ($subSection == ACCOUNT_CUSTOMIZE_DESIGN) {
if ($section == ACCOUNT_CUSTOMIZE_DESIGN) {
$data['customDesign'] = ($account->custom_design && !$design) ? $account->custom_design : $design;
}
} else if ($subSection == ACCOUNT_TEMPLATES_AND_REMINDERS) {
return View::make("accounts.{$section}", $data);
}
private function showTemplates()
{
$account = Auth::user()->account->load('country');
$data['account'] = $account;
$data['templates'] = [];
$data['defaultTemplates'] = [];
foreach ([ENTITY_INVOICE, ENTITY_QUOTE, ENTITY_PAYMENT, REMINDER1, REMINDER2, REMINDER3] as $type) {
@ -266,45 +366,37 @@ class AccountController extends BaseController
}
$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.templates_and_reminders', $data);
}
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);
}
}
public function doSection($section = ACCOUNT_DETAILS, $subSection = false)
public function doSection($section = ACCOUNT_COMPANY_DETAILS)
{
if ($section == ACCOUNT_DETAILS) {
if ($section === ACCOUNT_COMPANY_DETAILS) {
return AccountController::saveDetails();
} elseif ($section == ACCOUNT_IMPORT_EXPORT) {
} elseif ($section === ACCOUNT_USER_DETAILS) {
return AccountController::saveUserDetails();
} elseif ($section === ACCOUNT_LOCALIZATION) {
return AccountController::saveLocalization();
} elseif ($section === ACCOUNT_IMPORT_EXPORT) {
return AccountController::importFile();
} elseif ($section == ACCOUNT_MAP) {
} elseif ($section === ACCOUNT_MAP) {
return AccountController::mapFile();
} elseif ($section == ACCOUNT_NOTIFICATIONS) {
} elseif ($section === ACCOUNT_NOTIFICATIONS) {
return AccountController::saveNotifications();
} elseif ($section == ACCOUNT_EXPORT) {
} elseif ($section === ACCOUNT_EXPORT) {
return AccountController::export();
} elseif ($section == ACCOUNT_ADVANCED_SETTINGS) {
if ($subSection == ACCOUNT_INVOICE_SETTINGS) {
} elseif ($section === ACCOUNT_INVOICE_SETTINGS) {
return AccountController::saveInvoiceSettings();
} elseif ($subSection == ACCOUNT_INVOICE_DESIGN) {
} elseif ($section === ACCOUNT_INVOICE_DESIGN) {
return AccountController::saveInvoiceDesign();
} elseif ($subSection == ACCOUNT_CUSTOMIZE_DESIGN) {
} elseif ($section === ACCOUNT_CUSTOMIZE_DESIGN) {
return AccountController::saveCustomizeDesign();
} elseif ($subSection == ACCOUNT_TEMPLATES_AND_REMINDERS) {
} elseif ($section === ACCOUNT_TEMPLATES_AND_REMINDERS) {
return AccountController::saveEmailTemplates();
}
} elseif ($section == ACCOUNT_PRODUCTS) {
} elseif ($section === ACCOUNT_PRODUCTS) {
return AccountController::saveProducts();
} elseif ($section === ACCOUNT_TAX_RATES) {
return AccountController::saveTaxRates();
}
}
@ -318,7 +410,7 @@ class AccountController extends BaseController
Session::flash('message', trans('texts.updated_settings'));
}
return Redirect::to('company/advanced_settings/customize_design');
return Redirect::to('settings/' . ACCOUNT_CUSTOMIZE_DESIGN);
}
private function saveEmailTemplates()
@ -351,7 +443,21 @@ class AccountController extends BaseController
Session::flash('message', trans('texts.updated_settings'));
}
return Redirect::to('company/advanced_settings/templates_and_reminders');
return Redirect::to('settings/' . ACCOUNT_TEMPLATES_AND_REMINDERS);
}
private function saveTaxRates()
{
$account = Auth::user()->account;
$account->invoice_taxes = Input::get('invoice_taxes') ? true : false;
$account->invoice_item_taxes = Input::get('invoice_item_taxes') ? true : false;
$account->show_item_taxes = Input::get('show_item_taxes') ? true : false;
$account->default_tax_rate_id = Input::get('default_tax_rate_id');
$account->save();
Session::flash('message', trans('texts.updated_settings'));
return Redirect::to('settings/' . ACCOUNT_TAX_RATES);
}
private function saveProducts()
@ -363,14 +469,18 @@ class AccountController extends BaseController
$account->save();
Session::flash('message', trans('texts.updated_settings'));
return Redirect::to('company/products');
return Redirect::to('settings/' . ACCOUNT_PRODUCTS);
}
private function saveInvoiceSettings()
{
if (Auth::user()->account->isPro()) {
$rules = [];
$rules = [
'invoice_number_pattern' => 'has_counter',
'quote_number_pattern' => 'has_counter',
];
$user = Auth::user();
$iframeURL = preg_replace('/[^a-zA-Z0-9_\-\:\/\.]/', '', substr(strtolower(Input::get('iframe_url')), 0, MAX_IFRAME_URL_LENGTH));
$iframeURL = rtrim($iframeURL, "/");
@ -386,7 +496,7 @@ class AccountController extends BaseController
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Redirect::to('company/details')
return Redirect::to('settings/' . ACCOUNT_INVOICE_SETTINGS)
->withErrors($validator)
->withInput();
} else {
@ -406,22 +516,39 @@ class AccountController extends BaseController
$account->custom_invoice_text_label1 = trim(Input::get('custom_invoice_text_label1'));
$account->custom_invoice_text_label2 = trim(Input::get('custom_invoice_text_label2'));
$account->invoice_number_prefix = Input::get('invoice_number_prefix');
$account->invoice_number_counter = Input::get('invoice_number_counter');
$account->quote_number_prefix = Input::get('quote_number_prefix');
$account->share_counter = Input::get('share_counter') ? true : false;
$account->pdf_email_attachment = Input::get('pdf_email_attachment') ? true : false;
$account->auto_wrap = Input::get('auto_wrap') ? true : false;
if (Input::has('recurring_hour')) {
$account->recurring_hour = Input::get('recurring_hour');
}
if (!$account->share_counter) {
$account->quote_number_counter = Input::get('quote_number_counter');
}
if (Input::get('invoice_number_type') == 'prefix') {
$account->invoice_number_prefix = trim(Input::get('invoice_number_prefix'));
$account->invoice_number_pattern = null;
} else {
$account->invoice_number_pattern = trim(Input::get('invoice_number_pattern'));
$account->invoice_number_prefix = null;
}
if (Input::get('quote_number_type') == 'prefix') {
$account->quote_number_prefix = trim(Input::get('quote_number_prefix'));
$account->quote_number_pattern = null;
} else {
$account->quote_number_pattern = trim(Input::get('quote_number_pattern'));
$account->quote_number_prefix = null;
}
if (!$account->share_counter && $account->invoice_number_prefix == $account->quote_number_prefix) {
Session::flash('error', trans('texts.invalid_counter'));
return Redirect::to('company/advanced_settings/invoice_settings')->withInput();
return Redirect::to('settings/' . ACCOUNT_INVOICE_SETTINGS)->withInput();
} else {
$account->save();
Session::flash('message', trans('texts.updated_settings'));
@ -429,7 +556,7 @@ class AccountController extends BaseController
}
}
return Redirect::to('company/advanced_settings/invoice_settings');
return Redirect::to('settings/' . ACCOUNT_INVOICE_SETTINGS);
}
private function saveInvoiceDesign()
@ -447,7 +574,7 @@ class AccountController extends BaseController
}
$labels = [];
foreach (['item', 'description', 'unit_cost', 'quantity'] as $field) {
foreach (['item', 'description', 'unit_cost', 'quantity', 'line_total'] as $field) {
$labels[$field] = trim(Input::get("labels_{$field}"));
}
$account->invoice_labels = json_encode($labels);
@ -457,7 +584,7 @@ class AccountController extends BaseController
Session::flash('message', trans('texts.updated_settings'));
}
return Redirect::to('company/advanced_settings/invoice_design');
return Redirect::to('settings/' . ACCOUNT_INVOICE_DESIGN);
}
private function export()
@ -568,7 +695,7 @@ class AccountController extends BaseController
if ($file == null) {
Session::flash('error', trans('texts.select_file'));
return Redirect::to('company/import_export');
return Redirect::to('settings/' . ACCOUNT_IMPORT_EXPORT);
}
$name = $file->getRealPath();
@ -582,7 +709,7 @@ class AccountController extends BaseController
$message = trans('texts.limit_clients', ['count' => Auth::user()->getMaxNumClients()]);
Session::flash('error', $message);
return Redirect::to('company/import_export');
return Redirect::to('settings/' . ACCOUNT_IMPORT_EXPORT);
}
Session::put('data', $csv->data);
@ -680,26 +807,20 @@ class AccountController extends BaseController
Session::flash('message', trans('texts.updated_settings'));
return Redirect::to('company/notifications');
return Redirect::to('settings/' . ACCOUNT_NOTIFICATIONS);
}
private function saveDetails()
{
$rules = array(
'name' => 'required',
'logo' => 'sometimes|max:1024|mimes:jpeg,gif,png',
'logo' => 'sometimes|max:512|mimes:jpeg,gif,png',
);
$user = Auth::user()->account->users()->orderBy('id')->first();
if (Auth::user()->id === $user->id) {
$rules['email'] = 'email|required|unique:users,email,'.$user->id.',id';
}
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Redirect::to('company/details')
return Redirect::to('settings/' . ACCOUNT_COMPANY_DETAILS)
->withErrors($validator)
->withInput();
} else {
@ -717,30 +838,8 @@ class AccountController extends BaseController
$account->country_id = Input::get('country_id') ? Input::get('country_id') : null;
$account->size_id = Input::get('size_id') ? Input::get('size_id') : null;
$account->industry_id = Input::get('industry_id') ? Input::get('industry_id') : null;
$account->timezone_id = Input::get('timezone_id') ? Input::get('timezone_id') : null;
$account->date_format_id = Input::get('date_format_id') ? Input::get('date_format_id') : null;
$account->datetime_format_id = Input::get('datetime_format_id') ? Input::get('datetime_format_id') : null;
$account->currency_id = Input::get('currency_id') ? Input::get('currency_id') : 1; // US Dollar
$account->language_id = Input::get('language_id') ? Input::get('language_id') : 1; // English
$account->military_time = Input::get('military_time') ? true : false;
$account->save();
$user = Auth::user();
$user->first_name = trim(Input::get('first_name'));
$user->last_name = trim(Input::get('last_name'));
$user->username = trim(Input::get('email'));
$user->email = trim(strtolower(Input::get('email')));
$user->phone = trim(Input::get('phone'));
if (Utils::isNinja()) {
if (Input::get('referral_code') && !$user->referral_code) {
$user->referral_code = $this->accountRepo->getReferralCode();
}
}
if (Utils::isNinjaDev()) {
$user->dark_mode = Input::get('dark_mode') ? true : false;
}
$user->save();
/* Logo image file */
if ($file = Input::file('logo')) {
$path = Input::file('logo')->getRealPath();
@ -770,10 +869,61 @@ class AccountController extends BaseController
Event::fire(new UserSettingsChanged());
Session::flash('message', trans('texts.updated_settings'));
return Redirect::to('company/details');
return Redirect::to('settings/' . ACCOUNT_COMPANY_DETAILS);
}
}
private function saveUserDetails()
{
$user = Auth::user();
$rules = ['email' => 'email|required|unique:users,email,'.$user->id.',id'];
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Redirect::to('settings/' . ACCOUNT_USER_DETAILS)
->withErrors($validator)
->withInput();
} else {
$user->first_name = trim(Input::get('first_name'));
$user->last_name = trim(Input::get('last_name'));
$user->username = trim(Input::get('email'));
$user->email = trim(strtolower(Input::get('email')));
$user->phone = trim(Input::get('phone'));
if (Utils::isNinja()) {
if (Input::get('referral_code') && !$user->referral_code) {
$user->referral_code = $this->accountRepo->getReferralCode();
}
}
if (Utils::isNinjaDev()) {
$user->dark_mode = Input::get('dark_mode') ? true : false;
}
$user->save();
Event::fire(new UserSettingsChanged());
Session::flash('message', trans('texts.updated_settings'));
return Redirect::to('settings/' . ACCOUNT_USER_DETAILS);
}
}
private function saveLocalization()
{
$account = Auth::user()->account;
$account->timezone_id = Input::get('timezone_id') ? Input::get('timezone_id') : null;
$account->date_format_id = Input::get('date_format_id') ? Input::get('date_format_id') : null;
$account->datetime_format_id = Input::get('datetime_format_id') ? Input::get('datetime_format_id') : null;
$account->currency_id = Input::get('currency_id') ? Input::get('currency_id') : 1; // US Dollar
$account->language_id = Input::get('language_id') ? Input::get('language_id') : 1; // English
$account->military_time = Input::get('military_time') ? true : false;
$account->save();
Event::fire(new UserSettingsChanged());
Session::flash('message', trans('texts.updated_settings'));
return Redirect::to('settings/' . ACCOUNT_LOCALIZATION);
}
public function removeLogo()
{
File::delete('logo/'.Auth::user()->account->account_key.'.jpg');
@ -781,7 +931,7 @@ class AccountController extends BaseController
Session::flash('message', trans('texts.removed_logo'));
return Redirect::to('company/details');
return Redirect::to('settings/' . ACCOUNT_COMPANY_DETAILS);
}
public function checkEmail()
@ -864,7 +1014,10 @@ class AccountController extends BaseController
$this->userMailer->sendTo(CONTACT_EMAIL, $email, $name, 'Invoice Ninja Feedback [Canceled Account]', 'contact', $data);
}
$user = Auth::user();
$account = Auth::user()->account;
\Log::info("Canceled Account: {$account->name} - {$user->email}");
$this->accountRepo->unlinkAccount($account);
$account->forceDelete();
@ -879,6 +1032,26 @@ class AccountController extends BaseController
$user = Auth::user();
$this->userMailer->sendConfirmation($user);
return Redirect::to('/company/details')->with('message', trans('texts.confirmation_resent'));
return Redirect::to('/settings/' . ACCOUNT_COMPANY_DETAILS)->with('message', trans('texts.confirmation_resent'));
}
public function redirectLegacy($section, $subSection = false)
{
if ($section === 'details') {
$section = ACCOUNT_COMPANY_DETAILS;
} elseif ($section === 'payments') {
$section = ACCOUNT_PAYMENTS;
} elseif ($section === 'advanced_settings') {
$section = $subSection;
if ($section === 'token_management') {
$section = ACCOUNT_API_TOKENS;
}
}
if (!in_array($section, array_merge(Account::$basicSettings, Account::$advancedSettings))) {
$section = ACCOUNT_COMPANY_DETAILS;
}
return Redirect::to("/settings/$section/", 301);
}
}

View File

@ -19,6 +19,11 @@ use App\Ninja\Repositories\AccountRepository;
class AccountGatewayController extends BaseController
{
public function index()
{
return Redirect::to('settings/' . ACCOUNT_PAYMENTS);
}
public function getDatatable()
{
$query = DB::table('account_gateways')
@ -159,7 +164,6 @@ class AccountGatewayController extends BaseController
'gateways' => $gateways,
'creditCardTypes' => $creditCards,
'tokenBillingOptions' => $tokenBillingOptions,
'showBreadcrumbs' => false,
'countGateways' => count($currentGateways)
];
}
@ -173,7 +177,7 @@ class AccountGatewayController extends BaseController
Session::flash('message', trans('texts.deleted_gateway'));
return Redirect::to('company/payments');
return Redirect::to('settings/' . ACCOUNT_PAYMENTS);
}
/**

View File

@ -56,7 +56,7 @@ class AppController extends BaseController
$test = Input::get('test');
$app = Input::get('app');
$app['key'] = str_random(RANDOM_KEY_LENGTH);
$app['key'] = env('APP_KEY') ?: str_random(RANDOM_KEY_LENGTH);
$database = Input::get('database');
$dbType = $database['default'];
@ -94,7 +94,7 @@ class AppController extends BaseController
"MAIL_USERNAME={$mail['username']}\n".
"MAIL_FROM_NAME={$mail['from']['name']}\n".
"MAIL_PASSWORD={$mail['password']}\n\n".
"#PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'";
"PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'";
// Write Config Settings
$fp = fopen(base_path()."/.env", 'w');

View File

@ -61,7 +61,7 @@ class AuthController extends Controller {
$this->accountRepo->unlinkUserFromOauth(Auth::user());
Session::flash('message', trans('texts.updated_settings'));
return redirect()->to('/company/details');
return redirect()->to('/settings/' . ACCOUNT_USER_DETAILS);
}
public function getLoginWrapper()
@ -92,7 +92,7 @@ class AuthController extends Controller {
// 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'));
Session::flash('warning', trans('texts.associated_accounts'));
// check if other accounts are linked
} else {
$users = $this->accountRepo->loadAccounts(Auth::user()->id);

View File

@ -258,6 +258,13 @@ class ClientController extends BaseController
$client->payment_terms = Input::get('payment_terms') ?: 0;
$client->website = trim(Input::get('website'));
if (Input::has('invoice_number_counter')) {
$client->invoice_number_counter = (int) Input::get('invoice_number_counter');
}
if (Input::has('quote_number_counter')) {
$client->invoice_number_counter = (int) Input::get('quote_number_counter');
}
$client->save();
$data = json_decode(Input::get('data'));

View File

@ -59,16 +59,6 @@ class InvoiceApiController extends Controller
$data = Input::all();
$error = null;
// check if the invoice number is set and unique
if (!isset($data['invoice_number']) && !isset($data['id'])) {
$data['invoice_number'] = Auth::user()->account->getNextInvoiceNumber();
} else if (isset($data['invoice_number'])) {
$invoice = Invoice::scope()->where('invoice_number', '=', $data['invoice_number'])->first();
if ($invoice) {
$error = trans('validation.unique', ['attribute' => 'texts.invoice_number']);
}
}
if (isset($data['email'])) {
$client = Client::scope()->whereHas('contacts', function($query) use ($data) {
$query->where('email', '=', $data['email']);
@ -95,6 +85,16 @@ class InvoiceApiController extends Controller
$client = Client::scope($data['client_id'])->first();
}
// check if the invoice number is set and unique
if (!isset($data['invoice_number']) && !isset($data['id'])) {
// do nothing... invoice number will be set automatically
} else if (isset($data['invoice_number'])) {
$invoice = Invoice::scope()->where('invoice_number', '=', $data['invoice_number'])->first();
if ($invoice) {
$error = trans('validation.unique', ['attribute' => 'texts.invoice_number']);
}
}
if (!$error) {
if (!isset($data['client_id']) && !isset($data['email'])) {
$error = trans('validation.', ['attribute' => 'client_id or email']);

View File

@ -31,7 +31,6 @@ use App\Models\Gateway;
use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\TaxRateRepository;
use App\Events\InvoiceViewed;
class InvoiceController extends BaseController
@ -39,16 +38,14 @@ class InvoiceController extends BaseController
protected $mailer;
protected $invoiceRepo;
protected $clientRepo;
protected $taxRateRepo;
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, TaxRateRepository $taxRateRepo)
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo)
{
parent::__construct();
$this->mailer = $mailer;
$this->invoiceRepo = $invoiceRepo;
$this->clientRepo = $clientRepo;
$this->taxRateRepo = $taxRateRepo;
}
public function index()
@ -131,13 +128,22 @@ class InvoiceController extends BaseController
public function view($invitationKey)
{
$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey);
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
return response()->view('error', [
'error' => trans('texts.invoice_not_found'),
'hideHeader' => true,
]);
}
$invoice = $invitation->invoice;
$client = $invoice->client;
$account = $invoice->account;
if (!$account->checkSubdomain(Request::server('HTTP_HOST'))) {
app()->abort(404, trans('texts.invoice_not_found'));
return response()->view('error', [
'error' => trans('texts.invoice_not_found'),
'hideHeader' => true,
]);
}
if (!Input::has('phantomjs') && !Session::has($invitationKey) && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) {
@ -159,9 +165,7 @@ class InvoiceController extends BaseController
} else {
$invoice->invoice_design->javascript = $invoice->invoice_design->pdfmake;
}
$contact = $invitation->contact;
$contact->setVisible([
$contact = $invitation->contact; $contact->setVisible([
'first_name',
'last_name',
'email',
@ -229,6 +233,7 @@ class InvoiceController extends BaseController
public function edit($publicId, $clone = false)
{
$account = Auth::user()->account;
$invoice = Invoice::scope($publicId)->withTrashed()->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items')->firstOrFail();
$entityType = $invoice->getEntityType();
@ -241,7 +246,7 @@ class InvoiceController extends BaseController
if ($clone) {
$invoice->id = null;
$invoice->invoice_number = Auth::user()->account->getNextInvoiceNumber($invoice->is_quote);
$invoice->invoice_number = $account->getNextInvoiceNumber($invoice);
$invoice->balance = $invoice->amount;
$invoice->invoice_status_id = 0;
$invoice->invoice_date = date_create()->format('Y-m-d');
@ -299,7 +304,6 @@ class InvoiceController extends BaseController
'entityType' => $entityType,
'showBreadcrumbs' => $clone,
'invoice' => $invoice,
'data' => false,
'method' => $method,
'invitationContactIds' => $contactIds,
'url' => $url,
@ -327,7 +331,7 @@ class InvoiceController extends BaseController
if ($invitation->contact_id == $contact->id) {
$contact->email_error = $invitation->email_error;
$contact->invitation_link = $invitation->getLink();
$contact->invitation_viewed = $invitation->viewed_date;
$contact->invitation_viewed = $invitation->viewed_date && $invitation->viewed_date != '0000-00-00 00:00:00' ? $invitation->viewed_date : false;
$contact->invitation_status = $contact->email_error ? false : $invitation->getStatus();
}
}
@ -342,23 +346,21 @@ class InvoiceController extends BaseController
public function create($clientPublicId = 0, $isRecurring = false)
{
$client = null;
$invoiceNumber = $isRecurring ? microtime(true) : Auth::user()->account->getNextInvoiceNumber();
$account = Auth::user()->account;
$clientId = null;
if ($clientPublicId) {
$client = Client::scope($clientPublicId)->firstOrFail();
$clientId = Client::getPrivateId($clientPublicId);
}
$entityType = $isRecurring ? ENTITY_RECURRING_INVOICE : ENTITY_INVOICE;
$invoice = $account->createInvoice($entityType, $clientId);
$data = array(
'entityType' => ENTITY_INVOICE,
'invoice' => null,
'data' => Input::old('data'),
'invoiceNumber' => $invoiceNumber,
$data = [
'entityType' => $invoice->getEntityType(),
'invoice' => $invoice,
'method' => 'POST',
'url' => 'invoices',
'title' => trans('texts.new_invoice'),
'isRecurring' => $isRecurring,
'client' => $client);
];
$data = array_merge($data, self::getViewModel());
return View::make('invoices.edit', $data);
@ -383,8 +385,9 @@ class InvoiceController extends BaseController
}
return [
'data' => Input::old('data'),
'account' => Auth::user()->account->load('country'),
'products' => Product::scope()->orderBy('id')->get(array('product_key', 'notes', 'cost', 'qty')),
'products' => Product::scope()->with('default_tax_rate')->orderBy('id')->get(),
'countries' => Cache::get('countries'),
'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(),
'taxRates' => TaxRate::scope()->orderBy('name')->get(),
@ -432,9 +435,8 @@ class InvoiceController extends BaseController
if ($errors = $this->invoiceRepo->getErrors($input->invoice)) {
Session::flash('error', trans('texts.invoice_error'));
return Redirect::to("{$entityType}s/create")
->withInput()->withErrors($errors);
$url = "{$entityType}s/" . ($publicId ?: 'create');
return Redirect::to($url)->withInput()->withErrors($errors);
} else {
$invoice = $this->saveInvoice($publicId, $input, $entityType);
$url = "{$entityType}s/".$invoice->public_id.'/edit';
@ -443,8 +445,8 @@ class InvoiceController extends BaseController
// check if we created a new client with the invoice
if ($input->invoice->client->public_id == '-1') {
$message = $message.' '.trans('texts.and_created_client');
$url = URL::to('clients/'.$input->invoice->client->public_id);
Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT, $url);
$trackUrl = URL::to('clients/'.$invoice->client->public_id);
Utils::trackViewed($invoice->client->getDisplayName(), ENTITY_CLIENT, $trackUrl);
}
if ($action == 'clone') {
@ -468,8 +470,7 @@ class InvoiceController extends BaseController
if (!Auth::user()->confirmed) {
$errorMessage = trans(Auth::user()->registered ? 'texts.confirmation_required' : 'texts.registration_required');
Session::flash('error', $errorMessage);
Session::flash('message', $message);
return Redirect::to($url);
return Redirect::to('invoices/'.$invoice->public_id.'/edit');
}
if ($invoice->is_recurring) {
@ -491,7 +492,13 @@ class InvoiceController extends BaseController
private function emailRecurringInvoice(&$invoice)
{
if (!$invoice->shouldSendToday()) {
return trans('texts.recurring_too_soon');
if ($date = $invoice->getNextSendDate()) {
$date = $invoice->account->formatDate($date);
$date .= ' ' . DEFAULT_SEND_RECURRING_HOUR . ':00 am ' . $invoice->account->getTimezone();
return trans('texts.recurring_too_soon', ['date' => $date]);
} else {
return trans('texts.no_longer_running');
}
}
// switch from the recurring invoice to the generated invoice
@ -509,8 +516,6 @@ class InvoiceController extends BaseController
{
$invoice = $input->invoice;
$this->taxRateRepo->save($input->tax_rates);
$clientData = (array) $invoice->client;
$client = $this->clientRepo->save($invoice->client->public_id, $clientData);
@ -518,18 +523,6 @@ class InvoiceController extends BaseController
$invoiceData['client_id'] = $client->id;
$invoice = $this->invoiceRepo->save($publicId, $invoiceData, $entityType);
$account = Auth::user()->account;
if ($account->invoice_taxes != $input->invoice_taxes
|| $account->invoice_item_taxes != $input->invoice_item_taxes
|| $account->invoice_design_id != $input->invoice->invoice_design_id
|| $account->show_item_taxes != $input->show_item_taxes) {
$account->invoice_taxes = $input->invoice_taxes;
$account->invoice_item_taxes = $input->invoice_item_taxes;
$account->invoice_design_id = $input->invoice->invoice_design_id;
$account->show_item_taxes = $input->show_item_taxes;
$account->save();
}
$client->load('contacts');
$sendInvoiceIds = [];

View File

@ -174,7 +174,8 @@ class PaymentController extends BaseController
$acceptedCreditCardTypes = $accountGateway->getCreditcardTypes();
// Handle offsite payments
if ($useToken || $paymentType != PAYMENT_TYPE_CREDIT_CARD || $gateway->id == GATEWAY_EWAY) {
if ($useToken || $paymentType != PAYMENT_TYPE_CREDIT_CARD
|| $gateway->id == GATEWAY_EWAY || $gateway->id == GATEWAY_TWO_CHECKOUT) {
if (Session::has('error')) {
Session::reflash();
return Redirect::to('view/'.$invitationKey);
@ -418,7 +419,7 @@ class PaymentController extends BaseController
}
$gateway = $this->paymentService->createGateway($accountGateway);
$details = $this->paymentService->getPaymentDetails($invitation, $data);
$details = $this->paymentService->getPaymentDetails($invitation, $accountGateway, $data);
// check if we're creating/using a billing token
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
@ -439,7 +440,8 @@ class PaymentController extends BaseController
if ($accountGateway->gateway_id == GATEWAY_EWAY) {
$ref = $response->getData()['AccessCode'];
$token = $response->getCardReference();
} elseif ($accountGateway->gateway_id == GATEWAY_TWO_CHECKOUT) {
$ref = $response->getData()['cart_order_id'];
} else {
$ref = $response->getTransactionReference();
}
@ -467,7 +469,6 @@ class PaymentController extends BaseController
} elseif ($response->isRedirect()) {
$invitation->transaction_reference = $ref;
$invitation->save();
Session::put('transaction_reference', $ref);
Session::save();
$response->redirect();
@ -513,8 +514,8 @@ class PaymentController extends BaseController
}
try {
if (method_exists($gateway, 'completePurchase')) {
$details = $this->paymentService->getPaymentDetails($invitation);
if (method_exists($gateway, 'completePurchase') && !$accountGateway->isGateway(GATEWAY_TWO_CHECKOUT)) {
$details = $this->paymentService->getPaymentDetails($invitation, $accountGateway);
$response = $gateway->completePurchase($details)->send();
$ref = $response->getTransactionReference();

View File

@ -12,21 +12,38 @@ use Session;
use Redirect;
use App\Models\Product;
use App\Models\TaxRate;
class ProductController extends BaseController
{
public function index()
{
return Redirect::to('settings/' . ACCOUNT_PRODUCTS);
}
public function getDatatable()
{
$account = Auth::user()->account;
$query = DB::table('products')
->leftJoin('tax_rates', function($join){
$join->on('tax_rates.id', '=', 'products.default_tax_rate_id')
->whereNull('tax_rates.deleted_at');
})
->where('products.account_id', '=', Auth::user()->account_id)
->where('products.deleted_at', '=', null)
->select('products.public_id', 'products.product_key', 'products.notes', 'products.cost');
->select('products.public_id', 'products.product_key', 'products.notes', 'products.cost', 'tax_rates.name as tax_name', 'tax_rates.rate as tax_rate');
return Datatable::query($query)
$datatable = Datatable::query($query)
->addColumn('product_key', function ($model) { return link_to('products/'.$model->public_id.'/edit', $model->product_key); })
->addColumn('notes', function ($model) { return nl2br(Str::limit($model->notes, 100)); })
->addColumn('cost', function ($model) { return Utils::formatMoney($model->cost); })
->addColumn('dropdown', function ($model) {
->addColumn('cost', function ($model) { return Utils::formatMoney($model->cost); });
if ($account->invoice_item_taxes) {
$datatable->addColumn('tax_rate', function ($model) { return $model->tax_rate ? ($model->tax_name . ' ' . $model->tax_rate . '%') : ''; });
}
return $datatable->addColumn('dropdown', function ($model) {
return '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span>
@ -44,8 +61,11 @@ class ProductController extends BaseController
public function edit($publicId)
{
$account = Auth::user()->account;
$data = [
'showBreadcrumbs' => false,
'account' => $account,
'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->get(['id', 'name', 'rate']) : null,
'product' => Product::scope($publicId)->firstOrFail(),
'method' => 'PUT',
'url' => 'products/'.$publicId,
@ -57,8 +77,11 @@ class ProductController extends BaseController
public function create()
{
$account = Auth::user()->account;
$data = [
'showBreadcrumbs' => false,
'account' => $account,
'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->get(['id', 'name', 'rate']) : null,
'product' => null,
'method' => 'POST',
'url' => 'products',
@ -89,12 +112,14 @@ class ProductController extends BaseController
$product->product_key = trim(Input::get('product_key'));
$product->notes = trim(Input::get('notes'));
$product->cost = trim(Input::get('cost'));
$product->default_tax_rate_id = Input::get('default_tax_rate_id');
$product->save();
$message = $productPublicId ? trans('texts.updated_product') : trans('texts.created_product');
Session::flash('message', $message);
return Redirect::to('company/products');
return Redirect::to('settings/' . ACCOUNT_PRODUCTS);
}
public function archive($publicId)
@ -104,6 +129,6 @@ class ProductController extends BaseController
Session::flash('message', trans('texts.archived_product'));
return Redirect::to('company/products');
return Redirect::to('settings/' . ACCOUNT_PRODUCTS);
}
}

View File

@ -22,7 +22,9 @@ class PublicClientController extends BaseController
public function dashboard()
{
$invitation = $this->getInvitation();
if (!$invitation = $this->getInvitation()) {
return $this->returnError();
}
$account = $invitation->account;
$invoice = $invitation->invoice;
$client = $invoice->client;
@ -32,6 +34,7 @@ class PublicClientController extends BaseController
'color' => $color,
'account' => $account,
'client' => $client,
'hideLogo' => $account->isWhiteLabel(),
];
return response()->view('invited.dashboard', $data);
@ -39,7 +42,9 @@ class PublicClientController extends BaseController
public function activityDatatable()
{
$invitation = $this->getInvitation();
if (!$invitation = $this->getInvitation()) {
return false;
}
$invoice = $invitation->invoice;
$query = DB::table('activities')
@ -58,7 +63,9 @@ class PublicClientController extends BaseController
public function invoiceIndex()
{
$invitation = $this->getInvitation();
if (!$invitation = $this->getInvitation()) {
return $this->returnError();
}
$account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
@ -75,7 +82,9 @@ class PublicClientController extends BaseController
public function invoiceDatatable()
{
$invitation = $this->getInvitation();
if (!$invitation = $this->getInvitation()) {
return false;
}
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_INVOICE, Input::get('sSearch'));
}
@ -83,7 +92,9 @@ class PublicClientController extends BaseController
public function paymentIndex()
{
$invitation = $this->getInvitation();
if (!$invitation = $this->getInvitation()) {
return $this->returnError();
}
$account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
@ -100,7 +111,9 @@ class PublicClientController extends BaseController
public function paymentDatatable()
{
$invitation = $this->getInvitation();
if (!$invitation = $this->getInvitation()) {
return false;
}
$payments = $this->paymentRepo->findForContact($invitation->contact->id, Input::get('sSearch'));
return Datatable::query($payments)
@ -114,7 +127,9 @@ class PublicClientController extends BaseController
public function quoteIndex()
{
$invitation = $this->getInvitation();
if (!$invitation = $this->getInvitation()) {
return $this->returnError();
}
$account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
@ -132,29 +147,39 @@ class PublicClientController extends BaseController
public function quoteDatatable()
{
$invitation = $this->getInvitation();
if (!$invitation = $this->getInvitation()) {
return false;
}
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_QUOTE, Input::get('sSearch'));
}
private function returnError()
{
return response()->view('error', [
'error' => trans('texts.invoice_not_found'),
'hideHeader' => true,
]);
}
private function getInvitation()
{
$invitationKey = session('invitation_key');
if (!$invitationKey) {
app()->abort(404);
return false;
}
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->first();
if (!$invitation || $invitation->is_deleted) {
app()->abort(404);
return false;
}
$invoice = $invitation->invoice;
if (!$invoice || $invoice->is_deleted) {
app()->abort(404);
return false;
}
return $invitation;

View File

@ -81,23 +81,21 @@ class QuoteController extends BaseController
return Redirect::to('/invoices/create');
}
$client = null;
$invoiceNumber = Auth::user()->account->getNextInvoiceNumber(true);
$account = Account::with('country')->findOrFail(Auth::user()->account_id);
$account = Auth::user()->account;
$clientId = null;
if ($clientPublicId) {
$client = Client::scope($clientPublicId)->firstOrFail();
$clientId = Client::getPrivateId($clientPublicId);
}
$invoice = $account->createInvoice(ENTITY_QUOTE, $clientId);
$data = array(
'account' => $account,
'invoice' => null,
$data = [
'entityType' => $invoice->getEntityType(),
'invoice' => $invoice,
'data' => Input::old('data'),
'invoiceNumber' => $invoiceNumber,
'method' => 'POST',
'url' => 'invoices',
'title' => trans('texts.new_quote'),
'client' => $client, );
];
$data = array_merge($data, self::getViewModel());
return View::make('invoices.edit', $data);

View File

@ -33,7 +33,6 @@ class ReportController extends BaseController
}
$data = [
'feature' => ACCOUNT_DATA_VISUALIZATIONS,
'clients' => $clients,
'message' => $message,
];
@ -276,7 +275,6 @@ class ReportController extends BaseController
'startDate' => $startDate->format(Session::get(SESSION_DATE_FORMAT)),
'endDate' => $endDate->format(Session::get(SESSION_DATE_FORMAT)),
'groupBy' => $groupBy,
'feature' => ACCOUNT_CHART_BUILDER,
'displayData' => $displayData,
'columns' => $columns,
'reportTotals' => $reportTotals,

View File

@ -0,0 +1,110 @@
<?php namespace App\Http\Controllers;
use Auth;
use Str;
use DB;
use Datatable;
use Utils;
use URL;
use View;
use Input;
use Session;
use Redirect;
use App\Models\TaxRate;
class TaxRateController extends BaseController
{
public function index()
{
return Redirect::to('settings/' . ACCOUNT_TAX_RATES);
}
public function getDatatable()
{
$query = DB::table('tax_rates')
->where('tax_rates.account_id', '=', Auth::user()->account_id)
->where('tax_rates.deleted_at', '=', null)
->select('tax_rates.public_id', 'tax_rates.name', 'tax_rates.rate');
return Datatable::query($query)
->addColumn('name', function ($model) { return link_to('tax_rates/'.$model->public_id.'/edit', $model->name); })
->addColumn('rate', function ($model) { return $model->rate . '%'; })
->addColumn('dropdown', function ($model) {
return '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="'.URL::to('tax_rates/'.$model->public_id).'/edit">'.uctrans('texts.edit_tax_rate').'</a></li>
<li class="divider"></li>
<li><a href="'.URL::to('tax_rates/'.$model->public_id).'/archive">'.uctrans('texts.archive_tax_rate').'</a></li>
</ul>
</div>';
})
->orderColumns(['name', 'rate'])
->make();
}
public function edit($publicId)
{
$data = [
'taxRate' => TaxRate::scope($publicId)->firstOrFail(),
'method' => 'PUT',
'url' => 'tax_rates/'.$publicId,
'title' => trans('texts.edit_tax_rate'),
];
return View::make('accounts.tax_rate', $data);
}
public function create()
{
$data = [
'taxRate' => null,
'method' => 'POST',
'url' => 'tax_rates',
'title' => trans('texts.create_tax_rate'),
];
return View::make('accounts.tax_rate', $data);
}
public function store()
{
return $this->save();
}
public function update($publicId)
{
return $this->save($publicId);
}
private function save($publicId = false)
{
if ($publicId) {
$taxRate = TaxRate::scope($publicId)->firstOrFail();
} else {
$taxRate = TaxRate::createNew();
}
$taxRate->name = trim(Input::get('name'));
$taxRate->rate = Utils::parseFloat(Input::get('rate'));
$taxRate->save();
$message = $publicId ? trans('texts.updated_tax_rate') : trans('texts.created_tax_rate');
Session::flash('message', $message);
return Redirect::to('settings/' . ACCOUNT_TAX_RATES);
}
public function archive($publicId)
{
$tax_rate = TaxRate::scope($publicId)->firstOrFail();
$tax_rate->delete();
Session::flash('message', trans('texts.archived_tax_rate'));
return Redirect::to('settings/' . ACCOUNT_TAX_RATES);
}
}

View File

@ -25,6 +25,11 @@ use App\Ninja\Repositories\AccountRepository;
class TokenController extends BaseController
{
public function index()
{
return Redirect::to('settings/' . ACCOUNT_API_TOKENS);
}
public function getDatatable()
{
$query = DB::table('account_tokens')
@ -67,7 +72,6 @@ class TokenController extends BaseController
->where('public_id', '=', $publicId)->firstOrFail();
$data = [
'showBreadcrumbs' => false,
'token' => $token,
'method' => 'PUT',
'url' => 'tokens/'.$publicId,
@ -94,12 +98,10 @@ class TokenController extends BaseController
public function create()
{
$data = [
'showBreadcrumbs' => false,
'token' => null,
'method' => 'POST',
'url' => 'tokens',
'title' => trans('texts.add_token'),
'feature' => 'tokens',
];
return View::make('accounts.token', $data);
@ -115,7 +117,7 @@ class TokenController extends BaseController
Session::flash('message', trans('texts.deleted_token'));
return Redirect::to('company/advanced_settings/token_management');
return Redirect::to('settings/' . ACCOUNT_API_TOKENS);
}
/**
@ -163,7 +165,7 @@ class TokenController extends BaseController
Session::flash('message', $message);
}
return Redirect::to('company/advanced_settings/token_management');
return Redirect::to('settings/' . ACCOUNT_API_TOKENS);
}
}

View File

@ -35,6 +35,11 @@ class UserController extends BaseController
$this->userMailer = $userMailer;
}
public function index()
{
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
}
public function getDatatable()
{
$query = DB::table('users')
@ -106,7 +111,6 @@ class UserController extends BaseController
->where('public_id', '=', $publicId)->firstOrFail();
$data = [
'showBreadcrumbs' => false,
'user' => $user,
'method' => 'PUT',
'url' => 'users/'.$publicId,
@ -134,24 +138,22 @@ class UserController extends BaseController
{
if (!Auth::user()->registered) {
Session::flash('error', trans('texts.register_to_add_user'));
return Redirect::to('company/advanced_settings/user_management');
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
}
if (!Auth::user()->confirmed) {
Session::flash('error', trans('texts.confirmation_required'));
return Redirect::to('company/advanced_settings/user_management');
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
}
if (Utils::isNinja()) {
$count = User::where('account_id', '=', Auth::user()->account_id)->count();
if ($count >= MAX_NUM_USERS) {
Session::flash('error', trans('texts.limit_users'));
return Redirect::to('company/advanced_settings/user_management');
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
}
}
$data = [
'showBreadcrumbs' => false,
'user' => null,
'method' => 'POST',
'url' => 'users',
@ -171,7 +173,7 @@ class UserController extends BaseController
Session::flash('message', trans('texts.deleted_user'));
return Redirect::to('company/advanced_settings/user_management');
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
}
public function restoreUser($userPublicId)
@ -184,7 +186,7 @@ class UserController extends BaseController
Session::flash('message', trans('texts.restored_user'));
return Redirect::to('company/advanced_settings/user_management');
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
}
/**
@ -247,7 +249,7 @@ class UserController extends BaseController
Session::flash('message', $message);
}
return Redirect::to('company/advanced_settings/user_management');
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
}
public function sendConfirmation($userPublicId)
@ -258,7 +260,7 @@ class UserController extends BaseController
$this->userMailer->sendConfirmation($user, Auth::user());
Session::flash('message', trans('texts.sent_invite'));
return Redirect::to('company/advanced_settings/user_management');
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
}

View File

@ -27,11 +27,11 @@ class StartupCheck
{
// Set up trusted X-Forwarded-Proto proxies
// TRUSTED_PROXIES accepts a comma delimited list of subnets
//
// TRUSTED_PROXIES='10.0.0.0/8,172.16.0.0/12,192.168.0.0/16'
// ie, TRUSTED_PROXIES='10.0.0.0/8,172.16.0.0/12,192.168.0.0/16'
if (isset($_ENV['TRUSTED_PROXIES'])) {
Request::setTrustedProxies(array_map('trim',explode(",",env('TRUSTED_PROXIES'))));
Request::setTrustedProxies(array_map('trim', explode(',', env('TRUSTED_PROXIES'))));
}
// Ensure all request are over HTTPS in production
if (App::environment() == ENV_PRODUCTION && !Request::secure()) {
return Redirect::secure(Request::getRequestUri());

View File

@ -21,10 +21,10 @@
//Log::error('test');
// Application setup
Route::get('setup', 'AppController@showSetup');
Route::post('setup', 'AppController@doSetup');
Route::get('install', 'AppController@install');
Route::get('update', 'AppController@update');
Route::get('/setup', 'AppController@showSetup');
Route::post('/setup', 'AppController@doSetup');
Route::get('/install', 'AppController@install');
Route::get('/update', 'AppController@update');
/*
// Codeception code coverage
@ -35,11 +35,11 @@ Route::get('/c3.php', function () {
// Public pages
Route::get('/', 'HomeController@showIndex');
Route::get('terms', 'HomeController@showTerms');
Route::get('log_error', 'HomeController@logError');
Route::get('invoice_now', 'HomeController@invoiceNow');
Route::get('keep_alive', 'HomeController@keepAlive');
Route::post('get_started', 'AccountController@getStarted');
Route::get('/terms', 'HomeController@showTerms');
Route::get('/log_error', 'HomeController@logError');
Route::get('/invoice_now', 'HomeController@invoiceNow');
Route::get('/keep_alive', 'HomeController@keepAlive');
Route::post('/get_started', 'AccountController@getStarted');
// Client visible pages
Route::get('view/{invitation_key}', 'InvoiceController@view');
@ -64,21 +64,14 @@ Route::get('claim_license', 'PaymentController@claim_license');
Route::post('signup/validate', 'AccountController@checkEmail');
Route::post('signup/submit', 'AccountController@submitSignup');
Route::get('auth/{provider}', 'Auth\AuthController@authLogin');
Route::get('auth_unlink', 'Auth\AuthController@authUnlink');
Route::get('/auth/{provider}', 'Auth\AuthController@authLogin');
Route::get('/auth_unlink', 'Auth\AuthController@authUnlink');
Route::post('hook/email_bounced', 'AppController@emailBounced');
Route::post('hook/email_opened', 'AppController@emailOpened');
Route::post('/hook/email_bounced', 'AppController@emailBounced');
Route::post('/hook/email_opened', 'AppController@emailOpened');
// Laravel auth routes
/*
Route::controllers([
'auth' => 'Auth\AuthController',
'password' => 'Auth\PasswordController',
]);
*/
get('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@getRegister'));
post('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@postRegister'));
get('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@getLoginWrapper'));
@ -121,14 +114,20 @@ Route::group(['middleware' => 'auth'], function() {
Route::resource('products', 'ProductController');
Route::get('products/{product_id}/archive', 'ProductController@archive');
Route::get('company/advanced_settings/data_visualizations', 'ReportController@d3');
Route::get('company/advanced_settings/charts_and_reports', 'ReportController@showReports');
Route::post('company/advanced_settings/charts_and_reports', 'ReportController@showReports');
Route::get('api/tax_rates', array('as'=>'api.tax_rates', 'uses'=>'TaxRateController@getDatatable'));
Route::resource('tax_rates', 'TaxRateController');
Route::get('tax_rates/{tax_rates_id}/archive', 'TaxRateController@archive');
Route::get('company/{section}/{subSection?}', 'AccountController@redirectLegacy');
Route::get('settings/data_visualizations', 'ReportController@d3');
Route::get('settings/charts_and_reports', 'ReportController@showReports');
Route::post('settings/charts_and_reports', 'ReportController@showReports');
Route::post('settings/cancel_account', 'AccountController@cancelAccount');
Route::get('settings/{section?}', 'AccountController@showSection');
Route::post('settings/{section?}', 'AccountController@doSection');
Route::post('company/cancel_account', 'AccountController@cancelAccount');
Route::get('account/getSearchData', array('as' => 'getSearchData', 'uses' => 'AccountController@getSearchData'));
Route::get('company/{section?}/{sub_section?}', 'AccountController@showSection');
Route::post('company/{section?}/{sub_section?}', 'AccountController@doSection');
Route::post('user/setTheme', 'UserController@setTheme');
Route::post('remove_logo', 'AccountController@removeLogo');
Route::post('account/go_pro', 'AccountController@enableProPlan');
@ -183,7 +182,6 @@ Route::group(['middleware' => 'auth'], function() {
Route::post('credits/bulk', 'CreditController@bulk');
get('/resend_confirmation', 'AccountController@resendConfirmation');
//Route::resource('timesheets', 'TimesheetController');
});
// Route group for API
@ -253,21 +251,27 @@ if (!defined('CONTACT_EMAIL')) {
define('PERSON_CONTACT', 'contact');
define('PERSON_USER', 'user');
define('ACCOUNT_DETAILS', 'details');
define('BASIC_SETTINGS', 'basic_settings');
define('ADVANCED_SETTINGS', 'advanced_settings');
define('ACCOUNT_COMPANY_DETAILS', 'company_details');
define('ACCOUNT_USER_DETAILS', 'user_details');
define('ACCOUNT_LOCALIZATION', 'localization');
define('ACCOUNT_NOTIFICATIONS', 'notifications');
define('ACCOUNT_IMPORT_EXPORT', 'import_export');
define('ACCOUNT_PAYMENTS', 'payments');
define('ACCOUNT_PAYMENTS', 'online_payments');
define('ACCOUNT_MAP', 'import_map');
define('ACCOUNT_EXPORT', 'export');
define('ACCOUNT_TAX_RATES', 'tax_rates');
define('ACCOUNT_PRODUCTS', 'products');
define('ACCOUNT_ADVANCED_SETTINGS', 'advanced_settings');
define('ACCOUNT_INVOICE_SETTINGS', 'invoice_settings');
define('ACCOUNT_INVOICE_DESIGN', 'invoice_design');
define('ACCOUNT_CHART_BUILDER', 'chart_builder');
define('ACCOUNT_CHARTS_AND_REPORTS', 'charts_and_reports');
define('ACCOUNT_USER_MANAGEMENT', 'user_management');
define('ACCOUNT_DATA_VISUALIZATIONS', 'data_visualizations');
define('ACCOUNT_TEMPLATES_AND_REMINDERS', 'templates_and_reminders');
define('ACCOUNT_TOKEN_MANAGEMENT', 'token_management');
define('ACCOUNT_API_TOKENS', 'api_tokens');
define('ACCOUNT_CUSTOMIZE_DESIGN', 'customize_design');
@ -310,12 +314,16 @@ if (!defined('CONTACT_EMAIL')) {
define('RECENTLY_VIEWED_LIMIT', 8);
define('LOGGED_ERROR_LIMIT', 100);
define('RANDOM_KEY_LENGTH', 32);
define('MAX_NUM_CLIENTS', 500);
define('MAX_NUM_CLIENTS_PRO', 20000);
define('MAX_NUM_USERS', 20);
define('MAX_SUBDOMAIN_LENGTH', 30);
define('MAX_IFRAME_URL_LENGTH', 250);
define('DEFAULT_FONT_SIZE', 9);
define('DEFAULT_SEND_RECURRING_HOUR', 8);
define('MAX_NUM_CLIENTS', 100);
define('MAX_NUM_CLIENTS_PRO', 20000);
define('MAX_NUM_CLIENTS_LEGACY', 500);
define('LEGACY_CUTOFF', 57800);
define('INVOICE_STATUS_DRAFT', 1);
define('INVOICE_STATUS_SENT', 2);
@ -391,16 +399,16 @@ if (!defined('CONTACT_EMAIL')) {
define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG');
define('NINJA_WEB_URL', 'https://www.invoiceninja.com');
define('NINJA_APP_URL', 'https://app.invoiceninja.com');
define('NINJA_VERSION', '2.4.2');
define('NINJA_VERSION', '2.4.3');
define('NINJA_DATE', '2000-01-01');
define('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com');
define('RELEASES_URL', 'https://github.com/hillelcoren/invoice-ninja/releases/');
define('RELEASES_URL', 'https://trello.com/b/63BbiVVe/invoice-ninja');
define('ZAPIER_URL', 'https://zapier.com/zapbook/invoice-ninja');
define('OUTDATE_BROWSER_URL', 'http://browsehappy.com/');
define('PDFMAKE_DOCS', 'http://pdfmake.org/playground.html');
define('PHANTOMJS_CLOUD', 'http://api.phantomjscloud.com/single/browser/v1/');
define('GITHUB_RELEASES', 'https://github.com/hillelcoren/invoice-ninja/releases');
define('PHP_DATE_FORMATS', 'http://php.net/manual/en/function.date.php');
define('REFERRAL_PROGRAM_URL', false);
define('COUNT_FREE_DESIGNS', 4);

View File

@ -259,6 +259,10 @@ class Utils
public static function dateToString($date)
{
if (!$date) {
return false;
}
$dateTime = new DateTime($date);
$timestamp = $dateTime->getTimestamp();
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);

View File

@ -15,6 +15,27 @@ class Account extends Eloquent
protected $dates = ['deleted_at'];
protected $hidden = ['ip'];
public static $basicSettings = [
ACCOUNT_COMPANY_DETAILS,
ACCOUNT_USER_DETAILS,
ACCOUNT_LOCALIZATION,
ACCOUNT_PAYMENTS,
ACCOUNT_TAX_RATES,
ACCOUNT_PRODUCTS,
ACCOUNT_NOTIFICATIONS,
ACCOUNT_IMPORT_EXPORT,
];
public static $advancedSettings = [
ACCOUNT_INVOICE_SETTINGS,
ACCOUNT_INVOICE_DESIGN,
ACCOUNT_TEMPLATES_AND_REMINDERS,
ACCOUNT_CHARTS_AND_REPORTS,
ACCOUNT_DATA_VISUALIZATIONS,
ACCOUNT_USER_MANAGEMENT,
ACCOUNT_API_TOKENS,
];
/*
protected $casts = [
'invoice_settings' => 'object',
@ -86,6 +107,11 @@ class Account extends Eloquent
return $this->belongsTo('App\Models\Industry');
}
public function default_tax_rate()
{
return $this->belongsTo('App\Models\TaxRate');
}
public function isGatewayConfigured($gatewayId = 0)
{
$this->load('account_gateways');
@ -140,6 +166,25 @@ class Account extends Eloquent
}
}
public function getDateTime($date = 'now')
{
return new \DateTime($date, new \DateTimeZone($this->getTimezone()));
}
public function getCustomDateFormat()
{
return $this->date_format ? $this->date_format->format : DEFAULT_DATE_FORMAT;
}
public function formatDate($date)
{
if (!$date) {
return null;
}
return $date->format($this->getCustomDateFormat());
}
public function getGatewayByType($type = PAYMENT_TYPE_ANY)
{
foreach ($this->account_gateways as $gateway) {
@ -198,10 +243,131 @@ class Account extends Eloquent
return $height;
}
public function getNextInvoiceNumber($isQuote = false, $prefix = '')
public function createInvoice($entityType, $clientId = null)
{
$counter = $isQuote && !$this->share_counter ? $this->quote_number_counter : $this->invoice_number_counter;
$prefix .= $isQuote ? $this->quote_number_prefix : $this->invoice_number_prefix;
$invoice = Invoice::createNew();
$invoice->invoice_date = Utils::today();
$invoice->start_date = Utils::today();
$invoice->invoice_design_id = $this->invoice_design_id;
$invoice->client_id = $clientId;
if ($entityType === ENTITY_RECURRING_INVOICE) {
$invoice->invoice_number = microtime(true);
$invoice->is_recurring = true;
} else {
if ($entityType == ENTITY_QUOTE) {
$invoice->is_quote = true;
}
if ($this->hasClientNumberPattern($invoice) && !$clientId) {
// do nothing, we don't yet know the value
} else {
$invoice->invoice_number = $this->getNextInvoiceNumber($invoice);
}
}
if (!$clientId) {
$invoice->client = Client::createNew();
$invoice->client->public_id = 0;
}
return $invoice;
}
public function hasNumberPattern($isQuote)
{
return $isQuote ? ($this->quote_number_pattern ? true : false) : ($this->invoice_number_pattern ? true : false);
}
public function hasClientNumberPattern($invoice)
{
$pattern = $invoice->is_quote ? $this->quote_number_pattern : $this->invoice_number_pattern;
return strstr($pattern, '$custom');
}
public function getNumberPattern($invoice)
{
$pattern = $invoice->is_quote ? $this->quote_number_pattern : $this->invoice_number_pattern;
if (!$pattern) {
return false;
}
$search = ['{$year}'];
$replace = [date('Y')];
$search[] = '{$counter}';
$replace[] = str_pad($this->getCounter($invoice->is_quote), 4, '0', STR_PAD_LEFT);
if (strstr($pattern, '{$userId}')) {
$search[] = '{$userId}';
$replace[] = str_pad($invoice->user->public_id, 2, '0', STR_PAD_LEFT);
}
$matches = false;
preg_match('/{\$date:(.*?)}/', $pattern, $matches);
if (count($matches) > 1) {
$format = $matches[1];
$search[] = $matches[0];
$replace[] = str_replace($format, date($format), $matches[1]);
}
$pattern = str_replace($search, $replace, $pattern);
if ($invoice->client->id) {
$pattern = $this->getClientInvoiceNumber($pattern, $invoice);
}
return $pattern;
}
private function getClientInvoiceNumber($pattern, $invoice)
{
if (!$invoice->client) {
return $pattern;
}
$search = [
//'{$clientId}',
'{$custom1}',
'{$custom2}',
];
$replace = [
//str_pad($client->public_id, 3, '0', STR_PAD_LEFT),
$invoice->client->custom_value1,
$invoice->client->custom_value2,
];
return str_replace($search, $replace, $pattern);
}
// if we're using a pattern we don't know the next number until a client
// is selected, to support this the default value is blank
public function getDefaultInvoiceNumber($invoice = false)
{
if ($this->hasClientNumberPattern($invoice)) {
return false;
}
return $this->getNextInvoiceNumber($invoice);
}
public function getCounter($isQuote)
{
return $isQuote && !$this->share_counter ? $this->quote_number_counter : $this->invoice_number_counter;
}
public function getNextInvoiceNumber($invoice)
{
if ($this->hasNumberPattern($invoice->is_quote)) {
return $this->getNumberPattern($invoice);
}
$counter = $this->getCounter($invoice->is_quote);
$prefix = $invoice->is_quote ? $this->quote_number_prefix : $this->invoice_number_prefix;
$counterOffset = 0;
// confirm the invoice number isn't already taken
@ -214,7 +380,7 @@ class Account extends Eloquent
// update the invoice counter to be caught up
if ($counterOffset > 1) {
if ($isQuote && !$this->share_counter) {
if ($invoice->is_quote && !$this->share_counter) {
$this->quote_number_counter += $counterOffset - 1;
} else {
$this->invoice_number_counter += $counterOffset - 1;
@ -226,9 +392,9 @@ class Account extends Eloquent
return $number;
}
public function incrementCounter($isQuote = false)
public function incrementCounter($invoice)
{
if ($isQuote && !$this->share_counter) {
if ($invoice->is_quote && !$this->share_counter) {
$this->quote_number_counter += 1;
} else {
$this->invoice_number_counter += 1;
@ -237,18 +403,13 @@ class Account extends Eloquent
$this->save();
}
public function getLocale()
{
$language = Language::where('id', '=', $this->account->language_id)->first();
return $language->locale;
}
public function loadLocalizationSettings($client = false)
{
$this->load('timezone', 'date_format', 'datetime_format', 'language');
Session::put(SESSION_TIMEZONE, $this->timezone ? $this->timezone->name : DEFAULT_TIMEZONE);
$timezone = $this->timezone ? $this->timezone->name : DEFAULT_TIMEZONE;
Session::put(SESSION_TIMEZONE, $timezone);
Session::put(SESSION_DATE_FORMAT, $this->date_format ? $this->date_format->format : DEFAULT_DATE_FORMAT);
Session::put(SESSION_DATE_PICKER_FORMAT, $this->date_format ? $this->date_format->picker_format : DEFAULT_DATE_PICKER_FORMAT);

View File

@ -25,6 +25,11 @@ class Client extends EntityModel
return $this->belongsTo('App\Models\Account');
}
public function user()
{
return $this->belongsTo('App\Models\User');
}
public function invoices()
{
return $this->hasMany('App\Models\Invoice');
@ -168,6 +173,11 @@ class Client extends EntityModel
return $this->account->currency_id ?: DEFAULT_CURRENCY;
}
public function getCounter($isQuote)
{
return $isQuote ? $this->quote_number_counter : $this->invoice_number_counter;
}
}
/*

View File

@ -9,14 +9,14 @@ class EntityModel extends Eloquent
public $timestamps = true;
protected $hidden = ['id'];
public static function createNew($parent = false)
public static function createNew($context = null)
{
$className = get_called_class();
$entity = new $className();
if ($parent) {
$entity->user_id = $parent instanceof User ? $parent->id : $parent->user_id;
$entity->account_id = $parent->account_id;
if ($context) {
$entity->user_id = $context instanceof User ? $context->id : $context->user_id;
$entity->account_id = $context->account_id;
} elseif (Auth::check()) {
$entity->user_id = Auth::user()->id;
$entity->account_id = Auth::user()->account_id;

View File

@ -57,7 +57,7 @@ class Invitation extends EntityModel
foreach ($statuses as $status) {
$field = "{$status}_date";
$date = '';
if ($this->$field) {
if ($this->$field && $this->field != '0000-00-00 00:00:00') {
$date = Utils::dateToString($this->$field);
$hasValue = true;
}

View File

@ -6,7 +6,10 @@ use Illuminate\Database\Eloquent\SoftDeletes;
class Invoice extends EntityModel
{
use SoftDeletes;
use SoftDeletes {
SoftDeletes::trashed as parentTrashed;
}
protected $dates = ['deleted_at'];
protected $casts = [
@ -15,6 +18,24 @@ class Invoice extends EntityModel
'auto_bill' => 'boolean',
];
public static $patternFields = [
'counter',
'custom1',
'custom2',
'userId',
'year',
'date:',
];
public function trashed()
{
if ($this->client && $this->client->trashed()) {
return true;
}
return self::parentTrashed();
}
public function account()
{
return $this->belongsTo('App\Models\Account');
@ -216,6 +237,117 @@ class Invoice extends EntityModel
return $this;
}
public function getSchedule()
{
if (!$this->start_date || !$this->is_recurring || !$this->frequency_id) {
return false;
}
$startDate = $this->getOriginal('last_sent_date') ?: $this->getOriginal('start_date');
$startDate .= ' ' . $this->account->recurring_hour . ':00:00';
$startDate = $this->account->getDateTime($startDate);
$endDate = $this->end_date ? $this->account->getDateTime($this->getOriginal('end_date')) : null;
$timezone = $this->account->getTimezone();
$rule = $this->getRecurrenceRule();
$rule = new \Recurr\Rule("{$rule}", $startDate, $endDate, $timezone);
// Fix for months with less than 31 days
$transformerConfig = new \Recurr\Transformer\ArrayTransformerConfig();
$transformerConfig->enableLastDayOfMonthFix();
$transformer = new \Recurr\Transformer\ArrayTransformer();
$transformer->setConfig($transformerConfig);
$dates = $transformer->transform($rule);
if (count($dates) < 2) {
return false;
}
return $dates;
}
public function getNextSendDate()
{
if ($this->start_date && !$this->last_sent_date) {
$startDate = $this->getOriginal('start_date') . ' ' . $this->account->recurring_hour . ':00:00';
return $this->account->getDateTime($startDate);
}
if (!$schedule = $this->getSchedule()) {
return null;
}
if (count($schedule) < 2) {
return null;
}
return $schedule[1]->getStart();
}
public function getPrettySchedule($min = 1, $max = 10)
{
if (!$schedule = $this->getSchedule($max)) {
return null;
}
$dates = [];
for ($i=$min; $i<min($max, count($schedule)); $i++) {
$date = $schedule[$i];
$date = $this->account->formatDate($date->getStart());
$dates[] = $date;
}
return implode('<br/>', $dates);
}
private function getRecurrenceRule()
{
$rule = '';
switch ($this->frequency_id) {
case FREQUENCY_WEEKLY:
$rule = 'FREQ=WEEKLY;';
break;
case FREQUENCY_TWO_WEEKS:
$rule = 'FREQ=WEEKLY;INTERVAL=2;';
break;
case FREQUENCY_FOUR_WEEKS:
$rule = 'FREQ=WEEKLY;INTERVAL=4;';
break;
case FREQUENCY_MONTHLY:
$rule = 'FREQ=MONTHLY;';
break;
case FREQUENCY_THREE_MONTHS:
$rule = 'FREQ=MONTHLY;INTERVAL=3;';
break;
case FREQUENCY_SIX_MONTHS:
$rule = 'FREQ=MONTHLY;INTERVAL=6;';
break;
case FREQUENCY_ANNUALLY:
$rule = 'FREQ=YEARLY;';
break;
}
if ($this->end_date) {
$rule .= 'UNTIL=' . $this->end_date;
}
return $rule;
}
/*
public function shouldSendToday()
{
if (!$nextSendDate = $this->getNextSendDate()) {
return false;
}
return $this->account->getDateTime() >= $nextSendDate;
}
*/
public function shouldSendToday()
{
if (!$this->start_date || strtotime($this->start_date) > strtotime('now')) {
@ -268,7 +400,8 @@ class Invoice extends EntityModel
return false;
}
public function getReminder() {
public function getReminder()
{
for ($i=1; $i<=3; $i++) {
$field = "enable_reminder{$i}";
if (!$this->account->$field) {
@ -320,7 +453,7 @@ class Invoice extends EntityModel
Invoice::creating(function ($invoice) {
if (!$invoice->is_recurring) {
$invoice->account->incrementCounter($invoice->is_quote);
$invoice->account->incrementCounter($invoice);
}
});

View File

@ -11,4 +11,9 @@ class Product extends EntityModel
{
return Product::scope()->where('product_key', '=', $key)->first();
}
public function default_tax_rate()
{
return $this->belongsTo('App\Models\TaxRate');
}
}

View File

@ -142,7 +142,15 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
public function getMaxNumClients()
{
return $this->isPro() ? MAX_NUM_CLIENTS_PRO : MAX_NUM_CLIENTS;
if ($this->isPro()) {
return MAX_NUM_CLIENTS_PRO;
}
if ($this->id < LEGACY_CUTOFF) {
return MAX_NUM_CLIENTS_LEGACY;
}
return MAX_NUM_CLIENTS;
}
public function getRememberToken()
@ -195,7 +203,8 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
{
if (!$user->getOriginal('email')
|| $user->getOriginal('email') == TEST_USERNAME
|| $user->getOriginal('username') == TEST_USERNAME) {
|| $user->getOriginal('username') == TEST_USERNAME
|| $user->getOriginal('email') == 'tests@bitrock.com') {
event(new UserSignedUp());
}

View File

@ -145,7 +145,6 @@ class ContactMailer extends Mailer
$subject = $this->processVariables($emailSubject, $variables);
$data['invoice_id'] = $payment->invoice->id;
$invoice->updateCachedPDF();
if ($user->email && $contact->email) {
$this->sendTo($contact->email, $user->email, $accountName, $subject, $view, $data);

View File

@ -9,6 +9,10 @@ class Mailer
{
public function sendTo($toEmail, $fromEmail, $fromName, $subject, $view, $data = [])
{
if (stristr($toEmail, '@example.com')) {
return true;
}
if (isset($_ENV['POSTMARK_API_TOKEN'])) {
$views = 'emails.'.$view.'_html';
} else {
@ -63,7 +67,7 @@ class Mailer
private function handleFailure($exception)
{
if (isset($_ENV['POSTMARK_API_TOKEN'])) {
if (isset($_ENV['POSTMARK_API_TOKEN']) && $exception->getResponse()) {
$response = $exception->getResponse()->getBody()->getContents();
$response = json_decode($response);
$emailError = nl2br($response->Message);

View File

@ -139,7 +139,7 @@ class AccountRepository
$invoice->user_id = $account->users()->first()->id;
$invoice->public_id = $publicId;
$invoice->client_id = $client->id;
$invoice->invoice_number = $account->getNextInvoiceNumber();
$invoice->invoice_number = $account->getNextInvoiceNumber($invoice);
$invoice->invoice_date = date_create()->format('Y-m-d');
$invoice->amount = PRO_PLAN_PRICE;
$invoice->balance = PRO_PLAN_PRICE;

View File

@ -250,18 +250,17 @@ class InvoiceRepository
public function save($publicId, $data, $entityType)
{
$account = \Auth::user()->account;
if ($publicId) {
$invoice = Invoice::scope($publicId)->firstOrFail();
} else {
$invoice = Invoice::createNew();
if ($entityType == ENTITY_QUOTE) {
$invoice->is_quote = true;
if ($data['is_recurring']) {
$entityType = ENTITY_RECURRING_INVOICE;
}
$invoice = $account->createInvoice($entityType, $data['client_id']);
}
$account = \Auth::user()->account;
if ((isset($data['set_default_terms']) && $data['set_default_terms'])
|| (isset($data['set_default_footer']) && $data['set_default_footer'])) {
if (isset($data['set_default_terms']) && $data['set_default_terms']) {
@ -273,7 +272,7 @@ class InvoiceRepository
$account->save();
}
if (isset($data['invoice_number'])) {
if (isset($data['invoice_number']) && !$invoice->is_recurring) {
$invoice->invoice_number = trim($data['invoice_number']);
}
@ -283,11 +282,6 @@ class InvoiceRepository
$invoice->invoice_date = isset($data['invoice_date_sql']) ? $data['invoice_date_sql'] : Utils::toSqlDate($data['invoice_date']);
$invoice->has_tasks = isset($data['has_tasks']) ? $data['has_tasks'] : false;
if (!$publicId) {
$invoice->client_id = $data['client_id'];
$invoice->is_recurring = $data['is_recurring'] ? true : false;
}
if ($invoice->is_recurring) {
if ($invoice->start_date && $invoice->start_date != Utils::toSqlDate($data['start_date'])) {
$invoice->last_sent_date = null;
@ -478,7 +472,7 @@ class InvoiceRepository
}
$clone->invoice_number = $account->invoice_number_prefix.$invoiceNumber;
} else {
$clone->invoice_number = $account->getNextInvoiceNumber();
$clone->invoice_number = $account->getNextInvoiceNumber($invoice);
}
foreach ([
@ -579,20 +573,21 @@ class InvoiceRepository
public function findInvoiceByInvitation($invitationKey)
{
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->first();
if (!$invitation) {
app()->abort(404, trans('texts.invoice_not_found'));
return false;
}
$invoice = $invitation->invoice;
if (!$invoice || $invoice->is_deleted) {
app()->abort(404, trans('texts.invoice_not_found'));
return false;
}
$invoice->load('user', 'invoice_items', 'invoice_design', 'account.country', 'client.contacts', 'client.country');
$client = $invoice->client;
if (!$client || $client->is_deleted) {
app()->abort(404, trans('texts.invoice_not_found'));
return false;
}
return $invitation;
@ -630,7 +625,7 @@ class InvoiceRepository
$invoice = Invoice::createNew($recurInvoice);
$invoice->client_id = $recurInvoice->client_id;
$invoice->recurring_invoice_id = $recurInvoice->id;
$invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber(false, 'R');
$invoice->invoice_number = 'R' . $recurInvoice->account->getNextInvoiceNumber($recurInvoice);
$invoice->amount = $recurInvoice->amount;
$invoice->balance = $recurInvoice->amount;
$invoice->invoice_date = date_create()->format('Y-m-d');

View File

@ -5,6 +5,7 @@ use Utils;
class TaxRateRepository
{
/*
public function save($taxRates)
{
$taxRateIds = [];
@ -39,4 +40,5 @@ class TaxRateRepository
}
}
}
*/
}

View File

@ -144,6 +144,10 @@ class AppServiceProvider extends ServiceProvider {
Validator::replacer('less_than', function($message, $attribute, $rule, $parameters) {
return str_replace(':value', $parameters[0], $message);
});
Validator::extend('has_counter', function($attribute, $value, $parameters) {
return !$value || strstr($value, '{$counter}');
});
}
/**

View File

@ -46,10 +46,10 @@ class AuthService
if ($result === true) {
if (!$isRegistered) {
event(new UserSignedUp());
Session::flash('warning', trans('texts.success_message'));
Session::flash('message', trans('texts.success_message'));
} else {
Session::flash('message', trans('texts.updated_settings'));
return redirect()->to('/company/details');
return redirect()->to('/settings/' . ACCOUNT_USER_DETAILS);
}
} else {
Session::flash('error', $result);

View File

@ -36,7 +36,7 @@ class PaymentService {
$gateway->$function($val);
}
if ($accountGateway->gateway->id == GATEWAY_DWOLLA) {
if ($accountGateway->isGateway(GATEWAY_DWOLLA)) {
if ($gateway->getSandbox() && isset($_ENV['DWOLLA_SANDBOX_KEY']) && isset($_ENV['DWOLLA_SANSBOX_SECRET'])) {
$gateway->setKey($_ENV['DWOLLA_SANDBOX_KEY']);
$gateway->setSecret($_ENV['DWOLLA_SANSBOX_SECRET']);
@ -49,7 +49,7 @@ class PaymentService {
return $gateway;
}
public function getPaymentDetails($invitation, $input = null)
public function getPaymentDetails($invitation, $accountGateway, $input = null)
{
$invoice = $invitation->invoice;
$account = $invoice->account;
@ -66,8 +66,7 @@ class PaymentService {
}
$card = new CreditCard($data);
return [
$data = [
'amount' => $invoice->getRequestedAmount(),
'card' => $card,
'currency' => $currencyCode,
@ -77,6 +76,12 @@ class PaymentService {
'transactionId' => $invoice->invoice_number,
'transactionType' => 'Purchase',
];
if ($accountGateway->isGateway(GATEWAY_PAYPAL_EXPRESS) || $accountGateway->isGateway(GATEWAY_PAYPAL_PRO)) {
$data['ButtonSource'] = 'InvoiceNinja_SP';
};
return $data;
}
public function convertInputForOmnipay($input)
@ -222,7 +227,7 @@ class PaymentService {
// setup the gateway/payment info
$gateway = $this->createGateway($accountGateway);
$details = $this->getPaymentDetails($invitation);
$details = $this->getPaymentDetails($invitation, $accountGateway);
$details['cardReference'] = $token;
// submit purchase/get response

View File

@ -29,7 +29,7 @@
"coatesap/omnipay-datacash": "~2.0",
"alfaproject/omnipay-neteller": "1.0.*@dev",
"mfauveau/omnipay-pacnet": "~2.0",
"coatesap/omnipay-paymentsense": "~2.0",
"coatesap/omnipay-paymentsense": "2.0.0",
"coatesap/omnipay-realex": "~2.0",
"fruitcakestudio/omnipay-sisow": "~2.0",
"alfaproject/omnipay-skrill": "dev-master",
@ -38,7 +38,8 @@
"laravelcollective/html": "~5.0",
"wildbit/laravel-postmark-provider": "dev-master",
"Dwolla/omnipay-dwolla": "dev-master",
"laravel/socialite": "~2.0"
"laravel/socialite": "~2.0",
"simshaun/recurr": "dev-master"
},
"require-dev": {
"phpunit/phpunit": "~4.0",

7171
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -39,25 +39,25 @@ return [
'github' => [
'client_id' => env('GITHUB_CLIENT_ID'),
'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => 'http://ninja.dev/auth/github'
'redirect' => env('GITHUB_OAUTH_REDIRECT'),
],
'google' => [
'client_id' => '640903115046-dd09j2q24lcc3ilrrv5f2ft2i3n0sreg.apps.googleusercontent.com',
'client_secret' => 'Vsfhldq7mRxsCXQTQI8U_4Ua',
'redirect' => 'http://ninja.dev/auth/google',
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('GOOGLE_OAUTH_REDIRECT'),
],
'facebook' => [
'client_id' => '635126583203143',
'client_secret' => '7aa7c391019f2ece3c6aa90f4c9b1485',
'redirect' => 'http://ninja.dev/auth/facebook',
'client_id' => env('FACEBOOK_CLIENT_ID'),
'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
'redirect' => env('FACEBOOK_OAUTH_REDIRECT'),
],
'linkedin' => [
'client_id' => '778is2j21w25xj',
'client_secret' => 'DvDExxfBLXUtxc81',
'redirect' => 'http://ninja.dev/auth/linkedin',
'client_id' => env('LINKEDIN_CLIENT_ID'),
'client_secret' => env('LINKEDIN_CLIENT_SECRET'),
'redirect' => env('LINKEDIN_OAUTH_REDIRECT'),
],
];

View File

@ -14,10 +14,10 @@ class AddInvoiceNumberSettings extends Migration {
{
Schema::table('accounts', function($table)
{
$table->text('invoice_number_prefix')->nullable();
$table->string('invoice_number_prefix')->nullable();
$table->integer('invoice_number_counter')->default(1)->nullable();
$table->text('quote_number_prefix')->nullable();
$table->string('quote_number_prefix')->nullable();
$table->integer('quote_number_counter')->default(1)->nullable();
$table->boolean('share_counter')->default(true);

View File

@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
class AddDefaultTaxRates extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function ($table) {
$table->unsignedInteger('default_tax_rate_id')->nullable();
$table->smallInteger('recurring_hour')->default(DEFAULT_SEND_RECURRING_HOUR);
});
Schema::table('products', function ($table) {
$table->unsignedInteger('default_tax_rate_id')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function ($table) {
$table->dropColumn('default_tax_rate_id');
$table->dropColumn('recurring_hour');
});
Schema::table('products', function ($table) {
$table->dropColumn('default_tax_rate_id');
});
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
class AddInvoiceNumberPattern extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function ($table) {
$table->string('invoice_number_pattern')->nullable();
$table->string('quote_number_pattern')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function ($table) {
$table->dropColumn('invoice_number_pattern');
$table->dropColumn('quote_number_pattern');
});
}
}

View File

@ -95,6 +95,8 @@ class PaymentLibrariesSeeder extends Seeder
['name' => 'United Arab Emirates Dirham', 'code' => 'AED', 'symbol' => 'DH ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Hong Kong Dollar', 'code' => 'HKD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Indonesian Rupiah', 'code' => 'IDR', 'symbol' => 'Rp', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Mexican Peso', 'code' => 'MXN', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Egyptian Pound', 'code' => 'EGP', 'symbol' => '£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
];
foreach ($currencies as $currency) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

50
public/css/built.css vendored
View File

@ -2352,6 +2352,9 @@ body { background: #f8f8f8 !important;
font-family: 'Roboto', sans-serif;
font-size: 15px;
}
html {
overflow-y: scroll;
}
.bold { font-weight: 700; }
a {color:#0b4d78;}
/*a:hover { text-decoration: none; color: #0a3857;}*/
@ -3105,23 +3108,45 @@ box-shadow: 0px 0px 15px 0px rgba(0, 5, 5, 0.2);
text-decoration: line-through;
}
@media screen and (min-width: 992px) {
.hide-desktop {display: none;}
/* Custom, iPhone Retina */
@media only screen and (min-width : 320px) {
}
@media (max-width: 992px) {
.hide-phone {
display: none !important;
/* Extra Small Devices, Phones */
@media only screen and (min-width : 480px) {
}
/* Small Devices, Tablets */
@media only screen and (min-width : 768px) {
.form-padding-right {
padding-right: 40px;
}
}
@media screen and (min-width: 992px) {
/* Medium Devices, Desktops */
@media only screen and (min-width : 992px) {
.form-padding-right {
padding-right: 100px;
}
.medium-dialog {
width: 760px;
}
.large-dialog {
width: 960px;
}
.hide-desktop
{
display: none;
}
}
@media (max-width: 992px) {
.hide-phone {
display: none !important;
}
}
@media (max-width: 767px) {
@ -3337,7 +3362,18 @@ ul.user-accounts a:hover div.remove {
visibility: visible;
}
.tooltip-inner {
.invoice-contact .tooltip-inner {
text-align:left;
width: 350px;
}
/* Show selected section in settings nav */
.list-group-item.selected:before {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 2px;
content: "";
background-color: #e37329;
}

View File

@ -785,7 +785,9 @@ body {
font-size: 14px;
background-color: #f8f8f8;
}
html {
overflow-y: scroll;
}
@media screen and (min-width: 700px) {
.navbar-header {
@ -837,7 +839,7 @@ body {
display: inline-block;
width: 9px;
height: 15px;
background: url({{ asset('images/social/facebook.svg') }}) no-repeat;
background: url('../images/social/facebook.svg') no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;
@ -848,7 +850,7 @@ body {
display: inline-block;
width: 19px;
height: 16px;
background: url({{ asset('images/social/twitter.svg') }}) no-repeat;
background: url('../images/social/twitter.svg') no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;
@ -859,18 +861,18 @@ body {
display: inline-block;
width: 19px;
height: 16px;
background: url({{ asset('images/social/github.png') }}) no-repeat;
background: url('../images/social/github.png') no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;
}
/* Hide bootstrap sort header icons */
table.data-table thead .sorting:after { content: '' !important }
table.data-table thead .sorting_asc:after { content: '' !important }
table.data-table thead .sorting_desc:after { content: '' !important}
table.data-table thead .sorting_asc_disabled:after { content: '' !important }
table.data-table thead .sorting_desc_disabled:after { content: '' !important }
table.table thead .sorting:after { content: '' !important }
table.table thead .sorting_asc:after { content: '' !important }
table.table thead .sorting_desc:after { content: '' !important }
table.table thead .sorting_asc_disabled:after { content: '' !important }
table.table thead .sorting_desc_disabled:after { content: '' !important }
.dataTables_length {
padding-left: 20px;

View File

@ -3,7 +3,9 @@ body {
font-size: 14px;
background-color: #f8f8f8;
}
html {
overflow-y: scroll;
}
@media screen and (min-width: 700px) {
.navbar-header {
@ -55,7 +57,7 @@ body {
display: inline-block;
width: 9px;
height: 15px;
background: url({{ asset('images/social/facebook.svg') }}) no-repeat;
background: url('../images/social/facebook.svg') no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;
@ -66,7 +68,7 @@ body {
display: inline-block;
width: 19px;
height: 16px;
background: url({{ asset('images/social/twitter.svg') }}) no-repeat;
background: url('../images/social/twitter.svg') no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;
@ -77,7 +79,7 @@ body {
display: inline-block;
width: 19px;
height: 16px;
background: url({{ asset('images/social/github.png') }}) no-repeat;
background: url('../images/social/github.png') no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;

50
public/css/style.css vendored
View File

@ -2,6 +2,9 @@ body { background: #f8f8f8 !important;
font-family: 'Roboto', sans-serif;
font-size: 15px;
}
html {
overflow-y: scroll;
}
.bold { font-weight: 700; }
a {color:#0b4d78;}
/*a:hover { text-decoration: none; color: #0a3857;}*/
@ -755,23 +758,45 @@ box-shadow: 0px 0px 15px 0px rgba(0, 5, 5, 0.2);
text-decoration: line-through;
}
@media screen and (min-width: 992px) {
.hide-desktop {display: none;}
/* Custom, iPhone Retina */
@media only screen and (min-width : 320px) {
}
@media (max-width: 992px) {
.hide-phone {
display: none !important;
/* Extra Small Devices, Phones */
@media only screen and (min-width : 480px) {
}
/* Small Devices, Tablets */
@media only screen and (min-width : 768px) {
.form-padding-right {
padding-right: 40px;
}
}
@media screen and (min-width: 992px) {
/* Medium Devices, Desktops */
@media only screen and (min-width : 992px) {
.form-padding-right {
padding-right: 100px;
}
.medium-dialog {
width: 760px;
}
.large-dialog {
width: 960px;
}
.hide-desktop
{
display: none;
}
}
@media (max-width: 992px) {
.hide-phone {
display: none !important;
}
}
@media (max-width: 767px) {
@ -987,7 +1012,18 @@ ul.user-accounts a:hover div.remove {
visibility: visible;
}
.tooltip-inner {
.invoice-contact .tooltip-inner {
text-align:left;
width: 350px;
}
/* Show selected section in settings nav */
.list-group-item.selected:before {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 2px;
content: "";
background-color: #e37329;
}

View File

@ -32060,6 +32060,7 @@ NINJA.clientDetails = function(invoice) {
if (!client) {
return;
}
var account = invoice.account;
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;
@ -32070,6 +32071,11 @@ NINJA.clientDetails = function(invoice) {
cityStatePostal = formatAddress(client.city, client.state, client.postal_code, swap);
}
// if a custom field is used in the invoice/quote number then we'll hide it from the PDF
var pattern = invoice.is_quote ? account.quote_number_pattern : account.invoice_number_pattern;
var custom1InPattern = (pattern && pattern.indexOf('{$custom1}') >= 0);
var custom2InPattern = (pattern && pattern.indexOf('{$custom2}') >= 0);
data = [
{text:clientName || ' ', style: ['clientName']},
{text:client.id_number},
@ -32079,8 +32085,8 @@ NINJA.clientDetails = function(invoice) {
{text:cityStatePostal},
{text:client.country ? client.country.name : ''},
{text:clientEmail},
{text: invoice.client.custom_value1 ? invoice.account.custom_client_label1 + ' ' + invoice.client.custom_value1 : false},
{text: invoice.client.custom_value2 ? invoice.account.custom_client_label2 + ' ' + invoice.client.custom_value2 : false}
{text: client.custom_value1 && !custom1InPattern ? account.custom_client_label1 + ' ' + client.custom_value1 : false},
{text: client.custom_value2 && !custom2InPattern ? account.custom_client_label2 + ' ' + client.custom_value2 : false}
];
return NINJA.prepareDataList(data, 'clientDetails');

7928
public/js/pdf.built.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -487,6 +487,7 @@ NINJA.clientDetails = function(invoice) {
if (!client) {
return;
}
var account = invoice.account;
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;
@ -497,6 +498,11 @@ NINJA.clientDetails = function(invoice) {
cityStatePostal = formatAddress(client.city, client.state, client.postal_code, swap);
}
// if a custom field is used in the invoice/quote number then we'll hide it from the PDF
var pattern = invoice.is_quote ? account.quote_number_pattern : account.invoice_number_pattern;
var custom1InPattern = (pattern && pattern.indexOf('{$custom1}') >= 0);
var custom2InPattern = (pattern && pattern.indexOf('{$custom2}') >= 0);
data = [
{text:clientName || ' ', style: ['clientName']},
{text:client.id_number},
@ -506,8 +512,8 @@ NINJA.clientDetails = function(invoice) {
{text:cityStatePostal},
{text:client.country ? client.country.name : ''},
{text:clientEmail},
{text: invoice.client.custom_value1 ? invoice.account.custom_client_label1 + ' ' + invoice.client.custom_value1 : false},
{text: invoice.client.custom_value2 ? invoice.account.custom_client_label2 + ' ' + invoice.client.custom_value2 : false}
{text: client.custom_value1 && !custom1InPattern ? account.custom_client_label1 + ' ' + client.custom_value1 : false},
{text: client.custom_value2 && !custom2InPattern ? account.custom_client_label2 + ' ' + client.custom_value2 : false}
];
return NINJA.prepareDataList(data, 'clientDetails');

View File

@ -7,14 +7,13 @@
[![Join the chat at https://gitter.im/hillelcoren/invoice-ninja](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hillelcoren/invoice-ninja?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Please help our project by voting for us on [Product Hunt](http://www.producthunt.com/tech/invoice-ninja).
If you'd like to use our code to sell your own invoicing app email us for details about our affiliate program.
### Installation Options
* [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)
* [Self-Host Zip](https://www.invoiceninja.com/knowledgebase/self-host/) - Free
* [Docker File](https://github.com/rollbrettler/Dockerfiles/blob/master/invoice-ninja/Dockerfile) - Free
* [Bitnami](https://bitnami.com/stack/invoice-ninja) - Free
* [Softaculous](https://www.softaculous.com/apps/ecommerce/Invoice_Ninja) - $30
### Features
* Built using Laravel 5
@ -26,6 +25,7 @@ If you'd like to use our code to sell your own invoicing app email us for detail
* Tax rates and payment terms
* Reminder emails
* Partial payments
* Client portal
* Custom email templates
* [Zapier](https://zapier.com/) integration
* [D3.js](http://d3js.org/) visualizations
@ -78,3 +78,4 @@ If you'd like to use our code to sell your own invoicing app email us for detail
* [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
* [simshaun/recurr](https://github.com/simshaun/recurr) - PHP library for working with recurrence rules

View File

@ -262,7 +262,7 @@
'email_salutation' => 'Kære :name,',
'email_signature' => 'Med venlig hilsen,',
'email_from' => 'Invoice Ninja Teamet',
'user_email_footer' => 'For at justere varslings indstillingene besøg venligst'.SITE_URL.'/company/notifications',
'user_email_footer' => 'For at justere varslings indstillingene besøg venligst'.SITE_URL.'/settings/notifications',
'invoice_link_message' => 'Hvis du vil se din klient faktura klik på linket under:',
'notification_invoice_paid_subject' => 'Faktura :invoice betalt af :client',
'notification_invoice_sent_subject' => 'Faktura :invoice sendt til :client',
@ -271,15 +271,14 @@
'notification_invoice_sent' => 'En e-mail er blevet sendt til :client med faktura :invoice pålydende :amount.',
'notification_invoice_viewed' => ':client har set faktura :invoice pålydende :amount.',
'reset_password' => 'Du kan nulstille din adgangskode ved at besøge følgende link:',
'reset_password_footer' => 'Hvis du ikke bad om at få nulstillet din adgangskode kontakt venligst kundeservice: ' . CONTACT_EMAIL,
'reset_password_footer' => 'Hvis du ikke bad om at få nulstillet din adgangskode kontakt venligst kundeservice: '.CONTACT_EMAIL,
// Payment page
'secure_payment' => 'Sikker betaling',
'card_number' => 'Kortnummer',
'expiration_month' => 'Udløbsdato',
'expiration_year' => 'Udløbsår',
'cvv' =>'Kontrolcifre',
'cvv' => 'Kontrolcifre',
// Security alerts
'security' => [
@ -300,7 +299,7 @@
'logout' => 'Log ud',
'sign_up_to_save' => 'Registrer dig for at gemme dit arbejde',
'agree_to_terms' =>'Jeg accepterer Invoice Ninja :terms',
'agree_to_terms' => 'Jeg accepterer Invoice Ninja :terms',
'terms_of_service' => 'Vilkår for brug',
'email_taken' => 'E-mailadressen er allerede registreret',
'working' => 'Arbejder',
@ -420,7 +419,7 @@
'active' => 'Aktiv',
'pending' => 'Afventer',
'deleted_user' => 'Bruger slettet',
'limit_users' => 'Desværre, dette vil overstige grænsen på ' . MAX_NUM_USERS . ' brugere',
'limit_users' => 'Desværre, dette vil overstige grænsen på '.MAX_NUM_USERS.' brugere',
'confirm_email_invoice' => 'Er du sikker på at du vil sende en e-mail med denne faktura?',
'confirm_email_quote' => 'Er du sikker på du ville sende en e-mail med dette tilbud?',
@ -746,7 +745,6 @@
'current_user' => 'Nuværende bruger',
'new_recurring_invoice' => 'Ny gentaget fakture',
'recurring_invoice' => 'Gentaget faktura',
'recurring_too_soon' => 'Det er for tidligt at generere den næste faktura',
'created_by_invoice' => 'Oprettet fra :invoice',
'primary_user' => 'Primær bruger',
'help' => 'Hjælp',
@ -818,6 +816,15 @@
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
'verify_email' => 'Please visit the link in the account confirmation email to verify your email address.',
'basic_settings' => 'Basic Settings',
'pro' => 'Pro',
'gateways' => 'Payment Gateways',
'recurring_too_soon' => 'Det er for tidligt at generere den næste faktura, it\'s scheduled for :date',
'next_send_on' => 'Send Next: :date',
'no_longer_running' => 'This invoice is not scheduled to run',
'general_settings' => 'General Settings',
'customize' => 'Customize',
);

View File

@ -77,6 +77,7 @@ return array(
"has_credit" => "The client does not have enough credit.",
"notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
"has_counter" => 'The value must contain {$counter}',
/*
|--------------------------------------------------------------------------

View File

@ -0,0 +1,22 @@
<?php
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.
|
*/
"password" => "Beide Passwörter müssen übereinstimmen und mindestens 6 Zeichen lang sein.",
"user" => "Wir können keinen Nutzer mit dieser E-Mail Adresse finden.",
"token" => "Der Code zum Zurücksetzen des Passworts ist ungültig oder abgelaufen.",
"sent" => "Es wurde soeben eine E-Mail verschickt, die einen Link zum Zurücksetzen des Passworts enthält!",
"reset" => "Das Passwort wurde zurückgesetzt!",
];

View File

@ -46,7 +46,7 @@ return array(
'line_total' => 'Summe',
'subtotal' => 'Zwischensumme',
'paid_to_date' => 'Bereits gezahlt',
'balance_due' => 'Geschuldeter Betrag',
'balance_due' => 'Offener Betrag',
'invoice_design_id' => 'Design',
'terms' => 'Bedingungen',
'your_invoice' => 'Ihre Rechnung',
@ -262,7 +262,7 @@ return array(
'email_salutation' => 'Sehr geehrte/r :name,',
'email_signature' => 'Mit freundlichen Grüßen,',
'email_from' => 'Das InvoiceNinja Team',
'user_email_footer' => 'Um deine E-Mail-Benachrichtigungen anzupassen besuche bitte '.SITE_URL.'/company/notifications',
'user_email_footer' => 'Um deine E-Mail-Benachrichtigungen anzupassen besuche bitte '.SITE_URL.'/settings/notifications',
'invoice_link_message' => 'Um deine Kundenrechnung anzuschauen, klicke auf den folgenden Link:',
'notification_invoice_paid_subject' => 'Die Rechnung :invoice wurde von :client bezahlt.',
'notification_invoice_sent_subject' => 'Die Rechnung :invoice wurde an :client versendet.',
@ -308,7 +308,6 @@ return array(
'success_message' => 'Du hast dich erfolgreich registriert. Bitte besuche den Link in deiner Bestätigungsmail um deine E-Mail-Adresse zu verifizieren.',
'erase_data' => 'Diese Aktion wird deine Daten dauerhaft löschen.',
'password' => 'Passwort',
'close' => 'Schließen',
'pro_plan_product' => 'Pro Plan',
'pro_plan_description' => 'Jahresmitgliedschaft beim Invoice Ninja Pro Plan.',
@ -574,7 +573,8 @@ return array(
'recover_password' => 'Passwort wiederherstellen',
'forgot_password' => 'Passwort vergessen?',
'email_address' => 'E-Mail-Adresse',
'lets_go' => "Auf geht's",
'lets_go' => 'Auf geht\'s',
//'lets_go' => 'Login',
'password_recovery' => 'Passwort-Wiederherstellung',
'send_email' => 'E-Mail verschicken',
'set_password' => 'Passwort festlegen',
@ -745,14 +745,14 @@ return array(
'current_user' => 'Aktueller Benutzer',
'new_recurring_invoice' => 'Neue wiederkehrende Rechnung',
'recurring_invoice' => 'Wiederkehrende Rechnung',
'recurring_too_soon' => 'Es ist zu früh, um die nächste wiederkehrende Rechnung zu erstellen',
'recurring_too_soon' => 'Es ist zu früh die nächste wiederkehrende Rechnung zu erstellen, die nächste ist am :date geplant',
'created_by_invoice' => 'Erstellt durch :invoice',
'primary_user' => 'Primärer Benutzer',
'help' => 'Hilfe',
'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p>
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>',
'customize_help' => '<p>Wir benutzen zur deklarativen Definition der Rechnungsdesigns <a href="http://pdfmake.org/" target="_blank">pdfmake</a>. Der pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> bietet Gelegenheit die Bibliothek in Aktion zu sehen.</p>
<p>Man kann jedes Rechnungsfeld nutzen, in dem man <code>Value</code> hinten anhängt. Zum Beispiel zeigt <code>$invoiceNumberValue</code> die Rechnungsnummer.</p>
<p>Mit der <i>dot notation</i> kann auf Kind-Eigenschaften zugegriffen werden. Für den Kundennamen kann man zum Beispiel <code>$client.nameValue</code> benutzen.</p>
<p>Wenn du Hilfe brauchst schreibe uns gern im <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">Support Forum</a> (Englisch).</p>',
'invoice_due_date' => 'Fällig am',
'quote_due_date' => 'Gültig bis',
@ -767,11 +767,11 @@ return array(
'status_paid' => 'Bezahlt',
'show_line_item_tax' => '<b>Steuern für Belegpositionen</b> in der jeweiligen Zeile anzeigen',
'iframe_url' => 'Website',
'iframe_url' => 'Webseite',
'iframe_url_help1' => 'Kopiere den folgenden Code in eine Seite auf deiner Website.',
'iframe_url_help2' => 'You can test the feature by clicking \'View as recipient\' for an invoice.',
'iframe_url_help2' => 'Du kannst diese Funktion testen, in dem du für eine Rechnung \'Als Empfänger betrachten\'. anklickst.',
'auto_bill' => 'Auto Bill',
'auto_bill' => 'Auto-Rechnung',
'military_time' => '24-Stunden-Zeit',
'last_sent' => 'Zuletzt gesendet',
@ -792,31 +792,53 @@ return array(
'last_sent_on' => 'Zuletzt versendet am :date',
'page_expire' => 'Diese Seite wird bald ablaufen, :click_here um weiter zu arbeiten',
'upcoming_quotes' => 'Upcoming Quotes',
'expired_quotes' => 'Expired Quotes',
'upcoming_quotes' => 'Kommende Angebote',
'expired_quotes' => 'Abgelaufene Angebote',
'sign_up_using' => 'Sign up using',
'invalid_credentials' => 'These credentials do not match our records',
'show_all_options' => 'Show all options',
'user_details' => 'User Details',
'sign_up_using' => 'Anmelden mit',
'invalid_credentials' => 'Diese Zugangsdaten können wir nicht finden.',
'show_all_options' => 'Zeige alle Optionen',
'user_details' => 'Nutzer Details',
'oneclick_login' => 'One-Click Login',
'disable' => 'Disable',
'invoice_quote_number' => 'Invoice and Quote Numbers',
'invoice_charges' => 'Invoice Charges',
'disable' => 'Deaktivieren',
'invoice_quote_number' => 'Rechnungs- und Angebotsnummern',
'invoice_charges' => 'Rechnungs-Gebühren',
'invitation_status' => [
'sent' => 'Email Sent',
'opened' => 'Email Openend',
'viewed' => 'Invoice Viewed',
'sent' => 'E-Mail versandt',
'opened' => 'E-Mail geöffnet',
'viewed' => 'Rechnung angesehen',
],
'notification_invoice_bounced' => 'We were unable to deliver Invoice :invoice to :contact.',
'notification_invoice_bounced_subject' => 'Unable to deliver Invoice :invoice',
'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.',
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'notification_invoice_bounced' => 'Die Rechnung :invoice an :contact konnte nicht zugestellt werden.',
'notification_invoice_bounced_subject' => 'Rechnung :invoice nicht zugestellt.',
'notification_quote_bounced' => 'Das Angebot :invoice an :contact konnte nicht zugestellt werden.',
'notification_quote_bounced_subject' => 'Angebot :invoice wurde nicht zugestellt.',
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
'custom_invoice_link' => 'Manueller Rechnungs-Link',
'total_invoiced' => 'Rechnungs-Betrag',
'open_balance' => 'Offener Betrag',
'verify_email' => 'Bitte klicken sie auf den Link in der Bestätigungsmail, um die E-Mail-Adresse zu verifizieren.',
'basic_settings' => 'Allgemeine Einstellungen',
'pro' => 'Pro',
'gateways' => 'Payment Gateways',
'next_send_on' => 'Nächster Versand: :date',
'no_longer_running' => 'Diese Rechnung wird momentan nicht automatisch erstellt.',
'general_settings' => 'Allgemeine Einstellungen',
'customize' => 'Anpassen',
'oneclick_login_help' => 'Connect an account to login without a password',
'referral_code_help' => 'Earn money by sharing our app online',
'enable_with_stripe' => 'Enable | Requires Stripe',
'tax_settings' => 'Steuer Einstellungen',
'create_tax_rate' => 'Neuer Steuersatz',
'updated_tax_rate' => 'Steuersatz aktualisiert',
'created_tax_rate' => 'Steuersatz erstellt',
'edit_tax_rate' => 'Steuersatz bearbeiten',
'archive_tax_rate' => 'Steuersatz archivieren',
'archived_tax_rate' => 'Steuersatz archiviert',
'default_tax_rate_id' => 'Standard Steuersatz',
'tax_rate' => 'Steuersatz',
);

View File

@ -75,6 +75,7 @@ return array(
"has_credit" => "Der Kunde hat nicht genug Guthaben.",
"notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
"has_counter" => 'The value must contain {$counter}',
/*
|--------------------------------------------------------------------------

View File

@ -262,7 +262,7 @@ return array(
'email_salutation' => 'Dear :name,',
'email_signature' => 'Regards,',
'email_from' => 'The Invoice Ninja Team',
'user_email_footer' => 'To adjust your email notification settings please visit '.SITE_URL.'/company/notifications',
'user_email_footer' => 'To adjust your email notification settings please visit '.SITE_URL.'/settings/notifications',
'invoice_link_message' => 'To view your client invoice click the link below:',
'notification_invoice_paid_subject' => 'Invoice :invoice was paid by :client',
'notification_invoice_sent_subject' => 'Invoice :invoice was sent to :client',
@ -337,7 +337,7 @@ return array(
'fill_products_help' => 'Selecting a product will automatically <b>fill in the description and cost</b>',
'update_products' => 'Auto-update products',
'update_products_help' => 'Updating an invoice will automatically <b>update the product library</b>',
'create_product' => 'Create Product',
'create_product' => 'Add Product',
'edit_product' => 'Edit Product',
'archive_product' => 'Archive Product',
'updated_product' => 'Successfully updated product',
@ -557,7 +557,7 @@ return array(
'created_gateway' => 'Successfully created gateway',
'deleted_gateway' => 'Successfully deleted gateway',
'pay_with_paypal' => 'PayPal',
'pay_with_card' => 'Credit card',
'pay_with_card' => 'Credit Card',
'change_password' => 'Change password',
'current_password' => 'Current password',
@ -587,7 +587,7 @@ 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' => 'Credit Card',
'payment_type_paypal' => 'PayPal',
'payment_type_bitcoin' => 'Bitcoin',
'knowledge_base' => 'Knowledge Base',
@ -745,7 +745,7 @@ return array(
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice, it\'s scheduled for :date',
'created_by_invoice' => 'Created by :invoice',
'primary_user' => 'Primary User',
'help' => 'Help',
@ -789,7 +789,7 @@ return array(
'referral_program' => 'Referral Program',
'referral_code' => 'Referral Code',
'last_sent_on' => 'Last sent on :date',
'last_sent_on' => 'Sent Last: :date',
'page_expire' => 'This page will expire soon, :click_here to keep working',
'upcoming_quotes' => 'Upcoming Quotes',
@ -809,6 +809,7 @@ return array(
'opened' => 'Email Openend',
'viewed' => 'Invoice Viewed',
],
'notification_invoice_bounced' => 'We were unable to deliver Invoice :invoice to :contact.',
'notification_invoice_bounced_subject' => 'Unable to deliver Invoice :invoice',
'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.',
@ -818,6 +819,37 @@ return array(
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
'verify_email' => 'Please visit the link in the account confirmation email to verify your email address.',
'basic_settings' => 'Basic Settings',
'pro' => 'Pro',
'gateways' => 'Payment Gateways',
'next_send_on' => 'Send Next: :date',
'no_longer_running' => 'This invoice is not scheduled to run',
'general_settings' => 'General Settings',
'customize' => 'Customize',
'oneclick_login_help' => 'Connect an account to login without a password',
'referral_code_help' => 'Earn money by sharing our app online',
'enable_with_stripe' => 'Enable | Requires Stripe',
'tax_settings' => 'Tax Settings',
'create_tax_rate' => 'Add Tax Rate',
'updated_tax_rate' => 'Successfully updated tax rate',
'created_tax_rate' => 'Successfully created tax rate',
'edit_tax_rate' => 'Edit tax rate',
'archive_tax_rate' => 'Archive tax rate',
'archived_tax_rate' => 'Successfully archived the tax rate',
'default_tax_rate_id' => 'Default Tax Rate',
'tax_rate' => 'Tax Rate',
'recurring_hour' => 'Recurring Hour',
'pattern' => 'Pattern',
'pattern_help_title' => 'Pattern Help',
'pattern_help_1' => 'Create custom invoice and quote numbers by specifying a pattern',
'pattern_help_2' => 'Available variables:',
'pattern_help_3' => 'For example, :example would be converted to :value',
'see_options' => 'See options',
'invoice_counter' => 'Invoice Counter',
'quote_counter' => 'Quote Counter',
'type' => 'Type',
);

View File

@ -73,6 +73,7 @@ return array(
"has_credit" => "The client does not have enough credit.",
"notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
"has_counter" => 'The value must contain {$counter}',
/*
|--------------------------------------------------------------------------

View File

@ -256,7 +256,7 @@ return array(
'email_salutation' => 'Estimado :name,',
'email_signature' => 'Un saludo cordial,',
'email_from' => 'El equipo de Invoice Ninja ',
'user_email_footer' => 'Para ajustar la configuración de las notificaciones de tu correo, visita '.SITE_URL.'/company/notifications',
'user_email_footer' => 'Para ajustar la configuración de las notificaciones de tu correo, visita '.SITE_URL.'/settings/notifications',
'invoice_link_message' => 'Para visualizar la factura de cliente, haz clic en el enlace abajo:',
'notification_invoice_paid_subject' => 'La factura :invoice ha sido pagada por el cliente :client',
'notification_invoice_sent_subject' => 'La factura :invoice ha sido enviada a el cliente :client',
@ -723,7 +723,6 @@ return array(
'current_user' => 'Usuario Actual',
'new_recurring_invoice' => 'Nueva Factura Recurrente',
'recurring_invoice' => 'Factura Recurrente',
'recurring_too_soon' => 'Es my pronto para crear la siguiente factura recurrente',
'created_by_invoice' => 'Creado por :invoice',
'primary_user' => 'Usuario Primario',
'help' => 'Ayuda',
@ -795,6 +794,16 @@ return array(
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
'verify_email' => 'Please visit the link in the account confirmation email to verify your email address.',
'basic_settings' => 'Basic Settings',
'pro' => 'Pro',
'gateways' => 'Payment Gateways',
'recurring_too_soon' => 'Es my pronto para crear la siguiente factura recurrente, it\'s scheduled for :date',
'next_send_on' => 'Send Next: :date',
'no_longer_running' => 'This invoice is not scheduled to run',
'general_settings' => 'General Settings',
'customize' => 'Customize',
);

View File

@ -74,6 +74,7 @@ return array(
"has_credit" => "el cliente no tiene crédito suficiente.",
"notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
"has_counter" => 'The value must contain {$counter}',
/*
|--------------------------------------------------------------------------

View File

@ -1,4 +1,4 @@
<?php
<?php
return array(
@ -271,7 +271,7 @@ return array(
'email_salutation' => 'Estimado :name,',
'email_signature' => 'Un cordial saludo,',
'email_from' => 'El equipo de Invoice Ninja ',
'user_email_footer' => 'Para ajustar la configuración de las notificaciones de tu email, visita '.SITE_URL.'/company/notifications',
'user_email_footer' => 'Para ajustar la configuración de las notificaciones de tu email, visita '.SITE_URL.'/settings/notifications',
'invoice_link_message' => 'Para visualizar la factura de cliente, haz clic en el enlace de abajo:',
'notification_invoice_paid_subject' => 'La factura :invoice ha sido pagada por el cliente :client',
'notification_invoice_sent_subject' => 'La factura :invoice ha sido enviada a el cliente :client',
@ -280,7 +280,7 @@ return array(
'notification_invoice_sent' => 'La factura :invoice por importe de :amount fue enviada al cliente :cliente.',
'notification_invoice_viewed' => 'La factura :invoice por importe de :amount fue visualizada por el cliente :client.',
'reset_password' => 'Puedes reconfigurar la contraseña de tu cuenta haciendo clic en el siguiente enlace:',
'reset_password_footer' => 'Si no has solicitado un cambio de contraseña, por favor contactate con nosostros: ' . CONTACT_EMAIL,
'reset_password_footer' => 'Si no has solicitado un cambio de contraseña, por favor contactate con nosostros: '.CONTACT_EMAIL,
// Payment page
'secure_payment' => 'Pago seguro',
@ -307,7 +307,7 @@ return array(
],
'logout' => 'Cerrar sesión',
'sign_up_to_save' => 'Registrate para guardar tu trabajo',
'agree_to_terms' =>'Estoy de acuerdo con los términos de Invoice Ninja :terms',
'agree_to_terms' => 'Estoy de acuerdo con los términos de Invoice Ninja :terms',
'terms_of_service' => 'Términos de servicio',
'email_taken' => 'Esta dirección de correo electrónico ya se ha registrado',
'working' => 'Procesando',
@ -414,7 +414,7 @@ return array(
'active' => 'Activo',
'pending' => 'Pendiente',
'deleted_user' => 'Usario eliminado con éxito',
'limit_users' => 'Lo sentimos, esta acción excederá el límite de ' . MAX_NUM_USERS . ' usarios',
'limit_users' => 'Lo sentimos, esta acción excederá el límite de '.MAX_NUM_USERS.' usarios',
'confirm_email_invoice' => '¿Estás seguro que quieres enviar esta factura?',
'confirm_email_quote' => '¿Estás seguro que quieres enviar este presupuesto?',
'confirm_recurring_email_invoice' => 'Se ha marcado esta factura como recurrente, estás seguro que quieres enviar esta factura?',
@ -435,7 +435,6 @@ return array(
'invalid_counter' => 'Para evitar posibles conflictos, por favor crea un prefijo de facturación y de presupuesto.',
'mark_sent' => 'Marcar como enviado',
'gateway_help_1' => ':link para registrarse en Authorize.net.',
'gateway_help_2' => ':link para registrarse en Authorize.net.',
'gateway_help_17' => ':link para obtener su firma API de PayPal.',
@ -745,7 +744,7 @@ return array(
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice, it\'s scheduled for :date',
'created_by_invoice' => 'Created by :invoice',
'primary_user' => 'Primary User',
'help' => 'Help',
@ -817,6 +816,14 @@ return array(
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
'verify_email' => 'Please visit the link in the account confirmation email to verify your email address.',
'basic_settings' => 'Basic Settings',
'pro' => 'Pro',
'gateways' => 'Payment Gateways',
'next_send_on' => 'Send Next: :date',
'no_longer_running' => 'This invoice is not scheduled to run',
'general_settings' => 'General Settings',
'customize' => 'Customize',
);

View File

@ -74,6 +74,7 @@ return array(
"has_credit" => "el cliente no tiene crédito suficiente.",
"notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
"has_counter" => 'The value must contain {$counter}',
/*
|--------------------------------------------------------------------------

View File

@ -262,7 +262,7 @@ return array(
'email_salutation' => 'Cher :name,',
'email_signature' => 'Cordialement,',
'email_from' => 'L\'équipe Invoice Ninja',
'user_email_footer' => 'Pour modifier vos paramètres de notification par courriel, veuillez visiter '.SITE_URL.'/company/notifications',
'user_email_footer' => 'Pour modifier vos paramètres de notification par courriel, veuillez visiter '.SITE_URL.'/settings/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',
'notification_invoice_sent_subject' => 'La facture :invoice a été envoyée au client :client',
@ -532,16 +532,16 @@ return array(
'invoice_footer' => 'Pied de facture',
'save_as_default_footer' => 'Définir comme pied de facture par défatu',
'token_management' => 'Token Management',
'tokens' => 'Tokens',
'add_token' => 'Add Token',
'show_deleted_tokens' => 'Show deleted tokens',
'deleted_token' => 'Successfully deleted token',
'created_token' => 'Successfully created token',
'updated_token' => 'Successfully updated token',
'edit_token' => 'Edit Token',
'delete_token' => 'Delete Token',
'token' => 'Token',
'token_management' => 'Gestion des jetons',
'tokens' => 'Jetons',
'add_token' => 'Ajouter jeton',
'show_deleted_tokens' => 'Voir les jetons supprimés',
'deleted_token' => 'Jeton supprimé avec succès',
'created_token' => 'Jeton crée avec succès',
'updated_token' => 'Jeton mis à jour avec succès',
'edit_token' => 'Editer jeton',
'delete_token' => 'Supprimer jeton',
'token' => 'Jeton',
'add_gateway' => 'Ajouter passerelle',
'delete_gateway' => 'Supprimer passerelle',
@ -560,17 +560,17 @@ return array(
'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',
'api_tokens' => 'Jetons d\'API',
'users_and_tokens' => 'Utilisateurs & jetons',
'account_login' => 'Connexion à votre compte',
'recover_password' => 'Recover your password',
'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',
'converted' => 'Converted',
'password_recovery' => 'Récupération du mot de passe',
'send_email' => 'Envoyer email',
'set_password' => 'Ajouter mot de passe',
'converted' => 'Converti',
'email_approved' => 'Email me when a quote is <b>approved</b>',
'notification_quote_approved_subject' => 'Quote :invoice was approved by :client',
@ -730,14 +730,13 @@ return array(
'invoice_to' => 'Facture pour',
'invoice_no' => 'Facture n°',
'recent_payments' => 'Paiements récents',
'outstanding' => 'Extraordinaire',
'outstanding' => 'Impayé',
'manage_companies' => 'Gérer les sociétés',
'total_revenue' => 'Revenu total',
'current_user' => 'Utilisateur actuel',
'new_recurring_invoice' => 'Nouvelle facture récurrente',
'recurring_invoice' => 'Facture récurrente',
'recurring_too_soon' => 'Il est trop tôt pour créer la prochaine facture récurrente',
'created_by_invoice' => 'Créé par :invoice',
'primary_user' => 'Utilisateur principal',
'help' => 'Aide',
@ -763,17 +762,17 @@ return array(
'iframe_url_help1' => 'Copy the following code to a page on your site.',
'iframe_url_help2' => 'You can test the feature by clicking \'View as recipient\' for an invoice.',
'auto_bill' => 'Auto Bill',
'auto_bill' => 'Facturation automatique',
'military_time' => '24 Hour Time',
'last_sent' => 'Last Sent',
'last_sent' => 'Dernier envoi',
'reminder_emails' => 'Reminder Emails',
'reminder_emails' => 'Emails de rappel',
'templates_and_reminders' => 'Templates & Reminders',
'subject' => 'Subject',
'body' => 'Body',
'first_reminder' => 'First Reminder',
'second_reminder' => 'Second Reminder',
'third_reminder' => 'Third Reminder',
'subject' => 'Sujet',
'body' => 'Corps',
'first_reminder' => 'Premier rappel',
'second_reminder' => 'Second rappel',
'third_reminder' => 'Troisième rappel',
'num_days_reminder' => 'Days after due date',
'reminder_subject' => 'Reminder: Invoice :invoice from :account',
'reset' => 'Reset',
@ -784,8 +783,8 @@ return array(
'last_sent_on' => 'Last sent on :date',
'page_expire' => 'This page will expire soon, :click_here to keep working',
'upcoming_quotes' => 'Upcoming Quotes',
'expired_quotes' => 'Expired Quotes',
'upcoming_quotes' => 'Devis à venir',
'expired_quotes' => 'Devis expirés',
'sign_up_using' => 'Sign up using',
'invalid_credentials' => 'These credentials do not match our records',
@ -797,9 +796,9 @@ return array(
'invoice_charges' => 'Invoice Charges',
'invitation_status' => [
'sent' => 'Email Sent',
'opened' => 'Email Openend',
'viewed' => 'Invoice Viewed',
'sent' => 'Email envoyé',
'opened' => 'Email ouvert',
'viewed' => 'Facture vue',
],
'notification_invoice_bounced' => 'We were unable to deliver Invoice :invoice to :contact.',
'notification_invoice_bounced_subject' => 'Unable to deliver Invoice :invoice',
@ -809,6 +808,16 @@ return array(
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
'verify_email' => 'Please visit the link in the account confirmation email to verify your email address.',
'basic_settings' => 'Basic Settings',
'pro' => 'Pro',
'gateways' => 'Passerelles de paiement',
'recurring_too_soon' => 'Il est trop tôt pour créer la prochaine facture récurrente, it\'s scheduled for :date',
'next_send_on' => 'Send Next: :date',
'no_longer_running' => 'This invoice is not scheduled to run',
'general_settings' => 'General Settings',
'customize' => 'Customize',
);

View File

@ -75,6 +75,7 @@ return array(
"has_credit" => "The client does not have enough credit.",
"notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
"has_counter" => 'The value must contain {$counter}',
/*
|--------------------------------------------------------------------------

View File

@ -262,7 +262,7 @@ return array(
'email_salutation' => 'Cher :name,',
'email_signature' => 'Cordialement,',
'email_from' => 'L\'équipe InvoiceNinja',
'user_email_footer' => 'Pour modifier vos paramètres de notification par courriel, veuillez visiter '.SITE_URL.'/company/notifications',
'user_email_footer' => 'Pour modifier vos paramètres de notification par courriel, veuillez visiter '.SITE_URL.'/settings/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',
'notification_invoice_sent_subject' => 'La facture :invoice a été envoyée au client :client',
@ -738,7 +738,7 @@ return array(
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice, it\'s scheduled for :date',
'created_by_invoice' => 'Created by :invoice',
'primary_user' => 'Primary User',
'help' => 'Help',
@ -810,6 +810,15 @@ return array(
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
'verify_email' => 'Please visit the link in the account confirmation email to verify your email address.',
'basic_settings' => 'Basic Settings',
'pro' => 'Pro',
'gateways' => 'Payment Gateways',
'next_send_on' => 'Send Next: :date',
'no_longer_running' => 'This invoice is not scheduled to run',
'general_settings' => 'General Settings',
'customize' => 'Customize',
);

View File

@ -75,6 +75,7 @@ return array(
"has_credit" => "Le client n'a pas un crédit suffisant.",
"notmasked" => "Les valeurs sont masquées",
"less_than" => 'The :attribute must be less than :value',
"has_counter" => 'The value must contain {$counter}',
/*
|--------------------------------------------------------------------------

View File

@ -262,7 +262,7 @@ return array(
'email_salutation' => 'Caro :name,',
'email_signature' => 'Distinti saluti,',
'email_from' => 'Il Team di InvoiceNinja',
'user_email_footer' => 'Per modificare le impostazioni di notifiche via email per favore accedi a: '.SITE_URL.'/company/notifications',
'user_email_footer' => 'Per modificare le impostazioni di notifiche via email per favore accedi a: '.SITE_URL.'/settings/notifications',
'invoice_link_message' => 'Per visualizzare la tua fattura del cliente clicca sul link qui sotto:',
'notification_invoice_paid_subject' => 'La fattura :invoice è stata pagata da :client',
'notification_invoice_sent_subject' => 'La fattura :invoice è stata inviata a :client',
@ -740,7 +740,7 @@ return array(
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice, it\'s scheduled for :date',
'created_by_invoice' => 'Created by :invoice',
'primary_user' => 'Primary User',
'help' => 'Help',
@ -812,5 +812,14 @@ return array(
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
'verify_email' => 'Please visit the link in the account confirmation email to verify your email address.',
'basic_settings' => 'Basic Settings',
'pro' => 'Pro',
'gateways' => 'Payment Gateways',
'next_send_on' => 'Send Next: :date',
'no_longer_running' => 'This invoice is not scheduled to run',
'general_settings' => 'General Settings',
'customize' => 'Customize',
);

View File

@ -74,6 +74,7 @@ return array(
"has_credit" => "The client does not have enough credit.",
"notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
"has_counter" => 'The value must contain {$counter}',
/*
|--------------------------------------------------------------------------

View File

@ -262,7 +262,7 @@ return array(
'email_salutation' => 'Dear :name,',
'email_signature' => 'Regards,',
'email_from' => 'The Invoice Ninja Team',
'user_email_footer' => 'To adjust your email notification settings please visit '.SITE_URL.'/company/notifications',
'user_email_footer' => 'To adjust your email notification settings please visit '.SITE_URL.'/settings/notifications',
'invoice_link_message' => 'To view your client invoice click the link below:',
'notification_invoice_paid_subject' => 'Invoice :invoice was paid by :client',
'notification_invoice_sent_subject' => 'Invoice :invoice was sent to :client',
@ -337,7 +337,7 @@ return array(
'fill_products_help' => 'Selecting a product will automatically <b>fill in the description and cost</b>',
'update_products' => 'Auto-update products',
'update_products_help' => 'Updating an invoice will automatically <b>update the product library</b>',
'create_product' => 'Create Product',
'create_product' => 'Add Product',
'edit_product' => 'Edit Product',
'archive_product' => 'Archive Product',
'updated_product' => 'Successfully updated product',
@ -747,7 +747,7 @@ return array(
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice, it\'s scheduled for :date',
'created_by_invoice' => 'Created by :invoice',
'primary_user' => 'Primary User',
'help' => 'Help',
@ -819,6 +819,15 @@ return array(
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
'verify_email' => 'Please visit the link in the account confirmation email to verify your email address.',
'basic_settings' => 'Basic Settings',
'pro' => 'Pro',
'gateways' => 'Payment Gateways',
'next_send_on' => 'Send Next: :date',
'no_longer_running' => 'This invoice is not scheduled to run',
'general_settings' => 'General Settings',
'customize' => 'Customize',
);

View File

@ -262,7 +262,7 @@ return array(
'email_salutation' => 'Kj&#230;re :name,',
'email_signature' => 'Med vennlig hilsen,',
'email_from' => 'The Invoice Ninja Team',
'user_email_footer' => 'For &#229; justere varslingsinnstillingene vennligst bes&#248;k '.SITE_URL.'/company/notifications',
'user_email_footer' => 'For &#229; justere varslingsinnstillingene vennligst bes&#248;k '.SITE_URL.'/settings/notifications',
'invoice_link_message' => 'Hvis du vil se din klientfaktura klikk p&#229; linken under:',
'notification_invoice_paid_subject' => 'Faktura :invoice betalt av :client',
'notification_invoice_sent_subject' => 'Faktura :invoice sendt til :client',
@ -745,7 +745,7 @@ return array(
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice, it\'s scheduled for :date',
'created_by_invoice' => 'Created by :invoice',
'primary_user' => 'Primary User',
'help' => 'Help',
@ -817,5 +817,14 @@ return array(
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
'verify_email' => 'Please visit the link in the account confirmation email to verify your email address.',
'basic_settings' => 'Basic Settings',
'pro' => 'Pro',
'gateways' => 'Payment Gateways',
'next_send_on' => 'Send Next: :date',
'no_longer_running' => 'This invoice is not scheduled to run',
'general_settings' => 'General Settings',
'customize' => 'Customize',
);

View File

@ -73,6 +73,7 @@ return array(
"has_credit" => "Klienten har ikke h&#248;y nok kreditt.",
"notmasked" => "Verdiene er skjult",
"less_than" => 'The :attribute must be less than :value',
"has_counter" => 'The value must contain {$counter}',
/*
|--------------------------------------------------------------------------

View File

@ -260,7 +260,7 @@ return array(
'email_salutation' => 'Beste :name,',
'email_signature' => 'Met vriendelijke groeten,',
'email_from' => 'Het InvoiceNinja Team',
'user_email_footer' => 'Ga alstublieft naar '.SITE_URL.'/company/notifications om je e-mail notificatie instellingen aan te passen ',
'user_email_footer' => 'Ga alstublieft naar '.SITE_URL.'/settings/notifications om je e-mail notificatie instellingen aan te passen ',
'invoice_link_message' => 'Klik op volgende link om de Factuur van je klant te bekijken:',
'notification_invoice_paid_subject' => 'Factuur :invoice is betaald door :client',
'notification_invoice_sent_subject' => 'Factuur :invoice is gezonden door :client',
@ -740,7 +740,6 @@ return array(
'current_user' => 'Huidige gebruiker',
'new_recurring_invoice' => 'Nieuwe wederkerende factuur',
'recurring_invoice' => 'Wederkerende factuur',
'recurring_too_soon' => 'Het is te vroeg om de volgende wederkerende factuur aan te maken',
'created_by_invoice' => 'Aangemaakt door :invoice',
'primary_user' => 'Primaire gebruiker',
'help' => 'Help',
@ -812,5 +811,15 @@ return array(
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
'verify_email' => 'Please visit the link in the account confirmation email to verify your email address.',
'basic_settings' => 'Basic Settings',
'pro' => 'Pro',
'gateways' => 'Payment Gateways',
'recurring_too_soon' => 'Het is te vroeg om de volgende wederkerende factuur aan te maken, it\'s scheduled for :date',
'next_send_on' => 'Send Next: :date',
'no_longer_running' => 'This invoice is not scheduled to run',
'general_settings' => 'General Settings',
'customize' => 'Customize',
);

View File

@ -75,6 +75,7 @@ return array(
"has_credit" => "De klant heeft niet voldoende krediet.",
"notmasked" => "De waarden zijn verborgen",
"less_than" => 'Het :attribute moet minder zijn dan :value',
"has_counter" => 'The value must contain {$counter}',
/*
|--------------------------------------------------------------------------

View File

@ -15,10 +15,10 @@ return array(
"password" => "Senhas devem possuir no mínimo seis caracteres e devem ser iguais.",
"user" => "Não achamos um usuário com o endereço de e-mail informado.",
"user" => "Não foi encontrado um usuário com o endereço de e-mail informado.",
"token" => "Este token de redefinição de senha é inválido.",
"sent" => "Lmebrete de senha enviado!",
"sent" => "Lembrete de senha enviado!",
);

File diff suppressed because it is too large Load Diff

View File

@ -17,17 +17,17 @@ return array(
"active_url" => ":attribute não é uma URL válida.",
"after" => ":attribute deve ser uma data maior que :date.",
"alpha" => ":attribute deve conter apenas letras.",
"alpha_dash" => ":attribute pode conter apenas letras, número e traços",
"alpha_dash" => ":attribute pode conter apenas letras, número e hífens",
"alpha_num" => ":attribute pode conter apenas letras e números.",
"array" => ":attribute deve ser um array.",
"array" => ":attribute deve ser uma lista.",
"before" => ":attribute deve ser uma data anterior a :date.",
"between" => array(
"numeric" => ":attribute deve ser entre :min - :max.",
"file" => ":attribute deve ser entre :min - :max kilobytes.",
"string" => ":attribute deve ser entre :min - :max caracteres.",
"numeric" => ":attribute deve estar entre :min - :max.",
"file" => ":attribute deve estar entre :min - :max kilobytes.",
"string" => ":attribute deve estar entre :min - :max caracteres.",
"array" => ":attribute deve conter entre :min - :max itens.",
),
"confirmed" => ":attribute confirmação não correponde.",
"confirmed" => ":attribute confirmação não corresponde.",
"date" => ":attribute não é uma data válida.",
"date_format" => ":attribute não satisfaz o formato :format.",
"different" => ":attribute e :other devem ser diferentes.",
@ -71,8 +71,9 @@ return array(
"positive" => ":attribute deve ser maior que zero.",
"has_credit" => "O cliente não possui crédito suficiente.",
"notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
"notmasked" => "Os valores são mascarados",
"less_than" => ':attribute deve ser menor que :value',
"has_counter" => 'O valor deve conter {$counter}',
/*
|--------------------------------------------------------------------------

View File

@ -262,7 +262,7 @@ return array(
'email_salutation' => 'Hej :name,',
'email_signature' => 'Vänliga hälsningar,',
'email_from' => 'Invoice Ninja teamet',
'user_email_footer' => 'För att anpassa dina e-post notifieringar gå till '.SITE_URL.'/company/notifications',
'user_email_footer' => 'För att anpassa dina e-post notifieringar gå till '.SITE_URL.'/settings/notifications',
'invoice_link_message' => 'För att se din kundfaktura klicka på länken nedan:',
'notification_invoice_paid_subject' => 'Faktura :invoice är betald av :client',
'notification_invoice_sent_subject' => 'Faktura :invoice är skickad till :client',
@ -743,7 +743,7 @@ return array(
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice, it\'s scheduled for :date',
'created_by_invoice' => 'Created by :invoice',
'primary_user' => 'Primary User',
'help' => 'Help',
@ -815,5 +815,14 @@ return array(
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
'verify_email' => 'Please visit the link in the account confirmation email to verify your email address.',
'basic_settings' => 'Basic Settings',
'pro' => 'Pro',
'gateways' => 'Payment Gateways',
'next_send_on' => 'Send Next: :date',
'no_longer_running' => 'This invoice is not scheduled to run',
'general_settings' => 'General Settings',
'customize' => 'Customize',
);

View File

@ -77,6 +77,7 @@ return [
"has_credit" => "The client does not have enough credit.",
"notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
"has_counter" => 'The value must contain {$counter}',
/*
|--------------------------------------------------------------------------

View File

@ -1,9 +1,11 @@
@extends('accounts.nav')
@extends('header')
@section('content')
@parent
{!! Former::open($url)->method($method)->rule()->addClass('col-md-8 col-md-offset-2 warn-on-exit') !!}
@include('accounts.nav', ['selected' => ACCOUNT_PAYMENTS])
{!! Former::open($url)->method($method)->rule()->addClass('warn-on-exit') !!}
{!! Former::populate($account) !!}
@ -11,7 +13,7 @@
<div class="panel-heading">
<h3 class="panel-title">{!! trans($title) !!}</h3>
</div>
<div class="panel-body">
<div class="panel-body form-padding-right">
@if ($accountGateway)
{!! Former::populateField('gateway_id', $accountGateway->gateway_id) !!}
@ -106,7 +108,7 @@
<p/>&nbsp;<p/>
{!! Former::actions(
$countGateways > 0 ? Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/company/payments'))->appendIcon(Icon::create('remove-circle')) : false,
$countGateways > 0 ? Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/settings/online_payments'))->appendIcon(Icon::create('remove-circle')) : false,
Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk'))) !!}
{!! Former::close() !!}

View File

@ -1,8 +1,8 @@
@extends('accounts.nav')
@extends('header')
@section('content')
@parent
@include('accounts.nav_advanced')
@include('accounts.nav', ['selected' => ACCOUNT_API_TOKENS, 'advanced' => true])
{!! Former::open('tokens/delete')->addClass('user-form') !!}

View File

@ -1,16 +1,13 @@
@extends('accounts.nav')
@extends('header')
@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>
<script src="{{ asset('js/pdf.built.js') }}" type="text/javascript"></script>
<style type="text/css">
@ -28,9 +25,6 @@
@section('content')
@parent
@include('accounts.nav_advanced')
<script>
var invoiceDesigns = {!! $invoiceDesigns !!};
@ -155,17 +149,15 @@
{!! Former::select('invoice_design_id')->style('display:inline;width:120px')->fromQuery($invoiceDesigns, 'name', 'id')->onchange('onSelectChange()')->raw() !!}
<div class="pull-right">
{!! Button::normal(trans('texts.help'))->withAttributes(['onclick' => 'showHelp()'])->appendIcon(Icon::create('question-sign')) !!}
{!! Button::normal(trans('texts.cancel'))->asLinkTo(URL::to('/company/advanced_settings/invoice_design'))->appendIcon(Icon::create('remove-circle')) !!}
@if (Auth::user()->isPro())
{!! Button::success(trans('texts.save'))->withAttributes(['onclick' => 'submitForm()'])->appendIcon(Icon::create('floppy-disk')) !!}
@endif
{!! Button::normal(trans('texts.cancel'))->asLinkTo(URL::to('/settings/invoice_design'))->appendIcon(Icon::create('remove-circle')) !!}
{!! Button::success(trans('texts.save'))->withAttributes(['onclick' => 'submitForm()'])->appendIcon(Icon::create('floppy-disk'))->withAttributes(['class' => 'save-button']) !!}
</div>
</div>
<script>
@if (!Auth::user()->isPro())
$(function() {
$('form.warn-on-exit input').prop('disabled', true);
$('form.warn-on-exit input, .save-button').prop('disabled', true);
});
@endif

View File

@ -1,4 +1,4 @@
@extends('accounts.nav')
@extends('header')
@section('content')
@parent
@ -13,27 +13,20 @@
{!! Former::open_for_files()->addClass('warn-on-exit')->rules(array(
'name' => 'required',
'email' => 'email|required'
)) !!}
{{ Former::populate($account) }}
{{ Former::populateField('military_time', intval($account->military_time)) }}
{{ Former::populateField('first_name', $user->first_name) }}
{{ Former::populateField('last_name', $user->last_name) }}
{{ Former::populateField('email', $user->email) }}
{{ Former::populateField('phone', $user->phone) }}
@if (Utils::isNinjaDev())
{{ Former::populateField('dark_mode', intval($user->dark_mode)) }}
@endif
@include('accounts.nav', ['selected' => ACCOUNT_COMPANY_DETAILS])
<div class="row">
<div class="col-md-6">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.details') !!}</h3>
</div>
<div class="panel-body">
<div class="panel-body form-padding-right">
{!! Former::text('name') !!}
{!! Former::text('id_number') !!}
@ -58,7 +51,7 @@
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.address') !!}</h3>
</div>
<div class="panel-body">
<div class="panel-body form-padding-right">
{!! Former::text('address1') !!}
{!! Former::text('address2') !!}
@ -72,133 +65,13 @@
</div>
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.user_details') !!}</h3>
</div>
<div class="panel-body">
{!! Former::text('first_name') !!}
{!! Former::text('last_name') !!}
{!! Former::text('email') !!}
{!! Former::text('phone') !!}
@if (Utils::isNinja())
{!! Former::plaintext('oneclick_login')->value(
$user->oauth_provider_id ?
$oauthProviderName . ' - ' . link_to('#', trans('texts.disable'), ['onclick' => 'disableSocialLogin()']) :
DropdownButton::primary(trans('texts.enable'))->withContents($oauthLoginUrls)->small()
) !!}
@endif
@if (Utils::isNinja() && $user->confirmed)
@if ($user->referral_code)
{!! Former::plaintext('referral_code')
->value($user->referral_code . ' <a href="'.REFERRAL_PROGRAM_URL.'" target="_blank" title="'.trans('texts.learn_more').'">' . Icon::create('question-sign') . '</a>') !!}
@else
{!! Former::checkbox('referral_code')
->text(trans('texts.enable') . ' <a href="'.REFERRAL_PROGRAM_URL.'" target="_blank" title="'.trans('texts.learn_more').'">' . Icon::create('question-sign') . '</a>') !!}
@endif
@endif
@if (false && Utils::isNinjaDev())
{!! Former::checkbox('dark_mode')->text(trans('texts.dark_mode_help')) !!}
@endif
@if (Utils::isNinja())
<br/>
@if (Auth::user()->confirmed)
{!! Former::actions( Button::primary(trans('texts.change_password'))->small()->withAttributes(['onclick'=>'showChangePassword()'])) !!}
@elseif (Auth::user()->registered)
{!! Former::actions( Button::primary(trans('texts.resend_confirmation'))->asLinkTo(URL::to('/resend_confirmation'))->small() ) !!}
@endif
@endif
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.localization') !!}</h3>
</div>
<div class="panel-body">
{!! Former::select('currency_id')->addOption('','')
->fromQuery($currencies, 'name', 'id') !!}
{!! Former::select('language_id')->addOption('','')
->fromQuery($languages, 'name', 'id') !!}
{!! Former::select('timezone_id')->addOption('','')
->fromQuery($timezones, 'location', 'id') !!}
{!! Former::select('date_format_id')->addOption('','')
->fromQuery($dateFormats, 'label', 'id') !!}
{!! Former::select('datetime_format_id')->addOption('','')
->fromQuery($datetimeFormats, 'label', 'id') !!}
{!! Former::checkbox('military_time')->text(trans('texts.enable')) !!}
</div>
</div>
</div>
</div>
<center>
{!! Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) !!}
</center>
<div class="modal fade" id="passwordModal" tabindex="-1" role="dialog" aria-labelledby="passwordModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="passwordModalLabel">{{ trans('texts.change_password') }}</h4>
</div>
<div style="background-color: #fff" id="changePasswordDiv" onkeyup="validateChangePassword()" onclick="validateChangePassword()" onkeydown="checkForEnter(event)">
&nbsp;
{!! Former::password('current_password')->style('width:300px') !!}
{!! Former::password('newer_password')->style('width:300px')->label(trans('texts.new_password')) !!}
{!! Former::password('confirm_password')->style('width:300px') !!}
&nbsp;
<br/>
<center>
<div id="changePasswordError"></div>
</center>
<br/>
</div>
<div style="padding-left:40px;padding-right:40px;display:none;min-height:130px" id="working">
<h3>{{ trans('texts.working') }}...</h3>
<div class="progress progress-striped active">
<div class="progress-bar" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"></div>
</div>
</div>
<div style="background-color: #fff; padding-right:20px;padding-left:20px; display:none" id="successDiv">
<br/>
<h3>{{ trans('texts.success') }}</h3>
{{ trans('texts.updated_password') }}
<br/>
&nbsp;
<br/>
</div>
<div class="modal-footer" style="margin-top: 0px" id="changePasswordFooter">
<button type="button" class="btn btn-default" id="cancelChangePasswordButton" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-success" onclick="submitChangePassword()" id="changePasswordButton" disabled>
{{ trans('texts.save') }}
<i class="glyphicon glyphicon-floppy-disk"></i>
</button>
</div>
</div>
</div>
</div>
{!! Former::close() !!}
{!! Form::open(['url' => 'remove_logo', 'class' => 'removeLogoForm']) !!}
@ -209,21 +82,6 @@
$(function() {
$('#country_id').combobox();
$('#passwordModal').on('hidden.bs.modal', function () {
$(['current_password', 'newer_password', 'confirm_password']).each(function(i, field) {
var $input = $('form #'+field);
$input.val('');
$input.closest('div.form-group').removeClass('has-success');
});
$('#changePasswordButton').prop('disabled', true);
})
$('#passwordModal').on('shown.bs.modal', function () {
$('#current_password').focus();
})
localStorage.setItem('auth_provider', '{{ strtolower($oauthProviderName) }}');
});
function deleteLogo() {
@ -232,84 +90,6 @@
}
}
function showChangePassword() {
$('#passwordModal').modal('show');
}
function checkForEnter(event)
{
if (event.keyCode === 13){
event.preventDefault();
return false;
}
}
function validateChangePassword(showError)
{
var isFormValid = true;
$(['current_password', 'newer_password', 'confirm_password']).each(function(i, field) {
var $input = $('form #'+field),
val = $.trim($input.val());
var isValid = val && val.length >= 6;
if (isValid && field == 'confirm_password') {
isValid = val == $.trim($('#newer_password').val());
}
if (isValid) {
$input.closest('div.form-group').removeClass('has-error').addClass('has-success');
} else {
isFormValid = false;
$input.closest('div.form-group').removeClass('has-success');
if (showError) {
$input.closest('div.form-group').addClass('has-error');
}
}
});
$('#changePasswordButton').prop('disabled', !isFormValid);
return isFormValid;
}
function submitChangePassword()
{
if (!validateChangePassword(true)) {
return;
}
$('#changePasswordDiv, #changePasswordFooter').hide();
$('#working').show();
$.ajax({
type: 'POST',
url: '{{ URL::to('/users/change_password') }}',
data: 'current_password=' + encodeURIComponent($('form #current_password').val()) +
'&new_password=' + encodeURIComponent($('form #newer_password').val()) +
'&confirm_password=' + encodeURIComponent($('form #confirm_password').val()),
success: function(result) {
if (result == 'success') {
NINJA.formIsChanged = false;
$('#changePasswordButton').hide();
$('#successDiv').show();
$('#cancelChangePasswordButton').html('{{ trans('texts.close') }}');
} else {
$('#changePasswordError').html(result);
$('#changePasswordDiv').show();
}
$('#changePasswordFooter').show();
$('#working').hide();
}
});
}
function disableSocialLogin() {
if (!confirm("{!! trans('texts.are_you_sure') !!}")) {
return;
}
window.location = '{{ URL::to('/auth_unlink') }}';
}
</script>
@stop

View File

@ -1,7 +1,8 @@
@extends('accounts.nav')
@extends('header')
@section('content')
@parent
@include('accounts.nav', ['selected' => ACCOUNT_IMPORT_EXPORT])
{{ Former::open()->addClass('col-md-9 col-md-offset-1') }}
{{ Former::legend('Export Client Data') }}

View File

@ -1,9 +1,11 @@
@extends('accounts.nav')
@extends('header')
@section('content')
@parent
{!! Former::open_for_files('company/import_map')->addClass('col-md-8 col-md-offset-2') !!}
@include('accounts.nav', ['selected' => ACCOUNT_IMPORT_EXPORT])
{!! Former::open_for_files('settings/' . ACCOUNT_MAP) !!}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.import_clients') !!}</h3>
@ -16,7 +18,7 @@
{!! Former::close() !!}
{!! Former::open('company/export')->addClass('col-md-8 col-md-offset-2') !!}
{!! Former::open('settings/' . ACCOUNT_EXPORT) !!}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.export_clients') !!}</h3>
@ -28,7 +30,7 @@
{!! Former::close() !!}
{!! Former::open('company/cancel_account')->addClass('col-md-8 col-md-offset-2 cancel-account') !!}
{!! Former::open('settings/cancel_account')->addClass('cancel-account') !!}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.cancel_account') !!}</h3>

View File

@ -1,9 +1,11 @@
@extends('accounts.nav')
@extends('header')
@section('content')
@parent
{!! Former::open('company/import_export')->addClass('col-md-8 col-md-offset-2 warn-on-exit') !!}
@include('accounts.nav', ['selected' => ACCOUNT_IMPORT_EXPORT])
{!! Former::open('settings/' . ACCOUNT_IMPORT_EXPORT)->addClass('warn-on-exit') !!}
<div class="panel panel-default">
<div class="panel-heading">
@ -46,7 +48,7 @@
{!! Former::actions(
Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/company/import_export'))->appendIcon(Icon::create('remove-circle')),
Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/settings/import_export'))->appendIcon(Icon::create('remove-circle')),
Button::success(trans('texts.import'))->submit()->large()->appendIcon(Icon::create('floppy-disk'))) !!}
{!! Former::close() !!}

View File

@ -1,18 +1,15 @@
@extends('accounts.nav')
@extends('header')
@section('head')
@parent
<script src="{{ asset('js/pdf_viewer.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/compatibility.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/pdfmake.min.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/vfs_fonts.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/pdf.built.js') }}" type="text/javascript"></script>
@stop
@section('content')
@parent
@include('accounts.nav_advanced')
@include('accounts.nav', ['selected' => ACCOUNT_INVOICE_DESIGN, 'advanced' => true])
<script>
var invoiceDesigns = {!! $invoiceDesigns !!};
@ -40,7 +37,7 @@
NINJA.secondaryColor = $('#secondary_color').val();
NINJA.fontSize = parseInt($('#font_size').val());
var fields = ['item', 'description', 'unit_cost', 'quantity'];
var fields = ['item', 'description', 'unit_cost', 'quantity', 'line_total'];
invoiceLabels.old = {};
for (var i=0; i<fields.length; i++) {
var field = fields[i];
@ -76,7 +73,7 @@
<div class="row">
<div class="col-md-6">
<div class="col-md-12">
{!! Former::open()->addClass('warn-on-exit')->onchange('refreshPDF()') !!}
{!! Former::populate($account) !!}
@ -91,58 +88,74 @@
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.invoice_design') !!}</h3>
</div>
<div class="panel-body form-padding-right">
<div role="tabpanel">
<ul class="nav nav-tabs" role="tablist" style="border: none">
<li role="presentation" class="active"><a href="#generalSettings" aria-controls="generalSettings" role="tab" data-toggle="tab">{{ trans('texts.general_settings') }}</a></li>
<li role="presentation"><a href="#invoiceLabels" aria-controls="invoiceLabels" role="tab" data-toggle="tab">{{ trans('texts.invoice_labels') }}</a></li>
<li role="presentation"><a href="#invoiceOptions" aria-controls="invoiceOptions" role="tab" data-toggle="tab">{{ trans('texts.invoice_options') }}</a></li>
</ul>
</div>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="generalSettings">
<div class="panel-body">
@if (!Utils::isPro() || \App\Models\InvoiceDesign::count() == COUNT_FREE_DESIGNS_SELF_HOST)
{!! Former::select('invoice_design_id')->style('display:inline;width:120px')->fromQuery($invoiceDesigns, 'name', 'id')->addOption(trans('texts.more_designs') . '...', '-1') !!}
{!! Former::select('invoice_design_id')->style('display:inline')->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') !!}
{!! Former::select('invoice_design_id')->style('display:inline')->fromQuery($invoiceDesigns, 'name', 'id') !!}
@endif
{!! Former::text('font_size')->type('number')->min('0')->step('1')->style('width:120px') !!}
{!! Former::text('font_size')->type('number')->min('0')->step('1') !!}
{!! 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>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.invoice_labels') !!}</h3>
</div>
<div role="tabpanel" class="tab-pane" id="invoiceLabels">
<div class="panel-body">
{!! Former::text('labels_item')->label(trans('texts.item')) !!}
{!! Former::text('labels_description')->label(trans('texts.description')) !!}
{!! Former::text('labels_unit_cost')->label(trans('texts.unit_cost')) !!}
{!! Former::text('labels_quantity')->label(trans('texts.quantity')) !!}
{!! Former::text('labels_line_total')->label(trans('texts.line_total')) !!}
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.invoice_options') !!}</h3>
</div>
<div role="tabpanel" class="tab-pane" id="invoiceOptions">
<div class="panel-body">
{!! Former::checkbox('hide_quantity')->text(trans('texts.hide_quantity_help')) !!}
{!! Former::checkbox('hide_paid_to_date')->text(trans('texts.hide_paid_to_date_help')) !!}
</div>
</div>
</div>
</div>
</div>
@if (Auth::user()->isPro())
{!! Former::actions( Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk'))) !!}
@else
<br/>
{!! Former::actions(
Button::primary(trans('texts.customize'))
->appendIcon(Icon::create('edit'))
->asLinkTo(URL::to('/settings/customize_design'))
->large(),
Button::success(trans('texts.save'))
->submit()->large()
->appendIcon(Icon::create('floppy-disk'))
->withAttributes(['class' => 'save-button'])
) !!}
<br/>
@if (!Auth::user()->isPro())
<script>
$(function() {
$('form.warn-on-exit input').prop('disabled', true);
$('form.warn-on-exit input, .save-button').prop('disabled', true);
});
</script>
@endif
@ -150,11 +163,10 @@
{!! Former::close() !!}
</div>
<div class="col-md-6">
</div>
@include('invoices.pdf', ['account' => Auth::user()->account, 'pdfHeight' => 800])
</div>
</div>
@stop

View File

@ -1,4 +1,4 @@
@extends('accounts.nav')
@extends('header')
@section('head')
@parent
@ -10,7 +10,7 @@
.input-group-addon div.checkbox {
display: inline;
}
span.input-group-addon {
.tab-content .pad-checkbox span.input-group-addon {
padding-right: 30px;
}
</style>
@ -18,9 +18,9 @@
@section('content')
@parent
@include('accounts.nav_advanced')
@include('accounts.nav', ['selected' => ACCOUNT_INVOICE_SETTINGS, 'advanced' => true])
{!! Former::open()->addClass('col-md-8 col-md-offset-2 warn-on-exit') !!}
{!! Former::open()->rules(['iframe_url' => 'url'])->addClass('warn-on-exit') !!}
{{ Former::populate($account) }}
{{ Former::populateField('custom_invoice_taxes1', intval($account->custom_invoice_taxes1)) }}
{{ Former::populateField('custom_invoice_taxes2', intval($account->custom_invoice_taxes2)) }}
@ -32,9 +32,11 @@
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.email_settings') !!}</h3>
</div>
<div class="panel-body">
<div class="panel-body form-padding-right">
{!! Former::checkbox('pdf_email_attachment')->text(trans('texts.enable')) !!}
@if (Utils::isNinja())
{{-- Former::select('recurring_hour')->options($recurringHours) --}}
{!! Former::inline_radios('custom_invoice_link')
->onchange('onCustomLinkChange()')
->radios([
@ -42,17 +44,18 @@
trans('texts.website') => ['value' => 'website', 'name' => 'custom_link'],
])->check($account->iframe_url ? 'website' : 'subdomain') !!}
{{ Former::setOption('capitalize_translations', false) }}
{!! Former::text('subdomain')
->placeholder(trans('texts.www'))
->onchange('onSubdomainChange()')
->addGroupClass('subdomain')
->label(' ') !!}
{!! Former::text('iframe_url')
->placeholder('http://www.example.com/invoice')
->appendIcon('question-sign')
->addGroupClass('iframe_url')
->label(' ') !!}
@endif
</div>
</div>
@ -60,7 +63,8 @@
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.invoice_quote_number') !!}</h3>
</div>
<div class="panel-body">
<div class="panel-body form-padding-right">
<div role="tabpanel">
<ul class="nav nav-tabs" role="tablist" style="border: none">
<li role="presentation" class="active"><a href="#invoiceNumber" aria-controls="invoiceNumber" role="tab" data-toggle="tab">{{ trans('texts.invoice_number') }}</a></li>
@ -70,18 +74,56 @@
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="invoiceNumber">
<div class="panel-body">
{!! Former::text('invoice_number_prefix')->label(trans('texts.prefix')) !!}
{!! Former::text('invoice_number_counter')->label(trans('texts.counter')) !!}
{!! Former::inline_radios('invoice_number_type')
->onchange('onInvoiceNumberTypeChange()')
->label(trans('texts.type'))
->radios([
trans('texts.prefix') => ['value' => 'prefix', 'name' => 'invoice_number_type'],
trans('texts.pattern') => ['value' => 'pattern', 'name' => 'invoice_number_type'],
])->check($account->invoice_number_pattern ? 'pattern' : 'prefix') !!}
{!! Former::text('invoice_number_prefix')
->addGroupClass('invoice-prefix')
->label(' ') !!}
{!! Former::text('invoice_number_pattern')
->appendIcon('question-sign')
->addGroupClass('invoice-pattern')
->label(' ')
->addGroupClass('number-pattern') !!}
{!! Former::text('invoice_number_counter')
->label(trans('texts.counter')) !!}
</div>
</div>
<div role="tabpanel" class="tab-pane" id="quoteNumber">
<div class="panel-body">
{!! Former::text('quote_number_prefix')->label(trans('texts.prefix')) !!}
{!! Former::text('quote_number_counter')->label(trans('texts.counter'))
->append(Former::checkbox('share_counter')->raw()->onclick('setQuoteNumberEnabled()') . ' ' . trans('texts.share_invoice_counter')) !!}
{!! Former::inline_radios('quote_number_type')
->onchange('onQuoteNumberTypeChange()')
->label(trans('texts.type'))
->radios([
trans('texts.prefix') => ['value' => 'prefix', 'name' => 'quote_number_type'],
trans('texts.pattern') => ['value' => 'pattern', 'name' => 'quote_number_type'],
])->check($account->quote_number_pattern ? 'pattern' : 'prefix') !!}
{!! Former::text('quote_number_prefix')
->addGroupClass('quote-prefix')
->label(' ') !!}
{!! Former::text('quote_number_pattern')
->appendIcon('question-sign')
->addGroupClass('quote-pattern')
->addGroupClass('number-pattern')
->label(' ') !!}
{!! Former::text('quote_number_counter')
->label(trans('texts.counter'))
->addGroupClass('pad-checkbox')
->append(Former::checkbox('share_counter')->raw()
->onclick('setQuoteNumberEnabled()') . ' ' . trans('texts.share_invoice_counter')) !!}
</div>
</div>
</div>
</div>
</div>
@ -90,7 +132,7 @@
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.custom_fields') !!}</h3>
</div>
<div class="panel-body">
<div class="panel-body form-padding-right">
<div role="tabpanel">
<ul class="nav nav-tabs" role="tablist" style="border: none">
@ -131,10 +173,16 @@
<div role="tabpanel" class="tab-pane" id="invoiceCharges">
<div class="panel-body">
{!! Former::text('custom_invoice_label1')->label(trans('texts.field_label'))
->append(Former::checkbox('custom_invoice_taxes1')->raw() . trans('texts.charge_taxes')) !!}
{!! Former::text('custom_invoice_label2')->label(trans('texts.field_label'))
->append(Former::checkbox('custom_invoice_taxes2')->raw() . trans('texts.charge_taxes')) !!}
{!! Former::text('custom_invoice_label1')
->label(trans('texts.field_label'))
->addGroupClass('pad-checkbox')
->append(Former::checkbox('custom_invoice_taxes1')
->raw() . trans('texts.charge_taxes')) !!}
{!! Former::text('custom_invoice_label2')
->label(trans('texts.field_label'))
->addGroupClass('pad-checkbox')
->append(Former::checkbox('custom_invoice_taxes2')
->raw() . trans('texts.charge_taxes')) !!}
</div>
</div>
@ -147,12 +195,6 @@
<center>
{!! Button::success(trans('texts.save'))->large()->submit()->appendIcon(Icon::create('floppy-disk')) !!}
</center>
@else
<script>
$(function() {
$('form.warn-on-exit input').prop('disabled', true);
});
</script>
@endif
@ -185,6 +227,40 @@
</div>
</div>
<div class="modal fade" id="patternHelpModal" tabindex="-1" role="dialog" aria-labelledby="patternHelpModalLabel" aria-hidden="true">
<div class="modal-dialog" style="min-width:150px">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="patternHelpModalLabel">{{ trans('texts.pattern_help_title') }}</h4>
</div>
<div class="modal-body">
<p>{{ trans('texts.pattern_help_1') }}</p>
<p>{{ trans('texts.pattern_help_2') }}</p>
<ul>
@foreach (\App\Models\Invoice::$patternFields as $field)
@if ($field == 'date:')
<li>{$date:format} - {!! link_to(PHP_DATE_FORMATS, trans('texts.see_options'), ['target' => '_blank']) !!}</li>
@else
<li>{${{ $field }}}</li>
@endif
@endforeach
</ul>
<p>{{ trans('texts.pattern_help_3', [
'example' => '{$year}-{$counter}',
'value' => date('Y') . '-0001'
]) }}</p>
</div>
<div class="modal-footer" style="margin-top: 0px">
<button type="button" class="btn btn-primary" data-dismiss="modal">{{ trans('texts.close') }}</button>
</div>
</div>
</div>
</div>
{!! Former::close() !!}
@ -216,13 +292,41 @@
}
}
function onInvoiceNumberTypeChange() {
var val = $('input[name=invoice_number_type]:checked').val()
if (val == 'prefix') {
$('.invoice-prefix').show();
$('.invoice-pattern').hide();
} else {
$('.invoice-prefix').hide();
$('.invoice-pattern').show();
}
}
function onQuoteNumberTypeChange() {
var val = $('input[name=quote_number_type]:checked').val()
if (val == 'prefix') {
$('.quote-prefix').show();
$('.quote-pattern').hide();
} else {
$('.quote-prefix').hide();
$('.quote-pattern').show();
}
}
$('.iframe_url .input-group-addon').click(function() {
$('#iframeHelpModal').modal('show');
});
$('.number-pattern .input-group-addon').click(function() {
$('#patternHelpModal').modal('show');
});
$(function() {
setQuoteNumberEnabled();
onCustomLinkChange();
onInvoiceNumberTypeChange();
onQuoteNumberTypeChange();
$('#subdomain').change(function() {
$('#iframe_url').val('');

View File

@ -0,0 +1,46 @@
@extends('header')
@section('content')
@parent
{!! Former::open_for_files()->addClass('warn-on-exit') !!}
{{ Former::populate($account) }}
{{ Former::populateField('military_time', intval($account->military_time)) }}
@include('accounts.nav', ['selected' => ACCOUNT_LOCALIZATION])
<div class="row">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.localization') !!}</h3>
</div>
<div class="panel-body form-padding-right">
{!! Former::select('currency_id')->addOption('','')
->fromQuery($currencies, 'name', 'id') !!}
{!! Former::select('language_id')->addOption('','')
->fromQuery($languages, 'name', 'id') !!}
{!! Former::select('timezone_id')->addOption('','')
->fromQuery($timezones, 'location', 'id') !!}
{!! Former::select('date_format_id')->addOption('','')
->fromQuery($dateFormats, 'label', 'id') !!}
{!! Former::select('datetime_format_id')->addOption('','')
->fromQuery($datetimeFormats, 'label', 'id') !!}
{!! Former::checkbox('military_time')->text(trans('texts.enable')) !!}
</div>
</div>
</div>
<center>
{!! Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) !!}
</center>
{!! Former::close() !!}
@stop
@section('onReady')
$('#currency_id').focus();
@stop

View File

@ -1,16 +1,33 @@
@extends('header')
@if (!Utils::isPro() && isset($advanced) && $advanced)
<div class="alert alert-warning" style="font-size:larger;">
<center>
{!! trans('texts.pro_plan_advanced_settings', ['link'=>'<a href="#" onclick="showProPlan(\''.$selected.'\')">'.trans('texts.pro_plan.remove_logo_link').'</a>']) !!}
</center>
</div>
@endif
@section('content')
<div class="row">
<ul class="nav nav-tabs nav nav-justified">
{!! HTML::nav_link('company/details', 'company_details') !!}
{!! HTML::nav_link('company/payments', 'online_payments', 'gateways') !!}
{!! 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_design', 'advanced_settings', '*/advanced_settings/*') !!}
</ul>
<div class="col-md-3">
@foreach([
BASIC_SETTINGS => \App\Models\Account::$basicSettings,
ADVANCED_SETTINGS => \App\Models\Account::$advancedSettings,
] as $type => $settings)
<div class="panel panel-default">
<div class="panel-heading" style="color:white">
{{ trans("texts.{$type}") }}
@if ($type == ADVANCED_SETTINGS && !Utils::isPro())
<sup>{{ strtoupper(trans('texts.pro')) }}</sup>
@endif
</div>
<div class="list-group">
@foreach ($settings as $section)
<a href="{{ URL::to("settings/{$section}") }}" class="list-group-item {{ $selected === $section ? 'selected' : '' }}"
style="width:100%;text-align:left">{{ trans("texts.{$section}") }}</a>
@endforeach
</div>
</div>
@endforeach
</div>
<br/>
@stop
<div class="col-md-9">

View File

@ -1,9 +1,11 @@
@extends('accounts.nav')
@extends('header')
@section('content')
@parent
{!! Former::open()->addClass('col-md-8 col-md-offset-2 warn-on-exit') !!}
@include('accounts.nav', ['selected' => ACCOUNT_NOTIFICATIONS])
{!! Former::open()->addClass('warn-on-exit') !!}
{{ Former::populate($account) }}
{{ Former::populateField('notify_sent', intval(Auth::user()->notify_sent)) }}
{{ Former::populateField('notify_viewed', intval(Auth::user()->notify_viewed)) }}
@ -41,13 +43,12 @@
<div class="fb-follow" data-href="https://www.facebook.com/invoiceninja" data-colorscheme="light" data-layout="button" data-show-faces="false"></div>&nbsp;&nbsp;
<a href="https://twitter.com/invoiceninja" class="twitter-follow-button" data-show-count="false" data-related="hillelcoren" data-size="medium">Follow @invoiceninja</a>
<a href="https://twitter.com/invoiceninja" class="twitter-follow-button" data-show-count="false" data-related="hillelcoren" data-size="large">Follow @invoiceninja</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
</div></div>
</div>
</div>
-->
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.custom_messages') !!}</h3>

View File

@ -1,7 +1,8 @@
@extends('accounts.nav')
@extends('header')
@section('content')
@parent
@include('accounts.nav', ['selected' => ACCOUNT_PAYMENTS])
{!! Former::open('gateways/delete')->addClass('user-form') !!}

View File

@ -1,18 +1,20 @@
@extends('accounts.nav')
@extends('header')
@section('content')
@parent
@include('accounts.nav', ['selected' => ACCOUNT_PRODUCTS])
{!! Former::open($url)->method($method)
->rules(['product_key' => 'required|max:255'])
->addClass('col-md-8 col-md-offset-2 warn-on-exit') !!}
->addClass('warn-on-exit') !!}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! $title !!}</h3>
</div>
<div class="panel-body">
<div class="panel-body form-padding-right">
@if ($product)
{{ Former::populate($product) }}
@ -23,11 +25,18 @@
{!! Former::textarea('notes') !!}
{!! Former::text('cost') !!}
@if ($account->invoice_item_taxes)
{!! Former::select('default_tax_rate_id')
->addOption('', '')
->label(trans('texts.tax_rate'))
->fromQuery($taxRates, function($model) { return $model->name . ' ' . $model->rate . '%'; }, 'id') !!}
@endif
</div>
</div>
{!! Former::actions(
Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/company/products'))->appendIcon(Icon::create('remove-circle')),
Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/settings/products'))->appendIcon(Icon::create('remove-circle')),
Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk'))
) !!}

View File

@ -1,8 +1,10 @@
@extends('accounts.nav')
@extends('header')
@section('content')
@parent
@include('accounts.nav', ['selected' => ACCOUNT_PRODUCTS])
{!! Former::open()->addClass('warn-on-exit') !!}
{{ Former::populateField('fill_products', intval($account->fill_products)) }}
{{ Former::populateField('update_products', intval($account->update_products)) }}
@ -17,7 +19,7 @@
{!! Former::checkbox('fill_products')->text(trans('texts.fill_products_help')) !!}
{!! Former::checkbox('update_products')->text(trans('texts.update_products_help')) !!}
&nbsp;
{!! Former::actions( Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) ) !!}
{!! Former::actions( Button::success(trans('texts.save'))->submit()->appendIcon(Icon::create('floppy-disk')) ) !!}
{!! Former::close() !!}
</div>
</div>
@ -28,16 +30,12 @@
->appendIcon(Icon::create('plus-sign')) !!}
{!! Datatable::table()
->addColumn(
trans('texts.product'),
trans('texts.description'),
trans('texts.unit_cost'),
trans('texts.action'))
->addColumn($columns)
->setUrl(url('api/products/'))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
->setOptions('bAutoWidth', false)
->setOptions('aoColumns', [[ "sWidth"=> "20%" ], [ "sWidth"=> "45%" ], ["sWidth"=> "20%"], ["sWidth"=> "15%" ]])
//->setOptions('aoColumns', [[ "sWidth"=> "15%" ], [ "sWidth"=> "35%" ]])
->setOptions('aoColumnDefs', [['bSortable'=>false, 'aTargets'=>[3]]])
->render('datatable') !!}

View File

@ -0,0 +1,47 @@
@extends('header')
@section('content')
@parent
@include('accounts.nav', ['selected' => ACCOUNT_TAX_RATES])
{!! Former::open($url)->method($method)
->rules([
'name' => 'required',
'rate' => 'required'
])
->addClass('warn-on-exit') !!}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! $title !!}</h3>
</div>
<div class="panel-body form-padding-right">
@if ($taxRate)
{{ Former::populate($taxRate) }}
@endif
{!! Former::text('name')->label('texts.name') !!}
{!! Former::text('rate')->label('texts.rate')->append('%') !!}
</div>
</div>
{!! Former::actions(
Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/settings/tax_rates'))->appendIcon(Icon::create('remove-circle')),
Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk'))
) !!}
{!! Former::close() !!}
<script type="text/javascript">
$(function() {
$('#name').focus();
});
</script>
@stop

View File

@ -0,0 +1,79 @@
@extends('header')
@section('content')
@parent
@include('accounts.nav', ['selected' => ACCOUNT_TAX_RATES])
{!! Former::open()->addClass('warn-on-exit') !!}
{{ Former::populate($account) }}
{{ Former::populateField('invoice_taxes', intval($account->invoice_taxes)) }}
{{ Former::populateField('invoice_item_taxes', intval($account->invoice_item_taxes)) }}
{{ Former::populateField('show_item_taxes', intval($account->show_item_taxes)) }}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.tax_settings') !!}</h3>
</div>
<div class="panel-body">
{!! Former::checkbox('invoice_taxes')
->text(trans('texts.enable_invoice_tax'))
->label('&nbsp;') !!}
{!! Former::checkbox('invoice_item_taxes')
->text(trans('texts.enable_line_item_tax'))
->label('&nbsp;') !!}
{!! Former::checkbox('show_item_taxes')
->text(trans('texts.show_line_item_tax'))
->label('&nbsp;') !!}
&nbsp;
{!! Former::select('default_tax_rate_id')
->style('max-width: 250px')
->addOption('', '')
->fromQuery($taxRates, function($model) { return $model->name . ': ' . $model->rate . '%'; }, 'id') !!}
&nbsp;
{!! Former::actions( Button::success(trans('texts.save'))->submit()->appendIcon(Icon::create('floppy-disk')) ) !!}
{!! Former::close() !!}
</div>
</div>
{!! Button::primary(trans('texts.create_tax_rate'))
->asLinkTo(URL::to('/tax_rates/create'))
->withAttributes(['class' => 'pull-right'])
->appendIcon(Icon::create('plus-sign')) !!}
{!! Datatable::table()
->addColumn(
trans('texts.name'),
trans('texts.rate'),
trans('texts.action'))
->setUrl(url('api/tax_rates/'))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
->setOptions('bAutoWidth', false)
->setOptions('aoColumns', [[ "sWidth"=> "40%" ], [ "sWidth"=> "40%" ], ["sWidth"=> "20%"]])
->setOptions('aoColumnDefs', [['bSortable'=>false, 'aTargets'=>[2]]])
->render('datatable') !!}
<script>
window.onDatatableReady = function() {
$('tbody tr').mouseover(function() {
$(this).closest('tr').find('.tr-action').css('visibility','visible');
}).mouseout(function() {
$dropdown = $(this).closest('tr').find('.tr-action');
if (!$dropdown.hasClass('open')) {
$dropdown.css('visibility','hidden');
}
});
}
</script>
@stop

View File

@ -1,4 +1,4 @@
@extends('accounts.nav')
@extends('header')
@section('head')
@parent
@ -13,9 +13,9 @@
@section('content')
@parent
@include('accounts.nav_advanced')
@include('accounts.nav', ['selected' => ACCOUNT_TEMPLATES_AND_REMINDERS, 'advanced' => true])
{!! Former::vertical_open()->addClass('col-md-10 col-md-offset-1 warn-on-exit') !!}
{!! Former::vertical_open()->addClass('warn-on-exit') !!}
{!! Former::populate($account) !!}
@foreach ([ENTITY_INVOICE, ENTITY_QUOTE, ENTITY_PAYMENT, REMINDER1, REMINDER2, REMINDER3] as $type)

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