diff --git a/app/Http/Controllers/ProposalController.php b/app/Http/Controllers/ProposalController.php index c42a8a467e..b775432d49 100644 --- a/app/Http/Controllers/ProposalController.php +++ b/app/Http/Controllers/ProposalController.php @@ -104,8 +104,14 @@ class ProposalController extends BaseController public function store(CreateProposalRequest $request) { $proposal = $this->proposalService->save($request->input()); + $action = Input::get('action'); - Session::flash('message', trans('texts.created_proposal')); + if ($action == 'email') { + $this->contactMailer->sendInvoice($proposal->invoice, false, false, $proposal); + Session::flash('message', trans('texts.emailed_proposal')); + } else { + Session::flash('message', trans('texts.created_proposal')); + } return redirect()->to($proposal->getRoute()); } @@ -120,7 +126,8 @@ class ProposalController extends BaseController } if ($action == 'email') { - $this->contactMailer->sendProposal($proposal); + $this->contactMailer->sendInvoice($proposal->invoice, false, false, $proposal); + Session::flash('message', trans('texts.emailed_proposal')); } else { Session::flash('message', trans('texts.updated_proposal')); } diff --git a/app/Models/Traits/Inviteable.php b/app/Models/Traits/Inviteable.php index 28344c4338..098c65cf5f 100644 --- a/app/Models/Traits/Inviteable.php +++ b/app/Models/Traits/Inviteable.php @@ -24,6 +24,10 @@ trait Inviteable $this->load('account'); } + if ($this->proposal_id) { + $type = 'proposal'; + } + $account = $this->account; $iframe_url = $account->iframe_url; $url = trim(SITE_URL, '/'); diff --git a/app/Ninja/Mailers/ContactMailer.php b/app/Ninja/Mailers/ContactMailer.php index fa5a737a40..7c426b8930 100644 --- a/app/Ninja/Mailers/ContactMailer.php +++ b/app/Ninja/Mailers/ContactMailer.php @@ -4,8 +4,6 @@ namespace App\Ninja\Mailers; use App\Events\InvoiceWasEmailed; use App\Events\QuoteWasEmailed; -use App\Models\Invitation; -use App\Models\ProposalInvitation; use App\Models\Invoice; use App\Models\Proposal; use App\Models\Payment; @@ -40,18 +38,22 @@ class ContactMailer extends Mailer * * @return bool|null|string */ - public function sendInvoice(Invoice $invoice, $reminder = false, $template = false) + public function sendInvoice(Invoice $invoice, $reminder = false, $template = false, $proposal = false) { if ($invoice->is_recurring) { return false; } $invoice->load('invitations', 'client.language', 'account'); - $entityType = $invoice->getEntityType(); + + if ($proposal) { + $entityType = ENTITY_PROPOSAL; + } else { + $entityType = $invoice->getEntityType(); + } $client = $invoice->client; $account = $invoice->account; - $response = null; if ($client->trashed()) { @@ -68,10 +70,10 @@ class ContactMailer extends Mailer $pdfString = false; $ublString = false; - if ($account->attachPDF()) { + if ($account->attachPDF() && ! $proposal) { $pdfString = $invoice->getPDFString(); } - if ($account->attachUBL()) { + if ($account->attachUBL() && ! $proposal) { $ublString = dispatch(new ConvertInvoiceToUbl($invoice)); } @@ -96,11 +98,13 @@ class ContactMailer extends Mailer } $isFirst = true; - foreach ($invoice->invitations as $invitation) { + $invitations = $proposal ? $proposal->invitations : $invoice->invitations; + foreach ($invitations as $invitation) { $data = [ 'pdfString' => $pdfString, 'documentStrings' => $documentStrings, 'ublString' => $ublString, + 'proposal' => $proposal, ]; $response = $this->sendInvitation($invitation, $invoice, $emailTemplate, $emailSubject, $reminder, $isFirst, $data); $isFirst = false; @@ -111,7 +115,7 @@ class ContactMailer extends Mailer $account->loadLocalizationSettings(); - if ($sent === true) { + if ($sent === true && ! $proposal) { if ($invoice->isType(INVOICE_TYPE_QUOTE)) { event(new QuoteWasEmailed($invoice, $reminder)); } else { @@ -136,17 +140,18 @@ class ContactMailer extends Mailer * @return bool|string */ private function sendInvitation( - Invitation $invitation, + $invitation, Invoice $invoice, $body, $subject, $reminder, $isFirst, - $attachments + $data ) { $client = $invoice->client; $account = $invoice->account; $user = $invitation->user; + $proposal = $data['proposal']; if ($user->trashed()) { $user = $account->users()->orderBy('id')->first(); @@ -169,163 +174,20 @@ class ContactMailer extends Mailer 'amount' => $invoice->getRequestedAmount(), ]; - // Let the client know they'll be billed later - if ($client->autoBillLater()) { - $variables['autobill'] = $invoice->present()->autoBillEmailMessage(); - } + if (! $proposal) { + // Let the client know they'll be billed later + if ($client->autoBillLater()) { + $variables['autobill'] = $invoice->present()->autoBillEmailMessage(); + } - if (empty($invitation->contact->password) && $account->isClientPortalPasswordEnabled() && $account->send_portal_password) { - // The contact needs a password - $variables['password'] = $password = $this->generatePassword(); - $invitation->contact->password = bcrypt($password); - $invitation->contact->save(); - } - - $data = [ - 'body' => $this->templateService->processVariables($body, $variables), - 'link' => $invitation->getLink(), - 'entityType' => $invoice->getEntityType(), - 'invoiceId' => $invoice->id, - 'invitation' => $invitation, - 'account' => $account, - 'client' => $client, - 'invoice' => $invoice, - 'documents' => $attachments['documentStrings'], - 'notes' => $reminder, - 'bccEmail' => $isFirst ? $account->getBccEmail() : false, - 'fromEmail' => $account->getFromEmail(), - ]; - - if ($account->attachPDF()) { - $data['pdfString'] = $attachments['pdfString']; - $data['pdfFileName'] = $invoice->getFileName(); - } - if ($account->attachUBL()) { - $data['ublString'] = $attachments['ublString']; - $data['ublFileName'] = $invoice->getFileName('xml'); - } - - $subject = $this->templateService->processVariables($subject, $variables); - $fromEmail = $account->getReplyToEmail() ?: $user->email; - $view = $account->getTemplateView(ENTITY_INVOICE); - - $response = $this->sendTo($invitation->contact->email, $fromEmail, $account->getDisplayName(), $subject, $view, $data); - - if ($response === true) { - return true; - } else { - return $response; - } - } - - /** - * @param Invoice $invoice - * @param bool $reminder - * @param bool $pdfString - * - * @return bool|null|string - */ - public function sendProposal(Proposal $proposal, $template = false) - { - $proposal->load('invitations', 'invoice.client.language', 'account'); - - $account = $proposal->account; - $invoice = $proposal->invoice; - $client = $invoice->client; - - $response = null; - - if ($proposal->trashed()) { - return trans('texts.email_error_inactive_proposal'); - } elseif ($client->trashed()) { - return trans('texts.email_error_inactive_client'); - } elseif ($invoice->trashed()) { - return trans('texts.email_error_inactive_invoice'); - } - - $account->loadLocalizationSettings($client); - $emailTemplate = !empty($template['body']) ? $template['body'] : $account->getEmailTemplate(ENTITY_PROPOSAL); - $emailSubject = !empty($template['subject']) ? $template['subject'] : $account->getEmailSubject(ENTITY_PROPOSAL); - - $sent = false; - $pdfString = false; - - /* - if ($account->attachPDF()) { - $pdfString = $invoice->getPDFString(); - } - */ - - $isFirst = true; - foreach ($proposal->invitations as $invitation) { - $data = [ - //'pdfString' => $pdfString, - ]; - $response = $this->sendProposalInvitation($invitation, $proposal, $emailTemplate, $emailSubject, $isFirst, $data); - $isFirst = false; - if ($response === true) { - $sent = true; + if (empty($invitation->contact->password) && $account->isClientPortalPasswordEnabled() && $account->send_portal_password) { + // The contact needs a password + $variables['password'] = $password = $this->generatePassword(); + $invitation->contact->password = bcrypt($password); + $invitation->contact->save(); } } - $account->loadLocalizationSettings(); - - /* - if ($sent === true) { - event(new QuoteWasEmailed($invoice, $reminder)); - } - */ - - return $response; - } - - /** - * @param Invitation $invitation - * @param Invoice $invoice - * @param $body - * @param $subject - * @param $pdfString - * @param $documentStrings - * @param mixed $reminder - * - * @throws \Laracasts\Presenter\Exceptions\PresenterException - * - * @return bool|string - */ - private function sendProposalInvitation( - ProposalInvitation $invitation, - Proposal $proposal, - $body, - $subject, - $isFirst, - $attachments - ) { - $account = $proposal->account; - $invoice = $proposal->invoice; - $client = $invoice->client; - $user = $invitation->user; - - if ($user->trashed()) { - $user = $account->users()->orderBy('id')->first(); - } - - if (! $user->email || ! $user->registered) { - return trans('texts.email_error_user_unregistered'); - } elseif (! $user->confirmed || $this->isThrottled($account)) { - return trans('texts.email_error_user_unconfirmed'); - } elseif (! $invitation->contact->email) { - return trans('texts.email_error_invalid_contact_email'); - } elseif ($invitation->contact->trashed()) { - return trans('texts.email_error_inactive_contact'); - } - - $variables = [ - 'account' => $account, - 'client' => $client, - 'invitation' => $invitation, - 'amount' => $invoice->getRequestedAmount(), - ]; - $data = [ 'body' => $this->templateService->processVariables($body, $variables), 'link' => $invitation->getLink(), @@ -335,18 +197,22 @@ class ContactMailer extends Mailer 'account' => $account, 'client' => $client, 'invoice' => $invoice, - 'documents' => $attachments['documentStrings'], + 'documents' => $data['documentStrings'], 'notes' => $reminder, 'bccEmail' => $isFirst ? $account->getBccEmail() : false, 'fromEmail' => $account->getFromEmail(), ]; - /* - if ($account->attachPDF()) { - $data['pdfString'] = $attachments['pdfString']; - $data['pdfFileName'] = $invoice->getFileName(); + if (! $proposal) { + if ($account->attachPDF()) { + $data['pdfString'] = $data['pdfString']; + $data['pdfFileName'] = $invoice->getFileName(); + } + if ($account->attachUBL()) { + $data['ublString'] = $data['ublString']; + $data['ublFileName'] = $invoice->getFileName('xml'); + } } - */ $subject = $this->templateService->processVariables($subject, $variables); $fromEmail = $account->getReplyToEmail() ?: $user->email; @@ -361,7 +227,6 @@ class ContactMailer extends Mailer } } - /** * @param int $length * diff --git a/app/Ninja/Mailers/Mailer.php b/app/Ninja/Mailers/Mailer.php index aeda149421..f501b022de 100644 --- a/app/Ninja/Mailers/Mailer.php +++ b/app/Ninja/Mailers/Mailer.php @@ -121,7 +121,12 @@ class Mailer } $notes = isset($data['notes']) ? $data['notes'] : false; - $invoice->markInvitationSent($invitation, $messageId, true, $notes); + + if ($proposal = $data['proposal']) { + $invitation->markSent($messageId); + } else { + $invoice->markInvitationSent($invitation, $messageId, true, $notes); + } } return true; diff --git a/app/Services/TemplateService.php b/app/Services/TemplateService.php index e98cfff390..8ed901d406 100644 --- a/app/Services/TemplateService.php +++ b/app/Services/TemplateService.php @@ -27,7 +27,13 @@ class TemplateService /** @var \App\Models\Invitation $invitation */ $invitation = $data['invitation']; - $invoice = $invitation->invoice; + // check if it's a proposal + if ($invitation->proposal) { + $invoice = $invitation->proposal->invoice; + } else { + $invoice = $invitation->invoice; + } + $contact = $invitation->contact; $passwordHTML = isset($data['password']) ? '

'.trans('texts.password').': '.$data['password'].'

' : false; $documentsHTML = ''; diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 8380a64cc8..03bdad392c 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -2732,6 +2732,7 @@ $LANG = array( 'proposal_email' => 'Proposal Email', 'proposal_subject' => 'New proposal :number from :account', 'proposal_message' => 'To view your proposal for :amount, click the link below.', + 'emailed_proposal' => 'Successfully emailed proposal', ); diff --git a/resources/views/proposals/edit.blade.php b/resources/views/proposals/edit.blade.php index 6aa3f5382b..086e3b91a4 100644 --- a/resources/views/proposals/edit.blade.php +++ b/resources/views/proposals/edit.blade.php @@ -64,14 +64,15 @@ ->appendIcon(Icon::create('download-alt')) !!} {!! Button::success(trans("texts.save")) + ->withAttributes(['id' => 'saveButton']) ->submit() ->appendIcon(Icon::create('floppy-disk')) !!} - @if ($proposal) - {!! Button::info(trans('texts.email')) - ->withAttributes(['onclick' => 'onEmailClick()']) - ->appendIcon(Icon::create('send')) !!} + {!! Button::info(trans('texts.email')) + ->withAttributes(['id' => 'emailButton', 'onclick' => 'onEmailClick()']) + ->appendIcon(Icon::create('send')) !!} + @if ($proposal) {!! DropdownButton::normal(trans('texts.more_actions')) ->withContents($proposal->present()->moreActions()) !!} @endif @@ -88,22 +89,31 @@ var templates = {!! $templates !!}; var templateMap = {}; + var isFormSubmitting = false; function onFormSubmit() { + // prevent duplicate form submissions + if (isFormSubmitting) { + return; + } + isFormSubmitting = true; + $('#saveButton, #emailButton').prop('disabled', true); + $('#html').val(grapesjsEditor.getHtml()); $('#css').val(grapesjsEditor.getCss()); return true; } + function onEmailClick() { + $('#action').val('email'); + $('#saveButton').click(); + } + @if ($proposal) function onDownloadClick() { location.href = "{{ url("/proposals/{$proposal->public_id}/download") }}"; } - function onEmailClick() { - $('#action').val('email'); - $('#mainForm').submit(); - } @endif function loadTemplate() {