diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 78caf1f26929..4aa6dde3e805 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -510,7 +510,6 @@ class PaymentController extends BaseController $payments->each(function ($payment, $key) use ($action) { if (auth()->user()->can('edit', $payment)) { $this->performAction($payment, $action, true); - // $this->payment_repo->{$action}($payment); } }); diff --git a/app/Jobs/Mail/BaseMailerJob.php b/app/Jobs/Mail/BaseMailerJob.php index bc1a1e8ab5aa..b4b840756acd 100644 --- a/app/Jobs/Mail/BaseMailerJob.php +++ b/app/Jobs/Mail/BaseMailerJob.php @@ -69,7 +69,7 @@ class BaseMailerJob implements ShouldQueue } public function logMailError($errors, $recipient_object) - { + {info(print_r($errors,1)); SystemLogger::dispatch( $errors, SystemLog::CATEGORY_MAIL, diff --git a/app/Jobs/Payment/EmailPayment.php b/app/Jobs/Payment/EmailPayment.php index 8c3487a6824c..283b3265b1a9 100644 --- a/app/Jobs/Payment/EmailPayment.php +++ b/app/Jobs/Payment/EmailPayment.php @@ -2,6 +2,7 @@ namespace App\Jobs\Payment; +use App\DataMapper\Analytics\EmailInvoiceFailure; use App\Events\Invoice\InvoiceWasEmailed; use App\Events\Invoice\InvoiceWasEmailedAndFailed; use App\Events\Payment\PaymentWasEmailed; @@ -10,7 +11,9 @@ use App\Helpers\Email\BuildEmail; use App\Jobs\Mail\BaseMailerJob; use App\Jobs\Utils\SystemLogger; use App\Libraries\MultiDB; +use App\Mail\Engine\PaymentEmailEngine; use App\Mail\TemplateEmail; +use App\Models\ClientContact; use App\Models\Company; use App\Models\Payment; use App\Models\SystemLog; @@ -21,6 +24,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Mail; +use Turbo124\Beacon\Facades\LightLogs; class EmailPayment extends BaseMailerJob implements ShouldQueue { @@ -33,6 +37,8 @@ class EmailPayment extends BaseMailerJob implements ShouldQueue private $contact; private $company; + + public $settings; /** * Create a new job instance. * @@ -41,12 +47,12 @@ class EmailPayment extends BaseMailerJob implements ShouldQueue * @param $contact * @param $company */ - public function __construct(Payment $payment, $email_builder, $contact, company) + public function __construct(Payment $payment, Company $company, ClientContact $contact) { $this->payment = $payment; - $this->email_builder = $email_builder; $this->contact = $contact; $this->company = $company; + $this->settings = $payment->client->getMergedSettings(); } /** @@ -62,14 +68,15 @@ class EmailPayment extends BaseMailerJob implements ShouldQueue if ($this->contact->email) { - MultiDB::setDb($this->payment->company->db); //this may fail if we don't pass the serialized object with the company record - //todo fix!! + MultiDB::setDb($this->company->db); //if we need to set an email driver do it now $this->setMailDriver(); + $email_builder = (new PaymentEmailEngine($this->payment, $this->contact))->build(); + Mail::to($this->contact->email, $this->contact->present()->name()) - ->send(new TemplateEmail($this->email_builder, $this->contact->user, $this->contact->customer)); + ->send(new TemplateEmail($email_builder, $this->contact->user, $this->contact->client)); if (count(Mail::failures()) > 0) { event(new PaymentWasEmailedAndFailed($this->payment, Mail::failures(), Ninja::eventVars())); @@ -77,21 +84,22 @@ class EmailPayment extends BaseMailerJob implements ShouldQueue return $this->logMailError(Mail::failures()); } - //fire any events event(new PaymentWasEmailed($this->payment, $this->payment->company, Ninja::eventVars())); - //sleep(5); } } - private function logMailError($errors) + public function failed($exception = null) { - SystemLogger::dispatch( - $errors, - SystemLog::CATEGORY_MAIL, - SystemLog::EVENT_MAIL_SEND, - SystemLog::TYPE_FAILURE, - $this->payment->client - ); + info('the job failed'); + + $job_failure = new EmailInvoiceFailure(); + $job_failure->string_metric5 = 'payment'; + $job_failure->string_metric6 = $exception->getMessage(); + + LightLogs::create($job_failure) + ->batch(); + } + } diff --git a/app/Mail/Engine/PaymentEmailEngine.php b/app/Mail/Engine/PaymentEmailEngine.php new file mode 100644 index 000000000000..e95942ce34a1 --- /dev/null +++ b/app/Mail/Engine/PaymentEmailEngine.php @@ -0,0 +1,262 @@ +payment = $payment; + $this->company = $payment->company; + $this->client = $payment->client; + $this->contact = $contact ?: $this->client->primary_contact()->first(); + $this->settings = $this->client->getMergedSettings(); + $this->template_data = $template_data; + } + + public function build() + { + + if(is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) + $body_template = $this->template_data['body']; + elseif(strlen($this->client->getSetting('email_template_payment')) > 0) + $body_template = $this->client->getSetting('email_template_payment'); + else{ + $body_template = EmailTemplateDefaults::getDefaultTemplate('email_template_payment', $this->client->locale()); + } + + /* 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() + ); + } + + if(is_array($this->template_data) && array_key_exists('subject', $this->template_data) && strlen($this->template_data['subject']) > 0){ + $subject_template = $this->template_data['subject']; + } + elseif(strlen($this->client->getSetting('email_subject_payment')) > 0){ + $subject_template = $this->client->getSetting('email_subject_payment'); + } + else{ + $subject_template = EmailTemplateDefaults::getDefaultTemplate('email_subject_payment', $this->client->locale()); + } + + if (iconv_strlen($subject_template) == 0) { + $subject_template = trans( + 'texts.payment_subject', + ['number' => $payment->number, 'company' => $payment->company->present()->name()], + null, + $this->client->locale() + ); + } + + $this->setTemplate($this->client->getSetting('email_style')) + ->setContact($this->contact) + ->setVariables($this->makeValues()) + ->setSubject($subject_template) + ->setBody($body_template) + ->setFooter('') + ->setViewLink('') + ->setViewText(''); + + return $this; + + } + + + private function makePaymentVariables() + { + $data = []; + + $data['$from'] = ['value' => '', 'label' => ctrans('texts.from')]; + $data['$to'] = ['value' => '', 'label' => ctrans('texts.to')]; + $data['$number'] = ['value' => $this->payment->number ?: ' ', 'label' => ctrans('texts.payment_number')]; + $data['$entity'] = ['value' => '', 'label' => ctrans('texts.payment')]; + $data['$payment.amount'] = ['value' => Number::formatMoney($this->payment->amount, $this->client) ?: ' ', 'label' => ctrans('texts.amount')]; + $data['$amount'] = &$data['$payment.amount']; + $data['$payment.date'] = ['value' => $this->formatDate($this->payment->date, $this->client->date_format()), 'label' => ctrans('texts.payment_date')]; + $data['$transaction_reference'] = ['value' => $this->payment->transaction_reference, 'label' => ctrans('texts.transaction_reference')]; + $data['$public_notes'] = ['value' => $this->payment->public_notes, 'label' => ctrans('texts.notes')]; + + $data['$payment1'] = ['value' => $this->formatCustomFieldValue('payment1', $this->payment->custom_value1) ?: ' ', 'label' => $this->makeCustomField('payment1')]; + $data['$payment2'] = ['value' => $this->formatCustomFieldValue('payment2', $this->payment->custom_value2) ?: ' ', 'label' => $this->makeCustomField('payment2')]; + $data['$payment3'] = ['value' => $this->formatCustomFieldValue('payment3', $this->payment->custom_value3) ?: ' ', 'label' => $this->makeCustomField('payment3')]; + $data['$payment4'] = ['value' => $this->formatCustomFieldValue('payment4', $this->payment->custom_value4) ?: ' ', 'label' => $this->makeCustomField('payment4')]; + // $data['$type'] = ['value' => $this->payment->type->name ?: '', 'label' => ctrans('texts.payment_type')]; + + $data['$client1'] = ['value' => $this->formatCustomFieldValue('client1', $this->client->custom_value1) ?: ' ', 'label' => $this->makeCustomField('client1')]; + $data['$client2'] = ['value' => $this->formatCustomFieldValue('client2', $this->client->custom_value2) ?: ' ', 'label' => $this->makeCustomField('client2')]; + $data['$client3'] = ['value' => $this->formatCustomFieldValue('client3', $this->client->custom_value3) ?: ' ', 'label' => $this->makeCustomField('client3')]; + $data['$client4'] = ['value' => $this->formatCustomFieldValue('client4', $this->client->custom_value4) ?: ' ', 'label' => $this->makeCustomField('client4')]; + $data['$address1'] = ['value' => $this->client->address1 ?: ' ', 'label' => ctrans('texts.address1')]; + $data['$address2'] = ['value' => $this->client->address2 ?: ' ', 'label' => ctrans('texts.address2')]; + $data['$id_number'] = ['value' => $this->client->id_number ?: ' ', 'label' => ctrans('texts.id_number')]; + $data['$vat_number'] = ['value' => $this->client->vat_number ?: ' ', 'label' => ctrans('texts.vat_number')]; + $data['$website'] = ['value' => $this->client->present()->website() ?: ' ', 'label' => ctrans('texts.website')]; + $data['$phone'] = ['value' => $this->client->present()->phone() ?: ' ', 'label' => ctrans('texts.phone')]; + $data['$country'] = ['value' => isset($this->client->country->name) ? $this->client->country->name : '', 'label' => ctrans('texts.country')]; + $data['$email'] = ['value' => isset($this->contact) ? $this->contact->email : 'no contact email on record', 'label' => ctrans('texts.email')]; + $data['$client_name'] = ['value' => $this->client->present()->name() ?: ' ', 'label' => ctrans('texts.client_name')]; + $data['$client.name'] = &$data['$client_name']; + $data['$client.address1'] = &$data['$address1']; + $data['$client.address2'] = &$data['$address2']; + $data['$client_address'] = ['value' => $this->client->present()->address() ?: ' ', 'label' => ctrans('texts.address')]; + $data['$client.address'] = &$data['$client_address']; + $data['$client.id_number'] = &$data['$id_number']; + $data['$client.vat_number'] = &$data['$vat_number']; + $data['$client.website'] = &$data['$website']; + $data['$client.phone'] = &$data['$phone']; + $data['$city_state_postal'] = ['value' => $this->client->present()->cityStateZip($this->client->city, $this->client->state, $this->client->postal_code, false) ?: ' ', 'label' => ctrans('texts.city_state_postal')]; + $data['$client.city_state_postal'] = &$data['$city_state_postal']; + $data['$postal_city_state'] = ['value' => $this->client->present()->cityStateZip($this->client->city, $this->client->state, $this->client->postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city_state')]; + $data['$client.postal_city_state'] = &$data['$postal_city_state']; + $data['$client.country'] = &$data['$country']; + $data['$client.email'] = &$data['$email']; + + $data['$client.balance'] = ['value' => Number::formatMoney($this->client->balance, $this->client), 'label' => ctrans('texts.account_balance')]; + $data['$outstanding'] = ['value' => Number::formatMoney($this->client->balance, $this->client), 'label' => ctrans('texts.account_balance')]; + $data['$client_balance'] = ['value' => Number::formatMoney($this->client->balance, $this->client), 'label' => ctrans('texts.account_balance')]; + $data['$paid_to_date'] = ['value' => Number::formatMoney($this->client->paid_to_date, $this->client), 'label' => ctrans('texts.paid_to_date')]; + + $data['$contact.full_name'] = ['value' => $this->contact->present()->name(), 'label' => ctrans('texts.name')]; + $data['$contact.email'] = ['value' => $this->contact->email, 'label' => ctrans('texts.email')]; + $data['$contact.phone'] = ['value' => $this->contact->phone, 'label' => ctrans('texts.phone')]; + + $data['$contact.name'] = ['value' => isset($this->contact) ? $this->contact->present()->name() : 'no contact name on record', 'label' => ctrans('texts.contact_name')]; + $data['$contact.first_name'] = ['value' => isset($this->contact) ? $this->contact->first_name : '', 'label' => ctrans('texts.first_name')]; + $data['$contact.last_name'] = ['value' => isset($this->contact) ? $this->contact->last_name : '', 'label' => ctrans('texts.last_name')]; + $data['$contact.custom1'] = ['value' => isset($this->contact) ? $this->contact->custom_value1 : ' ', 'label' => $this->makeCustomField('contact1')]; + $data['$contact.custom2'] = ['value' => isset($this->contact) ? $this->contact->custom_value2 : ' ', 'label' => $this->makeCustomField('contact1')]; + $data['$contact.custom3'] = ['value' => isset($this->contact) ? $this->contact->custom_value3 : ' ', 'label' => $this->makeCustomField('contact1')]; + $data['$contact.custom4'] = ['value' => isset($this->contact) ? $this->contact->custom_value4 : ' ', 'label' => $this->makeCustomField('contact1')]; + + $data['$company.city_state_postal'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, false) ?: ' ', 'label' => ctrans('texts.city_state_postal')]; + $data['$company.postal_city_state'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city_state')]; + $data['$company.name'] = ['value' => $this->company->present()->name() ?: ' ', 'label' => ctrans('texts.company_name')]; + $data['$company.address1'] = ['value' => $this->settings->address1 ?: ' ', 'label' => ctrans('texts.address1')]; + $data['$company.address2'] = ['value' => $this->settings->address2 ?: ' ', 'label' => ctrans('texts.address2')]; + $data['$company.city'] = ['value' => $this->settings->city ?: ' ', 'label' => ctrans('texts.city')]; + $data['$company.state'] = ['value' => $this->settings->state ?: ' ', 'label' => ctrans('texts.state')]; + $data['$company.postal_code'] = ['value' => $this->settings->postal_code ?: ' ', 'label' => ctrans('texts.postal_code')]; + //$data['$company.country'] = ['value' => $this->getCountryName(), 'label' => ctrans('texts.country')]; + $data['$company.phone'] = ['value' => $this->settings->phone ?: ' ', 'label' => ctrans('texts.phone')]; + $data['$company.email'] = ['value' => $this->settings->email ?: ' ', 'label' => ctrans('texts.email')]; + $data['$company.vat_number'] = ['value' => $this->settings->vat_number ?: ' ', 'label' => ctrans('texts.vat_number')]; + $data['$company.id_number'] = ['value' => $this->settings->id_number ?: ' ', 'label' => ctrans('texts.id_number')]; + $data['$company.website'] = ['value' => $this->settings->website ?: ' ', 'label' => ctrans('texts.website')]; + $data['$company.address'] = ['value' => $this->company->present()->address($this->settings) ?: ' ', 'label' => ctrans('texts.address')]; + + $logo = $this->company->present()->logo($this->settings); + + $data['$company.logo'] = ['value' => $logo ?: ' ', 'label' => ctrans('texts.logo')]; + $data['$company_logo'] = &$data['$company.logo']; + $data['$company1'] = ['value' => $this->formatCustomFieldValue('company1', $this->settings->custom_value1) ?: ' ', 'label' => $this->makeCustomField('company1')]; + $data['$company2'] = ['value' => $this->formatCustomFieldValue('company2', $this->settings->custom_value2) ?: ' ', 'label' => $this->makeCustomField('company2')]; + $data['$company3'] = ['value' => $this->formatCustomFieldValue('company3', $this->settings->custom_value3) ?: ' ', 'label' => $this->makeCustomField('company3')]; + $data['$company4'] = ['value' => $this->formatCustomFieldValue('company4', $this->settings->custom_value4) ?: ' ', 'label' => $this->makeCustomField('company4')]; + + $data['$view_link'] = ['value' => ''.ctrans('texts.view_payment').'', 'label' => ctrans('texts.view_payment')]; + $data['$view_url'] = ['value' => $this->payment->getLink(), 'label' => ctrans('texts.view_payment')]; + + $data['$invoices'] = ['value' => $this->formatInvoices(), 'label' => ctrans('texts.invoices')]; + + return $data; + } + + private function formatInvoices() + { + $invoice_list = ''; + + foreach($this->payment->invoices as $invoice) + { + $invoice_list .= ctrans('texts.invoice_number_short') . " {$invoice->number} - " . Number::formatMoney($invoice->pivot->amount, $this->client) . "
"; + } + + return $invoice_list; + } + + private function makeCustomField($field) :string + { + $custom_fields = $this->company->custom_fields; + + if ($custom_fields && property_exists($custom_fields, $field)) { + $custom_field = $custom_fields->{$field}; + + $custom_field_parts = explode('|', $custom_field); + + return $custom_field_parts[0]; + } + + return ''; + } + + private function formatCustomFieldValue($field, $value) :string + { + $custom_fields = $this->company->custom_fields; + $custom_field = ''; + + if ($custom_fields && property_exists($custom_fields, $field)) { + $custom_field = $custom_fields->{$field}; + $custom_field_parts = explode('|', $custom_field); + + if(count($custom_field_parts) >= 2) + $custom_field = $custom_field_parts[1]; + } + + switch ($custom_field) { + case 'date': + return $this->formatDate($value, $this->client->date_format()); + break; + + default: + return is_null($value) ? '' : $value; + break; + } + } + + public function makeValues() :array + { + $data = []; + + $values = $this->makePaymentVariables(); + + foreach ($values as $key => $value) { + $data[$key] = $value['value']; + } + + return $data; + } +} + diff --git a/app/Models/Activity.php b/app/Models/Activity.php index e271bc920fb4..6ad61022787a 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -80,6 +80,10 @@ class Activity extends StaticModel const UPDATE_CLIENT = 61; // const UPDATE_VENDOR = 62; // + const INVOICE_REMINDER1_SENT = 63; + const INVOICE_REMINDER2_SENT = 64; + const INVOICE_REMINDER3_SENT = 65; + protected $casts = [ 'is_system' => 'boolean', 'updated_at' => 'timestamp', diff --git a/app/Models/Payment.php b/app/Models/Payment.php index f938009e0e81..3fcad72d5be5 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -288,4 +288,10 @@ class Payment extends BaseModel event(new PaymentWasVoided($this, $this->company, Ninja::eventVars())); } + + public function getLink() + { + return route('client.payments.show', $this->hashed_id); + } + } diff --git a/app/Services/Payment/SendEmail.php b/app/Services/Payment/SendEmail.php index dc4b42b7d008..388a8cf04add 100644 --- a/app/Services/Payment/SendEmail.php +++ b/app/Services/Payment/SendEmail.php @@ -11,7 +11,6 @@ namespace App\Services\Payment; -use App\Helpers\Email\PaymentEmail; use App\Jobs\Payment\EmailPayment; class SendEmail @@ -33,11 +32,9 @@ class SendEmail */ public function run() { - $email_builder = (new PaymentEmail())->build($this->payment, $this->contact); - - $this->payment->client->contacts->each(function ($contact) use ($email_builder) { - if ($contact->send && $contact->email) { - EmailPayment::dispatchNow($this->payment, $email_builder, $contact, $this->payment->company); + $this->payment->client->contacts->each(function ($contact) { + if ($contact->send_email && $contact->email) { + EmailPayment::dispatchNow($this->payment, $this->payment->company, $contact); } }); } diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index f6edcc45ee76..5e0815af6611 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -3297,5 +3297,9 @@ return [ 'n/a' => 'N/A', 'payment_number' => 'Payment Number', - + + 'activity_63' => ':user emailed reminder 1 for invoice :invoice to :contact', + 'activity_64' => ':user emailed reminder 2 for invoice :invoice to :contact', + 'activity_65' => ':user emailed reminder 3 for invoice :invoice to :contact', + ];