diff --git a/.env.example b/.env.example index 651ccbbc5207..bd40885edb04 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,7 @@ APP_ENV=development APP_DEBUG=true APP_URL=http://ninja.dev +APP_CIPHER=rijndael-128 APP_KEY= DB_TYPE=mysql diff --git a/app/Console/Commands/SendRecurringInvoices.php b/app/Console/Commands/SendRecurringInvoices.php index ece16d94baa5..a5fe2cdaf9b9 100644 --- a/app/Console/Commands/SendRecurringInvoices.php +++ b/app/Console/Commands/SendRecurringInvoices.php @@ -1,9 +1,15 @@ invoice = $invoice; + } + +} diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index c7a75d356dd5..1475cbb5cb09 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -1,5 +1,6 @@ $count < 2]); + return View::make('accounts.payments', ['showAdd' => $count < 3]); } } elseif ($section == ACCOUNT_NOTIFICATIONS) { $data = [ @@ -554,6 +555,7 @@ class AccountController extends BaseController $user->notify_sent = Input::get('notify_sent'); $user->notify_viewed = Input::get('notify_viewed'); $user->notify_paid = Input::get('notify_paid'); + $user->notify_approved = Input::get('notify_approved'); $user->save(); Session::flash('message', trans('texts.updated_settings')); @@ -675,7 +677,7 @@ class AccountController extends BaseController $user->last_name = trim(Input::get('new_last_name')); $user->email = trim(strtolower(Input::get('new_email'))); $user->username = $user->email; - $user->password = trim(Input::get('new_password')); + $user->password = bcrypt(trim(Input::get('new_password'))); $user->registered = true; $user->save(); @@ -738,4 +740,12 @@ class AccountController extends BaseController return Redirect::to('/')->with('clearGuestKey', true); } + + public function resendConfirmation() + { + $user = Auth::user(); + $this->userMailer->sendConfirmation($user); + + return Redirect::to('/company/details')->with('message', trans('texts.confirmation_resent')); + } } diff --git a/app/Http/Controllers/AccountGatewayController.php b/app/Http/Controllers/AccountGatewayController.php index 489b2fb2d65c..29002f8727b1 100644 --- a/app/Http/Controllers/AccountGatewayController.php +++ b/app/Http/Controllers/AccountGatewayController.php @@ -25,10 +25,11 @@ class AccountGatewayController extends BaseController ->join('gateways', 'gateways.id', '=', 'account_gateways.gateway_id') ->where('account_gateways.deleted_at', '=', null) ->where('account_gateways.account_id', '=', Auth::user()->account_id) - ->select('account_gateways.public_id', 'gateways.name', 'account_gateways.deleted_at'); + ->select('account_gateways.public_id', 'gateways.name', 'account_gateways.deleted_at', 'account_gateways.gateway_id'); return Datatable::query($query) ->addColumn('name', function ($model) { return link_to('gateways/'.$model->public_id.'/edit', $model->name); }) + ->addColumn('payment_type', function ($model) { return Gateway::getPrettyPaymentType($model->gateway_id); }) ->addColumn('dropdown', function ($model) { $actions = '
', $response->getMessage()); + return Redirect::to('view/'.$invitationKey); } } + + if ($response->isSuccessful()) { + $payment = self::createPayment($invitation, $ref); + Session::flash('message', trans('texts.applied_payment')); + + return Redirect::to('view/'.$payment->invitation->invitation_key); + } elseif ($response->isRedirect()) { + $invitation->transaction_reference = $ref; + $invitation->save(); + + Session::save(); + $response->redirect(); + } else { + Session::flash('error', $response->getMessage()); + + return Utils::fatalError('Sorry, there was an error processing your payment. Please try again later.
', $response->getMessage()); + } } catch (\Exception $e) { $errorMessage = trans('texts.payment_error'); Session::flash('error', $errorMessage."
".$e->getMessage()); @@ -663,12 +603,17 @@ class PaymentController extends BaseController $payment->invitation_id = $invitation->id; $payment->account_gateway_id = $accountGateway->id; $payment->invoice_id = $invoice->id; - $payment->amount = $invoice->balance; + $payment->amount = $invoice->getRequestedAmount(); $payment->client_id = $invoice->client_id; $payment->contact_id = $invitation->contact_id; $payment->transaction_reference = $ref; $payment->payment_date = date_create()->format('Y-m-d'); + if ($invoice->partial) { + $invoice->partial = 0; + $invoice->save(); + } + if ($payerId) { $payment->payer_id = $payerId; } diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index 3bee3b0c304a..039adc0b5643 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -6,7 +6,8 @@ use Redirect; use Utils; use View; use Cache; - +use Event; +use Session; use App\Models\Account; use App\Models\Client; use App\Models\Country; @@ -17,10 +18,13 @@ use App\Models\PaymentTerm; use App\Models\Product; use App\Models\Size; use App\Models\TaxRate; +use App\Models\Invitation; +use App\Models\Activity; use App\Ninja\Mailers\ContactMailer as Mailer; use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\TaxRateRepository; +use App\Events\QuoteApproved; class QuoteController extends BaseController { @@ -187,7 +191,9 @@ class QuoteController extends BaseController $invoice = $invitation->invoice; if ($invoice->is_quote && !$invoice->quote_invoice_id) { - Activity::approveQuote($invitation); + Event::fire(new QuoteApproved($invoice)); + Activity::approveQuote($invitation); + $invoice = $this->invoiceRepo->cloneInvoice($invoice, $invoice->id); Session::flash('message', trans('texts.converted_to_invoice')); diff --git a/app/Http/Controllers/TokenController.php b/app/Http/Controllers/TokenController.php index f84411a60243..3b9d546849ab 100644 --- a/app/Http/Controllers/TokenController.php +++ b/app/Http/Controllers/TokenController.php @@ -93,11 +93,6 @@ class TokenController extends BaseController */ public function create() { - if (!Auth::user()->confirmed) { - Session::flash('error', trans('texts.register_to_add_user')); - return Redirect::to('company/advanced_settings/user_management'); - } - $data = [ 'showBreadcrumbs' => false, 'token' => null, diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index e4d3d512bfa1..19f3708fee64 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -336,7 +336,7 @@ class UserController extends BaseController // save the new password $user = Auth::user(); - $user->password = $password; + $user->password = bcrypt($password); $user->save(); return RESULT_SUCCESS; diff --git a/app/Http/Middleware/StartupCheck.php b/app/Http/Middleware/StartupCheck.php index 361177721b25..50eebe347811 100644 --- a/app/Http/Middleware/StartupCheck.php +++ b/app/Http/Middleware/StartupCheck.php @@ -9,6 +9,7 @@ use Redirect; use Cache; use Session; use Event; +use App\Models\Language; use App\Events\UserSettingsChanged; class StartupCheck @@ -48,14 +49,13 @@ class StartupCheck ]; foreach ($cachedTables as $name => $class) { if (!Cache::has($name)) { - $orderBy = 'id'; - if ($name == 'paymentTerms') { $orderBy = 'num_days'; - } elseif (property_exists($class, 'name') && $name != 'paymentTypes') { + } elseif (in_array($name, ['currencies', 'sizes', 'industries', 'languages'])) { $orderBy = 'name'; + } else { + $orderBy = 'id'; } - Cache::forever($name, $class::orderBy($orderBy)->get()); } } diff --git a/app/Http/routes.php b/app/Http/routes.php index d090b43ec94a..5499f2c3537a 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -36,7 +36,7 @@ Route::post('get_started', 'AccountController@getStarted'); // Client visible pages Route::get('view/{invitation_key}', 'InvoiceController@view'); Route::get('approve/{invitation_key}', 'QuoteController@approve'); -Route::get('payment/{invitation_key}', 'PaymentController@show_payment'); +Route::get('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_payment'); Route::post('payment/{invitation_key}', 'PaymentController@do_payment'); Route::get('complete', 'PaymentController@offsite_payment'); Route::get('client/quotes', 'QuoteController@clientIndex'); @@ -71,7 +71,7 @@ get('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEma post('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail')); get('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset')); post('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset')); -get('user/confirm/{code}', 'UserController@confirm'); +get('/user/confirm/{code}', 'UserController@confirm'); /* // Confide routes @@ -167,6 +167,7 @@ Route::group(['middleware' => 'auth'], function() { Route::get('api/credits/{client_id?}', array('as'=>'api.credits', 'uses'=>'CreditController@getDatatable')); Route::post('credits/bulk', 'CreditController@bulk'); + get('/resend_confirmation', 'AccountController@resendConfirmation'); //Route::resource('timesheets', 'TimesheetController'); }); @@ -182,6 +183,33 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function() Route::post('email_invoice', 'InvoiceApiController@emailInvoice'); }); +// Redirects for legacy links +Route::get('/rocksteady', function() { + return Redirect::to(NINJA_WEB_URL, 301); +}); +Route::get('/about', function() { + return Redirect::to(NINJA_WEB_URL, 301); +}); +Route::get('/contact', function() { + return Redirect::to(NINJA_WEB_URL.'/contact', 301); +}); +Route::get('/plans', function() { + return Redirect::to(NINJA_WEB_URL.'/pricing', 301); +}); +Route::get('/faq', function() { + return Redirect::to(NINJA_WEB_URL.'/how-it-works', 301); +}); +Route::get('/features', function() { + return Redirect::to(NINJA_WEB_URL.'/features', 301); +}); +Route::get('/testimonials', function() { + return Redirect::to(NINJA_WEB_URL, 301); +}); +Route::get('/compare-online-invoicing{sites?}', function() { + return Redirect::to(NINJA_WEB_URL, 301); +}); + + define('CONTACT_EMAIL', Config::get('mail.from.address')); define('CONTACT_NAME', Config::get('mail.from.name')); define('SITE_URL', Config::get('app.url')); @@ -308,6 +336,7 @@ define('GATEWAY_TWO_CHECKOUT', 27); define('GATEWAY_BEANSTREAM', 29); define('GATEWAY_PSIGATE', 30); define('GATEWAY_MOOLAH', 31); +define('GATEWAY_BITPAY', 42); define('EVENT_CREATE_CLIENT', 1); define('EVENT_CREATE_INVOICE', 2); @@ -321,7 +350,7 @@ define('NINJA_GATEWAY_ID', GATEWAY_AUTHORIZE_NET); define('NINJA_GATEWAY_CONFIG', '{"apiLoginId":"626vWcD5","transactionKey":"4bn26TgL9r4Br4qJ","testMode":"","developerMode":""}'); define('NINJA_WEB_URL', 'https://www.invoiceninja.com'); define('NINJA_APP_URL', 'https://app.invoiceninja.com'); -define('NINJA_VERSION', '2.0'); +define('NINJA_VERSION', '1.7.2'); define('NINJA_DATE', '2000-01-01'); define('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com'); define('RELEASES_URL', 'https://github.com/hillelcoren/invoice-ninja/releases/'); @@ -350,6 +379,8 @@ define('TOKEN_BILLING_ALWAYS', 4); define('PAYMENT_TYPE_PAYPAL', 'PAYMENT_TYPE_PAYPAL'); define('PAYMENT_TYPE_CREDIT_CARD', 'PAYMENT_TYPE_CREDIT_CARD'); +define('PAYMENT_TYPE_BITCOIN', 'PAYMENT_TYPE_BITCOIN'); +define('PAYMENT_TYPE_TOKEN', 'PAYMENT_TYPE_TOKEN'); define('PAYMENT_TYPE_ANY', 'PAYMENT_TYPE_ANY'); /* diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index 33d799e4acb7..d6789909c4c5 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -165,6 +165,7 @@ class Utils 'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '', 'ip' => Request::getClientIp(), 'count' => Session::get('error_count', 0), + //'input' => Input::all() ]; Log::error($error."\n", $data); diff --git a/app/Listeners/HandleInvoicePaid.php b/app/Listeners/HandleInvoicePaid.php index 159b001abad4..d072abd00357 100644 --- a/app/Listeners/HandleInvoicePaid.php +++ b/app/Listeners/HandleInvoicePaid.php @@ -31,8 +31,10 @@ class HandleInvoicePaid { */ public function handle(InvoicePaid $event) { - $this->contactMailer->sendPaymentConfirmation($payment); + $payment = $event->payment; $invoice = $payment->invoice; + + $this->contactMailer->sendPaymentConfirmation($payment); foreach ($invoice->account->users as $user) { diff --git a/app/Listeners/HandleQuoteApproved.php b/app/Listeners/HandleQuoteApproved.php new file mode 100644 index 000000000000..3a49aa9b5af5 --- /dev/null +++ b/app/Listeners/HandleQuoteApproved.php @@ -0,0 +1,42 @@ +userMailer = $userMailer; + } + + /** + * Handle the event. + * + * @param QuoteApproved $event + * @return void + */ + public function handle(QuoteApproved $event) + { + $invoice = $event->invoice; + + foreach ($invoice->account->users as $user) + { + if ($user->{'notify_approved'}) + { + $this->userMailer->sendNotification($user, $invoice, 'approved'); + } + } + } + +} diff --git a/app/Models/Account.php b/app/Models/Account.php index ff896e30779e..4dadd3d521c3 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -113,9 +113,7 @@ class Account extends Eloquent foreach ($this->account_gateways as $gateway) { if (!$type || $type == PAYMENT_TYPE_ANY) { return $gateway; - } elseif ($gateway->isPayPal() && $type == PAYMENT_TYPE_PAYPAL) { - return $gateway; - } elseif (!$gateway->isPayPal() && $type == PAYMENT_TYPE_CREDIT_CARD) { + } elseif ($gateway->isPaymentType($type)) { return $gateway; } } @@ -230,6 +228,7 @@ class Account extends Eloquent 'subtotal', 'paid_to_date', 'balance_due', + 'amount_due', 'terms', 'your_invoice', 'quote', diff --git a/app/Models/AccountGateway.php b/app/Models/AccountGateway.php index c7f008fb67f8..62c3456473ce 100644 --- a/app/Models/AccountGateway.php +++ b/app/Models/AccountGateway.php @@ -1,5 +1,6 @@ gateway_id == GATEWAY_PAYPAL_EXPRESS; + public function getPaymentType() { + return Gateway::getPaymentType($this->gateway_id); + } + + public function isPaymentType($type) { + return $this->getPaymentType() == $type; } } diff --git a/app/Models/AccountGatewayToken.php b/app/Models/AccountGatewayToken.php index 7726df1301f0..b706882af77d 100644 --- a/app/Models/AccountGatewayToken.php +++ b/app/Models/AccountGatewayToken.php @@ -1,5 +1,6 @@ amount) - floatval($invoice->getOriginal('amount')); $fieldChanged = false; - foreach (['invoice_number', 'po_number', 'invoice_date', 'due_date', 'terms', 'public_notes', 'invoice_footer'] as $field) { + foreach (['invoice_number', 'po_number', 'invoice_date', 'due_date', 'terms', 'public_notes', 'invoice_footer', 'partial'] as $field) { if ($invoice->$field != $invoice->getOriginal($field)) { $fieldChanged = true; break; diff --git a/app/Models/Affiliate.php b/app/Models/Affiliate.php index f88b96c3fc1e..63fcb71ce357 100644 --- a/app/Models/Affiliate.php +++ b/app/Models/Affiliate.php @@ -1,5 +1,7 @@ belongsTo('\App\Models\PaymentLibrary', 'payment_library_id'); - } - public function getLogoUrl() { return '/images/gateways/logo_'.$this->provider.'.png'; @@ -37,18 +32,20 @@ class Gateway extends Eloquent public function getFields() { - $paymentLibrary = $this->paymentlibrary; + return Omnipay::create($this->provider)->getDefaultParameters(); + } - if ($paymentLibrary->id == PAYMENT_LIBRARY_OMNIPAY) { - $fields = Omnipay::create($this->provider)->getDefaultParameters(); + public static function getPaymentType($gatewayId) { + if ($gatewayId == GATEWAY_PAYPAL_EXPRESS) { + return PAYMENT_TYPE_PAYPAL; + } else if ($gatewayId == GATEWAY_BITPAY) { + return PAYMENT_TYPE_BITCOIN; } else { - $fields = Payment_Utility::load('config', 'drivers/'.strtolower($this->provider)); + return PAYMENT_TYPE_CREDIT_CARD; } + } - if ($fields == null) { - $fields = array(); - } - - return $fields; + public static function getPrettyPaymentType($gatewayId) { + return trans('texts.' . strtolower(Gateway::getPaymentType($gatewayId))); } } diff --git a/app/Models/Invitation.php b/app/Models/Invitation.php index 58aedfb5cd5b..d55a16d25a99 100644 --- a/app/Models/Invitation.php +++ b/app/Models/Invitation.php @@ -9,7 +9,7 @@ class Invitation extends EntityModel public function invoice() { - return $this->belongsTo('App\Models\Invoice'); + return $this->belongsTo('App\Models\Invoice')->withTrashed(); } public function contact() diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 294a4eef1ba3..e457732dcfff 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -72,6 +72,11 @@ class Invoice extends EntityModel return $this->invoice_status_id >= INVOICE_STATUS_PAID; } + public function getRequestedAmount() + { + return $this->partial > 0 ? $this->partial : $this->balance; + } + public function hidePrivateFields() { $this->setVisible([ @@ -99,6 +104,7 @@ class Invoice extends EntityModel 'custom_value2', 'custom_taxes1', 'custom_taxes2', + 'partial', ]); $this->client->setVisible([ @@ -236,8 +242,6 @@ Invoice::deleting(function ($invoice) { Activity::archiveInvoice($invoice); }); -// TODO: Fix for L5 -/*Invoice::restoring(function ($invoice) { +Invoice::restoring(function ($invoice) { Activity::restoreInvoice($invoice); }); -*/ \ No newline at end of file diff --git a/app/Models/License.php b/app/Models/License.php index dff6f25b409a..93898f8d7d18 100644 --- a/app/Models/License.php +++ b/app/Models/License.php @@ -1,5 +1,6 @@ $invoice->invoice_number, 'account' => $invoice->account->getDisplayName()]); $accountName = $invoice->account->getDisplayName(); $emailTemplate = $invoice->account->getEmailTemplate($entityType); - $invoiceAmount = Utils::formatMoney($invoice->amount, $invoice->client->currency_id); + $invoiceAmount = Utils::formatMoney($invoice->getRequestedAmount(), $invoice->client->currency_id); foreach ($invoice->invitations as $invitation) { if (!$invitation->user || !$invitation->user->email) { diff --git a/app/Ninja/Mailers/UserMailer.php b/app/Ninja/Mailers/UserMailer.php index ad82923b7727..5831e66b5cdc 100644 --- a/app/Ninja/Mailers/UserMailer.php +++ b/app/Ninja/Mailers/UserMailer.php @@ -39,8 +39,8 @@ class UserMailer extends Mailer return; } - $view = 'invoice_'.$notificationType; $entityType = $invoice->getEntityType(); + $view = "{$entityType}_{$notificationType}"; $data = [ 'entityType' => $entityType, diff --git a/app/Ninja/Repositories/ClientRepository.php b/app/Ninja/Repositories/ClientRepository.php index d4569bea4b47..2a5276787723 100644 --- a/app/Ninja/Repositories/ClientRepository.php +++ b/app/Ninja/Repositories/ClientRepository.php @@ -34,7 +34,10 @@ class ClientRepository public function getErrors($data) { $contact = isset($data['contacts']) ? (array) $data['contacts'][0] : (isset($data['contact']) ? $data['contact'] : []); - $validator = \Validator::make($contact, ['email' => 'required|email']); + $validator = \Validator::make($contact, [ + 'email' => 'email|required_without:first_name', + 'first_name' => 'required_without:email', + ]); if ($validator->fails()) { return $validator->messages(); } diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index 5bfbcdd7c187..af0857989321 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -19,7 +19,7 @@ class InvoiceRepository ->where('contacts.deleted_at', '=', null) ->where('invoices.is_recurring', '=', false) ->where('contacts.is_primary', '=', true) - ->select('clients.public_id as client_public_id', 'invoice_number', 'invoice_status_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'invoices.balance', 'invoice_date', 'due_date', 'invoice_statuses.name as invoice_status_name', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'quote_id', 'quote_invoice_id', 'invoices.deleted_at', 'invoices.is_deleted'); + ->select('clients.public_id as client_public_id', 'invoice_number', 'invoice_status_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'invoices.balance', 'invoice_date', 'due_date', 'invoice_statuses.name as invoice_status_name', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'quote_id', 'quote_invoice_id', 'invoices.deleted_at', 'invoices.is_deleted', 'invoices.partial'); if (!\Session::get('show_trash:'.$entityType)) { $query->where('invoices.deleted_at', '=', null); @@ -86,7 +86,7 @@ class InvoiceRepository ->where('invoices.is_deleted', '=', false) ->where('clients.deleted_at', '=', null) ->where('invoices.is_recurring', '=', false) - ->select('invitation_key', 'invoice_number', 'invoice_date', 'invoices.balance as balance', 'due_date', 'clients.public_id as client_public_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'start_date', 'end_date', 'clients.currency_id'); + ->select('invitation_key', 'invoice_number', 'invoice_date', 'invoices.balance as balance', 'due_date', 'clients.public_id as client_public_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'start_date', 'end_date', 'clients.currency_id', 'invoices.partial'); $table = \Datatable::query($query) ->addColumn('invoice_number', function ($model) use ($entityType) { return link_to('/view/'.$model->invitation_key, $model->invoice_number); }) @@ -94,7 +94,11 @@ class InvoiceRepository ->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); }); if ($entityType == ENTITY_INVOICE) { - $table->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); }); + $table->addColumn('balance', function ($model) { + return $model->partial > 0 ? + trans('texts.partial_remaining', ['partial' => Utils::formatMoney($model->partial, $model->currency_id), 'balance' => Utils::formatMoney($model->balance, $model->currency_id)]) : + Utils::formatMoney($model->balance, $model->currency_id); + }); } return $table->addColumn('due_date', function ($model) { return Utils::fromSqlDate($model->due_date); }) @@ -122,7 +126,11 @@ class InvoiceRepository ->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); }); if ($entityType == ENTITY_INVOICE) { - $table->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); }); + $table->addColumn('balance', function ($model) { + return $model->partial > 0 ? + trans('texts.partial_remaining', ['partial' => Utils::formatMoney($model->partial, $model->currency_id), 'balance' => Utils::formatMoney($model->balance, $model->currency_id)]) : + Utils::formatMoney($model->balance, $model->currency_id); + }); } return $table->addColumn('due_date', function ($model) { return Utils::fromSqlDate($model->due_date); }) @@ -182,7 +190,10 @@ class InvoiceRepository public function getErrors($input) { $contact = (array) $input->client->contacts[0]; - $rules = ['email' => 'required|email']; + $rules = [ + 'email' => 'email|required_without:first_name', + 'first_name' => 'required_without:email', + ]; $validator = \Validator::make($contact, $rules); if ($validator->fails()) { @@ -223,10 +234,22 @@ class InvoiceRepository $account = \Auth::user()->account; + if ((isset($data['set_default_terms']) && $data['set_default_terms']) + || (isset($data['set_default_footer']) && $data['set_default_footer'])) { + if (isset($data['set_default_terms']) && $data['set_default_terms']) { + $account->invoice_terms = trim($data['terms']); + } + if (isset($data['set_default_footer']) && $data['set_default_footer']) { + $account->invoice_footer = trim($data['invoice_footer']); + } + $account->save(); + } + $invoice->client_id = $data['client_id']; $invoice->discount = round(Utils::parseFloat($data['discount']), 2); $invoice->is_amount_discount = $data['is_amount_discount'] ? true : false; $invoice->invoice_number = trim($data['invoice_number']); + $invoice->partial = round(Utils::parseFloat($data['partial']), 2); $invoice->is_recurring = $data['is_recurring'] && !Utils::isDemo() ? true : false; $invoice->invoice_date = isset($data['invoice_date_sql']) ? $data['invoice_date_sql'] : Utils::toSqlDate($data['invoice_date']); @@ -360,17 +383,6 @@ class InvoiceRepository $invoice->invoice_items()->save($invoiceItem); } - if ((isset($data['set_default_terms']) && $data['set_default_terms']) - || (isset($data['set_default_footer']) && $data['set_default_footer'])) { - if (isset($data['set_default_terms']) && $data['set_default_terms']) { - $account->invoice_terms = trim($data['terms']); - } - if (isset($data['set_default_footer']) && $data['set_default_footer']) { - $account->invoice_footer = trim($data['invoice_footer']); - } - $account->save(); - } - return $invoice; } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index e5fd505a007a..3ce33b355412 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -26,6 +26,9 @@ class EventServiceProvider extends ServiceProvider { 'App\Events\InvoicePaid' => [ 'App\Listeners\HandleInvoicePaid', ], + 'App\Events\QuoteApproved' => [ + 'App\Listeners\HandleQuoteApproved', + ], ]; /** diff --git a/composer.json b/composer.json index cacff91bee1d..08c769297bbb 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,8 @@ "coatesap/omnipay-realex": "~2.0", "fruitcakestudio/omnipay-sisow": "~2.0", "alfaproject/omnipay-skrill": "dev-master", - "illuminate/html": "5.*" + "illuminate/html": "5.*", + "omnipay/bitpay": "dev-master" }, "require-dev": { "phpunit/phpunit": "~4.0", diff --git a/composer.lock b/composer.lock index f4e80c72ccd6..d1ea10533e32 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "f464570c808999ffcb0fa78193b13229", + "hash": "4093891914bbd46ffab78737da36898a", "packages": [ { "name": "alfaproject/omnipay-neteller", @@ -335,12 +335,12 @@ "source": { "type": "git", "url": "https://github.com/Chumper/Datatable.git", - "reference": "58f212270a4bd37ebf1371cb5302749cbbcb941d" + "reference": "bdd90cbe65b3544761f69ea199a1b9591770f3fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Chumper/Datatable/zipball/58f212270a4bd37ebf1371cb5302749cbbcb941d", - "reference": "58f212270a4bd37ebf1371cb5302749cbbcb941d", + "url": "https://api.github.com/repos/Chumper/Datatable/zipball/bdd90cbe65b3544761f69ea199a1b9591770f3fc", + "reference": "bdd90cbe65b3544761f69ea199a1b9591770f3fc", "shasum": "" }, "require": { @@ -380,7 +380,7 @@ "jquery", "laravel" ], - "time": "2015-04-04 15:19:43" + "time": "2015-04-08 12:11:23" }, { "name": "classpreloader/classpreloader", @@ -1374,12 +1374,12 @@ "source": { "type": "git", "url": "https://github.com/Intervention/image.git", - "reference": "8f3761154b4c1f5128365dfc32cbf757f27d97b6" + "reference": "7edc830d19733a40afae5641aabf0acf6be1961b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/8f3761154b4c1f5128365dfc32cbf757f27d97b6", - "reference": "8f3761154b4c1f5128365dfc32cbf757f27d97b6", + "url": "https://api.github.com/repos/Intervention/image/zipball/7edc830d19733a40afae5641aabf0acf6be1961b", + "reference": "7edc830d19733a40afae5641aabf0acf6be1961b", "shasum": "" }, "require": { @@ -1422,7 +1422,7 @@ "thumbnail", "watermark" ], - "time": "2015-03-18 17:41:04" + "time": "2015-04-08 17:43:59" }, { "name": "ircmaxell/password-compat", @@ -2346,6 +2346,64 @@ ], "time": "2015-01-19 19:06:04" }, + { + "name": "omnipay/bitpay", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/omnipay-bitpay.git", + "reference": "e659f0e993c586cb36acafaf50835570b4a16eb2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/omnipay-bitpay/zipball/e659f0e993c586cb36acafaf50835570b4a16eb2", + "reference": "e659f0e993c586cb36acafaf50835570b4a16eb2", + "shasum": "" + }, + "require": { + "omnipay/common": "~2.0" + }, + "require-dev": { + "omnipay/tests": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Omnipay\\BitPay\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Adrian Macneil", + "email": "adrian@adrianmacneil.com" + }, + { + "name": "Omnipay Contributors", + "homepage": "https://github.com/thephpleague/omnipay-bitpay/contributors" + } + ], + "description": "BitPay driver for the Omnipay payment processing library", + "homepage": "https://github.com/thephpleague/omnipay-bitpay", + "keywords": [ + "bitcoin", + "bitpay", + "gateway", + "merchant", + "omnipay", + "pay", + "payment" + ], + "time": "2015-03-23 14:18:26" + }, { "name": "omnipay/buckaroo", "version": "v2.0.1", @@ -2981,16 +3039,16 @@ }, { "name": "omnipay/mollie", - "version": "3.0.3", + "version": "v3.0.4", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-mollie.git", - "reference": "ea3830b0aaa845430387e5afc1f638be18a152ec" + "reference": "a89cb0d15447023b24c03f86873c1c1489cd021b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-mollie/zipball/ea3830b0aaa845430387e5afc1f638be18a152ec", - "reference": "ea3830b0aaa845430387e5afc1f638be18a152ec", + "url": "https://api.github.com/repos/thephpleague/omnipay-mollie/zipball/a89cb0d15447023b24c03f86873c1c1489cd021b", + "reference": "a89cb0d15447023b24c03f86873c1c1489cd021b", "shasum": "" }, "require": { @@ -3034,7 +3092,7 @@ "pay", "payment" ], - "time": "2014-10-15 14:25:03" + "time": "2015-03-03 18:55:42" }, { "name": "omnipay/multisafepay", @@ -3730,16 +3788,16 @@ }, { "name": "omnipay/stripe", - "version": "v2.2.0", + "version": "v2.2.1", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-stripe.git", - "reference": "b3ed1028bb72837905012311fa74259d9ed8ba3c" + "reference": "906377e50045dc2ba9c612aa1f6924157e1f750e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-stripe/zipball/b3ed1028bb72837905012311fa74259d9ed8ba3c", - "reference": "b3ed1028bb72837905012311fa74259d9ed8ba3c", + "url": "https://api.github.com/repos/thephpleague/omnipay-stripe/zipball/906377e50045dc2ba9c612aa1f6924157e1f750e", + "reference": "906377e50045dc2ba9c612aa1f6924157e1f750e", "shasum": "" }, "require": { @@ -3783,7 +3841,7 @@ "payment", "stripe" ], - "time": "2015-03-16 19:24:07" + "time": "2015-04-14 18:55:56" }, { "name": "omnipay/targetpay", @@ -5313,16 +5371,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "2.0.15", + "version": "2.0.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "34cc484af1ca149188d0d9e91412191e398e0b67" + "reference": "934fd03eb6840508231a7f73eb8940cf32c3b66c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/34cc484af1ca149188d0d9e91412191e398e0b67", - "reference": "34cc484af1ca149188d0d9e91412191e398e0b67", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/934fd03eb6840508231a7f73eb8940cf32c3b66c", + "reference": "934fd03eb6840508231a7f73eb8940cf32c3b66c", "shasum": "" }, "require": { @@ -5371,7 +5429,7 @@ "testing", "xunit" ], - "time": "2015-01-24 10:06:35" + "time": "2015-04-11 04:35:00" }, { "name": "phpunit/php-file-iterator", @@ -5510,16 +5568,16 @@ }, { "name": "phpunit/php-token-stream", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "db32c18eba00b121c145575fcbcd4d4d24e6db74" + "reference": "eab81d02569310739373308137284e0158424330" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/db32c18eba00b121c145575fcbcd4d4d24e6db74", - "reference": "db32c18eba00b121c145575fcbcd4d4d24e6db74", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/eab81d02569310739373308137284e0158424330", + "reference": "eab81d02569310739373308137284e0158424330", "shasum": "" }, "require": { @@ -5555,20 +5613,20 @@ "keywords": [ "tokenizer" ], - "time": "2015-01-17 09:51:32" + "time": "2015-04-08 04:46:07" }, { "name": "phpunit/phpunit", - "version": "4.6.1", + "version": "4.6.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "08b2aacdd8433abbba468f995d6d64b76a7a62ec" + "reference": "163232991e652e6efed2f8470326fffa61e848e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/08b2aacdd8433abbba468f995d6d64b76a7a62ec", - "reference": "08b2aacdd8433abbba468f995d6d64b76a7a62ec", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/163232991e652e6efed2f8470326fffa61e848e2", + "reference": "163232991e652e6efed2f8470326fffa61e848e2", "shasum": "" }, "require": { @@ -5607,9 +5665,6 @@ "autoload": { "classmap": [ "src/" - ], - "files": [ - "src/Framework/Assert/Functions.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -5630,7 +5685,7 @@ "testing", "xunit" ], - "time": "2015-04-03 13:46:59" + "time": "2015-04-11 05:23:21" }, { "name": "phpunit/phpunit-mock-objects", @@ -6118,7 +6173,8 @@ "webpatser/laravel-countries": 20, "lokielse/omnipay-alipay": 20, "alfaproject/omnipay-neteller": 20, - "alfaproject/omnipay-skrill": 20 + "alfaproject/omnipay-skrill": 20, + "omnipay/bitpay": 20 }, "prefer-stable": false, "prefer-lowest": false, diff --git a/config/app.php b/config/app.php index 9a72079f9e25..a8dd21bff24c 100644 --- a/config/app.php +++ b/config/app.php @@ -13,7 +13,7 @@ return [ | */ - 'debug' => true, + 'debug' => env('APP_DEBUG', ''), /* |-------------------------------------------------------------------------- @@ -80,7 +80,7 @@ return [ 'key' => env('APP_KEY', ''), - 'cipher' => MCRYPT_RIJNDAEL_128, + 'cipher' => env('APP_CIPHER', MCRYPT_RIJNDAEL_128), /* |-------------------------------------------------------------------------- diff --git a/database/migrations/2014_10_01_141248_add_company_vat_number.php b/database/migrations/2014_10_01_141248_add_company_vat_number.php index b93a217712ed..4222b2ee15fd 100644 --- a/database/migrations/2014_10_01_141248_add_company_vat_number.php +++ b/database/migrations/2014_10_01_141248_add_company_vat_number.php @@ -17,7 +17,7 @@ class AddCompanyVatNumber extends Migration { $table->string('vat_number')->nullable(); }); - Schema::table('clients', function($table) + Schema::table('clients', function($table) { $table->string('vat_number')->nullable(); }); @@ -34,7 +34,8 @@ class AddCompanyVatNumber extends Migration { { $table->dropColumn('vat_number'); }); - Schema::table('clients', function($table) + + Schema::table('clients', function($table) { $table->dropColumn('vat_number'); }); diff --git a/database/migrations/2015_04_12_093447_add_sv_language.php b/database/migrations/2015_04_12_093447_add_sv_language.php index 5b327827cf92..2228b6b3e94e 100644 --- a/database/migrations/2015_04_12_093447_add_sv_language.php +++ b/database/migrations/2015_04_12_093447_add_sv_language.php @@ -12,7 +12,8 @@ class AddSvLanguage extends Migration { */ public function up() { - DB::table('languages')->insert(['name' => 'Swedish', 'locale' => 'sv']); + DB::table('languages')->insert(['name' => 'Swedish', 'locale' => 'sv']); + DB::table('languages')->insert(['name' => 'Spanish - Spain', 'locale' => 'es_ES']); } /** @@ -22,8 +23,13 @@ class AddSvLanguage extends Migration { */ public function down() { - $language = \App\Models\Language::whereLocale('sv')->first(); - $language->delete(); + if ($language = \App\Models\Language::whereLocale('sv')->first()) { + $language->delete(); + } + + if ($language = \App\Models\Language::whereLocale('es_ES')->first()) { + $language->delete(); + } } } diff --git a/database/migrations/2015_04_13_100333_add_notify_approved.php b/database/migrations/2015_04_13_100333_add_notify_approved.php new file mode 100644 index 000000000000..8f0a4fd74060 --- /dev/null +++ b/database/migrations/2015_04_13_100333_add_notify_approved.php @@ -0,0 +1,34 @@ +boolean('notify_approved')->default(true); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function($table) + { + $table->dropColumn('notify_approved'); + }); + } + +} diff --git a/database/migrations/2015_04_16_122647_add_partial_amount_to_invoices.php b/database/migrations/2015_04_16_122647_add_partial_amount_to_invoices.php new file mode 100644 index 000000000000..eed323094502 --- /dev/null +++ b/database/migrations/2015_04_16_122647_add_partial_amount_to_invoices.php @@ -0,0 +1,34 @@ +decimal('partial', 13, 2)->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('invoices', function($table) + { + $table->dropColumn('partial'); + }); + } + +} diff --git a/database/seeds/PaymentLibrariesSeeder.php b/database/seeds/PaymentLibrariesSeeder.php index d676ea97058a..73b8090c73be 100644 --- a/database/seeds/PaymentLibrariesSeeder.php +++ b/database/seeds/PaymentLibrariesSeeder.php @@ -22,7 +22,8 @@ class PaymentLibrariesSeeder extends Seeder ['name' => 'PaymentSense', 'provider' => 'PaymentSense', 'payment_library_id' => 1], ['name' => 'Realex', 'provider' => 'Realex_Remote', 'payment_library_id' => 1], ['name' => 'Sisow', 'provider' => 'Sisow', 'payment_library_id' => 1], - ['name' => 'Skrill', 'provider' => 'Skrill', 'payment_library_id' => 1] + ['name' => 'Skrill', 'provider' => 'Skrill', 'payment_library_id' => 1], + ['name' => 'BitPay', 'provider' => 'BitPay', 'payment_library_id' => 1], ]; foreach ($gateways as $gateway) diff --git a/public/css/built.css b/public/css/built.css index f3ee1192a763..bc5b107ccf87 100644 --- a/public/css/built.css +++ b/public/css/built.css @@ -2863,6 +2863,7 @@ body.modal-open { overflow:inherit; padding-right:inherit !important; } .radio input[type="radio"], .checkbox input[type="checkbox"] { margin-left: 0; + padding-left: 0px !important; margin-right: 5px; height: inherit; width: inherit; @@ -2871,3 +2872,7 @@ body.modal-open { overflow:inherit; padding-right:inherit !important; } position: relative; margin-top: 3px; } + +div.checkbox > label { + padding-left: 0px !important; +} diff --git a/public/css/style.css b/public/css/style.css index 963e607370c6..05e67cd404b3 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -792,6 +792,7 @@ body.modal-open { overflow:inherit; padding-right:inherit !important; } .radio input[type="radio"], .checkbox input[type="checkbox"] { margin-left: 0; + padding-left: 0px !important; margin-right: 5px; height: inherit; width: inherit; @@ -800,3 +801,7 @@ body.modal-open { overflow:inherit; padding-right:inherit !important; } position: relative; margin-top: 3px; } + +div.checkbox > label { + padding-left: 0px !important; +} \ No newline at end of file diff --git a/public/js/built.js b/public/js/built.js index b7608fd24faa..f2489077e991 100644 --- a/public/js/built.js +++ b/public/js/built.js @@ -31896,6 +31896,12 @@ function GetPdf(invoice, javascript){ //set default style for report doc.setFont('Helvetica',''); + // For partial payments show "Amount Due" rather than "Balance Due" + if (!invoiceLabels.balance_due_orig) { + invoiceLabels.balance_due_orig = invoiceLabels.balance_due; + } + invoiceLabels.balance_due = NINJA.parseFloat(invoice.partial) ? invoiceLabels.amount_due : invoiceLabels.balance_due_orig; + eval(javascript); // add footer @@ -32528,13 +32534,20 @@ function displayInvoice(doc, invoice, x, y, layout, rightAlignX) { } function getInvoiceDetails(invoice) { - return [ + var fields = [ {'invoice_number': invoice.invoice_number}, {'po_number': invoice.po_number}, {'invoice_date': invoice.invoice_date}, {'due_date': invoice.due_date}, - {'balance_due': formatMoney(invoice.balance_amount, invoice.client.currency_id)}, ]; + + if (NINJA.parseFloat(invoice.partial)) { + fields.push({'total': formatMoney(invoice.total_amount, invoice.client.currency_id)}); + } + + fields.push({'balance_due': formatMoney(invoice.balance_amount, invoice.client.currency_id)}) + + return fields; } function getInvoiceDetailsHeight(invoice, layout) { @@ -32589,6 +32602,10 @@ function displaySubtotals(doc, layout, invoice, y, rightAlignTitleX) data.push({'paid_to_date': formatMoney(paid, invoice.client.currency_id)}); } + if (NINJA.parseFloat(invoice.partial) && invoice.total_amount != invoice.subtotal_amount) { + data.push({'total': formatMoney(invoice.total_amount, invoice.client.currency_id)}); + } + var options = { hasheader: true, rightAlignX: 550, @@ -32785,11 +32802,17 @@ function calculateAmounts(invoice) { total += roundToTwo(invoice.custom_value2); } - invoice.balance_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance)); + invoice.total_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance)); invoice.discount_amount = discount; invoice.tax_amount = tax; invoice.has_taxes = hasTaxes; + if (NINJA.parseFloat(invoice.partial)) { + invoice.balance_amount = roundToTwo(invoice.partial); + } else { + invoice.balance_amount = invoice.total_amount; + } + return invoice; } diff --git a/public/js/script.js b/public/js/script.js index 81913aa4c396..680231a07262 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -83,6 +83,12 @@ function GetPdf(invoice, javascript){ //set default style for report doc.setFont('Helvetica',''); + // For partial payments show "Amount Due" rather than "Balance Due" + if (!invoiceLabels.balance_due_orig) { + invoiceLabels.balance_due_orig = invoiceLabels.balance_due; + } + invoiceLabels.balance_due = NINJA.parseFloat(invoice.partial) ? invoiceLabels.amount_due : invoiceLabels.balance_due_orig; + eval(javascript); // add footer @@ -715,13 +721,20 @@ function displayInvoice(doc, invoice, x, y, layout, rightAlignX) { } function getInvoiceDetails(invoice) { - return [ + var fields = [ {'invoice_number': invoice.invoice_number}, {'po_number': invoice.po_number}, {'invoice_date': invoice.invoice_date}, {'due_date': invoice.due_date}, - {'balance_due': formatMoney(invoice.balance_amount, invoice.client.currency_id)}, ]; + + if (NINJA.parseFloat(invoice.partial)) { + fields.push({'total': formatMoney(invoice.total_amount, invoice.client.currency_id)}); + } + + fields.push({'balance_due': formatMoney(invoice.balance_amount, invoice.client.currency_id)}) + + return fields; } function getInvoiceDetailsHeight(invoice, layout) { @@ -776,6 +789,10 @@ function displaySubtotals(doc, layout, invoice, y, rightAlignTitleX) data.push({'paid_to_date': formatMoney(paid, invoice.client.currency_id)}); } + if (NINJA.parseFloat(invoice.partial) && invoice.total_amount != invoice.subtotal_amount) { + data.push({'total': formatMoney(invoice.total_amount, invoice.client.currency_id)}); + } + var options = { hasheader: true, rightAlignX: 550, @@ -972,11 +989,17 @@ function calculateAmounts(invoice) { total += roundToTwo(invoice.custom_value2); } - invoice.balance_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance)); + invoice.total_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance)); invoice.discount_amount = discount; invoice.tax_amount = tax; invoice.has_taxes = hasTaxes; + if (NINJA.parseFloat(invoice.partial)) { + invoice.balance_amount = roundToTwo(invoice.partial); + } else { + invoice.balance_amount = invoice.total_amount; + } + return invoice; } diff --git a/readme.md b/readme.md index 1ccf35513890..37236a85d6c3 100644 --- a/readme.md +++ b/readme.md @@ -3,7 +3,7 @@ ### [https://www.invoiceninja.com](https://www.invoiceninja.com) -#### Note: the application has recently been updated to Laravel 5. Please use the [1.7.x branch](https://github.com/hillelcoren/invoice-ninja/tree/1.7.x) for a more stable build. +##### Please [click here](https://bitnami.com/stack/invoice-ninja) to vote for us to be added to Bitnami's one-click install library If you'd like to use our code to sell your own invoicing app we have an affiliate program. Get in touch for more details. diff --git a/resources/lang/da/texts.php b/resources/lang/da/texts.php index 80cda729a834..59c05e563509 100644 --- a/resources/lang/da/texts.php +++ b/resources/lang/da/texts.php @@ -580,5 +580,19 @@ return array( 'send_email' => 'Send email', 'set_password' => 'Set Password', 'converted' => 'Converted', + + 'email_approved' => 'Email me when a quote is approved', + 'notification_quote_approved_subject' => 'Quote :invoice was approved by :client', + 'notification_quote_approved' => 'The following client :client approved Quote :invoice for :amount.', + 'resend_confirmation' => 'Resend confirmation email', + 'confirmation_resent' => 'The confirmation email was resent', + + 'gateway_help_42' => 'Note: use a BitPay Legacy API Key, not an API token.', + 'payment_type_credit_card' => 'Credit card', + 'payment_type_paypal' => 'PayPal', + 'payment_type_bitcoin' => 'Bitcoin', + 'knowledge_base' => 'Knowledge Base', + 'partial' => 'Partial', + 'partial_remaining' => ':partial of :balance', ); diff --git a/resources/lang/de/texts.php b/resources/lang/de/texts.php index cdb8b241bc4d..646ebf4af9e9 100644 --- a/resources/lang/de/texts.php +++ b/resources/lang/de/texts.php @@ -543,34 +543,48 @@ return array( 'delete_token' => 'Token löschen', 'token' => 'Token', - 'add_gateway' => 'Add Gateway', - 'delete_gateway' => 'Delete Gateway', - 'edit_gateway' => 'Edit Gateway', - 'updated_gateway' => 'Successfully updated gateway', - 'created_gateway' => 'Successfully created gateway', - 'deleted_gateway' => 'Successfully deleted gateway', + 'add_gateway' => 'Zahlungsanbieter hinzufügen', + 'delete_gateway' => 'Zahlungsanbieter löschen', + 'edit_gateway' => 'Zahlungsanbieter bearbeiten', + 'updated_gateway' => 'Zahlungsanbieter aktualisiert', + 'created_gateway' => 'Zahlungsanbieter erfolgreich hinzugefügt', + 'deleted_gateway' => 'Zahlungsanbieter erfolgreich gelöscht', 'pay_with_paypal' => 'PayPal', - 'pay_with_card' => 'Credit card', + 'pay_with_card' => 'Kreditkarte', 'change_password' => 'Passwort ändern', 'current_password' => 'Aktuelles Passwort', 'new_password' => 'Neues Passwort', 'confirm_password' => 'Passwort bestätigen', 'password_error_incorrect' => 'Das aktuelle Passwort ist nicht korrekt.', - 'password_error_invalid' => 'Das neue Kennwort ist ungültig.', + 'password_error_invalid' => 'Das neue Passwort ist ungültig.', 'updated_password' => 'Passwort erfolgreich aktualisiert', - 'api_tokens' => 'API Tokens', - 'users_and_tokens' => 'Users & Tokens', + 'api_tokens' => 'API Token', + 'users_and_tokens' => 'Benutzer & Token', 'account_login' => 'Account Login', - 'recover_password' => 'Recover your password', - 'forgot_password' => 'Forgot your password?', - 'email_address' => 'Email address', - 'lets_go' => 'Let’s go', - 'password_recovery' => 'Password Recovery', - 'send_email' => 'Send email', - 'set_password' => 'Set Password', - 'converted' => 'Converted', - + 'recover_password' => 'Passwort wiederherstellen', + 'forgot_password' => 'Passwort vergessen?', + 'email_address' => 'E-Mail-Adresse', + 'lets_go' => "Auf geht's", + 'password_recovery' => 'Passwort Wiederherstellung', + 'send_email' => 'E-Mail verschicken', + 'set_password' => 'Passwort festlegen', + 'converted' => 'Umgewandelt', -); + 'email_approved' => 'Per E-Mail benachrichtigen, wenn ein Angebot angenommen wurde', + 'notification_quote_approved_subject' => 'Angebot :invoice wurde von :client angenommen.', + 'notification_quote_approved' => 'Der folgende Kunde :client nahm das Angebot :invoice über :amount an.', + 'resend_confirmation' => 'Bestätigungsmail erneut senden', + 'confirmation_resent' => 'Bestätigungsemail wurde erneut gesendet', + + 'gateway_help_42' => 'Note: use a BitPay Legacy API Key, not an API token.', + 'payment_type_credit_card' => 'Credit card', + 'payment_type_paypal' => 'PayPal', + 'payment_type_bitcoin' => 'Bitcoin', + 'knowledge_base' => 'Knowledge Base', + 'partial' => 'Partial', + 'partial_remaining' => ':partial of :balance', + + +); \ No newline at end of file diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 8c3959020dc3..3ab0a35d27ee 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -173,7 +173,7 @@ return array( 'are_you_sure' => 'Are you sure?', // payment pages - 'payment_type_id' => 'Payment type', + 'payment_type_id' => 'Payment Type', 'amount' => 'Amount', // account/company pages @@ -187,7 +187,7 @@ return array( 'remove_logo' => 'Remove logo', 'logo_help' => 'Supported: JPEG, GIF and PNG. Recommended size: 200px width by 120px height', 'payment_gateway' => 'Payment Gateway', - 'gateway_id' => 'Provider', + 'gateway_id' => 'Gateway', 'email_notifications' => 'Email Notifications', 'email_sent' => 'Email me when an invoice is sent', 'email_viewed' => 'Email me when an invoice is viewed', @@ -578,6 +578,20 @@ return array( 'send_email' => 'Send email', 'set_password' => 'Set Password', 'converted' => 'Converted', + + 'email_approved' => 'Email me when a quote is approved', + 'notification_quote_approved_subject' => 'Quote :invoice was approved by :client', + 'notification_quote_approved' => 'The following client :client approved Quote :invoice for :amount.', + 'resend_confirmation' => 'Resend confirmation email', + 'confirmation_resent' => 'The confirmation email was resent', - + 'gateway_help_42' => 'Note: use a BitPay Legacy API Key, not an API token.', + 'payment_type_credit_card' => 'Credit card', + 'payment_type_paypal' => 'PayPal', + 'payment_type_bitcoin' => 'Bitcoin', + 'knowledge_base' => 'Knowledge Base', + 'partial' => 'Partial', + 'partial_remaining' => ':partial of :balance', + + ); diff --git a/resources/lang/es/texts.php b/resources/lang/es/texts.php index 2a06257d8c4b..e00057bcbb98 100644 --- a/resources/lang/es/texts.php +++ b/resources/lang/es/texts.php @@ -550,7 +550,21 @@ return array( 'send_email' => 'Send email', 'set_password' => 'Set Password', 'converted' => 'Converted', - + + 'email_approved' => 'Email me when a quote is approved', + 'notification_quote_approved_subject' => 'Quote :invoice was approved by :client', + 'notification_quote_approved' => 'The following client :client approved Quote :invoice for :amount.', + 'resend_confirmation' => 'Resend confirmation email', + 'confirmation_resent' => 'The confirmation email was resent', + + 'gateway_help_42' => 'Note: use a BitPay Legacy API Key, not an API token.', + 'payment_type_credit_card' => 'Credit card', + 'payment_type_paypal' => 'PayPal', + 'payment_type_bitcoin' => 'Bitcoin', + 'knowledge_base' => 'Knowledge Base', + 'partial' => 'Partial', + 'partial_remaining' => ':partial of :balance', + ); \ No newline at end of file diff --git a/resources/lang/es_ES/pagination.php b/resources/lang/es_ES/pagination.php new file mode 100644 index 000000000000..9cbe91da3011 --- /dev/null +++ b/resources/lang/es_ES/pagination.php @@ -0,0 +1,20 @@ + '« Anterior', + + 'next' => 'Siguiente »', + +); diff --git a/resources/lang/es_ES/public.php b/resources/lang/es_ES/public.php new file mode 100644 index 000000000000..7f5ecb9008e2 --- /dev/null +++ b/resources/lang/es_ES/public.php @@ -0,0 +1,209 @@ + 'Free Open-Source Online Invoicing', + 'description' => 'Invoice Ninja is a free, open-source solution for invoicing and billing customers. With Invoice Ninja, you can easily build and send beautiful invoices from any device that has access to the web. Your clients can print your invoices, download them as pdf files, and even pay you online from within the system.', + 'invoice_now' => 'Invoice Now', + 'no_signup_needed' => 'No signup needed', + + 'link_blog' => 'Blog', + 'link_about_us' => 'About Us', + 'link_contact_us' => 'Contact Us', + 'link_features' => 'Features', + 'link_plans' => 'Plans', + 'link_compare' => 'Compare', + 'link_testimonials' => 'Testimonials', + 'link_faq' => 'FAQ', + + 'my_account' => 'My Account', + 'login' => 'Login', + 'connect_with_us' => 'Connect with Us', + 'safe_and_secure' => 'Safe & Secure', + 'toggle_navigation' => 'Toggle navigation', + + + 'home' => [ + 'header' => 'THE SIMPLE & FREE WAY TO INVOICE CLIENTS', + 'sub_header' => 'It\'s just that easy. Stop spending time on complicated and expensive invoicing.No fuss, just get started and get paid.', + 'footer' => 'Simple, Intuitive Invoicing,Anywhere.', + + 'free_always' => 'Free, Always', + 'free_always_text' => 'Send unlimited invoices to 500 clients per month and never pay a dime. You are welcome to unlock still more awesome features with our Pro Plan, but our free app is a top-notch product that will do everything you need it to do, without any subscription or fees.', + + 'open_source' => 'Open-Source', + 'open_source_text' => 'No mysterious corporate silos here! Just full source code transparency and a devotion to working with anyone interested to build a better electronic invoicing platform. We even offer a handy zip download for a self-hosted version of Invoice Ninja.', + + 'live_pdf' => 'Live .PDF View', + 'live_pdf_text' => 'See how your edited invoice will look as a print-friendly pdf while you make the changes. Our pdf generator works in real time as you make your changes. You can even preview four beautiful preset designs. Just create, save, send, and you’re done!', + + 'online_payments' => 'Online Payments', + 'online_payments_text' => 'Invoices sent with our app integrate seamlessly with the gateway credit card processor of your choice, to make it super easy for your clients to send you money with just a few clicks. We play nicely with Authorize.Net, Stripe, PayPal and loads more - 23 in all!', + ], + + 'about' => [ + 'header' => 'About Invoice Ninja', + 'what_is' => 'What is Invoice Ninja?', + + 'team_ninja' => 'Team Ninja', + 'team_ninja_text' => 'Invoice Ninja is managed by a team of seasoned web workers. We launched in early 2014 and have been thrilled by the enthusiastic response we’ve received from our growing community of users.', + + 'co_founder' => 'Co-Founder', + 'ceo' => 'CEO', + 'cto' => '', + 'designer' => 'Designer', + 'marketing' => 'Marketing', + + 'shalom_bio' => 'Shalom has specialized in small business development for nearly 10 years. In addition to InvoiceNinja.com Shalom is CEO of a leading tour agency in Israel.', + 'hillel_bio' => 'Hillel has been developing enterprise applications for 15 years. His open-source AutoComplete component has been used by thousands of developers around the world.', + 'razi_bio' => 'Razi is a pixel nerd with a great deal of experience in design for web sites and applications. When she isn\'t busy with InvoiceNinja she runs a small web agency in Stockholm called kantorp-wegl.in', + 'ben_bio' => 'A veteran digital marketer and content strategist, Ben specializes in building communities around brands that make business easier for freelancers, SMBs and micro-entrepreneurs.', + ], + + 'contact' => [ + 'header' => 'Questions, special requests, or just want to say hi?', + 'sub_header' => 'Fill in the form below and we\'ll get back to you as soon as possible. Hope to hear from you!', + 'other_ways' => 'Other ways to reach us', + + 'name' => 'Name', + 'name_help' => 'Please enter your name.', + + 'email' => 'Email Address', + 'email_help' => 'Please enter a valid e-mail address.', + + 'message' => 'Message', + 'message_help' => 'Please enter a message.', + 'send_message' => 'Send Message', + ], + + 'features' => [ + 'header' => 'The Features', + 'footer' => 'Like what you see?', + 'footer_action' => 'Get started today!', + + 'open_source' => 'Open Source Platform', + 'open_source_text1' => 'Set the code free! Here at Invoice Ninja, we’re all about creating the best possible app, and inviting scrutiny via full code transparency is a central manifestation of this value.', + 'open_source_text2' => 'We firmly believe that being an open source product helps everyone involved. We’re looking forward to seeing what the developers out there can do to take Invoice Ninja into new realms of usefulness.', + + 'free_forever' => 'FREE. Forever.', + 'free_forever_text1' => 'Yeah, you read that correctly. You don’t have to pay us a cent to use our tools. We know how tough it is to make ends meet as a web-based business, and we’re bent on providing a top-notch product that will do everything you need it to do, without any subscription or opt-in fees.', + 'free_forever_text2' => 'Try Invoice Ninja out. You literally have nothing to lose. We’re confident that you’ll find the experience so positive that you’ll never need to turn elsewhere.', + + 'secure' => 'Secure & Private', + 'secure_text1' => 'Invoice Ninja has been built from the ground up to keep your data safe. Only you have access to your login & accounting details, & we will never share your transaction data to any third party.', + 'secure_text2' => 'Our website operates with 256-bit encryption, which is even more secure than most banking websites. Invoice Ninja uses the TLS 1.0 cryptographic protocol, AES_256_CBC string encryption, SHA1 message authentication and DHE_RSA key exchanges. We feel safe here and have invested heavily in measures to ensure that you do too.', + + 'live_pdf' => 'Live .PDF View', + 'live_pdf_text1' => 'With Invoice Ninja, we’ve done away with the need for cumbersome multi-click invoice previewing after each save.', + 'live_pdf_text2' => 'When you enter the details of your customer and/or invoice in our editor, you can instantly see the results in the pdf preview pane below. Want to see what your invoice would look like in a different layout style? The live pdf can show you four beautiful preset styles in real time too.', + 'live_pdf_text3' => 'Just create, save, send, and you’re done!', + + 'online_payments' => 'Online Payments', + 'online_payments_text1' => 'Invoice Ninja seamlessly integrates with all of the top internet payment processors and gateways so you can get paid for your work quickly and easily.', + 'online_payments_text2' => 'Invoices created with our tools aren’t just for bookkeeping purposes - they bring in the Benjamins. We also make it super easy to choose the right gateway for the specific needs of your business and are happy to help you to get started working with the gateway of your choice. What’s more, we’re constantly working on rolling out additional gateway integrations, so if you don’t see the one you use here, just let us know, and there’s a good chance we’ll add it for you.', + ], + + 'plans' => [ + 'header' => 'The Plans', + 'free' => 'Free', + 'unlimited' => 'Unlimited', + 'pro_plan' => 'Pro Plan', + + 'go_pro' => 'Go Pro to Unlock Premium Invoice Ninja Features', + 'go_pro_text' => 'We believe that the free version of Invoice Ninja is a truly awesome product loaded with the key features you need to bill your clients electronically. But for those who crave still more Ninja awesomeness, we\'ve unmasked the Invoice Ninja Pro plan, which offers more versatility, power and customization options for just $50 per year.', + + 'number_clients' => 'Number of clients per account', + 'unlimited_invoices' => 'Unlimited client invoices', + 'company_logo' => 'Add your company logo', + 'live_pdf' => 'Live .PDF invoice creation', + 'four_templates' => '4 beautiful invoice templates', + 'payments' => 'Accept credit card payments', + 'additional_templates' => 'Additional invoice templates', + 'multi_user' => 'Multi-user support', + 'quotes' => 'Quotes/pro-forma invoices', + 'advanced_settings' => 'Advanced invoice settings', + 'data_vizualizations' => 'Dynamic data vizualizations', + 'email_support' => 'Priority email support', + 'remove_created_by' => 'Remove "Created by Invoice Ninja"', + 'latest_features' => 'Latest and greatest features', + 'pricing' => 'Pricing', + 'free_always' => 'Free /Always!', + 'year_price' => '$50 /Year', + ], + + 'compare' => [ + 'header' => 'How We Compare', + 'free_plan_comparison' => 'Free Plan Comparison', + 'paid_plan_comparison' => 'Paid Plan Comparison', + 'app' => 'App', + 'cost' => 'Cost', + 'clients' => 'Clients', + 'invoices' => 'Invoices', + 'payment_gateways' => 'Payment Gateways', + 'custom_logo' => 'Custom Logo', + 'multiple_templates' => 'Multiple Templates', + 'recurring_payments' => 'Recurring Payments', + 'open_source' => 'Open Source', + 'per_month' => 'per month', + 'per_year' => 'per year', + 'free' => 'Free', + 'unlimited' => 'Unlimited', + ], + + 'testimonials' => [ + 'testimonials' => 'testimonials', + 'header' => 'Since we launched Invoice Ninja in March of 2014, we\'ve been overwhelmed by a deluge of user love. Here\'s a small taste of the glowing things people have to say about the great experiences the\'ve been having with our free e-invoicing app!', + ], + + 'faq' => [ + 'header' => 'The FAQs', + + 'question1' => 'I know it isn’t standard ninja practice to reveal too many identity details, but who are you guys exactly?', + 'answer1' => 'We’re a small team of highly skilled digital journeymen based in Israel. We love open source, we love disrupting the big business status quo, and we love building helpful tools that are easy to use. We believe that everyone else’s web-based cash flow tools are unnecessarily expensive, clunky and complicated - and we’re bent on proving these beliefs with Invoice Ninja.', + + 'question2' => 'How do I get started using Invoice Ninja?', + 'answer2' => 'Just click on the big, yellow "Invoice Now" button on our homepage!', + + 'question3' => 'Do you offer customer support?', + 'answer3' => 'We sure do. Support is super important to us. Feel free to email us at support@invoiceninja.com with any questions you might have. We almost always reply within one business day.', + + 'question4' => 'Is Invoice Ninja really free? For how long?', + 'answer4' => 'Yes, our basic app is 100% free. Forever and ever. For real. We also offer a paid Pro version of Invoice Ninja (you can learn all about its awesome features here), but it\'s important to us that the free version have all of the key features people need to do business.', + + 'question5' => 'How is Invoice Ninja able to offer this all for free? How are you making any money?', + 'answer5' => 'We’re mostly in this line of work because we believe it’s high time that a good electronic invoicing tool be available for free. There isn’t much money in it - yet. We do offer a paid Pro version of the app that we\'ve souped up with premium features. And when our users open up new accounts with payment processor gateways by clicking on links from our site, we make modest commissions as a gateway affiliate. So if zillions of freelancers and small businesses start running credit card charges through Invoice Ninja, or if scores of users go Pro, there’s a decent chance we\'ll recover our investment.', + + 'question6' => 'Really? So does that mean you’re not collecting information about me so you can sell me stuff or so that some other company can spam me according to my interests?', + 'answer6' => 'No way. We’re not mining your data, and we’re not selling you out. That wouldn’t be very ninja of us, would it?', + + 'question7' => 'But don’t you have access to my merchant and banking accounts?', + 'answer7' => 'Actually, we don’t. When you link an account at a third party financial institution with your Invoice Ninja account, you’re essentially giving our app permission to send money to you and nothing more. This is all managed by the tech teams at your financial service providers, who go to great lengths to ensure their integrations can’t be exploited or abused.', + + 'question8' => 'Given that Invoice Ninja is an open source app, how can I be sure that my financial information is safe with you?', + 'answer8' => 'There\'s a big difference between “open source" and “open data.” Anyone who wants to use the code that drives Invoice Ninja to create their own products or to make improvements to ours can do so. It’s available for anyone who wants to download and work with. But that’s just the source code - totally separate from what happens with that code on the Invoice Ninja servers. You’re the only one who has full access to what you\'re doing with our product. For more details on the security of our servers and how we handle our users\' information, please read the next question.', + + 'question9' => 'So just how secure is this app?', + 'answer9' => 'Extremely. Data uploaded by our users runs through connections with 256-bit encryption, which is twice as many encryption bits that most bank websites use. We use the TLS 1.0 cryptographic protocol, AES_256_CBC string encryption, SHA1 message authentication and DHE_RSA key exchanges. It’s fancy stuff that we put in place to make sure no one can gain access to your information except you.', + + 'question10' => 'How do I remove the small "Created by Invoice Ninja” image from the bottom of my invoices?', + 'answer10' => 'The amazingly affordable Pro version of Invoice Ninja allows you to do this and oh so much more.', + + 'question11' => 'Can I see what the application looks like with sample data?', + 'answer11' => 'Sure, click here to try out our demo account.', + + 'question12' => 'I hear that there\'s a version of Invoice Ninja that I can install myself on my own servers? Where can I learn more about this?', + 'answer12' => 'The rumors are true! Full instructions are available here.', + + 'question13' => 'I\'m seeing the options to assign various statuses to my invoices, clients, credits and payments. What\'s the difference between "active," "archived" and "deleted"?', + 'answer13' => 'These three handy statuses for invoices, clients, credits and payments allow you to keep your own cash flow management as straightforward and accessible as possible from your Invoice Ninja dashboard. None of these statuses will actually purge any records from your account - even "deleted" can always be recovered at any point in the future. "Active" means the record will appear in the relevant queue of records. To stash a record away so it\'s still fully operational but no longer cluttering up your interface, simply set it to be "archived." To deactivate a record and render it inaccessible to your clients, mark it "deleted."', + + 'question14' => 'My question wasn\'t covered by any of the content on this FAQ page. How can I get in touch with you?', + 'answer14' => 'Please email us at contact@invoiceninja.com with any questions or comments you have. We love hearing from the people who use our app! We’ll do our best to reply to your email within the business day.', + + 'miss_something' => 'Did we miss something?', + 'miss_something_text' => 'Please email us at contact@invoiceninja.com with any questions or comments you have. We love hearing from the people who use our app! We’ll do our best to reply to your email within the business day.', + + ], + + +]; \ No newline at end of file diff --git a/resources/lang/es_ES/reminders.php b/resources/lang/es_ES/reminders.php new file mode 100644 index 000000000000..094e8788814b --- /dev/null +++ b/resources/lang/es_ES/reminders.php @@ -0,0 +1,24 @@ + "Las contraseñas deben contener al menos 6 caracteres y coincidir.", + + "user" => "No podemos encontrar a un usuario con ese correo electrónico.", + + "token" => "Este token de recuperación de contraseña es inválido.", + + "sent" => "¡Recordatorio de contraseña enviado!", + +); diff --git a/resources/lang/es_ES/texts.php b/resources/lang/es_ES/texts.php new file mode 100644 index 000000000000..0fbf4030239e --- /dev/null +++ b/resources/lang/es_ES/texts.php @@ -0,0 +1,598 @@ + 'Empresa', + 'name' => 'Nombre de Empresa', + 'website' => 'Sitio Web', + 'work_phone' => 'Teléfono', + 'address' => 'Dirección', + 'address1' => 'Calle', + 'address2' => 'Bloq/Pta', + 'city' => 'Ciudad', + 'state' => 'Provincia', + 'postal_code' => 'Código Postal', + 'country_id' => 'País', + 'contacts' => 'Contactos', + 'first_name' => 'Nombres', + 'last_name' => 'Apellidos', + 'phone' => 'Teléfono', + 'email' => 'Email', + 'additional_info' => 'Información adicional', + 'payment_terms' => 'Plazos de pago', // + 'currency_id' => 'Divisa', + 'size_id' => 'Tamaño', + 'industry_id' => 'Industria', + 'private_notes' => 'Notas Privadas', + + // invoice + 'invoice' => 'Factura', + 'client' => 'Cliente', + 'invoice_date' => 'Fecha de factura', + 'due_date' => 'Fecha de pago', + 'invoice_number' => 'Número de Factura', + 'invoice_number_short' => 'Factura Nº', + 'po_number' => 'Apartado de correo', + 'po_number_short' => 'Apdo.', + 'frequency_id' => 'Frecuencia', + 'discount' => 'Descuento', + 'taxes' => 'Impuestos', + 'tax' => 'IVA', + 'item' => 'Concepto', + 'description' => 'Descripción', + 'unit_cost' => 'Coste unitario', + 'quantity' => 'Cantidad', + 'line_total' => 'Total', + 'subtotal' => 'Subtotal', + 'paid_to_date' => 'Pagado', + 'balance_due' => 'Pendiente', + 'invoice_design_id' => 'Diseño', + 'terms' => 'Términos', + 'your_invoice' => 'Tu factura', + 'remove_contact' => 'Eliminar contacto', + 'add_contact' => 'Añadir contacto', + 'create_new_client' => 'Crear nuevo cliente', + 'edit_client_details' => 'Editar detalles del cliente', + 'enable' => 'Activar', + 'learn_more' => 'Saber más', + 'manage_rates' => 'Gestionar tarifas', + 'note_to_client' => 'Nota para el cliente', + 'invoice_terms' => 'Términos de facturación', + 'save_as_default_terms' => 'Guardar como términos por defecto', + 'download_pdf' => 'Descargar PDF', + 'pay_now' => 'Pagar Ahora', + 'save_invoice' => 'Guardar factura', + 'clone_invoice' => 'Clonar factura', + 'archive_invoice' => 'Archivar factura', + 'delete_invoice' => 'Eliminar factura', + 'email_invoice' => 'Enviar factura por email', + 'enter_payment' => 'Agregar pago', + 'tax_rates' => 'Tasas de impuesto', + 'rate' => 'Tasas', + 'settings' => 'Configuración', + 'enable_invoice_tax' => 'Activar impuesto para la factura', + 'enable_line_item_tax' => 'Activar impuesto por concepto', + + // navigation + 'dashboard' => 'Inicio', + 'clients' => 'Clientes', + 'invoices' => 'Facturas', + 'payments' => 'Pagos', + 'credits' => 'Créditos', + 'history' => 'Historial', + 'search' => 'Búsqueda', + 'sign_up' => 'Registrarse', + 'guest' => 'Invitado', + 'company_details' => 'Detalles de la Empresa', + 'online_payments' => 'Pagos en Linea', + 'notifications' => 'Notificaciones', + 'import_export' => 'Importar/Exportar', + 'done' => 'Hecho', + 'save' => 'Guardar', + 'create' => 'Crear', + 'upload' => 'Subir', + 'import' => 'Importar', + 'download' => 'Descargar', + 'cancel' => 'Cancelar', + 'close' => 'Cerrar', + 'provide_email' => 'Por favor facilita una dirección de correo válida.', + 'powered_by' => 'Creado por', + 'no_items' => 'No hay datos', + + // recurring invoices + 'recurring_invoices' => 'Facturas recurrentes', + 'recurring_help' => '
Enviar facturas automáticamente a clientes semanalmente, bi-mensualmente, mensualmente, trimestral o anualmente.
Uso :MONTH, :QUARTER or :YEAR para fechas dinámicas. Matemáticas básicas también funcionan bien. Por ejemplo: :MONTH-1.
Ejemplos de variables dinámicas de factura: