Merge branch 'release-4.4.0'

This commit is contained in:
Hillel Coren 2018-04-24 20:59:50 +03:00
commit 748578a68a
194 changed files with 5048 additions and 2598 deletions

View File

@ -6,8 +6,6 @@ sudo: true
group: deprecated-2017Q4 group: deprecated-2017Q4
php: php:
# - 7.0
- 7.1
- 7.2 - 7.2
addons: addons:
@ -110,10 +108,10 @@ after_script:
- mysql -u root -e 'select * from account_gateways;' ninja - mysql -u root -e 'select * from account_gateways;' ninja
- mysql -u root -e 'select * from clients;' ninja - mysql -u root -e 'select * from clients;' ninja
- mysql -u root -e 'select * from contacts;' ninja - mysql -u root -e 'select * from contacts;' ninja
- mysql -u root -e 'select id, account_id, invoice_number, amount, balance from invoices;' ninja - mysql -u root -e 'select id, public_id, account_id, invoice_number, amount, balance from invoices;' ninja
- mysql -u root -e 'select * from invoice_items;' ninja - mysql -u root -e 'select * from invoice_items;' ninja
- mysql -u root -e 'select * from invitations;' ninja - mysql -u root -e 'select * from invitations;' ninja
- mysql -u root -e 'select id, account_id, invoice_id, amount, transaction_reference from payments;' ninja - mysql -u root -e 'select id, public_id, account_id, invoice_id, amount, transaction_reference from payments;' ninja
- mysql -u root -e 'select * from credits;' ninja - mysql -u root -e 'select * from credits;' ninja
- mysql -u root -e 'select * from expenses;' ninja - mysql -u root -e 'select * from expenses;' ninja
- mysql -u root -e 'select * from accounts;' ninja - mysql -u root -e 'select * from accounts;' ninja
@ -129,5 +127,5 @@ notifications:
email: email:
on_success: never on_success: never
on_failure: change on_failure: change
slack: slack:
invoiceninja: SLraaKBDvjeRuRtY9o3Yvp1b invoiceninja: SLraaKBDvjeRuRtY9o3Yvp1b

View File

@ -42,7 +42,7 @@ class MakeModule extends Command
$fields = $this->argument('fields'); $fields = $this->argument('fields');
$migrate = $this->option('migrate'); $migrate = $this->option('migrate');
$lower = strtolower($name); $lower = strtolower($name);
// convert 'name:string,description:text' to 'name,description' // convert 'name:string,description:text' to 'name,description'
$fillable = explode(',', $fields); $fillable = explode(',', $fields);
$fillable = array_map(function ($item) { $fillable = array_map(function ($item) {
@ -69,10 +69,10 @@ class MakeModule extends Command
Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'transformer', '--fields' => $fields]); Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'transformer', '--fields' => $fields]);
Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'lang', '--filename' => 'texts']); Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'lang', '--filename' => 'texts']);
if ($migrate == 'false') { if ($migrate == 'true') {
$this->info("Use the following command to run the migrations:\nphp artisan module:migrate $name");
} else {
Artisan::call('module:migrate', ['module' => $name]); Artisan::call('module:migrate', ['module' => $name]);
} else {
$this->info("Use the following command to run the migrations:\nphp artisan module:migrate $name");
} }
Artisan::call('module:dump'); Artisan::call('module:dump');

View File

@ -91,7 +91,7 @@ class SendRecurringInvoices extends Command
->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND is_public IS TRUE AND frequency_id > 0 AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', [$today, $today]) ->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND is_public IS TRUE AND frequency_id > 0 AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', [$today, $today])
->orderBy('id', 'asc') ->orderBy('id', 'asc')
->get(); ->get();
$this->info($invoices->count() . ' recurring invoice(s) found'); $this->info(date('r ') . $invoices->count() . ' recurring invoice(s) found');
foreach ($invoices as $recurInvoice) { foreach ($invoices as $recurInvoice) {
$shouldSendToday = $recurInvoice->shouldSendToday(); $shouldSendToday = $recurInvoice->shouldSendToday();
@ -100,7 +100,7 @@ class SendRecurringInvoices extends Command
continue; continue;
} }
$this->info('Processing Invoice: '. $recurInvoice->id); $this->info(date('r') . ' Processing Invoice: '. $recurInvoice->id);
$account = $recurInvoice->account; $account = $recurInvoice->account;
$account->loadLocalizationSettings($recurInvoice->client); $account->loadLocalizationSettings($recurInvoice->client);
@ -109,13 +109,13 @@ class SendRecurringInvoices extends Command
try { try {
$invoice = $this->invoiceRepo->createRecurringInvoice($recurInvoice); $invoice = $this->invoiceRepo->createRecurringInvoice($recurInvoice);
if ($invoice && ! $invoice->isPaid() && $account->auto_email_invoice) { if ($invoice && ! $invoice->isPaid() && $account->auto_email_invoice) {
$this->info('Not billed - Sending Invoice'); $this->info(date('r') . ' Not billed - Sending Invoice');
dispatch(new SendInvoiceEmail($invoice, $invoice->user_id)); dispatch(new SendInvoiceEmail($invoice, $invoice->user_id));
} elseif ($invoice) { } elseif ($invoice) {
$this->info('Successfully billed invoice'); $this->info(date('r') . ' Successfully billed invoice');
} }
} catch (Exception $exception) { } catch (Exception $exception) {
$this->info('Error: ' . $exception->getMessage()); $this->info(date('r') . ' Error: ' . $exception->getMessage());
Utils::logError($exception); Utils::logError($exception);
} }
@ -133,7 +133,7 @@ class SendRecurringInvoices extends Command
[$today->format('Y-m-d')]) [$today->format('Y-m-d')])
->orderBy('invoices.id', 'asc') ->orderBy('invoices.id', 'asc')
->get(); ->get();
$this->info($delayedAutoBillInvoices->count() . ' due recurring invoice instance(s) found'); $this->info(date('r ') . $delayedAutoBillInvoices->count() . ' due recurring invoice instance(s) found');
/** @var Invoice $invoice */ /** @var Invoice $invoice */
foreach ($delayedAutoBillInvoices as $invoice) { foreach ($delayedAutoBillInvoices as $invoice) {
@ -142,7 +142,7 @@ class SendRecurringInvoices extends Command
} }
if ($invoice->getAutoBillEnabled() && $invoice->client->autoBillLater()) { if ($invoice->getAutoBillEnabled() && $invoice->client->autoBillLater()) {
$this->info('Processing Autobill-delayed Invoice: ' . $invoice->id); $this->info(date('r') . ' Processing Autobill-delayed Invoice: ' . $invoice->id);
Auth::loginUsingId($invoice->activeUser()->id); Auth::loginUsingId($invoice->activeUser()->id);
$this->paymentService->autoBillInvoice($invoice); $this->paymentService->autoBillInvoice($invoice);
Auth::logout(); Auth::logout();
@ -158,7 +158,7 @@ class SendRecurringInvoices extends Command
->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', [$today, $today]) ->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', [$today, $today])
->orderBy('id', 'asc') ->orderBy('id', 'asc')
->get(); ->get();
$this->info($expenses->count() . ' recurring expenses(s) found'); $this->info(date('r ') . $expenses->count() . ' recurring expenses(s) found');
foreach ($expenses as $expense) { foreach ($expenses as $expense) {
$shouldSendToday = $expense->shouldSendToday(); $shouldSendToday = $expense->shouldSendToday();
@ -167,7 +167,7 @@ class SendRecurringInvoices extends Command
continue; continue;
} }
$this->info('Processing Expense: '. $expense->id); $this->info(date('r') . ' Processing Expense: '. $expense->id);
$this->recurringExpenseRepo->createRecurringExpense($expense); $this->recurringExpenseRepo->createRecurringExpense($expense);
} }
} }

View File

