From 9dececf08b1343458a6a22724e456bfdc8ecda88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 7 Sep 2021 17:56:35 +0200 Subject: [PATCH 01/14] Scaffold StatementController & update client.php --- .../ClientPortal/StatementController.php | 20 +++++++++++++++++++ routes/client.php | 2 ++ 2 files changed, 22 insertions(+) create mode 100644 app/Http/Controllers/ClientPortal/StatementController.php diff --git a/app/Http/Controllers/ClientPortal/StatementController.php b/app/Http/Controllers/ClientPortal/StatementController.php new file mode 100644 index 000000000000..2e7961ad3119 --- /dev/null +++ b/app/Http/Controllers/ClientPortal/StatementController.php @@ -0,0 +1,20 @@ + ['auth:contact', 'locale', 'check_client_existence Route::resource('tasks', 'ClientPortal\TaskController')->only(['index']); + Route::resource('statement', 'ClientPortal\StatementController'); + Route::post('upload', 'ClientPortal\UploadController')->name('upload.store'); Route::get('logout', 'Auth\ContactLoginController@logout')->name('logout'); From ce554f5fcdbb6b178344aa31d6588e828979f733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 7 Sep 2021 18:00:22 +0200 Subject: [PATCH 02/14] Add `Statement` to sidebar --- app/Http/Controllers/ClientPortal/StatementController.php | 5 ++++- app/Http/ViewComposers/PortalComposer.php | 2 ++ resources/views/portal/ninja2020/statement/index.blade.php | 7 +++++++ routes/client.php | 2 +- 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 resources/views/portal/ninja2020/statement/index.blade.php diff --git a/app/Http/Controllers/ClientPortal/StatementController.php b/app/Http/Controllers/ClientPortal/StatementController.php index 2e7961ad3119..d5d95aa885b2 100644 --- a/app/Http/Controllers/ClientPortal/StatementController.php +++ b/app/Http/Controllers/ClientPortal/StatementController.php @@ -16,5 +16,8 @@ use App\Http\Controllers\Controller; class StatementController extends Controller { - // + public function index() + { + return render('statement.index'); + } } diff --git a/app/Http/ViewComposers/PortalComposer.php b/app/Http/ViewComposers/PortalComposer.php index e5be85883ea4..3f589c759ef1 100644 --- a/app/Http/ViewComposers/PortalComposer.php +++ b/app/Http/ViewComposers/PortalComposer.php @@ -119,6 +119,8 @@ class PortalComposer $data[] = ['title' => ctrans('texts.tasks'), 'url' => 'client.tasks.index', 'icon' => 'clock']; } + $data[] = ['title' => ctrans('texts.statement'), 'url' => 'client.statement', 'icon' => 'activity']; + return $data; } } diff --git a/resources/views/portal/ninja2020/statement/index.blade.php b/resources/views/portal/ninja2020/statement/index.blade.php new file mode 100644 index 000000000000..6da02053909e --- /dev/null +++ b/resources/views/portal/ninja2020/statement/index.blade.php @@ -0,0 +1,7 @@ +@extends('portal.ninja2020.layout.app') + +@section('meta_title', ctrans('texts.statement')) + +@section('body') + +@endsection diff --git a/routes/client.php b/routes/client.php index c0509db16332..5d461dfe30c7 100644 --- a/routes/client.php +++ b/routes/client.php @@ -80,7 +80,7 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence Route::resource('tasks', 'ClientPortal\TaskController')->only(['index']); - Route::resource('statement', 'ClientPortal\StatementController'); + Route::get('statement', 'ClientPortal\StatementController@index')->name('statement'); Route::post('upload', 'ClientPortal\UploadController')->name('upload.store'); Route::get('logout', 'Auth\ContactLoginController@logout')->name('logout'); From 139ede5db098d2a554bd4d9fa568e32eca53a664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 14 Sep 2021 13:55:24 +0200 Subject: [PATCH 03/14] Generate statemets using service --- app/Services/Client/ClientService.php | 10 + app/Services/Client/Statement.php | 289 ++++++++++++++++++++++++++ 2 files changed, 299 insertions(+) create mode 100644 app/Services/Client/Statement.php diff --git a/app/Services/Client/ClientService.php b/app/Services/Client/ClientService.php index 840a3b7a9754..1fa8ee4b7324 100644 --- a/app/Services/Client/ClientService.php +++ b/app/Services/Client/ClientService.php @@ -85,6 +85,16 @@ class ClientService return $this; } + /** + * Generate the client statement. + * + * @param array $options + */ + public function statement(array $options = []) + { + return (new Statement($this->client, $options))->run(); + } + public function save() :Client { $this->client->save(); diff --git a/app/Services/Client/Statement.php b/app/Services/Client/Statement.php new file mode 100644 index 000000000000..822984e2a0e3 --- /dev/null +++ b/app/Services/Client/Statement.php @@ -0,0 +1,289 @@ +client = $client; + + $this->options = $options; + } + + public function run(): ?string + { + $this->setupEntity()->setupOptions(); + + $html = new HtmlEngine($this->getInvitation()); + + if ($this->getDesign()->is_custom) { + $this->options['custom_partials'] = \json_decode(\json_encode($this->getDesign()->design), true); + + $template = new PdfMakerDesign(\App\Services\PdfMaker\Design::CUSTOM, $this->options); + } else { + $template = new PdfMakerDesign(strtolower($this->getDesign()->name), $this->options); + } + + $variables = $html->generateLabelsAndValues(); + + $state = [ + 'template' => $template->elements([ + 'client' => $this->entity->client, + 'entity' => $this->entity, + 'pdf_variables' => (array)$this->entity->company->settings->pdf_variables, + '$product' => $this->getDesign()->design->product, + 'variables' => $variables, + 'invoices' => $this->getInvoices(), + 'payments' => $this->getPayments(), + 'aging' => $this->getAging(), + ], \App\Services\PdfMaker\Design::STATEMENT), + 'variables' => $variables, + 'options' => [], + 'process_markdown' => $this->entity->client->company->markdown_enabled, + ]; + + $maker = new PdfMaker($state); + + $maker + ->design($template) + ->build(); + + $pdf = null; + + try { + if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { + $pdf = (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true)); + } elseif (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { + $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); + } else { + $pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true)); + } + } catch (\Exception $e) { + nlog(print_r($e->getMessage(), 1)); + } + + return $pdf; + } + + /** + * Setup correct entity instance. + * + * @return Statement + */ + protected function setupEntity(): self + { + if (count($this->getInvoices()) >= 1) { + $this->entity = $this->getInvoices()->first(); + } + + if (count($this->getPayments()) >= 1) { + $this->entity = $this->getPayments()->first(); + } + + return $this; + } + + /** + * Setup & prepare options. + * + * @return Statement + */ + protected function setupOptions(): self + { + if (\array_key_exists('start_date', $this->options)) { + $this->options['start_date'] = now()->startOfYear()->format('Y-m-d'); + } + + if (\array_key_exists('end_date', $this->options)) { + $this->options['end_date'] = now()->format('Y-m-d'); + } + + if (\array_key_exists('show_payments_table', $this->options)) { + $this->options['show_payments_table'] = false; + } + + if (\array_key_exists('show_aging_table', $this->options)) { + $this->options['show_aging_table'] = false; + } + + return $this; + } + + /** + * The collection of invoices for the statement. + * + * @return Invoice[]|\Illuminate\Database\Eloquent\Collection + */ + protected function getInvoices(): Collection + { + return Invoice::where('company_id', $this->client->company->id) + ->where('client_id', $this->client->id) + ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID]) + ->whereBetween('date', [$this->options['start_date'], $this->options['end_date']]) + ->get(); + } + + /** + * The collection of payments for the statement. + * + * @return Payment[]|\Illuminate\Database\Eloquent\Collection + */ + protected function getPayments(): Collection + { + return Payment::where('company_id', $this->client->company->id) + ->where('client_id', $this->client->id) + ->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED]) + ->whereBetween('date', [$this->options['start_date'], $this->options['end_date']]) + ->get(); + } + + /** + * Get correct invitation ID. + * + * @return int|bool + */ + protected function getInvitation() + { + if ($this->entity instanceof Invoice || $this->entity instanceof Payment) { + return $this->entity->invitations->first(); + } + + return false; + } + + /** + * Get the array of aging data. + * + * @return array + */ + protected function getAging(): array + { + return [ + '0-30' => $this->getAgingAmount('30'), + '30-60' => $this->getAgingAmount('60'), + '60-90' => $this->getAgingAmount('90'), + '90-120' => $this->getAgingAmount('120'), + '120+' => $this->getAgingAmount('120+'), + ]; + } + + /** + * Generate aging amount. + * + * @param mixed $range + * @return string + */ + private function getAgingAmount($range) + { + $ranges = $this->calculateDateRanges($range); + + $from = $ranges[0]; + $to = $ranges[1]; + + $client = Client::where('id', $this->client->id)->first(); + + $amount = Invoice::where('company_id', $this->client->company->id) + ->where('client_id', $client->id) + ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) + ->where('balance', '>', 0) + ->whereBetween('date', [$from, $to]) + ->sum('balance'); + + return Number::formatMoney($amount, $client); + } + + /** + * Calculate date ranges for aging. + * + * @param mixed $range + * @return array + */ + private function calculateDateRanges($range) + { + $ranges = []; + + switch ($range) { + case '30': + $ranges[0] = now(); + $ranges[1] = now()->subDays(30); + return $ranges; + break; + case '60': + $ranges[0] = now()->subDays(30); + $ranges[1] = now()->subDays(60); + return $ranges; + break; + case '90': + $ranges[0] = now()->subDays(60); + $ranges[1] = now()->subDays(90); + return $ranges; + break; + case '120': + $ranges[0] = now()->subDays(90); + $ranges[1] = now()->subDays(120); + return $ranges; + break; + case '120+': + $ranges[0] = now()->subDays(120); + $ranges[1] = now()->subYears(40); + return $ranges; + break; + default: + $ranges[0] = now()->subDays(0); + $ranges[1] = now()->subDays(30); + return $ranges; + break; + } + } + + /** + * Get correct design for statement. + * + * @return \App\Models\Design + */ + protected function getDesign(): Design + { + $id = 1; + + if (!empty($this->client->getSetting('entity_design_id'))) { + $id = (int) $this->client->getSetting('entity_design_id'); + } + + return Design::find($id); + } +} From 745a81d3c8f8b587b50bca4071d2ba28c33f3cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 14 Sep 2021 13:55:33 +0200 Subject: [PATCH 04/14] Update ClientStatementController --- .../Controllers/ClientStatementController.php | 91 +------------------ 1 file changed, 5 insertions(+), 86 deletions(-) diff --git a/app/Http/Controllers/ClientStatementController.php b/app/Http/Controllers/ClientStatementController.php index 6acf4d6d89bc..002aadd2b882 100644 --- a/app/Http/Controllers/ClientStatementController.php +++ b/app/Http/Controllers/ClientStatementController.php @@ -110,8 +110,11 @@ class ClientStatementController extends BaseController public function statement(CreateStatementRequest $request) { - $pdf = $this->createStatement($request); - + $pdf = $request->client()->service()->statement([ + 'start_date' => $request->start_date, + 'end_date' => $request->end_date, + ]); + if ($pdf) { return response()->streamDownload(function () use ($pdf) { echo $pdf; @@ -120,88 +123,4 @@ class ClientStatementController extends BaseController return response()->json(['message' => 'Something went wrong. Please check logs.']); } - - protected function createStatement(CreateStatementRequest $request): ?string - { - $invitation = false; - - if ($request->getInvoices()->count() >= 1) { - $this->entity = $request->getInvoices()->first(); - $invitation = $this->entity->invitations->first(); - } - else if ($request->getPayments()->count() >= 1) { - $this->entity = $request->getPayments()->first()->invoices->first()->invitations->first(); - $invitation = $this->entity->invitations->first(); - } - - $entity_design_id = 1; - - $entity_design_id = $this->entity->design_id - ? $this->entity->design_id - : $this->decodePrimaryKey($this->entity->client->getSetting('invoice_design_id')); - - - $design = Design::find($entity_design_id); - - if (!$design) { - $design = Design::find($entity_design_id); - } - - $html = new HtmlEngine($invitation); - - $options = [ - 'start_date' => $request->start_date, - 'end_date' => $request->end_date, - 'show_payments_table' => $request->show_payments_table, - 'show_aging_table' => $request->show_aging_table, - ]; - - if ($design->is_custom) { - $options['custom_partials'] = \json_decode(\json_encode($design->design), true); - $template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options); - } else { - $template = new PdfMakerDesign(strtolower($design->name), $options); - } - - $variables = $html->generateLabelsAndValues(); - - $state = [ - 'template' => $template->elements([ - 'client' => $this->entity->client, - 'entity' => $this->entity, - 'pdf_variables' => (array)$this->entity->company->settings->pdf_variables, - '$product' => $design->design->product, - 'variables' => $variables, - 'invoices' => $request->getInvoices(), - 'payments' => $request->getPayments(), - 'aging' => $request->getAging(), - ], \App\Services\PdfMaker\Design::STATEMENT), - 'variables' => $variables, - 'options' => [], - 'process_markdown' => $this->entity->client->company->markdown_enabled, - ]; - - $maker = new PdfMakerService($state); - - $maker - ->design($template) - ->build(); - - $pdf = null; - - try { - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - $pdf = (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true)); - } - else if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - } else { - $pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true)); - } - } catch (\Exception $e) { - nlog(print_r($e->getMessage(), 1)); - } - - return $pdf; - } } From ee884811a074305004b2fcee81dfc3256ae680ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 14 Sep 2021 13:55:41 +0200 Subject: [PATCH 05/14] Clean up CreateStatementRequest --- .../Statements/CreateStatementRequest.php | 130 +----------------- 1 file changed, 6 insertions(+), 124 deletions(-) diff --git a/app/Http/Requests/Statements/CreateStatementRequest.php b/app/Http/Requests/Statements/CreateStatementRequest.php index 9a75794a550d..c4a2c31506ba 100644 --- a/app/Http/Requests/Statements/CreateStatementRequest.php +++ b/app/Http/Requests/Statements/CreateStatementRequest.php @@ -4,11 +4,7 @@ namespace App\Http\Requests\Statements; use App\Http\Requests\Request; use App\Models\Client; -use App\Models\Invoice; -use App\Models\Payment; -use App\Utils\Number; use App\Utils\Traits\MakesHash; -use Carbon\Carbon; class CreateStatementRequest extends Request { @@ -33,137 +29,23 @@ class CreateStatementRequest extends Request return [ 'start_date' => 'required|date_format:Y-m-d', 'end_date' => 'required|date_format:Y-m-d', - 'client_id' => 'bail|required|exists:clients,id,company_id,'.auth()->user()->company()->id, + 'client_id' => 'bail|required|exists:clients,id,company_id,' . auth()->user()->company()->id, 'show_payments_table' => 'boolean', 'show_aging_table' => 'boolean', ]; } + protected function prepareForValidation() { $input = $this->all(); $input = $this->decodePrimaryKeys($input); - + $this->replace($input); } - /** - * The collection of invoices for the statement. - * - * @return Invoice[]|\Illuminate\Database\Eloquent\Collection - */ - public function getInvoices() + + public function client(): ?Client { - $input = $this->all(); - - // $input['start_date & $input['end_date are available. - $client = Client::where('id', $input['client_id'])->first(); - - $from = Carbon::parse($input['start_date']); - $to = Carbon::parse($input['end_date']); - - return Invoice::where('company_id', auth()->user()->company()->id) - ->where('client_id', $client->id) - ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID]) - ->whereBetween('date',[$from, $to]) - ->get(); - } - - /** - * The collection of payments for the statement. - * - * @return Payment[]|\Illuminate\Database\Eloquent\Collection - */ - public function getPayments() - { - // $input['start_date & $input['end_date are available. - $input = $this->all(); - - $client = Client::where('id', $input['client_id'])->first(); - - $from = Carbon::parse($input['start_date']); - $to = Carbon::parse($input['end_date']); - - return Payment::where('company_id', auth()->user()->company()->id) - ->where('client_id', $client->id) - ->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED]) - ->whereBetween('date',[$from, $to]) - ->get(); - - } - - - /** - * The array of aging data. - */ - public function getAging(): array - { - return [ - '0-30' => $this->getAgingAmount('30'), - '30-60' => $this->getAgingAmount('60'), - '60-90' => $this->getAgingAmount('90'), - '90-120' => $this->getAgingAmount('120'), - '120+' => $this->getAgingAmount('120+'), - ]; - } - - private function getAgingAmount($range) - { - $input = $this->all(); - - $ranges = $this->calculateDateRanges($range); - - $from = $ranges[0]; - $to = $ranges[1]; - - $client = Client::where('id', $input['client_id'])->first(); - - $amount = Invoice::where('company_id', auth()->user()->company()->id) - ->where('client_id', $client->id) - ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) - ->where('balance', '>', 0) - ->whereBetween('date',[$from, $to]) - ->sum('balance'); - - return Number::formatMoney($amount, $client); - } - - private function calculateDateRanges($range) - { - - $ranges = []; - - switch ($range) { - case '30': - $ranges[0] = now(); - $ranges[1] = now()->subDays(30); - return $ranges; - break; - case '60': - $ranges[0] = now()->subDays(30); - $ranges[1] = now()->subDays(60); - return $ranges; - break; - case '90': - $ranges[0] = now()->subDays(60); - $ranges[1] = now()->subDays(90); - return $ranges; - break; - case '120': - $ranges[0] = now()->subDays(90); - $ranges[1] = now()->subDays(120); - return $ranges; - break; - case '120+': - $ranges[0] = now()->subDays(120); - $ranges[1] = now()->subYears(40); - return $ranges; - break; - default: - $ranges[0] = now()->subDays(0); - $ranges[1] = now()->subDays(30); - return $ranges; - break; - } - + return Client::where('id', $this->client_id)->first(); } } From 121e763e1ac180eb4e3e7791db429a821d4e67a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 14 Sep 2021 20:57:33 +0200 Subject: [PATCH 06/14] Scaffold components --- app/Http/Livewire/Statement.php | 23 +++++++++++++ .../ninja2020/components/statement.blade.php | 34 +++++++++++++++++++ .../ninja2020/statement/index.blade.php | 2 +- 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 app/Http/Livewire/Statement.php create mode 100644 resources/views/portal/ninja2020/components/statement.blade.php diff --git a/app/Http/Livewire/Statement.php b/app/Http/Livewire/Statement.php new file mode 100644 index 000000000000..8459e405589c --- /dev/null +++ b/app/Http/Livewire/Statement.php @@ -0,0 +1,23 @@ + +
+ + +
+ + + +
+ + + + +
+ \ No newline at end of file diff --git a/resources/views/portal/ninja2020/statement/index.blade.php b/resources/views/portal/ninja2020/statement/index.blade.php index 6da02053909e..eb999faccb14 100644 --- a/resources/views/portal/ninja2020/statement/index.blade.php +++ b/resources/views/portal/ninja2020/statement/index.blade.php @@ -3,5 +3,5 @@ @section('meta_title', ctrans('texts.statement')) @section('body') - + @livewire('statement') @endsection From 7961a99527fc529a4dc38d716ba06238580005cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 14 Sep 2021 21:10:56 +0200 Subject: [PATCH 07/14] Update translations --- .../ninja2020/components/statement.blade.php | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/resources/views/portal/ninja2020/components/statement.blade.php b/resources/views/portal/ninja2020/components/statement.blade.php index f18a12652f4b..d52539d977f9 100644 --- a/resources/views/portal/ninja2020/components/statement.blade.php +++ b/resources/views/portal/ninja2020/components/statement.blade.php @@ -1,34 +1,34 @@
-
- -
-
\ No newline at end of file From 99d600d4be1db77c3f9ab8855f0189389c74a98c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 14 Sep 2021 21:14:54 +0200 Subject: [PATCH 08/14] Fixes for flex spacing --- .../ninja2020/components/statement.blade.php | 59 ++++++++++--------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/resources/views/portal/ninja2020/components/statement.blade.php b/resources/views/portal/ninja2020/components/statement.blade.php index d52539d977f9..4f35bf121f65 100644 --- a/resources/views/portal/ninja2020/components/statement.blade.php +++ b/resources/views/portal/ninja2020/components/statement.blade.php @@ -1,34 +1,37 @@
-
- +
+
+ -
- +
+ - -
+ +
- + - + +
+
-
\ No newline at end of file +
From 0cbc9820e52b7fd12ca99c9392cc00156d5c7039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 15 Sep 2021 17:19:40 +0200 Subject: [PATCH 09/14] Add `statement/raw` route --- routes/client.php | 1 + 1 file changed, 1 insertion(+) diff --git a/routes/client.php b/routes/client.php index 72974245d6dd..f2e0cd8485a3 100644 --- a/routes/client.php +++ b/routes/client.php @@ -81,6 +81,7 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence Route::resource('tasks', 'ClientPortal\TaskController')->only(['index']); Route::get('statement', 'ClientPortal\StatementController@index')->name('statement'); + Route::get('statement/raw', 'ClientPortal\StatementController@raw')->name('statement.raw'); Route::post('upload', 'ClientPortal\UploadController')->name('upload.store'); Route::get('logout', 'Auth\ContactLoginController@logout')->name('logout'); From 00cf8b7023b01064659c05c76bb7b44d987f87ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 15 Sep 2021 17:21:16 +0200 Subject: [PATCH 10/14] Showing statement on the statement page --- .../ninja2020/components/pdf-viewer.blade.php | 6 +++--- .../ninja2020/components/statement.blade.php | 16 +++++++++------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/resources/views/portal/ninja2020/components/pdf-viewer.blade.php b/resources/views/portal/ninja2020/components/pdf-viewer.blade.php index ec97ae97e34b..7272095362a5 100644 --- a/resources/views/portal/ninja2020/components/pdf-viewer.blade.php +++ b/resources/views/portal/ninja2020/components/pdf-viewer.blade.php @@ -3,7 +3,7 @@ @endphp @push('head') - + @endpush @@ -72,7 +72,7 @@ class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg"> @@ -86,7 +86,7 @@
@else - + @endif diff --git a/resources/views/portal/ninja2020/components/statement.blade.php b/resources/views/portal/ninja2020/components/statement.blade.php index 4f35bf121f65..be4713a57c72 100644 --- a/resources/views/portal/ninja2020/components/statement.blade.php +++ b/resources/views/portal/ninja2020/components/statement.blade.php @@ -1,37 +1,39 @@
- --}}
- +
+ + @include('portal.ninja2020.components.pdf-viewer', ['url' => $url])
From 77f9c95139470015574ef9d50f9fae9f71acb7e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 15 Sep 2021 17:21:31 +0200 Subject: [PATCH 11/14] Fixes for options key pushing --- app/Services/Client/Statement.php | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/app/Services/Client/Statement.php b/app/Services/Client/Statement.php index 822984e2a0e3..c87719e8267e 100644 --- a/app/Services/Client/Statement.php +++ b/app/Services/Client/Statement.php @@ -47,7 +47,9 @@ class Statement public function run(): ?string { - $this->setupEntity()->setupOptions(); + $this + ->setupOptions() + ->setupEntity(); $html = new HtmlEngine($this->getInvitation()); @@ -111,10 +113,6 @@ class Statement $this->entity = $this->getInvoices()->first(); } - if (count($this->getPayments()) >= 1) { - $this->entity = $this->getPayments()->first(); - } - return $this; } @@ -125,19 +123,19 @@ class Statement */ protected function setupOptions(): self { - if (\array_key_exists('start_date', $this->options)) { + if (! \array_key_exists('start_date', $this->options)) { $this->options['start_date'] = now()->startOfYear()->format('Y-m-d'); } - if (\array_key_exists('end_date', $this->options)) { + if (! \array_key_exists('end_date', $this->options)) { $this->options['end_date'] = now()->format('Y-m-d'); } - if (\array_key_exists('show_payments_table', $this->options)) { + if (! \array_key_exists('show_payments_table', $this->options)) { $this->options['show_payments_table'] = false; } - if (\array_key_exists('show_aging_table', $this->options)) { + if (! \array_key_exists('show_aging_table', $this->options)) { $this->options['show_aging_table'] = false; } @@ -182,7 +180,7 @@ class Statement if ($this->entity instanceof Invoice || $this->entity instanceof Payment) { return $this->entity->invitations->first(); } - + return false; } From 7be7d2a9dacfef39c397b9b87b9e7e38be7b8c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 15 Sep 2021 17:23:07 +0200 Subject: [PATCH 12/14] Rendering PDF stream in client portal --- .../ClientPortal/StatementController.php | 38 +++++++++++++- app/Http/Livewire/Statement.php | 28 ++++++++++- .../Statements/ShowStatementRequest.php | 49 +++++++++++++++++++ 3 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 app/Http/Requests/ClientPortal/Statements/ShowStatementRequest.php diff --git a/app/Http/Controllers/ClientPortal/StatementController.php b/app/Http/Controllers/ClientPortal/StatementController.php index d5d95aa885b2..3579376cc64a 100644 --- a/app/Http/Controllers/ClientPortal/StatementController.php +++ b/app/Http/Controllers/ClientPortal/StatementController.php @@ -13,11 +13,47 @@ namespace App\Http\Controllers\ClientPortal; use App\Http\Controllers\Controller; +use App\Http\Requests\ClientPortal\Statements\ShowStatementRequest; +use Illuminate\Http\JsonResponse; +use Illuminate\View\View; +use Symfony\Component\HttpFoundation\StreamedResponse; class StatementController extends Controller { - public function index() + /** + * Show the statement in the client portal. + * + * @return View + */ + public function index(): View { return render('statement.index'); } + + /** + * Show the raw stream of statement PDF. + * + * @param ShowStatementRequest $request + * @return StreamedResponse|JsonResponse + */ + public function raw(ShowStatementRequest $request) + { + $pdf = $request->client()->service()->statement( + $request->only(['start_date', 'end_date', 'show_payments_table', 'show_aging_table']) + ); + + if ($pdf && $request->query('download')) { + return response()->streamDownload(function () use ($pdf) { + echo $pdf; + }, 'statement.pdf', ['Content-Type' => 'application/pdf']); + } + + if ($pdf) { + return response($pdf, 200)->withHeaders([ + 'Content-Type' => 'application/pdf', + ]); + } + + return response()->json(['message' => 'Something went wrong. Please check logs.']); + } } diff --git a/app/Http/Livewire/Statement.php b/app/Http/Livewire/Statement.php index 8459e405589c..42f5a7527d19 100644 --- a/app/Http/Livewire/Statement.php +++ b/app/Http/Livewire/Statement.php @@ -12,12 +12,38 @@ namespace App\Http\Livewire; +use Illuminate\View\View; use Livewire\Component; class Statement extends Component { - public function render() + public string $url; + + public array $options = [ + 'show_payments_table' => 0, + 'show_aging_table' => 0, + ]; + + public function mount(): void { + $this->options['start_date'] = now()->startOfYear()->format('Y-m-d'); + $this->options['end_date'] = now()->format('Y-m-d'); + } + + protected function getCurrentUrl(): string + { + return route('client.statement.raw', $this->options); + } + + public function download() + { + return redirect()->route('client.statement.raw', \array_merge($this->options, ['download' => 1])); + } + + public function render(): View + { + $this->url = route('client.statement.raw', $this->options); + return render('components.statement'); } } diff --git a/app/Http/Requests/ClientPortal/Statements/ShowStatementRequest.php b/app/Http/Requests/ClientPortal/Statements/ShowStatementRequest.php new file mode 100644 index 000000000000..c205aac9095c --- /dev/null +++ b/app/Http/Requests/ClientPortal/Statements/ShowStatementRequest.php @@ -0,0 +1,49 @@ +merge([ + 'show_payments_table' => $this->has('show_payments_table') ? \boolval($this->show_payments_table) : false, + 'show_aging_table' => $this->has('show_aging_table') ? \boolval($this->show_aging_table) : false, + ]); + } + + public function client(): Client + { + return auth('contact')->user()->client; + } +} From dd7edcff41b8136fa9a0ac6ae7148095b61c7310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 16 Sep 2021 14:21:29 +0200 Subject: [PATCH 13/14] Clean up --- .../ClientPortal/StatementController.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/ClientPortal/StatementController.php b/app/Http/Controllers/ClientPortal/StatementController.php index 3579376cc64a..2368ca395038 100644 --- a/app/Http/Controllers/ClientPortal/StatementController.php +++ b/app/Http/Controllers/ClientPortal/StatementController.php @@ -21,9 +21,9 @@ use Symfony\Component\HttpFoundation\StreamedResponse; class StatementController extends Controller { /** - * Show the statement in the client portal. - * - * @return View + * Show the statement in the client portal. + * + * @return View */ public function index(): View { @@ -31,17 +31,17 @@ class StatementController extends Controller } /** - * Show the raw stream of statement PDF. - * - * @param ShowStatementRequest $request - * @return StreamedResponse|JsonResponse + * Show the raw stream of the PDF. + * + * @param ShowStatementRequest $request + * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|JsonResponse|\Illuminate\Http\Response|StreamedResponse */ public function raw(ShowStatementRequest $request) { $pdf = $request->client()->service()->statement( $request->only(['start_date', 'end_date', 'show_payments_table', 'show_aging_table']) ); - + if ($pdf && $request->query('download')) { return response()->streamDownload(function () use ($pdf) { echo $pdf; From a87fb36cf1167350fad1d38dd5aef72c4bf4b3bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 16 Sep 2021 15:04:24 +0200 Subject: [PATCH 14/14] Fixes for showing non-range invoices --- app/Services/Client/Statement.php | 70 ++++++++++++++++++- .../Designs/Utilities/DesignHelpers.php | 5 +- app/Services/PdfMaker/PdfMaker.php | 6 ++ 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/app/Services/Client/Statement.php b/app/Services/Client/Statement.php index c87719e8267e..5d6d5a0e4376 100644 --- a/app/Services/Client/Statement.php +++ b/app/Services/Client/Statement.php @@ -12,10 +12,14 @@ namespace App\Services\Client; +use App\Factory\InvoiceFactory; +use App\Factory\InvoiceInvitationFactory; +use App\Factory\InvoiceItemFactory; use App\Models\Client; use App\Models\Design; use App\Models\Invoice; use App\Models\Payment; +use App\Models\Product; use App\Services\PdfMaker\Design as PdfMakerDesign; use App\Services\PdfMaker\PdfMaker; use App\Utils\HostedPDF\NinjaPdf; @@ -24,6 +28,7 @@ use App\Utils\Number; use App\Utils\PhantomJS\Phantom; use App\Utils\Traits\Pdf\PdfMaker as PdfMakerTrait; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Facades\DB; class Statement { @@ -38,6 +43,8 @@ class Statement protected array $options; + protected bool $rollback = false; + public function __construct(Client $client, array $options) { $this->client = $client; @@ -99,6 +106,10 @@ class Statement nlog(print_r($e->getMessage(), 1)); } + if ($this->rollback) { + DB::rollBack(); + } + return $pdf; } @@ -113,9 +124,66 @@ class Statement $this->entity = $this->getInvoices()->first(); } + if (\is_null($this->entity)) { + DB::beginTransaction(); + $this->rollback = true; + + $invoice = InvoiceFactory::create($this->client->company->id, $this->client->user->id); + $invoice->client_id = $this->client->id; + $invoice->line_items = $this->buildLineItems(); + $invoice->save(); + + $invitation = InvoiceInvitationFactory::create($invoice->company_id, $invoice->user_id); + $invitation->invoice_id = $invoice->id; + $invitation->client_contact_id = $this->client->contacts->first()->id; + $invitation->save(); + + $this->entity = $invoice; + } + return $this; } + protected function buildLineItems($count = 1) + { + $line_items = []; + + for ($x = 0; $x < $count; $x++) { + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + //$item->cost = 10; + + if (rand(0, 1)) { + $item->tax_name1 = 'GST'; + $item->tax_rate1 = 10.00; + } + + if (rand(0, 1)) { + $item->tax_name1 = 'VAT'; + $item->tax_rate1 = 17.50; + } + + if (rand(0, 1)) { + $item->tax_name1 = 'Sales Tax'; + $item->tax_rate1 = 5; + } + + $product = Product::all()->random(); + + $item->cost = (float) $product->cost; + $item->product_key = $product->product_key; + $item->notes = $product->notes; + $item->custom_value1 = $product->custom_value1; + $item->custom_value2 = $product->custom_value2; + $item->custom_value3 = $product->custom_value3; + $item->custom_value4 = $product->custom_value4; + + $line_items[] = $item; + } + + return $line_items; + } + /** * Setup & prepare options. * @@ -180,7 +248,7 @@ class Statement if ($this->entity instanceof Invoice || $this->entity instanceof Payment) { return $this->entity->invitations->first(); } - + return false; } diff --git a/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php b/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php index b9aea8184d42..02fb145f1280 100644 --- a/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php +++ b/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php @@ -40,7 +40,10 @@ trait DesignHelpers if (isset($this->context['invoices'])) { $this->invoices = $this->context['invoices']; - $this->entity = $this->invoices->first(); + + if (\count($this->invoices) >= 1) { + $this->entity = $this->invoices->first(); + } } if (isset($this->context['payments'])) { diff --git a/app/Services/PdfMaker/PdfMaker.php b/app/Services/PdfMaker/PdfMaker.php index aded4c4a54fe..aad88b9e37fb 100644 --- a/app/Services/PdfMaker/PdfMaker.php +++ b/app/Services/PdfMaker/PdfMaker.php @@ -82,6 +82,12 @@ class PdfMaker return $this; } + /** + * Return compiled HTML. + * + * @param bool $final deprecated + * @return mixed + */ public function getCompiledHTML($final = false) { $html = $this->document->saveHTML();