Support signing for invoice
This commit is contained in:
parent
fbf618a226
commit
3ccb33ec21
|
|
@ -747,12 +747,7 @@ class AccountController extends BaseController
|
|||
private function saveClientPortal()
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
|
||||
$account->enable_client_portal = !!Input::get('enable_client_portal');
|
||||
$account->enable_client_portal_dashboard = !!Input::get('enable_client_portal_dashboard');
|
||||
$account->enable_portal_password = !!Input::get('enable_portal_password');
|
||||
$account->send_portal_password = !!Input::get('send_portal_password');
|
||||
$account->enable_buy_now_buttons = !!Input::get('enable_buy_now_buttons');
|
||||
$account->fill(Input::all());
|
||||
|
||||
// Only allowed for pro Invoice Ninja users or white labeled self-hosted users
|
||||
if (Auth::user()->account->hasFeature(FEATURE_CLIENT_PORTAL_CSS)) {
|
||||
|
|
|
|||
|
|
@ -198,6 +198,19 @@ class ClientPortalController extends BaseController
|
|||
return $pdfString;
|
||||
}
|
||||
|
||||
public function sign($invitationKey)
|
||||
{
|
||||
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
|
||||
return RESULT_FAILURE;
|
||||
}
|
||||
|
||||
$invitation->signature_base64 = Input::get('signature');
|
||||
$invitation->signature_date = date_create();
|
||||
$invitation->save();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
public function dashboard($contactKey = false)
|
||||
{
|
||||
if ($contactKey) {
|
||||
|
|
|
|||
|
|
@ -218,6 +218,8 @@ class InvoiceController extends BaseController
|
|||
$contact->invitation_viewed = $invitation->viewed_date && $invitation->viewed_date != '0000-00-00 00:00:00' ? $invitation->viewed_date : false;
|
||||
$contact->invitation_openend = $invitation->opened_date && $invitation->opened_date != '0000-00-00 00:00:00' ? $invitation->opened_date : false;
|
||||
$contact->invitation_status = $contact->email_error ? false : $invitation->getStatus();
|
||||
$contact->invitation_signature_svg = $invitation->signature_base64;
|
||||
$contact->invitation_signature_date = $invitation->signature_date;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ Route::post('/get_started', 'AccountController@getStarted');
|
|||
Route::group(['middleware' => 'auth:client'], function() {
|
||||
Route::get('view/{invitation_key}', 'ClientPortalController@view');
|
||||
Route::get('download/{invitation_key}', 'ClientPortalController@download');
|
||||
Route::put('sign/{invitation_key}', 'ClientPortalController@sign');
|
||||
Route::get('view', 'HomeController@viewLogo');
|
||||
Route::get('approve/{invitation_key}', 'QuoteController@approve');
|
||||
Route::get('payment/{invitation_key}/{gateway_type?}/{source_id?}', 'OnlinePaymentController@showPayment');
|
||||
|
|
|
|||
|
|
@ -101,7 +101,9 @@ class Utils
|
|||
return false;
|
||||
}
|
||||
|
||||
return \App\Models\Account::first()->hasFeature(FEATURE_WHITE_LABEL);
|
||||
$account = \App\Models\Account::first();
|
||||
|
||||
return $account && $account->hasFeature(FEATURE_WHITE_LABEL);
|
||||
}
|
||||
|
||||
public static function getResllerType()
|
||||
|
|
|
|||
|
|
@ -70,6 +70,15 @@ class Account extends Eloquent
|
|||
'include_item_taxes_inline',
|
||||
'start_of_week',
|
||||
'financial_year_start',
|
||||
'enable_client_portal',
|
||||
'enable_client_portal_dashboard',
|
||||
'enable_portal_password',
|
||||
'send_portal_password',
|
||||
'enable_buy_now_buttons',
|
||||
'show_accept_invoice_terms',
|
||||
'show_accept_quote_terms',
|
||||
'require_invoice_signature',
|
||||
'require_quote_signature',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -1861,6 +1870,29 @@ class Account extends Eloquent
|
|||
|
||||
return $this->enabled_modules & static::$modules[$entityType];
|
||||
}
|
||||
|
||||
public function showAuthenticatePanel($invoice)
|
||||
{
|
||||
return $this->showAcceptTerms($invoice) || $this->showSignature($invoice);
|
||||
}
|
||||
|
||||
public function showAcceptTerms($invoice)
|
||||
{
|
||||
if ( ! $this->isPro() || ! $invoice->terms) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $invoice->is_quote ? $this->show_accept_quote_terms : $this->show_accept_invoice_terms;
|
||||
}
|
||||
|
||||
public function showSignature($invoice)
|
||||
{
|
||||
if ( ! $this->isPro()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $invoice->is_quote ? $this->require_quote_signature : $this->require_invoice_signature;
|
||||
}
|
||||
}
|
||||
|
||||
Account::updated(function ($account)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@
|
|||
"dropzone": "~4.3.0",
|
||||
"nouislider": "~8.5.1",
|
||||
"bootstrap-daterangepicker": "~2.1.24",
|
||||
"sweetalert2": "^5.3.8"
|
||||
"sweetalert2": "^5.3.8",
|
||||
"jSignature": "brinley/jSignature#^2.1.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"jquery": "~1.11"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddInvoiceSignature extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('invitations', function($table)
|
||||
{
|
||||
$table->text('signature_base64')->nullable();
|
||||
$table->timestamp('signature_date')->nullable();
|
||||
});
|
||||
|
||||
Schema::table('companies', function($table)
|
||||
{
|
||||
$table->string('utm_source')->nullable();
|
||||
$table->string('utm_medium')->nullable();
|
||||
$table->string('utm_campaign')->nullable();
|
||||
$table->string('utm_term')->nullable();
|
||||
$table->string('utm_content')->nullable();
|
||||
});
|
||||
|
||||
Schema::table('payment_methods', function($table)
|
||||
{
|
||||
$table->dropForeign('payment_methods_account_gateway_token_id_foreign');
|
||||
});
|
||||
|
||||
Schema::table('payment_methods', function($table)
|
||||
{
|
||||
$table->foreign('account_gateway_token_id')->references('id')->on('account_gateway_tokens')->onDelete('cascade');
|
||||
});
|
||||
|
||||
Schema::table('payments', function($table)
|
||||
{
|
||||
$table->dropForeign('payments_payment_method_id_foreign');
|
||||
});
|
||||
|
||||
Schema::table('payments', function($table)
|
||||
{
|
||||
$table->foreign('payment_method_id')->references('id')->on('payment_methods')->onDelete('cascade');;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('invitations', function($table)
|
||||
{
|
||||
$table->dropColumn('signature_base64');
|
||||
$table->dropColumn('signature_date');
|
||||
});
|
||||
|
||||
Schema::table('companies', function($table)
|
||||
{
|
||||
$table->dropColumn('utm_source');
|
||||
$table->dropColumn('utm_medium');
|
||||
$table->dropColumn('utm_campaign');
|
||||
$table->dropColumn('utm_term');
|
||||
$table->dropColumn('utm_content');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -80,6 +80,10 @@ elixir(function(mix) {
|
|||
bowerDir + '/bootstrap-daterangepicker/daterangepicker.js'
|
||||
], 'public/js/daterangepicker.min.js');
|
||||
|
||||
mix.scripts([
|
||||
bowerDir + '/jSignature/libs/jSignature.min.js'
|
||||
], 'public/js/jSignature.min.js');
|
||||
|
||||
mix.scripts([
|
||||
bowerDir + '/jquery/dist/jquery.js',
|
||||
bowerDir + '/jquery-ui/jquery-ui.js',
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -2175,6 +2175,19 @@ $LANG = array(
|
|||
'created_by' => 'Created by :name',
|
||||
'modules' => 'Modules',
|
||||
'financial_year_start' => 'First Month of the Year',
|
||||
'authentication' => 'Authentication',
|
||||
'checkbox' => 'Checkbox',
|
||||
'invoice_signature' => 'Signature',
|
||||
'show_accept_invoice_terms' => 'Invoice Terms Checkbox',
|
||||
'show_accept_invoice_terms_help' => 'Require client to confirm that they accept the invoice terms.',
|
||||
'show_accept_quote_terms' => 'Quote Terms Checkbox',
|
||||
'show_accept_quote_terms_help' => 'Require client to confirm that they accept the quote terms.',
|
||||
'require_invoice_signature' => 'Invoice Signature',
|
||||
'require_invoice_signature_help' => 'Require client to provide their signature.',
|
||||
'require_quote_signature' => 'Quote Signature',
|
||||
'require_quote_signature_help' => 'Require client to provide their signature.',
|
||||
'i_agree' => 'I Agree To The Terms & Conditions',
|
||||
'sign_here' => 'Please sign here:',
|
||||
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@
|
|||
{!! Former::populateField('enable_portal_password', intval($enable_portal_password)) !!}
|
||||
{!! Former::populateField('send_portal_password', intval($send_portal_password)) !!}
|
||||
{!! Former::populateField('enable_buy_now_buttons', intval($account->enable_buy_now_buttons)) !!}
|
||||
{!! Former::populateField('show_accept_invoice_terms', intval($account->show_accept_invoice_terms)) !!}
|
||||
{!! Former::populateField('show_accept_quote_terms', intval($account->show_accept_quote_terms)) !!}
|
||||
{!! Former::populateField('require_invoice_signature', intval($account->require_invoice_signature)) !!}
|
||||
{!! Former::populateField('require_quote_signature', intval($account->require_quote_signature)) !!}
|
||||
|
||||
@if (!Utils::isNinja() && !Auth::user()->account->hasFeature(FEATURE_WHITE_LABEL))
|
||||
<div class="alert alert-warning" style="font-size:larger;">
|
||||
|
|
@ -61,9 +65,20 @@
|
|||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{!! trans('texts.security') !!}</h3>
|
||||
<h3 class="panel-title">{!! trans('texts.authentication') !!}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div role="tabpanel">
|
||||
<ul class="nav nav-tabs" role="tablist" style="border: none">
|
||||
<li role="presentation" class="active"><a href="#password" aria-controls="password" role="tab" data-toggle="tab">{{ trans('texts.password') }}</a></li>
|
||||
<li role="presentation"><a href="#checkbox" aria-controls="checkbox" role="tab" data-toggle="tab">{{ trans('texts.checkbox') }}</a></li>
|
||||
<li role="presentation"><a href="#signature" aria-controls="signature" role="tab" data-toggle="tab">{{ trans('texts.invoice_signature') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="password">
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
{!! Former::checkbox('enable_portal_password')
|
||||
->text(trans('texts.enable'))
|
||||
|
|
@ -78,6 +93,46 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="checkbox">
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
{!! Former::checkbox('show_accept_invoice_terms')
|
||||
->text(trans('texts.enable'))
|
||||
->help(trans('texts.show_accept_invoice_terms_help'))
|
||||
->label(trans('texts.show_accept_invoice_terms')) !!}
|
||||
</div>
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
{!! Former::checkbox('show_accept_quote_terms')
|
||||
->text(trans('texts.enable'))
|
||||
->help(trans('texts.show_accept_quote_terms_help'))
|
||||
->label(trans('texts.show_accept_quote_terms')) !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="signature">
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
{!! Former::checkbox('require_invoice_signature')
|
||||
->text(trans('texts.enable'))
|
||||
->help(trans('texts.require_invoice_signature_help'))
|
||||
->label(trans('texts.require_invoice_signature')) !!}
|
||||
</div>
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
{!! Former::checkbox('require_quote_signature')
|
||||
->text(trans('texts.enable'))
|
||||
->help(trans('texts.require_quote_signature_help'))
|
||||
->label(trans('texts.require_quote_signature')) !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" id="buy_now">
|
||||
<div class="panel-heading">
|
||||
|
|
|
|||
|
|
@ -122,9 +122,11 @@
|
|||
@if (Utils::isConfirmed())
|
||||
<span style="vertical-align:text-top;color:red" class="fa fa-exclamation-triangle"
|
||||
data-bind="visible: $data.email_error, tooltip: {title: $data.email_error}"></span>
|
||||
<span style="vertical-align:text-top" class="glyphicon glyphicon-info-sign"
|
||||
<span style="vertical-align:text-top" class="fa fa-info-circle"
|
||||
data-bind="visible: $data.invitation_status, tooltip: {title: $data.invitation_status, html: true},
|
||||
style: {color: $data.info_color}"></span>
|
||||
<span style="vertical-align:text-top" class="fa fa-user-circle"
|
||||
data-bind="visible: $data.invitation_signature_svg, tooltip: {title: '', html: true}"></span>
|
||||
@endif
|
||||
</span>
|
||||
@endif
|
||||
|
|
@ -563,7 +565,7 @@
|
|||
</div>
|
||||
<p> </p>
|
||||
|
||||
@if (Auth::user()->account->live_preview))
|
||||
@if (Auth::user()->account->live_preview)
|
||||
@include('invoices.pdf', ['account' => Auth::user()->account])
|
||||
@else
|
||||
<script type="text/javascript">
|
||||
|
|
|
|||
|
|
@ -596,6 +596,8 @@ function ContactModel(data) {
|
|||
self.invitation_openend = ko.observable(false);
|
||||
self.invitation_viewed = ko.observable(false);
|
||||
self.email_error = ko.observable('');
|
||||
self.invitation_signature_svg = ko.observable('');
|
||||
self.invitation_signature_date = ko.observable('');
|
||||
|
||||
if (data) {
|
||||
ko.mapping.fromJS(data, {}, this);
|
||||
|
|
|
|||
|
|
@ -8,17 +8,28 @@
|
|||
@foreach ($invoice->client->account->getFontFolders() as $font)
|
||||
<script src="{{ asset('js/vfs_fonts/'.$font.'.js') }}" type="text/javascript"></script>
|
||||
@endforeach
|
||||
|
||||
<script src="{{ asset('pdf.built.js') }}?no_cache={{ NINJA_VERSION }}" type="text/javascript"></script>
|
||||
|
||||
@if ($account->showSignature($invoice))
|
||||
<script src="{{ asset('js/jSignature.min.js') }}"></script>
|
||||
@endif
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
|
||||
.dropdown-menu li a{
|
||||
overflow:hidden;
|
||||
margin-top:5px;
|
||||
margin-bottom:5px;
|
||||
}
|
||||
|
||||
#signature {
|
||||
border: 2px dotted black;
|
||||
background-color:lightgrey;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -99,7 +110,7 @@
|
|||
@if (!empty($partialView))
|
||||
@include($partialView)
|
||||
@else
|
||||
<div class="pull-right" style="text-align:right">
|
||||
<div id="paymentButtons" class="pull-right" style="text-align:right">
|
||||
@if ($invoice->isQuote())
|
||||
{!! Button::normal(trans('texts.download_pdf'))->withAttributes(['onclick' => 'onDownloadClick()'])->large() !!}
|
||||
@if ($showApprove)
|
||||
|
|
@ -190,6 +201,33 @@
|
|||
});
|
||||
@else
|
||||
refreshPDF();
|
||||
@endif
|
||||
|
||||
@if ($account->showAuthenticatePanel($invoice))
|
||||
$('#paymentButtons a').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
window.pendingPaymentHref = $(this).attr('href');
|
||||
@if ($account->showSignature($invoice))
|
||||
if (window.pendingPaymentInit) {
|
||||
$("#signature").jSignature('reset');
|
||||
}
|
||||
@endif
|
||||
@if ($account->showAcceptTerms($invoice))
|
||||
$('#termsCheckbox').attr('checked', false);
|
||||
@endif
|
||||
$('#authenticationModal').modal('show');
|
||||
});
|
||||
|
||||
@if ($account->showSignature($invoice))
|
||||
$('#authenticationModal').on('shown.bs.modal', function () {
|
||||
if ( ! window.pendingPaymentInit) {
|
||||
window.pendingPaymentInit = true;
|
||||
$("#signature").jSignature().bind('change', function(e) {
|
||||
setModalPayNowEnabled();
|
||||
});;
|
||||
}
|
||||
});
|
||||
@endif
|
||||
@endif
|
||||
});
|
||||
|
||||
|
|
@ -203,6 +241,48 @@
|
|||
$('#customGatewayModal').modal('show');
|
||||
}
|
||||
|
||||
function onModalPayNowClick() {
|
||||
@if ($account->showSignature($invoice))
|
||||
var data = {
|
||||
signature: $('#signature').jSignature('getData', 'svgbase64')[1]
|
||||
};
|
||||
$.ajax({
|
||||
url: "{{ URL::to('sign/' . $invitation->invitation_key) }}",
|
||||
type: 'PUT',
|
||||
data: data,
|
||||
success: function(response) {
|
||||
redirectToPayment();
|
||||
}
|
||||
});
|
||||
@else
|
||||
redirectToPayment();
|
||||
@endif
|
||||
}
|
||||
|
||||
function redirectToPayment() {
|
||||
$('#authenticationModal').modal('hide');
|
||||
location.href = window.pendingPaymentHref;
|
||||
}
|
||||
|
||||
function setModalPayNowEnabled() {
|
||||
var disabled = false;
|
||||
|
||||
@if ($account->showAcceptTerms($invoice))
|
||||
if ( ! $('#termsCheckbox').is(':checked')) {
|
||||
disabled = true;
|
||||
}
|
||||
@endif
|
||||
|
||||
@if ($account->showSignature($invoice))
|
||||
if ( ! $('#signature').jSignature('isModified')) {
|
||||
disabled = true;
|
||||
}
|
||||
@endif
|
||||
|
||||
$('#modalPayNowButton').attr('disabled', disabled);
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
@include('invoices.pdf', ['account' => $invoice->client->account, 'viewPDF' => true])
|
||||
|
|
@ -232,4 +312,42 @@
|
|||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($account->showAuthenticatePanel($invoice))
|
||||
<div class="modal fade" id="authenticationModal" tabindex="-1" role="dialog" aria-labelledby="authenticationModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title"> </h4>
|
||||
</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<div class="well">
|
||||
{!! nl2br(e($invoice->terms)) !!}
|
||||
</div>
|
||||
@if ($account->showSignature($invoice))
|
||||
<div>
|
||||
{{ trans('texts.sign_here') }}
|
||||
</div>
|
||||
<div id="signature"></div><br/>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
@if ($account->showAcceptTerms($invoice))
|
||||
<div class="pull-left">
|
||||
<label for="termsCheckbox" style="font-weight:normal">
|
||||
<input id="termsCheckbox" type="checkbox" onclick="setModalPayNowEnabled()"/>
|
||||
{{ trans('texts.i_agree') }}
|
||||
</label>
|
||||
</div>
|
||||
@endif
|
||||
<button id="modalPayNowButton" type="button" class="btn btn-success" onclick="onModalPayNowClick()" disabled="">{{ trans('texts.pay_now') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@stop
|
||||
|
|
|
|||
Loading…
Reference in New Issue