@ -6,6 +6,7 @@ use App\Libraries\CurlUtils;
use Carbon; use Carbon;
use Str; use Str;
use Cache; use Cache;
use Utils;
use Exception; use Exception;
use App\Jobs\SendInvoiceEmail; use App\Jobs\SendInvoiceEmail;
use App\Models\Invoice; use App\Models\Invoice;
@ -73,7 +74,7 @@ class SendReminders extends Command
$this->sendScheduledReports(); $this->sendScheduledReports();
$this->loadExchangeRates(); $this->loadExchangeRates();
$this->info('Done'); $this->info(date('r') . ' Done');
if ($errorEmail = env('ERROR_EMAIL')) { if ($errorEmail = env('ERROR_EMAIL')) {
\Mail::raw('EOM', function ($message) use ($errorEmail, $database) { \Mail::raw('EOM', function ($message) use ($errorEmail, $database) {
@ -87,7 +88,7 @@ class SendReminders extends Command
private function chargeLateFees() private function chargeLateFees()
{ {
$accounts = $this->accountRepo->findWithFees(); $accounts = $this->accountRepo->findWithFees();
$this->info($accounts->count() . ' accounts found with fees'); $this->info(date('r ') . $accounts->count() . ' accounts found with fees');
foreach ($accounts as $account) { foreach ($accounts as $account) {
if (! $account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) { if (! $account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) {
@ -95,11 +96,11 @@ class SendReminders extends Command
} }
$invoices = $this->invoiceRepo->findNeedingReminding($account, false); $invoices = $this->invoiceRepo->findNeedingReminding($account, false);
$this->info($account->name . ': ' . $invoices->count() . ' invoices found'); $this->info(date('r ') . $account->name . ': ' . $invoices->count() . ' invoices found');
foreach ($invoices as $invoice) { foreach ($invoices as $invoice) {
if ($reminder = $account->getInvoiceReminder($invoice, false)) { if ($reminder = $account->getInvoiceReminder($invoice, false)) {
$this->info('Charge fee: ' . $invoice->id); $this->info(date('r') . ' Charge fee: ' . $invoice->id);
$account->loadLocalizationSettings($invoice->client); // support trans to add fee line item $account->loadLocalizationSettings($invoice->client); // support trans to add fee line item
$number = preg_replace('/[^0-9]/', '', $reminder); $number = preg_replace('/[^0-9]/', '', $reminder);
@ -114,7 +115,7 @@ class SendReminders extends Command
private function sendReminderEmails() private function sendReminderEmails()
{ {
$accounts = $this->accountRepo->findWithReminders(); $accounts = $this->accountRepo->findWithReminders();
$this->info(count($accounts) . ' accounts found with reminders'); $this->info(date('r ') . count($accounts) . ' accounts found with reminders');
foreach ($accounts as $account) { foreach ($accounts as $account) {
if (! $account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) { if (! $account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) {
@ -123,27 +124,27 @@ class SendReminders extends Command
// standard reminders // standard reminders
$invoices = $this->invoiceRepo->findNeedingReminding($account); $invoices = $this->invoiceRepo->findNeedingReminding($account);
$this->info($account->name . ': ' . $invoices->count() . ' invoices found'); $this->info(date('r ') . $account->name . ': ' . $invoices->count() . ' invoices found');
foreach ($invoices as $invoice) { foreach ($invoices as $invoice) {
if ($reminder = $account->getInvoiceReminder($invoice)) { if ($reminder = $account->getInvoiceReminder($invoice)) {
if ($invoice->last_sent_date == date('Y-m-d')) { if ($invoice->last_sent_date == date('Y-m-d')) {
continue; continue;
} }
$this->info('Send email: ' . $invoice->id); $this->info(date('r') . ' Send email: ' . $invoice->id);
dispatch(new SendInvoiceEmail($invoice, $invoice->user_id, $reminder)); dispatch(new SendInvoiceEmail($invoice, $invoice->user_id, $reminder));
} }
} }
// endless reminders // endless reminders
$invoices = $this->invoiceRepo->findNeedingEndlessReminding($account); $invoices = $this->invoiceRepo->findNeedingEndlessReminding($account);
$this->info($account->name . ': ' . $invoices->count() . ' endless invoices found'); $this->info(date('r ') . $account->name . ': ' . $invoices->count() . ' endless invoices found');
foreach ($invoices as $invoice) { foreach ($invoices as $invoice) {
if ($invoice->last_sent_date == date('Y-m-d')) { if ($invoice->last_sent_date == date('Y-m-d')) {
continue; continue;
} }
$this->info('Send email: ' . $invoice->id); $this->info(date('r') . ' Send email: ' . $invoice->id);
dispatch(new SendInvoiceEmail($invoice, $invoice->user_id, 'reminder4')); dispatch(new SendInvoiceEmail($invoice, $invoice->user_id, 'reminder4'));
} }
} }
@ -154,10 +155,10 @@ class SendReminders extends Command
$scheduledReports = ScheduledReport::where('send_date', '<=', date('Y-m-d')) $scheduledReports = ScheduledReport::where('send_date', '<=', date('Y-m-d'))
->with('user', 'account.company') ->with('user', 'account.company')
->get(); ->get();
$this->info($scheduledReports->count() . ' scheduled reports'); $this->info(date('r ') . $scheduledReports->count() . ' scheduled reports');
foreach ($scheduledReports as $scheduledReport) { foreach ($scheduledReports as $scheduledReport) {
$this->info('Processing report: ' . $scheduledReport->id); $this->info(date('r') . ' Processing report: ' . $scheduledReport->id);
$user = $scheduledReport->user; $user = $scheduledReport->user;
$account = $scheduledReport->account; $account = $scheduledReport->account;
@ -179,12 +180,12 @@ class SendReminders extends Command
if ($file) { if ($file) {
try { try {
$this->userMailer->sendScheduledReport($scheduledReport, $file); $this->userMailer->sendScheduledReport($scheduledReport, $file);
$this->info('Sent report'); $this->info(date('r') . ' Sent report');
} catch (Exception $exception) { } catch (Exception $exception) {
$this->info('ERROR: ' . $exception->getMessage()); $this->info(date('r') . ' ERROR: ' . $exception->getMessage());
} }
} else { } else {
$this->info('ERROR: Failed to run report'); $this->info(date('r') . ' ERROR: Failed to run report');
} }
$scheduledReport->updateSendDate(); $scheduledReport->updateSendDate();
@ -195,7 +196,11 @@ class SendReminders extends Command
private function loadExchangeRates() private function loadExchangeRates()
{ {
$this->info('Loading latest exchange rates...'); if (Utils::isNinjaDev()) {
return;
}
$this->info(date('r') . ' Loading latest exchange rates...');
$data = CurlUtils::get(config('ninja.exchange_rates_url')); $data = CurlUtils::get(config('ninja.exchange_rates_url'));
$data = json_decode($data); $data = json_decode($data);

View File

@ -3,7 +3,7 @@
if (! defined('APP_NAME')) { if (! defined('APP_NAME')) {
define('APP_NAME', env('APP_NAME', 'Invoice Ninja')); define('APP_NAME', env('APP_NAME', 'Invoice Ninja'));
define('APP_DOMAIN', env('APP_DOMAIN', 'invoiceninja.com')); define('APP_DOMAIN', env('APP_DOMAIN', 'invoiceninja.com'));
define('CONTACT_EMAIL', env('MAIL_FROM_ADDRESS', env('MAIL_USERNAME'))); define('CONTACT_EMAIL', env('MAIL_FROM_ADDRESS'));
define('CONTACT_NAME', env('MAIL_FROM_NAME')); define('CONTACT_NAME', env('MAIL_FROM_NAME'));
define('SITE_URL', env('APP_URL')); define('SITE_URL', env('APP_URL'));
@ -158,7 +158,11 @@ if (! defined('APP_NAME')) {
define('MAX_DOCUMENT_SIZE', env('MAX_DOCUMENT_SIZE', 10000)); // KB define('MAX_DOCUMENT_SIZE', env('MAX_DOCUMENT_SIZE', 10000)); // KB
define('MAX_EMAIL_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 10000)); // Total KB define('MAX_EMAIL_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 10000)); // Total KB
define('MAX_ZIP_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 30000)); // Total KB (uncompressed) define('MAX_ZIP_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 30000)); // Total KB (uncompressed)
<<<<<<< HEAD
define('MAX_EMAILS_SENT_PER_DAY', 500); define('MAX_EMAILS_SENT_PER_DAY', 500);
=======
define('MAX_EMAILS_SENT_PER_DAY', 300);
>>>>>>> release-4.4.0
define('DOCUMENT_PREVIEW_SIZE', env('DOCUMENT_PREVIEW_SIZE', 300)); // pixels define('DOCUMENT_PREVIEW_SIZE', env('DOCUMENT_PREVIEW_SIZE', 300)); // pixels
define('DEFAULT_FONT_SIZE', 9); define('DEFAULT_FONT_SIZE', 9);
define('DEFAULT_HEADER_FONT', 1); // Roboto define('DEFAULT_HEADER_FONT', 1); // Roboto
@ -180,6 +184,7 @@ if (! defined('APP_NAME')) {
define('IMPORT_INVOICEPLANE', 'InvoicePlane'); define('IMPORT_INVOICEPLANE', 'InvoicePlane');
define('IMPORT_HARVEST', 'Harvest'); define('IMPORT_HARVEST', 'Harvest');
define('IMPORT_STRIPE', 'Stripe'); define('IMPORT_STRIPE', 'Stripe');
define('IMPORT_PANCAKE', 'Pancake');
define('MAX_NUM_CLIENTS', 100); define('MAX_NUM_CLIENTS', 100);
define('MAX_NUM_CLIENTS_PRO', 20000); define('MAX_NUM_CLIENTS_PRO', 20000);
@ -299,9 +304,11 @@ if (! defined('APP_NAME')) {
define('GATEWAY_PAYTRACE', 56); define('GATEWAY_PAYTRACE', 56);
define('GATEWAY_WEPAY', 60); define('GATEWAY_WEPAY', 60);
define('GATEWAY_BRAINTREE', 61); define('GATEWAY_BRAINTREE', 61);
define('GATEWAY_CUSTOM', 62); define('GATEWAY_CUSTOM1', 62);
define('GATEWAY_GOCARDLESS', 64); define('GATEWAY_GOCARDLESS', 64);
define('GATEWAY_PAYMILL', 66); define('GATEWAY_PAYMILL', 66);
define('GATEWAY_CUSTOM2', 67);
define('GATEWAY_CUSTOM3', 68);
// The customer exists, but only as a local concept // The customer exists, but only as a local concept
// The remote gateway doesn't understand the concept of customers // The remote gateway doesn't understand the concept of customers
@ -339,7 +346,7 @@ if (! defined('APP_NAME')) {
define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com')); define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com'));
define('NINJA_DOCS_URL', env('NINJA_DOCS_URL', 'http://docs.invoiceninja.com/en/latest')); define('NINJA_DOCS_URL', env('NINJA_DOCS_URL', 'http://docs.invoiceninja.com/en/latest'));
define('NINJA_DATE', '2000-01-01'); define('NINJA_DATE', '2000-01-01');
define('NINJA_VERSION', '4.3.1' . env('NINJA_VERSION_SUFFIX')); define('NINJA_VERSION', '4.4.0' . env('NINJA_VERSION_SUFFIX'));
define('NINJA_TERMS_VERSION', ''); define('NINJA_TERMS_VERSION', '');
define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja')); define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'));
@ -451,12 +458,14 @@ if (! defined('APP_NAME')) {
define('GATEWAY_TYPE_PAYPAL', 3); define('GATEWAY_TYPE_PAYPAL', 3);
define('GATEWAY_TYPE_BITCOIN', 4); define('GATEWAY_TYPE_BITCOIN', 4);
define('GATEWAY_TYPE_DWOLLA', 5); define('GATEWAY_TYPE_DWOLLA', 5);
define('GATEWAY_TYPE_CUSTOM', 6); define('GATEWAY_TYPE_CUSTOM1', 6);
define('GATEWAY_TYPE_ALIPAY', 7); define('GATEWAY_TYPE_ALIPAY', 7);
define('GATEWAY_TYPE_SOFORT', 8); define('GATEWAY_TYPE_SOFORT', 8);
define('GATEWAY_TYPE_SEPA', 9); define('GATEWAY_TYPE_SEPA', 9);
define('GATEWAY_TYPE_GOCARDLESS', 10); define('GATEWAY_TYPE_GOCARDLESS', 10);
define('GATEWAY_TYPE_APPLE_PAY', 11); define('GATEWAY_TYPE_APPLE_PAY', 11);
define('GATEWAY_TYPE_CUSTOM2', 12);
define('GATEWAY_TYPE_CUSTOM3', 13);
define('GATEWAY_TYPE_TOKEN', 'token'); define('GATEWAY_TYPE_TOKEN', 'token');
define('TEMPLATE_INVOICE', 'invoice'); define('TEMPLATE_INVOICE', 'invoice');
@ -469,6 +478,14 @@ if (! defined('APP_NAME')) {
define('TEMPLATE_REMINDER3', 'reminder3'); define('TEMPLATE_REMINDER3', 'reminder3');
define('TEMPLATE_REMINDER4', 'reminder4'); define('TEMPLATE_REMINDER4', 'reminder4');
define('CUSTOM_MESSAGE_DASHBOARD', 'dashboard');
define('CUSTOM_MESSAGE_UNPAID_INVOICE', 'unpaid_invoice');
define('CUSTOM_MESSAGE_PAID_INVOICE', 'paid_invoice');
define('CUSTOM_MESSAGE_UNAPPROVED_QUOTE', 'unapproved_quote');
define('CUSTOM_MESSAGE_APPROVED_QUOTE', 'approved_quote');
define('CUSTOM_MESSAGE_UNAPPROVED_PROPOSAL', 'unapproved_proposal');
define('CUSTOM_MESSAGE_APPROVED_PROPOSAL', 'approved_proposal');
define('RESET_FREQUENCY_DAILY', 1); define('RESET_FREQUENCY_DAILY', 1);
define('RESET_FREQUENCY_WEEKLY', 2); define('RESET_FREQUENCY_WEEKLY', 2);
define('RESET_FREQUENCY_MONTHLY', 3); define('RESET_FREQUENCY_MONTHLY', 3);

File diff suppressed because one or more lines are too long

View File

@ -52,7 +52,7 @@ class AccountGatewayController extends BaseController
$accountGateway = AccountGateway::scope($publicId)->firstOrFail(); $accountGateway = AccountGateway::scope($publicId)->firstOrFail();
$config = $accountGateway->getConfig(); $config = $accountGateway->getConfig();
if ($accountGateway->gateway_id != GATEWAY_CUSTOM) { if (! $accountGateway->isCustom()) {
foreach ($config as $field => $value) { foreach ($config as $field => $value) {
$config->$field = str_repeat('*', strlen($value)); $config->$field = str_repeat('*', strlen($value));
} }
@ -257,8 +257,6 @@ class AccountGatewayController extends BaseController
} }
if (! $value && in_array($field, ['testMode', 'developerMode', 'sandbox'])) { if (! $value && in_array($field, ['testMode', 'developerMode', 'sandbox'])) {
// do nothing // do nothing
} elseif ($gatewayId == GATEWAY_CUSTOM) {
$config->$field = Utils::isNinjaProd() ? strip_tags($value) : $value;
} else { } else {
$config->$field = $value; $config->$field = $value;
} }

View File

@ -282,7 +282,10 @@ class AppController extends BaseController
if (! Utils::isNinjaProd()) { if (! Utils::isNinjaProd()) {
if ($password = env('UPDATE_SECRET')) { if ($password = env('UPDATE_SECRET')) {
if (! hash_equals($password, request('secret') ?: '')) { if (! hash_equals($password, request('secret') ?: '')) {
abort(400, 'Invalid secret: /update?secret=<value>'); $message = 'Invalid secret: /update?secret=<value>';
Utils::logError($message);
echo $message;
exit;
} }
} }
@ -328,7 +331,7 @@ class AppController extends BaseController
} }
} }
return Redirect::to('/'); return Redirect::to('/?clear_cache=true');
} }
// MySQL changed the default table type from MyISAM to InnoDB // MySQL changed the default table type from MyISAM to InnoDB

View File

@ -58,8 +58,9 @@ class ResetPasswordController extends Controller
public function showResetForm(Request $request, $token = null) public function showResetForm(Request $request, $token = null)
{ {
return view('auth.passwords.reset')->with( return view('auth.passwords.reset')->with([
['token' => $token] 'token' => $token,
); 'url' => '/password/reset'
]);
} }
} }

View File

@ -158,8 +158,8 @@ class BotController extends Controller
private function parseMessage($message) private function parseMessage($message)
{ {
$appId = env('MSBOT_LUIS_APP_ID'); $appId = config('ninja.voice_commands.app_id');
$subKey = env('MSBOT_LUIS_SUBSCRIPTION_KEY'); $subKey = config('ninja.voice_commands.subscription_key');
$message = rawurlencode($message); $message = rawurlencode($message);
$url = sprintf('%s/%s?subscription-key=%s&verbose=true&q=%s', MSBOT_LUIS_URL, $appId, $subKey, $message); $url = sprintf('%s/%s?subscription-key=%s&verbose=true&q=%s', MSBOT_LUIS_URL, $appId, $subKey, $message);

View File

@ -54,9 +54,10 @@ class ResetPasswordController extends Controller
public function showResetForm(Request $request, $token = null) public function showResetForm(Request $request, $token = null)
{ {
return view('clientauth.passwords.reset')->with( return view('auth.passwords.reset')->with([
['token' => $token] 'token' => $token,
); 'url' => '/client/password/reset'
]);
} }
} }

View File

@ -197,8 +197,8 @@ class ClientController extends BaseController
'data' => Input::old('data'), 'data' => Input::old('data'),
'account' => Auth::user()->account, 'account' => Auth::user()->account,
'sizes' => Cache::get('sizes'), 'sizes' => Cache::get('sizes'),
'customLabel1' => Auth::user()->account->custom_client_label1, 'customLabel1' => Auth::user()->account->customLabel('client1'),
'customLabel2' => Auth::user()->account->custom_client_label2, 'customLabel2' => Auth::user()->account->customLabel('client2'),
]; ];
} }

View File

@ -128,7 +128,7 @@ class ClientPortalController extends BaseController
$paymentURL = ''; $paymentURL = '';
if (count($paymentTypes) == 1) { if (count($paymentTypes) == 1) {
$paymentURL = $paymentTypes[0]['url']; $paymentURL = $paymentTypes[0]['url'];
if ($paymentTypes[0]['gatewayTypeId'] == GATEWAY_TYPE_CUSTOM) { if (in_array($paymentTypes[0]['gatewayTypeId'], [GATEWAY_TYPE_CUSTOM1, GATEWAY_TYPE_CUSTOM2, GATEWAY_TYPE_CUSTOM3])) {
// do nothing // do nothing
} elseif (! $account->isGatewayConfigured(GATEWAY_PAYPAL_EXPRESS)) { } elseif (! $account->isGatewayConfigured(GATEWAY_PAYPAL_EXPRESS)) {
$paymentURL = URL::to($paymentURL); $paymentURL = URL::to($paymentURL);
@ -170,13 +170,6 @@ class ClientPortalController extends BaseController
'accountGateway' => $paymentDriver->accountGateway, 'accountGateway' => $paymentDriver->accountGateway,
]; ];
} }
if ($accountGateway = $account->getGatewayByType(GATEWAY_TYPE_CUSTOM)) {
$data += [
'customGatewayName' => $accountGateway->getConfigField('name'),
'customGatewayText' => $accountGateway->getConfigField('text'),
];
}
} }
if ($account->hasFeature(FEATURE_DOCUMENTS) && $this->canCreateZip()) { if ($account->hasFeature(FEATURE_DOCUMENTS) && $this->canCreateZip()) {
@ -215,7 +208,7 @@ class ClientPortalController extends BaseController
$invoice = $invitation->invoice; $invoice = $invitation->invoice;
$decode = ! request()->base64; $decode = ! request()->base64;
$pdfString = $invoice->getPDFString($decode); $pdfString = $invoice->getPDFString($invitation, $decode);
header('Content-Type: application/pdf'); header('Content-Type: application/pdf');
header('Content-Length: ' . strlen($pdfString)); header('Content-Length: ' . strlen($pdfString));

View File

@ -21,6 +21,7 @@ class DashboardApiController extends BaseAPIController
$viewAll = $user->hasPermission('view_all'); $viewAll = $user->hasPermission('view_all');
$userId = $user->id; $userId = $user->id;
$accountId = $user->account->id; $accountId = $user->account->id;
$defaultCurrency = $user->account->currency_id;
$dashboardRepo = $this->dashboardRepo; $dashboardRepo = $this->dashboardRepo;
$metrics = $dashboardRepo->totals($accountId, $userId, $viewAll); $metrics = $dashboardRepo->totals($accountId, $userId, $viewAll);
@ -44,11 +45,11 @@ class DashboardApiController extends BaseAPIController
$data = [ $data = [
'id' => 1, 'id' => 1,
'paidToDate' => $paidToDate->count() && $paidToDate[0]->value ? $paidToDate[0]->value : 0, 'paidToDate' => $paidToDate->count() && $paidToDate[0]->value ? $paidToDate[0]->value : 0,
'paidToDateCurrency' => $paidToDate->count() && $paidToDate[0]->currency_id ? $paidToDate[0]->currency_id : 0, 'paidToDateCurrency' => $paidToDate->count() && $paidToDate[0]->currency_id ? $paidToDate[0]->currency_id : $defaultCurrency,
'balances' => $balances->count() && $balances[0]->value ? $balances[0]->value : 0, 'balances' => $balances->count() && $balances[0]->value ? $balances[0]->value : 0,
'balancesCurrency' => $balances->count() && $balances[0]->currency_id ? $balances[0]->currency_id : 0, 'balancesCurrency' => $balances->count() && $balances[0]->currency_id ? $balances[0]->currency_id : $defaultCurrency,
'averageInvoice' => $averageInvoice->count() && $averageInvoice[0]->invoice_avg ? $averageInvoice[0]->invoice_avg : 0, 'averageInvoice' => $averageInvoice->count() && $averageInvoice[0]->invoice_avg ? $averageInvoice[0]->invoice_avg : 0,
'averageInvoiceCurrency' => $averageInvoice->count() && $averageInvoice[0]->currency_id ? $averageInvoice[0]->currency_id : 0, 'averageInvoiceCurrency' => $averageInvoice->count() && $averageInvoice[0]->currency_id ? $averageInvoice[0]->currency_id : $defaultCurrency,
'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0,
'activeClients' => $metrics ? $metrics->active_clients : 0, 'activeClients' => $metrics ? $metrics->active_clients : 0,
'activities' => $this->createCollection($activities, new ActivityTransformer(), ENTITY_ACTIVITY), 'activities' => $this->createCollection($activities, new ActivityTransformer(), ENTITY_ACTIVITY),

View File

@ -149,17 +149,7 @@ class HomeController extends BaseController
$subject = 'Customer Message ['; $subject = 'Customer Message [';
if (Utils::isNinjaProd()) { if (Utils::isNinjaProd()) {
$subject .= str_replace('db-ninja-', '', config('database.default')); $subject .= str_replace('db-ninja-', '', config('database.default'));
$account = Auth::user()->account; $subject .= Auth::user()->present()->statusCode . '] ';
if ($account->isTrial()) {
$subject .= 'T';
} elseif ($account->isEnterprise()) {
$subject .= 'E';
} elseif ($account->isPro()) {
$subject .= 'P';
} else {
$subject .= 'H';
}
$subject .= '] ';
} else { } else {
$subject .= 'Self-Host] | '; $subject .= 'Self-Host] | ';
} }

View File

@ -244,6 +244,16 @@ class NinjaController extends BaseController
$licenseKey = Input::get('license_key'); $licenseKey = Input::get('license_key');
$productId = Input::get('product_id', PRODUCT_ONE_CLICK_INSTALL); $productId = Input::get('product_id', PRODUCT_ONE_CLICK_INSTALL);
// add in dashes
if (strlen($licenseKey) == 20) {
$licenseKey = sprintf('%s-%s-%s-%s-%s',
substr($licenseKey, 0, 4),
substr($licenseKey, 4, 4),
substr($licenseKey, 8, 4),
substr($licenseKey, 12, 4),
substr($licenseKey, 16, 4));
}
$license = License::where('license_key', '=', $licenseKey) $license = License::where('license_key', '=', $licenseKey)
->where('is_claimed', '<', 10) ->where('is_claimed', '<', 10)
->where('product_id', '=', $productId) ->where('product_id', '=', $productId)

View File

@ -162,6 +162,7 @@ class ReportController extends BaseController
$schedule->config = json_encode($options); $schedule->config = json_encode($options);
$schedule->frequency = request('frequency'); $schedule->frequency = request('frequency');
$schedule->send_date = Utils::toSqlDate(request('send_date')); $schedule->send_date = Utils::toSqlDate(request('send_date'));
$schedule->ip = request()->getClientIp();
$schedule->save(); $schedule->save();
session()->flash('message', trans('texts.created_scheduled_report')); session()->flash('message', trans('texts.created_scheduled_report'));

View File

@ -149,6 +149,9 @@ class TaskApiController extends BaseAPIController
*/ */
public function update(UpdateTaskRequest $request) public function update(UpdateTaskRequest $request)
{ {
if ($request->action) {
return $this->handleAction($request);
}
$task = $request->entity(); $task = $request->entity();

View File

@ -8,6 +8,7 @@ use App\Http\Requests\UpdateTaskRequest;
use App\Models\Client; use App\Models\Client;
use App\Models\Project; use App\Models\Project;
use App\Models\Task; use App\Models\Task;
use App\Models\TaskStatus;
use App\Ninja\Datatables\TaskDatatable; use App\Ninja\Datatables\TaskDatatable;
use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\TaskRepository; use App\Ninja\Repositories\TaskRepository;
@ -267,6 +268,14 @@ class TaskController extends BaseController
$this->taskRepo->save($ids, ['action' => $action]); $this->taskRepo->save($ids, ['action' => $action]);
Session::flash('message', trans($action == 'stop' ? 'texts.stopped_task' : 'texts.resumed_task')); Session::flash('message', trans($action == 'stop' ? 'texts.stopped_task' : 'texts.resumed_task'));
return $this->returnBulk($this->entityType, $action, $ids); return $this->returnBulk($this->entityType, $action, $ids);
} elseif (strpos($action, 'update_status') === 0) {
list($action, $statusPublicId) = explode(':', $action);
Task::scope($ids)->update([
'task_status_id' => TaskStatus::getPrivateId($statusPublicId),
'task_status_sort_order' => 9999,
]);
Session::flash('message', trans('texts.updated_task_status'));
return $this->returnBulk($this->entityType, $action, $ids);
} elseif ($action == 'invoice' || $action == 'add_to_invoice') { } elseif ($action == 'invoice' || $action == 'add_to_invoice') {
$tasks = Task::scope($ids)->with('account', 'client', 'project')->orderBy('project_id', 'id')->get(); $tasks = Task::scope($ids)->with('account', 'client', 'project')->orderBy('project_id', 'id')->get();
$clientPublicId = false; $clientPublicId = false;

View File

@ -103,7 +103,7 @@ class Authenticate
} else { } else {
if ($guard == 'client') { if ($guard == 'client') {
$url = '/client/login'; $url = '/client/login';
if (Utils::isNinja()) { if (Utils::isNinjaProd()) {
if ($account && Utils::getSubdomain() == 'app') { if ($account && Utils::getSubdomain() == 'app') {
$url .= '?account_key=' . $account->account_key; $url .= '?account_key=' . $account->account_key;
} }

View File

@ -431,14 +431,12 @@ class Utils
public static function prepareErrorData($context) public static function prepareErrorData($context)
{ {
return [ $data = [
'context' => $context, 'context' => $context,
'user_id' => Auth::check() ? Auth::user()->id : 0, 'user_id' => Auth::check() ? Auth::user()->id : 0,
'account_id' => Auth::check() ? Auth::user()->account_id : 0, 'account_id' => Auth::check() ? Auth::user()->account_id : 0,
'user_name' => Auth::check() ? Auth::user()->getDisplayName() : '', 'user_name' => Auth::check() ? Auth::user()->getDisplayName() : '',
'method' => Request::method(), 'method' => Request::method(),
'url' => Input::get('url', Request::url()),
'previous' => url()->previous(),
'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '', 'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
'locale' => App::getLocale(), 'locale' => App::getLocale(),
'ip' => Request::getClientIp(), 'ip' => Request::getClientIp(),
@ -447,6 +445,15 @@ class Utils
'is_api' => session('token_id') ? 'yes' : 'no', 'is_api' => session('token_id') ? 'yes' : 'no',
'db_server' => config('database.default'), 'db_server' => config('database.default'),
]; ];
if (static::isNinja()) {
$data['url'] = Input::get('url', Request::url());
$data['previous'] = url()->previous();
} else {
$data['url'] = request()->path();
}
return $data;
} }
public static function getErrors() public static function getErrors()
@ -489,6 +496,21 @@ class Utils
return intval($value); return intval($value);
} }
public static function lookupIdInCache($name, $type)
{
$cache = Cache::get($type);
$data = $cache->filter(function ($item) use ($name) {
return strtolower($item->name) == trim(strtolower($name));
});
if ($record = $data->first()) {
return $record->id;
} else {
return null;
}
}
public static function getFromCache($id, $type) public static function getFromCache($id, $type)
{ {
$cache = Cache::get($type); $cache = Cache::get($type);

View File

@ -79,7 +79,7 @@ class HandleUserLoggedIn
if (! Utils::isNinja()) { if (! Utils::isNinja()) {
// check custom gateway id is correct // check custom gateway id is correct
$gateway = Gateway::find(GATEWAY_CUSTOM); $gateway = Gateway::find(GATEWAY_CUSTOM1);
if (! $gateway || $gateway->name !== 'Custom') { if (! $gateway || $gateway->name !== 'Custom') {
Session::flash('error', trans('texts.error_incorrect_gateway_ids')); Session::flash('error', trans('texts.error_incorrect_gateway_ids'));
} }

View File

@ -9,6 +9,7 @@ use App\Models\Traits\GeneratesNumbers;
use App\Models\Traits\PresentsInvoice; use App\Models\Traits\PresentsInvoice;
use App\Models\Traits\SendsEmails; use App\Models\Traits\SendsEmails;
use App\Models\Traits\HasLogo; use App\Models\Traits\HasLogo;
use App\Models\Traits\HasCustomMessages;
use Cache; use Cache;
use Carbon; use Carbon;
use DateTime; use DateTime;
@ -30,6 +31,7 @@ class Account extends Eloquent
use GeneratesNumbers; use GeneratesNumbers;
use SendsEmails; use SendsEmails;
use HasLogo; use HasLogo;
use HasCustomMessages;
/** /**
* @var string * @var string
@ -72,22 +74,12 @@ class Account extends Eloquent
'work_phone', 'work_phone',
'work_email', 'work_email',
'language_id', 'language_id',
'custom_label1',
'custom_value1',
'custom_label2',
'custom_value2',
'custom_client_label1',
'custom_client_label2',
'fill_products', 'fill_products',
'update_products', 'update_products',
'primary_color', 'primary_color',
'secondary_color', 'secondary_color',
'hide_quantity', 'hide_quantity',
'hide_paid_to_date', 'hide_paid_to_date',
'custom_invoice_label1',
'custom_invoice_label2',
'custom_invoice_taxes1',
'custom_invoice_taxes2',
'vat_number', 'vat_number',
'invoice_number_prefix', 'invoice_number_prefix',
'invoice_number_counter', 'invoice_number_counter',
@ -112,8 +104,6 @@ class Account extends Eloquent
'num_days_reminder1', 'num_days_reminder1',
'num_days_reminder2', 'num_days_reminder2',
'num_days_reminder3', 'num_days_reminder3',
'custom_invoice_text_label1',
'custom_invoice_text_label2',
'tax_name1', 'tax_name1',
'tax_rate1', 'tax_rate1',
'tax_name2', 'tax_name2',
@ -142,8 +132,6 @@ class Account extends Eloquent
'show_currency_code', 'show_currency_code',
'enable_portal_password', 'enable_portal_password',
'send_portal_password', 'send_portal_password',
'custom_invoice_item_label1',
'custom_invoice_item_label2',
'recurring_invoice_number_prefix', 'recurring_invoice_number_prefix',
'enable_client_portal', 'enable_client_portal',
'invoice_fields', 'invoice_fields',
@ -175,8 +163,6 @@ class Account extends Eloquent
'gateway_fee_enabled', 'gateway_fee_enabled',
'send_item_details', 'send_item_details',
'reset_counter_date', 'reset_counter_date',
'custom_contact_label1',
'custom_contact_label2',
'domain_id', 'domain_id',
'analytics_key', 'analytics_key',
'credit_number_counter', 'credit_number_counter',
@ -186,6 +172,10 @@ class Account extends Eloquent
'inclusive_taxes', 'inclusive_taxes',
'convert_products', 'convert_products',
'signature_on_pdf', 'signature_on_pdf',
'custom_fields',
'custom_value1',
'custom_value2',
'custom_messages',
]; ];
/** /**
@ -233,6 +223,27 @@ class Account extends Eloquent
'outstanding' => 4, 'outstanding' => 4,
]; ];
public static $customFields = [
'client1',
'client2',
'contact1',
'contact2',
'product1',
'product2',
'invoice1',
'invoice2',
'invoice_surcharge1',
'invoice_surcharge2',
'task1',
'task2',
'project1',
'project2',
'expense1',
'expense2',
'vendor1',
'vendor2',
];
public static $customLabels = [ public static $customLabels = [
'balance_due', 'balance_due',
'credit_card', 'credit_card',
@ -240,6 +251,8 @@ class Account extends Eloquent
'description', 'description',
'discount', 'discount',
'due_date', 'due_date',
'gateway_fee_item',
'gateway_fee_description',
'hours', 'hours',
'id_number', 'id_number',
'invoice', 'invoice',
@ -265,6 +278,16 @@ class Account extends Eloquent
'vat_number', 'vat_number',
]; ];
public static $customMessageTypes = [
CUSTOM_MESSAGE_DASHBOARD,
CUSTOM_MESSAGE_UNPAID_INVOICE,
CUSTOM_MESSAGE_PAID_INVOICE,
CUSTOM_MESSAGE_UNAPPROVED_QUOTE,
//CUSTOM_MESSAGE_APPROVED_QUOTE,
//CUSTOM_MESSAGE_UNAPPROVED_PROPOSAL,
//CUSTOM_MESSAGE_APPROVED_PROPOSAL,
];
/** /**
* @return \Illuminate\Database\Eloquent\Relations\HasMany * @return \Illuminate\Database\Eloquent\Relations\HasMany
*/ */
@ -361,6 +384,11 @@ class Account extends Eloquent
return $this->hasMany('App\Models\Document')->whereIsDefault(true); return $this->hasMany('App\Models\Document')->whereIsDefault(true);
} }
public function background_image()
{
return $this->hasOne('App\Models\Document', 'id', 'background_image_id');
}
/** /**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/ */
@ -497,6 +525,38 @@ class Account extends Eloquent
$this->attributes['size_id'] = $value ?: null; $this->attributes['size_id'] = $value ?: null;
} }
/**
* @param $value
*/
public function setCustomFieldsAttribute($data)
{
$fields = [];
if (! is_array($data)) {
$data = json_decode($data);
}
foreach ($data as $key => $value) {
if ($value) {
$fields[$key] = $value;
}
}
$this->attributes['custom_fields'] = count($fields) ? json_encode($fields) : null;
}
public function getCustomFieldsAttribute($value)
{
return json_decode($value ?: '{}');
}
public function customLabel($field)
{
$labels = $this->custom_fields;
return ! empty($labels->$field) ? $labels->$field : '';
}
/** /**
* @param int $gatewayId * @param int $gatewayId
* *
@ -707,6 +767,14 @@ class Account extends Eloquent
return $this->currency_id ?: DEFAULT_CURRENCY; return $this->currency_id ?: DEFAULT_CURRENCY;
} }
/**
* @return mixed
*/
public function getCountryId()
{
return $this->country_id ?: DEFAULT_COUNTRY;
}
/** /**
* @param $date * @param $date
* *
@ -825,8 +893,10 @@ class Account extends Eloquent
$gatewayTypes = []; $gatewayTypes = [];
$gatewayIds = []; $gatewayIds = [];
$usedGatewayIds = [];
foreach ($this->account_gateways as $accountGateway) { foreach ($this->account_gateways as $accountGateway) {
$usedGatewayIds[] = $accountGateway->gateway_id;
$paymentDriver = $accountGateway->paymentDriver(); $paymentDriver = $accountGateway->paymentDriver();
$gatewayTypes = array_unique(array_merge($gatewayTypes, $paymentDriver->gatewayTypes())); $gatewayTypes = array_unique(array_merge($gatewayTypes, $paymentDriver->gatewayTypes()));
} }
@ -1476,28 +1546,6 @@ class Account extends Eloquent
return true; return true;
} }
/**
* @param $field
* @param bool $entity
*
* @return bool
*/
public function showCustomField($field, $entity = false)
{
if ($this->hasFeature(FEATURE_INVOICE_SETTINGS) && $this->$field) {
return true;
}
if (! $entity) {
return false;
}
// convert (for example) 'custom_invoice_label1' to 'invoice.custom_value1'
$field = str_replace(['invoice_', 'label'], ['', 'value'], $field);
return Utils::isEmpty($entity->$field) ? false : true;
}
/** /**
* @return bool * @return bool
*/ */

View File

@ -2,6 +2,8 @@
namespace App\Models; namespace App\Models;
use Utils;
use HTMLUtils;
use Crypt; use Crypt;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Laracasts\Presenter\PresentableTrait; use Laracasts\Presenter\PresentableTrait;
@ -114,6 +116,11 @@ class AccountGateway extends EntityModel
} }
} }
public function isCustom()
{
return in_array($this->gateway_id, [GATEWAY_CUSTOM1, GATEWAY_CUSTOM2, GATEWAY_CUSTOM3]);
}
/** /**
* @param $config * @param $config
*/ */
@ -293,4 +300,22 @@ class AccountGateway extends EntityModel
return $this->getConfigField('testMode'); return $this->getConfigField('testMode');
} }
} }
public function getCustomHtml($invitation)
{
$text = $this->getConfigField('text');
if ($text == strip_tags($text)) {
$text = nl2br($text);
}
if (Utils::isNinja()) {
$text = HTMLUtils::sanitizeHTML($text);
}
$templateService = app('App\Services\TemplateService');
$text = $templateService->processVariables($text, ['invitation' => $invitation]);
return $text;
}
} }

View File

@ -6,6 +6,7 @@ use Carbon;
use DB; use DB;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Laracasts\Presenter\PresentableTrait; use Laracasts\Presenter\PresentableTrait;
use App\Models\Traits\HasCustomMessages;
use Utils; use Utils;
/** /**
@ -15,6 +16,7 @@ class Client extends EntityModel
{ {
use PresentableTrait; use PresentableTrait;
use SoftDeletes; use SoftDeletes;
use HasCustomMessages;
/** /**
* @var string * @var string
@ -61,6 +63,7 @@ class Client extends EntityModel
'shipping_country_id', 'shipping_country_id',
'show_tasks_in_portal', 'show_tasks_in_portal',
'send_reminders', 'send_reminders',
'custom_messages',
]; ];
/** /**

View File

@ -234,6 +234,23 @@ class Document extends EntityModel
return $disk->get($this->path); return $disk->get($this->path);
} }
/**
* @return mixed
*/
public function getRawCached()
{
$key = 'image:' . $this->path;
if ($image = cache($key)) {
// do nothing
} else {
$image = $this->getRaw();
cache([$key => $image], 120);
}
return $image;
}
/** /**
* @return mixed * @return mixed
*/ */
@ -356,6 +373,11 @@ class Document extends EntityModel
return $document; return $document;
} }
public function scopeProposalImages($query)
{
return $query->whereIsProposal(1);
}
} }
Document::deleted(function ($document) { Document::deleted(function ($document) {

View File

@ -108,7 +108,11 @@ class EntityModel extends Eloquent
$className = get_called_class(); $className = get_called_class();
return $className::scope($publicId)->withTrashed()->value('id'); if (method_exists($className, 'trashed')) {
return $className::scope($publicId)->withTrashed()->value('id');
} else {
return $className::scope($publicId)->value('id');
}
} }
/** /**

View File

@ -52,6 +52,8 @@ class Expense extends EntityModel
'transaction_reference', 'transaction_reference',
'invoice_documents', 'invoice_documents',
'should_be_invoiced', 'should_be_invoiced',
'custom_value1',
'custom_value2',
]; ];
public static function getImportColumns() public static function getImportColumns()
@ -64,6 +66,9 @@ class Expense extends EntityModel
'private_notes', 'private_notes',
'expense_category', 'expense_category',
'expense_date', 'expense_date',
'payment_type',
'payment_date',
'transaction_reference',
]; ];
} }
@ -76,7 +81,10 @@ class Expense extends EntityModel
'vendor' => 'vendor', 'vendor' => 'vendor',
'notes|details^private' => 'public_notes', 'notes|details^private' => 'public_notes',
'notes|details^public' => 'private_notes', 'notes|details^public' => 'private_notes',
'date' => 'expense_date', 'date^payment' => 'expense_date',
'payment type' => 'payment_type',
'payment date' => 'payment_date',
'reference' => 'transaction_reference',
]; ];
} }

View File

@ -48,7 +48,9 @@ class Gateway extends Eloquent
GATEWAY_AUTHORIZE_NET, GATEWAY_AUTHORIZE_NET,
GATEWAY_MOLLIE, GATEWAY_MOLLIE,
GATEWAY_GOCARDLESS, GATEWAY_GOCARDLESS,
GATEWAY_CUSTOM, GATEWAY_CUSTOM1,
GATEWAY_CUSTOM2,
GATEWAY_CUSTOM3,
]; ];
// allow adding these gateway if another gateway // allow adding these gateway if another gateway
@ -61,7 +63,9 @@ class Gateway extends Eloquent
GATEWAY_GOCARDLESS, GATEWAY_GOCARDLESS,
GATEWAY_BITPAY, GATEWAY_BITPAY,
GATEWAY_DWOLLA, GATEWAY_DWOLLA,
GATEWAY_CUSTOM, GATEWAY_CUSTOM1,
GATEWAY_CUSTOM2,
GATEWAY_CUSTOM3,
]; ];
/** /**
@ -141,6 +145,10 @@ class Gateway extends Eloquent
$query->where('payment_library_id', '=', 1) $query->where('payment_library_id', '=', 1)
->whereIn('id', static::$preferred) ->whereIn('id', static::$preferred)
->whereIn('id', $accountGatewaysIds); ->whereIn('id', $accountGatewaysIds);
if (! Utils::isNinja()) {
$query->where('id', '!=', GATEWAY_WEPAY);
}
} }
/** /**
@ -205,6 +213,6 @@ class Gateway extends Eloquent
public function isCustom() public function isCustom()
{ {
return $this->id === GATEWAY_CUSTOM; return in_array($this->id, [GATEWAY_CUSTOM1, GATEWAY_CUSTOM2, GATEWAY_CUSTOM3]);
} }
} }

View File

@ -529,6 +529,18 @@ class Invoice extends EntityModel implements BalanceAffecting
return $this->isType(INVOICE_TYPE_QUOTE); return $this->isType(INVOICE_TYPE_QUOTE);
} }
/**
* @return string
*/
public function getCustomMessageType()
{
if ($this->isQuote()) {
return $this->quote_invoice_id ? CUSTOM_MESSAGE_APPROVED_QUOTE : CUSTOM_MESSAGE_UNAPPROVED_QUOTE;
} else {
return $this->balance > 0 ? CUSTOM_MESSAGE_UNPAID_INVOICE : CUSTOM_MESSAGE_PAID_INVOICE;
}
}
/** /**
* @return bool * @return bool
*/ */
@ -1001,28 +1013,17 @@ class Invoice extends EntityModel implements BalanceAffecting
'country', 'country',
'country_id', 'country_id',
'currency_id', 'currency_id',
'custom_label1', 'custom_fields',
'custom_value1', 'custom_value1',
'custom_label2',
'custom_value2', 'custom_value2',
'custom_client_label1',
'custom_client_label2',
'custom_contact_label1',
'custom_contact_label2',
'primary_color', 'primary_color',
'secondary_color', 'secondary_color',
'hide_quantity', 'hide_quantity',
'hide_paid_to_date', 'hide_paid_to_date',
'all_pages_header', 'all_pages_header',
'all_pages_footer', 'all_pages_footer',
'custom_invoice_label1',
'custom_invoice_label2',
'pdf_email_attachment', 'pdf_email_attachment',
'show_item_taxes', 'show_item_taxes',
'custom_invoice_text_label1',
'custom_invoice_text_label2',
'custom_invoice_item_label1',
'custom_invoice_item_label2',
'invoice_embed_documents', 'invoice_embed_documents',
'page_size', 'page_size',
'include_item_taxes_inline', 'include_item_taxes_inline',
@ -1224,7 +1225,7 @@ class Invoice extends EntityModel implements BalanceAffecting
/** /**
* @return bool|string * @return bool|string
*/ */
public function getPDFString($decode = true) public function getPDFString($invitation = false, $decode = true)
{ {
if (! env('PHANTOMJS_CLOUD_KEY') && ! env('PHANTOMJS_BIN_PATH')) { if (! env('PHANTOMJS_CLOUD_KEY') && ! env('PHANTOMJS_BIN_PATH')) {
return false; return false;
@ -1234,7 +1235,7 @@ class Invoice extends EntityModel implements BalanceAffecting
return false; return false;
} }
$invitation = $this->invitations[0]; $invitation = $invitation ?: $this->invitations[0];
$link = $invitation->getLink('view', true, true); $link = $invitation->getLink('view', true, true);
$pdfString = false; $pdfString = false;
$phantomjsSecret = env('PHANTOMJS_SECRET'); $phantomjsSecret = env('PHANTOMJS_SECRET');

View File

@ -75,7 +75,7 @@ class InvoiceItem extends EntityModel
return $this->belongsTo('App\Models\Account'); return $this->belongsTo('App\Models\Account');
} }
public function amount() public function getPreTaxAmount()
{ {
$amount = $this->cost * $this->qty; $amount = $this->cost * $this->qty;
@ -83,21 +83,32 @@ class InvoiceItem extends EntityModel
if ($this->invoice->is_amount_discount) { if ($this->invoice->is_amount_discount) {
$amount -= $this->discount; $amount -= $this->discount;
} else { } else {
$amount -= $amount * $this->discount / 100; $amount -= round($amount * $this->discount / 100, 4);
} }
} }
$preTaxAmount = $amount; return $amount;
}
public function getTaxAmount()
{
$tax = 0;
$preTaxAmount = $this->getPreTaxAmount();
if ($this->tax_rate1) { if ($this->tax_rate1) {
$amount += $preTaxAmount * $this->tax_rate1 / 100; $tax += round($preTaxAmount * $this->tax_rate1 / 100, 2);
} }
if ($this->tax_rate2) { if ($this->tax_rate2) {
$amount += $preTaxAmount * $this->tax_rate2 / 100; $tax += round($preTaxAmount * $this->tax_rate2 / 100, 2);
} }
return $amount; return $tax;
}
public function amount()
{
return $this->getPreTaxAmount() + $this->getTaxAmount();
} }
public function markFeePaid() public function markFeePaid()

View File

@ -28,6 +28,8 @@ class Project extends EntityModel
'private_notes', 'private_notes',
'due_date', 'due_date',
'budgeted_hours', 'budgeted_hours',
'custom_value1',
'custom_value2',
]; ];
/** /**

View File

@ -115,6 +115,19 @@ class Proposal extends EntityModel
return trans('texts.proposal') . '_' . $this->invoice->invoice_number . '.' . $extension; return trans('texts.proposal') . '_' . $this->invoice->invoice_number . '.' . $extension;
} }
/**
* @return string
*/
public function getCustomMessageType()
{
if ($this->invoice->quote_invoice_id) {
return CUSTOM_MESSAGE_APPROVED_PROPOSAL;
} else {
return CUSTOM_MESSAGE_UNAPPROVED_PROPOSAL;
}
}
} }
Proposal::creating(function ($project) { Proposal::creating(function ($project) {

View File

@ -24,6 +24,8 @@ class Task extends EntityModel
'description', 'description',
'time_log', 'time_log',
'is_running', 'is_running',
'custom_value1',
'custom_value2',
]; ];
/** /**
@ -259,10 +261,7 @@ class Task extends EntityModel
$statuses[$status->public_id] = $status->name; $statuses[$status->public_id] = $status->name;
} }
if (! $taskStatues->count()) { $statuses[TASK_STATUS_LOGGED] = trans('texts.logged');
$statuses[TASK_STATUS_LOGGED] = trans('texts.logged');
}
$statuses[TASK_STATUS_RUNNING] = trans('texts.running'); $statuses[TASK_STATUS_RUNNING] = trans('texts.running');
$statuses[TASK_STATUS_INVOICED] = trans('texts.invoiced'); $statuses[TASK_STATUS_INVOICED] = trans('texts.invoiced');
$statuses[TASK_STATUS_PAID] = trans('texts.paid'); $statuses[TASK_STATUS_PAID] = trans('texts.paid');

View File

@ -0,0 +1,49 @@
<?php
namespace App\Models\Traits;
/**
* Class HasCustomMessages.
*/
trait HasCustomMessages
{
/**
* @param $value
*/
public function setCustomMessagesAttribute($data)
{
$fields = [];
if (! is_array($data)) {
$data = json_decode($data);
}
foreach ($data as $key => $value) {
if ($value) {
$fields[$key] = $value;
}
}
$this->attributes['custom_messages'] = count($fields) ? json_encode($fields) : null;
}
public function getCustomMessagesAttribute($value)
{
return json_decode($value ?: '{}');
}
public function customMessage($type)
{
$messages = $this->custom_messages;
if (! empty($messages->$type)) {
return $messages->$type;
}
if ($this->account) {
return $this->account->customMessage($type);
} else {
return '';
}
}
}

View File

@ -15,6 +15,18 @@ trait HasRecurrence
* @return bool * @return bool
*/ */
public function shouldSendToday() public function shouldSendToday()
{
if (Utils::isSelfHost()) {
return $this->shouldSendTodayNew();
} else {
return $this->shouldSendTodayOld();
}
}
/**
* @return bool
*/
public function shouldSendTodayOld()
{ {
if (! $this->user->confirmed) { if (! $this->user->confirmed) {
return false; return false;
@ -79,8 +91,7 @@ trait HasRecurrence
return false; return false;
} }
/* public function shouldSendTodayNew()
public function shouldSendToday()
{ {
if (! $this->user->confirmed) { if (! $this->user->confirmed) {
return false; return false;
@ -114,7 +125,6 @@ trait HasRecurrence
return $this->account->getDateTime() >= $nextSendDate; return $this->account->getDateTime() >= $nextSendDate;
} }
} }
*/
/** /**
* @throws \Recurr\Exception\MissingData * @throws \Recurr\Exception\MissingData

View File

@ -101,22 +101,22 @@ trait PresentsInvoice
] ]
]; ];
if ($this->custom_invoice_text_label1) { if ($this->customLabel('invoice_text1')) {
$fields[INVOICE_FIELDS_INVOICE][] = 'invoice.custom_text_value1'; $fields[INVOICE_FIELDS_INVOICE][] = 'invoice.custom_text_value1';
} }
if ($this->custom_invoice_text_label2) { if ($this->customLabel('invoice_text2')) {
$fields[INVOICE_FIELDS_INVOICE][] = 'invoice.custom_text_value2'; $fields[INVOICE_FIELDS_INVOICE][] = 'invoice.custom_text_value2';
} }
if ($this->custom_client_label1) { if ($this->customLabel('client1')) {
$fields[INVOICE_FIELDS_CLIENT][] = 'client.custom_value1'; $fields[INVOICE_FIELDS_CLIENT][] = 'client.custom_value1';
} }
if ($this->custom_client_label2) { if ($this->customLabel('client2')) {
$fields[INVOICE_FIELDS_CLIENT][] = 'client.custom_value2'; $fields[INVOICE_FIELDS_CLIENT][] = 'client.custom_value2';
} }
if ($this->custom_contact_label1) { if ($this->customLabel('contact1')) {
$fields[INVOICE_FIELDS_CLIENT][] = 'contact.custom_value1'; $fields[INVOICE_FIELDS_CLIENT][] = 'contact.custom_value1';
} }
if ($this->custom_contact_label2) { if ($this->customLabel('contact2')) {
$fields[INVOICE_FIELDS_CLIENT][] = 'contact.custom_value2'; $fields[INVOICE_FIELDS_CLIENT][] = 'contact.custom_value2';
} }
if ($this->custom_label1) { if ($this->custom_label1) {
@ -354,18 +354,18 @@ trait PresentsInvoice
} }
foreach ([ foreach ([
'account.custom_value1' => 'custom_label1', 'account.custom_value1' => 'account1',
'account.custom_value2' => 'custom_label2', 'account.custom_value2' => 'account2',
'invoice.custom_text_value1' => 'custom_invoice_text_label1', 'invoice.custom_text_value1' => 'invoice_text1',
'invoice.custom_text_value2' => 'custom_invoice_text_label2', 'invoice.custom_text_value2' => 'invoice_text2',
'client.custom_value1' => 'custom_client_label1', 'client.custom_value1' => 'client1',
'client.custom_value2' => 'custom_client_label2', 'client.custom_value2' => 'client2',
'contact.custom_value1' => 'custom_contact_label1', 'contact.custom_value1' => 'contact1',
'contact.custom_value2' => 'custom_contact_label2', 'contact.custom_value2' => 'contact2',
'product.custom_value1' => 'custom_invoice_item_label1', 'product.custom_value1' => 'product1',
'product.custom_value2' => 'custom_invoice_item_label2', 'product.custom_value2' => 'product2',
] as $field => $property) { ] as $field => $property) {
$data[$field] = e(Utils::getCustomLabel($this->$property)) ?: trans('texts.custom_field'); $data[$field] = e($this->present()->customLabel($property)) ?: trans('texts.custom_field');
} }
return $data; return $data;

View File

@ -204,4 +204,13 @@ trait SendsEmails
return Domain::getEmailFromId($this->domain_id); return Domain::getEmailFromId($this->domain_id);
} }
public function getDailyEmailLimit()
{
$limit = MAX_EMAILS_SENT_PER_DAY;
$limit += $this->created_at->diffInMonths() * 100;
return min($limit, 1000);
}
} }

View File

@ -44,6 +44,8 @@ class Vendor extends EntityModel
'currency_id', 'currency_id',
'website', 'website',
'transaction_name', 'transaction_name',
'custom_value1',
'custom_value2',
]; ];
/** /**
@ -98,10 +100,10 @@ class Vendor extends EntityModel
self::$fieldPostalCode, self::$fieldPostalCode,
self::$fieldCountry, self::$fieldCountry,
self::$fieldNotes, self::$fieldNotes,
VendorContact::$fieldFirstName, 'contact_first_name',
VendorContact::$fieldLastName, 'contact_last_name',
VendorContact::$fieldPhone, 'contact_email',
VendorContact::$fieldEmail, 'contact_phone',
]; ];
} }
@ -111,11 +113,12 @@ class Vendor extends EntityModel
public static function getImportMap() public static function getImportMap()
{ {
return [ return [
'first' => 'first_name', 'first' => 'contact_first_name',
'last' => 'last_name', 'last' => 'contact_last_name',
'email' => 'email', 'email' => 'contact_email',
'mobile|phone' => 'phone', 'mobile|phone' => 'contact_phone',
'name|organization' => 'name', 'work|office' => 'work_phone',
'name|organization|vendor' => 'name',
'street2|address2' => 'address2', 'street2|address2' => 'address2',
'street|address|address1' => 'address1', 'street|address|address1' => 'address1',
'city' => 'city', 'city' => 'city',

View File

@ -25,7 +25,7 @@ class AccountGatewayDatatable extends EntityDatatable
$accountGateway = $this->getAccountGateway($model->id); $accountGateway = $this->getAccountGateway($model->id);
if ($model->deleted_at) { if ($model->deleted_at) {
return $model->name; return $model->name;
} elseif ($model->gateway_id == GATEWAY_CUSTOM) { } elseif (in_array($model->gateway_id, [GATEWAY_CUSTOM1, GATEWAY_CUSTOM2, GATEWAY_CUSTOM3])) {
$name = $accountGateway->getConfigField('name') . ' [' . trans('texts.custom') . ']'; $name = $accountGateway->getConfigField('name') . ' [' . trans('texts.custom') . ']';
return link_to("gateways/{$model->public_id}/edit", $name)->toHtml(); return link_to("gateways/{$model->public_id}/edit", $name)->toHtml();
} elseif ($model->gateway_id != GATEWAY_WEPAY) { } elseif ($model->gateway_id != GATEWAY_WEPAY) {
@ -191,8 +191,12 @@ class AccountGatewayDatatable extends EntityDatatable
}, },
function ($model) use ($gatewayType) { function ($model) use ($gatewayType) {
// Only show this action if the given gateway supports this gateway type // Only show this action if the given gateway supports this gateway type
if ($model->gateway_id == GATEWAY_CUSTOM) { if ($model->gateway_id == GATEWAY_CUSTOM1) {
return $gatewayType->id == GATEWAY_TYPE_CUSTOM; return $gatewayType->id == GATEWAY_TYPE_CUSTOM1;
} elseif ($model->gateway_id == GATEWAY_CUSTOM2) {
return $gatewayType->id == GATEWAY_TYPE_CUSTOM2;
} elseif ($model->gateway_id == GATEWAY_CUSTOM3) {
return $gatewayType->id == GATEWAY_TYPE_CUSTOM3;
} else { } else {
$accountGateway = $this->getAccountGateway($model->id); $accountGateway = $this->getAccountGateway($model->id);
return $accountGateway->paymentDriver()->supportsGatewayType($gatewayType->id); return $accountGateway->paymentDriver()->supportsGatewayType($gatewayType->id);
@ -229,8 +233,12 @@ class AccountGatewayDatatable extends EntityDatatable
private function getGatewayTypes($id, $gatewayId) private function getGatewayTypes($id, $gatewayId)
{ {
if ($gatewayId == GATEWAY_CUSTOM) { if ($gatewayId == GATEWAY_CUSTOM1) {
$gatewayTypes = [GATEWAY_TYPE_CUSTOM]; $gatewayTypes = [GATEWAY_TYPE_CUSTOM1];
} elseif ($gatewayId == GATEWAY_CUSTOM2) {
$gatewayTypes = [GATEWAY_TYPE_CUSTOM2];
} elseif ($gatewayId == GATEWAY_CUSTOM3) {
$gatewayTypes = [GATEWAY_TYPE_CUSTOM3];
} else { } else {
$accountGateway = $this->getAccountGateway($id); $accountGateway = $this->getAccountGateway($id);
$paymentDriver = $accountGateway->paymentDriver(); $paymentDriver = $accountGateway->paymentDriver();

View File

@ -47,14 +47,14 @@ class ProductDatatable extends EntityDatatable
function ($model) { function ($model) {
return $model->custom_value1; return $model->custom_value1;
}, },
$account->custom_invoice_item_label1 $account->customLabel('product1')
], ],
[ [
'custom_value2', 'custom_value2',
function ($model) { function ($model) {
return $model->custom_value2; return $model->custom_value2;
}, },
$account->custom_invoice_item_label2 $account->customLabel('product2')
] ]
]; ];
} }

View File

@ -3,6 +3,7 @@
namespace App\Ninja\Datatables; namespace App\Ninja\Datatables;
use App\Models\Task; use App\Models\Task;
use App\Models\TaskStatus;
use Auth; use Auth;
use URL; use URL;
use Utils; use Utils;
@ -129,4 +130,26 @@ class TaskDatatable extends EntityDatatable
return "<h4><div class=\"label label-{$class}\">$label</div></h4>"; return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
} }
public function bulkActions()
{
$actions = [];
$statuses = TaskStatus::scope()->orderBy('sort_order')->get();
foreach ($statuses as $status) {
$actions[] = [
'label' => sprintf('%s %s', trans('texts.mark'), $status->name),
'url' => 'javascript:submitForm_' . $this->entityType . '("update_status:' . $status->public_id . '")',
];
}
if (count($actions)) {
$actions[] = \DropdownButton::DIVIDER;
}
$actions = array_merge($actions, parent::bulkActions());
return $actions;
}
} }

View File

@ -39,6 +39,19 @@ class BaseTransformer extends TransformerAbstract
return isset($this->maps[ENTITY_CLIENT][$name]); return isset($this->maps[ENTITY_CLIENT][$name]);
} }
/**
* @param $name
*
* @return bool
*/
public function hasVendor($name)
{
$name = trim(strtolower($name));
return isset($this->maps[ENTITY_VENDOR][$name]);
}
/** /**
* @param $key * @param $key
* *

View File

@ -2,6 +2,7 @@
namespace App\Ninja\Import\CSV; namespace App\Ninja\Import\CSV;
use Utils;
use App\Ninja\Import\BaseTransformer; use App\Ninja\Import\BaseTransformer;
use League\Fractal\Resource\Item; use League\Fractal\Resource\Item;
@ -18,14 +19,20 @@ class ExpenseTransformer extends BaseTransformer
public function transform($data) public function transform($data)
{ {
return new Item($data, function ($data) { return new Item($data, function ($data) {
$clientId = isset($data->client) ? $this->getClientId($data->client) : null;
return [ return [
'amount' => $this->getFloat($data, 'amount'), 'amount' => $this->getFloat($data, 'amount'),
'vendor_id' => isset($data->vendor) ? $this->getVendorId($data->vendor) : null, 'vendor_id' => isset($data->vendor) ? $this->getVendorId($data->vendor) : null,
'client_id' => isset($data->client) ? $this->getClientId($data->client) : null, 'client_id' => $clientId,
'expense_date' => isset($data->expense_date) ? date('Y-m-d', strtotime($data->expense_date)) : null, 'expense_date' => isset($data->expense_date) ? date('Y-m-d', strtotime($data->expense_date)) : null,
'public_notes' => $this->getString($data, 'public_notes'), 'public_notes' => $this->getString($data, 'public_notes'),
'private_notes' => $this->getString($data, 'private_notes'), 'private_notes' => $this->getString($data, 'private_notes'),
'expense_category_id' => isset($data->expense_category) ? $this->getExpenseCategoryId($data->expense_category) : null, 'expense_category_id' => isset($data->expense_category) ? $this->getExpenseCategoryId($data->expense_category) : null,
'payment_type_id' => isset($data->payment_type) ? Utils::lookupIdInCache($data->payment_type, 'paymentTypes') : null,
'payment_date' => isset($data->payment_date) ? date('Y-m-d', strtotime($data->payment_date)) : null,
'transaction_reference' => $this->getString($data, 'transaction_reference'),
'should_be_invoiced' => $clientId ? true : false,
]; ];
}); });
} }

View File

@ -31,12 +31,12 @@ class VendorTransformer extends BaseTransformer
'state' => $this->getString($data, 'state'), 'state' => $this->getString($data, 'state'),
'postal_code' => $this->getString($data, 'postal_code'), 'postal_code' => $this->getString($data, 'postal_code'),
'private_notes' => $this->getString($data, 'notes'), 'private_notes' => $this->getString($data, 'notes'),
'contacts' => [ 'vendor_contacts' => [
[ [
'first_name' => $this->getString($data, 'first_name'), 'first_name' => $this->getString($data, 'contact_first_name'),
'last_name' => $this->getString($data, 'last_name'), 'last_name' => $this->getString($data, 'contact_last_name'),
'email' => $this->getString($data, 'email'), 'email' => $this->getString($data, 'contact_email'),
'phone' => $this->getString($data, 'phone'), 'phone' => $this->getString($data, 'contact_phone'),
], ],
], ],
'country_id' => isset($data->country) ? $this->getCountryId($data->country) : null, 'country_id' => isset($data->country) ? $this->getCountryId($data->country) : null,

View File

@ -0,0 +1,41 @@
<?php
namespace App\Ninja\Import\Pancake;
use App\Ninja\Import\BaseTransformer;
use League\Fractal\Resource\Item;
/**
* Class ClientTransformer.
*/
class ClientTransformer extends BaseTransformer
{
/**
* @param $data
*
* @return bool|Item
*/
public function transform($data)
{
if ($this->hasClient($data->company)) {
return false;
}
return new Item($data, function ($data) {
return [
'name' => $this->getString($data, 'company'),
'work_phone' => $this->getString($data, 'telephone_number'),
'website' => $this->getString($data, 'website_url'),
'private_notes' => $this->getString($data, 'notes'),
'contacts' => [
[
'first_name' => $this->getString($data, 'first_name'),
'last_name' => $this->getString($data, 'last_name'),
'email' => $this->getString($data, 'email'),
'phone' => $this->getString($data, 'mobile_number'),
],
],
];
});
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace App\Ninja\Import\Pancake;
use App\Ninja\Import\BaseTransformer;
use League\Fractal\Resource\Item;
/**
* Class InvoiceTransformer.
*/
class InvoiceTransformer extends BaseTransformer
{
/**
* @param $data
*
* @return bool|Item
*/
public function transform($data)
{
if (! $this->getClientId($data->client)) {
return false;
}
if ($this->hasInvoice($data->invoice)) {
return false;
}
if ($data->recurring == 'Yes') {
return false;
}
return new Item($data, function ($data) {
return [
'client_id' => $this->getClientId($data->client),
'invoice_number' => $this->getInvoiceNumber($data->invoice),
'invoice_date' => ! empty($data->date_of_creation) ? date('Y-m-d', strtotime($data->date_of_creation)) : null,
'due_date' => ! empty($data->due_date) ? date('Y-m-d', strtotime($data->due_date)) : null,
'paid' => (float) $data->amount_paid,
'public_notes' => $this->getString($data, 'notes'),
'private_notes' => $this->getString($data, 'description'),
'invoice_date_sql' => $data->create_date,
'invoice_items' => [
[
'product_key' => $data->item_1_gross_discount > 0 ? trans('texts.discount') : $data->item_1_name,
'notes' => $data->item_1_description,
'cost' => (float) $data->item_1_gross_discount > 0 ? $data->item_1_gross_discount * -1 : $data->item_1_rate,
'qty' => $data->item_1_quantity,
],
[
'product_key' => $data->item_2_gross_discount > 0 ? trans('texts.discount') : $data->item_2_name,
'notes' => $data->item_2_description,
'cost' => (float) $data->item_2_gross_discount > 0 ? $data->item_2_gross_discount * -1 : $data->item_2_rate,
'qty' => $data->item_2_quantity,
],
[
'product_key' => $data->item_3_gross_discount > 0 ? trans('texts.discount') : $data->item_3_name,
'notes' => $data->item_3_description,
'cost' => (float) $data->item_3_gross_discount > 0 ? $data->item_3_gross_discount * -1 : $data->item_3_rate,
'qty' => $data->item_3_quantity,
],
[
'product_key' => $data->item_4_gross_discount > 0 ? trans('texts.discount') : $data->item_4_name,
'notes' => $data->item_4_description,
'cost' => (float) $data->item_4_gross_discount > 0 ? $data->item_4_gross_discount * -1 : $data->item_4_rate,
'qty' => $data->item_4_quantity,
],
],
];
});
}
}

View File

@ -70,9 +70,6 @@ class ContactMailer extends Mailer
$pdfString = false; $pdfString = false;
$ublString = false; $ublString = false;
if ($account->attachPDF() && ! $proposal) {
$pdfString = $invoice->getPDFString();
}
if ($account->attachUBL() && ! $proposal) { if ($account->attachUBL() && ! $proposal) {
$ublString = dispatch(new ConvertInvoiceToUbl($invoice)); $ublString = dispatch(new ConvertInvoiceToUbl($invoice));
} }
@ -100,6 +97,9 @@ class ContactMailer extends Mailer
$isFirst = true; $isFirst = true;
$invitations = $proposal ? $proposal->invitations : $invoice->invitations; $invitations = $proposal ? $proposal->invitations : $invoice->invitations;
foreach ($invitations as $invitation) { foreach ($invitations as $invitation) {
if ($account->attachPDF() && ! $proposal) {
$pdfString = $invoice->getPDFString($invitation);
}
$data = [ $data = [
'pdfString' => $pdfString, 'pdfString' => $pdfString,
'documentStrings' => $documentStrings, 'documentStrings' => $documentStrings,
@ -266,6 +266,7 @@ class ContactMailer extends Mailer
$account->loadLocalizationSettings($client); $account->loadLocalizationSettings($client);
$invoice = $payment->invoice; $invoice = $payment->invoice;
$invitation = $payment->invitation ?: $payment->invoice->invitations[0];
$accountName = $account->getDisplayName(); $accountName = $account->getDisplayName();
if ($refunded > 0) { if ($refunded > 0) {
@ -282,11 +283,9 @@ class ContactMailer extends Mailer
if ($payment->invitation) { if ($payment->invitation) {
$user = $payment->invitation->user; $user = $payment->invitation->user;
$contact = $payment->contact; $contact = $payment->contact;
$invitation = $payment->invitation;
} else { } else {
$user = $payment->user; $user = $payment->user;
$contact = $client->contacts->count() ? $client->contacts[0] : ''; $contact = $client->contacts->count() ? $client->contacts[0] : '';
$invitation = $payment->invoice->invitations[0];
} }
$variables = [ $variables = [
@ -382,7 +381,7 @@ class ContactMailer extends Mailer
// http://stackoverflow.com/questions/1375501/how-do-i-throttle-my-sites-api-users // http://stackoverflow.com/questions/1375501/how-do-i-throttle-my-sites-api-users
$day = 60 * 60 * 24; $day = 60 * 60 * 24;
$day_limit = MAX_EMAILS_SENT_PER_DAY; $day_limit = $account->getDailyEmailLimit();
$day_throttle = Cache::get("email_day_throttle:{$key}", null); $day_throttle = Cache::get("email_day_throttle:{$key}", null);
$last_api_request = Cache::get("last_email_request:{$key}", 0); $last_api_request = Cache::get("last_email_request:{$key}", 0);
$last_api_diff = time() - $last_api_request; $last_api_diff = time() - $last_api_request;

View File

@ -40,7 +40,10 @@ class Mailer
$toEmail = strtolower($toEmail); $toEmail = strtolower($toEmail);
$replyEmail = $fromEmail; $replyEmail = $fromEmail;
$fromEmail = CONTACT_EMAIL; $fromEmail = CONTACT_EMAIL;
//\Log::info("{$toEmail} | {$replyEmail} | $fromEmail");
if (Utils::isSelfHost() && config('app.debug')) {
\Log::info("Sending email - To: {$toEmail} | Reply: {$replyEmail} | From: $fromEmail");
}
// Optionally send for alternate domain // Optionally send for alternate domain
if (! empty($data['fromEmail'])) { if (! empty($data['fromEmail'])) {

View File

@ -986,8 +986,14 @@ class BasePaymentDriver
$gatewayTypeAlias = GatewayType::getAliasFromId($gatewayTypeId); $gatewayTypeAlias = GatewayType::getAliasFromId($gatewayTypeId);
if ($gatewayTypeId == GATEWAY_TYPE_CUSTOM) { if ($gatewayTypeId == GATEWAY_TYPE_CUSTOM1) {
$url = 'javascript:showCustomModal();'; $url = 'javascript:showCustom1Modal();';
$label = e($this->accountGateway->getConfigField('name'));
} elseif ($gatewayTypeId == GATEWAY_TYPE_CUSTOM2) {
$url = 'javascript:showCustom2Modal();';
$label = e($this->accountGateway->getConfigField('name'));
} elseif ($gatewayTypeId == GATEWAY_TYPE_CUSTOM3) {
$url = 'javascript:showCustom3Modal();';
$label = e($this->accountGateway->getConfigField('name')); $label = e($this->accountGateway->getConfigField('name'));
} else { } else {
$url = $this->paymentUrl($gatewayTypeAlias); $url = $this->paymentUrl($gatewayTypeAlias);

View File

@ -2,12 +2,12 @@
namespace App\Ninja\PaymentDrivers; namespace App\Ninja\PaymentDrivers;
class CustomPaymentDriver extends BasePaymentDriver class Custom1PaymentDriver extends BasePaymentDriver
{ {
public function gatewayTypes() public function gatewayTypes()
{ {
return [ return [
GATEWAY_TYPE_CUSTOM, GATEWAY_TYPE_CUSTOM1,
]; ];
} }
} }

View File

@ -0,0 +1,13 @@
<?php
namespace App\Ninja\PaymentDrivers;
class Custom2PaymentDriver extends BasePaymentDriver
{
public function gatewayTypes()
{
return [
GATEWAY_TYPE_CUSTOM2,
];
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Ninja\PaymentDrivers;
class Custom3PaymentDriver extends BasePaymentDriver
{
public function gatewayTypes()
{
return [
GATEWAY_TYPE_CUSTOM3,
];
}
}

View File

@ -195,20 +195,20 @@ class AccountPresenter extends Presenter
public function customTextFields() public function customTextFields()
{ {
$fields = [ $fields = [
'custom_client_label1' => 'custom_client1', 'client1' => 'custom_client1',
'custom_client_label2' => 'custom_client2', 'client1' => 'custom_client2',
'custom_contact_label1' => 'custom_contact1', 'contact1' => 'custom_contact1',
'custom_contact_label2' => 'custom_contact2', 'contact2' => 'custom_contact2',
'custom_invoice_text_label1' => 'custom_invoice1', 'invoice_text1' => 'custom_invoice1',
'custom_invoice_text_label2' => 'custom_invoice2', 'invoice_text2' => 'custom_invoice2',
'custom_invoice_item_label1' => 'custom_product1', 'product1' => 'custom_product1',
'custom_invoice_item_label2' => 'custom_product2', 'product2' => 'custom_product2',
]; ];
$data = []; $data = [];
foreach ($fields as $key => $val) { foreach ($fields as $key => $val) {
if ($this->$key) { if ($label = $this->customLabel($key)) {
$data[Utils::getCustomLabel($this->$key)] = [ $data[Utils::getCustomLabel($label)] = [
'value' => $val, 'value' => $val,
'name' => $val, 'name' => $val,
]; ];
@ -265,55 +265,8 @@ class AccountPresenter extends Presenter
return $url; return $url;
} }
public function customLabel($field)
public function customClientLabel1()
{ {
return Utils::getCustomLabel($this->entity->custom_client_label1); return Utils::getCustomLabel($this->entity->customLabel($field));
} }
public function customClientLabel2()
{
return Utils::getCustomLabel($this->entity->custom_client_label2);
}
public function customContactLabel1()
{
return Utils::getCustomLabel($this->entity->custom_contact_label1);
}
public function customContactLabel2()
{
return Utils::getCustomLabel($this->entity->custom_contact_label2);
}
public function customInvoiceLabel1()
{
return Utils::getCustomLabel($this->entity->custom_invoice_label1);
}
public function customInvoiceLabel2()
{
return Utils::getCustomLabel($this->entity->custom_invoice_label2);
}
public function customInvoiceTextLabel1()
{
return Utils::getCustomLabel($this->entity->custom_invoice_text_label1);
}
public function customInvoiceTextLabel2()
{
return Utils::getCustomLabel($this->entity->custom_invoice_text_label1);
}
public function customProductLabel1()
{
return Utils::getCustomLabel($this->entity->custom_invoice_item_label1);
}
public function customProductLabel2()
{
return Utils::getCustomLabel($this->entity->custom_invoice_item_label2);
}
} }

View File

@ -59,6 +59,15 @@ class ExpensePresenter extends EntityPresenter
return $this->entity->expense_category ? $this->entity->expense_category->name : ''; return $this->entity->expense_category ? $this->entity->expense_category->name : '';
} }
public function payment_type()
{
if (! $this->payment_type_id) {
return '';
}
return Utils::getFromCache($this->payment_type_id, 'paymentTypes')->name;
}
public function calendarEvent($subColors = false) public function calendarEvent($subColors = false)
{ {
$data = parent::calendarEvent(); $data = parent::calendarEvent();

View File

@ -13,4 +13,31 @@ class UserPresenter extends EntityPresenter
{ {
return $this->entity->first_name . ' ' . $this->entity->last_name; return $this->entity->first_name . ' ' . $this->entity->last_name;
} }
public function statusCode()
{
$status = '';
$user = $this->entity;
$account = $user->account;
if ($user->confirmed) {
$status .= 'C';
} elseif ($user->registered) {
$status .= 'R';
} else {
$status .= 'N';
}
if ($account->isTrial()) {
$status .= 'T';
} elseif ($account->isEnterprise()) {
$status .= 'E';
} elseif ($account->isPro()) {
$status .= 'P';
} else {
$status .= 'H';
}
return $status;
}
} }

View File

@ -22,11 +22,11 @@ class ClientReport extends AbstractReport
$user = auth()->user(); $user = auth()->user();
$account = $user->account; $account = $user->account;
if ($account->custom_client_label1) { if ($account->customLabel('client1')) {
$columns[$account->present()->customClientLabel1] = ['columnSelector-false', 'custom']; $columns[$account->present()->customLabel('client1')] = ['columnSelector-false', 'custom'];
} }
if ($account->custom_client_label2) { if ($account->customLabel('client2')) {
$columns[$account->present()->customClientLabel2] = ['columnSelector-false', 'custom']; $columns[$account->present()->customLabel('client2')] = ['columnSelector-false', 'custom'];
} }
return $columns; return $columns;
@ -75,10 +75,10 @@ class ClientReport extends AbstractReport
$client->user->getDisplayName(), $client->user->getDisplayName(),
]; ];
if ($account->custom_client_label1) { if ($account->customLabel('client1')) {
$row[] = $client->custom_value1; $row[] = $client->custom_value1;
} }
if ($account->custom_client_label2) { if ($account->customLabel('client2')) {
$row[] = $client->custom_value2; $row[] = $client->custom_value2;
} }

View File

@ -23,6 +23,16 @@ class ExpenseReport extends AbstractReport
'user' => ['columnSelector-false'], 'user' => ['columnSelector-false'],
]; ];
$user = auth()->user();
$account = $user->account;
if ($account->customLabel('expense1')) {
$columns[$account->present()->customLabel('expense1')] = ['columnSelector-false', 'custom'];
}
if ($account->customLabel('expense2')) {
$columns[$account->present()->customLabel('expense2')] = ['columnSelector-false', 'custom'];
}
if (TaxRate::scope()->count()) { if (TaxRate::scope()->count()) {
$columns['tax'] = ['columnSelector-false']; $columns['tax'] = ['columnSelector-false'];
} }
@ -85,6 +95,13 @@ class ExpenseReport extends AbstractReport
$expense->user->getDisplayName(), $expense->user->getDisplayName(),
]; ];
if ($account->customLabel('expense1')) {
$row[] = $expense->custom_value1;
}
if ($account->customLabel('expense2')) {
$row[] = $expense->custom_value2;
}
if ($hasTaxRates) { if ($hasTaxRates) {
$row[] = $expense->present()->taxAmount; $row[] = $expense->present()->taxAmount;
} }

View File

@ -31,11 +31,11 @@ class InvoiceReport extends AbstractReport
$account = auth()->user()->account; $account = auth()->user()->account;
if ($account->custom_invoice_text_label1) { if ($account->customLabel('invoice_text1')) {
$columns[$account->present()->customInvoiceTextLabel1] = ['columnSelector-false', 'custom']; $columns[$account->present()->customLabel('invoice_text1')] = ['columnSelector-false', 'custom'];
} }
if ($account->custom_invoice_text_label1) { if ($account->customLabel('invoice_text1')) {
$columns[$account->present()->customInvoiceTextLabel2] = ['columnSelector-false', 'custom']; $columns[$account->present()->customLabel('invoice_text2')] = ['columnSelector-false', 'custom'];
} }
return $columns; return $columns;
@ -108,10 +108,10 @@ class InvoiceReport extends AbstractReport
$row[] = $isFirst ? $account->formatMoney($invoice->getTaxTotal(), $client) : ''; $row[] = $isFirst ? $account->formatMoney($invoice->getTaxTotal(), $client) : '';
} }
if ($account->custom_invoice_text_label1) { if ($account->customLabel('invoice_text1')) {
$row[] = $invoice->custom_text_value1; $row[] = $invoice->custom_text_value1;
} }
if ($account->custom_invoice_text_label2) { if ($account->customLabel('invoice_text2')) {
$row[] = $invoice->custom_text_value2; $row[] = $invoice->custom_text_value2;
} }

View File

@ -31,12 +31,12 @@ class ProductReport extends AbstractReport
} }
} }
if ($account->custom_invoice_item_label1) { if ($account->customLabel('product1')) {
$columns[$account->present()->customProductLabel1] = ['columnSelector-false', 'custom']; $columns[$account->present()->customLabel('product1')] = ['columnSelector-false', 'custom'];
} }
if ($account->custom_invoice_item_label2) { if ($account->customLabel('product2')) {
$columns[$account->present()->customProductLabel2] = ['columnSelector-false', 'custom']; $columns[$account->present()->customLabel('product2')] = ['columnSelector-false', 'custom'];
} }
return $columns; return $columns;
@ -75,17 +75,14 @@ class ProductReport extends AbstractReport
]; ];
if ($account->invoice_item_taxes) { if ($account->invoice_item_taxes) {
$row[] = $item->present()->tax1; $row[] = Utils::roundSignificant($item->getTaxAmount(), 2);
if ($account->enable_second_tax_rate) {
$row[] = $item->present()->tax2;
}
} }
if ($account->custom_invoice_item_label1) { if ($account->customLabel('product1')) {
$row[] = $item->custom_value1; $row[] = $item->custom_value1;
} }
if ($account->custom_invoice_item_label2) { if ($account->customLabel('product2')) {
$row[] = $item->custom_value2; $row[] = $item->custom_value2;
} }

View File

@ -27,11 +27,11 @@ class QuoteReport extends AbstractReport
$account = auth()->user()->account; $account = auth()->user()->account;
if ($account->custom_invoice_text_label1) { if ($account->customLabel('invoice_text1')) {
$columns[$account->present()->customInvoiceTextLabel1] = ['columnSelector-false', 'custom']; $columns[$account->present()->customLabel('invoice_text1')] = ['columnSelector-false', 'custom'];
} }
if ($account->custom_invoice_text_label1) { if ($account->customLabel('invoice_text1')) {
$columns[$account->present()->customInvoiceTextLabel2] = ['columnSelector-false', 'custom']; $columns[$account->present()->customLabel('invoice_text2')] = ['columnSelector-false', 'custom'];
} }
return $columns; return $columns;
@ -93,10 +93,10 @@ class QuoteReport extends AbstractReport
$row[] = $account->formatMoney($invoice->getTaxTotal(), $client); $row[] = $account->formatMoney($invoice->getTaxTotal(), $client);
} }
if ($account->custom_invoice_text_label1) { if ($account->customLabel('invoice_text1')) {
$row[] = $invoice->custom_text_value1; $row[] = $invoice->custom_text_value1;
} }
if ($account->custom_invoice_text_label2) { if ($account->customLabel('invoice_text2')) {
$row[] = $invoice->custom_text_value2; $row[] = $invoice->custom_text_value2;
} }

View File

@ -4,12 +4,13 @@ namespace App\Ninja\Reports;
use App\Models\Task; use App\Models\Task;
use Utils; use Utils;
use Auth;
class TaskReport extends AbstractReport class TaskReport extends AbstractReport
{ {
public function getColumns() public function getColumns()
{ {
return [ $columns = [
'client' => [], 'client' => [],
'start_date' => [], 'start_date' => [],
'project' => [], 'project' => [],
@ -18,10 +19,23 @@ class TaskReport extends AbstractReport
'amount' => [], 'amount' => [],
'user' => ['columnSelector-false'], 'user' => ['columnSelector-false'],
]; ];
$user = auth()->user();
$account = $user->account;
if ($account->customLabel('task1')) {
$columns[$account->present()->customLabel('task1')] = ['columnSelector-false', 'custom'];
}
if ($account->customLabel('task2')) {
$columns[$account->present()->customLabel('task2')] = ['columnSelector-false', 'custom'];
}
return $columns;
} }
public function run() public function run()
{ {
$account = Auth::user()->account;
$startDate = date_create($this->startDate); $startDate = date_create($this->startDate);
$endDate = date_create($this->endDate); $endDate = date_create($this->endDate);
$subgroup = $this->options['subgroup']; $subgroup = $this->options['subgroup'];
@ -41,7 +55,7 @@ class TaskReport extends AbstractReport
$currencyId = auth()->user()->account->getCurrencyId(); $currencyId = auth()->user()->account->getCurrencyId();
} }
$this->data[] = [ $row = [
$task->client ? ($this->isExport ? $task->client->getDisplayName() : $task->client->present()->link) : trans('texts.unassigned'), $task->client ? ($this->isExport ? $task->client->getDisplayName() : $task->client->present()->link) : trans('texts.unassigned'),
$this->isExport ? $task->getStartTime() : link_to($task->present()->url, $task->getStartTime()), $this->isExport ? $task->getStartTime() : link_to($task->present()->url, $task->getStartTime()),
$task->present()->project, $task->present()->project,
@ -51,6 +65,15 @@ class TaskReport extends AbstractReport
$task->user->getDisplayName(), $task->user->getDisplayName(),
]; ];
if ($account->customLabel('task1')) {
$row[] = $task->custom_value1;
}
if ($account->customLabel('task2')) {
$row[] = $task->custom_value2;
}
$this->data[] = $row;
$this->addToTotals($currencyId, 'duration', $duration); $this->addToTotals($currencyId, 'duration', $duration);
$this->addToTotals($currencyId, 'amount', $amount); $this->addToTotals($currencyId, 'amount', $amount);

View File

@ -169,11 +169,11 @@ class AccountRepository
]; ];
// include custom client fields in search // include custom client fields in search
if ($account->custom_client_label1) { if ($account->customLabel('client1')) {
$data[$account->present()->customClientLabel1] = []; $data[$account->present()->customLabel('client1')] = [];
} }
if ($account->custom_client_label2) { if ($account->customLabel('client2')) {
$data[$account->present()->customClientLabel2] = []; $data[$account->present()->customLabel('client2')] = [];
} }
if ($user->hasPermission('view_all')) { if ($user->hasPermission('view_all')) {
@ -194,35 +194,37 @@ class AccountRepository
} }
foreach ($clients as $client) { foreach ($clients as $client) {
if ($client->name) { if (! $client->is_deleted) {
$data['clients'][] = [ if ($client->name) {
'value' => ($client->id_number ? $client->id_number . ': ' : '') . $client->name, $data['clients'][] = [
'tokens' => implode(',', [$client->name, $client->id_number, $client->vat_number, $client->work_phone]), 'value' => ($client->id_number ? $client->id_number . ': ' : '') . $client->name,
'url' => $client->present()->url, 'tokens' => implode(',', [$client->name, $client->id_number, $client->vat_number, $client->work_phone]),
]; 'url' => $client->present()->url,
} ];
}
if ($client->custom_value1) { if ($client->custom_value1) {
$data[$account->present()->customClientLabel1][] = [ $data[$account->present()->customLabel('client1')][] = [
'value' => "{$client->custom_value1}: " . $client->getDisplayName(), 'value' => "{$client->custom_value1}: " . $client->getDisplayName(),
'tokens' => $client->custom_value1, 'tokens' => $client->custom_value1,
'url' => $client->present()->url, 'url' => $client->present()->url,
]; ];
} }
if ($client->custom_value2) { if ($client->custom_value2) {
$data[$account->present()->customClientLabel2][] = [ $data[$account->present()->customLabel('client2')][] = [
'value' => "{$client->custom_value2}: " . $client->getDisplayName(), 'value' => "{$client->custom_value2}: " . $client->getDisplayName(),
'tokens' => $client->custom_value2, 'tokens' => $client->custom_value2,
'url' => $client->present()->url, 'url' => $client->present()->url,
]; ];
} }
foreach ($client->contacts as $contact) { foreach ($client->contacts as $contact) {
$data['contacts'][] = [ $data['contacts'][] = [
'value' => $contact->getSearchName(), 'value' => $contact->getSearchName(),
'tokens' => implode(',', [$contact->first_name, $contact->last_name, $contact->email, $contact->phone]), 'tokens' => implode(',', [$contact->first_name, $contact->last_name, $contact->email, $contact->phone]),
'url' => $client->present()->url, 'url' => $client->present()->url,
]; ];
}
} }
foreach ($client->invoices as $invoice) { foreach ($client->invoices as $invoice) {

View File

@ -399,7 +399,7 @@ class InvoiceRepository extends BaseRepository
$invoice->custom_taxes2 = $account->custom_invoice_taxes2 ?: false; $invoice->custom_taxes2 = $account->custom_invoice_taxes2 ?: false;
// set the default due date // set the default due date
if (empty($data['partial_due_date'])) { if ($entityType == ENTITY_INVOICE && empty($data['partial_due_date'])) {
$client = Client::scope()->whereId($data['client_id'])->first(); $client = Client::scope()->whereId($data['client_id'])->first();
$invoice->due_date = $account->defaultDueDate($client); $invoice->due_date = $account->defaultDueDate($client);
} }
@ -1322,10 +1322,23 @@ class InvoiceRepository extends BaseRepository
$data = $invoice->toArray(); $data = $invoice->toArray();
$fee = $invoice->calcGatewayFee($gatewayTypeId); $fee = $invoice->calcGatewayFee($gatewayTypeId);
$date = $account->getDateTime()->format($account->getCustomDateFormat());
$feeItemLabel = $account->getLabel('gateway_fee_item') ?: ($fee >= 0 ? trans('texts.surcharge') : trans('texts.discount'));
if ($feeDescriptionLabel = $account->getLabel('gateway_fee_description')) {
if (strpos($feeDescriptionLabel, '$date') !== false) {
$feeDescriptionLabel = str_replace('$date', $date, $feeDescriptionLabel);
} else {
$feeDescriptionLabel .= ' • ' . $date;
}
} else {
$feeDescriptionLabel = $fee >= 0 ? trans('texts.online_payment_surcharge') : trans('texts.online_payment_discount');
$feeDescriptionLabel .= ' • ' . $date;
}
$item = []; $item = [];
$item['product_key'] = $fee >= 0 ? trans('texts.surcharge') : trans('texts.discount'); $item['product_key'] = $feeItemLabel;
$item['notes'] = $fee >= 0 ? trans('texts.online_payment_surcharge') : trans('texts.online_payment_discount'); $item['notes'] = $feeDescriptionLabel;
$item['qty'] = 1; $item['qty'] = 1;
$item['cost'] = $fee; $item['cost'] = $fee;
$item['tax_rate1'] = $settings->fee_tax_rate1; $item['tax_rate1'] = $settings->fee_tax_rate1;

View File

@ -159,6 +159,8 @@ class TaskRepository extends BaseRepository
return $task; return $task;
} }
$task->fill($data);
if (isset($data['client'])) { if (isset($data['client'])) {
$task->client_id = $data['client'] ? Client::getPrivateId($data['client']) : null; $task->client_id = $data['client'] ? Client::getPrivateId($data['client']) : null;
} elseif (isset($data['client_id'])) { } elseif (isset($data['client_id'])) {

View File

@ -179,18 +179,10 @@ class AccountTransformer extends EntityTransformer
'fill_products' => (bool) $account->fill_products, 'fill_products' => (bool) $account->fill_products,
'update_products' => (bool) $account->update_products, 'update_products' => (bool) $account->update_products,
'vat_number' => $account->vat_number, 'vat_number' => $account->vat_number,
'custom_invoice_label1' => $account->custom_invoice_label1,
'custom_invoice_label2' => $account->custom_invoice_label2,
'custom_invoice_taxes1' => $account->custom_invoice_taxes1,
'custom_invoice_taxes2' => $account->custom_invoice_taxes1,
'custom_label1' => $account->custom_label1,
'custom_label2' => $account->custom_label2,
'custom_value1' => $account->custom_value1, 'custom_value1' => $account->custom_value1,
'custom_value2' => $account->custom_value2, 'custom_value2' => $account->custom_value2,
'primary_color' => $account->primary_color, 'primary_color' => $account->primary_color,
'secondary_color' => $account->secondary_color, 'secondary_color' => $account->secondary_color,
'custom_client_label1' => $account->custom_client_label1,
'custom_client_label2' => $account->custom_client_label2,
'hide_quantity' => (bool) $account->hide_quantity, 'hide_quantity' => (bool) $account->hide_quantity,
'hide_paid_to_date' => (bool) $account->hide_paid_to_date, 'hide_paid_to_date' => (bool) $account->hide_paid_to_date,
'invoice_number_prefix' => $account->invoice_number_prefix, 'invoice_number_prefix' => $account->invoice_number_prefix,
@ -215,8 +207,6 @@ class AccountTransformer extends EntityTransformer
'num_days_reminder1' => $account->num_days_reminder1, 'num_days_reminder1' => $account->num_days_reminder1,
'num_days_reminder2' => $account->num_days_reminder2, 'num_days_reminder2' => $account->num_days_reminder2,
'num_days_reminder3' => $account->num_days_reminder3, 'num_days_reminder3' => $account->num_days_reminder3,
'custom_invoice_text_label1' => $account->custom_invoice_text_label1,
'custom_invoice_text_label2' => $account->custom_invoice_text_label2,
'tax_name1' => $account->tax_name1 ?: '', 'tax_name1' => $account->tax_name1 ?: '',
'tax_rate1' => (float) $account->tax_rate1, 'tax_rate1' => (float) $account->tax_rate1,
'tax_name2' => $account->tax_name2 ?: '', 'tax_name2' => $account->tax_name2 ?: '',
@ -245,8 +235,6 @@ class AccountTransformer extends EntityTransformer
'show_currency_code' => (bool) $account->show_currency_code, 'show_currency_code' => (bool) $account->show_currency_code,
'enable_portal_password' => (bool) $account->enable_portal_password, 'enable_portal_password' => (bool) $account->enable_portal_password,
'send_portal_password' => (bool) $account->send_portal_password, 'send_portal_password' => (bool) $account->send_portal_password,
'custom_invoice_item_label1' => $account->custom_invoice_item_label1,
'custom_invoice_item_label2' => $account->custom_invoice_item_label2,
'recurring_invoice_number_prefix' => $account->recurring_invoice_number_prefix, 'recurring_invoice_number_prefix' => $account->recurring_invoice_number_prefix,
'enable_client_portal' => (bool) $account->enable_client_portal, 'enable_client_portal' => (bool) $account->enable_client_portal,
'invoice_fields' => $account->invoice_fields, 'invoice_fields' => $account->invoice_fields,
@ -277,12 +265,26 @@ class AccountTransformer extends EntityTransformer
'gateway_fee_enabled' => (bool) $account->gateway_fee_enabled, 'gateway_fee_enabled' => (bool) $account->gateway_fee_enabled,
'send_item_details' => (bool) $account->send_item_details, 'send_item_details' => (bool) $account->send_item_details,
'reset_counter_date' => $account->reset_counter_date, 'reset_counter_date' => $account->reset_counter_date,
'custom_contact_label1' => $account->custom_contact_label1,
'custom_contact_label2' => $account->custom_contact_label2,
'task_rate' => (float) $account->task_rate, 'task_rate' => (float) $account->task_rate,
'inclusive_taxes' => (bool) $account->inclusive_taxes, 'inclusive_taxes' => (bool) $account->inclusive_taxes,
'convert_products' => (bool) $account->convert_products, 'convert_products' => (bool) $account->convert_products,
'signature_on_pdf' => (bool) $account->signature_on_pdf, 'signature_on_pdf' => (bool) $account->signature_on_pdf,
'custom_invoice_taxes1' => $account->custom_invoice_taxes1,
'custom_invoice_taxes2' => $account->custom_invoice_taxes1,
'custom_fields' => $account->custom_fields,
'custom_messages' => $account->custom_messages,
'custom_invoice_label1' => $account->customLabel('invoice1'),
'custom_invoice_label2' => $account->customLabel('invoice2'),
'custom_client_label1' => $account->customLabel('client1'),
'custom_client_label2' => $account->customLabel('client2'),
'custom_contact_label1' => $account->customLabel('contact1'),
'custom_contact_label2' => $account->customLabel('contact2'),
'custom_label1' => $account->customLabel('account1'),
'custom_label2' => $account->customLabel('account2'),
'custom_invoice_text_label1' => $account->customLabel('invoice_text1'),
'custom_invoice_text_label2' => $account->customLabel('invoice_text2'),
'custom_invoice_item_label1' => $account->customLabel('product1'),
'custom_invoice_item_label2' => $account->customLabel('product2'),
]; ];
} }
} }

View File

@ -155,6 +155,7 @@ class ClientTransformer extends EntityTransformer
'show_tasks_in_portal' => (bool) $client->show_tasks_in_portal, 'show_tasks_in_portal' => (bool) $client->show_tasks_in_portal,
'send_reminders' => (bool) $client->send_reminders, 'send_reminders' => (bool) $client->send_reminders,
'credit_number_counter' => (int) $client->credit_number_counter, 'credit_number_counter' => (int) $client->credit_number_counter,
'custom_messages' => json_encode($client->custom_messages),
]); ]);
} }
} }

