diff --git a/app/Helpers/Mail/Webhook/BaseWebhookHandler.php b/app/Helpers/Mail/Webhook/BaseWebhookHandler.php index cb839f4b5ef4..b579ad3bd9e6 100644 --- a/app/Helpers/Mail/Webhook/BaseWebhookHandler.php +++ b/app/Helpers/Mail/Webhook/BaseWebhookHandler.php @@ -11,10 +11,17 @@ namespace App\Helpers\Mail\Webhook; +use App\Models\Company; + interface BaseWebhookHandler { public function process() { } + + protected function matchCompany(string $email) + { + return Company::where("expense_mailbox", $email)->first(); + } } diff --git a/app/Helpers/Mail/Webhook/Postmark/PostmarkWebhookHandler.php b/app/Helpers/Mail/Webhook/Postmark/PostmarkWebhookHandler.php index 64920a7ecb0d..51e458cb57d8 100644 --- a/app/Helpers/Mail/Webhook/Postmark/PostmarkWebhookHandler.php +++ b/app/Helpers/Mail/Webhook/Postmark/PostmarkWebhookHandler.php @@ -11,12 +11,21 @@ namespace App\Helpers\Mail\Webhook\Postmark; +use App\Factory\ExpenseFactory; use App\Helpers\Mail\Webhook\BaseWebhookHandler; interface PostmarkWebhookHandler extends BaseWebhookHandler { - public function process() + public function process($data) { + $email = ''; + + $company = $this->matchCompany($email); + if (!$company) + return false; + + $expense = ExpenseFactory::create($company->id, $company->owner()->id); + } } diff --git a/app/Http/Requests/Company/StoreCompanyRequest.php b/app/Http/Requests/Company/StoreCompanyRequest.php index 70e116d60eea..ab5e2201baaf 100644 --- a/app/Http/Requests/Company/StoreCompanyRequest.php +++ b/app/Http/Requests/Company/StoreCompanyRequest.php @@ -13,6 +13,7 @@ namespace App\Http\Requests\Company; use App\Http\Requests\Request; use App\Http\ValidationRules\Company\ValidCompanyQuantity; +use App\Http\ValidationRules\Company\ValidExpenseMailbox; use App\Http\ValidationRules\Company\ValidSubdomain; use App\Http\ValidationRules\ValidSettingsRule; use App\Models\Company; @@ -28,7 +29,7 @@ class StoreCompanyRequest extends Request * * @return bool */ - public function authorize() : bool + public function authorize(): bool { /** @var \App\Models\User auth()->user */ $user = auth()->user(); @@ -55,6 +56,8 @@ class StoreCompanyRequest extends Request } } + $rules['expense_mailbox'] = new ValidExpenseMailbox($this->company->key, $this->company->account->isPaid() && $this->company->account->plan == 'enterprise'); // @turbo124 check if this is right + return $rules; } diff --git a/app/Http/Requests/Company/UpdateCompanyRequest.php b/app/Http/Requests/Company/UpdateCompanyRequest.php index 4ffa0e33093d..65150e376ca1 100644 --- a/app/Http/Requests/Company/UpdateCompanyRequest.php +++ b/app/Http/Requests/Company/UpdateCompanyRequest.php @@ -13,6 +13,7 @@ namespace App\Http\Requests\Company; use App\DataMapper\CompanySettings; use App\Http\Requests\Request; +use App\Http\ValidationRules\Company\ValidExpenseMailbox; use App\Http\ValidationRules\Company\ValidSubdomain; use App\Http\ValidationRules\ValidSettingsRule; use App\Utils\Ninja; @@ -35,7 +36,7 @@ class UpdateCompanyRequest extends Request * * @return bool */ - public function authorize() : bool + public function authorize(): bool { /** @var \App\Models\User $user */ $user = auth()->user(); @@ -67,6 +68,8 @@ class UpdateCompanyRequest extends Request $rules['subdomain'] = ['nullable', 'regex:/^[a-zA-Z0-9.-]+[a-zA-Z0-9]$/', new ValidSubdomain()]; } + $rules['expense_mailbox'] = new ValidExpenseMailbox($this->company->key, $this->company->account->isPaid() && $this->company->account->plan == 'enterprise'); // @turbo124 check if this is right + return $rules; } @@ -80,14 +83,14 @@ class UpdateCompanyRequest extends Request } if (array_key_exists('settings', $input)) { - $input['settings'] = (array)$this->filterSaveableSettings($input['settings']); + $input['settings'] = (array) $this->filterSaveableSettings($input['settings']); } - if(array_key_exists('subdomain', $input) && $this->company->subdomain == $input['subdomain']) { + if (array_key_exists('subdomain', $input) && $this->company->subdomain == $input['subdomain']) { unset($input['subdomain']); } - if(array_key_exists('e_invoice_certificate_passphrase', $input) && empty($input['e_invoice_certificate_passphrase'])) { + if (array_key_exists('e_invoice_certificate_passphrase', $input) && empty($input['e_invoice_certificate_passphrase'])) { unset($input['e_invoice_certificate_passphrase']); } @@ -115,17 +118,17 @@ class UpdateCompanyRequest extends Request } if (isset($settings['email_style_custom'])) { - $settings['email_style_custom'] = str_replace(['{{','}}'], ['',''], $settings['email_style_custom']); + $settings['email_style_custom'] = str_replace(['{{', '}}'], ['', ''], $settings['email_style_custom']); } - if (! $account->isFreeHostedClient()) { + if (!$account->isFreeHostedClient()) { return $settings; } $saveable_casts = CompanySettings::$free_plan_casts; foreach ($settings as $key => $value) { - if (! array_key_exists($key, $saveable_casts)) { + if (!array_key_exists($key, $saveable_casts)) { unset($settings->{$key}); } } @@ -137,7 +140,7 @@ class UpdateCompanyRequest extends Request { if (Ninja::isHosted()) { $url = str_replace('http://', '', $url); - $url = parse_url($url, PHP_URL_SCHEME) === null ? $scheme.$url : $url; + $url = parse_url($url, PHP_URL_SCHEME) === null ? $scheme . $url : $url; } return rtrim($url, '/'); diff --git a/app/Http/ValidationRules/Company/ValidExpenseMailbox.php b/app/Http/ValidationRules/Company/ValidExpenseMailbox.php new file mode 100644 index 000000000000..bb1b90212e22 --- /dev/null +++ b/app/Http/ValidationRules/Company/ValidExpenseMailbox.php @@ -0,0 +1,64 @@ +company_key = $company_key; + $this->isEnterprise = $isEnterprise; + } + + public function passes($attribute, $value) + { + if (empty($value)) { + return true; + } + + // early return, if we dont have any additional validation + if (!config('ninja.inbound_expense.webhook.mailbox_schema') && !(Ninja::isHosted() && config('ninja.inbound_expense.webhook.mailbox_schema_enterprise'))) { + $this->validated_schema = true; + return MultiDB::checkExpenseMailboxAvailable($value); + } + + // Validate Schema + $validated = !config('ninja.inbound_expense.webhook.mailbox_schema') || (preg_match(config('ninja.inbound_expense.webhook.mailbox_schema'), $value) && (!config('ninja.inbound_expense.webhook.mailbox_schema_hascompanykey') || str_contains($value, $this->company_key))) ? true : false; + $validated_enterprise = !config('ninja.inbound_expense.webhook.mailbox_schema_enterprise') || (Ninja::isHosted() && $this->isEnterprise && preg_match(config('ninja.inbound_expense.webhook.mailbox_schema_enterprise'), $value)); + + if (!$validated && !$validated_enterprise) + return false; + + $this->validated_schema = true; + return MultiDB::checkExpenseMailboxAvailable($value); + } + + /** + * @return string + */ + public function message() + { + return $this->validated_schema ? ctrans('texts.expense_mailbox_taken') : ctrans('texts.expense_mailbox_invalid'); + } +} diff --git a/app/Libraries/MultiDB.php b/app/Libraries/MultiDB.php index 57e734253bcf..f58841800c7b 100644 --- a/app/Libraries/MultiDB.php +++ b/app/Libraries/MultiDB.php @@ -71,18 +71,20 @@ class MultiDB 'socket', ]; + private static $protected_expense_mailboxes = []; + /** * @return array */ - public static function getDbs() : array + public static function getDbs(): array { return self::$dbs; } - public static function checkDomainAvailable($subdomain) : bool + public static function checkDomainAvailable($subdomain): bool { - if (! config('ninja.db.multi_db_enabled')) { + if (!config('ninja.db.multi_db_enabled')) { return Company::whereSubdomain($subdomain)->count() == 0; } @@ -105,9 +107,35 @@ class MultiDB return true; } - public static function checkUserEmailExists($email) : bool + public static function checkExpenseMailboxAvailable($expense_mailbox): bool { - if (! config('ninja.db.multi_db_enabled')) { + + if (!config('ninja.db.multi_db_enabled')) { + return Company::where("expense_mailbox", $expense_mailbox)->withTrashed()->exists(); + } + + if (in_array($expense_mailbox, self::$protected_expense_mailboxes)) { + return false; + } + + $current_db = config('database.default'); + + foreach (self::$dbs as $db) { + if (Company::on($db)->where("expense_mailbox", $expense_mailbox)->withTrashed()->exists()) { + self::setDb($current_db); + + return false; + } + } + + self::setDb($current_db); + + return true; + } + + public static function checkUserEmailExists($email): bool + { + if (!config('ninja.db.multi_db_enabled')) { return User::where(['email' => $email])->withTrashed()->exists(); } // true >= 1 emails found / false -> == emails found @@ -139,7 +167,7 @@ class MultiDB * @param string $company_key The company key * @return bool True|False */ - public static function checkUserAndCompanyCoExist($email, $company_key) :bool + public static function checkUserAndCompanyCoExist($email, $company_key): bool { $current_db = config('database.default'); @@ -166,9 +194,9 @@ class MultiDB * @param array $data * @return User|null */ - public static function hasUser(array $data) : ?User + public static function hasUser(array $data): ?User { - if (! config('ninja.db.multi_db_enabled')) { + if (!config('ninja.db.multi_db_enabled')) { return User::where($data)->withTrashed()->first(); } @@ -190,9 +218,9 @@ class MultiDB * @param string $email * @return ClientContact|null */ - public static function hasContact(string $email) : ?ClientContact + public static function hasContact(string $email): ?ClientContact { - if (! config('ninja.db.multi_db_enabled')) { + if (!config('ninja.db.multi_db_enabled')) { return ClientContact::where('email', $email)->withTrashed()->first(); } @@ -217,9 +245,9 @@ class MultiDB * @param array $search * @return ClientContact|null */ - public static function findContact(array $search) : ?ClientContact + public static function findContact(array $search): ?ClientContact { - if (! config('ninja.db.multi_db_enabled')) { + if (!config('ninja.db.multi_db_enabled')) { return ClientContact::where($search)->first(); } @@ -240,7 +268,7 @@ class MultiDB return null; } - public static function contactFindAndSetDb($token) :bool + public static function contactFindAndSetDb($token): bool { $current_db = config('database.default'); @@ -257,7 +285,7 @@ class MultiDB return false; } - public static function userFindAndSetDb($email) : bool + public static function userFindAndSetDb($email): bool { $current_db = config('database.default'); @@ -275,7 +303,7 @@ class MultiDB return false; } - public static function documentFindAndSetDb($hash) : bool + public static function documentFindAndSetDb($hash): bool { $current_db = config('database.default'); @@ -293,7 +321,7 @@ class MultiDB return false; } - public static function findAndSetDb($token) :bool + public static function findAndSetDb($token): bool { $current_db = config('database.default'); @@ -310,7 +338,7 @@ class MultiDB return false; } - public static function findAndSetDbByCompanyKey($company_key) :bool + public static function findAndSetDbByCompanyKey($company_key): bool { $current_db = config('database.default'); @@ -327,7 +355,7 @@ class MultiDB return false; } - public static function findAndSetDbByCompanyId($company_id) :?Company + public static function findAndSetDbByCompanyId($company_id): ?Company { $current_db = config('database.default'); @@ -344,7 +372,7 @@ class MultiDB return null; } - public static function findAndSetDbByShopifyName($shopify_name) :?Company + public static function findAndSetDbByShopifyName($shopify_name): ?Company { $current_db = config('database.default'); @@ -361,7 +389,7 @@ class MultiDB return null; } - public static function findAndSetDbByAccountKey($account_key) :bool + public static function findAndSetDbByAccountKey($account_key): bool { $current_db = config('database.default'); @@ -378,7 +406,7 @@ class MultiDB return false; } - public static function findAndSetDbByInappTransactionId($transaction_id) :bool + public static function findAndSetDbByInappTransactionId($transaction_id): bool { $current_db = config('database.default'); @@ -396,7 +424,7 @@ class MultiDB } - public static function findAndSetDbByContactKey($contact_key) :bool + public static function findAndSetDbByContactKey($contact_key): bool { $current_db = config('database.default'); @@ -413,7 +441,7 @@ class MultiDB return false; } - public static function findAndSetDbByVendorContactKey($contact_key) :bool + public static function findAndSetDbByVendorContactKey($contact_key): bool { $current_db = config('database.default'); @@ -430,7 +458,7 @@ class MultiDB return false; } - public static function findAndSetDbByClientHash($client_hash) :bool + public static function findAndSetDbByClientHash($client_hash): bool { $current_db = config('database.default'); @@ -447,7 +475,7 @@ class MultiDB return false; } - public static function findAndSetDbByClientId($client_id) :?Client + public static function findAndSetDbByClientId($client_id): ?Client { $current_db = config('database.default'); @@ -466,7 +494,7 @@ class MultiDB public static function findAndSetDbByDomain($query_array) { - if (! config('ninja.db.multi_db_enabled')) { + if (!config('ninja.db.multi_db_enabled')) { return Company::where($query_array)->first(); } @@ -487,7 +515,7 @@ class MultiDB public static function findAndSetDbByInvitation($entity, $invitation_key) { - $class = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation'; + $class = 'App\Models\\' . ucfirst(Str::camel($entity)) . 'Invitation'; $current_db = config('database.default'); foreach (self::$dbs as $db) { @@ -507,12 +535,12 @@ class MultiDB * @param string $phone * @return bool */ - public static function hasPhoneNumber(string $phone) : bool + public static function hasPhoneNumber(string $phone): bool { - if (! config('ninja.db.multi_db_enabled')) { + if (!config('ninja.db.multi_db_enabled')) { return Account::where('account_sms_verification_number', $phone)->where('account_sms_verified', true)->exists(); } - + $current_db = config('database.default'); foreach (self::$dbs as $db) { @@ -528,7 +556,7 @@ class MultiDB return false; } - + public static function randomSubdomainGenerator(): string { @@ -548,7 +576,7 @@ class MultiDB $string .= $consonants[rand(0, 19)]; $string .= $vowels[rand(0, 4)]; } - } while (! self::checkDomainAvailable($string)); + } while (!self::checkDomainAvailable($string)); self::setDb($current_db); @@ -559,7 +587,7 @@ class MultiDB * @param $database * @return void */ - public static function setDB(string $database) : void + public static function setDB(string $database): void { /* This will set the database connection for the request */ config(['database.default' => $database]); diff --git a/app/Transformers/AccountTransformer.php b/app/Transformers/AccountTransformer.php index 15797cff2f01..c467cdee0d6e 100644 --- a/app/Transformers/AccountTransformer.php +++ b/app/Transformers/AccountTransformer.php @@ -90,7 +90,7 @@ class AccountTransformer extends EntityTransformer 'set_react_as_default_ap' => (bool) $account->set_react_as_default_ap, 'trial_days_left' => Ninja::isHosted() ? (int) $account->getTrialDays() : 0, 'account_sms_verified' => (bool) $account->account_sms_verified, - 'has_iap_plan' => (bool)$account->inapp_transaction_id, + 'has_iap_plan' => (bool) $account->inapp_transaction_id, 'tax_api_enabled' => (bool) config('services.tax.zip_tax.key') ? true : false ]; diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index 0ea80a1e33ec..943414879b20 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -133,7 +133,7 @@ class CompanyTransformer extends EntityTransformer 'show_product_details' => (bool) $company->show_product_details, 'enable_product_quantity' => (bool) $company->enable_product_quantity, 'default_quantity' => (bool) $company->default_quantity, - 'custom_fields' => (object) $company->custom_fields ?? $std, + 'custom_fields' => (object) $company->custom_fields ?? $std, 'size_id' => (string) $company->size_id ?: '', 'industry_id' => (string) $company->industry_id ?: '', 'first_month_of_year' => (string) $company->first_month_of_year ?: '1', @@ -146,7 +146,7 @@ class CompanyTransformer extends EntityTransformer 'enabled_modules' => (int) $company->enabled_modules, 'updated_at' => (int) $company->updated_at, 'archived_at' => (int) $company->deleted_at, - 'created_at' =>(int) $company->created_at, + 'created_at' => (int) $company->created_at, 'slack_webhook_url' => (string) $company->slack_webhook_url, 'google_analytics_url' => (string) $company->google_analytics_key, //@deprecate 1-2-2021 'google_analytics_key' => (string) $company->google_analytics_key, @@ -158,7 +158,7 @@ class CompanyTransformer extends EntityTransformer 'is_large' => (bool) $this->isLarge($company), 'is_disabled' => (bool) $company->is_disabled, 'enable_shop_api' => (bool) $company->enable_shop_api, - 'mark_expenses_invoiceable'=> (bool) $company->mark_expenses_invoiceable, + 'mark_expenses_invoiceable' => (bool) $company->mark_expenses_invoiceable, 'mark_expenses_paid' => (bool) $company->mark_expenses_paid, 'invoice_expense_documents' => (bool) $company->invoice_expense_documents, 'invoice_task_timelog' => (bool) $company->invoice_task_timelog, @@ -168,10 +168,10 @@ class CompanyTransformer extends EntityTransformer 'use_credits_payment' => 'always', // @deprecate 1-2-2021 'default_task_is_date_based' => (bool) $company->default_task_is_date_based, 'enable_product_discount' => (bool) $company->enable_product_discount, - 'calculate_expense_tax_by_amount' =>(bool) $company->calculate_expense_tax_by_amount, + 'calculate_expense_tax_by_amount' => (bool) $company->calculate_expense_tax_by_amount, 'hide_empty_columns_on_pdf' => false, // @deprecate 1-2-2021 'expense_inclusive_taxes' => (bool) $company->expense_inclusive_taxes, - 'expense_amount_is_pretax' =>(bool) true, //@deprecate 1-2-2021 + 'expense_amount_is_pretax' => (bool) true, //@deprecate 1-2-2021 'oauth_password_required' => (bool) $company->oauth_password_required, 'session_timeout' => (int) $company->session_timeout, 'default_password_timeout' => (int) $company->default_password_timeout, @@ -204,6 +204,7 @@ class CompanyTransformer extends EntityTransformer 'invoice_task_project_header' => (bool) $company->invoice_task_project_header, 'invoice_task_item_description' => (bool) $company->invoice_task_item_description, 'origin_tax_data' => $company->origin_tax_data ?: new \stdClass, + 'expense_mailbox' => $company->expense_mailbox, ]; } diff --git a/config/ninja.php b/config/ninja.php index 25505a49c6a2..df09c38580d9 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -238,7 +238,9 @@ return [ ], 'webhook' => [ 'mailbox_template' => env('INBOUND_EXPENSE_WEBHOOK_MAILBOXTEMPLATE', null), - 'mailbox_template_enterprise' => env('INBOUND_EXPENSE_WEBHOOK_MAILBOXTEMPLATE_ENTERPRISE', '{{input}}@expense.invoicing.co'), + 'mailbox_schema' => env('INBOUND_EXPENSE_WEBHOOK_MAILBOX_SCHEMA', null), + 'mailbox_schema_hascompanykey' => env('INBOUND_EXPENSE_WEBHOOK_MAILBOX_SCHEMA_HASCOMPANYKEY', false), + 'mailbox_schema_enterprise' => env('INBOUND_EXPENSE_WEBHOOK_MAILBOX_SCHEMA_ENTERPRISE', '.*@expense\.invoicing\.co$'), ], ], ]; diff --git a/lang/en/texts.php b/lang/en/texts.php index a4bc7734fc4b..f34136ad2d7f 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -2534,6 +2534,8 @@ $lang = array( 'local_storage_required' => 'Error: local storage is not available.', 'your_password_reset_link' => 'Your Password Reset Link', 'subdomain_taken' => 'The subdomain is already in use', + 'expense_mailbox_taken' => 'The mailbox is already in use', + 'expense_mailbox_invalid' => 'The mailbox does not match the required schema', 'client_login' => 'Client Login', 'converted_amount' => 'Converted Amount', 'default' => 'Default', @@ -3857,308 +3859,308 @@ $lang = array( 'registration_url' => 'Registration URL', 'show_product_cost' => 'Show Product Cost', 'complete' => 'Complete', - 'next' => 'Next', - 'next_step' => 'Next step', - 'notification_credit_sent_subject' => 'Credit :invoice was sent to :client', - 'notification_credit_viewed_subject' => 'Credit :invoice was viewed by :client', - 'notification_credit_sent' => 'The following client :client was emailed Credit :invoice for :amount.', - 'notification_credit_viewed' => 'The following client :client viewed Credit :credit for :amount.', - 'reset_password_text' => 'Enter your email to reset your password.', - 'password_reset' => 'Password reset', - 'account_login_text' => 'Welcome! Glad to see you.', - 'request_cancellation' => 'Request cancellation', - 'delete_payment_method' => 'Delete Payment Method', - 'about_to_delete_payment_method' => 'You are about to delete the payment method.', - 'action_cant_be_reversed' => 'Action can\'t be reversed', - 'profile_updated_successfully' => 'The profile has been updated successfully.', - 'currency_ethiopian_birr' => 'Ethiopian Birr', - 'client_information_text' => 'Use a permanent address where you can receive mail.', - 'status_id' => 'Invoice Status', - 'email_already_register' => 'This email is already linked to an account', - 'locations' => 'Locations', - 'freq_indefinitely' => 'Indefinitely', - 'cycles_remaining' => 'Cycles remaining', - 'i_understand_delete' => 'I understand, delete', - 'download_files' => 'Download Files', - 'download_timeframe' => 'Use this link to download your files, the link will expire in 1 hour.', - 'new_signup' => 'New Signup', - 'new_signup_text' => 'A new account has been created by :user - :email - from IP address: :ip', - 'notification_payment_paid_subject' => 'Payment was made by :client', - 'notification_partial_payment_paid_subject' => 'Partial payment was made by :client', - 'notification_payment_paid' => 'A payment of :amount was made by client :client towards :invoice', - 'notification_partial_payment_paid' => 'A partial payment of :amount was made by client :client towards :invoice', - 'notification_bot' => 'Notification Bot', - 'invoice_number_placeholder' => 'Invoice # :invoice', - 'entity_number_placeholder' => ':entity # :entity_number', - 'email_link_not_working' => 'If the button above isn\'t working for you, please click on the link', - 'display_log' => 'Display Log', - 'send_fail_logs_to_our_server' => 'Report errors in realtime', - 'setup' => 'Setup', - 'quick_overview_statistics' => 'Quick overview & statistics', - 'update_your_personal_info' => 'Update your personal information', - 'name_website_logo' => 'Name, website & logo', - 'make_sure_use_full_link' => 'Make sure you use full link to your site', - 'personal_address' => 'Personal address', - 'enter_your_personal_address' => 'Enter your personal address', - 'enter_your_shipping_address' => 'Enter your shipping address', - 'list_of_invoices' => 'List of invoices', - 'with_selected' => 'With selected', - 'invoice_still_unpaid' => 'This invoice is still not paid. Click the button to complete the payment', - 'list_of_recurring_invoices' => 'List of recurring invoices', - 'details_of_recurring_invoice' => 'Here are some details about recurring invoice', - 'cancellation' => 'Cancellation', - 'about_cancellation' => 'In case you want to stop the recurring invoice, please click to request the cancellation.', - 'cancellation_warning' => 'Warning! You are requesting a cancellation of this service. Your service may be cancelled with no further notification to you.', - 'cancellation_pending' => 'Cancellation pending, we\'ll be in touch!', - 'list_of_payments' => 'List of payments', - 'payment_details' => 'Details of the payment', - 'list_of_payment_invoices' => 'List of invoices affected by the payment', - 'list_of_payment_methods' => 'List of payment methods', - 'payment_method_details' => 'Details of payment method', - 'permanently_remove_payment_method' => 'Permanently remove this payment method.', - 'warning_action_cannot_be_reversed' => 'Warning! This action can not be reversed!', - 'confirmation' => 'Confirmation', - 'list_of_quotes' => 'Quotes', - 'waiting_for_approval' => 'Waiting for approval', - 'quote_still_not_approved' => 'This quote is still not approved', - 'list_of_credits' => 'Credits', - 'required_extensions' => 'Required extensions', - 'php_version' => 'PHP version', - 'writable_env_file' => 'Writable .env file', - 'env_not_writable' => '.env file is not writable by the current user.', - 'minumum_php_version' => 'Minimum PHP version', - 'satisfy_requirements' => 'Make sure all requirements are satisfied.', - 'oops_issues' => 'Oops, something does not look right!', - 'open_in_new_tab' => 'Open in new tab', - 'complete_your_payment' => 'Complete payment', - 'authorize_for_future_use' => 'Authorize payment method for future use', - 'page' => 'Page', - 'per_page' => 'Per page', - 'of' => 'Of', - 'view_credit' => 'View Credit', - 'to_view_entity_password' => 'To view the :entity you need to enter password.', - 'showing_x_of' => 'Showing :first to :last out of :total results', - 'no_results' => 'No results found.', - 'payment_failed_subject' => 'Payment failed for Client :client', - 'payment_failed_body' => 'A payment made by client :client failed with message :message', - 'register' => 'Register', - 'register_label' => 'Create your account in seconds', - 'password_confirmation' => 'Confirm your password', - 'verification' => 'Verification', - 'complete_your_bank_account_verification' => 'Before using a bank account it must be verified.', - 'checkout_com' => 'Checkout.com', - 'footer_label' => 'Copyright © :year :company.', - 'credit_card_invalid' => 'Provided credit card number is not valid.', - 'month_invalid' => 'Provided month is not valid.', - 'year_invalid' => 'Provided year is not valid.', - 'https_required' => 'HTTPS is required, form will fail', - 'if_you_need_help' => 'If you need help you can post to our', - 'update_password_on_confirm' => 'After updating password, your account will be confirmed.', - 'bank_account_not_linked' => 'To pay with a bank account, first you have to add it as payment method.', - 'application_settings_label' => 'Let\'s store basic information about your Invoice Ninja!', - 'recommended_in_production' => 'Highly recommended in production', - 'enable_only_for_development' => 'Enable only for development', - 'test_pdf' => 'Test PDF', - 'checkout_authorize_label' => 'Checkout.com can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Store credit card details" during payment process.', - 'sofort_authorize_label' => 'Bank account (SOFORT) can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Store payment details" during payment process.', - 'node_status' => 'Node status', - 'npm_status' => 'NPM status', - 'node_status_not_found' => 'I could not find Node anywhere. Is it installed?', - 'npm_status_not_found' => 'I could not find NPM anywhere. Is it installed?', - 'locked_invoice' => 'This invoice is locked and unable to be modified', - 'downloads' => 'Downloads', - 'resource' => 'Resource', - 'document_details' => 'Details about the document', - 'hash' => 'Hash', - 'resources' => 'Resources', - 'allowed_file_types' => 'Allowed file types:', - 'common_codes' => 'Common codes and their meanings', - 'payment_error_code_20087' => '20087: Bad Track Data (invalid CVV and/or expiry date)', - 'download_selected' => 'Download selected', - 'to_pay_invoices' => 'To pay invoices, you have to', - 'add_payment_method_first' => 'add payment method', - 'no_items_selected' => 'No items selected.', - 'payment_due' => 'Payment due', - 'account_balance' => 'Account Balance', - 'thanks' => 'Thanks', - 'minimum_required_payment' => 'Minimum required payment is :amount', - 'under_payments_disabled' => 'Company doesn\'t support underpayments.', - 'over_payments_disabled' => 'Company doesn\'t support overpayments.', - 'saved_at' => 'Saved at :time', - 'credit_payment' => 'Credit applied to Invoice :invoice_number', - 'credit_subject' => 'New credit :number from :account', - 'credit_message' => 'To view your credit for :amount, click the link below.', - 'payment_type_Crypto' => 'Cryptocurrency', - 'payment_type_Credit' => 'Credit', - 'store_for_future_use' => 'Store for future use', - 'pay_with_credit' => 'Pay with credit', - 'payment_method_saving_failed' => 'Payment method can\'t be saved for future use.', - 'pay_with' => 'Pay with', - 'n/a' => 'N/A', - 'by_clicking_next_you_accept_terms' => 'By clicking "Next step" you accept terms.', - 'not_specified' => 'Not specified', - 'before_proceeding_with_payment_warning' => 'Before proceeding with payment, you have to fill following fields', - 'after_completing_go_back_to_previous_page' => 'After completing, go back to previous page.', - 'pay' => 'Pay', - 'instructions' => 'Instructions', - 'notification_invoice_reminder1_sent_subject' => 'Reminder 1 for Invoice :invoice was sent to :client', - 'notification_invoice_reminder2_sent_subject' => 'Reminder 2 for Invoice :invoice was sent to :client', - 'notification_invoice_reminder3_sent_subject' => 'Reminder 3 for Invoice :invoice was sent to :client', - 'notification_invoice_custom_sent_subject' => 'Custom reminder for Invoice :invoice was sent to :client', - 'notification_invoice_reminder_endless_sent_subject' => 'Endless reminder for Invoice :invoice was sent to :client', - 'assigned_user' => 'Assigned User', - 'setup_steps_notice' => 'To proceed to next step, make sure you test each section.', - 'setup_phantomjs_note' => 'Note about Phantom JS. Read more.', - 'minimum_payment' => 'Minimum Payment', - 'no_action_provided' => 'No action provided. If you believe this is wrong, please contact the support.', - 'no_payable_invoices_selected' => 'No payable invoices selected. Make sure you are not trying to pay draft invoice or invoice with zero balance due.', - 'required_payment_information' => 'Required payment details', - 'required_payment_information_more' => 'To complete a payment we need more details about you.', - 'required_client_info_save_label' => 'We will save this, so you don\'t have to enter it next time.', - 'notification_credit_bounced' => 'We were unable to deliver Credit :invoice to :contact. \n :error', - 'notification_credit_bounced_subject' => 'Unable to deliver Credit :invoice', - 'save_payment_method_details' => 'Save payment method details', - 'new_card' => 'New card', - 'new_bank_account' => 'New bank account', - 'company_limit_reached' => 'Limit of :limit companies per account.', - 'credits_applied_validation' => 'Total credits applied cannot be MORE than total of invoices', - 'credit_number_taken' => 'Credit number already taken', - 'credit_not_found' => 'Credit not found', - 'invoices_dont_match_client' => 'Selected invoices are not from a single client', - 'duplicate_credits_submitted' => 'Duplicate credits submitted.', - 'duplicate_invoices_submitted' => 'Duplicate invoices submitted.', - 'credit_with_no_invoice' => 'You must have an invoice set when using a credit in a payment', - 'client_id_required' => 'Client id is required', - 'expense_number_taken' => 'Expense number already taken', - 'invoice_number_taken' => 'Invoice number already taken', - 'payment_id_required' => 'Payment `id` required.', - 'unable_to_retrieve_payment' => 'Unable to retrieve specified payment', - 'invoice_not_related_to_payment' => 'Invoice id :invoice is not related to this payment', - 'credit_not_related_to_payment' => 'Credit id :credit is not related to this payment', - 'max_refundable_invoice' => 'Attempting to refund more than allowed for invoice id :invoice, maximum refundable amount is :amount', - 'refund_without_invoices' => 'Attempting to refund a payment with invoices attached, please specify valid invoice/s to be refunded.', - 'refund_without_credits' => 'Attempting to refund a payment with credits attached, please specify valid credits/s to be refunded.', - 'max_refundable_credit' => 'Attempting to refund more than allowed for credit :credit, maximum refundable amount is :amount', - 'project_client_do_not_match' => 'Project client does not match entity client', - 'quote_number_taken' => 'Quote number already taken', - 'recurring_invoice_number_taken' => 'Recurring Invoice number :number already taken', - 'user_not_associated_with_account' => 'User not associated with this account', - 'amounts_do_not_balance' => 'Amounts do not balance correctly.', - 'insufficient_applied_amount_remaining' => 'Insufficient applied amount remaining to cover payment.', - 'insufficient_credit_balance' => 'Insufficient balance on credit.', - 'one_or_more_invoices_paid' => 'One or more of these invoices have been paid', - 'invoice_cannot_be_refunded' => 'Invoice id :number cannot be refunded', - 'attempted_refund_failed' => 'Attempting to refund :amount only :refundable_amount available for refund', - 'user_not_associated_with_this_account' => 'This user is unable to be attached to this company. Perhaps they have already registered a user on another account?', - 'migration_completed' => 'Migration completed', - 'migration_completed_description' => 'Your migration has completed, please review your data after logging in.', - 'api_404' => '404 | Nothing to see here!', - 'large_account_update_parameter' => 'Cannot load a large account without a updated_at parameter', - 'no_backup_exists' => 'No backup exists for this activity', - 'company_user_not_found' => 'Company User record not found', - 'no_credits_found' => 'No credits found.', - 'action_unavailable' => 'The requested action :action is not available.', - 'no_documents_found' => 'No Documents Found', - 'no_group_settings_found' => 'No group settings found', - 'access_denied' => 'Insufficient privileges to access/modify this resource', - 'invoice_cannot_be_marked_paid' => 'Invoice cannot be marked as paid', - 'invoice_license_or_environment' => 'Invalid license, or invalid environment :environment', - 'route_not_available' => 'Route not available', - 'invalid_design_object' => 'Invalid custom design object', - 'quote_not_found' => 'Quote/s not found', - 'quote_unapprovable' => 'Unable to approve this quote as it has expired.', - 'scheduler_has_run' => 'Scheduler has run', - 'scheduler_has_never_run' => 'Scheduler has never run', - 'self_update_not_available' => 'Self update not available on this system.', - 'user_detached' => 'User detached from company', - 'create_webhook_failure' => 'Failed to create Webhook', - 'payment_message_extended' => 'Thank you for your payment of :amount for :invoice', - 'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is bigger than $1 or currency equivalent.', - 'payment_token_not_found' => 'Payment token not found, please try again. If an issue still persist, try with another payment method', - 'vendor_address1' => 'Vendor Street', - 'vendor_address2' => 'Vendor Apt/Suite', - 'partially_unapplied' => 'Partially Unapplied', - 'select_a_gmail_user' => 'Please select a user authenticated with Gmail', - 'list_long_press' => 'List Long Press', - 'show_actions' => 'Show Actions', - 'start_multiselect' => 'Start Multiselect', - 'email_sent_to_confirm_email' => 'An email has been sent to confirm the email address', - 'converted_paid_to_date' => 'Converted Paid to Date', - 'converted_credit_balance' => 'Converted Credit Balance', - 'converted_total' => 'Converted Total', - 'reply_to_name' => 'Reply-To Name', - 'payment_status_-2' => 'Partially Unapplied', - 'color_theme' => 'Color Theme', - 'start_migration' => 'Start Migration', - 'recurring_cancellation_request' => 'Request for recurring invoice cancellation from :contact', - 'recurring_cancellation_request_body' => ':contact from Client :client requested to cancel Recurring Invoice :invoice', - 'hello' => 'Hello', - 'group_documents' => 'Group documents', - 'quote_approval_confirmation_label' => 'Are you sure you want to approve this quote?', - 'migration_select_company_label' => 'Select companies to migrate', - 'force_migration' => 'Force migration', - 'require_password_with_social_login' => 'Require Password with Social Login', - 'stay_logged_in' => 'Stay Logged In', - 'session_about_to_expire' => 'Warning: Your session is about to expire', - 'count_hours' => ':count Hours', - 'count_day' => '1 Day', - 'count_days' => ':count Days', - 'web_session_timeout' => 'Web Session Timeout', - 'security_settings' => 'Security Settings', - 'resend_email' => 'Resend Email', - 'confirm_your_email_address' => 'Please confirm your email address', - 'freshbooks' => 'FreshBooks', - 'invoice2go' => 'Invoice2go', - 'invoicely' => 'Invoicely', - 'waveaccounting' => 'Wave Accounting', - 'zoho' => 'Zoho', - 'accounting' => 'Accounting', - 'required_files_missing' => 'Please provide all CSVs.', - 'migration_auth_label' => 'Let\'s continue by authenticating.', - 'api_secret' => 'API secret', - 'migration_api_secret_notice' => 'You can find API_SECRET in the .env file or Invoice Ninja v5. If property is missing, leave field blank.', - 'billing_coupon_notice' => 'Your discount will be applied on the checkout.', - 'use_last_email' => 'Use last email', - 'activate_company' => 'Activate Company', - 'activate_company_help' => 'Enable emails, recurring invoices and notifications', - 'an_error_occurred_try_again' => 'An error occurred, please try again', - 'please_first_set_a_password' => 'Please first set a password', - 'changing_phone_disables_two_factor' => 'Warning: Changing your phone number will disable 2FA', - 'help_translate' => 'Help Translate', - 'please_select_a_country' => 'Please select a country', - 'disabled_two_factor' => 'Successfully disabled 2FA', - 'connected_google' => 'Successfully connected account', - 'disconnected_google' => 'Successfully disconnected account', - 'delivered' => 'Delivered', - 'spam' => 'Spam', - 'view_docs' => 'View Docs', - 'enter_phone_to_enable_two_factor' => 'Please provide a mobile phone number to enable two factor authentication', - 'send_sms' => 'Send SMS', - 'sms_code' => 'SMS Code', - 'connect_google' => 'Connect Google', - 'disconnect_google' => 'Disconnect Google', - 'disable_two_factor' => 'Disable Two Factor', - 'invoice_task_datelog' => 'Invoice Task Datelog', - 'invoice_task_datelog_help' => 'Add date details to the invoice line items', - 'promo_code' => 'Promo code', - 'recurring_invoice_issued_to' => 'Recurring invoice issued to', - 'subscription' => 'Subscription', - 'new_subscription' => 'New Subscription', - 'deleted_subscription' => 'Successfully deleted subscription', - 'removed_subscription' => 'Successfully removed subscription', - 'restored_subscription' => 'Successfully restored subscription', - 'search_subscription' => 'Search 1 Subscription', - 'search_subscriptions' => 'Search :count Subscriptions', - 'subdomain_is_not_available' => 'Subdomain is not available', - 'connect_gmail' => 'Connect Gmail', - 'disconnect_gmail' => 'Disconnect Gmail', - 'connected_gmail' => 'Successfully connected Gmail', - 'disconnected_gmail' => 'Successfully disconnected Gmail', - 'update_fail_help' => 'Changes to the codebase may be blocking the update, you can run this command to discard the changes:', - 'client_id_number' => 'Client ID Number', - 'count_minutes' => ':count Minutes', - 'password_timeout' => 'Password Timeout', - 'shared_invoice_credit_counter' => 'Share Invoice/Credit Counter', + 'next' => 'Next', + 'next_step' => 'Next step', + 'notification_credit_sent_subject' => 'Credit :invoice was sent to :client', + 'notification_credit_viewed_subject' => 'Credit :invoice was viewed by :client', + 'notification_credit_sent' => 'The following client :client was emailed Credit :invoice for :amount.', + 'notification_credit_viewed' => 'The following client :client viewed Credit :credit for :amount.', + 'reset_password_text' => 'Enter your email to reset your password.', + 'password_reset' => 'Password reset', + 'account_login_text' => 'Welcome! Glad to see you.', + 'request_cancellation' => 'Request cancellation', + 'delete_payment_method' => 'Delete Payment Method', + 'about_to_delete_payment_method' => 'You are about to delete the payment method.', + 'action_cant_be_reversed' => 'Action can\'t be reversed', + 'profile_updated_successfully' => 'The profile has been updated successfully.', + 'currency_ethiopian_birr' => 'Ethiopian Birr', + 'client_information_text' => 'Use a permanent address where you can receive mail.', + 'status_id' => 'Invoice Status', + 'email_already_register' => 'This email is already linked to an account', + 'locations' => 'Locations', + 'freq_indefinitely' => 'Indefinitely', + 'cycles_remaining' => 'Cycles remaining', + 'i_understand_delete' => 'I understand, delete', + 'download_files' => 'Download Files', + 'download_timeframe' => 'Use this link to download your files, the link will expire in 1 hour.', + 'new_signup' => 'New Signup', + 'new_signup_text' => 'A new account has been created by :user - :email - from IP address: :ip', + 'notification_payment_paid_subject' => 'Payment was made by :client', + 'notification_partial_payment_paid_subject' => 'Partial payment was made by :client', + 'notification_payment_paid' => 'A payment of :amount was made by client :client towards :invoice', + 'notification_partial_payment_paid' => 'A partial payment of :amount was made by client :client towards :invoice', + 'notification_bot' => 'Notification Bot', + 'invoice_number_placeholder' => 'Invoice # :invoice', + 'entity_number_placeholder' => ':entity # :entity_number', + 'email_link_not_working' => 'If the button above isn\'t working for you, please click on the link', + 'display_log' => 'Display Log', + 'send_fail_logs_to_our_server' => 'Report errors in realtime', + 'setup' => 'Setup', + 'quick_overview_statistics' => 'Quick overview & statistics', + 'update_your_personal_info' => 'Update your personal information', + 'name_website_logo' => 'Name, website & logo', + 'make_sure_use_full_link' => 'Make sure you use full link to your site', + 'personal_address' => 'Personal address', + 'enter_your_personal_address' => 'Enter your personal address', + 'enter_your_shipping_address' => 'Enter your shipping address', + 'list_of_invoices' => 'List of invoices', + 'with_selected' => 'With selected', + 'invoice_still_unpaid' => 'This invoice is still not paid. Click the button to complete the payment', + 'list_of_recurring_invoices' => 'List of recurring invoices', + 'details_of_recurring_invoice' => 'Here are some details about recurring invoice', + 'cancellation' => 'Cancellation', + 'about_cancellation' => 'In case you want to stop the recurring invoice, please click to request the cancellation.', + 'cancellation_warning' => 'Warning! You are requesting a cancellation of this service. Your service may be cancelled with no further notification to you.', + 'cancellation_pending' => 'Cancellation pending, we\'ll be in touch!', + 'list_of_payments' => 'List of payments', + 'payment_details' => 'Details of the payment', + 'list_of_payment_invoices' => 'List of invoices affected by the payment', + 'list_of_payment_methods' => 'List of payment methods', + 'payment_method_details' => 'Details of payment method', + 'permanently_remove_payment_method' => 'Permanently remove this payment method.', + 'warning_action_cannot_be_reversed' => 'Warning! This action can not be reversed!', + 'confirmation' => 'Confirmation', + 'list_of_quotes' => 'Quotes', + 'waiting_for_approval' => 'Waiting for approval', + 'quote_still_not_approved' => 'This quote is still not approved', + 'list_of_credits' => 'Credits', + 'required_extensions' => 'Required extensions', + 'php_version' => 'PHP version', + 'writable_env_file' => 'Writable .env file', + 'env_not_writable' => '.env file is not writable by the current user.', + 'minumum_php_version' => 'Minimum PHP version', + 'satisfy_requirements' => 'Make sure all requirements are satisfied.', + 'oops_issues' => 'Oops, something does not look right!', + 'open_in_new_tab' => 'Open in new tab', + 'complete_your_payment' => 'Complete payment', + 'authorize_for_future_use' => 'Authorize payment method for future use', + 'page' => 'Page', + 'per_page' => 'Per page', + 'of' => 'Of', + 'view_credit' => 'View Credit', + 'to_view_entity_password' => 'To view the :entity you need to enter password.', + 'showing_x_of' => 'Showing :first to :last out of :total results', + 'no_results' => 'No results found.', + 'payment_failed_subject' => 'Payment failed for Client :client', + 'payment_failed_body' => 'A payment made by client :client failed with message :message', + 'register' => 'Register', + 'register_label' => 'Create your account in seconds', + 'password_confirmation' => 'Confirm your password', + 'verification' => 'Verification', + 'complete_your_bank_account_verification' => 'Before using a bank account it must be verified.', + 'checkout_com' => 'Checkout.com', + 'footer_label' => 'Copyright © :year :company.', + 'credit_card_invalid' => 'Provided credit card number is not valid.', + 'month_invalid' => 'Provided month is not valid.', + 'year_invalid' => 'Provided year is not valid.', + 'https_required' => 'HTTPS is required, form will fail', + 'if_you_need_help' => 'If you need help you can post to our', + 'update_password_on_confirm' => 'After updating password, your account will be confirmed.', + 'bank_account_not_linked' => 'To pay with a bank account, first you have to add it as payment method.', + 'application_settings_label' => 'Let\'s store basic information about your Invoice Ninja!', + 'recommended_in_production' => 'Highly recommended in production', + 'enable_only_for_development' => 'Enable only for development', + 'test_pdf' => 'Test PDF', + 'checkout_authorize_label' => 'Checkout.com can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Store credit card details" during payment process.', + 'sofort_authorize_label' => 'Bank account (SOFORT) can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Store payment details" during payment process.', + 'node_status' => 'Node status', + 'npm_status' => 'NPM status', + 'node_status_not_found' => 'I could not find Node anywhere. Is it installed?', + 'npm_status_not_found' => 'I could not find NPM anywhere. Is it installed?', + 'locked_invoice' => 'This invoice is locked and unable to be modified', + 'downloads' => 'Downloads', + 'resource' => 'Resource', + 'document_details' => 'Details about the document', + 'hash' => 'Hash', + 'resources' => 'Resources', + 'allowed_file_types' => 'Allowed file types:', + 'common_codes' => 'Common codes and their meanings', + 'payment_error_code_20087' => '20087: Bad Track Data (invalid CVV and/or expiry date)', + 'download_selected' => 'Download selected', + 'to_pay_invoices' => 'To pay invoices, you have to', + 'add_payment_method_first' => 'add payment method', + 'no_items_selected' => 'No items selected.', + 'payment_due' => 'Payment due', + 'account_balance' => 'Account Balance', + 'thanks' => 'Thanks', + 'minimum_required_payment' => 'Minimum required payment is :amount', + 'under_payments_disabled' => 'Company doesn\'t support underpayments.', + 'over_payments_disabled' => 'Company doesn\'t support overpayments.', + 'saved_at' => 'Saved at :time', + 'credit_payment' => 'Credit applied to Invoice :invoice_number', + 'credit_subject' => 'New credit :number from :account', + 'credit_message' => 'To view your credit for :amount, click the link below.', + 'payment_type_Crypto' => 'Cryptocurrency', + 'payment_type_Credit' => 'Credit', + 'store_for_future_use' => 'Store for future use', + 'pay_with_credit' => 'Pay with credit', + 'payment_method_saving_failed' => 'Payment method can\'t be saved for future use.', + 'pay_with' => 'Pay with', + 'n/a' => 'N/A', + 'by_clicking_next_you_accept_terms' => 'By clicking "Next step" you accept terms.', + 'not_specified' => 'Not specified', + 'before_proceeding_with_payment_warning' => 'Before proceeding with payment, you have to fill following fields', + 'after_completing_go_back_to_previous_page' => 'After completing, go back to previous page.', + 'pay' => 'Pay', + 'instructions' => 'Instructions', + 'notification_invoice_reminder1_sent_subject' => 'Reminder 1 for Invoice :invoice was sent to :client', + 'notification_invoice_reminder2_sent_subject' => 'Reminder 2 for Invoice :invoice was sent to :client', + 'notification_invoice_reminder3_sent_subject' => 'Reminder 3 for Invoice :invoice was sent to :client', + 'notification_invoice_custom_sent_subject' => 'Custom reminder for Invoice :invoice was sent to :client', + 'notification_invoice_reminder_endless_sent_subject' => 'Endless reminder for Invoice :invoice was sent to :client', + 'assigned_user' => 'Assigned User', + 'setup_steps_notice' => 'To proceed to next step, make sure you test each section.', + 'setup_phantomjs_note' => 'Note about Phantom JS. Read more.', + 'minimum_payment' => 'Minimum Payment', + 'no_action_provided' => 'No action provided. If you believe this is wrong, please contact the support.', + 'no_payable_invoices_selected' => 'No payable invoices selected. Make sure you are not trying to pay draft invoice or invoice with zero balance due.', + 'required_payment_information' => 'Required payment details', + 'required_payment_information_more' => 'To complete a payment we need more details about you.', + 'required_client_info_save_label' => 'We will save this, so you don\'t have to enter it next time.', + 'notification_credit_bounced' => 'We were unable to deliver Credit :invoice to :contact. \n :error', + 'notification_credit_bounced_subject' => 'Unable to deliver Credit :invoice', + 'save_payment_method_details' => 'Save payment method details', + 'new_card' => 'New card', + 'new_bank_account' => 'New bank account', + 'company_limit_reached' => 'Limit of :limit companies per account.', + 'credits_applied_validation' => 'Total credits applied cannot be MORE than total of invoices', + 'credit_number_taken' => 'Credit number already taken', + 'credit_not_found' => 'Credit not found', + 'invoices_dont_match_client' => 'Selected invoices are not from a single client', + 'duplicate_credits_submitted' => 'Duplicate credits submitted.', + 'duplicate_invoices_submitted' => 'Duplicate invoices submitted.', + 'credit_with_no_invoice' => 'You must have an invoice set when using a credit in a payment', + 'client_id_required' => 'Client id is required', + 'expense_number_taken' => 'Expense number already taken', + 'invoice_number_taken' => 'Invoice number already taken', + 'payment_id_required' => 'Payment `id` required.', + 'unable_to_retrieve_payment' => 'Unable to retrieve specified payment', + 'invoice_not_related_to_payment' => 'Invoice id :invoice is not related to this payment', + 'credit_not_related_to_payment' => 'Credit id :credit is not related to this payment', + 'max_refundable_invoice' => 'Attempting to refund more than allowed for invoice id :invoice, maximum refundable amount is :amount', + 'refund_without_invoices' => 'Attempting to refund a payment with invoices attached, please specify valid invoice/s to be refunded.', + 'refund_without_credits' => 'Attempting to refund a payment with credits attached, please specify valid credits/s to be refunded.', + 'max_refundable_credit' => 'Attempting to refund more than allowed for credit :credit, maximum refundable amount is :amount', + 'project_client_do_not_match' => 'Project client does not match entity client', + 'quote_number_taken' => 'Quote number already taken', + 'recurring_invoice_number_taken' => 'Recurring Invoice number :number already taken', + 'user_not_associated_with_account' => 'User not associated with this account', + 'amounts_do_not_balance' => 'Amounts do not balance correctly.', + 'insufficient_applied_amount_remaining' => 'Insufficient applied amount remaining to cover payment.', + 'insufficient_credit_balance' => 'Insufficient balance on credit.', + 'one_or_more_invoices_paid' => 'One or more of these invoices have been paid', + 'invoice_cannot_be_refunded' => 'Invoice id :number cannot be refunded', + 'attempted_refund_failed' => 'Attempting to refund :amount only :refundable_amount available for refund', + 'user_not_associated_with_this_account' => 'This user is unable to be attached to this company. Perhaps they have already registered a user on another account?', + 'migration_completed' => 'Migration completed', + 'migration_completed_description' => 'Your migration has completed, please review your data after logging in.', + 'api_404' => '404 | Nothing to see here!', + 'large_account_update_parameter' => 'Cannot load a large account without a updated_at parameter', + 'no_backup_exists' => 'No backup exists for this activity', + 'company_user_not_found' => 'Company User record not found', + 'no_credits_found' => 'No credits found.', + 'action_unavailable' => 'The requested action :action is not available.', + 'no_documents_found' => 'No Documents Found', + 'no_group_settings_found' => 'No group settings found', + 'access_denied' => 'Insufficient privileges to access/modify this resource', + 'invoice_cannot_be_marked_paid' => 'Invoice cannot be marked as paid', + 'invoice_license_or_environment' => 'Invalid license, or invalid environment :environment', + 'route_not_available' => 'Route not available', + 'invalid_design_object' => 'Invalid custom design object', + 'quote_not_found' => 'Quote/s not found', + 'quote_unapprovable' => 'Unable to approve this quote as it has expired.', + 'scheduler_has_run' => 'Scheduler has run', + 'scheduler_has_never_run' => 'Scheduler has never run', + 'self_update_not_available' => 'Self update not available on this system.', + 'user_detached' => 'User detached from company', + 'create_webhook_failure' => 'Failed to create Webhook', + 'payment_message_extended' => 'Thank you for your payment of :amount for :invoice', + 'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is bigger than $1 or currency equivalent.', + 'payment_token_not_found' => 'Payment token not found, please try again. If an issue still persist, try with another payment method', + 'vendor_address1' => 'Vendor Street', + 'vendor_address2' => 'Vendor Apt/Suite', + 'partially_unapplied' => 'Partially Unapplied', + 'select_a_gmail_user' => 'Please select a user authenticated with Gmail', + 'list_long_press' => 'List Long Press', + 'show_actions' => 'Show Actions', + 'start_multiselect' => 'Start Multiselect', + 'email_sent_to_confirm_email' => 'An email has been sent to confirm the email address', + 'converted_paid_to_date' => 'Converted Paid to Date', + 'converted_credit_balance' => 'Converted Credit Balance', + 'converted_total' => 'Converted Total', + 'reply_to_name' => 'Reply-To Name', + 'payment_status_-2' => 'Partially Unapplied', + 'color_theme' => 'Color Theme', + 'start_migration' => 'Start Migration', + 'recurring_cancellation_request' => 'Request for recurring invoice cancellation from :contact', + 'recurring_cancellation_request_body' => ':contact from Client :client requested to cancel Recurring Invoice :invoice', + 'hello' => 'Hello', + 'group_documents' => 'Group documents', + 'quote_approval_confirmation_label' => 'Are you sure you want to approve this quote?', + 'migration_select_company_label' => 'Select companies to migrate', + 'force_migration' => 'Force migration', + 'require_password_with_social_login' => 'Require Password with Social Login', + 'stay_logged_in' => 'Stay Logged In', + 'session_about_to_expire' => 'Warning: Your session is about to expire', + 'count_hours' => ':count Hours', + 'count_day' => '1 Day', + 'count_days' => ':count Days', + 'web_session_timeout' => 'Web Session Timeout', + 'security_settings' => 'Security Settings', + 'resend_email' => 'Resend Email', + 'confirm_your_email_address' => 'Please confirm your email address', + 'freshbooks' => 'FreshBooks', + 'invoice2go' => 'Invoice2go', + 'invoicely' => 'Invoicely', + 'waveaccounting' => 'Wave Accounting', + 'zoho' => 'Zoho', + 'accounting' => 'Accounting', + 'required_files_missing' => 'Please provide all CSVs.', + 'migration_auth_label' => 'Let\'s continue by authenticating.', + 'api_secret' => 'API secret', + 'migration_api_secret_notice' => 'You can find API_SECRET in the .env file or Invoice Ninja v5. If property is missing, leave field blank.', + 'billing_coupon_notice' => 'Your discount will be applied on the checkout.', + 'use_last_email' => 'Use last email', + 'activate_company' => 'Activate Company', + 'activate_company_help' => 'Enable emails, recurring invoices and notifications', + 'an_error_occurred_try_again' => 'An error occurred, please try again', + 'please_first_set_a_password' => 'Please first set a password', + 'changing_phone_disables_two_factor' => 'Warning: Changing your phone number will disable 2FA', + 'help_translate' => 'Help Translate', + 'please_select_a_country' => 'Please select a country', + 'disabled_two_factor' => 'Successfully disabled 2FA', + 'connected_google' => 'Successfully connected account', + 'disconnected_google' => 'Successfully disconnected account', + 'delivered' => 'Delivered', + 'spam' => 'Spam', + 'view_docs' => 'View Docs', + 'enter_phone_to_enable_two_factor' => 'Please provide a mobile phone number to enable two factor authentication', + 'send_sms' => 'Send SMS', + 'sms_code' => 'SMS Code', + 'connect_google' => 'Connect Google', + 'disconnect_google' => 'Disconnect Google', + 'disable_two_factor' => 'Disable Two Factor', + 'invoice_task_datelog' => 'Invoice Task Datelog', + 'invoice_task_datelog_help' => 'Add date details to the invoice line items', + 'promo_code' => 'Promo code', + 'recurring_invoice_issued_to' => 'Recurring invoice issued to', + 'subscription' => 'Subscription', + 'new_subscription' => 'New Subscription', + 'deleted_subscription' => 'Successfully deleted subscription', + 'removed_subscription' => 'Successfully removed subscription', + 'restored_subscription' => 'Successfully restored subscription', + 'search_subscription' => 'Search 1 Subscription', + 'search_subscriptions' => 'Search :count Subscriptions', + 'subdomain_is_not_available' => 'Subdomain is not available', + 'connect_gmail' => 'Connect Gmail', + 'disconnect_gmail' => 'Disconnect Gmail', + 'connected_gmail' => 'Successfully connected Gmail', + 'disconnected_gmail' => 'Successfully disconnected Gmail', + 'update_fail_help' => 'Changes to the codebase may be blocking the update, you can run this command to discard the changes:', + 'client_id_number' => 'Client ID Number', + 'count_minutes' => ':count Minutes', + 'password_timeout' => 'Password Timeout', + 'shared_invoice_credit_counter' => 'Share Invoice/Credit Counter', 'activity_80' => ':user created subscription :subscription', 'activity_81' => ':user updated subscription :subscription', 'activity_82' => ':user archived subscription :subscription',