diff --git a/app/Events/Payment/PaymentWasEmailed.php b/app/Events/Payment/PaymentWasEmailed.php new file mode 100644 index 000000000000..d828b105e91e --- /dev/null +++ b/app/Events/Payment/PaymentWasEmailed.php @@ -0,0 +1,37 @@ +payment = $payment; + } +} diff --git a/app/Events/Payment/PaymentWasEmailedAndFailed.php b/app/Events/Payment/PaymentWasEmailedAndFailed.php new file mode 100644 index 000000000000..5a56119fa956 --- /dev/null +++ b/app/Events/Payment/PaymentWasEmailedAndFailed.php @@ -0,0 +1,45 @@ +payment = $payment; + + $this->errors = $errors; + } +} diff --git a/app/Events/Quote/QuoteWasEmailedAndFailed.php b/app/Events/Quote/QuoteWasEmailedAndFailed.php new file mode 100644 index 000000000000..97075e678692 --- /dev/null +++ b/app/Events/Quote/QuoteWasEmailedAndFailed.php @@ -0,0 +1,45 @@ +quote = $quote; + + $this->errors = $errors; + } +} diff --git a/app/Jobs/Payment/EmailPayment.php b/app/Jobs/Payment/EmailPayment.php new file mode 100644 index 000000000000..dccb94d2bb0f --- /dev/null +++ b/app/Jobs/Payment/EmailPayment.php @@ -0,0 +1,92 @@ +payment = $payment; + + $this->company = $company; + } + + /** + * Execute the job. + * + * + * @return void + */ + public function handle() + { + /*Jobs are not multi-db aware, need to set! */ + MultiDB::setDB($this->company->db); + + //todo - change runtime config of mail driver if necessary + + $template_style = $this->payment->client->getSetting('email_style'); + + $this->payment->client->contacts->each(function ($contact) use ($template_style) { + if ($contact->email) { + $message_array = $this->payment->getEmailData('', $contact); + $message_array['title'] = &$message_array['subject']; + $message_array['footer'] = "Sent to ".$contact->present()->name(); + + //change the runtime config of the mail provider here: + + //send message + Mail::to($contact->email, $contact->present()->name()) + ->send(new TemplateEmail($message_array, $template_style, $contact->user, $contact->client)); + + if (count(Mail::failures()) > 0) { + event(new PaymentWasEmailedAndFailed($this->payment, Mail::failures())); + + return $this->logMailError($errors); + } + + //fire any events + event(new PaymentWasEmailed($this->payment)); + + //sleep(5); + } + }); + } + + private function logMailError($errors) + { + SystemLogger::dispatch( + $errors, + SystemLog::CATEGORY_MAIL, + SystemLog::EVENT_MAIL_SEND, + SystemLog::TYPE_FAILURE, + $this->payment->client + ); + } +} diff --git a/app/Jobs/Quote/EmailQuote.php b/app/Jobs/Quote/EmailQuote.php new file mode 100644 index 000000000000..9f94d2eb8fc5 --- /dev/null +++ b/app/Jobs/Quote/EmailQuote.php @@ -0,0 +1,92 @@ +quote = $quote; + + $this->company = $company; + } + + /** + * Execute the job. + * + * + * @return void + */ + public function handle() + { + /*Jobs are not multi-db aware, need to set! */ + MultiDB::setDB($this->company->db); + + //todo - change runtime config of mail driver if necessary + + $template_style = $this->quote->client->getSetting('email_style'); + + $this->quote->invitations->each(function ($invitation) use ($template_style) { + if ($invitation->contact->email) { + $message_array = $this->quote->getEmailData('', $invitation->contact); + $message_array['title'] = &$message_array['subject']; + $message_array['footer'] = "Sent to ".$invitation->contact->present()->name(); + + //change the runtime config of the mail provider here: + + //send message + Mail::to($invitation->contact->email, $invitation->contact->present()->name()) + ->send(new TemplateEmail($message_array, $template_style, $invitation->contact->user, $invitation->contact->client)); + + if (count(Mail::failures()) > 0) { + event(new QuoteWasEmailedAndFailed($this->quote, Mail::failures())); + + return $this->logMailError($errors); + } + + //fire any events + event(new QuoteWasEmailed($this->quote)); + + //sleep(5); + } + }); + } + + private function logMailError($errors) + { + SystemLogger::dispatch( + $errors, + SystemLog::CATEGORY_MAIL, + SystemLog::EVENT_MAIL_SEND, + SystemLog::TYPE_FAILURE, + $this->quote->client + ); + } +} diff --git a/app/Repositories/QuoteRepository.php b/app/Repositories/QuoteRepository.php index 2f7791566521..cf281b036fe2 100644 --- a/app/Repositories/QuoteRepository.php +++ b/app/Repositories/QuoteRepository.php @@ -29,15 +29,15 @@ class QuoteRepository extends BaseRepository { use MakesHash; - + public function getClassName() { return Quote::class; } - + public function save($data, Quote $quote) : ?Quote { - + /* Always carry forward the initial invoice amount this is important for tracking client balance changes later......*/ $starting_amount = $quote->amount; @@ -85,16 +85,16 @@ class QuoteRepository extends BaseRepository /* If no invitations have been created, this is our fail safe to maintain state*/ if ($quote->invitations->count() == 0) { - CreateQuoteInvitations::dispatchNow($quote, $quote->company); + $quote->service()->createInvitations(); } $quote = $quote->calc()->getInvoice(); - + $quote->save(); $finished_amount = $quote->amount; - $quote = ApplyQuoteNumber::dispatchNow($quote, $quote->client->getMergedSettings(), $quote->company); + $quote = $quote->service()->applyNumber()->save(); return $quote->fresh(); } diff --git a/app/Utils/Traits/PaymentEmailBuilder.php b/app/Utils/Traits/PaymentEmailBuilder.php new file mode 100644 index 000000000000..d44fb0d2de92 --- /dev/null +++ b/app/Utils/Traits/PaymentEmailBuilder.php @@ -0,0 +1,84 @@ +client; + + + + //Need to determine which email template we are producing + return $this->generateTemplateData($reminder_template, $contact); + } + + private function generateTemplateData(string $reminder_template, $contact) :array + { + $data = []; + + $client = $this->client; + + $body_template = $client->getSetting('email_template_'.$reminder_template); + + /* Use default translations if a custom message has not been set*/ + if (iconv_strlen($body_template) == 0) { + $body_template = trans('texts.payment_message', ['amount'=>$this->present()->amount(),'account'=>$this->company->present()->name()], null, $this->client->locale()); + } + + $subject_template = $client->getSetting('payment_subject'); + + if (iconv_strlen($subject_template) == 0) { + $subject_template = trans('texts.invoice_subject', ['number'=>$this->present()->invoice_number(),'account'=>$this->company->present()->name()], null, $this->client->locale()); + } + + $data['body'] = $this->parseTemplate($body_template, false, $contact); + $data['subject'] = $this->parseTemplate($subject_template, true, $contact); + + if ($client->getSetting('pdf_email_attachment') !== false) { + $data['files'][] = $this->pdf_file_path(); + } + + return $data; + } + + private function parseTemplate(string $template_data, bool $is_markdown = true, $contact) :string + { + $invoice_variables = $this->makeValues($contact); + + //process variables + $data = str_replace(array_keys($invoice_variables), array_values($invoice_variables), $template_data); + + //process markdown + if ($is_markdown) { + //$data = Parsedown::instance()->line($data); + + $converter = new CommonMarkConverter([ + 'html_input' => 'strip', + 'allow_unsafe_links' => false, + ]); + + $data = $converter->convertToHtml($data); + } + + return $data; + } +} diff --git a/app/Utils/Traits/QuoteEmailBuilder.php b/app/Utils/Traits/QuoteEmailBuilder.php new file mode 100644 index 000000000000..a01fee8566cb --- /dev/null +++ b/app/Utils/Traits/QuoteEmailBuilder.php @@ -0,0 +1,129 @@ +client; + + if (!$reminder_template) { + $reminder_template = $this->calculateTemplate(); + } + + //Need to determine which email template we are producing + return $this->generateTemplateData($reminder_template, $contact); + } + + private function generateTemplateData(string $reminder_template, $contact) :array + { + $data = []; + + $client = $this->client; + + $body_template = $client->getSetting('email_template_'.$reminder_template); + + /* Use default translations if a custom message has not been set*/ + if (iconv_strlen($body_template) == 0) { + $body_template = trans('texts.quote_message', ['amount'=>$this->present()->amount(),'account'=>$this->company->present()->name()], null, $this->client->locale()); + } + + $subject_template = $client->getSetting('email_subject_'.$reminder_template); + + if (iconv_strlen($subject_template) == 0) { + if ($reminder_template == 'quote') { + $subject_template = trans('texts.quote_subject', ['number'=>$this->present()->invoice_number(),'account'=>$this->company->present()->name()], null, $this->client->locale()); + } else { + $subject_template = trans('texts.reminder_subject', ['number'=>$this->present()->invoice_number(),'account'=>$this->company->present()->name()], null, $this->client->locale()); + } + } + + $data['body'] = $this->parseTemplate($body_template, false, $contact); + $data['subject'] = $this->parseTemplate($subject_template, true, $contact); + + if ($client->getSetting('pdf_email_attachment') !== false) { + $data['files'][] = $this->pdf_file_path(); + } + + return $data; + } + + private function parseTemplate(string $template_data, bool $is_markdown = true, $contact) :string + { + $quote_variables = $this->makeValues($contact); + + //process variables + $data = str_replace(array_keys($quote_variables), array_values($quote_variables), $template_data); + + //process markdown + if ($is_markdown) { + //$data = Parsedown::instance()->line($data); + + $converter = new CommonMarkConverter([ + 'html_input' => 'strip', + 'allow_unsafe_links' => false, + ]); + + $data = $converter->convertToHtml($data); + } + + return $data; + } + + private function calculateTemplate() :string + { + //if invoice is currently a draft, or being marked as sent, this will be the initial email + $client = $this->client; + + //if the invoice + if ($this->status_id == Quote::STATUS_DRAFT || Carbon::parse($this->due_date) > now()) { + return 'quote'; + } elseif ($client->getSetting('enable_reminder1') !== false && $this->inReminderWindow($client->getSetting('schedule_reminder1'), $client->getSetting('num_days_reminder1'))) { + return 'template1'; + } elseif ($client->getSetting('enable_reminder2') !== false && $this->inReminderWindow($client->getSetting('schedule_reminder2'), $client->getSetting('num_days_reminder2'))) { + return 'template2'; + } elseif ($client->getSetting('enable_reminder3') !== false && $this->inReminderWindow($client->getSetting('schedule_reminder3'), $client->getSetting('num_days_reminder3'))) { + return 'template3'; + } else { + return 'quote'; + } + + //also implement endless reminders here + } + + private function inReminderWindow($schedule_reminder, $num_days_reminder) + { + switch ($schedule_reminder) { + case 'after_invoice_date': + return Carbon::parse($this->date)->addDays($num_days_reminder)->startOfDay()->eq(Carbon::now()->startOfDay()); + break; + case 'before_due_date': + return Carbon::parse($this->due_date)->subDays($num_days_reminder)->startOfDay()->eq(Carbon::now()->startOfDay()); + break; + case 'after_due_date': + return Carbon::parse($this->due_date)->addDays($num_days_reminder)->startOfDay()->eq(Carbon::now()->startOfDay()); + break; + default: + # code... + break; + } + } +}