View File

@ -84,6 +84,8 @@ class ExpenseTransformer extends EntityTransformer
'client_id' => $this->client ? $this->client->public_id : (isset($expense->client->public_id) ? (int) $expense->client->public_id : null), 'client_id' => $this->client ? $this->client->public_id : (isset($expense->client->public_id) ? (int) $expense->client->public_id : null),
'invoice_id' => isset($expense->invoice->public_id) ? (int) $expense->invoice->public_id : null, 'invoice_id' => isset($expense->invoice->public_id) ? (int) $expense->invoice->public_id : null,
'vendor_id' => isset($expense->vendor->public_id) ? (int) $expense->vendor->public_id : null, 'vendor_id' => isset($expense->vendor->public_id) ? (int) $expense->vendor->public_id : null,
'custom_value1' => $expense->custom_value1,
'custom_value2' => $expense->custom_value2,
]); ]);
} }
} }

View File

@ -34,6 +34,7 @@ class ProductTransformer extends EntityTransformer
'archived_at' => $this->getTimestamp($product->deleted_at), 'archived_at' => $this->getTimestamp($product->deleted_at),
'custom_value1' => $product->custom_value1, 'custom_value1' => $product->custom_value1,
'custom_value2' => $product->custom_value2, 'custom_value2' => $product->custom_value2,
'is_deleted' => (bool) $product->is_deleted,
]); ]);
} }
} }

