* Emails

* change to user service

* refactor emails

* refactor emails

* refactor emails

* refactor emails

* emails

* emails

* emails

* emails

* emails

* emails

* emails

* emails

* emails

* emails

* Update EmailPayment.php

* Update SendEmail.php

* Update SendEmail.php

* Update SendEmail.php

* Update and rename BuildEmail.php to EmailBuilder.php

* Create InvoiceEmail

* Create QuoteEmail.php

* Rename InvoiceEmail to InvoiceEmail.php

* Create PaymentEmail.php

* Update SendEmail.php

* Update SendEmail.php

* Update SendEmail.php

* Update SendEmail.php

* Update InvoiceEmail.php

* Update EmailInvoice.php

* Update SendEmail.php

* Update TemplateEmail.php

* Update EmailBuilder.php

* Update InvoiceEmail.php

* Update QuoteEmail.php

* Update PaymentEmail.php

* Update InvoiceEmail.php

* Update QuoteEmail.php

* Update QuoteInvitation.php

* Update EmailQuote.php

* Update SendEmail.php

* Update SendEmail.php

* Update PaymentService.php

* Update PaymentEmail.php

* Update PaymentEmail.php

* Update PaymentEmail.php

* Update EmailBuilder.php

* Update PaymentEmail.php

* Update EmailPayment.php

* Update SendEmail.php

* Update InvoiceService.php

* Update SendEmail.php

* Update PaymentService.php

* Update SendEmail.php

* Update QuoteService.php

* Update EmailPayment.php

Co-authored-by: David Bomba <turbo124@gmail.com>
This commit is contained in:
michael-hampton 2020-02-15 09:01:15 +00:00 committed by GitHub
parent 7dd6f814ac
commit f7650d0692
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 887 additions and 211 deletions

View File

