From 815c3991a60c9cb34d656f1fd6c0ffb907afc456 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 10 Aug 2016 15:57:34 +0300 Subject: [PATCH] Working on the bot --- app/Http/Controllers/BotController.php | 29 ++++--- app/Libraries/Skype/ButtonCard.php | 11 +++ app/Libraries/Skype/HeroCard.php | 26 +++++++ app/Libraries/Skype/InvoiceCard.php | 75 +++++++++++++++++++ app/Libraries/Skype/InvoiceItemCard.php | 12 +++ app/Libraries/Skype/SkypeResponse.php | 28 +++++++ app/Ninja/Intents/BaseIntent.php | 44 ++++++++++- app/Ninja/Intents/CreateInvoiceIntent.php | 14 ++-- .../Intents/CreateInvoiceItemsIntent.php | 39 ---------- app/Ninja/Intents/DownloadInvoiceIntent.php | 7 +- app/Ninja/Intents/EmailInvoiceIntent.php | 5 +- app/Ninja/Intents/InvoiceIntent.php | 27 +++++++ app/Ninja/Intents/ListProductsIntent.php | 19 +++++ app/Ninja/Intents/ProductIntent.php | 15 ++++ app/Ninja/Intents/UpdateInvoiceIntent.php | 43 +++++++++-- app/Ninja/Presenters/InvoicePresenter.php | 34 +++++++++ resources/views/bots/skype/invoice.blade.php | 28 ++++--- resources/views/bots/skype/list.blade.php | 29 +++++++ 18 files changed, 397 insertions(+), 88 deletions(-) create mode 100644 app/Libraries/Skype/ButtonCard.php create mode 100644 app/Libraries/Skype/HeroCard.php create mode 100644 app/Libraries/Skype/InvoiceCard.php create mode 100644 app/Libraries/Skype/InvoiceItemCard.php create mode 100644 app/Libraries/Skype/SkypeResponse.php delete mode 100644 app/Ninja/Intents/CreateInvoiceItemsIntent.php create mode 100644 app/Ninja/Intents/ListProductsIntent.php create mode 100644 app/Ninja/Intents/ProductIntent.php create mode 100644 resources/views/bots/skype/list.blade.php diff --git a/app/Http/Controllers/BotController.php b/app/Http/Controllers/BotController.php index b44c0065d1cf..fbcde8259859 100644 --- a/app/Http/Controllers/BotController.php +++ b/app/Http/Controllers/BotController.php @@ -2,8 +2,9 @@ namespace App\Http\Controllers; -use App\Libraries\CurlUtils; use Exception; +use App\Libraries\Skype\SkypeResponse; +use App\Libraries\CurlUtils; use App\Ninja\Intents\BaseIntent; class BotController extends Controller @@ -11,13 +12,15 @@ class BotController extends Controller public function handleMessage($platform) { $to = '29:1C-OsU7OWBEDOYJhQUsDkYHmycOwOq9QOg5FVTwRX9ts'; - //$message = 'create a new invoice for Mr. Gino '; - $message = 'add 1 item'; - //$message = 'set the cost to 20'; + //$message = 'new invoice for john for 2 items due tomorrow'; + //$message = 'create a new invoice for john smith for 2 tickets, set the invoice date to today, the due date to tomorrow, the deposit to 5 and the discount set to 10 percent'; + //$message = 'create a new invoice for john smith with a due date of September 7th'; + //$message = 'create a new invoice for john'; + $message = 'add 2 tickets and set the due date to yesterday'; + //$message = 'set the po number to 0004'; + //$message = 'set the quantity to 20'; //$message = 'send the invoice'; - - //$message = view('bots.skype.message', ['message' => $message])->render(); - //return $this->sendResponse($to, $message); + //$message = 'show me my products'; echo "Message: $message

"; $token = $this->authenticate(); @@ -33,9 +36,7 @@ class BotController extends Controller $this->saveState($token, $state); /* } catch (Exception $exception) { - $message = view('bots.skype.message', [ - 'message' => $exception->getMessage() - ])->render(); + SkypeResponse::message($exception->getMessage()); } */ @@ -86,7 +87,6 @@ class BotController extends Controller private function saveState($token, $data) { $url = sprintf('%s/botstate/skype/conversations/%s', MSBOT_STATE_URL, '29:1C-OsU7OWBEDOYJhQUsDkYHmycOwOq9QOg5FVTwRX9ts'); - var_dump($url); $headers = [ 'Authorization: Bearer ' . $token, @@ -94,11 +94,8 @@ class BotController extends Controller ]; $data = '{ eTag: "*", data: "' . addslashes(json_encode($data)) . '" }'; - //$data = '{ eTag: "*", data: "" }'; - var_dump($data); - $response = CurlUtils::post($url, $data, $headers); - var_dump($response); + CurlUtils::post($url, $data, $headers); } private function sendResponse($token, $to, $message) @@ -111,7 +108,7 @@ class BotController extends Controller $response = CurlUtils::post($url, $message, $headers); - var_dump($message); + echo "

" . htmlentities(json_encode(json_decode($message), JSON_PRETTY_PRINT)) . "
"; var_dump($response); } diff --git a/app/Libraries/Skype/ButtonCard.php b/app/Libraries/Skype/ButtonCard.php new file mode 100644 index 000000000000..c81060ccdfd3 --- /dev/null +++ b/app/Libraries/Skype/ButtonCard.php @@ -0,0 +1,11 @@ +type = $type; + $this->title = $title; + $this->value = $value; + } +} diff --git a/app/Libraries/Skype/HeroCard.php b/app/Libraries/Skype/HeroCard.php new file mode 100644 index 000000000000..3b3112c8a5be --- /dev/null +++ b/app/Libraries/Skype/HeroCard.php @@ -0,0 +1,26 @@ +contentType = 'application/vnd.microsoft.card.hero'; + $this->content = new stdClass; + $this->content->buttons = []; + } + + public function setTitle($title) + { + $this->content->title = $title; + } + + public function setSubitle($subtitle) + { + $this->content->subtitle = $subtitle; + } + + public function addButton($button) + { + $this->content->buttons[] = $button; + } +} diff --git a/app/Libraries/Skype/InvoiceCard.php b/app/Libraries/Skype/InvoiceCard.php new file mode 100644 index 000000000000..24c928d32a7e --- /dev/null +++ b/app/Libraries/Skype/InvoiceCard.php @@ -0,0 +1,75 @@ +contentType = 'application/vnd.microsoft.card.receipt'; + $this->content = new stdClass; + $this->content->facts = []; + $this->content->items = []; + $this->content->buttons = []; + + $this->setTitle('test'); + + $this->setTitle(trans('texts.invoice_for_client', [ + 'invoice' => link_to($invoice->getRoute(), $invoice->invoice_number), + 'client' => link_to($invoice->client->getRoute(), $invoice->client->getDisplayName()) + ])); + + $this->addFact(trans('texts.email'), HTML::mailto($invoice->client->contacts[0]->email)->toHtml()); + + if ($invoice->due_date) { + $this->addFact($invoice->present()->dueDateLabel, $invoice->present()->due_date); + } + + if ($invoice->po_number) { + $this->addFact(trans('texts.po_number'), $invoice->po_number); + } + + if ($invoice->discount) { + $this->addFact(trans('texts.discount'), $invoice->present()->discount); + } + + foreach ($invoice->invoice_items as $item) { + $this->addItem($item, $invoice->account); + } + + $this->setTotal($invoice->present()->requestedAmount); + + $this->addButton('imBack', trans('texts.send_email'), 'send_email'); + $this->addButton('imBack', trans('texts.download_pdf'), 'download_pdf'); + } + + public function setTitle($title) + { + $this->content->title = $title; + } + + public function setTotal($value) + { + $this->content->total = $value; + } + + public function addFact($key, $value) + { + $fact = new stdClass; + $fact->key = $key; + $fact->value = $value; + + $this->content->facts[] = $fact; + } + + public function addItem($item, $account) + { + $this->content->items[] = new InvoiceItemCard($item, $account); + } + + public function addButton($type, $title, $value) + { + $this->content->buttons[] = new ButtonCard($type, $title, $value); + } +} diff --git a/app/Libraries/Skype/InvoiceItemCard.php b/app/Libraries/Skype/InvoiceItemCard.php new file mode 100644 index 000000000000..17ac91a9129b --- /dev/null +++ b/app/Libraries/Skype/InvoiceItemCard.php @@ -0,0 +1,12 @@ +title = $invoiceItem->product_key; + $this->subtitle = $invoiceItem->notes; + $this->quantity = $invoiceItem->qty; + $this->price = $account->formatMoney($invoiceItem->cost); + } +} diff --git a/app/Libraries/Skype/SkypeResponse.php b/app/Libraries/Skype/SkypeResponse.php new file mode 100644 index 000000000000..094aec68093f --- /dev/null +++ b/app/Libraries/Skype/SkypeResponse.php @@ -0,0 +1,28 @@ +type = $type; + $this->attachments = []; + } + + public static function message($message) + { + $instance = new self('message/text'); + $instance->setText($message); + + return json_encode($instance); + } + + public function setText($text) + { + $this->text = $text; + } + + public function addAttachment($attachment) + { + $this->attachments[] = $attachment; + } +} diff --git a/app/Ninja/Intents/BaseIntent.php b/app/Ninja/Intents/BaseIntent.php index 66c60b69d6a3..c3e384340450 100644 --- a/app/Ninja/Intents/BaseIntent.php +++ b/app/Ninja/Intents/BaseIntent.php @@ -3,6 +3,7 @@ use stdClass; use Exception; use App\Libraries\CurlUtils; +use App\Libraries\Skype\SkypeResponse; class BaseIntent { @@ -26,7 +27,7 @@ class BaseIntent $this->state = $state; $this->data = $data; - var_dump($state); + //var_dump($state); } public static function createIntent($state, $data) @@ -38,9 +39,23 @@ class BaseIntent $intent = $data->intents[0]->intent; $entityType = false; - echo "Intent: $intent

"; + foreach ($data->entities as $entity) { + if ($entity->type === 'EntityType') { + $entityType = $entity->entity; + break; + } + } + + if ( ! $entityType) { + $entityType = $state->current->entityType; + } + + $entityType = ucwords(strtolower($entityType)); + $intent = str_replace('Entity', $entityType, $intent); $className = "App\\Ninja\\Intents\\{$intent}Intent"; + echo "Intent: $intent

"; + if ( ! class_exists($className)) { throw new Exception(trans('texts.intent_not_supported')); } @@ -109,11 +124,10 @@ class BaseIntent protected function parseClient() { $clientRepo = app('App\Ninja\Repositories\ClientRepository'); - $client = false; foreach ($this->data->entities as $param) { - if ($param->type == 'Client') { + if ($param->type == 'Name') { $client = $clientRepo->findPhonetically($param->entity); } } @@ -125,6 +139,10 @@ class BaseIntent { $data = []; + if ( ! isset($this->data->compositeEntities)) { + return []; + } + foreach ($this->data->compositeEntities as $compositeEntity) { if ($compositeEntity->parentType != 'FieldValuePair') { continue; @@ -178,4 +196,22 @@ class BaseIntent return $value; } + protected function createResponse($type, $content) + { + $response = new SkypeResponse($type); + + if (is_string($content)) { + $response->setText($content); + } else { + if ( ! is_array($content)) { + $content = [$content]; + } + + foreach ($content as $item) { + $response->addAttachment($item); + } + } + + return json_encode($response); + } } diff --git a/app/Ninja/Intents/CreateInvoiceIntent.php b/app/Ninja/Intents/CreateInvoiceIntent.php index 24d5dc5bac0d..a9daa84b6b2d 100644 --- a/app/Ninja/Intents/CreateInvoiceIntent.php +++ b/app/Ninja/Intents/CreateInvoiceIntent.php @@ -11,17 +11,16 @@ class CreateInvoiceIntent extends InvoiceIntent $invoiceItems = $this->parseInvoiceItems(); if ( ! $client) { - return view('bots.skype.message', [ - 'message' => trans('texts.client_not_found') - ])->render(); + throw new Exception(trans('texts.client_not_found')); } - $data = [ + $data = array_merge($this->parseFields(), [ 'client_id' => $client->id, 'invoice_items' => $invoiceItems, - ]; + ]); var_dump($data); + $valid = EntityModel::validate($data, ENTITY_INVOICE); if ($valid !== true) { @@ -29,6 +28,7 @@ class CreateInvoiceIntent extends InvoiceIntent } $invoice = $this->invoiceRepo->save($data); + $invoiceItemIds = array_map(function($item) { return $item['public_id']; }, $invoice->invoice_items->toArray()); @@ -38,8 +38,6 @@ class CreateInvoiceIntent extends InvoiceIntent $this->setEntities(ENTITY_INVOICE, $invoice->public_id); $this->setEntities(ENTITY_INVOICE_ITEM, $invoiceItemIds); - return view('bots.skype.invoice', [ - 'invoice' => $invoice - ])->render(); + return $this->createResponse('message/card.receipt', $invoice->present()->skypeBot); } } diff --git a/app/Ninja/Intents/CreateInvoiceItemsIntent.php b/app/Ninja/Intents/CreateInvoiceItemsIntent.php deleted file mode 100644 index eeb61703de12..000000000000 --- a/app/Ninja/Intents/CreateInvoiceItemsIntent.php +++ /dev/null @@ -1,39 +0,0 @@ -invoice(); - $invoiceItems = $this->parseInvoiceItems(); - - $data = [ - 'public_id' => $invoice->public_id, - 'invoice_items' => array_merge($invoice->invoice_items->toArray(), $invoiceItems), - ]; - - var_dump($data); - $valid = EntityModel::validate($data, ENTITY_INVOICE, $invoice); - - if ($valid !== true) { - throw new Exception($valid); - } - - $invoice = $this->invoiceRepo->save($data, $invoice); - - $invoiceItems = array_slice($invoice->invoice_items->toArray(), count($invoiceItems) * -1); - $invoiceItemIds = array_map(function($item) { - return $item['public_id']; - }, $invoiceItems); - - $this->setEntities(ENTITY_INVOICE_ITEM, $invoiceItemIds); - - return view('bots.skype.invoice', [ - 'invoice' => $invoice->load('invoice_items') - ])->render(); - } -} diff --git a/app/Ninja/Intents/DownloadInvoiceIntent.php b/app/Ninja/Intents/DownloadInvoiceIntent.php index 7ccd14e71ef1..400519155ff4 100644 --- a/app/Ninja/Intents/DownloadInvoiceIntent.php +++ b/app/Ninja/Intents/DownloadInvoiceIntent.php @@ -1,8 +1,9 @@ getEntityType()) . ' ' . $invoice->invoice_number; $message = link_to('/download/' . $invoice->invitations[0]->invitation_key, $message); - return view('bots.skype.message', [ - 'message' => $message - ])->render(); + return SkypeResponse::message($message); } } diff --git a/app/Ninja/Intents/EmailInvoiceIntent.php b/app/Ninja/Intents/EmailInvoiceIntent.php index 1c0d72843736..46e07ac0ad2a 100644 --- a/app/Ninja/Intents/EmailInvoiceIntent.php +++ b/app/Ninja/Intents/EmailInvoiceIntent.php @@ -4,6 +4,7 @@ use Auth; use Exception; use App\Models\EntityModel; use App\Models\Invoice; +use App\Libraries\Skype\SkypeResponse; class EmailInvoiceIntent extends InvoiceIntent { @@ -20,8 +21,6 @@ class EmailInvoiceIntent extends InvoiceIntent $message = trans('texts.bot_emailed_' . $invoice->getEntityType()); - return view('bots.skype.message', [ - 'message' => $message - ])->render(); + return SkypeResponse::message($message); } } diff --git a/app/Ninja/Intents/InvoiceIntent.php b/app/Ninja/Intents/InvoiceIntent.php index 4e6c9a7f6212..f8e69af63853 100644 --- a/app/Ninja/Intents/InvoiceIntent.php +++ b/app/Ninja/Intents/InvoiceIntent.php @@ -6,6 +6,9 @@ use App\Models\Invoice; class InvoiceIntent extends BaseIntent { + private $_invoice; + private $_invoiceItem; + public function __construct($state, $data) { $this->invoiceRepo = app('App\Ninja\Repositories\InvoiceRepository'); @@ -15,6 +18,10 @@ class InvoiceIntent extends BaseIntent protected function invoice() { + if ($this->_invoice) { + return $this->_invoice; + } + $invoiceId = $this->entity(ENTITY_INVOICE); if ( ! $invoiceId) { @@ -34,6 +41,19 @@ class InvoiceIntent extends BaseIntent return $invoice; } + protected function invoiceItem() + { + if ($this->_invoiceItem) { + return $this->_invoiceItem; + } + + $invoiceItemId = $this->entity(ENTITY_INVOICE_ITEM); + + if ( ! $invoiceItemId) { + $invoice = $this->invoice(); + } + } + protected function parseInvoiceItems() { $productRepo = app('App\Ninja\Repositories\ProductRepository'); @@ -58,6 +78,7 @@ class InvoiceIntent extends BaseIntent $item = $product->toArray(); $item['qty'] = $qty; + $invoiceItems[] = $item; } } @@ -65,4 +86,10 @@ class InvoiceIntent extends BaseIntent return $invoiceItems; } + protected function parseFields() + { + $data = parent::parseFields(); + + return $data; + } } diff --git a/app/Ninja/Intents/ListProductsIntent.php b/app/Ninja/Intents/ListProductsIntent.php new file mode 100644 index 000000000000..93baf14fc10c --- /dev/null +++ b/app/Ninja/Intents/ListProductsIntent.php @@ -0,0 +1,19 @@ +get(); + + return view('bots.skype.list', [ + 'items' => $products + ])->render(); + + } +} diff --git a/app/Ninja/Intents/ProductIntent.php b/app/Ninja/Intents/ProductIntent.php new file mode 100644 index 000000000000..bf8b72405b6a --- /dev/null +++ b/app/Ninja/Intents/ProductIntent.php @@ -0,0 +1,15 @@ +productRepo = app('App\Ninja\Repositories\ProductRepository'); + + parent::__construct($state, $data); + } +} diff --git a/app/Ninja/Intents/UpdateInvoiceIntent.php b/app/Ninja/Intents/UpdateInvoiceIntent.php index b0611475907f..72be9f6bc5e0 100644 --- a/app/Ninja/Intents/UpdateInvoiceIntent.php +++ b/app/Ninja/Intents/UpdateInvoiceIntent.php @@ -9,13 +9,46 @@ class UpdateInvoiceIntent extends InvoiceIntent public function process() { $invoice = $this->invoice(); + $invoiceItems = $this->parseInvoiceItems(); - $data = $this->parseFields(); + $data = array_merge($this->parseFields(), [ + 'public_id' => $invoice->public_id, + 'invoice_items' => array_merge($invoice->invoice_items->toArray(), $invoiceItems), + ]); - dd($data); + // map the cost and qty fields to the invoice items + if (isset($data['cost']) || isset($data['quantity'])) { + foreach ($data['invoice_items'] as $key => $item) { + // if it's new or we recently created it + if (empty($item['public_id']) || in_array($item['public_id'], $this->entities(ENTITY_INVOICE_ITEM))) { + $data['invoice_items'][$key]['cost'] = isset($data['cost']) ? $data['cost'] : $item['cost']; + $data['invoice_items'][$key]['qty'] = isset($data['quantity']) ? $data['quantity'] : $item['qty']; + } + } + } - return view('bots.skype.invoice', [ - 'invoice' => $invoice - ])->render(); + var_dump($data); + + $valid = EntityModel::validate($data, ENTITY_INVOICE, $invoice); + + if ($valid !== true) { + throw new Exception($valid); + } + + $invoice = $this->invoiceRepo->save($data, $invoice); + + $invoiceItems = array_slice($invoice->invoice_items->toArray(), count($invoiceItems) * -1); + $invoiceItemIds = array_map(function($item) { + return $item['public_id']; + }, $invoiceItems); + + $this->setEntities(ENTITY_INVOICE_ITEM, $invoiceItemIds); + + $response = $invoice + ->load('invoice_items') + ->present() + ->skypeBot; + + return $this->createResponse('message/card.receipt', $response); } } diff --git a/app/Ninja/Presenters/InvoicePresenter.php b/app/Ninja/Presenters/InvoicePresenter.php index 07b9a68ab20c..9f9870cb1fe0 100644 --- a/app/Ninja/Presenters/InvoicePresenter.php +++ b/app/Ninja/Presenters/InvoicePresenter.php @@ -1,6 +1,7 @@ formatMoney($invoice->amount, $invoice->client); } + public function requestedAmount() + { + $invoice = $this->entity; + $account = $invoice->account; + + return $account->formatMoney($invoice->getRequestedAmount(), $invoice->client); + } + public function balanceDueLabel() { if ($this->entity->partial > 0) { @@ -33,6 +42,26 @@ class InvoicePresenter extends EntityPresenter { } } + public function dueDateLabel() + { + if ($this->entity->isType(INVOICE_TYPE_STANDARD)) { + return trans('texts.due_date'); + } else { + return trans('texts.valid_until'); + } + } + + public function discount() + { + $invoice = $this->entity; + + if ($invoice->is_amount_discount) { + return $invoice->account->formatMoney($invoice->discount); + } else { + return $invoice->discount . '%'; + } + } + // https://schema.org/PaymentStatusType public function paymentStatus() { @@ -107,4 +136,9 @@ class InvoicePresenter extends EntityPresenter { return trans('texts.auto_bill_notification', $data); } + + public function skypeBot() + { + return new InvoiceCard($this->entity); + } } diff --git a/resources/views/bots/skype/invoice.blade.php b/resources/views/bots/skype/invoice.blade.php index 6b5856ac2b51..d4ab26f20a96 100644 --- a/resources/views/bots/skype/invoice.blade.php +++ b/resources/views/bots/skype/invoice.blade.php @@ -12,15 +12,25 @@ { "key": "{{ trans('texts.email') }}:", "value": "{!! addslashes(HTML::mailto($invoice->client->contacts[0]->email)->toHtml()) !!}" - }, - { - "key": "{{ trans('texts.paid_to_date') }}:", - "value": "{{ $invoice->client->present()->paid_to_date }}" - }, - { - "key": "{{ trans('texts.balance') }}:", - "value": "{{ $invoice->client->present()->balance }}" } + @if ($invoice->due_date) + , { + "key": "{{ $invoice->present()->dueDateLabel }}:", + "value": "{{ $invoice->present()->due_date }}" + } + @endif + @if ($invoice->po_number) + , { + "key": "{{ trans('texts.po_number') }}:", + "value": "{{ $invoice->po_number }}" + } + @endif + @if ($invoice->discount) + , { + "key": "{{ trans('texts.discount') }}:", + "value": "{{ $invoice->present()->discount }}" + } + @endif ], "items":[ @foreach ($invoice->invoice_items as $item) @@ -38,7 +48,7 @@ @if (false) "tax":"0.00", @endif - "total":"{{ $invoice->present()->amount }}", + "total":"{{ $invoice->present()->requestedAmount }}", "buttons":[ { "type":"imBack", diff --git a/resources/views/bots/skype/list.blade.php b/resources/views/bots/skype/list.blade.php new file mode 100644 index 000000000000..989e898a4a46 --- /dev/null +++ b/resources/views/bots/skype/list.blade.php @@ -0,0 +1,29 @@ +{ + "type":"message/card.carousel", + "attachments":[ + @foreach ($items as $item) + @if ($items[0] != $item) + , + @endif + { + "contentType": "application/vnd.microsoft.card.hero", + "content": { + "title": "{{ $item['title'] }}", + "subtitle": "{{ $item['subtitle'] }}", + "buttons": [ + @foreach($item['buttons'] as $button) + @if ($items['buttons'][0] != $button) + , + @endif + { + "type": "{{ $button['type'] }}", + "title": "{{ $button['title'] }}", + "value": "https://en.wikipedia.org/wiki/{cardContent.Key}" + } + @endforeach + ] + } + } + @endforeach + ] +}