1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-08 20:22:42 +01:00

Proposals

This commit is contained in:
Hillel Coren 2018-02-12 11:23:16 +02:00
parent d280383cfa
commit 754dd15af7
7 changed files with 81 additions and 183 deletions

View File

@ -104,8 +104,14 @@ class ProposalController extends BaseController
public function store(CreateProposalRequest $request) public function store(CreateProposalRequest $request)
{ {
$proposal = $this->proposalService->save($request->input()); $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()); return redirect()->to($proposal->getRoute());
} }
@ -120,7 +126,8 @@ class ProposalController extends BaseController
} }
if ($action == 'email') { if ($action == 'email') {
$this->contactMailer->sendProposal($proposal); $this->contactMailer->sendInvoice($proposal->invoice, false, false, $proposal);
Session::flash('message', trans('texts.emailed_proposal'));
} else { } else {
Session::flash('message', trans('texts.updated_proposal')); Session::flash('message', trans('texts.updated_proposal'));
} }

View File

@ -24,6 +24,10 @@ trait Inviteable
$this->load('account'); $this->load('account');
} }
if ($this->proposal_id) {
$type = 'proposal';
}
$account = $this->account; $account = $this->account;
$iframe_url = $account->iframe_url; $iframe_url = $account->iframe_url;
$url = trim(SITE_URL, '/'); $url = trim(SITE_URL, '/');

View File