@ -0,0 +1,154 @@
<?php
namespace App\Helpers\Email;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Quote;
use League\CommonMark\CommonMarkConverter;
abstract class EmailBuilder
{
protected $subject;
protected $body;
protected $recipients;
protected $attachments;
protected $footer;
protected $template_style;
protected $variables = [];
protected $contact = null;
private function parseTemplate(string $data, bool $is_markdown = true, $contact = null): string
{
//process variables
if (!empty($this->variables)) {
$data = str_replace(array_keys($this->variables), array_values($this->variables), $data);
}
//process markdown
if ($is_markdown) {
//$data = Parsedown::instance()->line($data);
$converter = new CommonMarkConverter([
'html_input' => 'allow',
'allow_unsafe_links' => true,
]);
$data = $converter->convertToHtml($data);
}
return $data;
}
/**
* @param $footer
* @return $this
*/
public function setFooter($footer)
{
$this->footer = $footer;
return $this;
}
public function setVariables($variables)
{
$this->variables = $variables;
return $this;
}
/**
* @param $contact
* @return $this
*/
public function setContact($contact)
{
$this->contact = $contact;
return $this;
}
/**
* @param $subject
* @return $this
*/
public function setSubject($subject)
{
$this->subject = $this->parseTemplate($subject, false, $this->contact);
return $this;
}
/**
* @param $body
* @return $this
*/
public function setBody($body)
{
$this->parseTemplate($body, true);
return $this;
}
/**
* @param $template_style
* @return $this
*/
public function setTemplate($template_style)
{
$this->template_style = $template_style;
return $this;
}
public function setAttachments($attachments)
{
$this->attachments[] = $attachments;
}
/**
* @return mixed
*/
public function getSubject()
{
return $this->subject;
}
/**
* @return mixed
*/
public function getBody()
{
return $this->body;
}
/**
* @return mixed
*/
public function getRecipients()
{
return $this->recipients;
}
/**
* @return mixed
*/
public function getAttachments()
{
return $this->attachments;
}
/**
* @return mixed
*/
public function getFooter()
{
return $this->footer;
}
/**
* @return mixed
*/
public function getTemplate()
{
return $this->template_style;
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* Created by PhpStorm.
* User: michael.hampton
* Date: 14/02/2020
* Time: 19:51
*/
namespace App\Helpers\Email;
use App\Models\Invoice;
class InvoiceEmail extends EmailBuilder
{
public function build(Invoice $invoice, $reminder_template, $contact = null)
{
$client = $invoice->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.invoice_message',
['amount' => $invoice->present()->amount(), 'company' => $invoice->company->present()->name()], null,
$invoice->client->locale());
}
$subject_template = $client->getSetting('email_subject_' . $reminder_template);
if (iconv_strlen($subject_template) == 0) {
if ($reminder_template == 'quote') {
$subject_template = trans('texts.invoice_subject',
[
'number' => $this->invoice->present()->invoice_number(),
'company' => $invoice->company->present()->name()
],
null, $invoice->client->locale());
} else {
$subject_template = trans('texts.reminder_subject',
[
'number' => $invoice->present()->invoice_number(),
'company' => $invoice->company->present()->name()
],
null, $invoice->client->locale());
}
}
$this->setTemplate($invoice->client->getSetting('email_style'))
->setContact($contact)
->setVariables($invoice->makeValues($contact))
->setSubject($subject_template)
->setBody($body_template)
->setFooter("<a href='{$invoice->invitations->first()->getLink()}'>Invoice Link</a>");
if ($client->getSetting('pdf_email_attachment') !== false) {
$this->setAttachments($invoice->pdf_file_path());
}
return $this;
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* Created by PhpStorm.
* User: michael.hampton
* Date: 14/02/2020
* Time: 19:51
*/
namespace App\Helpers\Email;
use App\Models\Payment;
class EmailPayment extends EmailBuilder
{
public function build(Payment $payment, $contact = null) {
$client = $payment->client;
$body_template = $client->getSetting('email_template_payment');
/* Use default translations if a custom message has not been set*/
if (iconv_strlen($body_template) == 0) {
$body_template = trans('texts.payment_message',
['amount' => $payment->amount, 'company' => $payment->company->present()->name()], null,
$this->client->locale());
}
$subject_template = $client->getSetting('email_subject_payment');
if (iconv_strlen($subject_template) == 0) {
$subject_template = trans('texts.payment_subject',
['number' => $payment->number, 'company' => $payment->company->present()->name()], null,
$payment->client->locale());
}
$this->setTemplate($payment->client->getSetting('email_style'))
->setSubject($subject_template)
->setBody($body_template);
return $this;
}
}

View File

@ -0,0 +1,58 @@
<?php
/**
* Created by PhpStorm.
* User: michael.hampton
* Date: 14/02/2020
* Time: 19:51
*/
namespace App\Helpers\Email;
use App\Models\Quote;
class QuoteEmail extends EmailBuilder
{
public function build(Quote $quote, $reminder_template, $contact = null)
{
$client = $quote->client;
$this->template_style = $quote->client->getSetting('email_style');
$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' => $quote->amount, 'company' => $quote->company->present()->name()], null,
$quote->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' => $quote->number, 'company' => $quote->company->present()->name()],
null, $quote->client->locale());
} else {
$subject_template = trans('texts.reminder_subject',
['number' => $quote->number, 'company' => $quote->company->present()->name()],
null, $quote->client->locale());
}
}
$this->setTemplate($quote->client->getSetting('email_style'))
->setContact($contact)
->setFooter("<a href='{$quote->invitations->first()->getLink()}'>Invoice Link</a>")
->setVariables($quote->makeValues($contact))
->setSubject($subject_template)
->setBody($body_template);
if ($client->getSetting('pdf_email_attachment') !== false) {
$this->attachments = $quote->pdf_file_path();
}
return $this;
}
}

View File

