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
## [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
- Improved labelling in the settings (client portal)
- Client portal: Multiple accounts access improvements (#5703)
- Client portal: "Credits" updates (#5734)
- 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)
## 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) {
$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_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_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])->get()->sum('pivot.refunded');
$total_invoice_payments += ($total_amount - $total_refund);
}
// 10/02/21
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) {
@ -347,10 +347,11 @@ class CheckData extends Command
$wrong_paid_to_dates = 0;
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) {
$total_amount = $invoice->payments->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.amount');
$total_refund = $invoice->payments->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.refunded');
$total_credit = $invoice->credits->sum('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()->get()->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.refunded');
$total_credit = $invoice->credits()->get()->sum('amount');
$total_paid = $total_amount - $total_refund;
$calculated_paid_amount = $invoice->amount - $invoice->balance - $total_credit;
@ -363,6 +364,7 @@ class CheckData extends Command
$this->isValid = false;
}
});
});
$this->logMessage("{$wrong_balances} clients with incorrect invoice balances");
@ -408,8 +410,8 @@ class CheckData extends Command
$wrong_paid_to_dates = 0;
foreach (Client::where('is_deleted', 0)->cursor() as $client) {
$invoice_balance = $client->invoices->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance');
$credit_balance = $client->credits->where('is_deleted', false)->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)->get()->sum('balance');
// if($client->balance != $invoice_balance)
// $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()
{
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_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) {
$this->LogMessage("V5_DB2");
$count_db_1 = $entity::on('db-ninja-02')->count();
$count_db_2 = $entity::on('db-ninja-01a')->count();

View File

@ -89,7 +89,7 @@ class SendRemindersCron extends Command
->cursor();
$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();
$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->job(new ReminderJob)->daily()->withoutOverlapping();
$schedule->job(new ReminderJob)->hourly()->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_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 $quote_footer = ''; //@implmented
@ -266,6 +268,7 @@ class CompanySettings extends BaseSettings
public $hide_empty_columns_on_pdf = false;
public static $casts = [
'entity_send_time' => 'int',
'shared_invoice_credit_counter' => 'bool',
'reply_to_name' => 'string',
'hide_empty_columns_on_pdf' => 'bool',

View File

@ -489,38 +489,54 @@ class LoginController extends BaseController
public function redirectToProvider(string $provider)
{
//'https://www.googleapis.com/auth/gmail.send','email','profile','openid'
$scopes = [];
$parameters = [];
if($provider == 'google'){
$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')) {
return $this->handleProviderCallback($provider);
} 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)
{
$socialite_user = Socialite::driver($provider)
->user();
$socialite_user = Socialite::driver($provider)->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))
{
nlog('found user and updating their user record');
$name = OAuth::splitName($socialite_user->getName());
$update_user = [
'first_name' => $name[0],
'last_name' => $name[1],
'password' => '',
'email' => $socialite_user->getEmail(),
'oauth_user_id' => $socialite_user->getId(),
'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);

View File

@ -379,6 +379,15 @@ class ClientController extends BaseController
$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);
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);
$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();
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\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Carbon\Carbon;
use Illuminate\Http\Request;
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)));
$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);
}

View File

@ -58,6 +58,9 @@ class PasswordProtection
$google = new Google();
$user = $google->getTokenResponse(request()->header('X-API-OAUTH-PASSWORD'));
nlog("user");
nlog($user);
if (is_array($user)) {
$query = [
@ -65,14 +68,20 @@ class PasswordProtection
'oauth_provider_id'=> 'google'
];
nlog($query);
//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);
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);
return $next($request);

View File

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

View File

