1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-28 20:27:13 +02:00

Merge branch 'v5-develop' into v5-stable

This commit is contained in:
David Bomba 2021-06-12 07:59:56 +10:00
commit 9b0c860d44
44 changed files with 168967 additions and 168865 deletions

View File

@ -1,11 +1,21 @@
# Release notes # Release notes
## [Unreleased (daily channel)](https://github.com/invoiceninja/invoiceninja/tree/v5-develop) ## [Unreleased (daily channel)](https://github.com/invoiceninja/invoiceninja/tree/v5-develop)
## [v5.2.0-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.2.0-release)
## Added:
- Timezone Offset: Schedule emails based on timezone and time offsets.
- Force client country to system country if none is set.
- GMail Oauth via web
## Fixed:
- Add Cache-control: no-cache to prevent overaggressive caching of assets - Add Cache-control: no-cache to prevent overaggressive caching of assets
- Improved labelling in the settings (client portal) - Improved labelling in the settings (client portal)
- Client portal: Multiple accounts access improvements (#5703) - Client portal: Multiple accounts access improvements (#5703)
- Client portal: "Credits" updates (#5734) - Client portal: "Credits" updates (#5734)
- Client portal: Make sidebar white color, in order to make logo displaying more simple. (#5753) - Client portal: Make sidebar white color, in order to make logo displaying more simple. (#5753)
- Inject small delay into emails to allow all resources to be produced (ie PDFs) prior to sending
- Fixes for endless reminders not firing
## [v5.1.56-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.1.56-release) ## [v5.1.56-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.1.56-release)
## Fixed: ## Fixed:

View File

@ -1 +1 @@
5.1.74 5.2.0

View File

@ -311,17 +311,17 @@ class CheckData extends Command
Client::withTrashed()->where('is_deleted', 0)->cursor()->each(function ($client) use ($wrong_paid_to_dates, $credit_total_applied) { Client::withTrashed()->where('is_deleted', 0)->cursor()->each(function ($client) use ($wrong_paid_to_dates, $credit_total_applied) {
$total_invoice_payments = 0; $total_invoice_payments = 0;
foreach ($client->invoices->where('is_deleted', false)->where('status_id', '>', 1) as $invoice) { foreach ($client->invoices()->where('is_deleted', false)->where('status_id', '>', 1)->get() as $invoice) {
$total_amount = $invoice->payments->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->sum('pivot.amount'); $total_amount = $invoice->payments()->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->get()->sum('pivot.amount');
$total_refund = $invoice->payments->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->sum('pivot.refunded'); $total_refund = $invoice->payments()->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->get()->sum('pivot.refunded');
$total_invoice_payments += ($total_amount - $total_refund); $total_invoice_payments += ($total_amount - $total_refund);
} }
// 10/02/21 // 10/02/21
foreach ($client->payments as $payment) { foreach ($client->payments as $payment) {
$credit_total_applied += $payment->paymentables->where('paymentable_type', App\Models\Credit::class)->sum(DB::raw('amount')); $credit_total_applied += $payment->paymentables()->where('paymentable_type', App\Models\Credit::class)->get()->sum(DB::raw('amount'));
} }
if ($credit_total_applied < 0) { if ($credit_total_applied < 0) {
@ -347,10 +347,11 @@ class CheckData extends Command
$wrong_paid_to_dates = 0; $wrong_paid_to_dates = 0;
Client::cursor()->where('is_deleted', 0)->each(function ($client) use ($wrong_balances) { Client::cursor()->where('is_deleted', 0)->each(function ($client) use ($wrong_balances) {
$client->invoices->where('is_deleted', false)->whereIn('status_id', '!=', Invoice::STATUS_DRAFT)->each(function ($invoice) use ($wrong_balances, $client) { $client->invoices->where('is_deleted', false)->whereIn('status_id', '!=', Invoice::STATUS_DRAFT)->each(function ($invoice) use ($wrong_balances, $client) {
$total_amount = $invoice->payments->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.amount'); $total_amount = $invoice->payments()->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->get()->sum('pivot.amount');
$total_refund = $invoice->payments->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.refunded'); $total_refund = $invoice->payments()->get()->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.refunded');
$total_credit = $invoice->credits->sum('amount'); $total_credit = $invoice->credits()->get()->sum('amount');
$total_paid = $total_amount - $total_refund; $total_paid = $total_amount - $total_refund;
$calculated_paid_amount = $invoice->amount - $invoice->balance - $total_credit; $calculated_paid_amount = $invoice->amount - $invoice->balance - $total_credit;
@ -363,6 +364,7 @@ class CheckData extends Command
$this->isValid = false; $this->isValid = false;
} }
}); });
}); });
$this->logMessage("{$wrong_balances} clients with incorrect invoice balances"); $this->logMessage("{$wrong_balances} clients with incorrect invoice balances");
@ -408,8 +410,8 @@ class CheckData extends Command
$wrong_paid_to_dates = 0; $wrong_paid_to_dates = 0;
foreach (Client::where('is_deleted', 0)->cursor() as $client) { foreach (Client::where('is_deleted', 0)->cursor() as $client) {
$invoice_balance = $client->invoices->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance'); $invoice_balance = $client->invoices()->where('is_deleted', false)->where('status_id', '>', 1)->get()->sum('balance');
$credit_balance = $client->credits->where('is_deleted', false)->sum('balance'); $credit_balance = $client->credits()->where('is_deleted', false)->get()->sum('balance');
// if($client->balance != $invoice_balance) // if($client->balance != $invoice_balance)
// $invoice_balance -= $credit_balance;//doesn't make sense to remove the credit amount // $invoice_balance -= $credit_balance;//doesn't make sense to remove the credit amount

View File

@ -126,9 +126,9 @@ class CheckDb extends Command
public function handle() public function handle()
{ {
foreach($this->entities as $entity) { $this->LogMessage("Checking - V5_DB1");
$this->LogMessage("V5_DB1"); foreach($this->entities as $entity) {
$count_db_1 = $entity::on('db-ninja-01')->count(); $count_db_1 = $entity::on('db-ninja-01')->count();
$count_db_2 = $entity::on('db-ninja-02a')->count(); $count_db_2 = $entity::on('db-ninja-02a')->count();
@ -140,11 +140,10 @@ class CheckDb extends Command
} }
$this->LogMessage("Checking - V5_DB2");
foreach($this->entities as $entity) { foreach($this->entities as $entity) {
$this->LogMessage("V5_DB2");
$count_db_1 = $entity::on('db-ninja-02')->count(); $count_db_1 = $entity::on('db-ninja-02')->count();
$count_db_2 = $entity::on('db-ninja-01a')->count(); $count_db_2 = $entity::on('db-ninja-01a')->count();

View File

@ -89,7 +89,7 @@ class SendRemindersCron extends Command
->cursor(); ->cursor();
$invoices->each(function ($invoice) { $invoices->each(function ($invoice) {
WebHookHandler::dispatch(Webhook::EVENT_LATE_INVOICE, $invoice, $invoice->company); WebhookHandler::dispatch(Webhook::EVENT_LATE_INVOICE, $invoice, $invoice->company);
}); });
@ -99,7 +99,7 @@ class SendRemindersCron extends Command
->cursor(); ->cursor();
$quotes->each(function ($quote) { $quotes->each(function ($quote) {
WebHookHandler::dispatch(Webhook::EVENT_EXPIRED_QUOTE, $quote, $quote->company); WebhookHandler::dispatch(Webhook::EVENT_EXPIRED_QUOTE, $quote, $quote->company);
}); });
} }

View File

@ -52,7 +52,7 @@ class Kernel extends ConsoleKernel
$schedule->command('ninja:check-data --database=db-ninja-01')->daily()->withoutOverlapping(); $schedule->command('ninja:check-data --database=db-ninja-01')->daily()->withoutOverlapping();
$schedule->job(new ReminderJob)->daily()->withoutOverlapping(); $schedule->job(new ReminderJob)->hourly()->withoutOverlapping();
$schedule->job(new CompanySizeCheck)->daily()->withoutOverlapping(); $schedule->job(new CompanySizeCheck)->daily()->withoutOverlapping();

View File

@ -65,6 +65,8 @@ class CompanySettings extends BaseSettings
public $auto_convert_quote = true; //@implemented public $auto_convert_quote = true; //@implemented
public $auto_email_invoice = true; //@only used for Recurring Invoices, if set to false, we never send? public $auto_email_invoice = true; //@only used for Recurring Invoices, if set to false, we never send?
public $entity_send_time = 0;
public $inclusive_taxes = false; //@implemented public $inclusive_taxes = false; //@implemented
public $quote_footer = ''; //@implmented public $quote_footer = ''; //@implmented
@ -266,6 +268,7 @@ class CompanySettings extends BaseSettings
public $hide_empty_columns_on_pdf = false; public $hide_empty_columns_on_pdf = false;
public static $casts = [ public static $casts = [
'entity_send_time' => 'int',
'shared_invoice_credit_counter' => 'bool', 'shared_invoice_credit_counter' => 'bool',
'reply_to_name' => 'string', 'reply_to_name' => 'string',
'hide_empty_columns_on_pdf' => 'bool', 'hide_empty_columns_on_pdf' => 'bool',

View File

@ -489,38 +489,54 @@ class LoginController extends BaseController
public function redirectToProvider(string $provider) public function redirectToProvider(string $provider)
{ {
//'https://www.googleapis.com/auth/gmail.send','email','profile','openid'
$scopes = []; $scopes = [];
$parameters = [];
if($provider == 'google'){ if($provider == 'google'){
$scopes = ['https://www.googleapis.com/auth/gmail.send','email','profile','openid']; $scopes = ['https://www.googleapis.com/auth/gmail.send','email','profile','openid'];
$parameters = ['access_type' => 'offline', "prompt" => "consent select_account", 'redirect_uri' => config('ninja.app_url')."/auth/google"];
} }
if (request()->has('code')) { if (request()->has('code')) {
return $this->handleProviderCallback($provider); return $this->handleProviderCallback($provider);
} else { } else {
return Socialite::driver($provider)->with(['redirect_uri' => config('ninja.app_url')."/auth/google"])->scopes($scopes)->redirect(); return Socialite::driver($provider)->with($parameters)->scopes($scopes)->redirect();
} }
} }
public function handleProviderCallback(string $provider) public function handleProviderCallback(string $provider)
{ {
$socialite_user = Socialite::driver($provider) $socialite_user = Socialite::driver($provider)->user();
->user();
$oauth_user_token = '';
if($socialite_user->refreshToken){
$client = new Google_Client();
$client->setClientId(config('ninja.auth.google.client_id'));
$client->setClientSecret(config('ninja.auth.google.client_secret'));
$client->fetchAccessTokenWithRefreshToken($socialite_user->refreshToken);
$oauth_user_token = $client->getAccessToken();
}
if($user = OAuth::handleAuth($socialite_user, $provider)) if($user = OAuth::handleAuth($socialite_user, $provider))
{ {
nlog('found user and updating their user record'); nlog('found user and updating their user record');
$name = OAuth::splitName($socialite_user->getName());
$update_user = [ $update_user = [
'first_name' => $name[0], 'first_name' => $name[0],
'last_name' => $name[1], 'last_name' => $name[1],
'password' => '',
'email' => $socialite_user->getEmail(), 'email' => $socialite_user->getEmail(),
'oauth_user_id' => $socialite_user->getId(), 'oauth_user_id' => $socialite_user->getId(),
'oauth_provider_id' => $provider, 'oauth_provider_id' => $provider,
'oauth_user_token' => $socialite_user->refreshToken, 'oauth_user_token' => $oauth_user_token,
'oauth_user_refresh_token' => $socialite_user->refreshToken
]; ];
$user->update($update_user); $user->update($update_user);

View File

@ -379,6 +379,15 @@ class ClientController extends BaseController
$client->load('contacts', 'primary_contact'); $client->load('contacts', 'primary_contact');
/* Set the client country to the company if none is set */
if(!$client->country_id && strlen($client->company->settings->country_id) > 1){
$client->country_id = $client->company->settings->country_id;
$client->save();
}
$this->uploadLogo($request->file('company_logo'), $client->company, $client); $this->uploadLogo($request->file('company_logo'), $client->company, $client);
event(new ClientWasCreated($client, $client->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); event(new ClientWasCreated($client, $client->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));

View File

@ -564,7 +564,7 @@ class CreditController extends BaseController
// EmailCredit::dispatch($credit, $credit->company); // EmailCredit::dispatch($credit, $credit->company);
$credit->invitations->load('contact.client.country', 'credit.client.country', 'credit.company')->each(function ($invitation) use ($credit) { $credit->invitations->load('contact.client.country', 'credit.client.country', 'credit.company')->each(function ($invitation) use ($credit) {
EmailEntity::dispatch($invitation, $credit->company, 'credit'); EmailEntity::dispatch($invitation, $credit->company, 'credit')->delay(now()->addSeconds(60));
}); });

View File

@ -132,7 +132,7 @@ class EmailController extends BaseController
$entity_obj->service()->markSent()->save(); $entity_obj->service()->markSent()->save();
EmailEntity::dispatch($invitation->fresh(), $invitation->company, $template, $data) EmailEntity::dispatch($invitation->fresh(), $invitation->company, $template, $data)
->delay(now()->addSeconds(5)); ->delay(now()->addSeconds(60));
} }

View File

@ -30,6 +30,7 @@ use App\Transformers\RecurringInvoiceTransformer;
use App\Utils\Ninja; use App\Utils\Ninja;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments; use App\Utils\Traits\SavesDocuments;
use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
@ -205,6 +206,10 @@ class RecurringInvoiceController extends BaseController
event(new RecurringInvoiceWasCreated($recurring_invoice, $recurring_invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); event(new RecurringInvoiceWasCreated($recurring_invoice, $recurring_invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
$offset = $recurring_invoice->client->timezone_offset();
$recurring_invoice->next_send_date = Carbon::parse($recurring_invoice->next_send_date)->startOfDay()->addSeconds($offset);
$recurring_invoice->save();
return $this->itemResponse($recurring_invoice); return $this->itemResponse($recurring_invoice);
} }

View File

@ -58,6 +58,9 @@ class PasswordProtection
$google = new Google(); $google = new Google();
$user = $google->getTokenResponse(request()->header('X-API-OAUTH-PASSWORD')); $user = $google->getTokenResponse(request()->header('X-API-OAUTH-PASSWORD'));
nlog("user");
nlog($user);
if (is_array($user)) { if (is_array($user)) {
$query = [ $query = [
@ -65,14 +68,20 @@ class PasswordProtection
'oauth_provider_id'=> 'google' 'oauth_provider_id'=> 'google'
]; ];
nlog($query);
//If OAuth and user also has a password set - check both //If OAuth and user also has a password set - check both
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->has_password && Hash::check(auth()->user()->password, $request->header('X-API-PASSWORD'))) { if ($existing_user = MultiDB::hasUser($query) && auth()->user()->company()->oauth_password_required && auth()->user()->has_password && Hash::check(auth()->user()->password, $request->header('X-API-PASSWORD'))) {
nlog("existing user with password");
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout); Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
return $next($request); return $next($request);
} }
elseif($existing_user = MultiDB::hasUser($query) && !auth()->user()->has_password){ elseif($existing_user = MultiDB::hasUser($query) && !auth()->user()->company()->oauth_password_required){
nlog("existing user without password");
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout); Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
return $next($request); return $next($request);

View File

@ -14,10 +14,12 @@ namespace App\Http\Requests\User;
use App\DataMapper\DefaultSettings; use App\DataMapper\DefaultSettings;
use App\Factory\UserFactory; use App\Factory\UserFactory;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use App\Http\ValidationRules\Ninja\CanAddUserRule;
use App\Http\ValidationRules\User\AttachableUser; use App\Http\ValidationRules\User\AttachableUser;
use App\Http\ValidationRules\ValidUserForCompany; use App\Http\ValidationRules\ValidUserForCompany;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\User; use App\Models\User;
use App\Utils\Ninja;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
class StoreUserRequest extends Request class StoreUserRequest extends Request
@ -45,8 +47,7 @@ class StoreUserRequest extends Request
$rules['email'] = ['email', new AttachableUser()]; $rules['email'] = ['email', new AttachableUser()];
} }
if (Ninja::isHosted()) {
if (auth()->user()->company()->account->isFreeHostedClient()) {
$rules['hosted_users'] = new CanAddUserRule(auth()->user()->company()->account); $rules['hosted_users'] = new CanAddUserRule(auth()->user()->company()->account);
} }

View File

@ -11,6 +11,7 @@
namespace App\Jobs\Company; namespace App\Jobs\Company;
use App\Libraries\MultiDB;
use App\Models\TaskStatus; use App\Models\TaskStatus;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
@ -44,6 +45,9 @@ class CreateCompanyTaskStatuses
*/ */
public function handle() public function handle()
{ {
MultiDB::setDb($this->company->db);
$task_statuses = [ $task_statuses = [
['name' => ctrans('texts.backlog'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 1], ['name' => ctrans('texts.backlog'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 1],
['name' => ctrans('texts.ready_to_do'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 2], ['name' => ctrans('texts.ready_to_do'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 2],

View File

@ -109,7 +109,6 @@ class EmailEntity implements ShouldQueue
App::setLocale($this->invitation->contact->preferredLocale()); App::setLocale($this->invitation->contact->preferredLocale());
$nmo = new NinjaMailerObject; $nmo = new NinjaMailerObject;
$nmo->mailable = new TemplateEmail($this->email_entity_builder, $this->invitation->contact, $this->invitation); $nmo->mailable = new TemplateEmail($this->email_entity_builder, $this->invitation->contact, $this->invitation);
$nmo->company = $this->company; $nmo->company = $this->company;

View File

@ -14,7 +14,7 @@ namespace App\Jobs\Ninja;
use App\DataMapper\InvoiceItem; use App\DataMapper\InvoiceItem;
use App\Events\Invoice\InvoiceWasEmailed; use App\Events\Invoice\InvoiceWasEmailed;
use App\Jobs\Entity\EmailEntity; use App\Jobs\Entity\EmailEntity;
use App\Jobs\Util\WebHookHandler; use App\Jobs\Util\WebhookHandler;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\Account; use App\Models\Account;
use App\Models\Invoice; use App\Models\Invoice;
@ -84,7 +84,7 @@ class SendReminders implements ShouldQueue
if (in_array($reminder_template, ['reminder1', 'reminder2', 'reminder3', 'endless_reminder'])) { if (in_array($reminder_template, ['reminder1', 'reminder2', 'reminder3', 'endless_reminder'])) {
$this->sendReminder($invoice, $reminder_template); $this->sendReminder($invoice, $reminder_template);
WebHookHandler::dispatch(Webhook::EVENT_REMIND_INVOICE, $invoice, $invoice->company); WebhookHandler::dispatch(Webhook::EVENT_REMIND_INVOICE, $invoice, $invoice->company);
} }
}); });
} }
@ -128,9 +128,9 @@ class SendReminders implements ShouldQueue
$set_reminder3 = false; $set_reminder3 = false;
if ((int)$settings->schedule_reminder1 > 0) { if ((int)$settings->schedule_reminder1 > 0) {
$next_reminder_date = $this->calculateScheduledDate($invoice, (int)$settings->schedule_reminder1, (int)$settings->num_days_reminder1); $next_reminder_date = $this->calculateScheduledDate($invoice, $settings->schedule_reminder1, (int)$settings->num_days_reminder1);
if ($next_reminder_date->gt(Carbon::parse($invoice->last_sent_date))); if ($next_reminder_date && $next_reminder_date->gt(Carbon::parse($invoice->last_sent_date)));
$dates->push($next_reminder_date); $dates->push($next_reminder_date);
if (!$invoice->reminder1_sent) { if (!$invoice->reminder1_sent) {
@ -139,20 +139,20 @@ class SendReminders implements ShouldQueue
} }
if ((int)$settings->num_days_reminder2 > 0) { if ((int)$settings->num_days_reminder2 > 0) {
$next_reminder_date = $this->calculateScheduledDate($invoice, (int)$settings->schedule_reminder2, (int)$settings->num_days_reminder2); $next_reminder_date = $this->calculateScheduledDate($invoice, $settings->schedule_reminder2, (int)$settings->num_days_reminder2);
if ($next_reminder_date->gt(Carbon::parse($invoice->last_sent_date))); if ($next_reminder_date && $next_reminder_date->gt(Carbon::parse($invoice->last_sent_date)));
$dates->push($next_reminder_date); $dates->push($next_reminder_date);
if (!$invoice->reminder2_sent) { if (!$invoice->reminder2_sent) {
$set_reminder3 = true; $set_reminder2 = true;
} }
} }
if ((int)$settings->num_days_reminder3 > 0) { if ((int)$settings->num_days_reminder3 > 0) {
$next_reminder_date = $this->calculateScheduledDate($invoice, (int)$settings->schedule_reminder3, (int)$settings->num_days_reminder3); $next_reminder_date = $this->calculateScheduledDate($invoice, $settings->schedule_reminder3, (int)$settings->num_days_reminder3);
if ($next_reminder_date->gt(Carbon::parse($invoice->last_sent_date))); if ($next_reminder_date && $next_reminder_date->gt(Carbon::parse($invoice->last_sent_date)));
$dates->push($next_reminder_date); $dates->push($next_reminder_date);
if (!$invoice->reminder3_sent) { if (!$invoice->reminder3_sent) {
@ -178,15 +178,17 @@ class SendReminders implements ShouldQueue
*/ */
private function calculateScheduledDate($invoice, $schedule_reminder, $num_days_reminder) :?Carbon private function calculateScheduledDate($invoice, $schedule_reminder, $num_days_reminder) :?Carbon
{ {
$offset = $invoice->client->timezone_offset();
switch ($schedule_reminder) { switch ($schedule_reminder) {
case 'after_invoice_date': case 'after_invoice_date':
return Carbon::parse($invoice->date)->addDays($num_days_reminder)->startOfDay(); return Carbon::parse($invoice->date)->addDays($num_days_reminder)->startOfDay()->addSeconds($offset);
break; break;
case 'before_due_date': case 'before_due_date':
return Carbon::parse($invoice->due_date)->subDays($num_days_reminder)->startOfDay(); return Carbon::parse($invoice->due_date)->subDays($num_days_reminder)->startOfDay()->addSeconds($offset);
break; break;
case 'after_due_date': case 'after_due_date':
return Carbon::parse($invoice->due_date)->addDays($num_days_reminder)->startOfDay(); return Carbon::parse($invoice->due_date)->addDays($num_days_reminder)->startOfDay()->addSeconds($offset);
break; break;
default: default:
return null; return null;
@ -211,7 +213,8 @@ class SendReminders implements ShouldQueue
if ($this->checkSendSetting($invoice, $template) && $invoice->company->account->hasFeature(Account::FEATURE_EMAIL_TEMPLATES_REMINDERS)) { if ($this->checkSendSetting($invoice, $template) && $invoice->company->account->hasFeature(Account::FEATURE_EMAIL_TEMPLATES_REMINDERS)) {
nlog("firing email"); nlog("firing email");
EmailEntity::dispatchNow($invitation, $invitation->company, $template); EmailEntity::dispatchNow($invitation, $invitation->company, $template)->delay(now()->addSeconds(60));
} }
}); });
@ -226,8 +229,9 @@ class SendReminders implements ShouldQueue
if (in_array($template, ['reminder1', 'reminder2', 'reminder3'])) { if (in_array($template, ['reminder1', 'reminder2', 'reminder3'])) {
$invoice->{$template."_sent"} = now(); $invoice->{$template."_sent"} = now();
} }
$invoice->service()->touchReminder($template)->save();
$invoice->save(); // $invoice->save();
} }
/** /**

View File

@ -70,9 +70,9 @@ class SendRecurring implements ShouldQueue
nlog("updating recurring invoice dates"); nlog("updating recurring invoice dates");
/* Set next date here to prevent a recurring loop forming */ /* Set next date here to prevent a recurring loop forming */
$this->recurring_invoice->next_send_date = $this->recurring_invoice->nextSendDate()->format('Y-m-d'); $this->recurring_invoice->next_send_date = $this->recurring_invoice->nextSendDate();
$this->recurring_invoice->remaining_cycles = $this->recurring_invoice->remainingCycles(); $this->recurring_invoice->remaining_cycles = $this->recurring_invoice->remainingCycles();
$this->recurring_invoice->last_sent_date = date('Y-m-d'); $this->recurring_invoice->last_sent_date = now();
/* Set completed if we don't have any more cycles remaining*/ /* Set completed if we don't have any more cycles remaining*/
if ($this->recurring_invoice->remaining_cycles == 0) { if ($this->recurring_invoice->remaining_cycles == 0) {
@ -96,7 +96,7 @@ class SendRecurring implements ShouldQueue
if ($invitation->contact && strlen($invitation->contact->email) >=1) { if ($invitation->contact && strlen($invitation->contact->email) >=1) {
try{ try{
EmailEntity::dispatch($invitation, $invoice->company); EmailEntity::dispatch($invitation, $invoice->company)->delay(now()->addSeconds(60));
} }
catch(\Exception $e) { catch(\Exception $e) {
nlog($e->getMessage()); nlog($e->getMessage());

View File

@ -929,7 +929,7 @@ class Import implements ShouldQueue
$modified['client_id'] = $this->transformId('clients', $resource['client_id']); $modified['client_id'] = $this->transformId('clients', $resource['client_id']);
if(array_key_exists('invoice_id', $resource) && $this->tryTransformingId('invoices', $resource['invoice_id'])) if(array_key_exists('invoice_id', $resource) && isset($resource['invoice_id']) && $this->tryTransformingId('invoices', $resource['invoice_id']))
$modified['invoice_id'] = $this->transformId('invoices', $resource['invoice_id']); $modified['invoice_id'] = $this->transformId('invoices', $resource['invoice_id']);
$modified['user_id'] = $this->processUserId($resource); $modified['user_id'] = $this->processUserId($resource);

View File

@ -53,14 +53,14 @@ class ReminderJob implements ShouldQueue
private function processReminders() private function processReminders()
{ {
Invoice::where('next_send_date', Carbon::today()->format('Y-m-d'))->with('invitations')->cursor()->each(function ($invoice) { Invoice::whereDate('next_send_date', '<=', now())->with('invitations')->cursor()->each(function ($invoice) {
if ($invoice->isPayable()) { if ($invoice->isPayable()) {
$reminder_template = $invoice->calculateTemplate('invoice'); $reminder_template = $invoice->calculateTemplate('invoice');
$invoice->service()->touchReminder($reminder_template)->save(); $invoice->service()->touchReminder($reminder_template)->save();
$invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) { $invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) {
EmailEntity::dispatch($invitation, $invitation->company, $reminder_template); EmailEntity::dispatch($invitation, $invitation->company, $reminder_template)->delay(now()->addSeconds(60));
nlog("Firing reminder email for invoice {$invoice->number}"); nlog("Firing reminder email for invoice {$invoice->number}");
}); });

View File

@ -64,7 +64,7 @@ class SendFailedEmails implements ShouldQueue
if ($invitation->invoice) { if ($invitation->invoice) {
if ($invitation->contact->send_email && $invitation->contact->email) { if ($invitation->contact->send_email && $invitation->contact->email) {
EmailEntity::dispatch($invitation, $invitation->company, $job_meta_array['reminder_template']); EmailEntity::dispatch($invitation, $invitation->company, $job_meta_array['reminder_template'])->delay(now()->addSeconds(60));
} }
} }
}); });

View File

@ -8,6 +8,7 @@
* *
* @license https://opensource.org/licenses/AAL * @license https://opensource.org/licenses/AAL
*/ */
namespace App\Jobs\Util; namespace App\Jobs\Util;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;

View File

@ -34,11 +34,11 @@ class OAuth
* @param Socialite $user * @param Socialite $user
* @return bool|\App\Models\User|\App\Libraries\App\Models\User|null * @return bool|\App\Models\User|\App\Libraries\App\Models\User|null
*/ */
public static function handleAuth(Socialite $user) public static function handleAuth($socialite_user, $provider)
{ {
/** 1. Ensure user arrives on the correct provider **/ /** 1. Ensure user arrives on the correct provider **/
$query = [ $query = [
'oauth_user_id' =>$user->getId(), 'oauth_user_id' =>$socialite_user->getId(),
'oauth_provider_id'=>$provider, 'oauth_provider_id'=>$provider,
]; ];

View File

@ -47,6 +47,9 @@ class CreditEmailEngine extends BaseEmailEngine
$t = app('translator'); $t = app('translator');
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings())); $t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
if($this->reminder_template == 'endless_reminder')
$this->reminder_template = 'reminder_endless';
if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) { if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) {
$body_template = $this->template_data['body']; $body_template = $this->template_data['body'];
} else { } else {

View File

@ -50,6 +50,9 @@ class InvoiceEmailEngine extends BaseEmailEngine
$t = app('translator'); $t = app('translator');
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings())); $t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
if($this->reminder_template == 'endless_reminder')
$this->reminder_template = 'reminder_endless';
if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) { if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) {
$body_template = $this->template_data['body']; $body_template = $this->template_data['body'];
} elseif (strlen($this->client->getSetting('email_template_'.$this->reminder_template)) > 0) { } elseif (strlen($this->client->getSetting('email_template_'.$this->reminder_template)) > 0) {

View File

@ -48,6 +48,9 @@ class QuoteEmailEngine extends BaseEmailEngine
$t = app('translator'); $t = app('translator');
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings())); $t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
if($this->reminder_template == 'endless_reminder')
$this->reminder_template = 'reminder_endless';
if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) { if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) {
$body_template = $this->template_data['body']; $body_template = $this->template_data['body'];
} else { } else {

View File

@ -105,9 +105,6 @@ class Account extends BaseModel
return $this->hasOne(Company::class, 'id', 'default_company_id'); return $this->hasOne(Company::class, 'id', 'default_company_id');
} }
/**
* @return BelongsTo
*/
public function payment() public function payment()
{ {
return $this->belongsTo(Payment::class)->withTrashed(); return $this->belongsTo(Payment::class)->withTrashed();
@ -323,4 +320,5 @@ class Account extends BaseModel
]; ];
} }
} }
} }

View File

@ -15,6 +15,7 @@ use App\DataMapper\ClientSettings;
use App\DataMapper\CompanySettings; use App\DataMapper\CompanySettings;
use App\Models\Presenters\ClientPresenter; use App\Models\Presenters\ClientPresenter;
use App\Services\Client\ClientService; use App\Services\Client\ClientService;
use App\Utils\Traits\AppSetup;
use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
@ -32,6 +33,7 @@ class Client extends BaseModel implements HasLocalePreference
use SoftDeletes; use SoftDeletes;
use Filterable; use Filterable;
use GeneratesCounter; use GeneratesCounter;
use AppSetup;
protected $presenter = ClientPresenter::class; protected $presenter = ClientPresenter::class;
@ -230,13 +232,16 @@ class Client extends BaseModel implements HasLocalePreference
public function language() public function language()
{ {
//return Language::find($this->getSetting('language_id'));
$languages = Cache::get('languages'); $languages = Cache::get('languages');
if(!$languages)
$this->buildCache(true);
return $languages->filter(function ($item) { return $languages->filter(function ($item) {
return $item->id == $this->getSetting('language_id'); return $item->id == $this->getSetting('language_id');
})->first(); })->first();
} }
public function locale() public function locale()
@ -257,6 +262,9 @@ class Client extends BaseModel implements HasLocalePreference
{ {
$currencies = Cache::get('currencies'); $currencies = Cache::get('currencies');
if(!$currencies)
$this->buildCache(true);
return $currencies->filter(function ($item) { return $currencies->filter(function ($item) {
return $item->id == $this->getSetting('currency_id'); return $item->id == $this->getSetting('currency_id');
})->first(); })->first();
@ -622,6 +630,9 @@ class Client extends BaseModel implements HasLocalePreference
{ {
$languages = Cache::get('languages'); $languages = Cache::get('languages');
if(!$languages)
$this->buildCache(true);
return $languages->filter(function ($item) { return $languages->filter(function ($item) {
return $item->id == $this->getSetting('language_id'); return $item->id == $this->getSetting('language_id');
})->first()->locale; })->first()->locale;
@ -684,4 +695,21 @@ class Client extends BaseModel implements HasLocalePreference
{ {
return $this->hasMany(Payment::class); return $this->hasMany(Payment::class);
} }
public function timezone_offset()
{
$offset = 0;
$entity_send_time = $this->getSetting('entity_send_time');
if($entity_send_time == 0)
return 0;
$timezone = $this->company->timezone();
$offset -= $timezone->utc_offset;
$offset += ($entity_send_time * 3600);
return $offset;
}
} }

View File

@ -220,34 +220,35 @@ class RecurringInvoice extends BaseModel
{ {
if (!$this->next_send_date) { if (!$this->next_send_date) {
return null; return null;
// $this->next_send_date = now()->format('Y-m-d');
} }
$offset = $this->client->timezone_offset();
switch ($this->frequency_id) { switch ($this->frequency_id) {
case self::FREQUENCY_DAILY: case self::FREQUENCY_DAILY:
return Carbon::parse($this->next_send_date)->addDay(); return Carbon::parse($this->next_send_date)->startOfDay()->addDay()->addSeconds($offset);
case self::FREQUENCY_WEEKLY: case self::FREQUENCY_WEEKLY:
return Carbon::parse($this->next_send_date)->addWeek(); return Carbon::parse($this->next_send_date)->startOfDay()->addWeek()->addSeconds($offset);
case self::FREQUENCY_TWO_WEEKS: case self::FREQUENCY_TWO_WEEKS:
return Carbon::parse($this->next_send_date)->addWeeks(2); return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(2)->addSeconds($offset);
case self::FREQUENCY_FOUR_WEEKS: case self::FREQUENCY_FOUR_WEEKS:
return Carbon::parse($this->next_send_date)->addWeeks(4); return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(4)->addSeconds($offset);
case self::FREQUENCY_MONTHLY: case self::FREQUENCY_MONTHLY:
return Carbon::parse($this->next_send_date)->addMonthNoOverflow(); return Carbon::parse($this->next_send_date)->startOfDay()->addMonthNoOverflow()->addSeconds($offset);
case self::FREQUENCY_TWO_MONTHS: case self::FREQUENCY_TWO_MONTHS:
return Carbon::parse($this->next_send_date)->addMonthsNoOverflow(2); return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(2)->addSeconds($offset);
case self::FREQUENCY_THREE_MONTHS: case self::FREQUENCY_THREE_MONTHS:
return Carbon::parse($this->next_send_date)->addMonthsNoOverflow(3); return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(3)->addSeconds($offset);
case self::FREQUENCY_FOUR_MONTHS: case self::FREQUENCY_FOUR_MONTHS:
return Carbon::parse($this->next_send_date)->addMonthsNoOverflow(4); return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(4)->addSeconds($offset);
case self::FREQUENCY_SIX_MONTHS: case self::FREQUENCY_SIX_MONTHS:
return Carbon::parse($this->next_send_date)->addMonthsNoOverflow(6); return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(6)->addSeconds($offset);
case self::FREQUENCY_ANNUALLY: case self::FREQUENCY_ANNUALLY:
return Carbon::parse($this->next_send_date)->addYear(); return Carbon::parse($this->next_send_date)->startOfDay()->addYear()->addSeconds($offset);
case self::FREQUENCY_TWO_YEARS: case self::FREQUENCY_TWO_YEARS:
return Carbon::parse($this->next_send_date)->addYears(2); return Carbon::parse($this->next_send_date)->startOfDay()->addYears(2)->addSeconds($offset);
case self::FREQUENCY_THREE_YEARS: case self::FREQUENCY_THREE_YEARS:
return Carbon::parse($this->next_send_date)->addYears(3); return Carbon::parse($this->next_send_date)->startOfDay()->addYears(3)->addSeconds($offset);
default: default:
return null; return null;
} }
@ -255,31 +256,33 @@ class RecurringInvoice extends BaseModel
public function nextDateByFrequency($date) public function nextDateByFrequency($date)
{ {
$offset = $this->client->timezone_offset();
switch ($this->frequency_id) { switch ($this->frequency_id) {
case self::FREQUENCY_DAILY: case self::FREQUENCY_DAILY:
return Carbon::parse($date)->addDay(); return Carbon::parse($date)->startOfDay()->addDay()->addSeconds($offset);
case self::FREQUENCY_WEEKLY: case self::FREQUENCY_WEEKLY:
return Carbon::parse($date)->addWeek(); return Carbon::parse($date)->startOfDay()->addWeek()->addSeconds($offset);
case self::FREQUENCY_TWO_WEEKS: case self::FREQUENCY_TWO_WEEKS:
return Carbon::parse($date)->addWeeks(2); return Carbon::parse($date)->startOfDay()->addWeeks(2)->addSeconds($offset);
case self::FREQUENCY_FOUR_WEEKS: case self::FREQUENCY_FOUR_WEEKS:
return Carbon::parse($date)->addWeeks(4); return Carbon::parse($date)->startOfDay()->addWeeks(4)->addSeconds($offset);
case self::FREQUENCY_MONTHLY: case self::FREQUENCY_MONTHLY:
return Carbon::parse($date)->addMonthNoOverflow(); return Carbon::parse($date)->startOfDay()->addMonthNoOverflow()->addSeconds($offset);
case self::FREQUENCY_TWO_MONTHS: case self::FREQUENCY_TWO_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(2); return Carbon::parse($date)->startOfDay()->addMonthsNoOverflow(2)->addSeconds($offset);
case self::FREQUENCY_THREE_MONTHS: case self::FREQUENCY_THREE_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(3); return Carbon::parse($date)->startOfDay()->addMonthsNoOverflow(3)->addSeconds($offset);
case self::FREQUENCY_FOUR_MONTHS: case self::FREQUENCY_FOUR_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(4); return Carbon::parse($date)->startOfDay()->addMonthsNoOverflow(4)->addSeconds($offset);
case self::FREQUENCY_SIX_MONTHS: case self::FREQUENCY_SIX_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(6); return Carbon::parse($date)->addMonthsNoOverflow(6)->addSeconds($offset);
case self::FREQUENCY_ANNUALLY: case self::FREQUENCY_ANNUALLY:
return Carbon::parse($date)->addYear(); return Carbon::parse($date)->startOfDay()->addYear()->addSeconds($offset);
case self::FREQUENCY_TWO_YEARS: case self::FREQUENCY_TWO_YEARS:
return Carbon::parse($date)->addYears(2); return Carbon::parse($date)->startOfDay()->addYears(2)->addSeconds($offset);
case self::FREQUENCY_THREE_YEARS: case self::FREQUENCY_THREE_YEARS:
return Carbon::parse($date)->addYears(3); return Carbon::parse($date)->startOfDay()->addYears(3)->addSeconds($offset);
default: default:
return null; return null;
} }

View File

@ -376,7 +376,9 @@ class InvoiceService
$this->invoice->reminder3_sent = now()->format('Y-m-d'); $this->invoice->reminder3_sent = now()->format('Y-m-d');
$this->invoice->reminder_last_sent = now()->format('Y-m-d'); $this->invoice->reminder_last_sent = now()->format('Y-m-d');
break; break;
case 'endless_reminder':
$this->invoice->reminder_last_sent = now()->format('Y-m-d');
break;
default: default:
// code... // code...
break; break;

View File

@ -62,7 +62,7 @@ class TriggeredActions extends AbstractService
$reminder_template = 'payment'; $reminder_template = 'payment';
$this->invoice->invitations->load('contact.client.country', 'invoice.client.country', 'invoice.company')->each(function ($invitation) use ($reminder_template) { $this->invoice->invitations->load('contact.client.country', 'invoice.client.country', 'invoice.company')->each(function ($invitation) use ($reminder_template) {
EmailEntity::dispatch($invitation, $this->invoice->company, $reminder_template); EmailEntity::dispatch($invitation, $this->invoice->company, $reminder_template)->delay(now()->addSeconds(60));
}); });
if ($this->invoice->invitations->count() > 0) { if ($this->invoice->invitations->count() > 0) {

View File

@ -41,87 +41,89 @@ class UpdateReminder extends AbstractService
return $this->invoice; //exit early return $this->invoice; //exit early
} }
$offset = $this->invoice->client->timezone_offset();
$date_collection = collect(); $date_collection = collect();
if (is_null($this->invoice->reminder1_sent) && if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'after_invoice_date' && $this->settings->schedule_reminder1 == 'after_invoice_date' &&
$this->settings->num_days_reminder1 > 0) { $this->settings->num_days_reminder1 > 0) {
$reminder_date = Carbon::parse($this->invoice->date)->addDays($this->settings->num_days_reminder1); $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d')); $date_collection->push($reminder_date);
} }
if (is_null($this->invoice->reminder1_sent) && if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'before_due_date' && $this->settings->schedule_reminder1 == 'before_due_date' &&
$this->settings->num_days_reminder1 > 0) { $this->settings->num_days_reminder1 > 0) {
$reminder_date = Carbon::parse($this->invoice->due_date)->subDays($this->settings->num_days_reminder1); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder1)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d')); $date_collection->push($reminder_date);
} }
if (is_null($this->invoice->reminder1_sent) && if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'after_due_date' && $this->settings->schedule_reminder1 == 'after_due_date' &&
$this->settings->num_days_reminder1 > 0) { $this->settings->num_days_reminder1 > 0) {
$reminder_date = Carbon::parse($this->invoice->due_date)->addDays($this->settings->num_days_reminder1); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d')); $date_collection->push($reminder_date);
} }
if (is_null($this->invoice->reminder2_sent) && if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'after_invoice_date' && $this->settings->schedule_reminder2 == 'after_invoice_date' &&
$this->settings->num_days_reminder2 > 0) { $this->settings->num_days_reminder2 > 0) {
$reminder_date = Carbon::parse($this->invoice->date)->addDays($this->settings->num_days_reminder2); $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d')); $date_collection->push($reminder_date);
} }
if (is_null($this->invoice->reminder2_sent) && if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'before_due_date' && $this->settings->schedule_reminder2 == 'before_due_date' &&
$this->settings->num_days_reminder2 > 0) { $this->settings->num_days_reminder2 > 0) {
$reminder_date = Carbon::parse($this->invoice->due_date)->subDays($this->settings->num_days_reminder2); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder2)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d')); $date_collection->push($reminder_date);
} }
if (is_null($this->invoice->reminder2_sent) && if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'after_due_date' && $this->settings->schedule_reminder2 == 'after_due_date' &&
$this->settings->num_days_reminder2 > 0) { $this->settings->num_days_reminder2 > 0) {
$reminder_date = Carbon::parse($this->invoice->due_date)->addDays($this->settings->num_days_reminder2); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d')); $date_collection->push($reminder_date);
} }
if (is_null($this->invoice->reminder3_sent) && if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'after_invoice_date' && $this->settings->schedule_reminder3 == 'after_invoice_date' &&
$this->settings->num_days_reminder3 > 0) { $this->settings->num_days_reminder3 > 0) {
$reminder_date = Carbon::parse($this->invoice->date)->addDays($this->settings->num_days_reminder3); $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d')); $date_collection->push($reminder_date);
} }
if (is_null($this->invoice->reminder3_sent) && if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'before_due_date' && $this->settings->schedule_reminder3 == 'before_due_date' &&
$this->settings->num_days_reminder3 > 0) { $this->settings->num_days_reminder3 > 0) {
$reminder_date = Carbon::parse($this->invoice->due_date)->subDays($this->settings->num_days_reminder3); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder3)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d')); $date_collection->push($reminder_date);
} }
if (is_null($this->invoice->reminder3_sent) && if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'after_due_date' && $this->settings->schedule_reminder3 == 'after_due_date' &&
$this->settings->num_days_reminder3 > 0) { $this->settings->num_days_reminder3 > 0) {
$reminder_date = Carbon::parse($this->invoice->due_date)->addDays($this->settings->num_days_reminder3); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d')); $date_collection->push($reminder_date);
} }
$this->invoice->next_send_date = $date_collection->sort()->first(); $this->invoice->next_send_date = $date_collection->sort()->first();

View File

@ -51,7 +51,7 @@ class CompanyUserTransformer extends EntityTransformer
'archived_at' => (int) $company_user->deleted_at, 'archived_at' => (int) $company_user->deleted_at,
'created_at' => (int) $company_user->created_at, 'created_at' => (int) $company_user->created_at,
'permissions_updated_at' => (int) $company_user->permissions_updated_at, 'permissions_updated_at' => (int) $company_user->permissions_updated_at,
//'number_years_active' => (int) $company_user->number_years_active, 'ninja_portal_url' => (string) $company_user->ninja_portal_url,
]; ];
} }

View File

@ -51,20 +51,20 @@ trait MakesReminders
if ($this->inReminderWindow( if ($this->inReminderWindow(
$client->getSetting('schedule_reminder1'), $client->getSetting('schedule_reminder1'),
$client->getSetting('num_days_reminder1') $client->getSetting('num_days_reminder1')
)) { ) && !$this->reminder1_sent) {
return 'reminder1'; return 'reminder1';
} elseif ($this->inReminderWindow( } elseif ($this->inReminderWindow(
$client->getSetting('schedule_reminder2'), $client->getSetting('schedule_reminder2'),
$client->getSetting('num_days_reminder2') $client->getSetting('num_days_reminder2')
)) { ) && !$this->reminder2_sent) {
return 'reminder2'; return 'reminder2';
} elseif ($this->inReminderWindow( } elseif ($this->inReminderWindow(
$client->getSetting('schedule_reminder3'), $client->getSetting('schedule_reminder3'),
$client->getSetting('num_days_reminder3') $client->getSetting('num_days_reminder3')
)) { ) && !$this->reminder3_sent) {
return 'reminder3'; return 'reminder3';
} elseif ($this->checkEndlessReminder( } elseif ($this->checkEndlessReminder(
$this->last_sent_date, $this->reminder_last_sent,
$client->getSetting('endless_reminder_frequency_id') $client->getSetting('endless_reminder_frequency_id')
)) { )) {
return 'endless_reminder'; return 'endless_reminder';
@ -77,6 +77,10 @@ trait MakesReminders
private function checkEndlessReminder($last_sent_date, $endless_reminder_frequency_id) :bool private function checkEndlessReminder($last_sent_date, $endless_reminder_frequency_id) :bool
{ {
nlog("endless date match = ".$this->addTimeInterval($last_sent_date, $endless_reminder_frequency_id));
nlog("Endless reminder bool = ");
nlog(Carbon::now()->startOfDay()->eq($this->addTimeInterval($last_sent_date, $endless_reminder_frequency_id)));
if (Carbon::now()->startOfDay()->eq($this->addTimeInterval($last_sent_date, $endless_reminder_frequency_id))) { if (Carbon::now()->startOfDay()->eq($this->addTimeInterval($last_sent_date, $endless_reminder_frequency_id))) {
return true; return true;
} }
@ -86,10 +90,13 @@ trait MakesReminders
private function addTimeInterval($date, $endless_reminder_frequency_id) :?Carbon private function addTimeInterval($date, $endless_reminder_frequency_id) :?Carbon
{ {
if (!$date) if (!$date)
return null; return null;
switch ($endless_reminder_frequency_id) { switch ($endless_reminder_frequency_id) {
case RecurringInvoice::FREQUENCY_DAILY:
return Carbon::parse($date)->addDay()->startOfDay();
case RecurringInvoice::FREQUENCY_WEEKLY: case RecurringInvoice::FREQUENCY_WEEKLY:
return Carbon::parse($date)->addWeek()->startOfDay(); return Carbon::parse($date)->addWeek()->startOfDay();
case RecurringInvoice::FREQUENCY_TWO_WEEKS: case RecurringInvoice::FREQUENCY_TWO_WEEKS:

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.1.74', 'app_version' => '5.2.0',
'app_tag' => '5.1.74-release', 'app_tag' => '5.2.0-release',
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''), 'api_secret' => env('API_SECRET', ''),

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddNinjaPortalColumnToAccountsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('company_user', function (Blueprint $table) {
$table->text('ninja_portal_url')->default('');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
}

View File

@ -5,7 +5,7 @@ const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = { const RESOURCES = {
"version.json": "9fe5b22a16f39b766c8fdc35a24b3efa", "version.json": "9fe5b22a16f39b766c8fdc35a24b3efa",
"favicon.ico": "51636d3a390451561744c42188ccd628", "favicon.ico": "51636d3a390451561744c42188ccd628",
"main.dart.js": "9f05b24849e19debf0c8286556e368e6", "main.dart.js": "33f9288e9a8ba68d21b46b8afda06fbb",
"/": "23224b5e03519aaa87594403d54412cf", "/": "23224b5e03519aaa87594403d54412cf",
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296", "assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296",
"assets/AssetManifest.json": "659dcf9d1baf3aed3ab1b9c42112bf8f", "assets/AssetManifest.json": "659dcf9d1baf3aed3ab1b9c42112bf8f",

169188
public/main.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

168238
public/main.foss.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,12 +7,11 @@
<p>Looks like your migration failed. Here's the error message:</p> <p>Looks like your migration failed. Here's the error message:</p>
<pre> <pre>
@if(\App\Utils\Ninja::isHosted()) @if(\App\Utils\Ninja::isSelfHost())
{!! $exception->getMessage() !!} {!! $exception->getMessage() !!}
{!! $content !!} {!! $content !!}
@else @else
{!! $exception->getMessage() !!} <p>Please contact us at contact@invoiceninja.com for more information on this error.</p>
{!! $content !!}
@endif @endif
</pre> </pre>
@endcomponent @endcomponent

View File

@ -77,7 +77,7 @@ class ReminderTest extends TestCase
$this->invoice->service()->markSent(); $this->invoice->service()->markSent();
$this->invoice->service()->setReminder($settings)->save(); $this->invoice->service()->setReminder($settings)->save();
$this->assertEquals($this->invoice->next_send_date, Carbon::now()->addDays(7)->format('Y-m-d')); $this->assertEquals(Carbon::parse($this->invoice->next_send_date)->format('Y-m-d'), Carbon::now()->addDays(7)->format('Y-m-d'));
// ReminderJob::dispatchNow(); // ReminderJob::dispatchNow();
} }
@ -106,7 +106,7 @@ class ReminderTest extends TestCase
$this->invoice->fresh(); $this->invoice->fresh();
$this->assertEquals($this->invoice->next_send_date, now()->format('Y-m-d')); $this->assertEquals(Carbon::parse($this->invoice->next_send_date)->format('Y-m-d'), now()->format('Y-m-d'));
} }
@ -136,7 +136,7 @@ class ReminderTest extends TestCase
$this->invoice->fresh(); $this->invoice->fresh();
$this->assertEquals($this->invoice->next_send_date, now()->format('Y-m-d')); $this->assertEquals(Carbon::parse($this->invoice->next_send_date)->format('Y-m-d'), now()->format('Y-m-d'));
} }