@ -4,8 +4,6 @@ namespace App\Ninja\Mailers;
use App\Events\InvoiceWasEmailed; use App\Events\InvoiceWasEmailed;
use App\Events\QuoteWasEmailed; use App\Events\QuoteWasEmailed;
use App\Models\Invitation;
use App\Models\ProposalInvitation;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Proposal; use App\Models\Proposal;
use App\Models\Payment; use App\Models\Payment;
@ -40,18 +38,22 @@ class ContactMailer extends Mailer
* *
* @return bool|null|string * @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) { if ($invoice->is_recurring) {
return false; return false;
} }
$invoice->load('invitations', 'client.language', 'account'); $invoice->load('invitations', 'client.language', 'account');
$entityType = $invoice->getEntityType();
if ($proposal) {
$entityType = ENTITY_PROPOSAL;
} else {
$entityType = $invoice->getEntityType();
}
$client = $invoice->client; $client = $invoice->client;
$account = $invoice->account; $account = $invoice->account;
$response = null; $response = null;
if ($client->trashed()) { if ($client->trashed()) {
@ -68,10 +70,10 @@ class ContactMailer extends Mailer
$pdfString = false; $pdfString = false;
$ublString = false; $ublString = false;
if ($account->attachPDF()) { if ($account->attachPDF() && ! $proposal) {
$pdfString = $invoice->getPDFString(); $pdfString = $invoice->getPDFString();
} }
if ($account->attachUBL()) { if ($account->attachUBL() && ! $proposal) {
$ublString = dispatch(new ConvertInvoiceToUbl($invoice)); $ublString = dispatch(new ConvertInvoiceToUbl($invoice));
} }
@ -96,11 +98,13 @@ class ContactMailer extends Mailer
} }
$isFirst = true; $isFirst = true;
foreach ($invoice->invitations as $invitation) { $invitations = $proposal ? $proposal->invitations : $invoice->invitations;
foreach ($invitations as $invitation) {
$data = [ $data = [
'pdfString' => $pdfString, 'pdfString' => $pdfString,
'documentStrings' => $documentStrings, 'documentStrings' => $documentStrings,
'ublString' => $ublString, 'ublString' => $ublString,
'proposal' => $proposal,
]; ];
$response = $this->sendInvitation($invitation, $invoice, $emailTemplate, $emailSubject, $reminder, $isFirst, $data); $response = $this->sendInvitation($invitation, $invoice, $emailTemplate, $emailSubject, $reminder, $isFirst, $data);
$isFirst = false; $isFirst = false;
@ -111,7 +115,7 @@ class ContactMailer extends Mailer
$account->loadLocalizationSettings(); $account->loadLocalizationSettings();
if ($sent === true) { if ($sent === true && ! $proposal) {
if ($invoice->isType(INVOICE_TYPE_QUOTE)) { if ($invoice->isType(INVOICE_TYPE_QUOTE)) {
event(new QuoteWasEmailed($invoice, $reminder)); event(new QuoteWasEmailed($invoice, $reminder));
} else { } else {
@ -136,17 +140,18 @@ class ContactMailer extends Mailer
* @return bool|string * @return bool|string
*/ */
private function sendInvitation( private function sendInvitation(
Invitation $invitation, $invitation,
Invoice $invoice, Invoice $invoice,
$body, $body,
$subject, $subject,
$reminder, $reminder,
$isFirst, $isFirst,
$attachments $data
) { ) {
$client = $invoice->client; $client = $invoice->client;
$account = $invoice->account; $account = $invoice->account;
$user = $invitation->user; $user = $invitation->user;
$proposal = $data['proposal'];
if ($user->trashed()) { if ($user->trashed()) {
$user = $account->users()->orderBy('id')->first(); $user = $account->users()->orderBy('id')->first();
@ -169,163 +174,20 @@ class ContactMailer extends Mailer
'amount' => $invoice->getRequestedAmount(), 'amount' => $invoice->getRequestedAmount(),
]; ];
// Let the client know they'll be billed later if (! $proposal) {
if ($client->autoBillLater()) { // Let the client know they'll be billed later
$variables['autobill'] = $invoice->present()->autoBillEmailMessage(); if ($client->autoBillLater()) {
} $variables['autobill'] = $invoice->present()->autoBillEmailMessage();
}
if (empty($invitation->contact->password) && $account->isClientPortalPasswordEnabled() && $account->send_portal_password) { if (empty($invitation->contact->password) && $account->isClientPortalPasswordEnabled() && $account->send_portal_password) {
// The contact needs a password // The contact needs a password
$variables['password'] = $password = $this->generatePassword(); $variables['password'] = $password = $this->generatePassword();
$invitation->contact->password = bcrypt($password); $invitation->contact->password = bcrypt($password);
$invitation->contact->save(); $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;
} }
} }
$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 = [ $data = [
'body' => $this->templateService->processVariables($body, $variables), 'body' => $this->templateService->processVariables($body, $variables),
'link' => $invitation->getLink(), 'link' => $invitation->getLink(),
@ -335,18 +197,22 @@ class ContactMailer extends Mailer
'account' => $account, 'account' => $account,
'client' => $client, 'client' => $client,
'invoice' => $invoice, 'invoice' => $invoice,
'documents' => $attachments['documentStrings'], 'documents' => $data['documentStrings'],
'notes' => $reminder, 'notes' => $reminder,
'bccEmail' => $isFirst ? $account->getBccEmail() : false, 'bccEmail' => $isFirst ? $account->getBccEmail() : false,
'fromEmail' => $account->getFromEmail(), 'fromEmail' => $account->getFromEmail(),
]; ];
/* if (! $proposal) {
if ($account->attachPDF()) { if ($account->attachPDF()) {
$data['pdfString'] = $attachments['pdfString']; $data['pdfString'] = $data['pdfString'];
$data['pdfFileName'] = $invoice->getFileName(); $data['pdfFileName'] = $invoice->getFileName();
}
if ($account->attachUBL()) {
$data['ublString'] = $data['ublString'];
$data['ublFileName'] = $invoice->getFileName('xml');
}
} }
*/
$subject = $this->templateService->processVariables($subject, $variables); $subject = $this->templateService->processVariables($subject, $variables);
$fromEmail = $account->getReplyToEmail() ?: $user->email; $fromEmail = $account->getReplyToEmail() ?: $user->email;
@ -361,7 +227,6 @@ class ContactMailer extends Mailer
} }
} }
/** /**
* @param int $length * @param int $length
* *

View File

@ -121,7 +121,12 @@ class Mailer
} }
$notes = isset($data['notes']) ? $data['notes'] : false; $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; return true;

View File

@ -27,7 +27,13 @@ class TemplateService
/** @var \App\Models\Invitation $invitation */ /** @var \App\Models\Invitation $invitation */
$invitation = $data['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; $contact = $invitation->contact;
$passwordHTML = isset($data['password']) ? '<p>'.trans('texts.password').': '.$data['password'].'<p>' : false; $passwordHTML = isset($data['password']) ? '<p>'.trans('texts.password').': '.$data['password'].'<p>' : false;
$documentsHTML = ''; $documentsHTML = '';

View File

@ -2732,6 +2732,7 @@ $LANG = array(
'proposal_email' => 'Proposal Email', 'proposal_email' => 'Proposal Email',
'proposal_subject' => 'New proposal :number from :account', 'proposal_subject' => 'New proposal :number from :account',
'proposal_message' => 'To view your proposal for :amount, click the link below.', 'proposal_message' => 'To view your proposal for :amount, click the link below.',
'emailed_proposal' => 'Successfully emailed proposal',
); );

View File

@ -64,14 +64,15 @@
->appendIcon(Icon::create('download-alt')) !!} ->appendIcon(Icon::create('download-alt')) !!}
{!! Button::success(trans("texts.save")) {!! Button::success(trans("texts.save"))
->withAttributes(['id' => 'saveButton'])
->submit() ->submit()
->appendIcon(Icon::create('floppy-disk')) !!} ->appendIcon(Icon::create('floppy-disk')) !!}
@if ($proposal) {!! Button::info(trans('texts.email'))
{!! Button::info(trans('texts.email')) ->withAttributes(['id' => 'emailButton', 'onclick' => 'onEmailClick()'])
->withAttributes(['onclick' => 'onEmailClick()']) ->appendIcon(Icon::create('send')) !!}
->appendIcon(Icon::create('send')) !!}
@if ($proposal)
{!! DropdownButton::normal(trans('texts.more_actions')) {!! DropdownButton::normal(trans('texts.more_actions'))
->withContents($proposal->present()->moreActions()) !!} ->withContents($proposal->present()->moreActions()) !!}
@endif @endif
@ -88,22 +89,31 @@
var templates = {!! $templates !!}; var templates = {!! $templates !!};
var templateMap = {}; var templateMap = {};
var isFormSubmitting = false;
function onFormSubmit() { function onFormSubmit() {
// prevent duplicate form submissions
if (isFormSubmitting) {
return;
}
isFormSubmitting = true;
$('#saveButton, #emailButton').prop('disabled', true);
$('#html').val(grapesjsEditor.getHtml()); $('#html').val(grapesjsEditor.getHtml());
$('#css').val(grapesjsEditor.getCss()); $('#css').val(grapesjsEditor.getCss());
return true; return true;
} }
function onEmailClick() {
$('#action').val('email');
$('#saveButton').click();
}
@if ($proposal) @if ($proposal)
function onDownloadClick() { function onDownloadClick() {
location.href = "{{ url("/proposals/{$proposal->public_id}/download") }}"; location.href = "{{ url("/proposals/{$proposal->public_id}/download") }}";
} }
function onEmailClick() {
$('#action').val('email');
$('#mainForm').submit();
}
@endif @endif
function loadTemplate() { function loadTemplate() {