From 0a02323792e1e6d2298b73351f1efcb599c9d320 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 28 Oct 2020 10:02:32 +1100 Subject: [PATCH 1/3] Quote email engine --- app/Events/Quote/QuoteWasViewed.php | 1 - app/Http/Controllers/CreditController.php | 9 +- app/Http/Controllers/EmailController.php | 14 ++- app/Http/Controllers/InvoiceController.php | 2 +- app/Http/Requests/Task/UpdateTaskRequest.php | 13 +-- .../ValidationRules/User/RelatedUserRule.php | 4 + app/Jobs/Util/ReminderJob.php | 2 +- app/Mail/Engine/QuoteEmailEngine.php | 90 +++++++++++++++++++ app/Models/Task.php | 1 + app/Services/Credit/CreditService.php | 9 ++ app/Services/Credit/SendEmail.php | 60 +++++++++++++ app/Services/Invoice/SendEmail.php | 5 +- app/Services/Quote/SendEmail.php | 5 +- tests/Integration/SendFailedEmailsTest.php | 7 +- 14 files changed, 195 insertions(+), 27 deletions(-) create mode 100644 app/Mail/Engine/QuoteEmailEngine.php create mode 100644 app/Services/Credit/SendEmail.php diff --git a/app/Events/Quote/QuoteWasViewed.php b/app/Events/Quote/QuoteWasViewed.php index 725964994a..8045299083 100644 --- a/app/Events/Quote/QuoteWasViewed.php +++ b/app/Events/Quote/QuoteWasViewed.php @@ -12,7 +12,6 @@ namespace App\Events\Quote; use App\Models\Company; -use App\Models\QuoteWasViewed; use Illuminate\Queue\SerializesModels; /** diff --git a/app/Http/Controllers/CreditController.php b/app/Http/Controllers/CreditController.php index 720a7f04a3..7b7800974c 100644 --- a/app/Http/Controllers/CreditController.php +++ b/app/Http/Controllers/CreditController.php @@ -16,6 +16,7 @@ use App\Http\Requests\Credit\ShowCreditRequest; use App\Http\Requests\Credit\StoreCreditRequest; use App\Http\Requests\Credit\UpdateCreditRequest; use App\Jobs\Credit\StoreCredit; +use App\Jobs\Entity\EmailEntity; use App\Jobs\Invoice\EmailCredit; use App\Jobs\Invoice\MarkInvoicePaid; use App\Models\Client; @@ -545,7 +546,13 @@ class CreditController extends BaseController } break; case 'email': - 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) { + EmailEntity::dispatch($invitation, $credit->company); + }); + + if (! $bulk) { return response()->json(['message'=>'email sent'], 200); } diff --git a/app/Http/Controllers/EmailController.php b/app/Http/Controllers/EmailController.php index f3e069e857..e99d92f0e2 100644 --- a/app/Http/Controllers/EmailController.php +++ b/app/Http/Controllers/EmailController.php @@ -19,10 +19,12 @@ use App\Jobs\Mail\EntitySentMailer; use App\Models\Credit; use App\Models\Invoice; use App\Models\Quote; +use App\Models\RecurringInvoice; use App\Notifications\SendGenericNotification; use App\Transformers\CreditTransformer; use App\Transformers\InvoiceTransformer; use App\Transformers\QuoteTransformer; +use App\Transformers\RecurringInvoiceTransformer; use App\Utils\Traits\MakesHash; class EmailController extends BaseController @@ -131,21 +133,27 @@ class EmailController extends BaseController EntitySentMailer::dispatch($invitation, $entity_string, $entity_obj->user, $invitation->company); - if ($this instanceof Invoice) { + if ($entity_obj instanceof Invoice) { $this->entity_type = Invoice::class; $this->entity_transformer = InvoiceTransformer::class; } - if ($this instanceof Quote) { + if ($entity_obj instanceof Quote) { $this->entity_type = Quote::class; $this->entity_transformer = QuoteTransformer::class; } - if ($this instanceof Credit) { + if ($entity_obj instanceof Credit) { $this->entity_type = Credit::class; $this->entity_transformer = CreditTransformer::class; } + if ($entity_obj instanceof RecurringInvoice) { + $this->entity_type = RecurringInvoice::class; + $this->entity_transformer = RecurringInvoiceTransformer::class; + } + + $entity_obj->service()->markSent()->save(); return $this->itemResponse($entity_obj); diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 26cf34d647..5cfc5dbdc5 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -706,7 +706,7 @@ class InvoiceController extends BaseController if (request()->has('email_type') && property_exists($invoice->company->settings, request()->input('email_type'))) { $this->reminder_template = $invoice->client->getSetting(request()->input('email_type')); } else { - $this->reminder_template = $invoice->calculateTemplate(); + $this->reminder_template = $invoice->calculateTemplate('invoice'); } //touch reminder1,2,3_sent + last_sent here if the email is a reminder. diff --git a/app/Http/Requests/Task/UpdateTaskRequest.php b/app/Http/Requests/Task/UpdateTaskRequest.php index a7f2b3311f..902e51a60c 100644 --- a/app/Http/Requests/Task/UpdateTaskRequest.php +++ b/app/Http/Requests/Task/UpdateTaskRequest.php @@ -40,25 +40,14 @@ class UpdateTaskRequest extends Request /* Ensure we have a client name, and that all emails are unique*/ if ($this->input('number')) { - $rules['number'] = 'unique:tasks,number,'.$this->id.',id,company_id,'.$this->taskss->company_id; + $rules['number'] = 'unique:tasks,number,'.$this->id.',id,company_id,'.$this->task->company_id; } return $this->globalRules($rules); } - // public function messages() - // { - // return [ - // 'unique' => ctrans('validation.unique', ['attribute' => 'email']), - // 'email' => ctrans('validation.email', ['attribute' => 'email']), - // 'name.required' => ctrans('validation.required', ['attribute' => 'name']), - // 'required' => ctrans('validation.required', ['attribute' => 'email']), - // ]; - // } - protected function prepareForValidation() { - $input = $this->decodePrimaryKeys($this->all()); $this->replace($input); diff --git a/app/Http/ValidationRules/User/RelatedUserRule.php b/app/Http/ValidationRules/User/RelatedUserRule.php index 03fa086299..82ecbc6d8a 100644 --- a/app/Http/ValidationRules/User/RelatedUserRule.php +++ b/app/Http/ValidationRules/User/RelatedUserRule.php @@ -50,6 +50,10 @@ class RelatedUserRule implements Rule */ private function checkUserIsRelated($user_id) : bool { + + if(empty($user_id)) + return true; + return User::query() ->where('id', $user_id) ->where('account_id', auth()->user()->company()->account_id) diff --git a/app/Jobs/Util/ReminderJob.php b/app/Jobs/Util/ReminderJob.php index 01826882b2..a49d560f90 100644 --- a/app/Jobs/Util/ReminderJob.php +++ b/app/Jobs/Util/ReminderJob.php @@ -66,7 +66,7 @@ class ReminderJob implements ShouldQueue if ($invoice->isPayable()) { - $reminder_template = $invoice->calculateTemplate(); + $reminder_template = $invoice->calculateTemplate('invoice'); $invoice->service()->touchReminder($this->reminder_template)->save(); $invoice->invitations->each(function ($invitation) use ($invoice) { diff --git a/app/Mail/Engine/QuoteEmailEngine.php b/app/Mail/Engine/QuoteEmailEngine.php new file mode 100644 index 0000000000..698127a988 --- /dev/null +++ b/app/Mail/Engine/QuoteEmailEngine.php @@ -0,0 +1,90 @@ +invitation = $invitation; + $this->reminder_template = $reminder_template; + $this->client = $invitation->contact->client; + $this->quote = $invitation->quote; + $this->contact = $invitation->contact; + } + + public function build() + { + + $body_template = $this->client->getSetting('email_template_'.$this->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', + [ + 'quote' => $this->quote->number, + 'company' => $this->quote->company->present()->name(), + 'amount' => Number::formatMoney($this->quote->balance, $this->client), + ], + null, + $this->client->locale() + ); + } + + $subject_template = $this->client->getSetting('email_subject_'.$this->reminder_template); + + if (iconv_strlen($subject_template) == 0) { + + $subject_template = trans( + 'texts.quote_subject', + [ + 'number' => $this->quote->number, + 'account' => $this->quote->company->present()->name(), + ], + null, + $this->client->locale() + ); + + } + + $this->setTemplate($this->client->getSetting('email_style')) + ->setContact($this->contact) + ->setVariables($this->quote->makeValues($this->contact))//move make values into the htmlengine + ->setSubject($subject_template) + ->setBody($body_template) + ->setFooter("".ctrans('texts.view_quote').'') + ->setViewLink($this->invitation->getLink()) + ->setViewText(ctrans('texts.view_quote')); + + if ($this->client->getSetting('pdf_email_attachment') !== false) { + $this->setAttachments($invitation->pdf_file_path()); + } + + return $this; + + } + +} + diff --git a/app/Models/Task.php b/app/Models/Task.php index 70cda18fd1..373ae4f548 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -26,6 +26,7 @@ class Task extends BaseModel 'client_id', 'invoice_id', 'project_id', + 'assigned_user_id', 'custom_value1', 'custom_value2', 'custom_value3', diff --git a/app/Services/Credit/CreditService.php b/app/Services/Credit/CreditService.php index cd49828145..4db7a15ae8 100644 --- a/app/Services/Credit/CreditService.php +++ b/app/Services/Credit/CreditService.php @@ -15,6 +15,7 @@ use App\Models\Credit; use App\Services\Credit\ApplyPayment; use App\Services\Credit\CreateInvitations; use App\Services\Credit\MarkSent; +use App\Services\Credit\SendEmail; class CreditService { @@ -55,6 +56,14 @@ class CreditService return $this; } + public function sendEmail($contact = null) + { + $send_email = new SendEmail($this->credit, null, $contact); + + return $send_email->run(); + } + + public function setCalculatedStatus() { diff --git a/app/Services/Credit/SendEmail.php b/app/Services/Credit/SendEmail.php new file mode 100644 index 0000000000..31e12ac6b3 --- /dev/null +++ b/app/Services/Credit/SendEmail.php @@ -0,0 +1,60 @@ +credit = $credit; + + $this->reminder_template = $reminder_template; + + $this->contact = $contact; + } + + /** + * Builds the correct template to send. + * @param string $this->reminder_template The template name ie reminder1 + * @return array + */ + public function run() + { + if (! $this->reminder_template) { + $this->reminder_template = $this->credit->calculateTemplate('credit'); + } + + $this->credit->invitations->each(function ($invitation) { + if ($invitation->contact->send_email && $invitation->contact->email) { + $email_builder = (new CreditEmail())->build($invitation, $this->reminder_template); + + // EmailCredit::dispatchNow($email_builder, $invitation, $invitation->company); + EmailEntity::dispatchNow($invitation, $invitation->company); + + } + }); + + $this->credit->service()->markSent(); + } +} diff --git a/app/Services/Invoice/SendEmail.php b/app/Services/Invoice/SendEmail.php index 293273c051..332ae1e812 100644 --- a/app/Services/Invoice/SendEmail.php +++ b/app/Services/Invoice/SendEmail.php @@ -12,6 +12,7 @@ namespace App\Services\Invoice; use App\Helpers\Email\InvoiceEmail; +use App\Jobs\Entity\EmailEntity; use App\Jobs\Invoice\EmailInvoice; use App\Models\ClientContact; use App\Models\Invoice; @@ -50,7 +51,9 @@ class SendEmail extends AbstractService $email_builder = (new InvoiceEmail())->build($invitation, $this->reminder_template); if ($invitation->contact->send_email && $invitation->contact->email) { - EmailInvoice::dispatch($email_builder, $invitation, $invitation->company); +// EmailInvoice::dispatch($email_builder, $invitation, $invitation->company); + EmailEntity::dispatchNow($invitation, $invitation->company); + } }); } diff --git a/app/Services/Quote/SendEmail.php b/app/Services/Quote/SendEmail.php index 2096a36db8..23d900c4b1 100644 --- a/app/Services/Quote/SendEmail.php +++ b/app/Services/Quote/SendEmail.php @@ -12,6 +12,7 @@ namespace App\Services\Quote; use App\Helpers\Email\QuoteEmail; +use App\Jobs\Entity\EmailEntity; use App\Jobs\Quote\EmailQuote; use App\Models\ClientContact; use App\Models\Quote; @@ -48,7 +49,9 @@ class SendEmail if ($invitation->contact->send_email && $invitation->contact->email) { $email_builder = (new QuoteEmail())->build($invitation, $this->reminder_template); - EmailQuote::dispatchNow($email_builder, $invitation, $invitation->company); + // EmailQuote::dispatchNow($email_builder, $invitation, $invitation->company); + EmailEntity::dispatchNow($invitation, $invitation->company); + } }); diff --git a/tests/Integration/SendFailedEmailsTest.php b/tests/Integration/SendFailedEmailsTest.php index b61038ef65..c7b0a4afa3 100644 --- a/tests/Integration/SendFailedEmailsTest.php +++ b/tests/Integration/SendFailedEmailsTest.php @@ -40,7 +40,7 @@ class SendFailedEmailsTest extends TestCase public function testReminderFires() { $invitation = $this->invoice->invitations->first(); - $reminder_template = $this->invoice->calculateTemplate(); + $reminder_template = $this->invoice->calculateTemplate('invoice'); $sl = [ 'entity_name' => \App\Models\InvoiceInvitation::class, @@ -63,11 +63,6 @@ class SendFailedEmailsTest extends TestCase $this->assertNotNull($sys_log); - // Queue::fake(); SendFailedEmails::dispatch(); - - //Queue::assertPushed(SendFailedEmails::class); - //Queue::assertPushed(EmailInvoice::class); - //$this->expectsJobs(EmailInvoice::class); } } From 2c40adca8dbb5171aa47358a38e88ed65cbe46f0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 28 Oct 2020 10:21:53 +1100 Subject: [PATCH 2/3] Credit Emailer --- app/Events/Credit/CreditWasViewed.php | 1 - app/Mail/Engine/CreditEmailEngine.php | 90 +++++++++++++++++++++++++ app/Repositories/ActivityRepository.php | 62 ++++++++++++++++- routes/client.php | 3 +- 4 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 app/Mail/Engine/CreditEmailEngine.php diff --git a/app/Events/Credit/CreditWasViewed.php b/app/Events/Credit/CreditWasViewed.php index 2fb428f182..d7e6a07ddd 100644 --- a/app/Events/Credit/CreditWasViewed.php +++ b/app/Events/Credit/CreditWasViewed.php @@ -12,7 +12,6 @@ namespace App\Events\Credit; use App\Models\Company; -use App\Models\CreditWasViewed; use Illuminate\Queue\SerializesModels; /** diff --git a/app/Mail/Engine/CreditEmailEngine.php b/app/Mail/Engine/CreditEmailEngine.php new file mode 100644 index 0000000000..2ee6aed084 --- /dev/null +++ b/app/Mail/Engine/CreditEmailEngine.php @@ -0,0 +1,90 @@ +invitation = $invitation; + $this->reminder_template = $reminder_template; + $this->client = $invitation->contact->client; + $this->credit = $invitation->credit; + $this->contact = $invitation->contact; + } + + public function build() + { + + $body_template = $this->client->getSetting('email_template_'.$this->reminder_template); + + /* Use default translations if a custom message has not been set*/ + if (iconv_strlen($body_template) == 0) { + $body_template = trans( + 'texts.credit_message', + [ + 'credit' => $this->credit->number, + 'company' => $this->credit->company->present()->name(), + 'amount' => Number::formatMoney($this->credit->balance, $this->client), + ], + null, + $this->client->locale() + ); + } + + $subject_template = $this->client->getSetting('email_subject_'.$this->reminder_template); + + if (iconv_strlen($subject_template) == 0) { + + $subject_template = trans( + 'texts.credit_subject', + [ + 'number' => $this->credit->number, + 'account' => $this->credit->company->present()->name(), + ], + null, + $this->client->locale() + ); + + } + + $this->setTemplate($this->client->getSetting('email_style')) + ->setContact($this->contact) + ->setVariables($this->credit->makeValues($this->contact))//move make values into the htmlengine + ->setSubject($subject_template) + ->setBody($body_template) + ->setFooter("".ctrans('texts.view_credit').'') + ->setViewLink($this->invitation->getLink()) + ->setViewText(ctrans('texts.view_credit')); + + if ($this->client->getSetting('pdf_email_attachment') !== false) { + $this->setAttachments($invitation->pdf_file_path()); + } + + return $this; + + } + +} + diff --git a/app/Repositories/ActivityRepository.php b/app/Repositories/ActivityRepository.php index 6e0173f793..349e47db19 100644 --- a/app/Repositories/ActivityRepository.php +++ b/app/Repositories/ActivityRepository.php @@ -17,11 +17,18 @@ use App\Models\Backup; use App\Models\Client; use App\Models\CompanyToken; use App\Models\Credit; +use App\Models\Design; use App\Models\Invoice; use App\Models\Quote; +use App\Models\RecurringInvoice; use App\Models\User; +use App\Utils\HtmlEngine; +use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesInvoiceHtml; use Illuminate\Support\Facades\Log; +use App\Services\PdfMaker\Design as PdfDesignModel; +use App\Services\PdfMaker\Design as PdfMakerDesign; +use App\Services\PdfMaker\PdfMaker as PdfMakerService; /** * Class for activity repository. @@ -29,7 +36,7 @@ use Illuminate\Support\Facades\Log; class ActivityRepository extends BaseRepository { use MakesInvoiceHtml; - + use MakesHash; /** * Save the Activity. * @@ -68,7 +75,7 @@ class ActivityRepository extends BaseRepository if (get_class($entity) == Invoice::class || get_class($entity) == Quote::class || get_class($entity) == Credit::class) { $contact = $entity->client->primary_contact()->first(); - $backup->html_backup = $this->generateEntityHtml($entity->getEntityDesigner(), $entity, $contact); + $backup->html_backup = $this->generateHtml($entity); $backup->amount = $entity->amount; } @@ -90,4 +97,55 @@ class ActivityRepository extends BaseRepository return false; } + + private function generateHtml($entity) + { + $entity_design_id = ''; + + if($entity instanceof Invoice || $entity instanceof RecurringInvoice){ + $entity_design_id = 'invoice_design_id'; + } + elseif($entity instanceof Quote){ + $entity_design_id = 'quote_design_id'; + } + elseif($entity instanceof Credit){ + $entity_design_id = 'credit_design_id'; + } + + $entity_design_id = $entity->design_id ? $entity->design_id : $this->decodePrimaryKey($entity->client->getSetting($entity_design_id)); + + $design = Design::find($entity_design_id); + $html = new HtmlEngine($invitation); + + if ($design->is_custom) { + $options = [ + 'custom_partials' => json_decode(json_encode($design->design), true) + ]; + $template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options); + } else { + $template = new PdfMakerDesign(strtolower($design->name)); + } + + $state = [ + 'template' => $template->elements([ + 'client' => $entity->client, + 'entity' => $entity, + 'pdf_variables' => (array) $entity->company->settings->pdf_variables, + 'products' => $design->design->product, + ]), + 'variables' => $html->generateLabelsAndValues(), + 'options' => [ + 'all_pages_header' => $entity->client->getSetting('all_pages_header'), + 'all_pages_footer' => $entity->client->getSetting('all_pages_footer'), + ], + ]; + + $maker = new PdfMakerService($state); + + return $maker->design($template) + ->build() + ->getCompiledHTML(true); + + } + } diff --git a/routes/client.php b/routes/client.php index 685482d0ed..9cf7fa3a50 100644 --- a/routes/client.php +++ b/routes/client.php @@ -57,7 +57,8 @@ Route::group(['middleware' => ['auth:contact', 'locale'], 'prefix' => 'client', Route::get('quotes/{quote}', 'ClientPortal\QuoteController@show')->name('quote.show'); Route::get('quotes/{quote_invitation}', 'ClientPortal\QuoteController@show')->name('quote.show_invitation'); - Route::resource('credits', 'ClientPortal\CreditController')->only('index', 'show'); + Route::get('credits', 'ClientPortal\CreditController@index')->name('credits.index'); + Route::get('credits/{credit}', 'ClientPortal\CreditController@show')->name('credits.show'); Route::get('client/switch_company/{contact}', 'ClientPortal\SwitchCompanyController')->name('switch_company'); From 7aed55178c8adfa3a49691486ccad35408d74af9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 28 Oct 2020 10:29:54 +1100 Subject: [PATCH 3/3] Refactor HTML generation in activities --- app/Models/BaseModel.php | 10 ---------- app/Repositories/ActivityRepository.php | 4 ++-- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php index d6ea195907..a1cb856d2c 100644 --- a/app/Models/BaseModel.php +++ b/app/Models/BaseModel.php @@ -13,7 +13,6 @@ namespace App\Models; use App\DataMapper\ClientSettings; use App\DataMapper\CompanySettings; -use App\Designs\Designer; use App\Filters\QueryFilters; use App\Models\Design; use App\Utils\Traits\MakesHash; @@ -176,15 +175,6 @@ class BaseModel extends Model ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); } - public function getEntityDesigner() - { - $design = Design::find($this->decodePrimaryKey($this->client->getSetting('invoice_design_id'))); - - $entity = strtolower(class_basename($this)); - - return new Designer($this, $design, $this->client->getSetting('pdf_variables'), $entity); - } - /** * @return string */ diff --git a/app/Repositories/ActivityRepository.php b/app/Repositories/ActivityRepository.php index 349e47db19..ec1de4df14 100644 --- a/app/Repositories/ActivityRepository.php +++ b/app/Repositories/ActivityRepository.php @@ -115,7 +115,7 @@ class ActivityRepository extends BaseRepository $entity_design_id = $entity->design_id ? $entity->design_id : $this->decodePrimaryKey($entity->client->getSetting($entity_design_id)); $design = Design::find($entity_design_id); - $html = new HtmlEngine($invitation); + $html = new HtmlEngine($entity->invitations->first()); if ($design->is_custom) { $options = [ @@ -147,5 +147,5 @@ class ActivityRepository extends BaseRepository ->getCompiledHTML(true); } - + }