@ -1,50 +1,40 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Jobs\Invoice;
use App\Events\Invoice\InvoiceWasEmailed;
use App\Events\Invoice\InvoiceWasEmailedAndFailed;
use App\Libraries\MultiDB;
use App\Helpers\Email\InvoiceEmail;
use App\Jobs\Utils\SystemLogger;
use App\Mail\TemplateEmail;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\SystemLog;
use App\Models\InvoiceInvitation;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;
use Symfony\Component\Mime\Test\Constraint\EmailTextBodyContains;
class EmailInvoice implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $invoice;
public $invoice_invitation;
public $message_array = [];
private $company;
public $email_builder;
/**
* Create a new job instance.
*
* @return void
* EmailQuote constructor.
* @param BuildEmail $email_builder
* @param QuoteInvitation $quote_invitation
*/
public function __construct(Invoice $invoice, Company $company)
public function __construct(InvoiceEmail $email_builder, InvoiceInvitation $invoice_invitation)
{
$this->invoice = $invoice;
$this->company = $company;
$this->invoice_invitation = $invoice_invitation;
$this->email_builder = $email_builder;
}
/**
@ -53,46 +43,22 @@ class EmailInvoice implements ShouldQueue
*
* @return void
*/
public function handle()
{
/*Jobs are not multi-db aware, need to set! */
MultiDB::setDB($this->company->db);
$email_builder = $this->email_builder;
//todo - change runtime config of mail driver if necessary
Mail::to($this->invoice_invitation->contact->email, $this->invoice_invitation->contact->present()->name())
->send(new TemplateEmail($email_builder,
$this->invoice_invitation->contact->user,
$this->invoice_invitation->contact->client
)
);
$template_style = $this->invoice->client->getSetting('email_style');
$this->invoice->invitations->each(function ($invitation) use ($template_style) {
if ($invitation->contact->send_invoice && $invitation->contact->email) {
$message_array = $this->invoice->getEmailData('', $invitation->contact);
$message_array['title'] = &$message_array['subject'];
//$message_array['footer'] = "Sent to ".$invitation->contact->present()->name();
$message_array['footer'] = "<a href='{$invitation->getLink()}'>Invoice Link</a>";
//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 InvoiceWasEmailedAndFailed($this->invoice, Mail::failures()));
return $this->logMailError($errors);
}
//fire any events
event(new InvoiceWasEmailed($this->invoice));
//sleep(5);
}
});
if (count(Mail::failures()) > 0) {
return $this->logMailError($errors);
}
}
private function logMailError($errors)

View File

@ -1,9 +1,13 @@
<?php
namespace App\Jobs\Payment;
use App\Events\Invoice\InvoiceWasEmailed;
use App\Events\Invoice\InvoiceWasEmailedAndFailed;
use App\Events\Payment\PaymentWasEmailed;
use App\Events\Payment\PaymentWasEmailedAndFailed;
use App\Jobs\Util\SystemLogger;
use App\Helpers\Email\BuildEmail;
use App\Jobs\Utils\SystemLogger;
use App\Libraries\MultiDB;
use App\Mail\TemplateEmail;
use App\Models\Company;
@ -22,21 +26,23 @@ class EmailPayment implements ShouldQueue
public $payment;
public $message_array = [];
public $email_builder;
private $contact;
private $company;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Payment $payment, Company $company)
{
public function __construct(Payment $payment, $email_builder, $contact)
{
$this->payment = $payment;
$this->email_builder = $email_builder;
$this->contact = $contact;
}
$this->company = $company;
}
/**
* Execute the job.
@ -46,37 +52,24 @@ class EmailPayment implements ShouldQueue
*/
public function handle()
{
/*Jobs are not multi-db aware, need to set! */
MultiDB::setDB($this->company->db);
$email_builder = $this->email_builder;
//todo - change runtime config of mail driver if necessary
if ($this->contact->email) {
Mail::to($this->contact->email, $this->contact->present()->name())
->send(new TemplateEmail($email_builder, $this->contact->user, $this->contact->customer));
$template_style = $this->payment->client->getSetting('email_style');
if (count(Mail::failures()) > 0) {
event(new PaymentWasEmailedAndFailed($this->payment, Mail::failures()));
$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);
return $this->logMailError($errors);
}
});
//fire any events
event(new PaymentWasEmailed($this->payment));
//sleep(5);
}
}
private function logMailError($errors)

View File

