mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-08 20:22:42 +01:00
Proposals
This commit is contained in:
parent
d280383cfa
commit
754dd15af7
@ -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'));
|
||||||
}
|
}
|
||||||
|
@ -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, '/');
|
||||||
|
@ -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
|
||||||
*
|
*
|
||||||
|
@ -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;
|
||||||
|
@ -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 = '';
|
||||||
|
@ -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',
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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() {
|
||||||
|
Loading…
Reference in New Issue
Block a user