diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index b3ab47c4a5d4..374e7783c3fe 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -1243,6 +1243,7 @@ class BaseExport * Add Date Range * * @param Builder $query + * @param ?string $table_name * @return Builder */ protected function addDateRange(Builder $query, ?string $table_name = null): Builder @@ -1251,7 +1252,7 @@ class BaseExport $date_range = $this->input['date_range']; - if (array_key_exists('date_key', $this->input) && strlen($this->input['date_key']) > 1 && ($this->table_name && $this->columnExists($table_name, $this->input['date_key']))) { + if (array_key_exists('date_key', $this->input) && strlen($this->input['date_key']) > 1 && ($table_name && $this->columnExists($table_name, $this->input['date_key']))) { $this->date_key = $this->input['date_key']; } diff --git a/app/Jobs/Util/QuoteReminderJob.php b/app/Jobs/Util/QuoteReminderJob.php index b2fe8dbc1603..ed1aa5fa722a 100644 --- a/app/Jobs/Util/QuoteReminderJob.php +++ b/app/Jobs/Util/QuoteReminderJob.php @@ -125,22 +125,15 @@ class QuoteReminderJob implements ShouldQueue return; } - $reminder_template = $quote->calculateTemplate('invoice'); + $reminder_template = $quote->calculateTemplate('quote'); nrlog("#{$quote->number} => reminder template = {$reminder_template}"); $quote->service()->touchReminder($reminder_template)->save(); - $fees = $this->calcLateFee($quote, $reminder_template); - - if($quote->isLocked()) { - return $this->addFeeToNewQuote($quote, $reminder_template, $fees); - } - - $quote = $this->setLateFee($quote, $fees[0], $fees[1]); //20-04-2022 fixes for endless reminders - generic template naming was wrong - $enabled_reminder = 'enable_'.$reminder_template; - if ($reminder_template == 'endless_reminder') { - $enabled_reminder = 'enable_reminder_endless'; - } + $enabled_reminder = 'enable_quote_'.$reminder_template; + // if ($reminder_template == 'endless_reminder') { + // $enabled_reminder = 'enable_reminder_endless'; + // } if (in_array($reminder_template, ['reminder1', 'reminder2', 'reminder3', 'reminder_endless', 'endless_reminder']) && $quote->client->getSetting($enabled_reminder) && @@ -149,9 +142,9 @@ class QuoteReminderJob implements ShouldQueue $quote->invitations->each(function ($invitation) use ($quote, $reminder_template) { if ($invitation->contact && !$invitation->contact->trashed() && $invitation->contact->email) { EmailEntity::dispatch($invitation, $invitation->company, $reminder_template); - nrlog("Firing reminder email for invoice {$quote->number} - {$reminder_template}"); + nrlog("Firing reminder email for quote {$quote->number} - {$reminder_template}"); $quote->entityEmailEvent($invitation, $reminder_template); - $quote->sendEvent(Webhook::EVENT_REMIND_INVOICE, "client"); + $quote->sendEvent(Webhook::EVENT_REMIND_QUOTE, "client"); } }); } @@ -162,162 +155,4 @@ class QuoteReminderJob implements ShouldQueue } } - private function addFeeToNewQuote(Quote $over_due_quote, string $reminder_template, array $fees) - { - - $amount = $fees[0]; - $percent = $fees[1]; - - $quote = false; - - //2024-06-07 this early return prevented any reminders from sending for users who enabled lock_invoices. - if ($amount > 0 || $percent > 0) { - // return; - - $fee = $amount; - - if ($over_due_quote->partial > 0) { - $fee += round($over_due_quote->partial * $percent / 100, 2); - } else { - $fee += round($over_due_quote->balance * $percent / 100, 2); - } - - /** @var \App\Models\Invoice $quote */ - $quote = InvoiceFactory::create($over_due_quote->company_id, $over_due_quote->user_id); - $quote->client_id = $over_due_quote->client_id; - $quote->date = now()->format('Y-m-d'); - $quote->due_date = now()->format('Y-m-d'); - - $quote_item = new InvoiceItem(); - $quote_item->type_id = '5'; - $quote_item->product_key = trans('texts.fee'); - $quote_item->notes = ctrans('texts.late_fee_added_locked_invoice', ['invoice' => $over_due_quote->number, 'date' => $this->translateDate(now()->startOfDay(), $over_due_invoice->client->date_format(), $over_due_invoice->client->locale())]); - $quote_item->quantity = 1; - $quote_item->cost = $fee; - - $quote_items = []; - $quote_items[] = $quote_item; - - $quote->line_items = $quote_items; - - /**Refresh Invoice values*/ - $quote = $quote->calc()->getInvoice(); - $quote->service() - ->createInvitations() - ->applyNumber() - ->markSent() - ->save(); - } - - if(!$quote) { - $quote = $over_due_quote; - } - - $enabled_reminder = 'enable_'.$reminder_template; - // if ($reminder_template == 'endless_reminder') { - // $enabled_reminder = 'enable_reminder_endless'; - // } - - if (in_array($reminder_template, ['reminder1', 'reminder2', 'reminder3', 'reminder_endless', 'endless_reminder']) && - $quote->client->getSetting($enabled_reminder) && - $quote->client->getSetting('send_reminders') && - (Ninja::isSelfHost() || $quote->company->account->isPaidHostedClient())) { - $quote->invitations->each(function ($invitation) use ($quote, $reminder_template) { - if ($invitation->contact && !$invitation->contact->trashed() && $invitation->contact->email) { - EmailEntity::dispatch($invitation, $invitation->company, $reminder_template); - nrlog("Firing reminder email for qipte {$quote->number} - {$reminder_template}"); - event(new QuoteReminderWasEmailed($invitation, $invitation->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null), $reminder_template)); - $quote->sendEvent(Webhook::EVENT_REMIND_QUOTE, "client"); - } - }); - } - - $quote->service()->setReminder()->save(); - - } - - /** - * Calculates the late if - if any - and rebuilds the invoice - * - * @param Invoice $quote - * @param string $template - * @return array - */ - private function calcLateFee($quote, $template): array - { - $late_fee_amount = 0; - $late_fee_percent = 0; - - switch ($template) { - case 'reminder1': - $late_fee_amount = $quote->client->getSetting('late_fee_amount1'); - $late_fee_percent = $quote->client->getSetting('late_fee_percent1'); - break; - case 'reminder2': - $late_fee_amount = $quote->client->getSetting('late_fee_amount2'); - $late_fee_percent = $quote->client->getSetting('late_fee_percent2'); - break; - case 'reminder3': - $late_fee_amount = $quote->client->getSetting('late_fee_amount3'); - $late_fee_percent = $quote->client->getSetting('late_fee_percent3'); - break; - case 'endless_reminder': - $late_fee_amount = $quote->client->getSetting('late_fee_endless_amount'); - $late_fee_percent = $quote->client->getSetting('late_fee_endless_percent'); - break; - default: - $late_fee_amount = 0; - $late_fee_percent = 0; - break; - } - - return [$late_fee_amount, $late_fee_percent]; - } - - /** - * Applies the late fee to the invoice line items - * - * @param Invoice $quote - * @param float $amount The fee amount - * @param float $percent The fee percentage amount - * - * @return Invoice - */ - private function setLateFee($quote, $amount, $percent): Invoice - { - - $temp_invoice_balance = $quote->balance; - - if ($amount <= 0 && $percent <= 0) { - return $quote; - } - - $fee = $amount; - - if ($quote->partial > 0) { - $fee += round($quote->partial * $percent / 100, 2); - } else { - $fee += round($quote->balance * $percent / 100, 2); - } - - $quote_item = new InvoiceItem(); - $quote_item->type_id = '5'; - $quote_item->product_key = trans('texts.fee'); - $quote_item->notes = ctrans('texts.late_fee_added', ['date' => $this->translateDate(now()->startOfDay(), $quote->client->date_format(), $quote->client->locale())]); - $quote_item->quantity = 1; - $quote_item->cost = $fee; - - $quote_items = $quote->line_items; - $quote_items[] = $quote_item; - - $quote->line_items = $quote_items; - - /**Refresh Invoice values*/ - $quote = $quote->calc()->getInvoice(); - - $quote->ledger()->updateInvoiceBalance($quote->balance - $temp_invoice_balance, "Late Fee Adjustment for invoice {$quote->number}"); - $quote->client->service()->calculateBalance(); - - return $quote; - } } diff --git a/app/Models/Quote.php b/app/Models/Quote.php index b0f7a17f6d53..5c9780b65df8 100644 --- a/app/Models/Quote.php +++ b/app/Models/Quote.php @@ -397,28 +397,50 @@ class Quote extends BaseModel */ public function calculateTemplate(string $entity_string): string { - return $entity_string; + + $client = $this->client; + + if ($entity_string != 'quote') { + return $entity_string; + } + + if ($this->inReminderWindow( + $client->getSetting('quote_schedule_reminder1'), + $client->getSetting('quote_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->reminder_last_sent, + // $client->getSetting('endless_reminder_frequency_id') + // )) { + // return 'endless_reminder'; + } else { + return $entity_string; + } + } /** - * isPayable - proxy for matching Invoice status as - * to whether the quote is still valid, allows - * reuse of UpdateReminder class - * * @return bool */ - public function isPayable(): bool + public function isRemindable(): bool { - if ($this->status_id == self::STATUS_SENT && $this->is_deleted == false && $this->due_date->gte(now()->addSeconds($this->timezone_offset()))) { - return true; - } elseif ($this->status_id == self::STATUS_DRAFT || $this->is_deleted) { + if (in_array($this->status_id, [self::STATUS_DRAFT, self::STATUS_APPROVED, self::STATUS_CONVERTED]) || $this->is_deleted) return false; - } elseif (in_array($this->status_id, [self::STATUS_APPROVED, self::STATUS_CONVERTED])) { - return false; - } else { - return false; - } + + return true; + } } diff --git a/app/Services/Invoice/UpdateReminder.php b/app/Services/Invoice/UpdateReminder.php index 2d861632d2f8..c47736a77645 100644 --- a/app/Services/Invoice/UpdateReminder.php +++ b/app/Services/Invoice/UpdateReminder.php @@ -19,7 +19,7 @@ use Carbon\Carbon; class UpdateReminder extends AbstractService { - public function __construct(public Invoice | Quote $invoice, public mixed $settings = null) + public function __construct(public Invoice $invoice, public mixed $settings = null) { } diff --git a/app/Services/Quote/QuoteService.php b/app/Services/Quote/QuoteService.php index cdc1b1dccd6c..ce1a5d5da01b 100644 --- a/app/Services/Quote/QuoteService.php +++ b/app/Services/Quote/QuoteService.php @@ -17,7 +17,7 @@ use App\Jobs\EDocument\CreateEDocument; use App\Models\Project; use App\Models\Quote; use App\Repositories\QuoteRepository; -use App\Services\Invoice\UpdateReminder; +use App\Services\Quote\UpdateReminder; use App\Utils\Ninja; use App\Utils\Traits\MakesHash; use Illuminate\Support\Facades\Storage; @@ -266,6 +266,41 @@ class QuoteService return $this; } + + /*When a reminder is sent we want to touch the dates they were sent*/ + public function touchReminder(string $reminder_template) + { + nrlog(now()->format('Y-m-d h:i:s') . " INV #{$this->quote->number} : Touching Reminder => {$reminder_template}"); + switch ($reminder_template) { + case 'reminder1': + $this->quote->reminder1_sent = now(); + $this->quote->reminder_last_sent = now(); + $this->quote->last_sent_date = now(); + break; + case 'reminder2': + $this->quote->reminder2_sent = now(); + $this->quote->reminder_last_sent = now(); + $this->quote->last_sent_date = now(); + break; + case 'reminder3': + $this->quote->reminder3_sent = now(); + $this->quote->reminder_last_sent = now(); + $this->quote->last_sent_date = now(); + break; + case 'endless_reminder': + $this->quote->reminder_last_sent = now(); + $this->invoice->last_sent_date = now(); + break; + default: + $this->quote->reminder1_sent = now(); + $this->quote->reminder_last_sent = now(); + $this->quote->last_sent_date = now(); + break; + } + + return $this; + } + /** * Saves the quote. * @return Quote|null diff --git a/app/Services/Quote/UpdateReminder.php b/app/Services/Quote/UpdateReminder.php new file mode 100644 index 000000000000..71e9fe68b54d --- /dev/null +++ b/app/Services/Quote/UpdateReminder.php @@ -0,0 +1,209 @@ +settings) { + $this->settings = $this->quote->client->getMergedSettings(); + } + + if (! $this->quote->isRemindable()) { + $this->quote->next_send_date = null; + $this->quote->saveQuietly(); + + return $this->quote; //exit early + } + + if ($this->quote->next_send_date) { + $this->quote->next_send_date = null; + } + + $offset = $this->quote->client->timezone_offset(); + + $date_collection = collect(); + + if (is_null($this->quote->reminder1_sent) && + $this->settings->schedule_reminder1 == 'after_quote_date') { + $reminder_date = Carbon::parse($this->quote->date)->startOfDay()->addDays($this->settings->num_days_reminder1); + + if ($reminder_date->gt(now())) { + $date_collection->push($reminder_date); + } + } + + if (is_null($this->quote->reminder1_sent) && + ($this->quote->partial_due_date || $this->quote->due_date) && + $this->settings->schedule_reminder1 == 'before_valid_until_date') { + $partial_or_due_date = ($this->quote->partial > 0 && isset($this->quote->partial_due_date)) ? $this->quote->partial_due_date : $this->quote->due_date; + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->subDays($this->settings->num_days_reminder1); + // nlog("1. {$reminder_date->format('Y-m-d')}"); + + if ($reminder_date->gt(now())) { + $date_collection->push($reminder_date); + } + } + + if (is_null($this->quote->reminder1_sent) && + ($this->quote->partial_due_date || $this->quote->due_date) && + $this->settings->schedule_reminder1 == 'after_valid_until_date') { + + $partial_or_due_date = ($this->quote->partial > 0 && isset($this->quote->partial_due_date)) ? $this->quote->partial_due_date : $this->quote->due_date; + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->addDays($this->settings->num_days_reminder1); + // nlog("2. {$reminder_date->format('Y-m-d')}"); + + if ($reminder_date->gt(now())) { + $date_collection->push($reminder_date); + } + } + + if (is_null($this->quote->reminder2_sent) && + $this->settings->schedule_reminder2 == 'after_valid_until_date') { + $reminder_date = Carbon::parse($this->quote->date)->startOfDay()->addDays($this->settings->num_days_reminder2); + + if ($reminder_date->gt(now())) { + $date_collection->push($reminder_date); + } + } + + if (is_null($this->quote->reminder2_sent) && + ($this->quote->partial_due_date || $this->quote->due_date) && + $this->settings->schedule_reminder2 == 'before_valid_until_date') { + + $partial_or_due_date = ($this->quote->partial > 0 && isset($this->quote->partial_due_date)) ? $this->quote->partial_due_date : $this->quote->due_date; + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->subDays($this->settings->num_days_reminder2); + // nlog("3. {$reminder_date->format('Y-m-d')}"); + + if ($reminder_date->gt(now())) { + $date_collection->push($reminder_date); + } + } + + if (is_null($this->quote->reminder2_sent) && + ($this->quote->partial_due_date || $this->quote->due_date) && + $this->settings->schedule_reminder2 == 'after_valid_until_date') { + + $partial_or_due_date = ($this->quote->partial > 0 && isset($this->quote->partial_due_date)) ? $this->quote->partial_due_date : $this->quote->due_date; + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->addDays($this->settings->num_days_reminder2); + // nlog("4. {$reminder_date->format('Y-m-d')}"); + + if ($reminder_date->gt(now())) { + $date_collection->push($reminder_date); + } + } + + if (is_null($this->quote->reminder3_sent) && + $this->settings->schedule_reminder3 == 'after_valid_until_date') { + $reminder_date = Carbon::parse($this->quote->date)->startOfDay()->addDays($this->settings->num_days_reminder3); + + if ($reminder_date->gt(now())) { + $date_collection->push($reminder_date); + } + } + + if (is_null($this->quote->reminder3_sent) && + ($this->quote->partial_due_date || $this->quote->due_date) && + $this->settings->schedule_reminder3 == 'before_valid_until_date') { + + $partial_or_due_date = ($this->quote->partial > 0 && isset($this->quote->partial_due_date)) ? $this->quote->partial_due_date : $this->quote->due_date; + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->subDays($this->settings->num_days_reminder3); + // nlog("5. {$reminder_date->format('Y-m-d')}"); + + if ($reminder_date->gt(now())) { + $date_collection->push($reminder_date); + } + } + + if (is_null($this->quote->reminder3_sent) && + ($this->quote->partial_due_date || $this->quote->due_date) && + $this->settings->schedule_reminder3 == 'after_valid_until_date') { + + $partial_or_due_date = ($this->quote->partial > 0 && isset($this->quote->partial_due_date)) ? $this->quote->partial_due_date : $this->quote->due_date; + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->addDays($this->settings->num_days_reminder3); + // nlog("6. {$reminder_date->format('Y-m-d')}"); + + if ($reminder_date->gt(now())) { + $date_collection->push($reminder_date); + } + } + + if ($this->quote->last_sent_date && + $this->settings->enable_reminder_endless && + ($this->quote->reminder1_sent || $this->settings->schedule_reminder1 == "" || !$this->settings->enable_reminder1) && + ($this->quote->reminder2_sent || $this->settings->schedule_reminder2 == "" || !$this->settings->enable_reminder2) && + ($this->quote->reminder3_sent || $this->settings->schedule_reminder3 == "" || !$this->settings->enable_reminder3)) { + $reminder_date = $this->addTimeInterval($this->quote->last_sent_date, (int) $this->settings->endless_reminder_frequency_id); + + if ($reminder_date) { + if ($reminder_date->gt(now())) { + $date_collection->push($reminder_date); + } + } + } + + if ($date_collection->count() >= 1 && $date_collection->sort()->first()->gte(now())) { + $this->quote->next_send_date = $date_collection->sort()->first()->addSeconds($offset); + } else { + $this->quote->next_send_date = null; + } + + return $this->quote; + } + + 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: + return Carbon::parse($date)->addWeeks(2)->startOfDay(); + case RecurringInvoice::FREQUENCY_FOUR_WEEKS: + return Carbon::parse($date)->addWeeks(4)->startOfDay(); + case RecurringInvoice::FREQUENCY_MONTHLY: + return Carbon::parse($date)->addMonthNoOverflow()->startOfDay(); + case RecurringInvoice::FREQUENCY_TWO_MONTHS: + return Carbon::parse($date)->addMonthsNoOverflow(2)->startOfDay(); + case RecurringInvoice::FREQUENCY_THREE_MONTHS: + return Carbon::parse($date)->addMonthsNoOverflow(3)->startOfDay(); + case RecurringInvoice::FREQUENCY_FOUR_MONTHS: + return Carbon::parse($date)->addMonthsNoOverflow(4)->startOfDay(); + case RecurringInvoice::FREQUENCY_SIX_MONTHS: + return Carbon::parse($date)->addMonthsNoOverflow(6)->startOfDay(); + case RecurringInvoice::FREQUENCY_ANNUALLY: + return Carbon::parse($date)->addYear()->startOfDay(); + case RecurringInvoice::FREQUENCY_TWO_YEARS: + return Carbon::parse($date)->addYears(2)->startOfDay(); + case RecurringInvoice::FREQUENCY_THREE_YEARS: + return Carbon::parse($date)->addYears(3)->startOfDay(); + default: + return null; + } + } +} diff --git a/phpstan.neon b/phpstan.neon index 718f0385499c..5066456a20bb 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -30,4 +30,5 @@ parameters: - '#Socialite#' - '#Access to protected property#' - '#Call to undefined method .*#' - - '#Argument of an invalid type stdClass supplied for foreach, only iterables are supported.#' \ No newline at end of file + - '#Argument of an invalid type stdClass supplied for foreach, only iterables are supported.#' + - '#Comparison operation ">=" between int<1, max> and 1 is always true#' \ No newline at end of file diff --git a/tests/Feature/QuoteReminderTest.php b/tests/Feature/QuoteReminderTest.php new file mode 100644 index 000000000000..bba3097503f7 --- /dev/null +++ b/tests/Feature/QuoteReminderTest.php @@ -0,0 +1,188 @@ +withoutMiddleware( + ThrottleRequests::class + ); + + $this->faker = \Faker\Factory::create(); + + Model::reguard(); + + $this->makeTestData(); + + $this->withoutExceptionHandling(); + } + public $company; + + public $user; + + public $payload; + + public $account; + + public $client; + + public $token; + + public $cu; + + public $invoice; + + private function buildData($settings = null) + { + $this->account = Account::factory()->create([ + 'hosted_client_count' => 1000, + 'hosted_company_count' => 1000, + ]); + + $this->account->num_users = 3; + $this->account->save(); + + $this->user = User::factory()->create([ + 'account_id' => $this->account->id, + 'confirmation_code' => 'xyz123', + 'email' => $this->faker->unique()->safeEmail(), + ]); + + if(!$settings) { + $settings = CompanySettings::defaults(); + $settings->client_online_payment_notification = false; + $settings->client_manual_payment_notification = false; + } + + $this->company = Company::factory()->create([ + 'account_id' => $this->account->id, + 'settings' => $settings, + ]); + + $this->company->settings = $settings; + $this->company->save(); + + $this->cu = CompanyUserFactory::create($this->user->id, $this->company->id, $this->account->id); + $this->cu->is_owner = true; + $this->cu->is_admin = true; + $this->cu->is_locked = false; + $this->cu->save(); + + $this->token = \Illuminate\Support\Str::random(64); + + $company_token = new CompanyToken; + $company_token->user_id = $this->user->id; + $company_token->company_id = $this->company->id; + $company_token->account_id = $this->account->id; + $company_token->name = 'test token'; + $company_token->token = $this->token; + $company_token->is_system = true; + + $company_token->save(); + + $this->client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'is_deleted' => 0, + 'name' => 'bob', + 'address1' => '1234', + 'balance' => 100, + 'paid_to_date' => 50, + ]); + + ClientContact::factory()->create([ + 'user_id' => $this->user->id, + 'client_id' => $this->client->id, + 'company_id' => $this->company->id, + 'is_primary' => 1, + 'first_name' => 'john', + 'last_name' => 'doe', + 'email' => 'john@doe.com', + 'send_email' => true, + ]); + + $this->quote = Quote::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'client_id' => $this->client->id, + 'date' => now()->addSeconds($this->client->timezone_offset())->format('Y-m-d'), + 'next_send_date' => null, + 'due_date' => Carbon::now()->addSeconds($this->client->timezone_offset())->addDays(5)->format('Y-m-d'), + 'last_sent_date' => now()->addSeconds($this->client->timezone_offset()), + 'reminder_last_sent' => null, + 'status_id' => 2, + 'amount' => 10, + 'balance' => 10, + ]); + + } + + + public function testReminderInThePast() + { + + $translations = new \stdClass; + $translations->late_fee_added = "Fee added :date"; + + $settings = $this->company->settings; + $settings->enable_quote_reminder1 = false; + $settings->quote_schedule_reminder1 = ''; + $settings->quote_num_days_reminder1 = 1; + + $this->buildData(($settings)); + + $this->quote->date = now()->subMonths(2)->format('Y-m-d'); + $this->quote->due_date = now()->subMonth()->format('Y-m-d'); + $this->quote->last_sent_date = now(); + $this->quote->next_send_date = null; + + $this->quote->service()->setReminder($settings)->save(); + + $this->quote = $this->quote->fresh(); + + $this->assertNull($this->quote->next_send_date); + } + +}