1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-09 20:52:56 +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)
{
$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'));
}

View File

@ -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, '/');

View File

@ -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
*

View File

@ -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;

View File

@ -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']) ? '<p>'.trans('texts.password').': '.$data['password'].'<p>' : false;
$documentsHTML = '';

View File

@ -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',
);

View File

@ -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() {