diff --git a/README.md b/README.md index 7c8727f8c0ab..77af4773cb0b 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,18 @@ ![v5-develop phpunit](https://github.com/invoiceninja/invoiceninja/workflows/phpunit/badge.svg?branch=v5-develop) ![v5-stable phpunit](https://github.com/invoiceninja/invoiceninja/workflows/phpunit/badge.svg?branch=v5-stable) -[![codecov](https://codecov.io/gh/invoiceninja/invoiceninja/branch/v2/graph/badge.svg)](https://codecov.io/gh/invoiceninja/invoiceninja) + [![Codacy Badge](https://api.codacy.com/project/badge/Grade/d39acb4bf0f74a0698dc77f382769ba5)](https://www.codacy.com/app/turbo124/invoiceninja?utm_source=github.com&utm_medium=referral&utm_content=invoiceninja/invoiceninja&utm_campaign=Badge_Grade) -# Invoice Ninja version 5 is in Beta! +# Invoice Ninja version 5.1 RC2! -We will be using the lessons learnt in Invoice Ninja 4.0 to build a bigger better platform to work from. If you would like to contribute to the project we will gladly accept contributions for code, user guides, bug tracking and feedback! Please consider the following guidelines prior to submitting a pull request: +Invoice Ninja version 5.1 has now reached Release Candidate 2! + +What does this mean exactly? We consider this version _almost_ stable. There may be some remaining small issues which we would love to get feedback on. We would really appreciate the community booting up this version and attempting the migration from their Invoice Ninja V4 application and inspect the migrated data. + +We'd also like feedback on any issues that you can see, and help us nail down the few remaining issues before Version 5 graduates to Stable Gold Release. + +Please note we do not consider this version ready for production use, please stick with your V4 installation for your production clients! ## Quick Start @@ -17,13 +23,10 @@ Currently the client portal and API are of alpha quality, to get started: ```bash git clone https://github.com/invoiceninja/invoiceninja.git -git checkout v2 +git checkout v5-stable cp .env.example .env -cp .env.dusk.example .env.dusk.local php artisan key:generate composer update -npm i -npm run production ``` Please Note: Your APP_KEY in the .env file is used to encrypt data, if you lose this you will not be able to run the application. @@ -33,7 +36,7 @@ Run if you want to load sample data, remember to configure .env php artisan migrate:fresh --seed && php artisan db:seed && php artisan ninja:create-test-data ``` -To Run the web server +To run the web server ``` php artisan serve ``` diff --git a/VERSION.txt b/VERSION.txt index 65c5e6f31326..aec0ea1d292f 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.0.55 \ No newline at end of file +5.0.56 \ No newline at end of file diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 45f6ce34af0e..e1aa1975cc29 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -235,8 +235,8 @@ class CompanySettings extends BaseSettings public $font_size = 7; //@implemented public $primary_font = 'Roboto'; public $secondary_font = 'Roboto'; - public $primary_color = '#4caf50'; - public $secondary_color = '#2196f3'; + public $primary_color = '#142cb5'; + public $secondary_color = '#7081e0'; public $hide_paid_to_date = false; //@TODO where? public $embed_documents = false; //@TODO where? diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 68ac5202d06d..7ebcda5eba44 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -184,7 +184,9 @@ class BaseController extends Controller protected function refreshResponse($query) { - if (auth()->user()->getCompany()->is_large) + $user = auth()->user(); + + if ($user->getCompany()->is_large) $this->manager->parseIncludes($this->mini_load); else $this->manager->parseIncludes($this->first_load); @@ -200,74 +202,145 @@ class BaseController extends Controller $transformer = new $this->entity_transformer($this->serializer); $updated_at = request()->has('updated_at') ? request()->input('updated_at') : 0; - // if (auth()->user()->getCompany()->is_large && ! request()->has('updated_at')) { - // return response()->json(['message' => ctrans('texts.large_account_update_parameter'), 'errors' =>[]], 401); - // } - $updated_at = date('Y-m-d H:i:s', $updated_at); $query->with( [ - 'company' => function ($query) use ($updated_at) { + 'company' => function ($query) use ($updated_at, $user) { $query->whereNotNull('updated_at')->with('documents'); }, - 'company.clients' => function ($query) use ($updated_at) { + 'company.clients' => function ($query) use ($updated_at, $user) { $query->where('clients.updated_at', '>=', $updated_at)->with('contacts.company', 'gateway_tokens', 'documents'); + + if(!$user->hasPermission('view_client')) + $query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id); + }, - 'company.company_gateways' => function ($query) { + 'company.company_gateways' => function ($query) use ($user) { $query->whereNotNull('updated_at'); + + if(!$user->isAdmin()) + $query->where('company_gateways.user_id', $user->id); + }, - 'company.credits'=> function ($query) use ($updated_at) { + 'company.credits'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents'); + + if(!$user->hasPermission('view_credit')) + $query->where('credits.user_id', $user->id)->orWhere('credits.assigned_user_id', $user->id); + }, - 'company.designs'=> function ($query) use ($updated_at) { + 'company.designs'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('company'); + + if(!$user->isAdmin()) + $query->where('designs.user_id', $user->id); }, - 'company.documents'=> function ($query) use ($updated_at) { + 'company.documents'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at); }, - 'company.expenses'=> function ($query) use ($updated_at) { + 'company.expenses'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('documents'); + + if(!$user->hasPermission('view_expense')) + $query->where('expenses.user_id', $user->id)->orWhere('expenses.assigned_user_id', $user->id); }, - 'company.groups' => function ($query) use ($updated_at) { + 'company.groups' => function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at); + + if(!$user->isAdmin()) + $query->where('group_settings.user_id', $user->id); }, - 'company.invoices'=> function ($query) use ($updated_at) { + 'company.invoices'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents'); + + if(!$user->hasPermission('view_invoice')) + $query->where('invoices.user_id', $user->id)->orWhere('invoices.assigned_user_id', $user->id); + }, - 'company.payments'=> function ($query) use ($updated_at) { + 'company.payments'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('paymentables', 'documents'); + + if(!$user->hasPermission('view_payment')) + $query->where('payments.user_id', $user->id)->orWhere('payments.assigned_user_id', $user->id); + }, - 'company.payment_terms'=> function ($query) use ($updated_at) { + 'company.payment_terms'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at); + + if(!$user->isAdmin()) + $query->where('payment_terms.user_id', $user->id); + }, - 'company.products' => function ($query) use ($updated_at) { + 'company.products' => function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('documents'); + + if(!$user->hasPermission('view_product')) + $query->where('products.user_id', $user->id)->orWhere('products.assigned_user_id', $user->id); + }, - 'company.projects'=> function ($query) use ($updated_at) { + 'company.projects'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('documents'); + + if(!$user->hasPermission('view_project')) + $query->where('projects.user_id', $user->id)->orWhere('projects.assigned_user_id', $user->id); + }, - 'company.quotes'=> function ($query) use ($updated_at) { + 'company.quotes'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents'); + + if(!$user->hasPermission('view_quote')) + $query->where('quotes.user_id', $user->id)->orWhere('quotes.assigned_user_id', $user->id); + }, - 'company.recurring_invoices'=> function ($query) use ($updated_at) { + 'company.recurring_invoices'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents'); + + if(!$user->hasPermission('view_recurring_invoice')) + $query->where('recurring_invoices.user_id', $user->id)->orWhere('recurring_invoices.assigned_user_id', $user->id); + }, - 'company.tasks'=> function ($query) use ($updated_at) { + 'company.tasks'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('documents'); + + if(!$user->hasPermission('view_task')) + $query->where('tasks.user_id', $user->id)->orWhere('tasks.assigned_user_id', $user->id); + }, - 'company.tax_rates' => function ($query) use ($updated_at) { + 'company.tax_rates' => function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at); + + if(!$user->isAdmin()) + $query->where('tax_rates.user_id', $user->id); + }, - 'company.vendors'=> function ($query) use ($updated_at) { + 'company.vendors'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('contacts', 'documents'); + + if(!$user->hasPermission('view_vendor')) + $query->where('vendors.user_id', $user->id)->orWhere('vendors.assigned_user_id', $user->id); + }, - 'company.expense_categories'=> function ($query) use ($updated_at) { + 'company.expense_categories'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at); + + if(!$user->isAdmin()) + $query->where('expense_categories.user_id', $user->id); + }, - 'company.task_statuses'=> function ($query) use ($updated_at) { + 'company.task_statuses'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at); + + if(!$user->isAdmin()) + $query->where('task_statuses.user_id', $user->id); + }, + 'company.activities'=> function ($query) use($user) { + + if(!$user->isAdmin()) + $query->where('activities.user_id', $user->id); + + } ] ); diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 37f956b50abb..bc84f38f12e3 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -370,14 +370,25 @@ class UserController extends BaseController public function update(UpdateUserRequest $request, User $user) { $old_email = $user->email; + $old_company_user = $user->company_user; + $old_user = $user; + $new_email = $request->input('email'); $user = $this->user_repo->save($request->all(), $user); + $user = $user->fresh(); if ($old_email != $new_email) { UserEmailChanged::dispatch($new_email, $old_email, auth()->user()->company()); } + if( + strcasecmp($old_company_user->permissions, $user->company_user->permissions) != 0 || + $old_company_user->is_admin != $user->company_user->is_admin + ){ + $user->company_user()->update(["permissions_updated_at" => now()]); + } + event(new UserWasUpdated($user, auth()->user(), auth()->user()->company, Ninja::eventVars())); return $this->itemResponse($user); diff --git a/app/Models/CompanyUser.php b/app/Models/CompanyUser.php index e7fa44c44b9f..20089288b5d4 100644 --- a/app/Models/CompanyUser.php +++ b/app/Models/CompanyUser.php @@ -28,6 +28,7 @@ class CompanyUser extends Pivot * @var array */ protected $casts = [ + 'permissions_updated_at' => 'timestamp', 'updated_at' => 'timestamp', 'created_at' => 'timestamp', 'deleted_at' => 'timestamp', diff --git a/app/Models/User.php b/app/Models/User.php index e77c9a611fa3..9e0133057188 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -202,15 +202,22 @@ class User extends Authenticatable implements MustVerifyEmail $this->id = auth()->user()->id; } - if (request()->header('X-API-TOKEN')) { - return $this->hasOneThrough(CompanyUser::class, CompanyToken::class, 'user_id', 'company_id', 'id', 'company_id') - ->where('company_tokens.token', request()->header('X-API-TOKEN')) - ->withTrashed(); - } else { - return $this->hasOneThrough(CompanyUser::class, CompanyToken::class, 'user_id', 'company_id', 'id', 'company_id') - ->where('company_user.user_id', $this->id) - ->withTrashed(); - } + return $this->hasOneThrough(CompanyUser::class, CompanyToken::class, 'user_id', 'user_id', 'id', 'user_id') + ->withTrashed(); + + // if (request()->header('X-API-TOKEN')) { + + // nlog("with an API token"); + // nlog(request()->header('X-API-TOKEN')); + + // return $this->hasOneThrough(CompanyUser::class, CompanyToken::class, 'user_id', 'company_id', 'id', 'company_id') + // ->where('company_tokens.token', request()->header('X-API-TOKEN')) + // ->withTrashed(); + // } else { + // return $this->hasOneThrough(CompanyUser::class, CompanyToken::class, 'user_id', 'company_id', 'id', 'company_id') + // ->where('company_user.user_id', $this->id) + // ->withTrashed(); + // } } /** diff --git a/app/Transformers/CompanyUserTransformer.php b/app/Transformers/CompanyUserTransformer.php index a3b6f6785374..2ead828ebeb4 100644 --- a/app/Transformers/CompanyUserTransformer.php +++ b/app/Transformers/CompanyUserTransformer.php @@ -48,7 +48,7 @@ class CompanyUserTransformer extends EntityTransformer 'updated_at' => (int) $company_user->updated_at, 'archived_at' => (int) $company_user->deleted_at, 'created_at' => (int) $company_user->created_at, - + 'permissions_updated_at' => (int) $company_user->permissions_updated_at, ]; } diff --git a/app/Utils/Traits/GeneratesCounter.php b/app/Utils/Traits/GeneratesCounter.php index 45b31134b4e0..0364ee0fbd3c 100644 --- a/app/Utils/Traits/GeneratesCounter.php +++ b/app/Utils/Traits/GeneratesCounter.php @@ -33,6 +33,119 @@ trait GeneratesCounter //todo in the form validation, we need to ensure that if a prefix and pattern is set we throw a validation error, //only one type is allow else this will cause confusion to the end user + + + private function getNextEntityNumber($entity, Client $client) + { + $prefix = ''; + + $this->resetCounters($client); + + $is_client_counter = false; + + $counter_string = $this->getEntityCounter($entity, $client); + $pattern = $this->getNumberPattern($entity, $client); + + if (strpos($pattern, 'clientCounter') || strpos($pattern, 'client_counter')) { + + if (property_exists($client->settings, $counter_string)) { + $counter = $client->settings->{$counter_string}; + } else { + $counter = 1; + } + + $counter_entity = $client; + } elseif (strpos($pattern, 'groupCounter') || strpos($pattern, 'group_counter')) { + + if (property_exists($client->group_settings, $counter_string)) { + $counter = $client->group_settings->{$counter_string}; + } else { + $counter = 1; + } + + $counter_entity = $client->group_settings; + + } else { + $counter = $client->company->settings->{$counter_string}; + $counter_entity = $client->company; + } + + //If it is a quote - we need to + $pattern = $this->getNumberPattern($entity, $client); + + $padding = $client->getSetting('counter_padding'); + + if($entity instanceof Invoice && $entity && $entity->recurring_id) + $prefix = $client->getSetting('recurring_number_prefix'); + + $entity_number = $this->checkEntityNumber($entity, $client, $counter, $padding, $pattern, $prefix); + + $this->incrementCounter($counter_entity, $counter_string); + + return $entity_number; + + } + + private function getNumberPattern($entity, Client $client) + { + $pattern_string = ''; + + switch ($entity) { + case Invoice::class: + $pattern_string = 'invoice_number_pattern'; + break; + case Quote::class: + $pattern_string = 'quote_number_pattern'; + break; + case RecurringInvoice::class: + $pattern_string = 'recurring_invoice_number_pattern'; + break; + case Payment::class: + $pattern_string = 'payment_number_pattern'; + break; + case Credit::class: + $pattern_string = 'credit_number_pattern'; + break; + case Project::class: + $pattern_string = 'project_number_pattern'; + break; + } + + return $client->getSetting($pattern_string); + } + + private function getEntityCounter($entity, $client) + { + switch ($entity) { + case Invoice::class: + return 'invoice_number_counter'; + break; + case Quote::class: + + if ($this->hasSharedCounter($client)) + return 'invoice_number_counter'; + + return 'quote_number_counter'; + break; + case RecurringInvoice::class: + return 'recurring_invoice_number_counter'; + break; + case Payment::class: + return 'payment_number_counter'; + break; + case Credit::class: + return 'credit_number_counter'; + break; + case Project::class: + return 'project_number_counter'; + break; + + default: + return 'default_number_counter'; + break; + } + } + /** * Gets the next invoice number. * @@ -43,42 +156,7 @@ trait GeneratesCounter */ public function getNextInvoiceNumber(Client $client, ?Invoice $invoice) :string { - //Reset counters if enabled - $this->resetCounters($client); - - //todo handle if we have specific client patterns in the future - $pattern = $client->getSetting('invoice_number_pattern'); - //Determine if we are using client_counters - if (strpos($pattern, 'clientCounter') || strpos($pattern, 'client_counter')) { - if (property_exists($client->settings, 'invoice_number_counter')) { - $counter = $client->settings->invoice_number_counter; - } else { - $counter = 1; - } - - $counter_entity = $client; - } elseif (strpos($pattern, 'groupCounter') || strpos($pattern, 'group_counter')) { - $counter = $client->group_settings->invoice_number_counter; - $counter_entity = $client->group_settings; - } else { - $counter = $client->company->settings->invoice_number_counter; - $counter_entity = $client->company; - } - - //Return a valid counter - $pattern = $client->getSetting('invoice_number_pattern'); - $padding = $client->getSetting('counter_padding'); - $prefix = ''; - - if ($invoice && $invoice->recurring_id) { - $prefix = $client->getSetting('recurring_number_prefix'); - } - - $invoice_number = $this->checkEntityNumber(Invoice::class, $client, $counter, $padding, $pattern, $prefix); - - $this->incrementCounter($counter_entity, 'invoice_number_counter'); - - return $invoice_number; + return $this->getNextEntityNumber(Invoice::class, $client); } /** @@ -90,145 +168,36 @@ trait GeneratesCounter */ public function getNextCreditNumber(Client $client) :string { - //Reset counters if enabled - $this->resetCounters($client); - - //todo handle if we have specific client patterns in the future - $pattern = $client->getSetting('credit_number_pattern'); - //Determine if we are using client_counters - if (strpos($pattern, 'clientCounter') || strpos($pattern, 'client_counter')) { - $counter = $client->settings->credit_number_counter; - $counter_entity = $client; - } elseif (strpos($pattern, 'groupCounter') || strpos($pattern, 'group_counter')) { - $counter = $client->group_settings->credit_number_counter; - $counter_entity = $client->group_settings; - } else { - $counter = $client->company->settings->credit_number_counter; - $counter_entity = $client->company; - } - - //Return a valid counter - $pattern = $client->getSetting('credit_number_pattern'); - $padding = $client->getSetting('counter_padding'); - - $credit_number = $this->checkEntityNumber(Credit::class, $client, $counter, $padding, $pattern); - - $this->incrementCounter($counter_entity, 'credit_number_counter'); - - return $credit_number; + return $this->getNextEntityNumber(Credit::class, $client); } + /** + * Gets the next quote number. + * + * @param Client $client The client + * + * @return string The next credit number. + */ public function getNextQuoteNumber(Client $client) { - //Reset counters if enabled - $this->resetCounters($client); - - $used_counter = 'quote_number_counter'; - - if ($this->hasSharedCounter($client)) { - $used_counter = 'invoice_number_counter'; - } - - //todo handle if we have specific client patterns in the future - $pattern = $client->getSetting('quote_number_pattern'); - //Determine if we are using client_counters - if (strpos($pattern, 'clientCounter') || strpos($pattern, 'client_counter')) { - $counter = $client->settings->{$used_counter}; - $counter_entity = $client; - } elseif (strpos($pattern, 'groupCounter') || strpos($pattern, 'group_counter')) { - $counter = $client->group_settings->{$used_counter}; - $counter_entity = $client->group_settings; - } else { - $counter = $client->company->settings->{$used_counter}; - $counter_entity = $client->company; - } - - //Return a valid counter - $pattern = $client->getSetting('quote_number_pattern'); - $padding = $client->getSetting('counter_padding'); - - $quote_number = $this->checkEntityNumber(Quote::class, $client, $counter, $padding, $pattern); - - // if($this->recurring_id) - // $quote_number = $this->prefixCounter($quote_number, $client->getSetting('recurring_number_prefix')); - - $this->incrementCounter($counter_entity, $used_counter); - - return $quote_number; + return $this->getNextEntityNumber(Quote::class, $client); } public function getNextRecurringInvoiceNumber(Client $client) { - - //Reset counters if enabled - $this->resetCounters($client); - - $is_client_counter = false; - - //todo handle if we have specific client patterns in the future - $pattern = $client->company->settings->recurring_invoice_number_pattern; - - //Determine if we are using client_counters - if (strpos($pattern, 'client_counter') === false) { - $counter = $client->company->settings->recurring_invoice_number_counter; - } else { - $counter = $client->settings->recurring_invoice_number_counter; - $is_client_counter = true; - } - - //Return a valid counter - $pattern = ''; - $padding = $client->getSetting('counter_padding'); - $invoice_number = $this->checkEntityNumber(RecurringInvoice::class, $client, $counter, $padding, $pattern); - //$invoice_number = $this->prefixCounter($invoice_number, $client->getSetting('recurring_number_prefix')); - - //increment the correct invoice_number Counter (company vs client) - if ($is_client_counter) { - $this->incrementCounter($client, 'recurring_invoice_number_counter'); - } else { - $this->incrementCounter($client->company, 'recurring_invoice_number_counter'); - } - - return $invoice_number; + return $this->getNextEntityNumber(RecurringInvoice::class, $client); } /** - * Payment Number Generator. - * @param Client $client - * @return string The payment number + * Gets the next Payment number. + * + * @param Client $client The client + * + * @return string The next payment number. */ public function getNextPaymentNumber(Client $client) :string { - - //Reset counters if enabled - $this->resetCounters($client); - - $is_client_counter = false; - - //todo handle if we have specific client patterns in the future - $pattern = $client->company->settings->payment_number_pattern; - - //Determine if we are using client_counters - if (strpos($pattern, 'client_counter') === false) { - $counter = $client->company->settings->payment_number_counter; - } else { - $counter = $client->settings->payment_number_counter; - $is_client_counter = true; - } - - //Return a valid counter - $pattern = ''; - $padding = $client->getSetting('counter_padding'); - $payment_number = $this->checkEntityNumber(Payment::class, $client, $counter, $padding, $pattern); - - //increment the correct invoice_number Counter (company vs client) - if ($is_client_counter) { - $this->incrementCounter($client, 'payment_number_counter'); - } else { - $this->incrementCounter($client->company, 'payment_number_counter'); - } - - return (string) $payment_number; + return $this->getNextEntityNumber(Payment::class, $client); } /** @@ -342,7 +311,7 @@ trait GeneratesCounter * * @return bool True if has shared counter, False otherwise. */ - public function hasSharedCounter(Client $client) : bool + public function hasSharedCounter(Client $client) : bool { return (bool) $client->getSetting('shared_invoice_quote_counter'); } @@ -404,6 +373,9 @@ trait GeneratesCounter $settings->invoice_number_counter = 0; } + if(!property_exists($settings, $counter_name)) + $settings->{$counter_name} = 1; + $settings->{$counter_name} = $settings->{$counter_name} + 1; $entity->settings = $settings; diff --git a/config/ninja.php b/config/ninja.php index 283c361c0a58..53b659058f8b 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -13,7 +13,7 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', ''), - 'app_version' => '5.0.55', + 'app_version' => '5.0.56', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', false), diff --git a/database/migrations/2021_01_29_121502_add_permission_changed_timestamp.php b/database/migrations/2021_01_29_121502_add_permission_changed_timestamp.php new file mode 100644 index 000000000000..30ff2096d393 --- /dev/null +++ b/database/migrations/2021_01_29_121502_add_permission_changed_timestamp.php @@ -0,0 +1,30 @@ +timestamp('permissions_updated_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index e0baaf184b5e..76909da18ce5 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -286,7 +286,7 @@ return [ 'password' => 'Password', 'pro_plan_product' => 'Pro Plan', 'pro_plan_success' => 'Thanks for choosing Invoice Ninja\'s Pro plan! 
- Next StepsA payable invoice has been sent to the email + Next Steps. A payable invoice has been sent to the email address associated with your account. To unlock all of the awesome Pro features, please follow the instructions on the invoice to pay for a year of Pro-level invoicing. @@ -1091,7 +1091,7 @@ return [ 'invoice_item_fields' => 'Invoice Item Fields', 'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.', 'recurring_invoice_number' => 'Recurring Number', - 'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices.', + 'recurring_invoice_number_prefix_help' => 'Specify a prefix to be added to the invoice number for recurring invoices.', // Client Passwords 'enable_portal_password' => 'Password Protect Invoices',