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

Enabled setting an invoice footer

This commit is contained in:
Hillel Coren 2015-02-28 23:42:47 +02:00
parent dc117dbaff
commit cfe01e7e05
22 changed files with 178 additions and 57 deletions

View File

@ -13,7 +13,8 @@ open-source software.
1. Redistributions of source code, in whole or part and with or without
modification requires the express permission of the author and must prominently
display "Powered by InvoiceNinja" in verifiable form with hyperlink to said site.
display "Powered by InvoiceNinja" or the Invoice Ninja logo in verifiable form
with hyperlink to said site.
2. Neither the name nor any trademark of the Author may be used to
endorse or promote products derived from this software without specific
prior written permission.

View File

@ -53,6 +53,7 @@ class SendRecurringInvoices extends Command
$invoice->po_number = $recurInvoice->po_number;
$invoice->public_notes = $recurInvoice->public_notes;
$invoice->terms = $recurInvoice->terms;
$invoice->invoice_footer = $recurInvoice->invoice_footer;
$invoice->tax_name = $recurInvoice->tax_name;
$invoice->tax_rate = $recurInvoice->tax_rate;
$invoice->invoice_design_id = $recurInvoice->invoice_design_id;

View File

@ -597,6 +597,7 @@ class AccountController extends \BaseController
{
$account = Auth::user()->account;
$account->invoice_terms = Input::get('invoice_terms');
$account->invoice_footer = Input::get('invoice_footer');
$account->email_footer = Input::get('email_footer');
$account->save();

View File

@ -87,7 +87,8 @@ class InvoiceApiController extends Controller
$fields = [
'discount' => 0,
'is_amount_discount' => false,
'terms' => $account->invoice_terms,
'terms' => '',
'invoice_footer' => '',
'public_notes' => '',
'po_number' => '',
'invoice_design_id' => $account->invoice_design_id,

View File

@ -535,5 +535,8 @@ return array(
'match_address' => '*Address must match address accociated with credit card.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
);

View File

@ -525,5 +525,8 @@ return array(
'match_address' => '*Address must match address accociated with credit card.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
);

View File

@ -194,8 +194,8 @@ return array(
'email_paid' => 'Email me when an invoice is <b>paid</b>',
'site_updates' => 'Site Updates',
'custom_messages' => 'Custom Messages',
'default_invoice_terms' => 'Set default invoice terms',
'default_email_footer' => 'Set default email signature',
'default_invoice_terms' => 'Set default <b>invoice terms</b>',
'default_email_footer' => 'Set default <b>email signature</b>',
'import_clients' => 'Import Client Data',
'csv_file' => 'Select CSV file',
'export_clients' => 'Export Client Data',
@ -533,5 +533,8 @@ return array(
'match_address' => '*Address must match address accociated with credit card.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'default_invoice_footer' => 'Set default <b>invoice footer</b>',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
);

View File

@ -505,5 +505,8 @@ return array(
'match_address' => '*Address must match address accociated with credit card.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
);

View File

@ -525,6 +525,9 @@ return array(
'order_overview' => 'Order overview',
'match_address' => '*Address must match address accociated with credit card.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
);

View File

@ -528,5 +528,8 @@ return array(
'match_address' => '*Address must match address accociated with credit card.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
);

View File

@ -536,6 +536,9 @@ return array(
'match_address' => '*Address must match address accociated with credit card.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
);

View File

@ -533,7 +533,10 @@ return array(
'order_overview' => 'Order overview',
'match_address' => '*Address must match address accociated with credit card.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
);

View File

@ -529,6 +529,9 @@ return array(
'match_address' => '*Address must match address accociated with credit card.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
);

View File

@ -516,5 +516,8 @@ return array(
'match_address' => '*Address must match address accociated with credit card.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
);

View File

@ -177,26 +177,32 @@ class Activity extends Eloquent
} else {
$diff = floatval($invoice->amount) - floatval($invoice->getOriginal('amount'));
if ($diff == 0) {
return;
$fieldChanged = false;
foreach (['invoice_number', 'po_number', 'invoice_date', 'due_date', 'terms', 'public_notes', 'invoice_footer'] as $field) {
if ($invoice->$field != $invoice->getOriginal($field)) {
$fieldChanged = true;
break;
}
}
$backupInvoice = Invoice::with('invoice_items', 'client.account', 'client.contacts')->find($invoice->id);
if ($diff > 0 || $fieldChanged) {
$backupInvoice = Invoice::with('invoice_items', 'client.account', 'client.contacts')->find($invoice->id);
if (!$invoice->is_quote && !$invoice->is_recurring) {
$client->balance = $client->balance + $diff;
$client->save();
if ($diff > 0 && !$invoice->is_quote && !$invoice->is_recurring) {
$client->balance = $client->balance + $diff;
$client->save();
}
$activity = Activity::getBlank($invoice);
$activity->client_id = $invoice->client_id;
$activity->invoice_id = $invoice->id;
$activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_UPDATE_QUOTE : ACTIVITY_TYPE_UPDATE_INVOICE;
$activity->message = Utils::encodeActivity(Auth::user(), 'updated', $invoice);
$activity->balance = $client->balance;
$activity->adjustment = $invoice->is_quote || $invoice->is_recurring ? 0 : $diff;
$activity->json_backup = $backupInvoice->hidePrivateFields()->toJSON();
$activity->save();
}
$activity = Activity::getBlank($invoice);
$activity->client_id = $invoice->client_id;
$activity->invoice_id = $invoice->id;
$activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_UPDATE_QUOTE : ACTIVITY_TYPE_UPDATE_INVOICE;
$activity->message = Utils::encodeActivity(Auth::user(), 'updated', $invoice);
$activity->balance = $client->balance;
$activity->adjustment = $invoice->is_quote || $invoice->is_recurring ? 0 : $diff;
$activity->json_backup = $backupInvoice->hidePrivateFields()->toJSON();
$activity->save();
}
}

View File

@ -77,6 +77,7 @@ class Invoice extends EntityModel
'invoice_date',
'due_date',
'terms',
'invoice_footer',
'public_notes',
'amount',
'balance',

View File

@ -221,6 +221,8 @@ class InvoiceRepository
}
}
$account = \Auth::user()->account;
$invoice->client_id = $data['client_id'];
$invoice->discount = round(Utils::parseFloat($data['discount']), 2);
$invoice->is_amount_discount = $data['is_amount_discount'] ? true : false;
@ -240,7 +242,8 @@ class InvoiceRepository
$invoice->end_date = null;
}
$invoice->terms = trim($data['terms']);
$invoice->terms = trim($data['terms']) ? trim($data['terms']) : $account->invoice_terms;
$invoice->invoice_footer = trim($data['invoice_footer']) ? trim($data['invoice_footer']) : $account->invoice_footer;
$invoice->public_notes = trim($data['public_notes']);
$invoice->po_number = trim($data['po_number']);
$invoice->invoice_design_id = $data['invoice_design_id'];
@ -357,9 +360,14 @@ class InvoiceRepository
$invoice->invoice_items()->save($invoiceItem);
}
if (isset($data['set_default_terms']) && $data['set_default_terms']) {
$account = \Auth::user()->account;
$account->invoice_terms = $invoice->terms;
if ((isset($data['set_default_terms']) && $data['set_default_terms'])
|| (isset($data['set_default_footer']) && $data['set_default_footer'])) {
if (isset($data['set_default_terms']) && $data['set_default_terms']) {
$account->invoice_terms = trim($data['terms']);
}
if (isset($data['set_default_footer']) && $data['set_default_footer']) {
$account->invoice_footer = trim($data['invoice_footer']);
}
$account->save();
}
@ -400,6 +408,7 @@ class InvoiceRepository
'start_date',
'end_date',
'terms',
'invoice_footer',
'public_notes',
'invoice_design_id',
'tax_name',

View File

@ -37,7 +37,8 @@
</div></div>
{{ Former::legend('custom_messages') }}
{{ Former::textarea('invoice_terms')->label(trans('texts.default_invoice_terms')) }}
{{ Former::textarea('invoice_terms')->label(trans('texts.default_invoice_terms')) }}
{{ Former::textarea('invoice_footer')->label(trans('texts.default_invoice_footer')) }}
{{ Former::textarea('email_footer')->label(trans('texts.default_email_footer')) }}
{{ Former::actions( Button::lg_success_submit(trans('texts.save'))->append_with_icon('floppy-disk') ) }}

View File

@ -165,16 +165,36 @@
<td class="hide-border"/>
<td colspan="2" rowspan="6" style="vertical-align:top">
<br/>
{{ Former::textarea('public_notes')->data_bind("value: wrapped_notes, valueUpdate: 'afterkeydown'")
->label(false)->placeholder(trans('texts.note_to_client'))->style('resize: none') }}
{{ Former::textarea('terms')->data_bind("value: wrapped_terms, valueUpdate: 'afterkeydown'")
->label(false)->placeholder(trans('texts.invoice_terms'))->style('resize: none')
->addGroupClass('less-space-bottom') }}
<label class="checkbox" style="width: 200px">
<input type="checkbox" style="width: 24px" data-bind="checked: set_default_terms"/>{{ trans('texts.save_as_default_terms') }}
</label>
<div role="tabpanel">
<ul class="nav nav-tabs" role="tablist" style="border: none">
<li role="presentation" class="active"><a href="#notes" aria-controls="notes" role="tab" data-toggle="tab">{{ trans('texts.note_to_client') }}</a></li>
<li role="presentation"><a href="#terms" aria-controls="terms" role="tab" data-toggle="tab">{{ trans('texts.invoice_terms') }}</a></li>
<li role="presentation"><a href="#footer" aria-controls="footer" role="tab" data-toggle="tab">{{ trans('texts.invoice_footer') }}</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="notes" style="padding-bottom:44px">
{{ Former::textarea('public_notes')->data_bind("value: wrapped_notes, valueUpdate: 'afterkeydown'")
->label(null)->style('resize: none; min-width: 460px;')->rows(3) }}
</div>
<div role="tabpanel" class="tab-pane" id="terms">
{{ Former::textarea('terms')->data_bind("value:wrapped_terms, placeholder: default_terms, valueUpdate: 'afterkeydown'")
->label(false)->style('resize: none; min-width: 460px')->rows(3)
->help('<label class="checkbox" style="width: 200px">
<input type="checkbox" style="width: 24px" data-bind="checked: set_default_terms"/>'.trans('texts.save_as_default_terms').'</label>') }}
</div>
<div role="tabpanel" class="tab-pane" id="footer">
{{ Former::textarea('invoice_footer')->data_bind("value:wrapped_footer, placeholder: default_footer, valueUpdate: 'afterkeydown'")
->label(false)->style('resize: none; min-width: 460px')->rows(3)
->help('<label class="checkbox" style="width: 200px">
<input type="checkbox" style="width: 24px" data-bind="checked: set_default_footer"/>'.trans('texts.save_as_default_footer').'</label>') }}
</div>
</div>
</div>
</td>
<td style="display:none" data-bind="visible: $root.invoice_item_taxes.show"/>
<td class="hide-border" style="display:none" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="{{ $account->hide_quantity ? 1 : 2 }}">{{ trans('texts.subtotal') }}</td>
<td style="text-align: right"><span data-bind="text: totals.subtotal"/></td>
</tr>
@ -243,7 +263,7 @@
<tr>
<td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
<td style="display:none" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="{{ $account->hide_quantity ? 1 : 2 }}"><b>{{ trans($entityType == ENTITY_INVOICE ? 'texts.balance_due' : 'texts.total') }}</b></td>
<td style="text-align: right"><span data-bind="text: totals.total"/></td>
</tr>
@ -568,7 +588,7 @@
});
}
$('#terms, #public_notes, #invoice_number, #invoice_date, #due_date, #po_number, #discount, #currency_id, #invoice_design_id, #recurring, #is_amount_discount').change(function() {
$('#invoice_footer, #terms, #public_notes, #invoice_number, #invoice_date, #due_date, #po_number, #discount, #currency_id, #invoice_design_id, #recurring, #is_amount_discount').change(function() {
setTimeout(function() {
refreshPDF();
}, 1);
@ -618,8 +638,7 @@
var client = model.invoice().client();
setComboboxValue($('.client_select'),
client.public_id(),
client.name.display());
client.name.display());
});
function applyComboboxListeners() {
@ -653,6 +672,13 @@
invoice.is_quote = {{ $entityType == ENTITY_QUOTE ? 'true' : 'false' }};
invoice.contact = _.findWhere(invoice.client.contacts, {send_invoice: true});
if (!invoice.terms) {
invoice.terms = "{{ $account->invoice_terms }}";
}
if (!invoice.invoice_footer) {
invoice.invoice_footer = "{{ $account->invoice_footer }}";
}
@if (file_exists($account->getLogoPath()))
invoice.image = "{{ HTML::image_data($account->getLogoPath()) }}";
invoice.imageWidth = {{ $account->getLogoWidth() }};
@ -1025,8 +1051,12 @@
self.is_amount_discount = ko.observable(0);
self.frequency_id = ko.observable('');
//self.currency_id = ko.observable({{ $client && $client->currency_id ? $client->currency_id : Session::get(SESSION_CURRENCY) }});
self.terms = ko.observable(wordWrapText('{{ str_replace(["\r\n","\r","\n"], '\n', addslashes($account->invoice_terms)) }}', 300));
self.set_default_terms = ko.observable(false);
self.terms = ko.observable('');
self.default_terms = ko.observable({{ $account->invoice_terms ? 'true' : 'false' }} ? wordWrapText('{{ str_replace(["\r\n","\r","\n"], '\n', addslashes($account->invoice_terms)) }}', 300) : "{{ trans('texts.invoice_terms') }}");
self.set_default_terms = ko.observable(false);
self.invoice_footer = ko.observable('');
self.default_footer = ko.observable({{ $account->invoice_footer ? 'true' : 'false' }} ? wordWrapText('{{ str_replace(["\r\n","\r","\n"], '\n', addslashes($account->invoice_footer)) }}', 600) : "{{ trans('texts.invoice_footer') }}");
self.set_default_footer = ko.observable(false);
self.public_notes = ko.observable('');
self.po_number = ko.observable('');
self.invoice_date = ko.observable('{{ Utils::today() }}');
@ -1102,31 +1132,37 @@
self.wrapped_terms = ko.computed({
read: function() {
$('#terms').height(this.terms().split('\n').length * 36);
return this.terms();
},
write: function(value) {
value = wordWrapText(value, 300);
self.terms(value);
$('#terms').height(value.split('\n').length * 36);
},
owner: this
});
self.wrapped_notes = ko.computed({
read: function() {
$('#public_notes').height(this.public_notes().split('\n').length * 36);
return this.public_notes();
},
write: function(value) {
value = wordWrapText(value, 300);
self.public_notes(value);
$('#public_notes').height(value.split('\n').length * 36);
},
owner: this
});
self.wrapped_notes = ko.computed({
read: function() {
return this.public_notes();
},
write: function(value) {
value = wordWrapText(value, 300);
self.public_notes(value);
},
owner: this
});
self.wrapped_footer = ko.computed({
read: function() {
return this.invoice_footer();
},
write: function(value) {
value = wordWrapText(value, 600);
self.invoice_footer(value);
},
owner: this
});
self.removeItem = function(item) {
self.invoice_items.remove(item);

View File

@ -69,7 +69,7 @@
} else {
window.accountLogo = "{{ HTML::image_data($account->getLogoPath()) }}";
}
@endif
@endif
var NINJA = NINJA || {};
NINJA.primaryColor = "{{ $account->primary_color }}";

View File

@ -31603,6 +31603,16 @@ function GetPdf(invoice, javascript){
eval(javascript);
// add footer
if (invoice.invoice_footer) {
doc.setFontType('normal');
doc.setFontSize('8');
SetPdfColor('Black',doc);
var top = doc.internal.pageSize.height - layout.marginLeft;
var numLines = invoice.invoice_footer.split("\n").length - 1;
doc.text(layout.marginLeft, top - (numLines * 8), invoice.invoice_footer);
}
return doc;
}
@ -31991,6 +32001,13 @@ if (window.ko) {
if (value) $(element).datepicker('update', value);
}
};
ko.bindingHandlers.placeholder = {
init: function (element, valueAccessor, allBindingsAccessor) {
var underlyingObservable = valueAccessor();
ko.applyBindingsToNode(element, { attr: { placeholder: underlyingObservable } } );
}
};
}
function wordWrapText(value, width)

View File

@ -80,6 +80,16 @@ function GetPdf(invoice, javascript){
eval(javascript);
// add footer
if (invoice.invoice_footer) {
doc.setFontType('normal');
doc.setFontSize('8');
SetPdfColor('Black',doc);
var top = doc.internal.pageSize.height - layout.marginLeft;
var numLines = invoice.invoice_footer.split("\n").length - 1;
doc.text(layout.marginLeft, top - (numLines * 8), invoice.invoice_footer);
}
return doc;
}
@ -468,6 +478,13 @@ if (window.ko) {
if (value) $(element).datepicker('update', value);
}
};
ko.bindingHandlers.placeholder = {
init: function (element, valueAccessor, allBindingsAccessor) {
var underlyingObservable = valueAccessor();
ko.applyBindingsToNode(element, { attr: { placeholder: underlyingObservable } } );
}
};
}
function wordWrapText(value, width)