@ -11,6 +11,7 @@
namespace App\Jobs\Company;
use App\Libraries\MultiDB;
use App\Models\TaskStatus;
use App\Utils\Traits\MakesHash;
use Illuminate\Foundation\Bus\Dispatchable;
@ -44,6 +45,9 @@ class CreateCompanyTaskStatuses
*/
public function handle()
{
MultiDB::setDb($this->company->db);
$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.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,9 +109,8 @@ class EmailEntity implements ShouldQueue
App::setLocale($this->invitation->contact->preferredLocale());
$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->settings = $this->settings;
$nmo->to_user = $this->invitation->contact;

View File

@ -14,7 +14,7 @@ namespace App\Jobs\Ninja;
use App\DataMapper\InvoiceItem;
use App\Events\Invoice\InvoiceWasEmailed;
use App\Jobs\Entity\EmailEntity;
use App\Jobs\Util\WebHookHandler;
use App\Jobs\Util\WebhookHandler;
use App\Libraries\MultiDB;
use App\Models\Account;
use App\Models\Invoice;
@ -84,7 +84,7 @@ class SendReminders implements ShouldQueue
if (in_array($reminder_template, ['reminder1', 'reminder2', 'reminder3', 'endless_reminder'])) {
$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;
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);
if (!$invoice->reminder1_sent) {
@ -139,20 +139,20 @@ class SendReminders implements ShouldQueue
}
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);
if (!$invoice->reminder2_sent) {
$set_reminder3 = true;
$set_reminder2 = true;
}
}
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);
if (!$invoice->reminder3_sent) {
@ -178,15 +178,17 @@ class SendReminders implements ShouldQueue
*/
private function calculateScheduledDate($invoice, $schedule_reminder, $num_days_reminder) :?Carbon
{
$offset = $invoice->client->timezone_offset();
switch ($schedule_reminder) {
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;
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;
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;
default:
return null;
@ -211,7 +213,8 @@ class SendReminders implements ShouldQueue
if ($this->checkSendSetting($invoice, $template) && $invoice->company->account->hasFeature(Account::FEATURE_EMAIL_TEMPLATES_REMINDERS)) {
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'])) {
$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");
/* 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->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*/
if ($this->recurring_invoice->remaining_cycles == 0) {
@ -96,7 +96,7 @@ class SendRecurring implements ShouldQueue
if ($invitation->contact && strlen($invitation->contact->email) >=1) {
try{
EmailEntity::dispatch($invitation, $invoice->company);
EmailEntity::dispatch($invitation, $invoice->company)->delay(now()->addSeconds(60));
}
catch(\Exception $e) {
nlog($e->getMessage());

View File

@ -929,7 +929,7 @@ class Import implements ShouldQueue
$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['user_id'] = $this->processUserId($resource);

View File

@ -53,14 +53,14 @@ class ReminderJob implements ShouldQueue
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()) {
$reminder_template = $invoice->calculateTemplate('invoice');
$invoice->service()->touchReminder($reminder_template)->save();
$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}");
});

View File

@ -64,7 +64,7 @@ class SendFailedEmails implements ShouldQueue
if ($invitation->invoice) {
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
*/
namespace App\Jobs\Util;
use App\Jobs\Util\SystemLogger;

View File

@ -34,11 +34,11 @@ class OAuth
* @param Socialite $user
* @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 **/
$query = [
'oauth_user_id' =>$user->getId(),
'oauth_user_id' =>$socialite_user->getId(),
'oauth_provider_id'=>$provider,
];

View File

@ -47,6 +47,9 @@ class CreditEmailEngine extends BaseEmailEngine
$t = app('translator');
$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) {
$body_template = $this->template_data['body'];
} else {

View File

@ -50,6 +50,9 @@ class InvoiceEmailEngine extends BaseEmailEngine
$t = app('translator');
$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) {
$body_template = $this->template_data['body'];
} 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->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) {
$body_template = $this->template_data['body'];
} else {

View File

@ -105,9 +105,6 @@ class Account extends BaseModel
return $this->hasOne(Company::class, 'id', 'default_company_id');
}
/**
* @return BelongsTo
*/
public function payment()
{
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\Models\Presenters\ClientPresenter;
use App\Services\Client\ClientService;
use App\Utils\Traits\AppSetup;
use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash;
@ -32,6 +33,7 @@ class Client extends BaseModel implements HasLocalePreference
use SoftDeletes;
use Filterable;
use GeneratesCounter;
use AppSetup;
protected $presenter = ClientPresenter::class;
@ -230,13 +232,16 @@ class Client extends BaseModel implements HasLocalePreference
public function language()
{
//return Language::find($this->getSetting('language_id'));
$languages = Cache::get('languages');
if(!$languages)
$this->buildCache(true);
return $languages->filter(function ($item) {
return $item->id == $this->getSetting('language_id');
})->first();
}
public function locale()
@ -257,6 +262,9 @@ class Client extends BaseModel implements HasLocalePreference
{
$currencies = Cache::get('currencies');
if(!$currencies)
$this->buildCache(true);
return $currencies->filter(function ($item) {
return $item->id == $this->getSetting('currency_id');
})->first();
@ -622,6 +630,9 @@ class Client extends BaseModel implements HasLocalePreference
{
$languages = Cache::get('languages');
if(!$languages)
$this->buildCache(true);
return $languages->filter(function ($item) {
return $item->id == $this->getSetting('language_id');
})->first()->locale;
@ -684,4 +695,21 @@ class Client extends BaseModel implements HasLocalePreference
{
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) {
return null;
// $this->next_send_date = now()->format('Y-m-d');
}
$offset = $this->client->timezone_offset();
switch ($this->frequency_id) {
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:
return Carbon::parse($this->next_send_date)->addWeek();
return Carbon::parse($this->next_send_date)->startOfDay()->addWeek()->addSeconds($offset);
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:
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:
return Carbon::parse($this->next_send_date)->addMonthNoOverflow();
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthNoOverflow()->addSeconds($offset);
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:
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:
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:
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:
return Carbon::parse($this->next_send_date)->addYear();
return Carbon::parse($this->next_send_date)->startOfDay()->addYear()->addSeconds($offset);
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:
return Carbon::parse($this->next_send_date)->addYears(3);
return Carbon::parse($this->next_send_date)->startOfDay()->addYears(3)->addSeconds($offset);
default:
return null;
}
@ -255,31 +256,33 @@ class RecurringInvoice extends BaseModel
public function nextDateByFrequency($date)
{
$offset = $this->client->timezone_offset();
switch ($this->frequency_id) {
case self::FREQUENCY_DAILY:
return Carbon::parse($date)->addDay();
return Carbon::parse($date)->startOfDay()->addDay()->addSeconds($offset);
case self::FREQUENCY_WEEKLY:
return Carbon::parse($date)->addWeek();
return Carbon::parse($date)->startOfDay()->addWeek()->addSeconds($offset);
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:
return Carbon::parse($date)->addWeeks(4);
return Carbon::parse($date)->startOfDay()->addWeeks(4)->addSeconds($offset);
case self::FREQUENCY_MONTHLY:
return Carbon::parse($date)->addMonthNoOverflow();
return Carbon::parse($date)->startOfDay()->addMonthNoOverflow()->addSeconds($offset);
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:
return Carbon::parse($date)->addMonthsNoOverflow(3);
return Carbon::parse($date)->startOfDay()->addMonthsNoOverflow(3)->addSeconds($offset);
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:
return Carbon::parse($date)->addMonthsNoOverflow(6);
return Carbon::parse($date)->addMonthsNoOverflow(6)->addSeconds($offset);
case self::FREQUENCY_ANNUALLY:
return Carbon::parse($date)->addYear();
return Carbon::parse($date)->startOfDay()->addYear()->addSeconds($offset);
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:
return Carbon::parse($date)->addYears(3);
return Carbon::parse($date)->startOfDay()->addYears(3)->addSeconds($offset);
default:
return null;
}

View File

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

View File

@ -62,7 +62,7 @@ class TriggeredActions extends AbstractService
$reminder_template = 'payment';
$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) {

View File

@ -41,87 +41,89 @@ class UpdateReminder extends AbstractService
return $this->invoice; //exit early
}
$offset = $this->invoice->client->timezone_offset();
$date_collection = collect();
if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'after_invoice_date' &&
$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)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'before_due_date' &&
$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)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'after_due_date' &&
$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)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'after_invoice_date' &&
$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)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'before_due_date' &&
$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)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'after_due_date' &&
$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)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'after_invoice_date' &&
$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)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'before_due_date' &&
$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)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'after_due_date' &&
$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)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
$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,
'created_at' => (int) $company_user->created_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(
$client->getSetting('schedule_reminder1'),
$client->getSetting('num_days_reminder1')
)) {
) && !$this->reminder1_sent) {
return 'reminder1';
} elseif ($this->inReminderWindow(
$client->getSetting('schedule_reminder2'),
$client->getSetting('num_days_reminder2')
)) {
) && !$this->reminder2_sent) {
return 'reminder2';
} elseif ($this->inReminderWindow(
$client->getSetting('schedule_reminder3'),
$client->getSetting('num_days_reminder3')
)) {
) && !$this->reminder3_sent) {
return 'reminder3';
} elseif ($this->checkEndlessReminder(
$this->last_sent_date,
$this->reminder_last_sent,
$client->getSetting('endless_reminder_frequency_id')
)) {
return 'endless_reminder';
@ -77,6 +77,10 @@ trait MakesReminders
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))) {
return true;
}
@ -86,10 +90,13 @@ trait MakesReminders
private function addTimeInterval($date, $endless_reminder_frequency_id) :?Carbon
{
if (!$date)
return null;
switch ($endless_reminder_frequency_id) {
case RecurringInvoice::FREQUENCY_DAILY:
return Carbon::parse($date)->addDay()->startOfDay();
case RecurringInvoice::FREQUENCY_WEEKLY:
return Carbon::parse($date)->addWeek()->startOfDay();
case RecurringInvoice::FREQUENCY_TWO_WEEKS:

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.1.74',
'app_tag' => '5.1.74-release',
'app_version' => '5.2.0',
'app_tag' => '5.2.0-release',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'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 = {
"version.json": "9fe5b22a16f39b766c8fdc35a24b3efa",
"favicon.ico": "51636d3a390451561744c42188ccd628",
"main.dart.js": "9f05b24849e19debf0c8286556e368e6",
"main.dart.js": "33f9288e9a8ba68d21b46b8afda06fbb",
"/": "23224b5e03519aaa87594403d54412cf",
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296",
"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>
<pre>
@if(\App\Utils\Ninja::isHosted())
@if(\App\Utils\Ninja::isSelfHost())
{!! $exception->getMessage() !!}
{!! $content !!}
@else
{!! $exception->getMessage() !!}
{!! $content !!}
<p>Please contact us at contact@invoiceninja.com for more information on this error.</p>
@endif
</pre>
@endcomponent

View File

@ -77,7 +77,7 @@ class ReminderTest extends TestCase
$this->invoice->service()->markSent();
$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();
}
@ -106,7 +106,7 @@ class ReminderTest extends TestCase
$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->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'));
}