View File

@ -34,6 +34,8 @@ class ProjectTransformer extends EntityTransformer
'due_date' => $project->due_date, 'due_date' => $project->due_date,
'private_notes' => $project->private_notes, 'private_notes' => $project->private_notes,
'budgeted_hours' => (float) $project->budgeted_hours, 'budgeted_hours' => (float) $project->budgeted_hours,
'custom_value1' => $project->custom_value1,
'custom_value2' => $project->custom_value2,
]); ]);
} }
} }

View File

@ -62,6 +62,8 @@ class TaskTransformer extends EntityTransformer
'is_deleted' => (bool) $task->is_deleted, 'is_deleted' => (bool) $task->is_deleted,
'time_log' => $task->time_log, 'time_log' => $task->time_log,
'is_running' => (bool) $task->is_running, 'is_running' => (bool) $task->is_running,
'custom_value1' => $task->custom_value1,
'custom_value2' => $task->custom_value2,
]); ]);
} }
} }

View File

@ -86,6 +86,8 @@ class VendorTransformer extends EntityTransformer
'vat_number' => $vendor->vat_number, 'vat_number' => $vendor->vat_number,
'id_number' => $vendor->id_number, 'id_number' => $vendor->id_number,
'currency_id' => (int) $vendor->currency_id, 'currency_id' => (int) $vendor->currency_id,
'custom_value1' => $vendor->custom_value1,
'custom_value2' => $vendor->custom_value2,
]); ]);
} }
} }