@ -1,13 +1,17 @@
<?php
namespace App\Jobs\Quote;
use App\Events\Invoice\InvoiceWasEmailed;
use App\Events\Invoice\InvoiceWasEmailedAndFailed;
use App\Events\Quote\QuoteWasEmailed;
use App\Events\Quote\QuoteWasEmailedAndFailed;
use App\Jobs\Util\SystemLogger;
use App\Jobs\Utils\SystemLogger;
use App\Libraries\MultiDB;
use App\Mail\TemplateEmail;
use App\Models\Company;
use App\Models\Quote;
use App\Models\QuoteInvitation;
use App\Models\SystemLog;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -20,22 +24,19 @@ class EmailQuote implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $quote;
public $quote_invitation;
public $message_array = [];
private $company;
public $email_builder;
/**
* Create a new job instance.
*
* @return void
* EmailQuote constructor.
* @param BuildEmail $email_builder
* @param QuoteInvitation $quote_invitation
*/
public function __construct(Quote $quote, Company $company)
public function __construct($email_builder, QuoteInvitation $quote_invitation)
{
$this->quote = $quote;
$this->company = $company;
$this->quote_invitation = $quote_invitation;
$this->email_builder = $email_builder;
}
/**
@ -46,37 +47,19 @@ class EmailQuote implements ShouldQueue
*/
public function handle()
{
/*Jobs are not multi-db aware, need to set! */
MultiDB::setDB($this->company->db);
$email_builder = $this->email_builder;
//todo - change runtime config of mail driver if necessary
Mail::to($this->quote_invitation->contact->email, $this->quote_invitation->contact->present()->name())
->send(new TemplateEmail($email_builder,
$this->quote_invitation->contact->user,
$this->quote_invitation->contact->client
)
);
$template_style = $this->quote->client->getSetting('email_style');
if (count(Mail::failures()) > 0) {
return $this->logMailError($errors);
}
$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'] = "<a href='{$invitation->getLink()}'>Quote Link</a>";
//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)

View File

@ -1,8 +1,6 @@
<?php
namespace App\Mail;
use App\Utils\Ninja;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
@ -11,22 +9,16 @@ use Illuminate\Queue\SerializesModels;
class TemplateEmail extends Mailable
{
use Queueable, SerializesModels;
private $template; //the template to use
private $message; //the message array // ['body', 'footer', 'title', 'files']
private $build_email; //the message array // ['body', 'footer', 'title', 'files']
private $user; //the user the email will be sent from
private $customer;
private $footer;
private $client;
public function __construct($message, $template, $user, $client)
public function __construct($build_email, $user, $customer)
{
$this->message = $message;
$this->template = $template;
$this->build_email = $build_email;
$this->user = $user; //this is inappropriate here, need to refactor 'user' in this context the 'user' could also be the 'system'
$this->client = $client;
$this->customer = $customer;
}
/**
@ -37,33 +29,32 @@ class TemplateEmail extends Mailable
public function build()
{
/*Alter Run Time Mailer configuration (driver etc etc) to regenerate the Mailer Singleton*/
//if using a system level template
$template_name = 'email.template.'.$this->template;
$template_name = 'email.template.' . $this->build_email->getTemplate();
$settings = $this->client->getMergedSettings();
$settings = $this->customer->getMergedSettings();
\Log::error(print_r($settings, 1));
$company = $this->customer->account;
$company = $this->client->company;
$message = $this->from($this->user->email, $this->user->present()->name()) //todo this needs to be fixed to handle the hosted version
->subject($this->message['subject'])
->text('email.template.plain', ['body' => $this->message['body'], 'footer' => $this->message['footer']])
$message = $this->from($this->user->email,
$this->user->present()->name())//todo this needs to be fixed to handle the hosted version
->subject($this->build_email->getSubject())
->text('email.template.plain', ['body' => $this->build_email->getBody(), 'footer' => $this->build_email->getFooter()])
->view($template_name, [
'body' => $this->message['body'],
'footer' => $this->message['footer'],
'title' => $this->message['title'],
'body' => $this->build_email->getBody(),
'footer' => $this->build_email->getFooter(),
'title' => $this->build_email->getSubject(),
'settings' => $settings,
'company' => $company
]);
//conditionally attach files
if($settings->pdf_email_attachment !== false && !empty($this->build_email->getAttachments())){
//conditionally attach files
if ($settings->pdf_email_attachment !== false && array_key_exists('files', $this->message)) {
foreach ($this->message['files'] as $file) {
$message->attach($file);
}
}
foreach($this->build_email->getAttachments() as $file)
$message->attach($file);
}
return $message;
return $message;
}
}

View File

