From 815f099116da12419f272fc47fa42365bfea2b86 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 2 Apr 2017 23:59:44 +0300 Subject: [PATCH 01/28] Update download link --- docs/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/install.rst b/docs/install.rst index d8708539ee71..f5eb71e61b22 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -29,7 +29,7 @@ Step 1: Download the code You can either download the zip file below or checkout the code from our GitHub repository. The zip includes all third party libraries whereas using GitHub requires you to use Composer to install the dependencies. -https://download.invoiceninja.com/ninja-v3.1.3.zip +https://download.invoiceninja.com/ninja-v3.2.0.zip .. Note:: All Pro and Enterprise features from our hosted app are included in both the zip file and the GitHub repository. We offer a $20 per year white-label license to remove our branding. From 5e2e35b2baf247127e65639d3fec7be5384d0e34 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 3 Apr 2017 00:41:48 +0300 Subject: [PATCH 02/28] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a11471427832..3413fa5e5135 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ All contributors are welcome! For information on how contribute to Invoice Ninja, please see our [contributing guide](CONTRIBUTING.md). ## Credits -* [Hillel Coren](https://github.com/hillelcoren) +* [Hillel Coren](https://hillelcoren.com/) * [All contributors](https://github.com/invoiceninja/invoiceninja/graphs/contributors) **Special thanks to:** From f9afbf44bcc7cc6143339bc62366c617ac112e5f Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 3 Apr 2017 13:22:01 +0300 Subject: [PATCH 03/28] Keep logging ModelNotFoundException errors --- app/Exceptions/Handler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 9ed644515c34..f7c8b2197b2a 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -28,7 +28,7 @@ class Handler extends ExceptionHandler */ protected $dontReport = [ TokenMismatchException::class, - ModelNotFoundException::class, + //ModelNotFoundException::class, //AuthorizationException::class, //HttpException::class, //ValidationException::class, From f4c2ede5308e577c9a3c156a5c7927432f78b583 Mon Sep 17 00:00:00 2001 From: Dennis Hermsmeier Date: Mon, 3 Apr 2017 15:58:49 +0200 Subject: [PATCH 04/28] Add REDIS_HOST If you want to use Redis as backend for caching you have to use "REDIS_HOST" instead of "CACHE_HOST". --- .env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.example b/.env.example index ec281e283601..ce6f298ebdf7 100644 --- a/.env.example +++ b/.env.example @@ -45,6 +45,7 @@ FCM_API_TOKEN= #CACHE_DRIVER= #CACHE_HOST= +#REDIS_HOST= #CACHE_PORT1= #CACHE_PORT2= From c0558de528ced174b9dfd9e48a95293e67184124 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 3 Apr 2017 17:21:12 +0300 Subject: [PATCH 05/28] Fix for client balance when voiding Braintree payment --- app/Models/Payment.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Models/Payment.php b/app/Models/Payment.php index b836bacba449..fb51b594d662 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -238,12 +238,12 @@ class Payment extends EntityModel return false; } + Event::fire(new PaymentWasVoided($this)); + $this->refunded = $this->amount; $this->payment_status_id = PAYMENT_STATUS_VOIDED; $this->save(); - Event::fire(new PaymentWasVoided($this)); - return true; } From 4ed8468195f81a3a32c5b46999153bf1bb670992 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 3 Apr 2017 22:06:09 +0300 Subject: [PATCH 06/28] Always show recurring invoices in client portal --- app/Http/Controllers/ClientPortalController.php | 10 ++++++++-- app/Models/Client.php | 10 +++++++++- app/Ninja/Repositories/InvoiceRepository.php | 8 ++++++-- resources/views/public_list.blade.php | 6 +++--- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/ClientPortalController.php b/app/Http/Controllers/ClientPortalController.php index c583835fd49d..9800a1ed1651 100644 --- a/app/Http/Controllers/ClientPortalController.php +++ b/app/Http/Controllers/ClientPortalController.php @@ -326,14 +326,20 @@ class ClientPortalController extends BaseController } $color = $account->primary_color ? $account->primary_color : '#0b4d78'; + $columns = ['frequency', 'start_date', 'end_date', 'invoice_total']; + $client = $contact->client; + + if ($client->hasAutoBillConfigurableInvoices()) { + $columns[] = 'auto_bill'; + } $data = [ 'color' => $color, 'account' => $account, - 'client' => $contact->client, + 'client' => $client, 'title' => trans('texts.recurring_invoices'), 'entityType' => ENTITY_RECURRING_INVOICE, - 'columns' => Utils::trans(['frequency', 'start_date', 'end_date', 'invoice_total', 'auto_bill']), + 'columns' => Utils::trans($columns), ]; return response()->view('public_list', $data); diff --git a/app/Models/Client.php b/app/Models/Client.php index 5a42983d38ba..7f886fc96879 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -543,7 +543,15 @@ class Client extends EntityModel */ public function hasAutoBillConfigurableInvoices() { - return $this->invoices()->whereIn('auto_bill', [AUTO_BILL_OPT_IN, AUTO_BILL_OPT_OUT])->count() > 0; + return $this->invoices()->whereIsPublic(true)->whereIn('auto_bill', [AUTO_BILL_OPT_IN, AUTO_BILL_OPT_OUT])->count() > 0; + } + + /** + * @return bool + */ + public function hasRecurringInvoices() + { + return $this->invoices()->whereIsPublic(true)->whereIsRecurring(true)->count() > 0; } public function defaultDaysDue() diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index b52982841163..3daa8b3d89b4 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -211,7 +211,6 @@ class InvoiceRepository extends BaseRepository ->where('clients.deleted_at', '=', null) ->where('invoices.is_recurring', '=', true) ->where('invoices.is_public', '=', true) - ->whereIn('invoices.auto_bill', [AUTO_BILL_OPT_IN, AUTO_BILL_OPT_OUT]) //->where('invoices.start_date', '>=', date('Y-m-d H:i:s')) ->select( DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'), @@ -225,6 +224,7 @@ class InvoiceRepository extends BaseRepository 'invoices.amount', 'invoices.start_date', 'invoices.end_date', + 'invoices.auto_bill', 'invoices.client_enable_auto_bill', 'frequencies.name as frequency' ); @@ -243,7 +243,11 @@ class InvoiceRepository extends BaseRepository return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); }) ->addColumn('client_enable_auto_bill', function ($model) { - if ($model->client_enable_auto_bill) { + if ($model->auto_bill == AUTO_BILL_OFF) { + return trans('texts.disabled'); + } elseif ($model->auto_bill == AUTO_BILL_ALWAYS) { + return trans('texts.enabled'); + } elseif ($model->client_enable_auto_bill) { return trans('texts.enabled') . ' - '.trans('texts.disable').''; } else { return trans('texts.disabled') . ' - '.trans('texts.enable').''; diff --git a/resources/views/public_list.blade.php b/resources/views/public_list.blade.php index bc8e7d35839f..bd81eb30dadd 100644 --- a/resources/views/public_list.blade.php +++ b/resources/views/public_list.blade.php @@ -35,9 +35,9 @@ --> - @if($entityType == ENTITY_INVOICE && $account->getTokenGatewayId() && $client->hasAutoBillConfigurableInvoices()) + @if($entityType == ENTITY_INVOICE && $client->hasRecurringInvoices())
- {!! Button::info(trans("texts.manage_auto_bill"))->asLinkTo(URL::to('/client/invoices/recurring'))->appendIcon(Icon::create('repeat')) !!} + {!! Button::primary(trans("texts.recurring_invoices"))->asLinkTo(URL::to('/client/invoices/recurring')) !!}
@endif