View File

@ -85,11 +85,11 @@ class AppServiceProvider extends ServiceProvider
->render(); ->render();
}); });
Form::macro('emailPaymentButton', function ($link = '#') { Form::macro('emailPaymentButton', function ($link = '#', $label = 'pay_now') {
return view('partials.email_button') return view('partials.email_button')
->with([ ->with([
'link' => $link, 'link' => $link,
'field' => 'pay_now', 'field' => $label,
'color' => '#36c157', 'color' => '#36c157',
]) ])
->render(); ->render();

View File

@ -35,6 +35,9 @@ use Session;
use stdClass; use stdClass;
use Utils; use Utils;
use Carbon; use Carbon;
use League\Csv\Reader;
use League\Csv\Statement;
/** /**
* Class ImportService. * Class ImportService.
@ -97,6 +100,7 @@ class ImportService
ENTITY_PAYMENT, ENTITY_PAYMENT,
ENTITY_TASK, ENTITY_TASK,
ENTITY_PRODUCT, ENTITY_PRODUCT,
ENTITY_VENDOR,
ENTITY_EXPENSE, ENTITY_EXPENSE,
ENTITY_CUSTOMER, ENTITY_CUSTOMER,
]; ];
@ -112,6 +116,7 @@ class ImportService
IMPORT_INVOICEABLE, IMPORT_INVOICEABLE,
IMPORT_INVOICEPLANE, IMPORT_INVOICEPLANE,
IMPORT_NUTCACHE, IMPORT_NUTCACHE,
IMPORT_PANCAKE,
IMPORT_RONIN, IMPORT_RONIN,
IMPORT_STRIPE, IMPORT_STRIPE,
IMPORT_WAVE, IMPORT_WAVE,
@ -651,8 +656,11 @@ class ImportService
private function getCsvData($fileName) private function getCsvData($fileName)
{ {
$this->checkForFile($fileName); $this->checkForFile($fileName);
$file = file_get_contents($fileName);
$data = array_map("str_getcsv", preg_split('/\r*\n+|\r+/', $file)); $csv = Reader::createFromPath($fileName, 'r');
//$csv->setHeaderOffset(0); //set the CSV header offset
$stmt = new Statement();
$data = iterator_to_array($stmt->process($csv));
if (count($data) > 0) { if (count($data) > 0) {
$headers = $data[0]; $headers = $data[0];

View File

@ -18,15 +18,17 @@ class TemplateService
*/ */
public function processVariables($template, array $data) public function processVariables($template, array $data)
{ {
/** @var \App\Models\Account $account */
$account = $data['account'];
/** @var \App\Models\Client $client */
$client = $data['client'];
/** @var \App\Models\Invitation $invitation */ /** @var \App\Models\Invitation $invitation */
$invitation = $data['invitation']; $invitation = $data['invitation'];
/** @var \App\Models\Account $account */
$account = ! empty($data['account']) ? $data['account'] : $invitation->account;
/** @var \App\Models\Client $client */
$client = ! empty($data['client']) ? $data['client'] : $invitation->invoice->client;
$amount = ! empty($data['amount']) ? $data['amount'] : $invitation->invoice->getRequestedAmount();
// check if it's a proposal // check if it's a proposal
if ($invitation->proposal) { if ($invitation->proposal) {
$invoice = $invitation->proposal->invoice; $invoice = $invitation->proposal->invoice;
@ -59,7 +61,7 @@ class TemplateService
'$invoiceDate' => $account->formatDate($invoice->invoice_date), '$invoiceDate' => $account->formatDate($invoice->invoice_date),
'$contact' => $contact->getDisplayName(), '$contact' => $contact->getDisplayName(),
'$firstName' => $contact->first_name, '$firstName' => $contact->first_name,
'$amount' => $account->formatMoney($data['amount'], $client), '$amount' => $account->formatMoney($amount, $client),
'$total' => $invoice->present()->amount, '$total' => $invoice->present()->amount,
'$balance' => $invoice->present()->balance, '$balance' => $invoice->present()->balance,
'$invoice' => $invoice->invoice_number, '$invoice' => $invoice->invoice_number,
@ -72,6 +74,8 @@ class TemplateService
'$viewButton' => Form::emailViewButton($invitation->getLink(), $entityType).'$password', '$viewButton' => Form::emailViewButton($invitation->getLink(), $entityType).'$password',
'$paymentLink' => $invitation->getLink('payment').'$password', '$paymentLink' => $invitation->getLink('payment').'$password',
'$paymentButton' => Form::emailPaymentButton($invitation->getLink('payment')).'$password', '$paymentButton' => Form::emailPaymentButton($invitation->getLink('payment')).'$password',
'$approveLink' => $invitation->getLink('approve').'$password',
'$approveButton' => Form::emailPaymentButton($invitation->getLink('approve'), 'approve').'$password',
'$customClient1' => $client->custom_value1, '$customClient1' => $client->custom_value1,
'$customClient2' => $client->custom_value2, '$customClient2' => $client->custom_value2,
'$customContact1' => $contact->custom_value1, '$customContact1' => $contact->custom_value1,

View File

@ -41,7 +41,7 @@
"jt.timepicker": "jquery-timepicker-jt#^1.11.12", "jt.timepicker": "jquery-timepicker-jt#^1.11.12",
"qrcode.js": "qrcode-js#*", "qrcode.js": "qrcode-js#*",
"money.js": "^0.1.3", "money.js": "^0.1.3",
"grapesjs": "^0.13.8" "grapesjs": "^0.13.8",
}, },
"resolutions": { "resolutions": {
"jquery": "~1.11" "jquery": "~1.11"

View File

@ -46,6 +46,7 @@
"laravel/socialite": "~3.0", "laravel/socialite": "~3.0",
"laravel/tinker": "^1.0", "laravel/tinker": "^1.0",
"laravelcollective/html": "5.4.*", "laravelcollective/html": "5.4.*",
"league/csv": "^9.1",
"league/flysystem-aws-s3-v3": "~1.0", "league/flysystem-aws-s3-v3": "~1.0",
"league/flysystem-rackspace": "~1.0", "league/flysystem-rackspace": "~1.0",
"league/fractal": "0.13.*", "league/fractal": "0.13.*",

528
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -271,6 +271,7 @@ return [
'Utils' => App\Libraries\Utils::class, 'Utils' => App\Libraries\Utils::class,
'DateUtils' => App\Libraries\DateUtils::class, 'DateUtils' => App\Libraries\DateUtils::class,
'HTMLUtils' => App\Libraries\HTMLUtils::class, 'HTMLUtils' => App\Libraries\HTMLUtils::class,
'CurlUtils' => App\Libraries\CurlUtils::class,
'Domain' => App\Constants\Domain::class, 'Domain' => App\Constants\Domain::class,
'Google2FA' => PragmaRX\Google2FALaravel\Facade::class, 'Google2FA' => PragmaRX\Google2FALaravel\Facade::class,

View File

@ -26,4 +26,14 @@ return [
// privacy policy // privacy policy
'privacy_policy_url' => env('PRIVACY_POLICY_URL', ''), 'privacy_policy_url' => env('PRIVACY_POLICY_URL', ''),
// Google maps
'google_maps_enabled' => env('GOOGLE_MAPS_ENABLED', true),
'google_maps_api_key' => env('GOOGLE_MAPS_API_KEY', ''),
// Voice commands
'voice_commands' => [
'app_id' => env('MSBOT_LUIS_APP_ID', 'ea1cda29-5994-47c4-8c25-2b58ae7ae7a8'),
'subscription_key' => env('MSBOT_LUIS_SUBSCRIPTION_KEY'),
],
]; ];

View File

@ -0,0 +1,153 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use App\Models\Account;
class AddMoreCustomFields extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function ($table) {
$table->mediumText('custom_fields')->nullable();
});
$accounts = Account::where('custom_label1', '!=', '')
->orWhere('custom_label2', '!=', '')
->orWhere('custom_client_label1', '!=', '')
->orWhere('custom_client_label2', '!=', '')
->orWhere('custom_contact_label1', '!=', '')
->orWhere('custom_contact_label2', '!=', '')
->orWhere('custom_invoice_label1', '!=', '')
->orWhere('custom_invoice_label2', '!=', '')
->orWhere('custom_invoice_text_label1', '!=', '')
->orWhere('custom_invoice_text_label2', '!=', '')
->orWhere('custom_invoice_item_label1', '!=', '')
->orWhere('custom_invoice_item_label2', '!=', '')
->orderBy('id')
->get();
$fields = [
'account1' => 'custom_label1',
'account2' => 'custom_label2',
'client1' => 'custom_client_label1',
'client2' => 'custom_client_label2',
'contact1' => 'custom_contact_label1',
'contact2' => 'custom_contact_label2',
'invoice1' => 'custom_invoice_label1',
'invoice2' => 'custom_invoice_label2',
'invoice_text1' => 'custom_invoice_text_label1',
'invoice_text2' => 'custom_invoice_text_label2',
'product1' => 'custom_invoice_item_label1',
'product2' => 'custom_invoice_item_label2',
];
foreach ($accounts as $account) {
$config = [];
foreach ($fields as $key => $field) {
if ($account->$field) {
$config[$key] = $account->$field;
}
}
if (count($config)) {
$account->custom_fields = $config;
$account->save();
}
}
Schema::table('accounts', function ($table) {
$table->dropColumn('custom_label1');
$table->dropColumn('custom_label2');
$table->dropColumn('custom_client_label1');
$table->dropColumn('custom_client_label2');
$table->dropColumn('custom_contact_label1');
$table->dropColumn('custom_contact_label2');
$table->dropColumn('custom_invoice_label1');
$table->dropColumn('custom_invoice_label2');
$table->dropColumn('custom_invoice_text_label1');
$table->dropColumn('custom_invoice_text_label2');
$table->dropColumn('custom_invoice_item_label1');
$table->dropColumn('custom_invoice_item_label2');
});
Schema::table('accounts', function ($table) {
$table->unsignedInteger('background_image_id')->nullable();
$table->mediumText('custom_messages')->nullable();
});
Schema::table('clients', function ($table) {
$table->mediumText('custom_messages')->nullable();
});
Schema::table('tasks', function ($table) {
$table->text('custom_value1')->nullable();
$table->text('custom_value2')->nullable();
});
Schema::table('projects', function ($table) {
$table->text('custom_value1')->nullable();
$table->text('custom_value2')->nullable();
});
Schema::table('expenses', function ($table) {
$table->text('custom_value1')->nullable();
$table->text('custom_value2')->nullable();
});
Schema::table('vendors', function ($table) {
$table->text('custom_value1')->nullable();
$table->text('custom_value2')->nullable();
});
Schema::table('products', function ($table) {
$table->text('custom_value1')->nullable()->change();
$table->text('custom_value2')->nullable()->change();
});
Schema::table('clients', function ($table) {
$table->text('custom_value1')->nullable()->change();
$table->text('custom_value2')->nullable()->change();
});
Schema::table('contacts', function ($table) {
$table->text('custom_value1')->nullable()->change();
$table->text('custom_value2')->nullable()->change();
});
Schema::table('invoices', function ($table) {
$table->text('custom_text_value1')->nullable()->change();
$table->text('custom_text_value2')->nullable()->change();
});
Schema::table('invoice_items', function ($table) {
$table->text('custom_value1')->nullable()->change();
$table->text('custom_value2')->nullable()->change();
});
Schema::table('scheduled_reports', function ($table) {
$table->string('ip')->nullable();
});
DB::statement('UPDATE gateways SET provider = "Custom1" WHERE id = 62');
DB::statement('UPDATE gateway_types SET alias = "custom1", name = "Custom 1" WHERE id = 6');
DB::statement('ALTER TABLE recurring_expenses MODIFY COLUMN last_sent_date DATE');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -84,6 +84,7 @@ class CurrenciesSeeder extends Seeder
['name' => 'Brunei Dollar', 'code' => 'BND', 'symbol' => 'B$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Brunei Dollar', 'code' => 'BND', 'symbol' => 'B$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Georgian Lari', 'code' => 'GEL', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ' ', 'decimal_separator' => ','], ['name' => 'Georgian Lari', 'code' => 'GEL', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ' ', 'decimal_separator' => ','],
['name' => 'Qatari Riyal', 'code' => 'QAR', 'symbol' => 'QR', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Qatari Riyal', 'code' => 'QAR', 'symbol' => 'QR', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Honduran Lempira', 'code' => 'HNL', 'symbol' => 'L', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
]; ];
foreach ($currencies as $currency) { foreach ($currencies as $currency) {

View File

@ -14,16 +14,18 @@ class GatewayTypesSeeder extends Seeder
['alias' => 'paypal', 'name' => 'PayPal'], ['alias' => 'paypal', 'name' => 'PayPal'],
['alias' => 'bitcoin', 'name' => 'Bitcoin'], ['alias' => 'bitcoin', 'name' => 'Bitcoin'],
['alias' => 'dwolla', 'name' => 'Dwolla'], ['alias' => 'dwolla', 'name' => 'Dwolla'],
['alias' => 'custom', 'name' => 'Custom'], ['alias' => 'custom1', 'name' => 'Custom'],
['alias' => 'alipay', 'name' => 'Alipay'], ['alias' => 'alipay', 'name' => 'Alipay'],
['alias' => 'sofort', 'name' => 'Sofort'], ['alias' => 'sofort', 'name' => 'Sofort'],
['alias' => 'sepa', 'name' => 'SEPA'], ['alias' => 'sepa', 'name' => 'SEPA'],
['alias' => 'gocardless', 'name' => 'GoCardless'], ['alias' => 'gocardless', 'name' => 'GoCardless'],
['alias' => 'apple_pay', 'name' => 'Apple Pay'], ['alias' => 'apple_pay', 'name' => 'Apple Pay'],
['alias' => 'custom2', 'name' => 'Custom'],
['alias' => 'custom3', 'name' => 'Custom'],
]; ];
foreach ($gateway_types as $gateway_type) { foreach ($gateway_types as $gateway_type) {
$record = GatewayType::where('name', '=', $gateway_type['name'])->first(); $record = GatewayType::where('alias', '=', $gateway_type['alias'])->first();
if (! $record) { if (! $record) {
GatewayType::create($gateway_type); GatewayType::create($gateway_type);
} }

View File

@ -29,7 +29,7 @@ class PaymentLibrariesSeeder extends Seeder
['name' => 'PayPal Pro', 'provider' => 'PayPal_Pro'], ['name' => 'PayPal Pro', 'provider' => 'PayPal_Pro'],
['name' => 'Pin', 'provider' => 'Pin'], ['name' => 'Pin', 'provider' => 'Pin'],
['name' => 'SagePay Direct', 'provider' => 'SagePay_Direct'], ['name' => 'SagePay Direct', 'provider' => 'SagePay_Direct'],
['name' => 'SagePay Server', 'provider' => 'SagePay_Server', 'is_offsite' => true], ['name' => 'SagePay Server', 'provider' => 'SagePay_Server', 'is_offsite' => true, 'payment_library_id' => 2],
['name' => 'SecurePay DirectPost', 'provider' => 'SecurePay_DirectPost'], ['name' => 'SecurePay DirectPost', 'provider' => 'SecurePay_DirectPost'],
['name' => 'Stripe', 'provider' => 'Stripe', 'sort_order' => 1], ['name' => 'Stripe', 'provider' => 'Stripe', 'sort_order' => 1],
['name' => 'TargetPay Direct eBanking', 'provider' => 'TargetPay_Directebanking'], ['name' => 'TargetPay Direct eBanking', 'provider' => 'TargetPay_Directebanking'],
@ -42,7 +42,7 @@ class PaymentLibrariesSeeder extends Seeder
['name' => 'moolah', 'provider' => 'AuthorizeNet_AIM'], ['name' => 'moolah', 'provider' => 'AuthorizeNet_AIM'],
['name' => 'Alipay', 'provider' => 'Alipay_Express'], ['name' => 'Alipay', 'provider' => 'Alipay_Express'],
['name' => 'Buckaroo', 'provider' => 'Buckaroo_CreditCard'], ['name' => 'Buckaroo', 'provider' => 'Buckaroo_CreditCard'],
['name' => 'Coinbase', 'provider' => 'Coinbase'], ['name' => 'Coinbase', 'provider' => 'Coinbase', 'is_offsite' => true],
['name' => 'DataCash', 'provider' => 'DataCash'], ['name' => 'DataCash', 'provider' => 'DataCash'],
['name' => 'Neteller', 'provider' => 'Neteller', 'payment_library_id' => 2], ['name' => 'Neteller', 'provider' => 'Neteller', 'payment_library_id' => 2],
['name' => 'Pacnet', 'provider' => 'Pacnet'], ['name' => 'Pacnet', 'provider' => 'Pacnet'],
@ -70,11 +70,13 @@ class PaymentLibrariesSeeder extends Seeder
['name' => 'WeChat Express', 'provider' => 'WeChat_Express', 'payment_library_id' => 2], ['name' => 'WeChat Express', 'provider' => 'WeChat_Express', 'payment_library_id' => 2],
['name' => 'WePay', 'provider' => 'WePay', 'is_offsite' => false, 'sort_order' => 3], ['name' => 'WePay', 'provider' => 'WePay', 'is_offsite' => false, 'sort_order' => 3],
['name' => 'Braintree', 'provider' => 'Braintree', 'sort_order' => 3], ['name' => 'Braintree', 'provider' => 'Braintree', 'sort_order' => 3],
['name' => 'Custom', 'provider' => 'Custom', 'is_offsite' => true, 'sort_order' => 20], ['name' => 'Custom', 'provider' => 'Custom1', 'is_offsite' => true, 'sort_order' => 20],
['name' => 'FirstData Payeezy', 'provider' => 'FirstData_Payeezy'], ['name' => 'FirstData Payeezy', 'provider' => 'FirstData_Payeezy'],
['name' => 'GoCardless', 'provider' => 'GoCardlessV2\Redirect', 'sort_order' => 9, 'is_offsite' => true], ['name' => 'GoCardless', 'provider' => 'GoCardlessV2\Redirect', 'sort_order' => 9, 'is_offsite' => true],
['name' => 'PagSeguro', 'provider' => 'PagSeguro'], ['name' => 'PagSeguro', 'provider' => 'PagSeguro'],
['name' => 'PAYMILL', 'provider' => 'Paymill'], ['name' => 'PAYMILL', 'provider' => 'Paymill'],
['name' => 'Custom', 'provider' => 'Custom2', 'is_offsite' => true, 'sort_order' => 21],
['name' => 'Custom', 'provider' => 'Custom3', 'is_offsite' => true, 'sort_order' => 22],
]; ];
foreach ($gateways as $gateway) { foreach ($gateways as $gateway) {

File diff suppressed because one or more lines are too long

View File

@ -57,9 +57,9 @@ author = u'Invoice Ninja'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = u'4.3' version = u'4.4'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = u'4.3.1' release = u'4.4.0'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

View File

@ -117,14 +117,11 @@ You can disable the feature by adding ``GOOGLE_MAPS_ENABLED=false`` to the .env
Voice Commands Voice Commands
"""""""""""""" """"""""""""""
Supporting voice commands requires creating a `LUIS.ai <https://www.luis.ai/home/index>`_ app, once the app is created you can import this `model file <https://download.invoiceninja.com/luis.json>`_. Supporting voice commands requires creating a `LUIS.ai subscription key <https://docs.microsoft.com/en-us/azure/cognitive-services/luis/azureibizasubscription>`_, then set the following values in the .env file.
You'll also need to set the following values in the .env file.
.. code-block:: shell .. code-block:: shell
SPEECH_ENABLED=true SPEECH_ENABLED=true
MSBOT_LUIS_APP_ID=...
MSBOT_LUIS_SUBSCRIPTION_KEY=... MSBOT_LUIS_SUBSCRIPTION_KEY=...
Lock Invoices Lock Invoices

View File

@ -20,11 +20,7 @@ For example:
php artisan module:install invoiceninja/sprockets --type=github php artisan module:install invoiceninja/sprockets --type=github
You can check the current module status with: .. TIP:: One a module is installed it can enabled/disabled on Settings > Account Management
.. code-block:: php
php artisan module:list
Create Module Create Module
@ -36,17 +32,13 @@ Run the following command to create a CRUD module:
php artisan ninja:make-module <module> <fields> php artisan ninja:make-module <module> <fields>
For example:
.. code-block:: php .. code-block:: php
php artisan ninja:make-module Inventory 'name:string,description:text' php artisan ninja:make-module Inventory 'name:string,description:text'
To edit the migration before it's run add ``--migrate=false`` To run the database migration use:
.. code-block:: php
php artisan ninja:make-module <module> <fields> --migrate=false
After making adjustments to the migration file you can run:
.. code-block:: php .. code-block:: php
@ -55,10 +47,16 @@ After making adjustments to the migration file you can run:
.. Tip:: You can specify the module icon by setting a value from http://fontawesome.io/icons/ for "icon" in modules.json. .. Tip:: You can specify the module icon by setting a value from http://fontawesome.io/icons/ for "icon" in modules.json.
There are two types of modules: you can either create a standard module which displays a list of a new entity type or you can create a blank module which adds functionality. For example, a custom integration with a third-party app.
If you're looking for a module to work on you can see suggested issues `listed here <https://github.com/invoiceninja/invoiceninja/issues?q=is%3Aissue+is%3Aopen+label%3A%22custom+module%22>`_.
.. NOTE:: Our module implemention is currenty being actively worked on, you can join the discussion on our Slack group: http://slack.invoiceninja.com/
Share Module Share Module
"""""""""""" """"""""""""
To share your module create a new project on GitHub and then commit the code: To share your module create a new project on GitHub and then run the following code:
.. code-block:: php .. code-block:: php

View File

@ -133,15 +133,6 @@ Directly to the left of the Balance Due section, you'll see a text box with a nu
.. TIP:: The Invoices page is rich in clickable links, providing you with a shortcut to relevant pages you may wish to view. For example, all invoice numbers are clickable, taking you directly to the specific invoice page, and all client names are clickable, taking you directly to the specific client summary page. .. TIP:: The Invoices page is rich in clickable links, providing you with a shortcut to relevant pages you may wish to view. For example, all invoice numbers are clickable, taking you directly to the specific invoice page, and all client names are clickable, taking you directly to the specific client summary page.
Invoice Preview
^^^^^^^^^^^^^^^
Did you know that all this time you've been creating the new invoice, a live PDF preview of the invoice appears below, and it changes in real time according to the data you've entered?
Scroll down below the invoice data fields to check out the invoice preview.
But before we get there you'll see a row of colorful buttons, giving you a range of options:
- **Blue button Download PDF**: Download the invoice as a PDF file. You can then print or save to your PC or mobile device. - **Blue button Download PDF**: Download the invoice as a PDF file. You can then print or save to your PC or mobile device.
- **Gray button Save Draft**: Save the latest version of the invoice. The data is saved in your Invoice Ninja account. You can return to the invoice at any time to continue working on it. Note: An invoice in the Draft stage is not viewable to the client in the client portal, and the amount on the invoice is not reflected in the client's invoicing balance. - **Gray button Save Draft**: Save the latest version of the invoice. The data is saved in your Invoice Ninja account. You can return to the invoice at any time to continue working on it. Note: An invoice in the Draft stage is not viewable to the client in the client portal, and the amount on the invoice is not reflected in the client's invoicing balance.
- **Green button Mark Sent**: If you mark the invoice as sent, then the invoice will be viewable to your client in the client portal. The amount on the invoice will also be calculated in the client's balance data. - **Green button Mark Sent**: If you mark the invoice as sent, then the invoice will be viewable to your client in the client portal. The amount on the invoice will also be calculated in the client's balance data.

View File

@ -127,9 +127,3 @@ Click on More Actions to open the following action list:
- **Delete Quote**: Want to delete the quote? Click here. The quote will be deleted and removed from the Quotes list page. - **Delete Quote**: Want to delete the quote? Click here. The quote will be deleted and removed from the Quotes list page.
.. TIP:: At the left of these colorful buttons, you'll see a field with an arrow that opens a drop-down menu. This field provides you with template options for the quote design. Click on the arrow to select the desired template. When selected, the quote preview will change to reflect the new template. .. TIP:: At the left of these colorful buttons, you'll see a field with an arrow that opens a drop-down menu. This field provides you with template options for the quote design. Click on the arrow to select the desired template. When selected, the quote preview will change to reflect the new template.
Quote Preview
^^^^^^^^^^^^^
Did you know that all this time you've been creating the new quote, a preview of the quote appears below, and it changes in real time according to the data you've entered? The PDF is created in real time; all you have to do is click Save.
To check out the quote preview, scroll down below the invoice data fields.

View File

@ -28,8 +28,13 @@ A common error with shared hosting is "open_basedir restriction in effect", if y
.. TIP:: You can see the detailed changes for each release on our `GitHub release notes <https://github.com/invoiceninja/invoiceninja/releases>`_. .. TIP:: You can see the detailed changes for each release on our `GitHub release notes <https://github.com/invoiceninja/invoiceninja/releases>`_.
Version 4.3
"""""""""""
You may need to manually delete ``bootstrap/cache/compiled.php``.
Version 4.0 Version 4.0
""""""""""""" """""""""""
The minimum PHP version is now 7.0.0 The minimum PHP version is now 7.0.0

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1417,3 +1417,7 @@ div.panel-body div.panel-body {
padding-right:0; padding-right:0;
padding-left:0; padding-left:0;
} }
.input-group-addon .glyphicon-question-sign {
color: #117cc1;
}

View File

@ -189,6 +189,17 @@ function GetPdfMake(invoice, javascript, callback) {
if(!dd.defaultStyle)dd.defaultStyle = {font:NINJA.bodyFont}; if(!dd.defaultStyle)dd.defaultStyle = {font:NINJA.bodyFont};
else if(!dd.defaultStyle.font)dd.defaultStyle.font = NINJA.bodyFont; else if(!dd.defaultStyle.font)dd.defaultStyle.font = NINJA.bodyFont;
if (window.accountBackground) {
var origBackground = dd.background;
dd.background = function(currentPage) {
var allPages = origBackground.length && origBackground[0].pages == 'all';
return currentPage == 1 || allPages ? origBackground : false;
}
} else {
// prevent unnecessarily showing blank image
dd.background = false;
}
doc = pdfMake.createPdf(dd); doc = pdfMake.createPdf(dd);
doc.save = function(fileName) { doc.save = function(fileName) {
this.download(fileName); this.download(fileName);
@ -228,6 +239,7 @@ NINJA.decodeJavascript = function(invoice, javascript)
var json = { var json = {
'accountName': account.name || ' ', 'accountName': account.name || ' ',
'accountLogo': window.accountLogo ? window.accountLogo : blankImage, 'accountLogo': window.accountLogo ? window.accountLogo : blankImage,
'accountBackground': window.accountBackground ? window.accountBackground : blankImage,
'accountDetails': NINJA.accountDetails(invoice), 'accountDetails': NINJA.accountDetails(invoice),
'accountAddress': NINJA.accountAddress(invoice), 'accountAddress': NINJA.accountAddress(invoice),
'invoiceDetails': NINJA.invoiceDetails(invoice), 'invoiceDetails': NINJA.invoiceDetails(invoice),
@ -670,7 +682,7 @@ NINJA.invoiceLines = function(invoice, isSecondTable) {
'product.discount', 'product.discount',
]; ];
if (isSecondTable) { if (isSecondTable && invoice.hasStandard) {
styles.push('secondTableHeader'); styles.push('secondTableHeader');
} }
@ -689,13 +701,13 @@ NINJA.invoiceLines = function(invoice, isSecondTable) {
if (field == 'custom_value1') { if (field == 'custom_value1') {
if (invoice.has_custom_item_value1) { if (invoice.has_custom_item_value1) {
value = NINJA.getCustomLabel(account.custom_invoice_item_label1); value = NINJA.getCustomLabel(account.custom_fields.product1);
} else { } else {
continue; continue;
} }
} else if (field == 'custom_value2') { } else if (field == 'custom_value2') {
if (invoice.has_custom_item_value2) { if (invoice.has_custom_item_value2) {
value = NINJA.getCustomLabel(account.custom_invoice_item_label2); value = NINJA.getCustomLabel(account.custom_fields.product2);
} else { } else {
continue; continue;
} }
@ -935,10 +947,10 @@ NINJA.subtotals = function(invoice, hideBalance)
} }
var customValue1 = NINJA.parseFloat(invoice.custom_value1); var customValue1 = NINJA.parseFloat(invoice.custom_value1);
var customValue1Label = account.custom_invoice_label1 || invoiceLabels.surcharge; var customValue1Label = account.custom_fields.invoice1 || invoiceLabels.surcharge;
var customValue2 = NINJA.parseFloat(invoice.custom_value2); var customValue2 = NINJA.parseFloat(invoice.custom_value2);
var customValue2Label = account.custom_invoice_label2 || invoiceLabels.surcharge; var customValue2Label = account.custom_fields.invoice2 || invoiceLabels.surcharge;
if (customValue1 && invoice.custom_taxes1 == '1') { if (customValue1 && invoice.custom_taxes1 == '1') {
data.push([{text: customValue1Label, style: ['subtotalsLabel', 'customTax1Label']}, {text: formatMoneyInvoice(invoice.custom_value1, invoice), style: ['subtotals', 'customTax1']}]); data.push([{text: customValue1Label, style: ['subtotalsLabel', 'customTax1Label']}, {text: formatMoneyInvoice(invoice.custom_value1, invoice), style: ['subtotals', 'customTax1']}]);
@ -1113,7 +1125,7 @@ NINJA.renderField = function(invoice, field, twoColumn) {
return false; return false;
} }
var account = invoice.account; var account = invoice.account;
var contact = client.contacts[0]; var contact = invoice.contact || client.contacts[0];
var clientName = client.name || (contact.first_name || contact.last_name ? ((contact.first_name || '') + ' ' + (contact.last_name || '')) : contact.email); var clientName = client.name || (contact.first_name || contact.last_name ? ((contact.first_name || '') + ' ' + (contact.last_name || '')) : contact.email);
var label = false; var label = false;
@ -1184,23 +1196,23 @@ NINJA.renderField = function(invoice, field, twoColumn) {
} else if (field == 'client.phone') { } else if (field == 'client.phone') {
value = contact.phone; value = contact.phone;
} else if (field == 'client.custom_value1') { } else if (field == 'client.custom_value1') {
if (account.custom_client_label1 && client.custom_value1) { if (account.custom_fields.client1 && client.custom_value1) {
label = NINJA.getCustomLabel(account.custom_client_label1); label = NINJA.getCustomLabel(account.custom_fields.client1);
value = client.custom_value1; value = client.custom_value1;
} }
} else if (field == 'client.custom_value2') { } else if (field == 'client.custom_value2') {
if (account.custom_client_label2 && client.custom_value2) { if (account.custom_fields.client2 && client.custom_value2) {
label = NINJA.getCustomLabel(account.custom_client_label2); label = NINJA.getCustomLabel(account.custom_fields.client2);
value = client.custom_value2; value = client.custom_value2;
} }
} else if (field == 'contact.custom_value1') { } else if (field == 'contact.custom_value1') {
if (account.custom_contact_label1 && contact.custom_value1) { if (account.custom_fields.contact1 && contact.custom_value1) {
label = NINJA.getCustomLabel(account.custom_contact_label1); label = NINJA.getCustomLabel(account.custom_fields.contact1);
value = contact.custom_value1; value = contact.custom_value1;
} }
} else if (field == 'contact.custom_value2') { } else if (field == 'contact.custom_value2') {
if (account.custom_contact_label2 && contact.custom_value2) { if (account.custom_fields.contact2 && contact.custom_value2) {
label = NINJA.getCustomLabel(account.custom_contact_label2); label = NINJA.getCustomLabel(account.custom_fields.contact2);
value = contact.custom_value2; value = contact.custom_value2;
} }
} else if (field == 'account.company_name') { } else if (field == 'account.company_name') {
@ -1241,13 +1253,13 @@ NINJA.renderField = function(invoice, field, twoColumn) {
} else if (field == 'account.country') { } else if (field == 'account.country') {
value = account.country ? account.country.name : false; value = account.country ? account.country.name : false;
} else if (field == 'account.custom_value1') { } else if (field == 'account.custom_value1') {
if (invoice.account.custom_label1 && invoice.account.custom_value1) { if (invoice.account.custom_fields.account1 && invoice.account.custom_value1) {
label = invoice.account.custom_label1; label = invoice.account.custom_fields.account1;
value = invoice.account.custom_value1; value = invoice.account.custom_value1;
} }
} else if (field == 'account.custom_value2') { } else if (field == 'account.custom_value2') {
if (invoice.account.custom_label2 && invoice.account.custom_value2) { if (invoice.account.custom_fields.account2 && invoice.account.custom_value2) {
label = invoice.account.custom_label2; label = invoice.account.custom_fields.account2;
value = invoice.account.custom_value2; value = invoice.account.custom_value2;
} }
} else if (field == 'invoice.invoice_number') { } else if (field == 'invoice.invoice_number') {
@ -1268,13 +1280,13 @@ NINJA.renderField = function(invoice, field, twoColumn) {
value = invoice.due_date; value = invoice.due_date;
} }
} else if (field == 'invoice.custom_text_value1') { } else if (field == 'invoice.custom_text_value1') {
if (invoice.custom_text_value1 && account.custom_invoice_text_label1) { if (invoice.custom_text_value1 && account.custom_fields.invoice_text1) {
label = NINJA.getCustomLabel(invoice.account.custom_invoice_text_label1); label = NINJA.getCustomLabel(invoice.account.custom_fields.invoice_text1);
value = invoice.is_recurring ? processVariables(invoice.custom_text_value1) : invoice.custom_text_value1; value = invoice.is_recurring ? processVariables(invoice.custom_text_value1) : invoice.custom_text_value1;
} }
} else if (field == 'invoice.custom_text_value2') { } else if (field == 'invoice.custom_text_value2') {
if (invoice.custom_text_value2 && account.custom_invoice_text_label2) { if (invoice.custom_text_value2 && account.custom_fields.invoice_text2) {
label = NINJA.getCustomLabel(invoice.account.custom_invoice_text_label2); label = NINJA.getCustomLabel(invoice.account.custom_fields.invoice_text2);
value = invoice.is_recurring ? processVariables(invoice.custom_text_value2) : invoice.custom_text_value2; value = invoice.is_recurring ? processVariables(invoice.custom_text_value2) : invoice.custom_text_value2;
} }
} else if (field == 'invoice.balance_due') { } else if (field == 'invoice.balance_due') {

View File

@ -1265,3 +1265,33 @@ function openUrlOnClick(url, event) {
window.location = url; window.location = url;
} }
} }
// https://stackoverflow.com/a/11268104/497368
function scorePassword(pass) {
var score = 0;
if (!pass)
return score;
// award every unique letter until 5 repetitions
var letters = new Object();
for (var i=0; i<pass.length; i++) {
letters[pass[i]] = (letters[pass[i]] || 0) + 1;
score += 5.0 / letters[pass[i]];
}
// bonus points for mixing it up
var variations = {
digits: /\d/.test(pass),
lower: /[a-z]/.test(pass),
upper: /[A-Z]/.test(pass),
nonWords: /\W/.test(pass),
}
variationCount = 0;
for (var check in variations) {
variationCount += (variations[check] == true) ? 1 : 0;
}
score += (variationCount - 1) * 10;
return parseInt(score);
}

View File

@ -2407,6 +2407,8 @@ $LANG = array(
'currency_barbadian_dollar' => 'Barbadian Dollar', 'currency_barbadian_dollar' => 'Barbadian Dollar',
'currency_brunei_dollar' => 'Brunei Dollar', 'currency_brunei_dollar' => 'Brunei Dollar',
'currency_georgian_lari' => 'Georgian Lari', 'currency_georgian_lari' => 'Georgian Lari',
'currency_qatari_riyal' => 'Qatari Riyal',
'currency_honduran_lempira' => 'Honduran Lempira',
'review_app_help' => 'We hope you\'re enjoying using the app.<br/>If you\'d consider :link we\'d greatly appreciate it!', 'review_app_help' => 'We hope you\'re enjoying using the app.<br/>If you\'d consider :link we\'d greatly appreciate it!',
'writing_a_review' => 'writing a review', 'writing_a_review' => 'writing a review',
@ -2423,8 +2425,8 @@ $LANG = array(
'contact_custom1' => 'Contact First Custom', 'contact_custom1' => 'Contact First Custom',
'contact_custom2' => 'Contact Second Custom', 'contact_custom2' => 'Contact Second Custom',
'currency' => 'Currency', 'currency' => 'Currency',
'ofx_help' => 'In most cases the default values should work, if you\'re unable to connect it may help to :link.', 'ofx_help' => 'To troubleshoot check for comments on :ofxhome_link and test with :ofxget_link.',
'adjust_the_settings' => 'adjust the settings', 'comments' => 'comments',
'item_product' => 'Item Product', 'item_product' => 'Item Product',
'item_notes' => 'Item Notes', 'item_notes' => 'Item Notes',
@ -2652,7 +2654,7 @@ $LANG = array(
'signature_on_pdf_help' => 'Show the client signature on the invoice/quote PDF.', 'signature_on_pdf_help' => 'Show the client signature on the invoice/quote PDF.',
'expired_white_label' => 'The white label license has expired', 'expired_white_label' => 'The white label license has expired',
'return_to_login' => 'Return to Login', 'return_to_login' => 'Return to Login',
'convert_products_tip' => 'Note: add a custom field named ":name" to see the exchange rate.', 'convert_products_tip' => 'Note: add a :link named ":name" to see the exchange rate.',
'amount_greater_than_balance' => 'The amount is greater than the invoice balance, a credit will be created with the remaining amount.', 'amount_greater_than_balance' => 'The amount is greater than the invoice balance, a credit will be created with the remaining amount.',
'custom_fields_tip' => 'Use <code>Label|Option1,Option2</code> to show a select box.', 'custom_fields_tip' => 'Use <code>Label|Option1,Option2</code> to show a select box.',
'client_information' => 'Client Information', 'client_information' => 'Client Information',
@ -2793,9 +2795,50 @@ $LANG = array(
'purge_client_warning' => 'All related records (invoices, tasks, expenses, documents, etc) will also be deleted.', 'purge_client_warning' => 'All related records (invoices, tasks, expenses, documents, etc) will also be deleted.',
'clone_product' => 'Clone Product', 'clone_product' => 'Clone Product',
'item_details' => 'Item Details', 'item_details' => 'Item Details',
'send_item_details_help' => 'Send the line item details to the payment gateway.', 'send_item_details_help' => 'Send line item details to the payment gateway.',
'view_proposal' => 'View Proposal', 'view_proposal' => 'View Proposal',
'view_in_portal' => 'View in Portal', 'view_in_portal' => 'View in Portal',
'cookie_message' => 'This website uses cookies to ensure you get the best experience on our website.',
'got_it' => 'Got it!',
'vendor_will_create' => 'vendor will be created',
'vendors_will_create' => 'vendors will be created',
'created_vendors' => 'Successfully created :count vendor(s)',
'import_vendors' => 'Import Vendors',
'company' => 'Company',
'client_field' => 'Client Field',
'contact_field' => 'Contact Field',
'product_field' => 'Product Field',
'task_field' => 'Task Field',
'project_field' => 'Project Field',
'expense_field' => 'Expense Field',
'vendor_field' => 'Vendor Field',
'company_field' => 'Company Field',
'invoice_field' => 'Invoice Field',
'invoice_surcharge' => 'Invoice Surcharge',
'custom_task_fields_help' => 'Add a field when creating a task.',
'custom_project_fields_help' => 'Add a field when creating a project.',
'custom_expense_fields_help' => 'Add a field when creating an expense.',
'custom_vendor_fields_help' => 'Add a field when creating a vendor.',
'messages' => 'Messages',
'unpaid_invoice' => 'Unpaid Invoice',
'paid_invoice' => 'Paid Invoice',
'unapproved_quote' => 'Unapproved Quote',
'unapproved_proposal' => 'Unapproved Proposal',
'autofills_city_state' => 'Auto-fills city/state',
'no_match_found' => 'No match found',
'password_strength' => 'Password Strength',
'strength_weak' => 'Weak',
'strength_good' => 'Good',
'strength_strong' => 'Strong',
'mark' => 'Mark',
'updated_task_status' => 'Successfully update task status',
'background_image' => 'Background Image',
'background_image_help' => 'Use the :link to manage your images, we recommend using a small file.',
'proposal_editor' => 'proposal editor',
'background' => 'Background',
'guide' => 'Guide',
'gateway_fee_item' => 'Gateway Fee Item',
'gateway_fee_description' => 'Gateway Fee Surcharge',
); );

View File

@ -2409,6 +2409,8 @@ $LANG = array(
'currency_barbadian_dollar' => 'Barbadian Dollar', 'currency_barbadian_dollar' => 'Barbadian Dollar',
'currency_brunei_dollar' => 'Brunei Dollar', 'currency_brunei_dollar' => 'Brunei Dollar',
'currency_georgian_lari' => 'Georgian Lari', 'currency_georgian_lari' => 'Georgian Lari',
'currency_qatari_riyal' => 'Qatari Riyal',
'currency_honduran_lempira' => 'Honduran Lempira',
'review_app_help' => 'We hope you\'re enjoying using the app.<br/>If you\'d consider :link we\'d greatly appreciate it!', 'review_app_help' => 'We hope you\'re enjoying using the app.<br/>If you\'d consider :link we\'d greatly appreciate it!',
'writing_a_review' => 'writing a review', 'writing_a_review' => 'writing a review',
@ -2425,8 +2427,8 @@ $LANG = array(
'contact_custom1' => 'Contact First Custom', 'contact_custom1' => 'Contact First Custom',
'contact_custom2' => 'Contact Second Custom', 'contact_custom2' => 'Contact Second Custom',
'currency' => 'Currency', 'currency' => 'Currency',
'ofx_help' => 'In most cases the default values should work, if you\'re unable to connect it may help to :link.', 'ofx_help' => 'To troubleshoot check for comments on :ofxhome_link and test with :ofxget_link.',
'adjust_the_settings' => 'adjust the settings', 'comments' => 'comments',
'item_product' => 'Item Product', 'item_product' => 'Item Product',
'item_notes' => 'Item Notes', 'item_notes' => 'Item Notes',
@ -2654,7 +2656,7 @@ $LANG = array(
'signature_on_pdf_help' => 'Show the client signature on the invoice/quote PDF.', 'signature_on_pdf_help' => 'Show the client signature on the invoice/quote PDF.',
'expired_white_label' => 'The white label license has expired', 'expired_white_label' => 'The white label license has expired',
'return_to_login' => 'Return to Login', 'return_to_login' => 'Return to Login',
'convert_products_tip' => 'Note: add a custom field named ":name" to see the exchange rate.', 'convert_products_tip' => 'Note: add a :link named ":name" to see the exchange rate.',
'amount_greater_than_balance' => 'The amount is greater than the invoice balance, a credit will be created with the remaining amount.', 'amount_greater_than_balance' => 'The amount is greater than the invoice balance, a credit will be created with the remaining amount.',
'custom_fields_tip' => 'Use <code>Label|Option1,Option2</code> to show a select box.', 'custom_fields_tip' => 'Use <code>Label|Option1,Option2</code> to show a select box.',
'client_information' => 'Client Information', 'client_information' => 'Client Information',
@ -2795,9 +2797,50 @@ $LANG = array(
'purge_client_warning' => 'All related records (invoices, tasks, expenses, documents, etc) will also be deleted.', 'purge_client_warning' => 'All related records (invoices, tasks, expenses, documents, etc) will also be deleted.',
'clone_product' => 'Clone Product', 'clone_product' => 'Clone Product',
'item_details' => 'Item Details', 'item_details' => 'Item Details',
'send_item_details_help' => 'Send the line item details to the payment gateway.', 'send_item_details_help' => 'Send line item details to the payment gateway.',
'view_proposal' => 'View Proposal', 'view_proposal' => 'View Proposal',
'view_in_portal' => 'View in Portal', 'view_in_portal' => 'View in Portal',
'cookie_message' => 'This website uses cookies to ensure you get the best experience on our website.',
'got_it' => 'Got it!',
'vendor_will_create' => 'vendor will be created',
'vendors_will_create' => 'vendors will be created',
'created_vendors' => 'Successfully created :count vendor(s)',
'import_vendors' => 'Import Vendors',
'company' => 'Company',
'client_field' => 'Client Field',
'contact_field' => 'Contact Field',
'product_field' => 'Product Field',
'task_field' => 'Task Field',
'project_field' => 'Project Field',
'expense_field' => 'Expense Field',
'vendor_field' => 'Vendor Field',
'company_field' => 'Company Field',
'invoice_field' => 'Invoice Field',
'invoice_surcharge' => 'Invoice Surcharge',
'custom_task_fields_help' => 'Add a field when creating a task.',
'custom_project_fields_help' => 'Add a field when creating a project.',
'custom_expense_fields_help' => 'Add a field when creating an expense.',
'custom_vendor_fields_help' => 'Add a field when creating a vendor.',
'messages' => 'Messages',
'unpaid_invoice' => 'Unpaid Invoice',
'paid_invoice' => 'Paid Invoice',
'unapproved_quote' => 'Unapproved Quote',
'unapproved_proposal' => 'Unapproved Proposal',
'autofills_city_state' => 'Auto-fills city/state',
'no_match_found' => 'No match found',
'password_strength' => 'Password Strength',
'strength_weak' => 'Weak',
'strength_good' => 'Good',
'strength_strong' => 'Strong',
'mark' => 'Mark',
'updated_task_status' => 'Successfully update task status',
'background_image' => 'Background Image',
'background_image_help' => 'Use the :link to manage your images, we recommend using a small file.',
'proposal_editor' => 'proposal editor',
'background' => 'Background',
'guide' => 'Guide',
'gateway_fee_item' => 'Gateway Fee Item',
'gateway_fee_description' => 'Gateway Fee Surcharge',
); );

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