@ -146,7 +146,7 @@ class Invoice extends BaseModel
public function payments()
{
return $this->morphToMany(Payment::class, 'paymentable')->withPivot('amount','refunded')->withTimestamps();;
return $this->morphToMany(Payment::class, 'paymentable')->withPivot('amount', 'refunded')->withTimestamps();;
}
public function company_ledger()
@ -156,13 +156,14 @@ class Invoice extends BaseModel
public function credits()
{
return $this->belongsToMany(Credit::class)->using(Paymentable::class)->withPivot('amount','refunded')->withTimestamps();;
return $this->belongsToMany(Credit::class)->using(Paymentable::class)->withPivot('amount',
'refunded')->withTimestamps();;
}
/**
* Service entry points
*/
public function service() :InvoiceService
public function service(): InvoiceService
{
return new InvoiceService($this);
}
@ -192,12 +193,12 @@ class Invoice extends BaseModel
*
* @return boolean isLocked
*/
public function isLocked() : bool
public function isLocked(): bool
{
return $this->client->getSetting('lock_sent_invoices');
}
public function isPayable() : bool
public function isPayable(): bool
{
if ($this->status_id == Invoice::STATUS_SENT && $this->is_deleted == false) {
return true;
@ -212,23 +213,25 @@ class Invoice extends BaseModel
}
}
public function isRefundable() : bool
public function isRefundable(): bool
{
if($this->is_deleted)
if ($this->is_deleted) {
return false;
}
if(($this->amount - $this->balance) == 0)
if (($this->amount - $this->balance) == 0) {
return false;
}
return true;
}
/**
* @return bool
*/
public function isPartial() : bool
public function isPartial(): bool
{
return $this->status_id >= self::STATUS_PARTIAL;
}
@ -236,7 +239,7 @@ class Invoice extends BaseModel
/**
* @return bool
*/
public function hasPartial() : bool
public function hasPartial(): bool
{
return ($this->partial && $this->partial > 0) === true;
}
@ -245,28 +248,28 @@ class Invoice extends BaseModel
{
switch ($status) {
case Invoice::STATUS_DRAFT:
return '<h5><span class="badge badge-light">'.ctrans('texts.draft').'</span></h5>';
return '<h5><span class="badge badge-light">' . ctrans('texts.draft') . '</span></h5>';
break;
case Invoice::STATUS_SENT:
return '<h5><span class="badge badge-primary">'.ctrans('texts.sent').'</span></h5>';
return '<h5><span class="badge badge-primary">' . ctrans('texts.sent') . '</span></h5>';
break;
case Invoice::STATUS_PARTIAL:
return '<h5><span class="badge badge-primary">'.ctrans('texts.partial').'</span></h5>';
return '<h5><span class="badge badge-primary">' . ctrans('texts.partial') . '</span></h5>';
break;
case Invoice::STATUS_PAID:
return '<h5><span class="badge badge-success">'.ctrans('texts.paid').'</span></h5>';
return '<h5><span class="badge badge-success">' . ctrans('texts.paid') . '</span></h5>';
break;
case Invoice::STATUS_CANCELLED:
return '<h5><span class="badge badge-secondary">'.ctrans('texts.cancelled').'</span></h5>';
return '<h5><span class="badge badge-secondary">' . ctrans('texts.cancelled') . '</span></h5>';
break;
case Invoice::STATUS_OVERDUE:
return '<h5><span class="badge badge-danger">'.ctrans('texts.overdue').'</span></h5>';
return '<h5><span class="badge badge-danger">' . ctrans('texts.overdue') . '</span></h5>';
break;
case Invoice::STATUS_UNPAID:
return '<h5><span class="badge badge-warning">'.ctrans('texts.unpaid').'</span></h5>';
return '<h5><span class="badge badge-warning">' . ctrans('texts.unpaid') . '</span></h5>';
break;
case Invoice::STATUS_REVERSED:
return '<h5><span class="badge badge-info">'.ctrans('texts.reversed').'</span></h5>';
return '<h5><span class="badge badge-info">' . ctrans('texts.reversed') . '</span></h5>';
break;
default:
# code...
@ -306,13 +309,14 @@ class Invoice extends BaseModel
break;
}
}
/**
* Returns the template for the invoice
*
* @return string Either the template view, OR the template HTML string
* @todo this needs attention, invoice->settings needs clarification
*/
public function design() :string
public function design(): string
{
if ($this->client->getSetting('design')) {
return File::exists(resource_path($this->client->getSetting('design'))) ? File::get(resource_path($this->client->getSetting('design'))) : File::get(resource_path('views/pdf/design1.blade.php'));
@ -346,9 +350,10 @@ class Invoice extends BaseModel
// $storage_path = 'public/' . $this->client->client_hash . '/invoices/'. $this->number . '.pdf';
$public_path = $this->client->client_hash . '/invoices/'. $this->number . '.pdf';
$public_path = $this->client->client_hash . '/invoices/' . $this->number . '.pdf';
$storage_path = $this->client->client_hash . '/invoices/' . $this->number . '.pdf';
$storage_path = 'storage/' . $this->client->client_hash . '/invoices/'. $this->number . '.pdf';
$disk = config('filesystems.default');
@ -362,7 +367,7 @@ class Invoice extends BaseModel
public function pdf_file_path()
{
$storage_path = 'storage/' . $this->client->client_hash . '/invoices/'. $this->number . '.pdf';
$storage_path = 'storage/' . $this->client->client_hash . '/invoices/' . $this->number . '.pdf';
if (!Storage::exists($storage_path)) {
CreateInvoicePdf::dispatchNow($this, $this->company, $this->client->primary_contact()->first());
@ -386,8 +391,7 @@ class Invoice extends BaseModel
});
}
/* Graveyard */
/* Graveyard */
// /**
// * Determines if invoice overdue.

View File

@ -0,0 +1,82 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Models\Presenters;
use App\Utils\Number;
use App\Utils\Traits\MakesDates;
/**
* Class InvoicePresenter
*
* For convenience and to allow users to easiliy
* customise their invoices, we provide all possible
* invoice variables to be available from this presenter.
*
* Shortcuts to other presenters are here to facilitate
* a clean UI / UX
*
* @package App\Models\Presenters
*/
class QuotePresenter extends EntityPresenter
{
use MakesDates;
public function amount()
{
return Number::formatMoney($this->balance, $this->client);
}
public function invoice_number()
{
if ($this->number != '') {
return $this->number;
} else {
return '';
}
}
public function clientName()
{
return $this->client->present()->name();
}
public function address()
{
return $this->client->present()->address();
}
public function shippingAddress()
{
return $this->client->present()->shipping_address();
}
public function companyLogo()
{
return $this->company->logo;
}
public function clientLogo()
{
return $this->client->logo;
}
public function companyName()
{
return $this->company->present()->name();
}
public function companyAddress()
{
return $this->company->present()->address();
}
}

View File

@ -16,6 +16,8 @@ use App\Helpers\Invoice\InvoiceSumInclusive;
use App\Models\Filterable;
use App\Services\Quote\QuoteService;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesReminders;
use Laracasts\Presenter\PresentableTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
@ -24,6 +26,10 @@ class Quote extends BaseModel
use MakesHash;
use Filterable;
use SoftDeletes;
use MakesReminders;
use PresentableTrait;
protected $presenter = 'App\Models\Presenters\QuotePresenter';
protected $fillable = [
'number',

View File

@ -44,7 +44,7 @@ class QuoteInvitation extends BaseModel
*/
public function contact()
{
return $this->belongsTo(ClientContact::class)->withTrashed();
return $this->belongsTo(ClientContact::class, 'client_contact_id', 'id')->withTrashed();
}
/**

View File

@ -0,0 +1,40 @@
<?php
namespace App\Services\Credit;
use App\Credit;
use App\Events\Payment\PaymentWasCreated;
use App\Factory\PaymentFactory;
use App\Jobs\Customer\UpdateCustomerBalance;
use App\Jobs\Customer\UpdateCustomerPaidToDate;
use App\Jobs\Company\UpdateCompanyLedgerWithPayment;
use App\Models\Invoice;
use App\Models\Payment;
use App\Services\Customer\CustomerService;
use App\Services\Payment\PaymentService;
use App\Traits\GeneratesCounter;
class ApplyNumber
{
use GeneratesCounter;
private $customer;
public function __construct($customer)
{
$this->customer = $customer;
}
public function __invoke($credit)
{
if ($credit->number != '') {
return $credit;
}
$credit->number = $this->getNextCreditNumber($this->customer);
return $credit;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Services\Credit;
use App\Factory\CreditInvitationFactory;
use App\Models\CreditInvitation;
class CreateInvitations
{
public function __construct()
{
}
public function __invoke($credit)
{
$contacts = $credit->customer->contacts;
$contacts->each(function ($contact) use($credit){
$invitation = CreditInvitation::whereCompanyId($credit->account_id)
->whereClientContactId($contact->id)
->whereCreditId($credit->id)
->first();
if (!$invitation) {
$ii = CreditInvitationFactory::create($credit->account_id, $credit->user_id);
$ii->credit_id = $credit->id;
$ii->client_contact_id = $contact->id;
$ii->save();
} elseif ($invitation && !$contact->send_credit) {
$invitation->delete();
}
});
return $credit;
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Services\Credit;
use App\Credit;
class CreditService
{
protected $credit;
public function __construct($credit)
{
$this->credit = $credit;
}
public function getCreditPdf($contact)
{
$get_invoice_pdf = new GetCreditPdf();
return $get_invoice_pdf($this->credit, $contact);
}
/**
* Applies the invoice number
* @return $this InvoiceService object
*/
public function applyNumber()
{
$apply_number = new ApplyNumber($this->credit->customer);
$this->credit = $apply_number($this->credit);
return $this;
}
public function createInvitations()
{
$create_invitation = new CreateInvitations();
$this->invoice = $create_invitation($this->invoice);
return $this;
}
/**
* Saves the credit
* @return Credit object
*/
public function save() : ?Credit
{
$this->credit->save();
return $this->credit;
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Services\Credit;
use App\Jobs\Invoice\CreateInvoicePdf;
use Illuminate\Support\Facades\Storage;
class GetCreditPdf
{
public function __construct()
{
}
public function __invoke($credit, $contact = null)
{
if (!$contact) {
$contact = $credit->customer->primary_contact()->first();
}
$path = 'public/' . $credit->customer->id . '/credits/';
$file_path = $path . $credit->number . '.pdf';
$disk = config('filesystems.default');
$file = Storage::disk($disk)->exists($file_path);
if (!$file) {
$file_path = CreateInvoicePdf::dispatchNow($this, $credit->account, $contact);
}
return Storage::disk($disk)->url($file_path);
}
}

View File

@ -120,14 +120,12 @@ class InvoiceService
return $get_invoice_pdf->run($this->invoice, $contact);
}
public function sendEmail($contact)
{
$send_email = new SendEmail($this->invoice);
return $send_email->run(null, $contact);
}
public function markViewed()
{

View File

@ -0,0 +1,39 @@
<?php
namespace App\Services\Invoice;
use App\Helpers\Email\InvoiceEmail;
use App\Jobs\Invoice\EmailInvoice;
use App\Models\Invoice;
use Illuminate\Support\Carbon;
class SendEmail
{
public $invoice;
public function __construct($invoice)
{
$this->invoice = $invoice;
}
/**
* Builds the correct template to send
* @param string $reminder_template The template name ie reminder1
* @return array
*/
public function run($reminder_template = null, $contact = null): array
{
if (!$reminder_template) {
$reminder_template = $this->invoice->status_id == Invoice::STATUS_DRAFT || Carbon::parse($this->invoice->due_date) > now() ? 'invoice' : $this->invoice->calculateTemplate();
}
$email_builder = (new InvoiceEmail())->build($this->invoice, $reminder_template, $contact);
$this->invoice->invitations->each(function ($invitation) use ($email_builder) {
if ($invitation->contact->send_invoice && $invitation->contact->email) {
EmailInvoice::dispatchNow($email_builder, $invitation);
}
});
}
}

View File

@ -42,4 +42,11 @@ class PaymentService
return $payment;
}
public function sendEmail($contact = null)
{
$send_email = new SendEmail($this->payment);
return $send_email->run(null, $contact);
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Services\Payment;
use App\Helpers\Email\PaymentEmail;
use App\Jobs\Payment\EmailPayment;
class SendEmail
{
public $payment;
public function __construct($payment)
{
$this->payment = $payment;
}
/**
* Builds the correct template to send
* @param string $reminder_template The template name ie reminder1
* @return array
*/
public function run($contact = null): array
{
$email_builder = (new PaymentEmail())->build($this->payment, $contact);
$this->payment->client->contacts->each(function ($contact) use ($email_builder) {
if ($contact->send_invoice && $contact->email) {
EmailPayment::dispatchNow($this->payment, $email_builder, $contact);
}
});
}
}

View File

@ -35,6 +35,20 @@ class QuoteService
return $this;
}
public function getQuotePdf($contact)
{
$get_invoice_pdf = new GetQuotePdf();
return $get_invoice_pdf($this->quote, $contact);
}
public function sendEmail($contact)
{
$send_email = new SendEmail($this->quote);
return $send_email->run(null, $contact);
}
/**
* Applies the invoice number
* @return $this InvoiceService object

View File

@ -0,0 +1,40 @@
<?php
namespace App\Services\Quote;
use App\Helpers\Email\QuoteEmail;
use App\Jobs\Quote\EmailQuote;
use App\Models\Quote;
class SendEmail
{
public $quote;
public function __construct($quote)
{
$this->quote = $quote;
}
/**
* Builds the correct template to send
* @param string $reminder_template The template name ie reminder1
* @return array
*/
public function run($reminder_template = null, $contact = null): array
{
if (!$reminder_template) {
$reminder_template = $this->quote->status_id == Quote::STATUS_DRAFT || Carbon::parse($this->quote->due_date) > now() ? 'invoice' : $this->quote->calculateTemplate();
}
$email_builder = (new QuoteEmail())->build($this->quote, $reminder_template, $contact);
$this->quote->invitations->each(function ($invitation) use ($email_builder) {
if ($invitation->contact->send_invoice && $invitation->contact->email) {
EmailQuote::dispatchNow($email_builder, $invitation);
}
});
}
}

View File

@ -40,12 +40,12 @@ trait MakesReminders
if (!$nsd) {
$nsd = $reminder_date;
}
if ($reminder_date->lt($nsd)) {
$nsd = $reminder_date;
}
}
if ($settings->enable_reminder1 !== false &&
$settings->schedule_reminder1 == 'before_due_date' &&
$settings->num_days_reminder1 > 0) {
@ -54,7 +54,7 @@ trait MakesReminders
if (!$nsd) {
$nsd = $reminder_date;
}
if ($reminder_date->lt($nsd)) {
$nsd = $reminder_date;
}
@ -69,12 +69,12 @@ trait MakesReminders
if (!$nsd) {
$nsd = $reminder_date;
}
if ($reminder_date->lt($nsd)) {
$nsd = $reminder_date;
}
}
if ($settings->enable_reminder2 !== false &&
$settings->schedule_reminder2 == 'after_invoice_date' &&
$settings->num_days_reminder2 > 0) {
@ -83,12 +83,12 @@ trait MakesReminders
if (!$nsd) {
$nsd = $reminder_date;
}
if ($reminder_date->lt($nsd)) {
$nsd = $reminder_date;
}
}
if ($settings->enable_reminder2 !== false &&
$settings->schedule_reminder2 == 'before_due_date' &&
$settings->num_days_reminder2 > 0) {
@ -97,7 +97,7 @@ trait MakesReminders
if (!$nsd) {
$nsd = $reminder_date;
}
if ($reminder_date->lt($nsd)) {
$nsd = $reminder_date;
}
@ -112,7 +112,7 @@ trait MakesReminders
if (!$nsd) {
$nsd = $reminder_date;
}
if ($reminder_date->lt($nsd)) {
$nsd = $reminder_date;
}
@ -126,12 +126,12 @@ trait MakesReminders
if (!$nsd) {
$nsd = $reminder_date;
}
if ($reminder_date->lt($nsd)) {
$nsd = $reminder_date;
}
}
if ($settings->enable_reminder3 !== false &&
$settings->schedule_reminder3 == 'before_due_date' &&
$settings->num_days_reminder3 > 0) {
@ -140,7 +140,7 @@ trait MakesReminders
if (!$nsd) {
$nsd = $reminder_date;
}
if ($reminder_date->lt($nsd)) {
$nsd = $reminder_date;
}
@ -155,13 +155,53 @@ trait MakesReminders
if (!$nsd) {
$nsd = $reminder_date;
}
if ($reminder_date->lt($nsd)) {
$nsd = $reminder_date;
}
}
$this->next_send_date = $nsd;
$this->save();
}
public 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;
}
}
public function calculateTemplate(): string
{
//if invoice is currently a draft, or being marked as sent, this will be the initial email
$customer = $this->client;
//if the invoice
if ($customer->getSetting('enable_reminder1') !== false && $this->inReminderWindow($customer->getSetting('schedule_reminder1'),
$customer->getSetting('num_days_reminder1'))) {
return 'template1';
} elseif ($customer->getSetting('enable_reminder2') !== false && $this->inReminderWindow($customer->getSetting('schedule_reminder2'),
$customer->getSetting('num_days_reminder2'))) {
return 'template2';
} elseif ($customer->getSetting('enable_reminder3') !== false && $this->inReminderWindow($customer->getSetting('schedule_reminder3'),
$customer->getSetting('num_days_reminder3'))) {
return 'template3';
} else {
return 'invoice';
}
//also implement endless reminders here
}
}