diff --git a/app/Http/Controllers/PublicClientController.php b/app/Http/Controllers/PublicClientController.php index 3a5dd61e3215..72e48c2e2444 100644 --- a/app/Http/Controllers/PublicClientController.php +++ b/app/Http/Controllers/PublicClientController.php @@ -305,7 +305,7 @@ class PublicClientController extends BaseController ->make(); } - public function invoiceIndex() + public function recurringInvoiceIndex() { if (!$invitation = $this->getInvitation()) { return $this->returnError(); @@ -322,6 +322,34 @@ class PublicClientController extends BaseController $data = [ 'color' => $color, 'account' => $account, + 'client' => $invitation->invoice->client, + 'clientFontUrl' => $account->getFontsUrl(), + 'title' => trans('texts.recurring_invoices'), + 'entityType' => ENTITY_RECURRING_INVOICE, + 'columns' => Utils::trans(['frequency', 'start_date', 'end_date', 'invoice_total', 'auto_bill']), + ]; + + return response()->view('public_list', $data); + } + + public function invoiceIndex() + { + if (!$invitation = $this->getInvitation()) { + return $this->returnError(); + } + + $account = $invitation->account; + + if (!$account->enable_client_portal) { + return $this->returnError(); + } + + $color = $account->primary_color ? $account->primary_color : '#0b4d78'; + + $data = [ + 'color' => $color, + 'account' => $account, + 'client' => $invitation->invoice->client, 'clientFontUrl' => $account->getFontsUrl(), 'title' => trans('texts.invoices'), 'entityType' => ENTITY_INVOICE, @@ -340,6 +368,15 @@ class PublicClientController extends BaseController return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_INVOICE, Input::get('sSearch')); } + public function recurringInvoiceDatatable() + { + if (!$invitation = $this->getInvitation()) { + return ''; + } + + return $this->invoiceRepo->getClientRecurringDatatable($invitation->contact_id); + } + public function paymentIndex() { @@ -860,4 +897,28 @@ class PublicClientController extends BaseController Session::flash('error', $message); Utils::logError("Payment Method Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message), 'PHP', true); } + + public function setAutoBill(){ + if (!$invitation = $this->getInvitation()) { + return $this->returnError(); + } + + $validator = Validator::make(Input::all(), array('public_id' => 'required')); + $client = $invitation->invoice->client; + + if ($validator->fails()) { + return Redirect::to('client/invoices/recurring'); + } + + $publicId = Input::get('public_id'); + $enable = Input::get('enable'); + $invoice = $client->invoices->where('public_id', intval($publicId))->first(); + + if ($invoice && $invoice->is_recurring && $invoice->enable_auto_bill > AUTO_BILL_OFF) { + $invoice->auto_bill = $enable ? true : false; + $invoice->save(); + } + + return Redirect::to('client/invoices/recurring'); + } } diff --git a/app/Http/routes.php b/app/Http/routes.php index 8e6d430a6729..fec67489b79d 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -51,6 +51,8 @@ Route::group(['middleware' => 'auth:client'], function() { Route::post('client/paymentmethods/{source_id}/remove', 'PublicClientController@removePaymentMethod'); Route::get('client/quotes', 'PublicClientController@quoteIndex'); Route::get('client/invoices', 'PublicClientController@invoiceIndex'); + Route::get('client/invoices/recurring', 'PublicClientController@recurringInvoiceIndex'); + Route::post('client/invoices/auto_bill', 'PublicClientController@setAutoBill'); Route::get('client/documents', 'PublicClientController@documentIndex'); Route::get('client/payments', 'PublicClientController@paymentIndex'); Route::get('client/dashboard', 'PublicClientController@dashboard'); @@ -60,6 +62,7 @@ Route::group(['middleware' => 'auth:client'], function() { Route::get('api/client.quotes', array('as'=>'api.client.quotes', 'uses'=>'PublicClientController@quoteDatatable')); Route::get('api/client.invoices', array('as'=>'api.client.invoices', 'uses'=>'PublicClientController@invoiceDatatable')); + Route::get('api/client.recurring_invoices', array('as'=>'api.client.recurring_invoices', 'uses'=>'PublicClientController@recurringInvoiceDatatable')); Route::get('api/client.documents', array('as'=>'api.client.documents', 'uses'=>'PublicClientController@documentDatatable')); Route::get('api/client.payments', array('as'=>'api.client.payments', 'uses'=>'PublicClientController@paymentDatatable')); Route::get('api/client.activity', array('as'=>'api.client.activity', 'uses'=>'PublicClientController@activityDatatable')); @@ -687,6 +690,10 @@ if (!defined('CONTACT_EMAIL')) { define('RESELLER_REVENUE_SHARE', 'A'); define('RESELLER_LIMITED_USERS', 'B'); + + define('AUTO_BILL_OFF', 0); + define('AUTO_BILL_OPT_IN', 1); + define('AUTO_BILL_OPT_OUT', 2); // These must be lowercase define('PLAN_FREE', 'free'); diff --git a/app/Models/Client.php b/app/Models/Client.php index e51f15f124af..a60d44793c22 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -319,6 +319,10 @@ class Client extends EntityModel $this->last_login = Carbon::now()->toDateTimeString(); $this->save(); } + + public function hasAutoBillInvoices(){ + return $this->invoices()->where('auto_bill', 1)->count() > 0; + } } Client::creating(function ($client) { diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index 6b41db461a31..d57710d4689b 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -145,21 +145,68 @@ class InvoiceRepository extends BaseRepository return $query; } - public function getClientDatatable($contactId, $entityType, $search) + public function getClientRecurringDatatable($contactId) { $query = DB::table('invitations') ->join('accounts', 'accounts.id', '=', 'invitations.account_id') ->join('invoices', 'invoices.id', '=', 'invitations.invoice_id') ->join('clients', 'clients.id', '=', 'invoices.client_id') + ->join('frequencies', 'frequencies.id', '=', 'invoices.frequency_id') ->where('invitations.contact_id', '=', $contactId) ->where('invitations.deleted_at', '=', null) - ->where('invoices.is_quote', '=', $entityType == ENTITY_QUOTE) + ->where('invoices.is_quote', '=', false) ->where('invoices.is_deleted', '=', false) ->where('clients.deleted_at', '=', null) - ->where('invoices.is_recurring', '=', false) - // This needs to be a setting to also hide the activity on the dashboard page - //->where('invoices.invoice_status_id', '>=', INVOICE_STATUS_SENT) + ->where('invoices.is_recurring', '=', true) + ->where('invoices.enable_auto_bill', '>', AUTO_BILL_OFF) + //->where('invoices.start_date', '>=', date('Y-m-d H:i:s')) ->select( + DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'), + DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'), + 'invitations.invitation_key', + 'invoices.invoice_number', + 'invoices.due_date', + 'clients.public_id as client_public_id', + 'clients.name as client_name', + 'invoices.public_id', + 'invoices.amount', + 'invoices.start_date', + 'invoices.end_date', + 'invoices.auto_bill', + 'frequencies.name as frequency' + ); + + $table = \Datatable::query($query) + ->addColumn('frequency', function ($model) { return $model->frequency; }) + ->addColumn('start_date', function ($model) { return Utils::fromSqlDate($model->start_date); }) + ->addColumn('end_date', function ($model) { return Utils::fromSqlDate($model->end_date); }) + ->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); }) + ->addColumn('auto_bill', function ($model) { + if ($model->auto_bill) { + return trans('texts.enabled') . ' ('.trans('texts.disable').')'; + } else { + return trans('texts.disabled') . ' ('.trans('texts.enable').')'; + } + }); + + return $table->make(); + } + + public function getClientDatatable($contactId, $entityType, $search) + { + $query = DB::table('invitations') + ->join('accounts', 'accounts.id', '=', 'invitations.account_id') + ->join('invoices', 'invoices.id', '=', 'invitations.invoice_id') + ->join('clients', 'clients.id', '=', 'invoices.client_id') + ->where('invitations.contact_id', '=', $contactId) + ->where('invitations.deleted_at', '=', null) + ->where('invoices.is_quote', '=', $entityType == ENTITY_QUOTE) + ->where('invoices.is_deleted', '=', false) + ->where('clients.deleted_at', '=', null) + ->where('invoices.is_recurring', '=', false) + // This needs to be a setting to also hide the activity on the dashboard page + //->where('invoices.invoice_status_id', '>=', INVOICE_STATUS_SENT) + ->select( DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'), DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'), 'invitations.invitation_key', @@ -269,6 +316,16 @@ class InvoiceRepository extends BaseRepository $invoice->start_date = Utils::toSqlDate($data['start_date']); $invoice->end_date = Utils::toSqlDate($data['end_date']); $invoice->auto_bill = isset($data['auto_bill']) && $data['auto_bill'] ? true : false; + $invoice->enable_auto_bill = isset($data['enable_auto_bill']) ? intval($data['enable_auto_bill']) : 0; + + if ($invoice->enable_auto_bill != AUTO_BILL_OPT_IN && $invoice->enable_auto_bill != AUTO_BILL_OPT_OUT ) { + // Auto-bill is not enabled + $invoice->enable_auto_bill = AUTO_BILL_OFF; + $invoice->auto_bill = false; + } elseif ($isNew) { + $invoice->auto_bill = $invoice->enable_auto_bill == AUTO_BILL_OPT_OUT; + } + if (isset($data['recurring_due_date'])) { $invoice->due_date = $data['recurring_due_date']; diff --git a/database/migrations/2016_04_23_182223_payments_changes.php b/database/migrations/2016_04_23_182223_payments_changes.php index 839fe26c940c..5dd1bb6154d4 100644 --- a/database/migrations/2016_04_23_182223_payments_changes.php +++ b/database/migrations/2016_04_23_182223_payments_changes.php @@ -33,6 +33,15 @@ class PaymentsChanges extends Migration $table->date('expiration')->nullable(); $table->text('gateway_error')->nullable(); }); + + Schema::table('invoices', function($table) + { + $table->tinyInteger('enable_auto_bill')->default(AUTO_BILL_OFF); + }); + + \DB::table('invoices') + ->where('auto_bill', '=', 1) + ->update(array('enable_auto_bill' => AUTO_BILL_OPT_OUT)); } /** @@ -53,6 +62,10 @@ class PaymentsChanges extends Migration $table->dropColumn('expiration'); $table->dropColumn('gateway_error'); }); + + Schema::table('invoices', function ($table) { + $table->dropColumn('enable_auto_bill'); + }); Schema::dropIfExists('payment_statuses'); } diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 8cb584b396c0..bcca0ed6e7ff 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1266,7 +1266,15 @@ $LANG = array( 'add_payment_method' => 'Add Payment Method', 'account_holder_type' => 'Account Holder Type', 'ach_authorization' => 'I authorize :company to electronically debit my account and, if necessary, electronically credit my account to correct erroneous debits.', - 'ach_authorization_required' => 'You must consent to ACH transactions.' + 'ach_authorization_required' => 'You must consent to ACH transactions.', + 'off' => 'Off', + 'opt_in' => 'Opt-in', + 'opt_out' => 'Opt-out', + 'enabled_by_client' => 'Enabled by client', + 'disabled_by_client' => 'Disabled by client', + 'manage_auto_bill' => 'Manage Auto-bill', + 'enabled' => 'Enabled', + 'disabled' => 'Disabled', ); return $LANG; diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 1ce548133c06..9a83ec57a520 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -159,10 +159,25 @@ @if($account->getTokenGatewayId()) - {!! Former::checkbox('auto_bill') - ->label(trans('texts.auto_bill')) - ->text(trans('texts.enable')) - ->data_bind("checked: auto_bill, valueUpdate: 'afterkeydown'") !!} + {!! Former::radios('enable_auto_bill')->radios([ + trans('texts.off') => array('name' => 'enable_auto_bill', 'value' => 0, 'data-bind' => "checked: enable_auto_bill, valueUpdate: 'afterkeydown', checkedValue:0"), + trans('texts.opt_in') => array('name' => 'enable_auto_bill', 'value' => 1, 'data-bind' => "checked: enable_auto_bill, valueUpdate: 'afterkeydown', checkedValue:1"), + trans('texts.opt_out') => array('name' => 'enable_auto_bill', 'value' => 2, 'data-bind' => "checked: enable_auto_bill, valueUpdate: 'afterkeydown', checkedValue:2"), + ])->inline() + ->label(trans('texts.auto_bill')) !!} + +
+
+
+
+ {{trans('texts.enabled_by_client')}} ({{trans('texts.disable')}}) +
+
+ {{trans('texts.disabled_by_client')}} ({{trans('texts.enable')}}) +
+
+
+
@endif {!! Former::text('po_number')->label(trans('texts.po_number_short'))->data_bind("value: po_number, valueUpdate: 'afterkeydown'") !!} diff --git a/resources/views/invoices/knockout.blade.php b/resources/views/invoices/knockout.blade.php index 8fe4bcbcce80..5b39703105ca 100644 --- a/resources/views/invoices/knockout.blade.php +++ b/resources/views/invoices/knockout.blade.php @@ -188,7 +188,8 @@ function InvoiceModel(data) { self.tax_rate2 = ko.observable(); self.is_recurring = ko.observable(0); self.is_quote = ko.observable({{ $entityType == ENTITY_QUOTE ? '1' : '0' }}); - self.auto_bill = ko.observable(); + self.auto_bill = ko.observable(false); + self.enable_auto_bill = ko.observable(0); self.invoice_status_id = ko.observable(0); self.invoice_items = ko.observableArray(); self.documents = ko.observableArray(); diff --git a/resources/views/public_list.blade.php b/resources/views/public_list.blade.php index 90041e21ea73..d6ee070b50e3 100644 --- a/resources/views/public_list.blade.php +++ b/resources/views/public_list.blade.php @@ -35,7 +35,12 @@ --> -

{{ $title }}

+ @if($entityType == ENTITY_INVOICE && $account->getTokenGatewayId() && $client->hasAutoBillInvoices()) +
+ {!! Button::info(trans("texts.manage_auto_bill"))->asLinkTo(URL::to('/client/invoices/recurring'))->appendIcon(Icon::create('repeat')) !!} +
+ @endif +

{{ $title }}

{!! Datatable::table() ->addColumn($columns) @@ -45,6 +50,22 @@ + @if($entityType == ENTITY_RECURRING_INVOICE) + {!! Former::open(URL::to('/client/invoices/auto_bill'))->id('auto_bill_form') !!} + + + {!! Former::close() !!} + + + @endif + +

 

@stop \ No newline at end of file