{{ $title }}

@@ -49,7 +49,7 @@ ->render('datatable') !!} - @if($entityType == ENTITY_RECURRING_INVOICE) + @if ($entityType == ENTITY_RECURRING_INVOICE) {!! Former::open(URL::to('/client/invoices/auto_bill'))->id('auto_bill_form') !!} From d8076f08fc060e0b3c8fa04d27113390e7f3efa4 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 3 Apr 2017 22:07:18 +0300 Subject: [PATCH 07/28] Don't warn on exit in reports --- resources/views/reports/chart_builder.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/reports/chart_builder.blade.php b/resources/views/reports/chart_builder.blade.php index f519a9bb6740..5984af31c11c 100644 --- a/resources/views/reports/chart_builder.blade.php +++ b/resources/views/reports/chart_builder.blade.php @@ -75,7 +75,7 @@ - {!! Former::open()->rules(['start_date' => 'required', 'end_date' => 'required'])->addClass('warn-on-exit') !!} + {!! Former::open()->rules(['start_date' => 'required', 'end_date' => 'required']) !!}
{!! Former::text('action') !!} From 23e9dd6a97b41880333403175a9552a62328afc6 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 3 Apr 2017 22:48:47 +0300 Subject: [PATCH 08/28] Remove blank contacts --- resources/views/invoices/knockout.blade.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/resources/views/invoices/knockout.blade.php b/resources/views/invoices/knockout.blade.php index a57cc8b882e3..39c8c713a23e 100644 --- a/resources/views/invoices/knockout.blade.php +++ b/resources/views/invoices/knockout.blade.php @@ -35,6 +35,16 @@ function ViewModel(data) { @endif } + self.clearBlankContacts = function() { + var client = self.invoice().client(); + var contacts = client.contacts(); + $(contacts).each(function(index, contact) { + if (index > 0 && contact.isBlank()) { + client.contacts.remove(contact); + } + }); + } + self.invoice_taxes = ko.observable({{ Auth::user()->account->invoice_taxes ? 'true' : 'false' }}); self.invoice_item_taxes = ko.observable({{ Auth::user()->account->invoice_item_taxes ? 'true' : 'false' }}); self.show_item_taxes = ko.observable({{ Auth::user()->account->show_item_taxes ? 'true' : 'false' }}); @@ -115,6 +125,8 @@ function ViewModel(data) { } model.setDueDate(); + model.clearBlankContacts(); + setComboboxValue($('.client_select'), -1, name); var client = $.parseJSON(ko.toJSON(self.invoice().client())); @@ -638,6 +650,10 @@ function ContactModel(data) { ko.mapping.fromJS(data, {}, this); } + self.isBlank = ko.computed(function() { + return ! self.first_name() && ! self.last_name() && ! self.email() && ! self.phone(); + }); + self.displayName = ko.computed(function() { var str = ''; if (self.first_name() || self.last_name()) { From 9dd2ab43c22549282425dac5f90e19f77345d1c1 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 4 Apr 2017 12:54:38 +0300 Subject: [PATCH 09/28] Working on speech rec --- app/Constants.php | 3 + app/Http/Controllers/BotController.php | 10 +- app/Http/routes.php | 1 + app/Ninja/Intents/BaseIntent.php | 11 +- .../Intents/WebApp/CreateInvoiceIntent.php | 28 +++ resources/views/header.blade.php | 13 +- .../partials/speech_recognition.blade.php | 215 ++++++++++++++++++ 7 files changed, 272 insertions(+), 9 deletions(-) create mode 100644 app/Ninja/Intents/WebApp/CreateInvoiceIntent.php create mode 100644 resources/views/partials/speech_recognition.blade.php diff --git a/app/Constants.php b/app/Constants.php index 75634ad9da1e..c7d5ccd45a7b 100644 --- a/app/Constants.php +++ b/app/Constants.php @@ -325,6 +325,9 @@ if (! defined('APP_NAME')) { define('SKYPE_API_URL', 'https://apis.skype.com/v3'); define('MSBOT_STATE_URL', 'https://state.botframework.com/v3'); + define('BOT_PLATFORM_WEB_APP', 'WebApp'); + define('BOT_PLATFORM_SKYPE', 'Skype'); + define('BLANK_IMAGE', 'data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='); define('COUNT_FREE_DESIGNS', 4); diff --git a/app/Http/Controllers/BotController.php b/app/Http/Controllers/BotController.php index 02267924f23a..365e48c07537 100644 --- a/app/Http/Controllers/BotController.php +++ b/app/Http/Controllers/BotController.php @@ -80,7 +80,7 @@ class BotController extends Controller $user->account->loadLocalizationSettings(); $data = $this->parseMessage($text); - $intent = BaseIntent::createIntent($state, $data); + $intent = BaseIntent::createIntent($platform, $state, $data); $response = $intent->process(); $state = $intent->getState(); } @@ -97,6 +97,14 @@ class BotController extends Controller return RESULT_SUCCESS; } + public function handleCommand() + { + $data = $this->parseMessage(request()->command); + $intent = BaseIntent::createIntent(BOT_PLATFORM_WEB_APP, false, $data); + + return $intent->process(); + } + private function authenticate($input) { $token = isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] : false; diff --git a/app/Http/routes.php b/app/Http/routes.php index 2a4258f8c43a..614954aa3d02 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -127,6 +127,7 @@ Route::group(['middleware' => 'auth:user'], function () { Route::get('check_invoice_number/{invoice_id?}', 'InvoiceController@checkInvoiceNumber'); Route::post('save_sidebar_state', 'UserController@saveSidebarState'); Route::post('contact_us', 'HomeController@contactUs'); + Route::post('handle_command', 'BotController@handleCommand'); Route::get('settings/user_details', 'AccountController@showUserDetails'); Route::post('settings/user_details', 'AccountController@saveUserDetails'); diff --git a/app/Ninja/Intents/BaseIntent.php b/app/Ninja/Intents/BaseIntent.php index 012a75d46eb1..766810c0180c 100644 --- a/app/Ninja/Intents/BaseIntent.php +++ b/app/Ninja/Intents/BaseIntent.php @@ -32,7 +32,7 @@ class BaseIntent //var_dump($state); } - public static function createIntent($state, $data) + public static function createIntent($platform, $state, $data) { if (! count($data->intents)) { throw new Exception(trans('texts.intent_not_found')); @@ -48,13 +48,18 @@ class BaseIntent } } - if (! $entityType) { + if ($state && ! $entityType) { $entityType = $state->current->entityType; } $entityType = ucwords(strtolower($entityType)); $intent = str_replace('Entity', $entityType, $intent); - $className = "App\\Ninja\\Intents\\{$intent}Intent"; + + if ($platform == BOT_PLATFORM_WEB_APP) { + $className = "App\\Ninja\\Intents\\WebApp\\{$intent}Intent"; + } else { + $className = "App\\Ninja\\Intents\\{$intent}Intent"; + } //echo "Intent: $intent

"; diff --git a/app/Ninja/Intents/WebApp/CreateInvoiceIntent.php b/app/Ninja/Intents/WebApp/CreateInvoiceIntent.php new file mode 100644 index 000000000000..d2962fd18499 --- /dev/null +++ b/app/Ninja/Intents/WebApp/CreateInvoiceIntent.php @@ -0,0 +1,28 @@ +requestClient(); + $invoiceItems = $this->requestInvoiceItems(); + + if (! $client) { + throw new Exception(trans('texts.client_not_found')); + } + + $data = array_merge($this->requestFields(), [ + 'client_id' => $client->public_id, + 'invoice_items' => $invoiceItems, + ]); + + //var_dump($data); + dd($data); + } +} diff --git a/resources/views/header.blade.php b/resources/views/header.blade.php index df27b0ab4b1e..ff4ddbe8e2b2 100644 --- a/resources/views/header.blade.php +++ b/resources/views/header.blade.php @@ -309,12 +309,15 @@

-