From c5e8fc9ed248ec374e0fff6fb42fc91b9198f492 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 28 Jan 2025 14:56:16 +1100 Subject: [PATCH 01/43] Remove redundant pdf generator --- app/Http/Controllers/PreviewController.php | 76 +++++----------------- 1 file changed, 16 insertions(+), 60 deletions(-) diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 7e39fd4d34..f9384deaae 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -145,14 +145,16 @@ class PreviewController extends BaseController public function show(ShowPreviewRequest $request) { + if ($request->input('design.is_template')) { + nlog("I Template"); return $this->template(); } - if (request()->has('entity') && - request()->has('entity_id') && - ! empty(request()->input('entity')) && - ! empty(request()->input('entity_id'))) { + nlog("pre"); + + if ($request->input('entity', false) && + $request->input('entity_id', false) != '-1') { if ($request->input('entity') == 'purchase_order') { return app(\App\Http\Controllers\PreviewPurchaseOrderController::class)->show($request); @@ -180,64 +182,18 @@ class PreviewController extends BaseController $t = app('translator'); App::setLocale($entity_obj->client->preferredLocale()); $t->replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings())); + $invitation = $entity_obj->invitations()->first(); - if ($entity_obj->client) { - $html = new HtmlEngine($entity_obj->invitations()->first()); - } else { - $html = new VendorHtmlEngine($entity_obj->invitations()->first()); - } + $ps = new PdfService($invitation, 'product', [ + 'client' => $entity_obj->client ?? false, + 'vendor' => $entity_obj->vendor ?? false, + $request->input('entity')."s" => [$entity_obj], + ]); - $design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]); - - $state = [ - 'template' => $design->elements([ - 'client' => $entity_obj->client, - 'entity' => $entity_obj, - 'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables, - 'products' => request()->design['design']['product'], - ]), - 'variables' => $html->generateLabelsAndValues(), - 'process_markdown' => $entity_obj->client->company->markdown_enabled, - 'options' => [ - 'client' => $entity_obj->client ?? [], - 'vendor' => $entity_obj->vendor ?? [], - request()->input('entity_type', 'invoice')."s" => [$entity_obj], - ] - ]; - - $maker = new PdfMaker($state); - - $maker - ->design($design) - ->build(); - - if (request()->query('html') == 'true') { - - return $maker->getCompiledHTML(); - } - - //if phantom js...... inject here.. - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom())->convertHtmlToPdf($maker->getCompiledHTML(true)); - } - - /** @var \App\Models\User $user */ - $user = auth()->user(); - $company = $user->company(); - - if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - $numbered_pdf = $this->pageNumbering($pdf, $company); - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - - return $pdf; - - } - - $pdf = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); + $pdf = $ps->boot() + ->designer + ->buildFromPartials($request->design['design']) + ->getPdf(); return response()->streamDownload(function () use ($pdf) { echo $pdf; From a4686ca2342a5846e181b545a27922ea8de6e60e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 28 Jan 2025 15:28:46 +1100 Subject: [PATCH 02/43] Remove redundant pdf generator --- app/Http/Controllers/PreviewController.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index f9384deaae..8e849dce81 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -147,12 +147,9 @@ class PreviewController extends BaseController if ($request->input('design.is_template')) { - nlog("I Template"); return $this->template(); } - nlog("pre"); - if ($request->input('entity', false) && $request->input('entity_id', false) != '-1') { @@ -160,17 +157,17 @@ class PreviewController extends BaseController return app(\App\Http\Controllers\PreviewPurchaseOrderController::class)->show($request); } - $design_object = json_decode(json_encode(request()->input('design'))); + $design_object = json_decode(json_encode($request->input('design'))); if (! is_object($design_object)) { return response()->json(['message' => ctrans('texts.invalid_design_object')], 400); } - $entity = Str::camel(request()->input('entity')); + $entity = Str::camel($request->input('entity')); $class = "App\Models\\$entity"; - $entity_obj = $class::whereId($this->decodePrimaryKey(request()->input('entity_id')))->company()->first(); + $entity_obj = $class::whereId($this->decodePrimaryKey($request->input('entity_id')))->company()->first(); if (! $entity_obj) { return $this->blankEntity(); From cffb77fe360704816cbc9f21ad6a06070bcedc62 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 28 Jan 2025 16:29:32 +1100 Subject: [PATCH 03/43] Remove redundant pdf generator --- app/Http/Controllers/PreviewController.php | 203 +++++---------------- app/Services/Pdf/PdfDesigner.php | 2 + app/Services/Pdf/PdfMock.php | 41 +++-- 3 files changed, 73 insertions(+), 173 deletions(-) diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 8e849dce81..3857ef5f3c 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -189,8 +189,13 @@ class PreviewController extends BaseController $pdf = $ps->boot() ->designer - ->buildFromPartials($request->design['design']) - ->getPdf(); + ->buildFromPartials($request->design['design']); + + if ($request->query('html') == 'true') { + return $ps->getHtml(); + } + + $pdf = $ps->getPdf(); return response()->streamDownload(function () use ($pdf) { echo $pdf; @@ -281,6 +286,8 @@ class PreviewController extends BaseController private function blankEntity() { + nlog("blankEntity"); + /** @var \App\Models\User $user */ $user = auth()->user(); @@ -299,192 +306,66 @@ class PreviewController extends BaseController return $this->mockEntity(); } - $design_object = json_decode(json_encode(request()->input('design'))); + $design_object = json_decode(json_encode(request()->input('design')), true); - if (! is_object($design_object)) { + if (! is_array($design_object)) { return response()->json(['message' => 'Invalid custom design object'], 400); } - $html = new HtmlEngine($invitation); + $ps = new PdfService($invitation, 'product', [ + 'client' => $invitation->client ?? false, + 'invoices' => [$invitation->invoice], + ]); - $design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]); + $ps->boot() + ->designer + ->buildFromPartials($design_object['design']) + ->build(); - $state = [ - 'template' => $design->elements([ - 'client' => $invitation->invoice->client, - 'entity' => $invitation->invoice, - 'pdf_variables' => (array) $invitation->invoice->company->settings->pdf_variables, - 'products' => request()->design['design']['product'], - ]), - 'variables' => $html->generateLabelsAndValues(), - 'process_markdown' => $invitation->invoice->client->company->markdown_enabled, - 'options' => [ - 'client' => $invitation->invoice->client, - 'invoices' => [$invitation->invoice], - ] - ]; - - $maker = new PdfMaker($state); - - $maker - ->design($design) - ->build(); if (request()->query('html') == 'true') { - return $maker->getCompiledHTML(); + return $ps->getHtml(); } - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom())->convertHtmlToPdf($maker->getCompiledHTML(true)); - } + $pdf = $ps->getPdf(); - /** @var \App\Models\User $user */ - $user = auth()->user(); + return response()->streamDownload(function () use ($pdf) { + echo $pdf; + }, 'preview.pdf', [ + 'Content-Disposition' => 'inline', + 'Content-Type' => 'application/pdf', + 'Cache-Control:' => 'no-cache', + ]); - /** @var \App\Models\Company $company */ - $company = $user->company(); - - if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - - $numbered_pdf = $this->pageNumbering($pdf, $company); - - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - - return $pdf; - } - - $file_path = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); - - $response = Response::make($file_path, 200); - $response->header('Content-Type', 'application/pdf'); - - return $response; } + private function mockEntity() { - /** @var \App\Models\User $user */ + + $start = microtime(true); $user = auth()->user(); /** @var \App\Models\Company $company */ $company = $user->company(); - try { - DB::connection($company->db)->beginTransaction(); - - /** @var \App\Models\Client $client */ - $client = Client::factory()->create([ - 'user_id' => $user->id, - 'company_id' => $company->id, - ]); - - /** @var \App\Models\ClientContact $contact */ - $contact = ClientContact::factory()->create([ - 'user_id' => $user->id, - 'company_id' => $company->id, - 'client_id' => $client->id, - 'is_primary' => 1, - 'send_email' => true, - ]); - - $settings = $company->settings; - - /** @var \App\Models\Invoice $invoice */ - $invoice = Invoice::factory()->create([ - 'user_id' => $user->id, - 'company_id' => $company->id, - 'client_id' => $client->id, - 'terms' => $company->settings->invoice_terms, - 'footer' => $company->settings->invoice_footer, - 'public_notes' => 'Sample Public Notes', - ]); - - if ($settings->invoice_number_pattern) { - $invoice->number = $this->getFormattedEntityNumber( - $invoice, - rand(1, 9999), - $settings->counter_padding ?: 4, - $settings->invoice_number_pattern, - ); - $invoice->save(); - } - - $invitation = InvoiceInvitation::factory()->create([ - 'user_id' => $user->id, - 'company_id' => $company->id, - 'invoice_id' => $invoice->id, - 'client_contact_id' => $contact->id, - ]); - - $invoice->setRelation('invitations', $invitation); - $invoice->setRelation('client', $client); - $invoice->setRelation('company', $company); - $invoice->load('client.company'); - - $design_object = json_decode(json_encode(request()->input('design'))); - - if (! is_object($design_object)) { - return response()->json(['message' => 'Invalid custom design object'], 400); - } - - $html = new HtmlEngine($invoice->invitations()->first()); - - $design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]); - - $state = [ - 'template' => $design->elements([ - 'client' => $invoice->client, - 'entity' => $invoice, - 'pdf_variables' => (array) $settings->pdf_variables, - 'products' => request()->design['design']['product'], - ]), - 'variables' => $html->generateLabelsAndValues(), - 'process_markdown' => $invoice->client->company->markdown_enabled, - 'options' => [ - 'client' => $invoice->client, - 'invoices' => [$invoice], - ] - ]; - - $maker = new PdfMaker($state); - - $maker - ->design($design) - ->build(); - - DB::connection($company->db)->rollBack(); - } catch (\Exception $e) { - DB::connection($company->db)->rollBack(); - return response()->json(['message' => $e->getMessage()], 400); - } + $request = request()->input('design'); + $request['entity_type'] = request()->input('entity', 'invoice'); + $pdf = (new PdfMock($request, $company))->build(); + if (request()->query('html') == 'true') { - return $maker->getCompiledHTML(); + return $pdf->getHtml(); } - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom())->convertHtmlToPdf($maker->getCompiledHTML(true)); - } + $pdf = $pdf->getPdf(); - if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - - $numbered_pdf = $this->pageNumbering($pdf, $company); - - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - - return $pdf; - } - - $file_path = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); - - $response = Response::make($file_path, 200); + $response = Response::make($pdf, 200); $response->header('Content-Type', 'application/pdf'); + $response->header('Server-Timing', (string) (microtime(true) - $start)); + return $response; + } + } diff --git a/app/Services/Pdf/PdfDesigner.php b/app/Services/Pdf/PdfDesigner.php index 031373b1e0..55feb25c5e 100644 --- a/app/Services/Pdf/PdfDesigner.php +++ b/app/Services/Pdf/PdfDesigner.php @@ -59,6 +59,8 @@ class PdfDesigner $this->template = $this->composeFromPartials($partials); + nlog("template is"); + nlog($this->template); return $this; } diff --git a/app/Services/Pdf/PdfMock.php b/app/Services/Pdf/PdfMock.php index 05cc7f0021..3a8396b1e6 100644 --- a/app/Services/Pdf/PdfMock.php +++ b/app/Services/Pdf/PdfMock.php @@ -42,18 +42,20 @@ class PdfMock private string $entity_string = 'invoice'; + private PdfService $pdf_service; + public function __construct(public array $request, public Company $company) { } - public function getPdf(): mixed + public function setPdfService(): self { //need to resolve the pdf type here, ie product / purchase order $document_type = $this->request['entity_type'] == 'purchase_order' ? 'purchase_order' : 'product'; - $pdf_service = new PdfService($this->mock->invitation, $document_type); + $this->pdf_service = new PdfService($this->mock->invitation, $document_type); - $pdf_config = (new PdfConfiguration($pdf_service)); + $pdf_config = (new PdfConfiguration($this->pdf_service)); $pdf_config->entity = $this->mock; $pdf_config->entity_string = $this->request['entity_type']; $this->entity_string = $this->request['entity_type']; @@ -76,29 +78,44 @@ class PdfMock $pdf_config->design = Design::withTrashed()->find($this->decodePrimaryKey($pdf_config->entity_design_id)); } - $pdf_service->config = $pdf_config; + $this->pdf_service->config = $pdf_config; if (isset($this->request['design'])) { - $pdf_designer = (new PdfDesigner($pdf_service))->buildFromPartials($this->request['design']); + $pdf_designer = (new PdfDesigner($this->pdf_service))->buildFromPartials($this->request['design']); } else { - $pdf_designer = (new PdfDesigner($pdf_service))->build(); + $pdf_designer = (new PdfDesigner($this->pdf_service))->build(); } - $pdf_service->designer = $pdf_designer; + $this->pdf_service->designer = $pdf_designer; - $pdf_service->html_variables = $document_type == 'purchase_order' ? $this->getVendorStubVariables() : $this->getStubVariables(); + $this->pdf_service->html_variables = $document_type == 'purchase_order' ? $this->getVendorStubVariables() : $this->getStubVariables(); - $pdf_builder = (new PdfBuilder($pdf_service))->build(); - $pdf_service->builder = $pdf_builder; + $pdf_builder = (new PdfBuilder($this->pdf_service))->build(); + $this->pdf_service->builder = $pdf_builder; - $html = $pdf_service->getHtml(); + return $this; + } + + public function getPdf(): mixed + { + + $html = $this->pdf_service->getHtml(); + + return $this->pdf_service->resolvePdfEngine($html); + + } + + public function getHtml(): string + { + return $this->pdf_service->getHtml(); - return $pdf_service->resolvePdfEngine($html); } public function build(): self { $this->mock = $this->initEntity(); + + $this->setPdfService(); return $this; } From 4fe7b44c5164890758ce82b0bf8596d1e2bbd52c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 28 Jan 2025 17:28:15 +1100 Subject: [PATCH 04/43] Remove redundant pdf generator --- app/Http/Controllers/PreviewController.php | 48 +++++++++++++++------- app/Models/Vendor.php | 5 +++ app/Services/Pdf/PdfDesigner.php | 2 - app/Services/Pdf/PdfMock.php | 2 +- 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 3857ef5f3c..fd722059e1 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -21,6 +21,7 @@ use App\Jobs\Util\PreviewPdf; use App\Models\ClientContact; use App\Services\Pdf\PdfMock; use App\Utils\Traits\MakesHash; +use App\Utils\VendorHtmlEngine; use App\Services\Pdf\PdfService; use App\Utils\PhantomJS\Phantom; use App\Models\InvoiceInvitation; @@ -32,6 +33,7 @@ use Illuminate\Support\Facades\App; use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\MakesInvoiceHtml; use Turbo124\Beacon\Facades\LightLogs; +use App\Models\PurchaseOrderInvitation; use App\Utils\Traits\Pdf\PageNumbering; use Illuminate\Support\Facades\Response; use App\DataMapper\Analytics\LivePreview; @@ -39,7 +41,6 @@ use App\Services\Template\TemplateService; use App\Http\Requests\Preview\ShowPreviewRequest; use App\Http\Requests\Preview\DesignPreviewRequest; use App\Http\Requests\Preview\PreviewInvoiceRequest; -use App\Utils\VendorHtmlEngine; class PreviewController extends BaseController { @@ -153,10 +154,6 @@ class PreviewController extends BaseController if ($request->input('entity', false) && $request->input('entity_id', false) != '-1') { - if ($request->input('entity') == 'purchase_order') { - return app(\App\Http\Controllers\PreviewPurchaseOrderController::class)->show($request); - } - $design_object = json_decode(json_encode($request->input('design'))); if (! is_object($design_object)) { @@ -173,12 +170,21 @@ class PreviewController extends BaseController return $this->blankEntity(); } - $entity_obj->load('client'); + if($entity_obj->client){ + $entity_obj->load('client'); + $locale = $entity_obj->client->preferredLocale(); + $settings = $entity_obj->client->getMergedSettings(); + } + else { + $entity_obj->load('vendor'); + $locale = $entity_obj->vendor->preferredLocale(); + $settings = $entity_obj->vendor->getMergedSettings(); + } App::forgetInstance('translator'); $t = app('translator'); - App::setLocale($entity_obj->client->preferredLocale()); - $t->replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings())); + App::setLocale($locale); + $t->replace(Ninja::transformTranslations($settings)); $invitation = $entity_obj->invitations()->first(); $ps = new PdfService($invitation, 'product', [ @@ -187,9 +193,12 @@ class PreviewController extends BaseController $request->input('entity')."s" => [$entity_obj], ]); - $pdf = $ps->boot() + $ps->boot() ->designer ->buildFromPartials($request->design['design']); + + $ps->builder + ->build(); if ($request->query('html') == 'true') { return $ps->getHtml(); @@ -286,8 +295,6 @@ class PreviewController extends BaseController private function blankEntity() { - nlog("blankEntity"); - /** @var \App\Models\User $user */ $user = auth()->user(); @@ -298,8 +305,16 @@ class PreviewController extends BaseController $t = app('translator'); $t->replace(Ninja::transformTranslations($company->settings)); - /** @var \App\Models\InvoiceInvitation $invitation */ - $invitation = InvoiceInvitation::where('company_id', $company->id)->orderBy('id', 'desc')->first(); + $entity_string = 'invoice'; + + if(request()->input('entity') == 'purchase_order') { + $invitation = PurchaseOrderInvitation::where('company_id', $company->id)->orderBy('id', 'desc')->first(); + $entity_string = 'purchase_order'; + } + else{ + /** @var \App\Models\InvoiceInvitation $invitation */ + $invitation = InvoiceInvitation::where('company_id', $company->id)->orderBy('id', 'desc')->first(); + } /* If we don't have a valid invitation in the system - create a mock using transactions */ if (! $invitation) { @@ -314,12 +329,15 @@ class PreviewController extends BaseController $ps = new PdfService($invitation, 'product', [ 'client' => $invitation->client ?? false, - 'invoices' => [$invitation->invoice], + 'vendor' => $invitation->vendor ?? false, + "{$entity_string}s" => [$invitation->{$entity_string}], ]); $ps->boot() ->designer - ->buildFromPartials($design_object['design']) + ->buildFromPartials($design_object['design']); + + $ps->builder ->build(); diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php index 2778cbebfd..7d3d26a71b 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -319,6 +319,11 @@ class Vendor extends BaseModel return $this->language ? $this->language->locale : $this->company->locale(); } + public function preferredLocale(): string + { + return $this->locale(); + } + public function language(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Language::class); diff --git a/app/Services/Pdf/PdfDesigner.php b/app/Services/Pdf/PdfDesigner.php index 55feb25c5e..031373b1e0 100644 --- a/app/Services/Pdf/PdfDesigner.php +++ b/app/Services/Pdf/PdfDesigner.php @@ -59,8 +59,6 @@ class PdfDesigner $this->template = $this->composeFromPartials($partials); - nlog("template is"); - nlog($this->template); return $this; } diff --git a/app/Services/Pdf/PdfMock.php b/app/Services/Pdf/PdfMock.php index 3a8396b1e6..fd8437b769 100644 --- a/app/Services/Pdf/PdfMock.php +++ b/app/Services/Pdf/PdfMock.php @@ -80,7 +80,7 @@ class PdfMock $this->pdf_service->config = $pdf_config; - if (isset($this->request['design'])) { + if (isset($this->request['design']) && is_array($this->request['design'])) { $pdf_designer = (new PdfDesigner($this->pdf_service))->buildFromPartials($this->request['design']); } else { $pdf_designer = (new PdfDesigner($this->pdf_service))->build(); From 2f5d01c6af284b54f5d594734a93f023a046a0f8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 28 Jan 2025 17:39:04 +1100 Subject: [PATCH 05/43] Remove redundant pdf generator --- .../PreviewPurchaseOrderController.php | 305 +++++++----------- 1 file changed, 115 insertions(+), 190 deletions(-) diff --git a/app/Http/Controllers/PreviewPurchaseOrderController.php b/app/Http/Controllers/PreviewPurchaseOrderController.php index 86f218292e..05ec469613 100644 --- a/app/Http/Controllers/PreviewPurchaseOrderController.php +++ b/app/Http/Controllers/PreviewPurchaseOrderController.php @@ -109,63 +109,95 @@ class PreviewPurchaseOrderController extends BaseController App::setLocale($entity_obj->company->locale()); $t->replace(Ninja::transformTranslations($entity_obj->company->settings)); - $html = new VendorHtmlEngine($entity_obj->invitations()->first()); - $design_namespace = 'App\Services\PdfMaker\Designs\\'.request()->design['name']; - $design_class = new $design_namespace(); +$invitation = $entity_obj->invitations()->first(); - $state = [ - 'template' => $design_class->elements([ - 'client' => null, - 'vendor' => $entity_obj->vendor, - 'entity' => $entity_obj, - 'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables, - 'variables' => $html->generateLabelsAndValues(), - ]), - 'variables' => $html->generateLabelsAndValues(), - 'process_markdown' => $entity_obj->company->markdown_enabled, - 'options' => [ - 'vendor' => $entity_obj->vendor ?? [], - request()->input('entity')."s" => [$entity_obj], - ] - ]; +$ps = new PdfService($invitation, 'product', [ + 'client' => $entity_obj->client ?? false, + 'vendor' => $entity_obj->vendor ?? false, + $request->input('entity')."s" => [$entity_obj], +]); - $design = new Design(request()->design['name']); - $maker = new PdfMaker($state); +$ps->boot() +->designer +->buildFromPartials($request->design['design']); - $maker - ->design($design) - ->build(); +$ps->builder +->build(); - if (request()->query('html') == 'true') { - return $maker->getCompiledHTML(); - } +if ($request->query('html') == 'true') { + return $ps->getHtml(); +} - //if phantom js...... inject here.. - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom())->convertHtmlToPdf($maker->getCompiledHTML(true)); - } +$pdf = $ps->getPdf(); - /** @var \App\Models\User $user */ - $user = auth()->user(); +return response()->streamDownload(function () use ($pdf) { + echo $pdf; +}, 'preview.pdf', [ + 'Content-Disposition' => 'inline', + 'Content-Type' => 'application/pdf', + 'Cache-Control:' => 'no-cache', +]); - if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - $numbered_pdf = $this->pageNumbering($pdf, $user->company()); + // $html = new VendorHtmlEngine($entity_obj->invitations()->first()); - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } + // $design_namespace = 'App\Services\PdfMaker\Designs\\'.request()->design['name']; - return $pdf; - } + // $design_class = new $design_namespace(); - //else - $file_path = (new PreviewPdf($maker->getCompiledHTML(true), $user->company()))->handle(); + // $state = [ + // 'template' => $design_class->elements([ + // 'client' => null, + // 'vendor' => $entity_obj->vendor, + // 'entity' => $entity_obj, + // 'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables, + // 'variables' => $html->generateLabelsAndValues(), + // ]), + // 'variables' => $html->generateLabelsAndValues(), + // 'process_markdown' => $entity_obj->company->markdown_enabled, + // 'options' => [ + // 'vendor' => $entity_obj->vendor ?? [], + // request()->input('entity')."s" => [$entity_obj], + // ] + // ]; - return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true); + // $design = new Design(request()->design['name']); + // $maker = new PdfMaker($state); + + // $maker + // ->design($design) + // ->build(); + + // if (request()->query('html') == 'true') { + // return $maker->getCompiledHTML(); + // } + + // //if phantom js...... inject here.. + // if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { + // return (new Phantom())->convertHtmlToPdf($maker->getCompiledHTML(true)); + // } + + // /** @var \App\Models\User $user */ + // $user = auth()->user(); + + // if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { + // $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); + + // $numbered_pdf = $this->pageNumbering($pdf, $user->company()); + + // if ($numbered_pdf) { + // $pdf = $numbered_pdf; + // } + + // return $pdf; + // } + + // //else + // $file_path = (new PreviewPdf($maker->getCompiledHTML(true), $user->company()))->handle(); + + // return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true); } return $this->blankEntity(); @@ -238,177 +270,70 @@ class PreviewPurchaseOrderController extends BaseController return $this->mockEntity(); } - $design_object = json_decode(json_encode(request()->input('design'))); + +$design_object = json_decode(json_encode(request()->input('design')), true); - if (! is_object($design_object)) { - return response()->json(['message' => 'Invalid custom design object'], 400); - } +if (! is_array($design_object)) { + return response()->json(['message' => 'Invalid custom design object'], 400); +} - $html = new VendorHtmlEngine($invitation); +$ps = new PdfService($invitation, 'product', [ + 'client' => $invitation->client ?? false, + 'vendor' => $invitation->vendor ?? false, + "{$entity_string}s" => [$invitation->{$entity_string}], +]); - $design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]); +$ps->boot() +->designer +->buildFromPartials($design_object['design']); - $state = [ - 'template' => $design->elements([ - 'client' => null, - 'vendor' => $invitation->purchase_order->vendor, - 'entity' => $invitation->purchase_order, - 'pdf_variables' => (array) $invitation->company->settings->pdf_variables, - 'products' => request()->design['design']['product'], - ]), - 'variables' => $html->generateLabelsAndValues(), - 'process_markdown' => $invitation->company->markdown_enabled, - 'options' => [ - 'vendor' => $invitation->purchase_order->vendor, - 'purchase_orders' => [$invitation->purchase_order], - ], - ]; +$ps->builder +->build(); - $maker = new PdfMaker($state); +if (request()->query('html') == 'true') { + return $ps->getHtml(); +} - $maker - ->design($design) - ->build(); +$pdf = $ps->getPdf(); - if (request()->query('html') == 'true') { - return $maker->getCompiledHTML(); - } +return response()->streamDownload(function () use ($pdf) { + echo $pdf; +}, 'preview.pdf', [ + 'Content-Disposition' => 'inline', + 'Content-Type' => 'application/pdf', + 'Cache-Control:' => 'no-cache', +]); - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom())->convertHtmlToPdf($maker->getCompiledHTML(true)); - } - - /** @var \App\Models\User $user */ - $user = auth()->user(); - - if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - - $numbered_pdf = $this->pageNumbering($pdf, $user->company()); - - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - - return $pdf; - } - - $file_path = (new PreviewPdf($maker->getCompiledHTML(true), $user->company()))->handle(); - - $response = Response::make($file_path, 200); - $response->header('Content-Type', 'application/pdf'); - - return $response; } private function mockEntity() { - /** @var \App\Models\User $user */ + + nlog("mockEntity"); + + $start = microtime(true); $user = auth()->user(); - DB::connection($user->company()->db)->beginTransaction(); + /** @var \App\Models\Company $company */ + $company = $user->company(); - /** @var \App\Models\Vendor $vendor */ - $vendor = Vendor::factory()->create([ - 'user_id' => $user->id, - 'company_id' => $user->company()->id, - ]); + $request = request()->input('design'); + $request['entity_type'] = request()->input('entity', 'invoice'); - /** @var \App\Models\VendorContact $contact */ - $contact = VendorContact::factory()->create([ - 'user_id' => $user->id, - 'company_id' => $user->company()->id, - 'vendor_id' => $vendor->id, - 'is_primary' => 1, - 'send_email' => true, - ]); - - /** @var \App\Models\PurchaseOrder $purchase_order */ - $purchase_order = PurchaseOrder::factory()->create([ - 'user_id' => $user->id, - 'company_id' => $user->company()->id, - 'vendor_id' => $vendor->id, - 'terms' => 'Sample Terms', - 'footer' => 'Sample Footer', - 'public_notes' => 'Sample Public Notes', - ]); - - /** @var \App\Models\PurchaseOrderInvitation $invitation */ - $invitation = PurchaseOrderInvitation::factory()->create([ - 'user_id' => $user->id, - 'company_id' => $user->company()->id, - 'purchase_order_id' => $purchase_order->id, - 'vendor_contact_id' => $contact->id, - ]); - - $purchase_order->setRelation('invitations', $invitation); - $purchase_order->setRelation('vendor', $vendor); - $purchase_order->setRelation('company', $user->company()); - $purchase_order->load('vendor.company'); - - $design_object = json_decode(json_encode(request()->input('design'))); - - if (! is_object($design_object)) { - return response()->json(['message' => 'Invalid custom design object'], 400); - } - - $html = new VendorHtmlEngine($purchase_order->invitations()->first()); - - $design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]); - - $state = [ - 'template' => $design->elements([ - 'client' => null, - 'vendor' => $purchase_order->vendor, - 'entity' => $purchase_order, - 'pdf_variables' => (array) $purchase_order->company->settings->pdf_variables, - 'products' => request()->design['design']['product'], - ]), - 'variables' => $html->generateLabelsAndValues(), - 'process_markdown' => $purchase_order->company->markdown_enabled, - 'options' => [ - 'vendor' => $invitation->purchase_order->vendor, - 'purchase_orders' => [$invitation->purchase_order], - ], - ]; - - $maker = new PdfMaker($state); - - $maker - ->design($design) - ->build(); - - /** @var \App\Models\User $user */ - $user = auth()->user(); - - DB::connection($user->company()->db)->rollBack(); + $pdf = (new PdfMock($request, $company))->build(); if (request()->query('html') == 'true') { - return $maker->getCompiledHTML(); + return $pdf->getHtml(); } - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom())->convertHtmlToPdf($maker->getCompiledHTML(true)); - } + $pdf = $pdf->getPdf(); - if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - - $numbered_pdf = $this->pageNumbering($pdf, $user->company()); - - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - - return $pdf; - } - - $file_path = (new PreviewPdf($maker->getCompiledHTML(true), $user->company()))->handle(); - - $response = Response::make($file_path, 200); + $response = Response::make($pdf, 200); $response->header('Content-Type', 'application/pdf'); + $response->header('Server-Timing', (string) (microtime(true) - $start)); return $response; + } } From 5fccf7f3599a4d620106cadc949f4eebef92f6e4 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 28 Jan 2025 22:16:36 +1100 Subject: [PATCH 06/43] Remove redundant pdf generator --- .../PreviewPurchaseOrderController.php | 182 ++++++------------ .../Preview/PreviewPurchaseOrderRequest.php | 14 +- 2 files changed, 71 insertions(+), 125 deletions(-) diff --git a/app/Http/Controllers/PreviewPurchaseOrderController.php b/app/Http/Controllers/PreviewPurchaseOrderController.php index 05ec469613..35c4722036 100644 --- a/app/Http/Controllers/PreviewPurchaseOrderController.php +++ b/app/Http/Controllers/PreviewPurchaseOrderController.php @@ -87,18 +87,17 @@ class PreviewPurchaseOrderController extends BaseController */ public function show(ShowPreviewRequest $request) { - if (request()->has('entity') && - request()->has('entity_id') && - ! empty(request()->input('entity')) && - ! empty(request()->input('entity_id')) && - request()->has('body')) { - $design_object = json_decode(json_encode(request()->input('design'))); + if ($request->input('entity', false) && + $request->input('entity_id', false) != '-1' && + $request->has('body')) { - if (! is_object($design_object)) { + $design_object = json_decode(json_encode($request->input('design')), true); + + if (! is_array($design_object)) { return response()->json(['message' => ctrans('texts.invalid_design_object')], 400); } - $entity_obj = PurchaseOrder::query()->whereId($this->decodePrimaryKey(request()->input('entity_id')))->company()->first(); + $entity_obj = PurchaseOrder::query()->whereId($this->decodePrimaryKey($request->input('entity_id')))->company()->first(); if (! $entity_obj) { return $this->blankEntity(); @@ -109,95 +108,36 @@ class PreviewPurchaseOrderController extends BaseController App::setLocale($entity_obj->company->locale()); $t->replace(Ninja::transformTranslations($entity_obj->company->settings)); + $invitation = $entity_obj->invitations()->first(); + + $ps = new PdfService($invitation, 'product', [ + 'client' => $entity_obj->client ?? false, + 'vendor' => $entity_obj->vendor ?? false, + $request->input('entity')."s" => [$entity_obj], + ]); + + $ps->boot() + ->designer + ->buildFromPartials($request->design['design']); + + $ps->builder + ->build(); + + if ($request->query('html') == 'true') { + return $ps->getHtml(); + } + + $pdf = $ps->getPdf(); + + return response()->streamDownload(function () use ($pdf) { + echo $pdf; + }, 'preview.pdf', [ + 'Content-Disposition' => 'inline', + 'Content-Type' => 'application/pdf', + 'Cache-Control:' => 'no-cache', + ]); -$invitation = $entity_obj->invitations()->first(); - -$ps = new PdfService($invitation, 'product', [ - 'client' => $entity_obj->client ?? false, - 'vendor' => $entity_obj->vendor ?? false, - $request->input('entity')."s" => [$entity_obj], -]); - -$ps->boot() -->designer -->buildFromPartials($request->design['design']); - -$ps->builder -->build(); - -if ($request->query('html') == 'true') { - return $ps->getHtml(); -} - -$pdf = $ps->getPdf(); - -return response()->streamDownload(function () use ($pdf) { - echo $pdf; -}, 'preview.pdf', [ - 'Content-Disposition' => 'inline', - 'Content-Type' => 'application/pdf', - 'Cache-Control:' => 'no-cache', -]); - - - // $html = new VendorHtmlEngine($entity_obj->invitations()->first()); - - // $design_namespace = 'App\Services\PdfMaker\Designs\\'.request()->design['name']; - - // $design_class = new $design_namespace(); - - // $state = [ - // 'template' => $design_class->elements([ - // 'client' => null, - // 'vendor' => $entity_obj->vendor, - // 'entity' => $entity_obj, - // 'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables, - // 'variables' => $html->generateLabelsAndValues(), - // ]), - // 'variables' => $html->generateLabelsAndValues(), - // 'process_markdown' => $entity_obj->company->markdown_enabled, - // 'options' => [ - // 'vendor' => $entity_obj->vendor ?? [], - // request()->input('entity')."s" => [$entity_obj], - // ] - // ]; - - // $design = new Design(request()->design['name']); - // $maker = new PdfMaker($state); - - // $maker - // ->design($design) - // ->build(); - - // if (request()->query('html') == 'true') { - // return $maker->getCompiledHTML(); - // } - - // //if phantom js...... inject here.. - // if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - // return (new Phantom())->convertHtmlToPdf($maker->getCompiledHTML(true)); - // } - - // /** @var \App\Models\User $user */ - // $user = auth()->user(); - - // if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - // $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - - // $numbered_pdf = $this->pageNumbering($pdf, $user->company()); - - // if ($numbered_pdf) { - // $pdf = $numbered_pdf; - // } - - // return $pdf; - // } - - // //else - // $file_path = (new PreviewPdf($maker->getCompiledHTML(true), $user->company()))->handle(); - - // return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true); } return $this->blankEntity(); @@ -270,40 +210,40 @@ return response()->streamDownload(function () use ($pdf) { return $this->mockEntity(); } - -$design_object = json_decode(json_encode(request()->input('design')), true); + + $design_object = json_decode(json_encode(request()->input('design')), true); -if (! is_array($design_object)) { - return response()->json(['message' => 'Invalid custom design object'], 400); -} + if (! is_array($design_object)) { + return response()->json(['message' => 'Invalid custom design object'], 400); + } -$ps = new PdfService($invitation, 'product', [ - 'client' => $invitation->client ?? false, - 'vendor' => $invitation->vendor ?? false, - "{$entity_string}s" => [$invitation->{$entity_string}], -]); + $ps = new PdfService($invitation, 'product', [ + 'client' => $invitation->client ?? false, + 'vendor' => $invitation->vendor ?? false, + "{$entity_string}s" => [$invitation->{$entity_string}], + ]); -$ps->boot() -->designer -->buildFromPartials($design_object['design']); + $ps->boot() + ->designer + ->buildFromPartials($design_object['design']); -$ps->builder -->build(); + $ps->builder + ->build(); -if (request()->query('html') == 'true') { - return $ps->getHtml(); -} + if (request()->query('html') == 'true') { + return $ps->getHtml(); + } -$pdf = $ps->getPdf(); + $pdf = $ps->getPdf(); -return response()->streamDownload(function () use ($pdf) { - echo $pdf; -}, 'preview.pdf', [ - 'Content-Disposition' => 'inline', - 'Content-Type' => 'application/pdf', - 'Cache-Control:' => 'no-cache', -]); + return response()->streamDownload(function () use ($pdf) { + echo $pdf; + }, 'preview.pdf', [ + 'Content-Disposition' => 'inline', + 'Content-Type' => 'application/pdf', + 'Cache-Control:' => 'no-cache', + ]); } diff --git a/app/Http/Requests/Preview/PreviewPurchaseOrderRequest.php b/app/Http/Requests/Preview/PreviewPurchaseOrderRequest.php index 4d11f21758..ad1a31ec44 100644 --- a/app/Http/Requests/Preview/PreviewPurchaseOrderRequest.php +++ b/app/Http/Requests/Preview/PreviewPurchaseOrderRequest.php @@ -11,12 +11,13 @@ namespace App\Http\Requests\Preview; -use App\Http\Requests\Request; -use App\Models\PurchaseOrder; -use App\Models\PurchaseOrderInvitation; use App\Models\Vendor; -use App\Utils\Traits\CleanLineItems; +use App\Models\PurchaseOrder; +use App\Http\Requests\Request; use App\Utils\Traits\MakesHash; +use Illuminate\Validation\Rule; +use App\Utils\Traits\CleanLineItems; +use App\Models\PurchaseOrderInvitation; class PreviewPurchaseOrderRequest extends Request { @@ -40,9 +41,14 @@ class PreviewPurchaseOrderRequest extends Request public function rules() { + + /** @var \App\Models\User $user */ + $user = auth()->user(); + $rules = []; $rules['number'] = ['nullable']; + $rules['vendor_id'] = ['required', Rule::exists(Vendor::class, 'id')->where('is_deleted', 0)->where('company_id', $user->company()->id)]; return $rules; } From 4c77278b1dfdc2e12b6814e59e09013686dd3a1a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 28 Jan 2025 22:45:30 +1100 Subject: [PATCH 07/43] Remove redundant pdf generator --- .../PreviewPurchaseOrderController.php | 2 - app/Jobs/Vendor/CreatePurchaseOrderPdf.php | 218 ------------------ app/Repositories/ActivityRepository.php | 138 +++-------- app/Services/Pdf/PdfService.php | 1 - 4 files changed, 36 insertions(+), 323 deletions(-) delete mode 100644 app/Jobs/Vendor/CreatePurchaseOrderPdf.php diff --git a/app/Http/Controllers/PreviewPurchaseOrderController.php b/app/Http/Controllers/PreviewPurchaseOrderController.php index 35c4722036..4c0323ba48 100644 --- a/app/Http/Controllers/PreviewPurchaseOrderController.php +++ b/app/Http/Controllers/PreviewPurchaseOrderController.php @@ -37,8 +37,6 @@ use Illuminate\Support\Facades\Response; use App\DataMapper\Analytics\LivePreview; use App\Repositories\PurchaseOrderRepository; use App\Http\Requests\Preview\ShowPreviewRequest; -use App\Services\PdfMaker\Design as PdfDesignModel; -use App\Services\PdfMaker\Design as PdfMakerDesign; use App\Http\Requests\Preview\PreviewPurchaseOrderRequest; class PreviewPurchaseOrderController extends BaseController diff --git a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php b/app/Jobs/Vendor/CreatePurchaseOrderPdf.php deleted file mode 100644 index d40765b560..0000000000 --- a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php +++ /dev/null @@ -1,218 +0,0 @@ -invitation = $invitation; - $this->company = $invitation->company; - - $this->entity = $invitation->purchase_order; - $this->entity_string = 'purchase_order'; - - $this->contact = $invitation->contact; - - $this->vendor = $invitation->contact->vendor; - $this->vendor->load('company'); - - $this->disk = $disk ?? config('filesystems.default'); - } - - public function handle() - { - /** Testing this override to improve PDF generation performance */ - $ps = new PdfService($this->invitation, 'product', [ - 'client' => $this->entity->client ?? false, - 'vendor' => $this->entity->vendor ?? false, - "{$this->entity_string}s" => [$this->entity], - ]); - - nlog("returning purchase order"); - - return $ps->boot()->getPdf(); - - } - - public function rawPdf() - { - MultiDB::setDb($this->company->db); - - /* Forget the singleton*/ - App::forgetInstance('translator'); - - /* Init a new copy of the translator*/ - $t = app('translator'); - /* Set the locale*/ - App::setLocale($this->vendor->locale()); - - /* Set customized translations _NOW_ */ - $t->replace(Ninja::transformTranslations($this->company->settings)); - - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom())->generate($this->invitation, true); - } - - $entity_design_id = ''; - - $this->path = $this->vendor->purchase_order_filepath($this->invitation); - $entity_design_id = 'purchase_order_design_id'; - - $this->file_path = $this->path.$this->entity->numberFormatter().'.pdf'; - - $entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey('Wpmbk5ezJn'); - - $design = Design::withTrashed()->find($entity_design_id); - - /* Catch all in case migration doesn't pass back a valid design */ - if (!$design) { - /** @var \App\Models\Design $design */ - $design = Design::find(2); - } - - $html = new VendorHtmlEngine($this->invitation); - - 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)); - } - - $variables = $html->generateLabelsAndValues(); - - $state = [ - 'template' => $template->elements([ - 'client' => null, - 'vendor' => $this->vendor, - 'entity' => $this->entity, - 'pdf_variables' => (array) $this->company->settings->pdf_variables, - '$product' => $design->design->product, - 'variables' => $variables, - ]), - 'variables' => $variables, - 'options' => [ - 'all_pages_header' => $this->entity->company->getSetting('all_pages_header'), - 'all_pages_footer' => $this->entity->company->getSetting('all_pages_footer'), - 'client' => null, - 'vendor' => $this->vendor, - 'entity' => $this->entity, - 'variables' => $variables, - ], - 'process_markdown' => $this->entity->company->markdown_enabled, - ]; - - $maker = new PdfMakerService($state); - - $maker - ->design($template) - ->build(); - - $pdf = null; - - try { - if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - - $numbered_pdf = $this->pageNumbering($pdf, $this->company); - - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - } else { - $pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true)); - - $numbered_pdf = $this->pageNumbering($pdf, $this->company); - - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - } - } catch (\Exception $e) { - nlog($e->getMessage()); - } - - if (config('ninja.log_pdf_html')) { - nlog($maker->getCompiledHTML()); - } - - $maker = null; - $state = null; - - return $pdf; - } - - public function failed($e) - { - } -} diff --git a/app/Repositories/ActivityRepository.php b/app/Repositories/ActivityRepository.php index 79c0e43974..a5c2edc040 100644 --- a/app/Repositories/ActivityRepository.php +++ b/app/Repositories/ActivityRepository.php @@ -11,23 +11,24 @@ namespace App\Repositories; -use App\Models\Activity; +use App\Models\User; +use App\Models\Quote; use App\Models\Backup; -use App\Models\CompanyToken; use App\Models\Credit; use App\Models\Design; use App\Models\Invoice; +use App\Models\Activity; +use App\Utils\HtmlEngine; +use App\Models\CompanyToken; use App\Models\PurchaseOrder; -use App\Models\Quote; +use App\Utils\Traits\MakesHash; +use App\Utils\VendorHtmlEngine; use App\Models\RecurringInvoice; -use App\Models\User; +use App\Services\Pdf\PdfService; +use App\Utils\Traits\MakesInvoiceHtml; use App\Services\PdfMaker\Design as PdfDesignModel; use App\Services\PdfMaker\Design as PdfMakerDesign; use App\Services\PdfMaker\PdfMaker as PdfMakerService; -use App\Utils\HtmlEngine; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\MakesInvoiceHtml; -use App\Utils\VendorHtmlEngine; /** * Class for activity repository. @@ -111,7 +112,7 @@ class ActivityRepository extends BaseRepository $backup->json_backup = ''; $backup->save(); - $backup->storeRemotely($this->generateVendorHtml($entity), $entity->vendor); + $backup->storeRemotely($this->generateHtml($entity), $entity->vendor); return; @@ -132,79 +133,44 @@ class ActivityRepository extends BaseRepository return false; } - private function generateVendorHtml($entity) - { - $entity_design_id = $entity->design_id ? $entity->design_id : $this->decodePrimaryKey($entity->vendor->getSetting('purchase_order_design_id')); - - $design = Design::withTrashed()->find($entity_design_id); - - if (! $entity->invitations()->exists() || ! $design) { - return ''; - } - - $entity->load('vendor.company', 'invitations'); - - $html = new VendorHtmlEngine($entity->invitations->first()->load('purchase_order', 'contact')); - - 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)); - } - - $state = [ - 'template' => $template->elements([ - 'vendor' => $entity->vendor, - 'entity' => $entity, - 'pdf_variables' => (array) $entity->company->settings->pdf_variables, - '$product' => $design->design->product, - ]), - 'variables' => $html->generateLabelsAndValues(), - 'options' => [ - 'all_pages_header' => $entity->vendor->getSetting('all_pages_header'), - 'all_pages_footer' => $entity->vendor->getSetting('all_pages_footer'), - 'vendor' => $entity->vendor, - 'entity' => $entity, - ], - 'process_markdown' => $entity->vendor->company->markdown_enabled, - ]; - - $maker = new PdfMakerService($state); - - $html = $maker->design($template) - ->build() - ->getCompiledHTML(true); - - $maker = null; - $state = null; - - return $html; - - } - private function generateHtml($entity) { $entity_design_id = ''; $entity_type = ''; + $settings = $entity->client ? $entity->client->getMergedSettings() : $entity->vendor->getMergedSettings(); + if ($entity instanceof Invoice) { $entity_type = 'invoice'; $entity_design_id = 'invoice_design_id'; + $entity->load('client.company', 'invitations'); + $document_type = 'product'; } elseif ($entity instanceof RecurringInvoice) { $entity_type = 'recurring_invoice'; $entity_design_id = 'invoice_design_id'; + + $entity->load('client.company', 'invitations'); + $document_type = 'product'; } elseif ($entity instanceof Quote) { $entity_type = 'quote'; $entity_design_id = 'quote_design_id'; + + $entity->load('client.company', 'invitations'); + $document_type = 'product'; } elseif ($entity instanceof Credit) { - $entity_type = 'credit'; + $entity_type = 'product'; + + $entity->load('client.company', 'invitations'); $entity_design_id = 'credit_design_id'; + $document_type = 'credit'; + } elseif ($entity instanceof PurchaseOrder) { + $entity_type = 'purchase_order'; + $entity_design_id = 'purchase_order_design_id'; + $document_type = 'purchase_order'; + $entity->load('vendor.company', 'invitations'); } - $entity_design_id = $entity->design_id ? $entity->design_id : $this->decodePrimaryKey($entity->client->getSetting($entity_design_id)); + $entity_design_id = $entity->design_id ? $entity->design_id : $this->decodePrimaryKey($settings->{$entity_design_id}); $design = Design::withTrashed()->find($entity_design_id); @@ -212,45 +178,13 @@ class ActivityRepository extends BaseRepository return ''; } - $entity->load('client.company', 'invitations'); + $ps = new PdfService($entity->invitations()->first(), $document_type, [ + 'client' => $entity->client ?? false, + 'vendor' => $entity->vendor ?? false, + "{$entity_type}s" => [$entity], + ]); - $html = new HtmlEngine($entity->invitations->first()->load($entity_type, 'contact')); + return $ps->boot()->getHtml(); - 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)); - } - - $state = [ - 'template' => $template->elements([ - 'client' => $entity->client, - 'entity' => $entity, - 'pdf_variables' => (array) $entity->company->settings->pdf_variables, - '$product' => $design->design->product, - ]), - 'variables' => $html->generateLabelsAndValues(), - 'options' => [ - 'all_pages_header' => $entity->client->getSetting('all_pages_header'), - 'all_pages_footer' => $entity->client->getSetting('all_pages_footer'), - 'client' => $entity->client, - 'entity' => $entity, - ], - 'process_markdown' => $entity->client->company->markdown_enabled, - ]; - - $maker = new PdfMakerService($state); - - $html = $maker->design($template) - ->build() - ->getCompiledHTML(true); - - $maker = null; - $state = null; - - return $html; } } diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index 3b23761149..3546c308ca 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -143,7 +143,6 @@ class PdfService $this->config = (new PdfConfiguration($this))->init(); - $this->html_variables = $this->config->client ? (new HtmlEngine($this->invitation))->generateLabelsAndValues() : (new VendorHtmlEngine($this->invitation))->generateLabelsAndValues(); From 09dffc6920d4d8ad4fb673a3afa94076081e2f87 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 28 Jan 2025 23:55:07 +1100 Subject: [PATCH 08/43] Remove redundant pdf generator --- app/Repositories/ActivityRepository.php | 3 -- app/Services/Client/Statement.php | 65 +++++++++---------------- app/Services/Pdf/PdfBuilder.php | 26 +++++++--- app/Services/Pdf/PdfConfiguration.php | 12 ----- 4 files changed, 41 insertions(+), 65 deletions(-) diff --git a/app/Repositories/ActivityRepository.php b/app/Repositories/ActivityRepository.php index a5c2edc040..a4cab0c8ab 100644 --- a/app/Repositories/ActivityRepository.php +++ b/app/Repositories/ActivityRepository.php @@ -26,9 +26,6 @@ use App\Utils\VendorHtmlEngine; use App\Models\RecurringInvoice; use App\Services\Pdf\PdfService; use App\Utils\Traits\MakesInvoiceHtml; -use App\Services\PdfMaker\Design as PdfDesignModel; -use App\Services\PdfMaker\Design as PdfMakerDesign; -use App\Services\PdfMaker\PdfMaker as PdfMakerService; /** * Class for activity repository. diff --git a/app/Services/Client/Statement.php b/app/Services/Client/Statement.php index 9fe51761bf..2c01f20bac 100644 --- a/app/Services/Client/Statement.php +++ b/app/Services/Client/Statement.php @@ -89,52 +89,33 @@ class Statement return $pdf; } - 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['values']['$show_paid_stamp'] = 'none'; //do not show paid stamp on statement - $state = [ - 'template' => $template->elements([ - 'client' => $this->client, - 'entity' => $this->entity, - 'pdf_variables' => (array) $this->entity->company->settings->pdf_variables, - '$product' => $this->getDesign()->design->product, - 'variables' => $variables, - 'invoices' => $this->getInvoices()->cursor(), - 'payments' => $this->getPayments()->cursor(), - 'credits' => $this->getCredits()->cursor(), - 'aging' => $this->getAging(), - 'unapplied' => $this->getUnapplied()->cursor(), - ], \App\Services\PdfMaker\Design::STATEMENT), + $options = [ + 'client' => $this->entity->client, + 'entity' => $this->entity, + 'pdf_variables' => (array) $this->entity->company->settings->pdf_variables, + '$product' => $this->getDesign()->design->product, 'variables' => $variables, - 'options' => [ - ], - 'process_markdown' => $this->entity->client->company->markdown_enabled, + 'invoices' => $this->getInvoices()->cursor(), + 'payments' => $this->getPayments()->cursor(), + 'credits' => $this->getCredits()->cursor(), + 'aging' => $this->getAging(), + 'unapplied' => $this->getUnapplied()->cursor() ]; - $maker = new PdfMaker($state); + $ps = new \App\Services\Pdf\PdfService($invitation, 'statement', array_merge($options, $this->options)); + $pdf = $ps->boot(); + + $ps->config->pdf_variables = (array) $this->entity->company->settings->pdf_variables; + $ps->config->html_variables = $variables; + $ps->config->design = $this->getDesign(); - $maker - ->design($template) - ->build(); + $ps->designer->buildFromPartials((array)$ps->config->design->design); + $ps->builder->build(); + $pdf = $ps->getPdf(); - $pdf = null; - $html = $maker->getCompiledHTML(true); - - // nlog($html); - - $pdf = $this->convertToPdf($html); - - $this->setVariables($variables); - - $maker = null; - $state = null; return $pdf; @@ -219,7 +200,7 @@ class Statement protected function setupEntity(): self { if ($this->getInvoices()->count() >= 1) { - $this->entity = $this->getInvoices()->first();//@phpstan-ignore-line + $this->entity = $this->getInvoices()->first(); //@phpstan-ignore-line } else { $this->entity = $this->client->invoices()->whereHas('invitations')->first(); @@ -235,7 +216,7 @@ class Statement $this->entity = \App\Models\Invoice::factory()->make(); //@phpstan-ignore-line $this->entity->client = \App\Models\Client::factory()->make(['settings' => $settings]); //@phpstan-ignore-line $this->entity->client->setRelation('company', $this->client->company); - $this->entity->invitation = \App\Models\InvoiceInvitation::factory()->make(); //@phpstan-ignore-line + $this->entity->setRelation('invitations', \App\Models\InvoiceInvitation::factory()->make()); //@phpstan-ignore-line $this->entity->setRelation('company', $this->client->company); $this->entity->setRelation('user', $this->client->user); @@ -540,8 +521,8 @@ class Statement { $id = 1; - if (! empty($this->client->getSetting('entity_design_id'))) { - $id = (int) $this->client->getSetting('entity_design_id'); + if (! empty($this->client->getSetting('statement_design_id'))) { + $id = (int) $this->client->getSetting('statement_design_id'); } return Design::withTrashed()->find($id); diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 48e37b1a08..9991f5a794 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -296,6 +296,10 @@ class PdfBuilder $this->genericSectionBuilder(); $this->mergeSections([ + 'client-details' => [ + 'id' => 'client-details', + 'elements' => $this->clientDetails(), + ], 'statement-invoice-table' => [ 'id' => 'statement-invoice-table', 'elements' => $this->statementInvoiceTable(), @@ -320,8 +324,8 @@ class PdfBuilder 'id' => 'statement-payment-table-totals', 'elements' => $this->statementPaymentTableTotals(), ], - 'statement-credits-table' => [ - 'id' => 'statement-credits-table', + 'statement-credit-table' => [ + 'id' => 'statement-credit-table', 'elements' => $this->statementCreditTable(), ], 'statement-credit-table-totals' => [ @@ -385,7 +389,7 @@ class PdfBuilder } return [ - ['element' => 'thead', 'elements' => $this->buildTableHeader('statement_invoice')], + ['element' => 'thead', 'elements' => $this->buildTableHeader('statement_credit')], ['element' => 'tbody', 'elements' => $tbody], ]; @@ -400,6 +404,10 @@ class PdfBuilder public function statementCreditTableTotals(): array { $outstanding = $this->service->options['credits']->sum('balance'); + + if (\array_key_exists('show_credits_table', $this->service->options) && $this->service->options['show_credits_table'] === false) { + return []; + } return [ ['element' => 'div', 'content' => '$credit.balance_label: ' . $this->service->config->formatMoney($outstanding)], @@ -501,8 +509,8 @@ class PdfBuilder return [ ['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), $this->service->config->formatMoney($this->payment_amount_total))], - ['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.payment_method'), $payment->translatedType())], - ['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.payment_date'), $this->translateDate($payment->date, $this->service->config->date_format, $this->service->config->locale) ?: ' ')], + // ['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.payment_type'), $payment->translatedType())], + // ['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.payment_date'), $this->translateDate($payment->date, $this->service->config->date_format, $this->service->config->locale) ?: ' ')], ]; } @@ -520,9 +528,9 @@ class PdfBuilder $payment = $this->service->options['unapplied']->first(); return [ - ['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.payment_balance'), $this->service->config->formatMoney($this->unapplied_total))], - ['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.payment_method'), $payment->translatedType())], - ['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.payment_date'), $this->translateDate($payment->date, $this->service->config->date_format, $this->service->config->locale) ?: ' ')], + ['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.payment_balance_on_file'), $this->service->config->formatMoney($this->unapplied_total))], + // ['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.payment_type'), $payment->translatedType())], + // ['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.payment_date'), $this->translateDate($payment->date, $this->service->config->date_format, $this->service->config->locale) ?: ' ')], ]; } @@ -545,6 +553,8 @@ class PdfBuilder } $tbody = []; + + $this->unapplied_total = 0; //24-03-2022 show payments per invoice foreach ($this->service->options['unapplied'] as $unapplied_payment) { diff --git a/app/Services/Pdf/PdfConfiguration.php b/app/Services/Pdf/PdfConfiguration.php index 0dae12a97c..8ea7d84771 100644 --- a/app/Services/Pdf/PdfConfiguration.php +++ b/app/Services/Pdf/PdfConfiguration.php @@ -400,18 +400,6 @@ class PdfConfiguration $precision = strlen($parts[1]); } - /* 08-02-2023 special if block to render $0.5 to $0.50*/ - // if ($v < 1 && strlen($v) == 3) { - // $precision = 2; - // } elseif ($v < 1) { - // $precision = strlen($v) - strrpos($v, '.') - 1; - // } elseif ($v > 1) { - // $precision = strlen($v) - strrpos($v, '.') - 1; - // } - // if (is_array($parts) && $parts[0] != 0) { - // $precision = 2; - // } - //04-04-2023 if currency = JPY override precision to 0 if ($this->currency->code == 'JPY') { $precision = 0; From b607493b94b1344a92d7ff8087e081655b4ece51 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 29 Jan 2025 14:26:15 +1100 Subject: [PATCH 09/43] Tweak values for show/hide shipping --- app/Services/Client/Statement.php | 3 - app/Services/Invoice/GenerateDeliveryNote.php | 94 ++++++++++--------- app/Services/Pdf/PdfBuilder.php | 7 +- 3 files changed, 56 insertions(+), 48 deletions(-) diff --git a/app/Services/Client/Statement.php b/app/Services/Client/Statement.php index 2c01f20bac..a056e7294f 100644 --- a/app/Services/Client/Statement.php +++ b/app/Services/Client/Statement.php @@ -20,15 +20,12 @@ use App\Models\Credit; use App\Models\Design; use App\Models\Invoice; use App\Models\Payment; -use App\Services\PdfMaker\Design as PdfMakerDesign; -use App\Services\PdfMaker\PdfMaker; use App\Utils\HostedPDF\NinjaPdf; use App\Utils\HtmlEngine; use App\Utils\Number; use App\Utils\PhantomJS\Phantom; use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesHash; -use App\Utils\Traits\Pdf\PdfMaker as PdfMakerTrait; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Carbon; diff --git a/app/Services/Invoice/GenerateDeliveryNote.php b/app/Services/Invoice/GenerateDeliveryNote.php index 0c1cf2dc38..db373f6520 100644 --- a/app/Services/Invoice/GenerateDeliveryNote.php +++ b/app/Services/Invoice/GenerateDeliveryNote.php @@ -67,60 +67,66 @@ class GenerateDeliveryNote // return (new \App\Services\Pdf\PdfService($invitation, 'delivery_note'))->boot()->getPdf(); - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom())->generate($this->invoice->invitations->first()); - } + // if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { + // return (new Phantom())->generate($this->invoice->invitations->first()); + // } $design = Design::withTrashed()->find($design_id); - $html = new HtmlEngine($invitation); - if ($design->is_custom) { - $options = ['custom_partials' => json_decode(json_encode($design->design), true)]; - $template = new PdfMakerDesign(PdfMakerDesign::CUSTOM, $options); - } else { - $template = new PdfMakerDesign(strtolower($design->name)); - } - $variables = $html->generateLabelsAndValues(); - $variables['labels']['$entity_label'] = ctrans('texts.delivery_note'); - $variables['labels']['$invoice.date_label'] = ctrans('texts.date'); - $variables['labels']['$invoice.number_label'] = ctrans('texts.number'); + $ps = new \App\Services\Pdf\PdfService($invitation, 'delivery_note'); + return $ps->boot()->getPdf(); - $state = [ - 'template' => $template->elements([ - 'client' => $this->invoice->client, - 'entity' => $this->invoice, - 'pdf_variables' => (array) $this->invoice->company->settings->pdf_variables, - 'contact' => $this->contact, - ], 'delivery_note'), - 'variables' => $variables, - 'options' => [ - 'client' => $this->invoice->client, - 'entity' => $this->invoice, - 'contact' => $this->contact, - ], - 'process_markdown' => $this->invoice->client->company->markdown_enabled, - ]; - $maker = new PdfMakerService($state); - $maker - ->design($template) - ->build(); + // $html = new HtmlEngine($invitation); - 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()); - } + // if ($design->is_custom) { + // $options = ['custom_partials' => json_decode(json_encode($design->design), true)]; + // $template = new PdfMakerDesign(PdfMakerDesign::CUSTOM, $options); + // } else { + // $template = new PdfMakerDesign(strtolower($design->name)); + // } - if (config('ninja.log_pdf_html')) { - info($maker->getCompiledHTML()); - } + // $variables = $html->generateLabelsAndValues(); + // $variables['labels']['$entity_label'] = ctrans('texts.delivery_note'); + // $variables['labels']['$invoice.date_label'] = ctrans('texts.date'); + // $variables['labels']['$invoice.number_label'] = ctrans('texts.number'); - $maker = null; - $state = null; + // $state = [ + // 'template' => $template->elements([ + // 'client' => $this->invoice->client, + // 'entity' => $this->invoice, + // 'pdf_variables' => (array) $this->invoice->company->settings->pdf_variables, + // 'contact' => $this->contact, + // ], 'delivery_note'), + // 'variables' => $variables, + // 'options' => [ + // 'client' => $this->invoice->client, + // 'entity' => $this->invoice, + // 'contact' => $this->contact, + // ], + // 'process_markdown' => $this->invoice->client->company->markdown_enabled, + // ]; - return $pdf; + // $maker = new PdfMakerService($state); + // $maker + // ->design($template) + // ->build(); + + // 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()); + // } + + // if (config('ninja.log_pdf_html')) { + // info($maker->getCompiledHTML()); + // } + + // $maker = null; + // $state = null; + + // return $pdf; } } diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 9991f5a794..0d992a08af 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -1770,6 +1770,11 @@ class PdfBuilder return $elements; } + + $this->service->html_variables['values']['$show_shipping_address_block'] = 'none'; + $this->service->html_variables['values']['$show_shipping_address'] = 'none'; + $this->service->html_variables['values']['$show_shipping_address_visibility'] = 'hidden'; + $elements = [ ['element' => 'div', 'content' => ctrans('texts.delivery_note'), 'properties' => ['data-ref' => 'delivery_note-label', 'style' => 'font-weight: bold; text-transform: uppercase']], ['element' => 'div', 'content' => $this->service->config->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.name']], @@ -1816,7 +1821,7 @@ class PdfBuilder { $elements = []; - if (!$this->service->config->client) { + if (!$this->service->config->client || $this->service->document_type == PdfService::DELIVERY_NOTE) { return $elements; } From 7aa75f62563d629dc94ebf7d56409131af7b7447 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 29 Jan 2025 15:28:19 +1100 Subject: [PATCH 10/43] Move design extractor into its own class --- app/Console/Commands/DesignUpdate.php | 6 +- app/Services/Invoice/GenerateDeliveryNote.php | 67 +--- app/Services/Pdf/DesignExtractor.php | 86 +++++ app/Services/Pdf/PdfService.php | 2 +- app/Services/PdfMaker/Design.php | 1 + app/Services/PdfMaker/PdfMakerUtilities.php | 1 + app/Utils/PhantomJS/Phantom.php | 194 ----------- database/seeders/DesignSeeder.php | 7 +- routes/client.php | 19 -- tests/Feature/PdfMaker/PdfMakerTest.php | 319 ------------------ tests/Feature/Template/TemplateTest.php | 79 ----- tests/Integration/HtmlGenerationTest.php | 51 --- 12 files changed, 98 insertions(+), 734 deletions(-) create mode 100644 app/Services/Pdf/DesignExtractor.php delete mode 100644 tests/Feature/PdfMaker/PdfMakerTest.php diff --git a/app/Console/Commands/DesignUpdate.php b/app/Console/Commands/DesignUpdate.php index 623364aea8..e582cc1822 100644 --- a/app/Console/Commands/DesignUpdate.php +++ b/app/Console/Commands/DesignUpdate.php @@ -69,9 +69,9 @@ class DesignUpdate extends Command private function handleOnDb() { - foreach (Design::whereIsCustom(false)->get() as $design) { - $invoice_design = new \App\Services\PdfMaker\Design(strtolower($design->name)); - $invoice_design->document(); + foreach (Design::where('is_custom', false)->get() as $design) { + + $invoice_design = new \App\Services\Pdf\DesignExtractor($design->name); $design_object = new stdClass(); $design_object->includes = $invoice_design->getSectionHTML('style'); diff --git a/app/Services/Invoice/GenerateDeliveryNote.php b/app/Services/Invoice/GenerateDeliveryNote.php index db373f6520..ea7eef0d99 100644 --- a/app/Services/Invoice/GenerateDeliveryNote.php +++ b/app/Services/Invoice/GenerateDeliveryNote.php @@ -12,18 +12,14 @@ namespace App\Services\Invoice; -use App\Models\ClientContact; use App\Models\Design; use App\Models\Invoice; -use App\Services\PdfMaker\Design as PdfMakerDesign; -use App\Services\PdfMaker\PdfMaker as PdfMakerService; -use App\Services\Template\TemplateService; -use App\Utils\HostedPDF\NinjaPdf; use App\Utils\HtmlEngine; -use App\Utils\PhantomJS\Phantom; +use App\Models\ClientContact; use App\Utils\Traits\MakesHash; use App\Utils\Traits\Pdf\PdfMaker; use Illuminate\Support\Facades\Storage; +use App\Services\Template\TemplateService; class GenerateDeliveryNote { @@ -65,68 +61,11 @@ class GenerateDeliveryNote $invitation = $this->invoice->invitations->first(); - // return (new \App\Services\Pdf\PdfService($invitation, 'delivery_note'))->boot()->getPdf(); - - // if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - // return (new Phantom())->generate($this->invoice->invitations->first()); - // } - $design = Design::withTrashed()->find($design_id); - $ps = new \App\Services\Pdf\PdfService($invitation, 'delivery_note'); + return $ps->boot()->getPdf(); - - // $html = new HtmlEngine($invitation); - - // if ($design->is_custom) { - // $options = ['custom_partials' => json_decode(json_encode($design->design), true)]; - // $template = new PdfMakerDesign(PdfMakerDesign::CUSTOM, $options); - // } else { - // $template = new PdfMakerDesign(strtolower($design->name)); - // } - - // $variables = $html->generateLabelsAndValues(); - // $variables['labels']['$entity_label'] = ctrans('texts.delivery_note'); - // $variables['labels']['$invoice.date_label'] = ctrans('texts.date'); - // $variables['labels']['$invoice.number_label'] = ctrans('texts.number'); - - // $state = [ - // 'template' => $template->elements([ - // 'client' => $this->invoice->client, - // 'entity' => $this->invoice, - // 'pdf_variables' => (array) $this->invoice->company->settings->pdf_variables, - // 'contact' => $this->contact, - // ], 'delivery_note'), - // 'variables' => $variables, - // 'options' => [ - // 'client' => $this->invoice->client, - // 'entity' => $this->invoice, - // 'contact' => $this->contact, - // ], - // 'process_markdown' => $this->invoice->client->company->markdown_enabled, - // ]; - - // $maker = new PdfMakerService($state); - // $maker - // ->design($template) - // ->build(); - - // 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()); - // } - - // if (config('ninja.log_pdf_html')) { - // info($maker->getCompiledHTML()); - // } - - // $maker = null; - // $state = null; - - // return $pdf; - } } diff --git a/app/Services/Pdf/DesignExtractor.php b/app/Services/Pdf/DesignExtractor.php new file mode 100644 index 0000000000..56fb713594 --- /dev/null +++ b/app/Services/Pdf/DesignExtractor.php @@ -0,0 +1,86 @@ +design = $design : $this->design = "{$design}.html"; + } + + $this->options = $options; + } + + public function setHtml(string $html): self + { + $this->html = $html; + + return $this; + } + + private function getHtml(): string + { + nlog($this->design); + if($this->html) { + return $this->html; + } + + $this->html = file_get_contents( config('ninja.designs.base_path') . $this->design ); + + return $this->html; + } + + public function getSectionHTML(string $section, $id = true): ?string + { + + $document = new \DOMDocument(); + + $document->validateOnParse = true; + @$document->loadHTML($this->getHtml()); + + if ($id) { + $element = $document->getElementById($section); + } else { + $elements = $document->getElementsByTagName($section); + $element = $elements[0]; + } + + if ($element) { + + $_document = new \DOMDocument(); + $_document->preserveWhiteSpace = false; + $_document->formatOutput = true; + + $_document->appendChild( + $_document->importNode($element, true) + ); + + $html = $_document->saveHTML(); + + return str_replace('%24', '$', $html); + } + + return ''; + + } +} diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index 3546c308ca..68ea3ad316 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -92,7 +92,7 @@ class PdfService try { $html = $this->getHtml(); - // nlog($html); + $pdf = $this->resolvePdfEngine($html); $numbered_pdf = $this->pageNumbering($pdf, $this->company); diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index 4dce8a21cd..3b98ad1d6e 100644 --- a/app/Services/PdfMaker/Design.php +++ b/app/Services/PdfMaker/Design.php @@ -22,6 +22,7 @@ use App\Utils\Traits\MakesInvoiceValues; use DOMDocument; use Illuminate\Support\Str; +/** @deprecated */ class Design extends BaseDesign { use MakesInvoiceValues; diff --git a/app/Services/PdfMaker/PdfMakerUtilities.php b/app/Services/PdfMaker/PdfMakerUtilities.php index be3b7b62e3..4c6750bf88 100644 --- a/app/Services/PdfMaker/PdfMakerUtilities.php +++ b/app/Services/PdfMaker/PdfMakerUtilities.php @@ -15,6 +15,7 @@ namespace App\Services\PdfMaker; use DOMDocument; use DOMXPath; +/** @deprecated */ trait PdfMakerUtilities { private function initializeDomDocument() diff --git a/app/Utils/PhantomJS/Phantom.php b/app/Utils/PhantomJS/Phantom.php index 0f1483ba85..b404260905 100644 --- a/app/Utils/PhantomJS/Phantom.php +++ b/app/Utils/PhantomJS/Phantom.php @@ -11,114 +11,11 @@ namespace App\Utils\PhantomJS; -use App\Exceptions\PhantomPDFFailure; -use App\Jobs\Util\SystemLogger; -use App\Models\CreditInvitation; -use App\Models\Design; -use App\Models\InvoiceInvitation; -use App\Models\QuoteInvitation; -use App\Models\RecurringInvoiceInvitation; -use App\Models\SystemLog; -use App\Services\PdfMaker\Design as PdfDesignModel; -use App\Services\PdfMaker\Design as PdfMakerDesign; -use App\Services\PdfMaker\PdfMaker as PdfMakerService; use App\Utils\CurlUtils; -use App\Utils\HtmlEngine; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\Pdf\PageNumbering; -use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Response; -use Illuminate\Support\Facades\Storage; -use Illuminate\Support\Str; class Phantom { - use MakesHash; - use PageNumbering; - - /** - * Generate a PDF from the - * Phantom JS API. - * - * @param $invitation - */ - public function generate($invitation, $return_pdf = false) - { - $entity = false; - $path = ''; - - if ($invitation instanceof InvoiceInvitation) { - $entity = 'invoice'; - $entity_design_id = 'invoice_design_id'; - } elseif ($invitation instanceof CreditInvitation) { - $entity = 'credit'; - $entity_design_id = 'credit_design_id'; - } elseif ($invitation instanceof QuoteInvitation) { - $entity = 'quote'; - $entity_design_id = 'quote_design_id'; - } elseif ($invitation instanceof RecurringInvoiceInvitation) { - $entity = 'recurring_invoice'; - $entity_design_id = 'invoice_design_id'; - } - - $entity_obj = $invitation->{$entity}; - - if ($entity == 'invoice') { - $path = $entity_obj->client->invoice_filepath($invitation); - } - - if ($entity == 'quote') { - $path = $entity_obj->client->quote_filepath($invitation); - } - - if ($entity == 'credit') { - $path = $entity_obj->client->credit_filepath($invitation); - } - - if ($entity == 'recurring_invoice') { - $path = $entity_obj->client->recurring_invoice_filepath($invitation); - } - - $file_path = $path.$entity_obj->numberFormatter().'.pdf'; - - $url = config('ninja.app_url').'/phantom/'.$entity.'/'.$invitation->key.'?phantomjs_secret='.config('ninja.phantomjs_secret'); - info($url); - - $key = config('ninja.phantomjs_key'); - $phantom_url = "https://phantomjscloud.com/api/browser/v2/{$key}/"; - $pdf = CurlUtils::post($phantom_url, json_encode([ - 'url' => $url, - 'renderType' => 'pdf', - 'outputAsJson' => false, - 'renderSettings' => [ - 'emulateMedia' => 'print', - 'pdfOptions' => [ - 'preferCSSPageSize' => true, - 'printBackground' => true, - ], - ], - ])); - - $this->checkMime($pdf, $invitation, $entity); - - $numbered_pdf = $this->pageNumbering($pdf, $invitation->company); - - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - - if (! Storage::disk(config('filesystems.default'))->exists($path)) { - Storage::disk(config('filesystems.default'))->makeDirectory($path); - } - - $instance = Storage::disk(config('filesystems.default'))->put($file_path, $pdf); - - if ($return_pdf) { - return $pdf; - } - - return $file_path; - } public function convertHtmlToPdf($html) { @@ -143,95 +40,4 @@ class Phantom return $response; } - /* Check if the returning PDF is valid. */ - private function checkMime($pdf, $invitation, $entity) - { - $finfo = new \finfo(FILEINFO_MIME); - - if ($finfo->buffer($pdf) != 'application/pdf; charset=binary') { - SystemLogger::dispatch( - $pdf, - SystemLog::CATEGORY_PDF, - SystemLog::EVENT_PDF_RESPONSE, - SystemLog::TYPE_PDF_FAILURE, - $invitation->contact->client, - $invitation->company, - ); - - throw new PhantomPDFFailure('There was an error generating the PDF with Phantom JS'); - } else { - SystemLogger::dispatch( - 'Entity PDF generated sucessfully => '.$invitation->{$entity}->number, - SystemLog::CATEGORY_PDF, - SystemLog::EVENT_PDF_RESPONSE, - SystemLog::TYPE_PDF_SUCCESS, - $invitation->contact->client, - $invitation->company, - ); - } - } - - public function displayInvitation(string $entity, string $invitation_key) - { - $key = $entity.'_id'; - - $invitation_instance = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation'; - $invitation = $invitation_instance::where('key', $invitation_key)->first(); - - $entity_obj = $invitation->{$entity}; - - $entity_obj->load('client'); - - App::setLocale($invitation->contact->preferredLocale()); - - $entity_design_id = $entity.'_design_id'; - - if ($entity == 'recurring_invoice') { - $entity_design_id = 'invoice_design_id'; - } - - $design_id = $entity_obj->design_id ? $entity_obj->design_id : $this->decodePrimaryKey($entity_obj->client->getSetting($entity_design_id)); - - $design = Design::withTrashed()->find($design_id); - $html = new HtmlEngine($invitation); - - 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)); - } - - $state = [ - 'template' => $template->elements([ - 'client' => $entity_obj->client, - 'entity' => $entity_obj, - 'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables, - '$product' => $design->design->product, - ]), - 'variables' => $html->generateLabelsAndValues(), - 'options' => [ - 'all_pages_header' => $entity_obj->client->getSetting('all_pages_header'), - 'all_pages_footer' => $entity_obj->client->getSetting('all_pages_footer'), - 'client' => $entity_obj->client, - 'entity' => $entity_obj, - ], - 'process_markdown' => $entity_obj->client->company->markdown_enabled, - ]; - - $maker = new PdfMakerService($state); - - $data['html'] = $maker->design($template) - ->build() - ->getCompiledHTML(true); - - if (config('ninja.log_pdf_html')) { - info($data['html']); - } - - return view('pdf.html', $data); - } - } diff --git a/database/seeders/DesignSeeder.php b/database/seeders/DesignSeeder.php index f1a1fd0ede..9e3580a8eb 100644 --- a/database/seeders/DesignSeeder.php +++ b/database/seeders/DesignSeeder.php @@ -12,7 +12,6 @@ namespace Database\Seeders; use App\Models\Design; -use App\Services\PdfMaker\Design as PdfMakerDesign; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Seeder; @@ -49,9 +48,9 @@ class DesignSeeder extends Seeder } } - foreach (Design::all() as $design) { - $template = new PdfMakerDesign(strtolower($design->name)); - $template->document(); + foreach (Design::where('is_custom', false)->get() as $design) { + + $template = new \App\Services\Pdf\DesignExtractor($design->name); $design_object = new \stdClass; $design_object->includes = $template->getSectionHTML('style'); diff --git a/routes/client.php b/routes/client.php index 72a2e16fca..a7d5c42f52 100644 --- a/routes/client.php +++ b/routes/client.php @@ -15,7 +15,6 @@ use App\Http\Controllers\QuoteController; use App\Http\Controllers\RecurringInvoiceController; use App\Models\Account; use App\Utils\Ninja; -use App\Utils\PhantomJS\Phantom; use Illuminate\Support\Facades\Route; Route::get('client', [ContactLoginController::class, 'showLoginForm'])->name('client.catchall')->middleware(['domain_db', 'contact_account','locale', 'throttle:portal']); //catch all @@ -146,24 +145,6 @@ Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'clie Route::get('unsubscribe/{entity}/{invitation_key}', [App\Http\Controllers\ClientPortal\InvitationController::class, 'unsubscribe'])->name('unsubscribe'); }); -// Route::get('route/{hash}', function ($hash) { - -// $route = '/'; - -// try { -// $route = decrypt($hash); -// } -// catch (\Exception $e) { -// abort(404); -// } - -// return redirect($route); - -// })->middleware('throttle:404'); - -Route::get('phantom/{entity}/{invitation_key}', [Phantom::class, 'displayInvitation'])->middleware(['invite_db', 'phantom_secret'])->name('phantom_view'); -Route::get('blade/', [Phantom::class, 'blade'])->name('blade'); - Route::get('.env', function () { })->middleware('throttle:honeypot'); diff --git a/tests/Feature/PdfMaker/PdfMakerTest.php b/tests/Feature/PdfMaker/PdfMakerTest.php deleted file mode 100644 index 786976efb4..0000000000 --- a/tests/Feature/PdfMaker/PdfMakerTest.php +++ /dev/null @@ -1,319 +0,0 @@ - [], - 'variables' => [ - 'labels' => [], - 'values' => [], - ], - ]; - - public function testDesignLoadsCorrectly() - { - $design = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]); - $maker = new PdfMaker($this->state); - - $maker->design($design); - - $this->assertInstanceOf(Design::class, $maker->design); - } - - public function testHtmlDesignLoadsCorrectly() - { - $design = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]); - - $maker = new PdfMaker($this->state); - - $maker - ->design($design) - ->build(); - - $this->assertStringContainsString('Template: Example', $maker->getCompiledHTML()); - } - - public function testGetSectionUtility() - { - $design = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]); - - $maker = new PdfMaker($this->state); - - $maker - ->design($design) - ->build(); - - $this->assertEquals('table', $maker->getSectionNode('product-table')->nodeName); - } - - public function testTableAttributesAreInjected() - { - $state = [ - 'template' => [ - 'product-table' => [ - 'id' => 'product-table', - 'properties' => [ - 'class' => 'my-awesome-class', - 'style' => 'margin-top: 10px;', - 'script' => '', - ], - ], - ], - 'variables' => [ - 'labels' => [], - 'values' => [], - ], - ]; - - $design = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]); - $maker = new PdfMaker($state); - - $maker - ->design($design) - ->build(); - - $this->assertStringContainsString('my-awesome-class', $maker->getSection('product-table', 'class')); - // $this->assertStringContainsString('margin-top: 10px', $maker->getSection('product-table', 'style')); - // $this->assertStringContainsString('console.log(1)', $maker->getSection('product-table', 'script')); - } - - public function testVariablesAreReplaced() - { - $state = [ - 'template' => [ - 'product-table' => [ - 'id' => 'product-table', - ], - ], - 'variables' => [ - 'labels' => [], - 'values' => [ - '$company.name' => 'Invoice Ninja', - ], - ], - ]; - - $design = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]); - $maker = new PdfMaker($state); - - $maker - ->design($design) - ->build(); - - $this->assertStringContainsString('Invoice Ninja', $maker->getCompiledHTML()); - $this->assertStringContainsString('Invoice Ninja', $maker->getSection('header')); - } - - public function testElementContentIsGenerated() - { - $state = [ - 'template' => [ - 'product-table' => [ - 'id' => 'product-table', - 'properties' => [], - 'elements' => [ - ['element' => 'thead', 'content' => '', 'elements' => [ - ['element' => 'th', 'content' => 'Company'], - ['element' => 'th', 'content' => 'Contact'], - ['element' => 'th', 'content' => 'Country', 'properties' => [ - 'colspan' => 3, - ]], - ]], - ['element' => 'tr', 'content' => '', 'elements' => [ - ['element' => 'td', 'content' => '$company'], - ['element' => 'td', 'content' => '$email'], - ['element' => 'td', 'content' => '$country', 'elements' => [ - ['element' => 'a', 'content' => 'Click here for a link', 'properties' => [ - 'href' => 'https://github.com/invoiceninja/invoiceninja', - ]], - ]], - ]], - ], - ], - ], - 'variables' => [ - 'labels' => [], - 'values' => [ - '$company' => 'Invoice Ninja', - '$email' => 'contact@invoiceninja.com', - '$country' => 'UK', - ], - ], - ]; - - $design = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]); - $maker = new PdfMaker($state); - - $maker - ->design($design) - ->build(); - - $compiled = 'contact@invoiceninja.com'; - - $this->assertStringContainsString($compiled, $maker->getCompiledHTML()); - } - - public function testConditionalRenderingOfElements() - { - $design1 = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]); - - $maker1 = new PdfMaker([ - 'template' => [ - 'header' => [ - 'id' => 'header', - 'properties' => [], - ], - ], - ]); - - $maker1 - ->design($design1) - ->build(); - - $output1 = $maker1->getCompiledHTML(); - - $this->assertStringContainsString('
-

$entity_issued_to_label:

+

$entity_issued_to_label

diff --git a/resources/views/pdf-designs/hipster.html b/resources/views/pdf-designs/hipster.html index e49b73fb28..ab85848036 100644 --- a/resources/views/pdf-designs/hipster.html +++ b/resources/views/pdf-designs/hipster.html @@ -129,6 +129,10 @@ text-transform: uppercase; } + .entity-property-label:not(:empty)::after { + content: ":"; + } + .entity-details-wrapper [data-element='entity-details-wrapper-invoice-number-label'], .entity-details-wrapper @@ -473,20 +477,20 @@
- $entity_number_label: + $entity_number_label $entity_number
- $date_label: + $date_label $date
- $payment_due_label: + $payment_due_label $payment_due
- $amount_due_label: + $amount_due_label makeTestData(); } + public function testMultiDesignGeneration() + { + + if (config('ninja.testvars.travis')) { + $this->markTestSkipped(); + } + + \App\Models\Design::where('is_custom',false)->cursor()->each(function ($design){ + + + $this->invoice->design_id = $design->id; + $this->invoice->save(); + $this->invoice = $this->invoice->fresh(); + + $invitation = $this->invoice->invitations->first(); + + $service = (new PdfService($invitation))->boot(); + $pdf = $service->getPdf(); + + $this->assertNotNull($pdf); + + \Illuminate\Support\Facades\Storage::put('/pdf/' . $design->name.'.pdf', $pdf); + + }); + + + \App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) { + + + $this->invoice->design_id = $design->id; + $this->invoice->save(); + $this->invoice = $this->invoice->fresh(); + + $invitation = $this->invoice->invitations->first(); + + $service = (new PdfService($invitation, 'delivery_note'))->boot(); + $pdf = $service->getPdf(); + + $this->assertNotNull($pdf); + + \Illuminate\Support\Facades\Storage::put('/pdf/dn_' . $design->name.'.pdf', $pdf); + + }); + + } + + public function testStatementPdfGeneration() + { + + $pdf = $this->client->service()->statement([ + 'client_id' => $this->client->hashed_id, + 'start_date' => '2000-01-01', + 'end_date' => '2023-01-01', + 'show_aging_table' => true, + 'show_payments_table' => true, + 'status' => 'all' + ]); + + + $this->assertNotNull($pdf); + + \Illuminate\Support\Facades\Storage::put('/pdf/statement.pdf', $pdf); + + + } + public function testPdfGeneration() { From a84e1278463f4bafcf192ceeda745f0304550435 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 29 Jan 2025 20:19:14 +1100 Subject: [PATCH 13/43] Clean up --- app/Services/Pdf/PdfBuilder.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index b70fc19a94..244d8fc116 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -1350,15 +1350,11 @@ class PdfBuilder $_variable = $aliases[$variable]; } - if (is_null($this->service->config->entity->{$_variable})) { + if (is_null($this->service->config->entity->{$_variable}) || empty($this->service->config->entity->{$_variable})) { return true; } - if (empty($this->service->config->entity->{$_variable})) { - return true; - } - - nlog("{$_variable} - {$this->service->config->entity->{$_variable}}"); + // nlog("{$_variable} - {$this->service->config->entity->{$_variable}}"); return false; } From a225ff14d88f27bbd6295990848611371226b402 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 30 Jan 2025 09:17:44 +1100 Subject: [PATCH 14/43] Updating designs --- app/Services/Pdf/PdfBuilder.php | 10 +++++----- app/Services/Pdf/PdfService.php | 2 +- resources/views/pdf-designs/hipster.html | 12 +++++++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 244d8fc116..9ad8137ffb 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -1757,6 +1757,7 @@ class PdfBuilder ['element' => 'div', 'content' => optional($this->service->config->client->shipping_country)->name, 'show_empty' => false], ]; + if (!is_null($this->service->config->contact)) { $elements[] = ['element' => 'div', 'content' => $this->service->config->contact->email, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-contact.email']]; } @@ -1796,15 +1797,14 @@ class PdfBuilder $elements = [ ['element' => 'div', 'content' => ctrans('texts.shipping_address'), 'properties' => ['data-ref' => 'shipping_address-label', 'style' => 'font-weight: bold; text-transform: uppercase']], - // ['element' => 'div', 'content' => $this->service->config->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.name']], ['element' => 'div', 'content' => $this->service->config->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address1']], ['element' => 'div', 'content' => $this->service->config->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address2']], ['element' => 'div', 'show_empty' => false, 'elements' => [ - ['element' => 'span', 'content' => "{$this->service->config->client->shipping_city} ", 'properties' => ['ref' => 'shipping_address-client.shipping_city']], - ['element' => 'span', 'content' => "{$this->service->config->client->shipping_state} ", 'properties' => ['ref' => 'shipping_address-client.shipping_state']], - ['element' => 'span', 'content' => "{$this->service->config->client->shipping_postal_code} ", 'properties' => ['ref' => 'shipping_address-client.shipping_postal_code']], + ['element' => 'span', 'content' => "{$this->service->config->client->shipping_city} ", 'properties' => ['data-ref' => 'shipping_address-client.shipping_city']], + ['element' => 'span', 'content' => "{$this->service->config->client->shipping_state} ", 'properties' => ['data-ref' => 'shipping_address-client.shipping_state']], + ['element' => 'span', 'content' => "{$this->service->config->client->shipping_postal_code} ", 'properties' => ['data-ref' => 'shipping_address-client.shipping_postal_code']], ]], - ['element' => 'div', 'content' => optional($this->service->config->client->shipping_country)->name, 'show_empty' => false], + ['element' => 'div', 'content' => optional($this->service->config->client->shipping_country)->name, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_country']], ]; return $elements; diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index 68ea3ad316..b37c47559c 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -92,7 +92,7 @@ class PdfService try { $html = $this->getHtml(); - + nlog($html); $pdf = $this->resolvePdfEngine($html); $numbered_pdf = $this->pageNumbering($pdf, $this->company); diff --git a/resources/views/pdf-designs/hipster.html b/resources/views/pdf-designs/hipster.html index ab85848036..6be7408940 100644 --- a/resources/views/pdf-designs/hipster.html +++ b/resources/views/pdf-designs/hipster.html @@ -71,24 +71,30 @@ border-left: 1px solid #303030; padding-left: 1rem; - grid-template-columns: auto auto auto; + grid-template-columns: 1fr auto auto; grid-template-areas: "a b c"; grid-auto-columns: minmax(0, 1fr); grid-auto-flow: column; - justify-content:left; - + justify-content: end; } .header-wrapper .header-right-side-wrapper #client-details { display: flex; flex-direction: column; margin-top: 0.8rem; + grid-area: a; } .header-wrapper .header-right-side-wrapper #shipping-details { display: $show_shipping_address; flex-direction: column; margin-top: 0.8rem; + grid-area: b; + } + + .header-wrapper .header-right-side-wrapper-right { + float:right; + grid-area: c; } .shipping-text-label { From ca003e8ae4200c35ba1020f43d91d57fa031d992 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 30 Jan 2025 11:56:29 +1100 Subject: [PATCH 15/43] Fixes for hipster designs --- app/Services/Pdf/PdfBuilder.php | 1 + resources/views/pdf-designs/hipster.html | 13 ++++++++++--- tests/Pdf/PdfServiceTest.php | 4 ++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 9ad8137ffb..7f0794b82e 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -1730,6 +1730,7 @@ class PdfBuilder return $elements; } + $this->service->html_variables['values']['$show_paid_stamp'] = 'none'; $this->service->html_variables['values']['$show_shipping_address_block'] = 'none'; $this->service->html_variables['values']['$show_shipping_address'] = 'none'; $this->service->html_variables['values']['$show_shipping_address_visibility'] = 'hidden'; diff --git a/resources/views/pdf-designs/hipster.html b/resources/views/pdf-designs/hipster.html index 6be7408940..9d07bc3947 100644 --- a/resources/views/pdf-designs/hipster.html +++ b/resources/views/pdf-designs/hipster.html @@ -71,7 +71,7 @@ border-left: 1px solid #303030; padding-left: 1rem; - grid-template-columns: 1fr auto auto; + grid-template-columns: auto auto auto; grid-template-areas: "a b c"; grid-auto-columns: minmax(0, 1fr); grid-auto-flow: column; @@ -86,12 +86,17 @@ } .header-wrapper .header-right-side-wrapper #shipping-details { - display: $show_shipping_address; + display: $show_shipping_address_block; flex-direction: column; margin-top: 0.8rem; grid-area: b; } + .header-wrapper .header-right-side-wrapper-left-shipping { + padding-left: 1rem; + border-left: 1px solid #303030; + } + .header-wrapper .header-right-side-wrapper-right { float:right; grid-area: c; @@ -111,6 +116,8 @@ .company-logo { max-width: $company_logo_size; + float:right; + object-fit: contain; } .entity-label { @@ -466,7 +473,7 @@
-
+

$shipping_label:

diff --git a/tests/Pdf/PdfServiceTest.php b/tests/Pdf/PdfServiceTest.php index 9f32b035d8..da47ca9e0c 100644 --- a/tests/Pdf/PdfServiceTest.php +++ b/tests/Pdf/PdfServiceTest.php @@ -25,6 +25,10 @@ class PdfServiceTest extends TestCase { use MockAccountData; + private string $max_pdf_variables = '{"client_details":["$client.name","$contact.full_name","$client.address1","$client.city_state_postal","$client.number","$client.vat_number","$client.postal_city_state","$client.website","$client.country","$client.custom3","$client.id_number","$client.phone","$client.address2","$client.custom1","$contact.custom1"],"vendor_details":["$vendor.name","$vendor.number","$vendor.vat_number","$vendor.address1","$vendor.address2","$vendor.city_state_postal","$vendor.country","$vendor.phone","$contact.email","$vendor.id_number","$vendor.website","$vendor.custom2","$vendor.custom1","$vendor.custom4","$vendor.custom3","$contact.phone","$contact.full_name","$contact.custom2","$contact.custom1"],"purchase_order_details":["$purchase_order.number","$purchase_order.date","$purchase_order.total","$purchase_order.balance_due","$purchase_order.due_date","$purchase_order.po_number","$purchase_order.custom1","$purchase_order.custom2","$purchase_order.custom3"],"company_details":["$company.name","$company.email","$company.phone","$company.id_number","$company.vat_number","$company.website","$company.address2","$company.address1","$company.city_state_postal","$company.postal_city_state","$company.custom1","$company.custom3"],"company_address":["$company.address1","$company.city_state_postal","$company.country","$company.id_number","$company.vat_number","$company.website","$company.email","$company.name","$company.custom1"],"invoice_details":["$invoice.number","$invoice.date","$invoice.balance","$invoice.custom1","$invoice.due_date","$invoice.project","$invoice.balance_due","$invoice.custom3","$invoice.po_number","$invoice.custom2","$invoice.amount","$invoice.custom4"],"quote_details":["$quote.number","$quote.custom1","$quote.po_number","$quote.date","$quote.valid_until","$quote.total","$quote.custom2","$quote.custom3","$quote.custom4"],"credit_details":["$credit.number","$credit.balance","$credit.po_number","$credit.date","$credit.valid_until","$credit.total","$credit.custom1","$credit.custom2","$credit.custom3"],"product_columns":["$product.item","$product.product1","$product.description","$product.product2","$product.tax","$product.line_total","$product.quantity","$product.unit_cost","$product.discount","$product.product3","$product.product4","$product.gross_line_total"],"product_quote_columns":["$product.item","$product.description","$product.unit_cost","$product.quantity","$product.discount","$product.tax","$product.line_total"],"task_columns":["$task.service","$task.description","$task.rate","$task.hours","$task.discount","$task.line_total","$task.tax","$task.tax_amount","$task.task2","$task.task1","$task.task3"],"total_columns":["$total","$line_taxes","$total_taxes","$discount","$custom_surcharge1","$outstanding","$net_subtotal","$custom_surcharge2","$custom_surcharge3","$subtotal","$paid_to_date"],"statement_invoice_columns":["$invoice.number","$invoice.date","$due_date","$total","$balance"],"statement_payment_columns":["$invoice.number","$payment.date","$method","$statement_amount"],"statement_credit_columns":["$credit.number","$credit.date","$total","$credit.balance"],"statement_details":["$statement_date","$balance"],"delivery_note_columns":["$product.item","$product.description","$product.quantity"],"statement_unapplied_columns":["$payment.number","$payment.date","$payment.amount","$payment.payment_balance"]}'; + + private string $min_pdf_variables = '{"client_details":["$client.name","$client.vat_number","$client.address1","$client.city_state_postal","$client.country"],"vendor_details":["$vendor.name","$vendor.vat_number","$vendor.address1","$vendor.city_state_postal","$vendor.country"],"purchase_order_details":["$purchase_order.number","$purchase_order.date","$purchase_order.total"],"company_details":["$company.name","$company.address1","$company.city_state_postal"],"company_address":["$company.name","$company.website"],"invoice_details":["$invoice.number","$invoice.date","$invoice.due_date","$invoice.balance"],"quote_details":["$quote.number","$quote.date","$quote.valid_until"],"credit_details":["$credit.date","$credit.number","$credit.balance"],"product_columns":["$product.item","$product.description","$product.line_total"],"product_quote_columns":["$product.item","$product.description","$product.unit_cost","$product.quantity","$product.discount","$product.tax","$product.line_total"],"task_columns":["$task.description","$task.rate","$task.line_total"],"total_columns":["$total","$total_taxes","$outstanding"],"statement_invoice_columns":["$invoice.number","$invoice.date","$due_date","$total","$balance"],"statement_payment_columns":["$invoice.number","$payment.date","$method","$statement_amount"],"statement_credit_columns":["$credit.number","$credit.date","$total","$credit.balance"],"statement_details":["$statement_date","$balance"],"delivery_note_columns":["$product.item","$product.description","$product.quantity"],"statement_unapplied_columns":["$payment.number","$payment.date","$payment.amount","$payment.payment_balance"]}'; + protected function setUp(): void { parent::setUp(); From 9285ae1a3d5d93935e4d9aa1b4d00025f7f24975 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 30 Jan 2025 14:44:04 +1100 Subject: [PATCH 16/43] Adjustments for designs --- app/Services/Pdf/PdfBuilder.php | 6 +-- app/Utils/PhantomJS/Phantom.php | 2 +- resources/views/pdf-designs/hipster.html | 2 +- resources/views/pdf-designs/modern.html | 8 +-- tests/Pdf/PdfServiceTest.php | 69 ++++++++++++++++++++++++ 5 files changed, 79 insertions(+), 8 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 7f0794b82e..e5b3651419 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -1801,9 +1801,9 @@ class PdfBuilder ['element' => 'div', 'content' => $this->service->config->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address1']], ['element' => 'div', 'content' => $this->service->config->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address2']], ['element' => 'div', 'show_empty' => false, 'elements' => [ - ['element' => 'span', 'content' => "{$this->service->config->client->shipping_city} ", 'properties' => ['data-ref' => 'shipping_address-client.shipping_city']], - ['element' => 'span', 'content' => "{$this->service->config->client->shipping_state} ", 'properties' => ['data-ref' => 'shipping_address-client.shipping_state']], - ['element' => 'span', 'content' => "{$this->service->config->client->shipping_postal_code} ", 'properties' => ['data-ref' => 'shipping_address-client.shipping_postal_code']], + ['element' => 'p', 'content' => "{$this->service->config->client->shipping_city} ", 'properties' => ['data-ref' => 'shipping_address-client.shipping_city']], + ['element' => 'p', 'content' => "{$this->service->config->client->shipping_state} ", 'properties' => ['data-ref' => 'shipping_address-client.shipping_state']], + ['element' => 'p', 'content' => "{$this->service->config->client->shipping_postal_code} ", 'properties' => ['data-ref' => 'shipping_address-client.shipping_postal_code']], ]], ['element' => 'div', 'content' => optional($this->service->config->client->shipping_country)->name, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_country']], ]; diff --git a/app/Utils/PhantomJS/Phantom.php b/app/Utils/PhantomJS/Phantom.php index b404260905..f7f1ec2f26 100644 --- a/app/Utils/PhantomJS/Phantom.php +++ b/app/Utils/PhantomJS/Phantom.php @@ -40,4 +40,4 @@ class Phantom return $response; } -} +} \ No newline at end of file diff --git a/resources/views/pdf-designs/hipster.html b/resources/views/pdf-designs/hipster.html index 9d07bc3947..9dd42fc39e 100644 --- a/resources/views/pdf-designs/hipster.html +++ b/resources/views/pdf-designs/hipster.html @@ -98,7 +98,7 @@ } .header-wrapper .header-right-side-wrapper-right { - float:right; + float:left; grid-area: c; } diff --git a/resources/views/pdf-designs/modern.html b/resources/views/pdf-designs/modern.html index dec2b45186..bb42f06086 100644 --- a/resources/views/pdf-designs/modern.html +++ b/resources/views/pdf-designs/modern.html @@ -85,7 +85,7 @@ .logo-client-wrapper { margin-left: 2rem; display: grid; - grid-template-columns: 1fr auto auto; + grid-template-columns: 2fr auto auto ; margin-top: 1rem; margin-bottom: 1rem; } @@ -98,7 +98,8 @@ display: flex; flex-direction: column; margin-left: 10px; - margin-right: 10px; + margin-right: 2rem; + float:right; } @@ -107,6 +108,7 @@ flex-direction: column; margin-left: 10px; margin-right: 10px; + float:right; } #client-details > * { @@ -439,9 +441,9 @@
+
-
diff --git a/tests/Pdf/PdfServiceTest.php b/tests/Pdf/PdfServiceTest.php index da47ca9e0c..e6c0cce635 100644 --- a/tests/Pdf/PdfServiceTest.php +++ b/tests/Pdf/PdfServiceTest.php @@ -82,6 +82,75 @@ class PdfServiceTest extends TestCase } + public function testMaxInvoiceFields() + { + $max_settings = json_decode($this->max_pdf_variables); + + $settings = $this->company->settings; + $settings->pdf_variables = $max_settings; + + $this->company->settings = $settings; + $this->company->save(); + + $this->invoice->company->settings->pdf_variables = $max_settings; + + \App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) use ($max_settings) { + + + $this->invoice->design_id = $design->id; + $this->invoice->save(); + $this->invoice = $this->invoice->fresh(); + + $invitation = $this->invoice->invitations->first(); + $invitation = $invitation->fresh(); + + $service = (new PdfService($invitation))->boot(); + $pdf = $service->getPdf(); + + $this->assertNotNull($pdf); + + \Illuminate\Support\Facades\Storage::put('/pdf/max_fields_' . $design->name.'.pdf', $pdf); + + }); + + + } + + public function testMinInvoiceFields() + { + $min_settings = json_decode($this->min_pdf_variables); + + $settings = $this->company->settings; + $settings->pdf_variables = $min_settings; + + $this->company->settings = $settings; + $this->company->save(); + + $this->invoice->company->settings->pdf_variables = $min_settings; + + \App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) use ($min_settings) { + + + $this->invoice->design_id = $design->id; + $this->invoice->save(); + $this->invoice = $this->invoice->fresh(); + + $invitation = $this->invoice->invitations->first(); + $invitation = $invitation->fresh(); + + $service = (new PdfService($invitation))->boot(); + $pdf = $service->getPdf(); + + $this->assertNotNull($pdf); + + \Illuminate\Support\Facades\Storage::put('/pdf/min_fields_' . $design->name.'.pdf', $pdf); + + }); + + + } + + public function testStatementPdfGeneration() { From aa85b327299dd7b6e7afa5b2ff9627485cd4e258 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 30 Jan 2025 17:27:42 +1100 Subject: [PATCH 17/43] Adjustments for modern design --- app/Services/Pdf/PdfBuilder.php | 47 +++++++++++++------------ app/Services/PdfMaker/Design.php | 9 +++-- resources/views/pdf-designs/modern.html | 25 +++++++++---- 3 files changed, 48 insertions(+), 33 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index e5b3651419..4768c31616 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -1750,11 +1750,7 @@ class PdfBuilder ['element' => 'div', 'content' => $this->service->config->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.name']], ['element' => 'div', 'content' => $this->service->config->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address1']], ['element' => 'div', 'content' => $this->service->config->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address2']], - ['element' => 'div', 'show_empty' => false, 'elements' => [ - ['element' => 'span', 'content' => "{$this->service->config->client->shipping_city} ", 'properties' => ['ref' => 'delivery_note-client.shipping_city']], - ['element' => 'span', 'content' => "{$this->service->config->client->shipping_state} ", 'properties' => ['ref' => 'delivery_note-client.shipping_state']], - ['element' => 'span', 'content' => "{$this->service->config->client->shipping_postal_code} ", 'properties' => ['ref' => 'delivery_note-client.shipping_postal_code']], - ]], + ['element' => 'div', 'content' => "{$this->service->config->client->shipping_city} {$this->service->config->client->shipping_state} {$this->service->config->client->shipping_postal_code}", 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.city_state_postal']], ['element' => 'div', 'content' => optional($this->service->config->client->shipping_country)->name, 'show_empty' => false], ]; @@ -1800,11 +1796,7 @@ class PdfBuilder ['element' => 'div', 'content' => ctrans('texts.shipping_address'), 'properties' => ['data-ref' => 'shipping_address-label', 'style' => 'font-weight: bold; text-transform: uppercase']], ['element' => 'div', 'content' => $this->service->config->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address1']], ['element' => 'div', 'content' => $this->service->config->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address2']], - ['element' => 'div', 'show_empty' => false, 'elements' => [ - ['element' => 'p', 'content' => "{$this->service->config->client->shipping_city} ", 'properties' => ['data-ref' => 'shipping_address-client.shipping_city']], - ['element' => 'p', 'content' => "{$this->service->config->client->shipping_state} ", 'properties' => ['data-ref' => 'shipping_address-client.shipping_state']], - ['element' => 'p', 'content' => "{$this->service->config->client->shipping_postal_code} ", 'properties' => ['data-ref' => 'shipping_address-client.shipping_postal_code']], - ]], + ['element' => 'div', 'content' => "{$this->service->config->client->shipping_city} {$this->service->config->client->shipping_state} {$this->service->config->client->shipping_postal_code}", 'properties' => ['data-ref' => 'shipping_address-client.city_state_postal']], ['element' => 'div', 'content' => optional($this->service->config->client->shipping_country)->name, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_country']], ]; @@ -1884,7 +1876,7 @@ class PdfBuilder $elements = []; foreach ($variables as $variable) { - $elements[] = ['element' => 'div', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]]; + $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]]; } return $elements; @@ -2107,30 +2099,41 @@ class PdfBuilder public function getEmptyElements(): self { - foreach ($this->sections as $element) { + foreach ($this->sections as $key => $element) { if (isset($element['elements'])) { - $this->getEmptyChildrens($element['elements']); + $this->sections[$key] = $this->getEmptyChildren($element); } } return $this; } - public function getEmptyChildrens(array $children) + public function getEmptyChildren(array $element): array { - foreach ($children as $key => $child) { - if (isset($child['content']) && isset($child['show_empty']) && $child['show_empty'] === false) { - $value = strtr($child['content'], $this->service->html_variables['values']); - if ($value === '' || $value === ' ' || $value === ' ') { - $child['is_empty'] = true; - } + foreach ($element['elements'] as $key => &$child) { + if ($this->isChildEmpty($child)) { + $child['is_empty'] = true; } if (isset($child['elements'])) { - $this->getEmptyChildrens($child['elements']); + $child = $this->getEmptyChildren($child); } } - return $this; + return $element; + } + + private function isChildEmpty(array $child): bool + { + if (!isset($child['content']) && isset($child['show_empty']) && $child['show_empty'] === false) { + return true; + } + + if (isset($child['content']) && isset($child['show_empty']) && $child['show_empty'] === false) { + $value = strtr($child['content'], $this->service->html_variables['values']); + return empty($value) || $value === ' ' || $value === ' '; + } + + return false; } } diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index 3b98ad1d6e..09f48fb0b0 100644 --- a/app/Services/PdfMaker/Design.php +++ b/app/Services/PdfMaker/Design.php @@ -328,9 +328,9 @@ class Design extends BaseDesign ['element' => 'div', 'content' => $this->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address1']], ['element' => 'div', 'content' => $this->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address2']], ['element' => 'div', 'show_empty' => false, 'elements' => [ - ['element' => 'span', 'content' => "{$this->client->shipping_city} ", 'properties' => ['ref' => 'delivery_note-client.shipping_city']], - ['element' => 'span', 'content' => "{$this->client->shipping_state} ", 'properties' => ['ref' => 'delivery_note-client.shipping_state']], - ['element' => 'span', 'content' => "{$this->client->shipping_postal_code} ", 'properties' => ['ref' => 'delivery_note-client.shipping_postal_code']], + ['element' => 'p', 'content' => "{$this->client->shipping_city} ", 'properties' => ['ref' => 'delivery_note-client.shipping_city']], + ['element' => 'p', 'content' => "{$this->client->shipping_state} ", 'properties' => ['ref' => 'delivery_note-client.shipping_state']], + ['element' => 'p', 'content' => "{$this->client->shipping_postal_code} ", 'properties' => ['ref' => 'delivery_note-client.shipping_postal_code']], ]], ['element' => 'div', 'content' => optional($this->client->shipping_country)->name, 'show_empty' => false], ]; @@ -345,7 +345,7 @@ class Design extends BaseDesign $variables = $this->context['pdf_variables']['client_details']; foreach ($variables as $variable) { - $elements[] = ['element' => 'div', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'client_details-' . substr($variable, 1)]]; + $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'client_details-' . substr($variable, 1)]]; } return $elements; @@ -406,7 +406,6 @@ class Design extends BaseDesign $_variable = explode('.', $variable)[1]; $_customs = ['custom1', 'custom2', 'custom3', 'custom4']; - /* 2/7/2022 don't show custom values if they are empty */ $var = str_replace("custom", "custom_value", $_variable); if (in_array($_variable, $_customs) && !empty($this->entity->{$var})) { diff --git a/resources/views/pdf-designs/modern.html b/resources/views/pdf-designs/modern.html index bb42f06086..b173d97e4d 100644 --- a/resources/views/pdf-designs/modern.html +++ b/resources/views/pdf-designs/modern.html @@ -72,9 +72,10 @@ } #entity-details { + margin-top: 1rem; text-align: left; color: #fff4e9 !important; - line-height: 1.6; + line-height: 1.6 !important; } #entity-details > tr, @@ -106,11 +107,15 @@ #shipping-details { display: $show_shipping_address; flex-direction: column; - margin-left: 10px; - margin-right: 10px; + padding-left: 10px; + padding-right: 2rem; float:right; } + #shipping-details > * { + margin-bottom: 0.5rem; + } + #client-details > * { margin-bottom: 0.5rem; } @@ -119,6 +124,11 @@ margin: 0rem 2rem; } + [data-ref="delivery_note-client.city_state_postal"] p{ + display: inline-block; + white-space: nowrap; + } + [data-ref="table"] { min-width: 100%; table-layout: fixed; @@ -203,7 +213,6 @@ #company-address { display: flex; flex-direction: column; - gap: 0.5rem; } #company-address > *, @@ -424,8 +433,12 @@ /** To find out selectors on your own: https://invoiceninja.github.io/docs/custom-fields/#snippets **/
From 084caf989dd6671cc866384fa4436f5ea634a89c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 30 Jan 2025 20:30:02 +1100 Subject: [PATCH 18/43] Fixes for hipster designs --- resources/views/pdf-designs/creative.html | 57 ++++++++++++++++------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/resources/views/pdf-designs/creative.html b/resources/views/pdf-designs/creative.html index 590aea888b..342dde036d 100644 --- a/resources/views/pdf-designs/creative.html +++ b/resources/views/pdf-designs/creative.html @@ -46,7 +46,7 @@ grid-auto-columns: minmax(0, 1fr); grid-auto-flow: column; justify-content:left; - gap: 20px; + gap: 0px; line-height: var(--line-height); } @@ -55,16 +55,16 @@ float:right; } - ,logo-wrapper { + .logo-wrapper { grid-area: e; align-content: right; - border:1px solid #000; + margin-left: 1rem; } #entity-details { width: 100%; white-space: nowrap; - margin-right: 3rem; + text-align: right; } #entity-details p { @@ -91,40 +91,61 @@ .header-wrapper .company-info-wrapper > * { margin-bottom: 1rem; grid-row-end: 4; + margin-left: 1rem; } .entity-label-wrapper { - display: grid; - grid-template-columns: 3fr 1fr; - margin-top: 1rem; + display: flex; + align-items: center; + width: 100%; + overflow: hidden; + box-sizing: border-box; + justify-content: space-between; + margin-top:1rem; + margin-bottom:1rem; + + } + + .entity-label { + display: flex; + } + + .entity-label h4 { + margin-top:0rem; + margin-bottom:0rem; + } + .entity-details-wrapper { + flex-shrink: 0; } .entity-label-wrapper .entity-label > * { font-size: 3rem; + } .entity-label-wrapper .entity-label > *:first-child { text-transform: uppercase; + white-space: nowrap; } .entity-label-wrapper .entity-label > *:last-child { color: var(--primary-color); font-style: italic; - } - - .entity-label-wrapper #entity-details { - text-align: left; + } #shipping-details { display: $show_shipping_address; flex-direction: column; line-height: var(--line-height) !important; + margin-left: 1rem; } .entity-label-wrapper #entity-details > tr, .entity-label-wrapper #entity-details th { font-weight: normal; + line-height: var(--line-height); + padding-left: 1rem; } [data-ref="table"] { @@ -136,7 +157,7 @@ [data-ref="table"]:not(:empty) { border-top: 5px solid var(--primary-color); - margin-top: 3rem; + margin-top: 1rem; } .task-time-details { @@ -385,10 +406,10 @@
-

- $entity_label  - #$entity_number -

+
+

$entity_label

+

#$entity_number

+
@@ -400,8 +421,8 @@
-
-
+
+
$status_logo
From 43be37a323570005d91aa8919c8597bf8d5eea62 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 30 Jan 2025 21:39:48 +1100 Subject: [PATCH 19/43] Adjustments for plain design --- resources/views/pdf-designs/bold.html | 1 + resources/views/pdf-designs/hipster.html | 4 +++- resources/views/pdf-designs/plain.html | 10 ++++++---- resources/views/pdf-designs/playful.html | 1 + resources/views/pdf-designs/tech.html | 6 +++--- tests/Pdf/PdfServiceTest.php | 10 ++++++---- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/resources/views/pdf-designs/bold.html b/resources/views/pdf-designs/bold.html index 0497a442fd..c9b321a585 100644 --- a/resources/views/pdf-designs/bold.html +++ b/resources/views/pdf-designs/bold.html @@ -73,6 +73,7 @@ max-width: $company_logo_size; object-fit: contain; object-position: left center; + max-height: 160px; } #company-details, diff --git a/resources/views/pdf-designs/hipster.html b/resources/views/pdf-designs/hipster.html index 9dd42fc39e..745bf8a7d8 100644 --- a/resources/views/pdf-designs/hipster.html +++ b/resources/views/pdf-designs/hipster.html @@ -95,6 +95,7 @@ .header-wrapper .header-right-side-wrapper-left-shipping { padding-left: 1rem; border-left: 1px solid #303030; + float:left; } .header-wrapper .header-right-side-wrapper-right { @@ -216,7 +217,7 @@ display: grid; grid-template-columns: 2fr 1fr; padding-top: 0.5rem; - margin-right: 1rem; + margin-right: 0rem; gap: 80px; page-break-inside:auto; overflow: visible !important; @@ -507,6 +508,7 @@ $amount_due
diff --git a/resources/views/pdf-designs/plain.html b/resources/views/pdf-designs/plain.html index 0e52daf18a..07b0bdc81d 100644 --- a/resources/views/pdf-designs/plain.html +++ b/resources/views/pdf-designs/plain.html @@ -48,7 +48,7 @@ .client-wrapper { display: grid; - grid-template-columns: 1fr auto 1fr; + grid-template-columns: auto 1fr auto; border: 0px solid #000; } @@ -64,7 +64,8 @@ #entity-container { border: 0px solid #000; - width: 100%; + margin-right: 0; + margin-left: auto; } #entity-details { @@ -72,8 +73,7 @@ line-height: var(--line-height) !important; white-space: nowrap; border: 0px solid #000; - margin-right: 0; - margin-left: auto; + float:right; } #entity-details>tr, @@ -102,6 +102,8 @@ line-height: var(--line-height); white-space: nowrap; border: 0px solid #000; + margin-left: auto; + margin-right:auto; } [data-ref="table"] { diff --git a/resources/views/pdf-designs/playful.html b/resources/views/pdf-designs/playful.html index a731537270..a36c8bf074 100644 --- a/resources/views/pdf-designs/playful.html +++ b/resources/views/pdf-designs/playful.html @@ -131,6 +131,7 @@ .contacts-wrapper #shipping-details { margin-top: 1rem; padding: 1rem; + padding-right: 0rem; border-top: 1px solid var(--primary-color); } diff --git a/resources/views/pdf-designs/tech.html b/resources/views/pdf-designs/tech.html index 6d166e706b..8b2343c561 100644 --- a/resources/views/pdf-designs/tech.html +++ b/resources/views/pdf-designs/tech.html @@ -440,15 +440,15 @@ $entity_number - $date_label: + $date_label $date - $payment_due_label: + $payment_due_label $due_date - $amount_due_label: + $amount_due_label $balance_due diff --git a/tests/Pdf/PdfServiceTest.php b/tests/Pdf/PdfServiceTest.php index e6c0cce635..05d5313f03 100644 --- a/tests/Pdf/PdfServiceTest.php +++ b/tests/Pdf/PdfServiceTest.php @@ -97,12 +97,13 @@ class PdfServiceTest extends TestCase \App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) use ($max_settings) { - $this->invoice->design_id = $design->id; - $this->invoice->save(); + $this->invoice->design_id = $design->id; + $this->invoice->client->settings->pdf_variables = $max_settings; + $this->invoice->push(); $this->invoice = $this->invoice->fresh(); $invitation = $this->invoice->invitations->first(); - $invitation = $invitation->fresh(); + $invitation->setRelation('company', $this->company); $service = (new PdfService($invitation))->boot(); $pdf = $service->getPdf(); @@ -132,7 +133,8 @@ class PdfServiceTest extends TestCase $this->invoice->design_id = $design->id; - $this->invoice->save(); + $this->invoice->client->settings = $min_settings; + $this->invoice->push(); $this->invoice = $this->invoice->fresh(); $invitation = $this->invoice->invitations->first(); From 7d4069e166b520f14033f447c3420eb67f7d9b62 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 1 Feb 2025 21:31:59 +1100 Subject: [PATCH 20/43] Fixes for bold design --- resources/views/pdf-designs/bold.html | 34 ++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/resources/views/pdf-designs/bold.html b/resources/views/pdf-designs/bold.html index c9b321a585..be0475c7b9 100644 --- a/resources/views/pdf-designs/bold.html +++ b/resources/views/pdf-designs/bold.html @@ -24,8 +24,6 @@ html { - /* width: 210mm; */ - /* height: 200mm; */ margin: 0; padding: 0; } @@ -157,28 +155,38 @@ } [data-ref="table"] > thead > tr > th { - padding: 1.5rem; + padding-top: 1.5rem; + padding-bottom: 1.5rem; + padding-left: 0; + padding-right: 0; font-size: 1rem; } - [data-ref="table"] > thead > tr > th:last-child { - text-align: right; - } [data-ref="table"] > tbody > tr > td { - padding: 1.5rem; + padding-top: 1.5rem; + padding-bottom: 1.5rem; + padding-left: 0; + padding-right: 0; + } + + th.left-radius { + padding-left: 1rem !important; } th.right-radius { text-align:right !important; + padding-right: 1rem !important; } td.right-radius { text-align: right !important; + padding-right: 1rem !important; } td.left-radius{ font-weight: bold; + padding-left: 1rem !important; } [data-ref="table"] > tbody > tr:nth-child(odd) { @@ -309,9 +317,19 @@ margin-top: 1rem; } - [data-ref='product_table-product.description-th'] { + [data-ref='product_table-product.description-th'], + [data-ref='product_table-product.description-td'] { width:30%; overflow-wrap: break-word; + padding-right: 1rem !important; + } + + [data-ref='product_table-product.tax1-th'], + [data-ref='product_table-product.tax1-td'], + [data-ref='product_table-product.discount-th'], + [data-ref='product_table-product.discount-td'] { + width:9%; + overflow-wrap: break-word; } .left-radius { From e683bdb33526a16558d00899ac8c8781a95e504d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 1 Feb 2025 21:43:34 +1100 Subject: [PATCH 21/43] Adjustments for elegant design --- resources/views/pdf-designs/elegant.html | 4 ++-- resources/views/pdf-designs/hipster.html | 19 ++++++++++++------- resources/views/pdf-designs/modern.html | 8 ++++++-- resources/views/pdf-designs/playful.html | 2 +- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/resources/views/pdf-designs/elegant.html b/resources/views/pdf-designs/elegant.html index 2eeba2d836..6bacfb22a8 100644 --- a/resources/views/pdf-designs/elegant.html +++ b/resources/views/pdf-designs/elegant.html @@ -64,8 +64,8 @@ .wrapper-left-side { display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); /* Force equal columns */ - gap: 20px; + grid-template-columns: repeat(2, minmax(0, 1fr)) repeat(2, minmax(0, 1.5fr)); + gap: 0px; width: 100%; } diff --git a/resources/views/pdf-designs/hipster.html b/resources/views/pdf-designs/hipster.html index 745bf8a7d8..36c9c01946 100644 --- a/resources/views/pdf-designs/hipster.html +++ b/resources/views/pdf-designs/hipster.html @@ -279,17 +279,14 @@ width:14%; } + [data-ref='task_table-task.description-th'], + [data-ref='product_table-product.description-th'], [data-ref='product_table-product.description-td'], [data-ref="task_table-task.description-td"] { - min-width:100px; + min-width:100px !important;; overflow-wrap: break-word; } - [data-ref='task_table-task.description-th'] { - overflow-wrap: break-word; - min-width: 100px !important; - } - [data-ref="product_table-product.unit_cost-td"], [data-ref="product_table-product.unit_cost-th"], [data-ref='task_table-task.cost-th'], @@ -310,11 +307,19 @@ padding-right:0 !important; } + [data-ref="task_table-task.discount-th"], + [data-ref="task_table-task.discount-td"], + [data-ref="product_table-product.discount-th"], + [data-ref="product_table-product.discount-td"] { + text-align: left !important; + width: 9%; + } + [data-ref="task_table-task.tax1-th"], [data-ref="task_table-task.tax1-td"], [data-ref="product_table-product.tax1-th"], [data-ref="product_table-product.tax1-td"] { - text-align: center; + text-align: center !important; width: 9%; padding-left:0 !important; padding-right:0 !important; diff --git a/resources/views/pdf-designs/modern.html b/resources/views/pdf-designs/modern.html index b173d97e4d..510217348c 100644 --- a/resources/views/pdf-designs/modern.html +++ b/resources/views/pdf-designs/modern.html @@ -320,7 +320,6 @@ [data-ref='product_table-product.product3-th'], [data-ref='product_table-product.product4-th'], [data-ref='task_table-task.hours-th'], - [data-ref='task_table-task.discount-th'], [data-ref='task_table-task.cost-th'], [data-ref='task_table-task.quantity-th'], [data-ref='task_table-task.task1-th'], @@ -332,7 +331,12 @@ [data-ref='product_table-product.tax1-th'], [data-ref='task_table-task.tax1-th'] { - width: 10%; + width: 7%; + } + + [data-ref='product_table-product.discount-th'], + [data-ref='task_table-task.discount-th'] { + width: 8%; } [data-ref='product_table-product.line_total-th'], diff --git a/resources/views/pdf-designs/playful.html b/resources/views/pdf-designs/playful.html index a36c8bf074..bb21ae1eea 100644 --- a/resources/views/pdf-designs/playful.html +++ b/resources/views/pdf-designs/playful.html @@ -77,7 +77,7 @@ .contacts-wrapper { display: grid; gap: 20px; - padding: 1rem 0rem 0rem 2rem; + padding: 1rem 0rem 0rem 1rem; grid-template-columns: 1fr 1fr auto; grid-template-areas: "a b c"; grid-auto-columns: minmax(0, 5fr); From 6da33ea7fee58b06f2a3099145e8897e38e7f015 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 2 Feb 2025 11:24:36 +1100 Subject: [PATCH 22/43] Improvements for clean design --- app/Http/Controllers/DesignController.php | 3 --- resources/views/pdf-designs/clean.html | 17 ++++++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/DesignController.php b/app/Http/Controllers/DesignController.php index 9d509762e2..37b77acaca 100644 --- a/app/Http/Controllers/DesignController.php +++ b/app/Http/Controllers/DesignController.php @@ -554,9 +554,6 @@ class DesignController extends BaseController $company = $user->getCompany(); - nlog("Design Change {$company->id}"); - nlog($request->all()); - $design = Design::where('company_id', $company->id) ->orWhereNull('company_id') ->where('id', $design_id) diff --git a/resources/views/pdf-designs/clean.html b/resources/views/pdf-designs/clean.html index 1bba0cb3bd..1784284038 100644 --- a/resources/views/pdf-designs/clean.html +++ b/resources/views/pdf-designs/clean.html @@ -46,13 +46,15 @@ .header-container { display: grid; - grid-template-columns: repeat(3, 1fr); - justify-content: space-between; + grid-template-columns: 1fr auto 1fr; + align-items: start; + gap: 1rem; width:100%; } .company-logo-container { - display: inline-block; + justify-self: start; + align-self: start; } .company-logo { @@ -60,6 +62,8 @@ } #company-details { + justify-self: center; + align-self: start; display: flex; flex-direction: column; line-height: var(--line-height); @@ -70,6 +74,8 @@ } #company-address { + justify-self: end; + align-self: start; display: flex; flex-direction: column; line-height: var(--line-height); @@ -87,7 +93,8 @@ .client-and-entity-wrapper { padding: 1rem; display: grid; - grid-template-columns: 1fr 1fr 1fr; + grid-template-columns: 1fr auto 1fr; + align-items: start; border-top: 1px solid #d8d8d8; border-bottom: 1px solid #d8d8d8; } @@ -363,7 +370,7 @@

$entity_label

-
+
From f24fd3f2a09232826095d4be32df55f7b2eab845 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 2 Feb 2025 11:30:19 +1100 Subject: [PATCH 23/43] Improvements for clean design --- app/Services/Pdf/PdfService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index b37c47559c..68ea3ad316 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -92,7 +92,7 @@ class PdfService try { $html = $this->getHtml(); - nlog($html); + $pdf = $this->resolvePdfEngine($html); $numbered_pdf = $this->pageNumbering($pdf, $this->company); From b42799c68400aa40f2a647041f6acf5c2881a24b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 2 Feb 2025 13:20:16 +1100 Subject: [PATCH 24/43] Improvements for business design --- app/Services/Pdf/PdfBuilder.php | 38 +++++----- app/Services/Pdf/PdfService.php | 2 +- resources/views/pdf-designs/bold.html | 86 +++++++++-------------- resources/views/pdf-designs/business.html | 53 ++++++-------- resources/views/pdf-designs/clean.html | 20 +++--- resources/views/pdf-designs/creative.html | 7 -- resources/views/pdf-designs/elegant.html | 15 ---- resources/views/pdf-designs/hipster.html | 15 ---- resources/views/pdf-designs/modern.html | 6 -- resources/views/pdf-designs/playful.html | 15 ---- 10 files changed, 80 insertions(+), 177 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 4768c31616..2cb64047dc 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -1353,8 +1353,6 @@ class PdfBuilder if (is_null($this->service->config->entity->{$_variable}) || empty($this->service->config->entity->{$_variable})) { return true; } - - // nlog("{$_variable} - {$this->service->config->entity->{$_variable}}"); return false; } @@ -1377,11 +1375,11 @@ class PdfBuilder $elements = [ ['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [ ['element' => 'div', 'properties' => ['data-ref' => 'total_table-public_notes', 'style' => 'text-align: left;'], 'elements' => [ - ['element' => 'span', 'content' => strtr(str_replace(["labels", "values"], ["",""], $_variables['values']['$entity.public_notes']), $_variables)] + ['element' => 'div', 'content' => strtr(str_replace(["labels", "values"], ["",""], $_variables['values']['$entity.public_notes']), $_variables)] ]], ['element' => 'div', 'content' => '', 'properties' => ['style' => 'text-align: left; display: flex; flex-direction: column; page-break-inside: auto;'], 'elements' => [ - ['element' => 'span', 'content' => '$entity.terms_label: ', 'properties' => ['data-ref' => 'total_table-terms-label', 'style' => "font-weight:bold; text-align: left; margin-top: 1rem; {$show_terms_label}"]], - ['element' => 'span', 'content' => strtr(str_replace("labels", "", $_variables['values']['$entity.terms']), $_variables['labels']), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']], + ['element' => 'div', 'content' => '$entity.terms_label: ', 'properties' => ['data-ref' => 'total_table-terms-label', 'style' => "font-weight:bold; text-align: left; margin-top: 1rem; {$show_terms_label}"]], + ['element' => 'div', 'content' => strtr(str_replace("labels", "", $_variables['values']['$entity.terms']), $_variables['labels']), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']], ]], ['element' => 'img', 'properties' => ['style' => 'max-width: 50%; height: auto;', 'src' => '$contact.signature', 'id' => 'contact-signature']], ['element' => 'div', 'properties' => ['style' => 'display: flex; align-items: flex-start; page-break-inside: auto;'], 'elements' => [ @@ -1440,8 +1438,8 @@ class PdfBuilder foreach ($taxes as $i => $tax) { $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ - ['element' => 'span', 'content', 'content' => $tax['name'], 'properties' => ['data-ref' => 'totals-table-total_tax_' . $i . '-label']], - ['element' => 'span', 'content', 'content' => $this->service->config->formatMoney($tax['total']), 'properties' => ['data-ref' => 'totals-table-total_tax_' . $i]], + ['element' => 'p', 'content', 'content' => $tax['name'], 'properties' => ['data-ref' => 'totals-table-total_tax_' . $i . '-label']], + ['element' => 'p', 'content', 'content' => $this->service->config->formatMoney($tax['total']), 'properties' => ['data-ref' => 'totals-table-total_tax_' . $i]], ]]; } } elseif ($variable == '$line_taxes') { @@ -1453,8 +1451,8 @@ class PdfBuilder foreach ($taxes as $i => $tax) { $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ - ['element' => 'span', 'content', 'content' => $tax['name'], 'properties' => ['data-ref' => 'totals-table-line_tax_' . $i . '-label']], - ['element' => 'span', 'content', 'content' => $this->service->config->formatMoney($tax['total']), 'properties' => ['data-ref' => 'totals-table-line_tax_' . $i]], + ['element' => 'p', 'content', 'content' => $tax['name'], 'properties' => ['data-ref' => 'totals-table-line_tax_' . $i . '-label']], + ['element' => 'p', 'content', 'content' => $this->service->config->formatMoney($tax['total']), 'properties' => ['data-ref' => 'totals-table-line_tax_' . $i]], ]]; } } elseif (Str::startsWith($variable, '$custom_surcharge')) { @@ -1463,28 +1461,28 @@ class PdfBuilder $visible = intval(str_replace(['0','.'], '', ($this->service->config->entity->{$_variable} ?? ''))) != 0; $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ - ['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], - ['element' => 'span', 'content' => $variable, 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1)]], + ['element' => 'p', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], + ['element' => 'p', 'content' => $variable, 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1)]], ]]; } elseif (Str::startsWith($variable, '$custom')) { $field = explode('_', $variable); $visible = is_object($this->service->company->custom_fields) && property_exists($this->service->company->custom_fields, $field[1]) && !empty($this->service->company->custom_fields->{$field[1]}); $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ - ['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], - ['element' => 'span', 'content' => $variable, 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1)]], + ['element' => 'p', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], + ['element' => 'p', 'content' => $variable, 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1)]], ]]; } else { $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ - ['element' => 'span', 'content' => $variable . '_label', 'properties' => ['data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], - ['element' => 'span', 'content' => $variable, 'properties' => ['data-ref' => 'totals_table-' . substr($variable, 1)]], - ]]; + ['element' => 'p', 'content' => $variable . '_label', 'properties' => ['data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], + ['element' => 'p', 'content' => $variable, 'properties' => ['data-ref' => 'totals_table-' . substr($variable, 1)]], + ], 'properties' => ['class' => 'totals_table-' . substr($variable, 1)]]; } } $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ - ['element' => 'span', 'content' => '',], - ['element' => 'span', 'content' => ''], + ['element' => 'p', 'content' => '',], + ['element' => 'p', 'content' => ''], ]]; @@ -1991,7 +1989,8 @@ class PdfBuilder '/\*\*.*?\*\*/', // Bold '/\*.*?\*/', // Italic '/__.*?__/', // Bold - '/_.*?_/', // Italic + // '/_.*?_/', // Italic + '/(?/m', // Blockquotes '/^\s*```/m', // Code blocks @@ -2027,7 +2026,6 @@ class PdfBuilder $contains_html = str_contains($child['content'], '<') && str_contains($child['content'], '>'); if ($contains_html) { - // Encode any HTML elements now so that DOMDocument doesn't throw any errors, // Later we can decode specific elements. diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index 68ea3ad316..b37c47559c 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -92,7 +92,7 @@ class PdfService try { $html = $this->getHtml(); - + nlog($html); $pdf = $this->resolvePdfEngine($html); $numbered_pdf = $this->pageNumbering($pdf, $this->company); diff --git a/resources/views/pdf-designs/bold.html b/resources/views/pdf-designs/bold.html index be0475c7b9..846da450b4 100644 --- a/resources/views/pdf-designs/bold.html +++ b/resources/views/pdf-designs/bold.html @@ -227,22 +227,6 @@ text-align: right; } - #table-totals - > * - [data-element='product-table-balance-due-label'], - #table-totals - > * - [data-element='product-table-balance-due'] { - font-weight: bold; - font-size: 1.4rem; - } - - #table-totals - > * - [data-element='product-table-balance-due'] { - color: var(--primary-color); - } - #table-totals > * > :last-child { text-align: right; padding-right: 1.5rem; @@ -438,47 +422,43 @@ - - -
-
-
-

$entity_label

-
-
+ + +
+
+
+

$entity_label

+
+
+
+
+

&

+
+
-
-

&

-
+ +
+

$entity_label

+
+
+
-
-

$entity_label

-
-
-
-
-
- -
- -
- -
- -
-
- -
-
-
-
-
-
-
$status_logo
- - +
+
+
+
+
+
+
+
+
+
+
+
$status_logo
+ + diff --git a/resources/views/pdf-designs/business.html b/resources/views/pdf-designs/business.html index d3a43f2c5f..ed0d574b76 100644 --- a/resources/views/pdf-designs/business.html +++ b/resources/views/pdf-designs/business.html @@ -41,11 +41,18 @@ .header-container { display: grid; - grid-template-columns: 1.8fr 1fr 1fr; - grid-gap: 20px; + grid-template-columns: 1fr auto 1fr; + align-items: start; + gap: 1rem; + width:100%; margin-bottom: 2rem; } + .company-logo-container { + justify-self: start; + align-self: start; + } + .company-logo { max-width: $company_logo_size; } @@ -55,6 +62,8 @@ } #company-details { + justify-self: center; + align-self: start; display: flex; flex-direction: column; color: #AAA9A9; @@ -62,6 +71,8 @@ } #company-address { + justify-self: end; + align-self: start; display: flex; flex-direction: column; color: #b1b1b1; @@ -173,14 +184,6 @@ background: #f7f7f7; } - [data-element='product-table-balance-due-label'], - [data-element='product-table-balance-due'], - [data-element='task-table-balance-due-label'], - [data-element='task-table-balance-due'] { - color: var(--secondary-color) !important; - font-weight: bold !important; - } - #table-totals > *:last-child { border-bottom-left-radius: 1rem; border-bottom-right-radius: 1rem; @@ -220,22 +223,6 @@ padding-right: 17px; } - #table-totals - > * - [data-element='product-table-balance-due-label'], - #table-totals - > * - [data-element='product-table-balance-due'] { - font-weight: bold; - font-size: 1.2rem; - } - - #table-totals - > * - [data-element='product-table-balance-due'] { - color: red; - } - #table-totals > * > :last-child { text-align: right; padding-right: 1rem; @@ -263,6 +250,7 @@ padding: 1rem; border-top-left-radius: 7px; border-bottom-left-radius: 7px; + white-space: nowrap; } [data-ref="totals_table-outstanding"] { background-color: var(--secondary-color); @@ -272,6 +260,7 @@ padding: 1rem; border-top-right-radius: 7px; border-bottom-right-radius: 7px; + white-space: nowrap; } [data-ref="statement-totals"] { @@ -399,13 +388,11 @@
- -
-
+
+ +
+
+

$entity_issued_to_label

diff --git a/resources/views/pdf-designs/clean.html b/resources/views/pdf-designs/clean.html index 1784284038..a9ad0d6f01 100644 --- a/resources/views/pdf-designs/clean.html +++ b/resources/views/pdf-designs/clean.html @@ -62,7 +62,7 @@ } #company-details { - justify-self: center; + justify-self: center; align-self: start; display: flex; flex-direction: column; @@ -185,7 +185,7 @@ padding-right: 1rem; padding-left: 1rem; gap: 80px; - page-break-inside: avoid; + page-break-inside: auto; overflow: visible !important; } @@ -209,15 +209,6 @@ margin-bottom: calc(.75rem * var(--tw-space-y-reverse)); } - #table-totals>* [data-element='product-table-balance-due-label'], - #table-totals>* [data-element='product-table-balance-due'] { - font-weight: bold; - } - - #table-totals>* [data-element='product-table-balance-due'] { - color: var(--primary-color); - } - #table-totals>*> :last-child { text-align: right; padding-right: 0.5rem; @@ -388,10 +379,15 @@
-
$status_logo
+
+ + +
$status_logo
+ + diff --git a/resources/views/pdf-designs/creative.html b/resources/views/pdf-designs/creative.html index 342dde036d..d4373e9b11 100644 --- a/resources/views/pdf-designs/creative.html +++ b/resources/views/pdf-designs/creative.html @@ -233,13 +233,6 @@ text-align: right; } - #table-totals - > * - [data-element='product-table-balance-due'] { - font-weight: bold; - color: var(--primary-color); - } - #table-totals > * > * { padding-left: 0.5rem; } diff --git a/resources/views/pdf-designs/elegant.html b/resources/views/pdf-designs/elegant.html index 6bacfb22a8..933499ece1 100644 --- a/resources/views/pdf-designs/elegant.html +++ b/resources/views/pdf-designs/elegant.html @@ -214,21 +214,6 @@ text-align: right; } - #table-totals - > * - [data-element='product-table-balance-due-label'], - #table-totals - > * - [data-element='product-table-balance-due'] { - font-weight: bold; - } - - #table-totals - > * - [data-element='product-table-balance-due'] { - color: var(--primary-color); - } - #table-totals > * > :last-child { text-align: right; padding-right: 0.5rem; diff --git a/resources/views/pdf-designs/hipster.html b/resources/views/pdf-designs/hipster.html index 36c9c01946..623b2a9845 100644 --- a/resources/views/pdf-designs/hipster.html +++ b/resources/views/pdf-designs/hipster.html @@ -243,21 +243,6 @@ text-align: right; } - #table-totals - > * - [data-element='product-table-balance-due-label'], - #table-totals - > * - [data-element='product-table-balance-due'] { - font-weight: bold; - } - - #table-totals - > * - [data-element='product-table-balance-due'] { - color: var(--primary-color); - } - #table-totals > * > :last-child { text-align: right; padding-right: 0rem; diff --git a/resources/views/pdf-designs/modern.html b/resources/views/pdf-designs/modern.html index 510217348c..b2fc774524 100644 --- a/resources/views/pdf-designs/modern.html +++ b/resources/views/pdf-designs/modern.html @@ -257,12 +257,6 @@ text-align: right; } - #table-totals > * [data-element="product-table-balance-due-label"], - #table-totals > * [data-element="product-table-balance-due"] { - font-weight: bold; - font-size: 1.3rem; - } - [data-ref="total_table-footer"] { margin-top: 1rem; margin-bottom: 1rem; diff --git a/resources/views/pdf-designs/playful.html b/resources/views/pdf-designs/playful.html index bb21ae1eea..d81874365d 100644 --- a/resources/views/pdf-designs/playful.html +++ b/resources/views/pdf-designs/playful.html @@ -231,21 +231,6 @@ text-align: right; } - #table-totals - > * - [data-element='product-table-balance-due-label'], - #table-totals - > * - [data-element='product-table-balance-due'] { - font-weight: bold; - } - - #table-totals - > * - [data-element='product-table-balance-due'] { - color: var(--primary-color); - } - #table-totals > * > :last-child { text-align: right; padding-right: 1rem; From b76e13b661e9bdb37ee026b784b14dbdd06b4cb7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 2 Feb 2025 15:22:40 +1100 Subject: [PATCH 25/43] Fixes for the calm desing --- resources/views/pdf-designs/calm.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/views/pdf-designs/calm.html b/resources/views/pdf-designs/calm.html index 49f089a2cd..bb283c4979 100644 --- a/resources/views/pdf-designs/calm.html +++ b/resources/views/pdf-designs/calm.html @@ -184,6 +184,7 @@ display: grid; grid-template-columns: 1.5fr 1fr; padding-top: .5rem; + padding-left: 1rem; gap: 80px; page-break-inside: auto; overflow: visible !important; @@ -201,6 +202,7 @@ text-align: left; margin-top: .25rem; padding-left: 7px; + } #table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) { @@ -211,7 +213,7 @@ #table-totals>.totals-table-right-side>*> :nth-child(2) { text-align: right; - padding-right: 0px; + padding-right: 0.75rem; } #table-totals>* [data-element='total-table-balance-due-label'], From c9a9ee342c40b5da5ab48e8a55e4c0d6ccb423c5 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 2 Feb 2025 15:38:41 +1100 Subject: [PATCH 26/43] Improvements for business design --- resources/views/pdf-designs/creative.html | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/resources/views/pdf-designs/creative.html b/resources/views/pdf-designs/creative.html index d4373e9b11..e89798f826 100644 --- a/resources/views/pdf-designs/creative.html +++ b/resources/views/pdf-designs/creative.html @@ -41,11 +41,11 @@ .header-wrapper { display: grid; grid-template-rows:0.5fr; - grid-template-columns: auto auto auto auto; + grid-template-columns: auto auto auto auto auto; grid-template-areas: "a b c d e"; grid-auto-columns: minmax(0, 1fr); grid-auto-flow: column; - justify-content:left; + align-items: start; gap: 0px; line-height: var(--line-height); } @@ -56,9 +56,9 @@ } .logo-wrapper { - grid-area: e; - align-content: right; - margin-left: 1rem; + grid-area: e; + justify-self: end; + align-self: start; } #entity-details { @@ -91,7 +91,6 @@ .header-wrapper .company-info-wrapper > * { margin-bottom: 1rem; grid-row-end: 4; - margin-left: 1rem; } .entity-label-wrapper { @@ -208,6 +207,7 @@ grid-template-columns: 2fr 1fr; padding-top: 1rem; margin-right: .75rem; + padding-left: 0.5rem; gap: 80px; page-break-inside:auto; overflow: visible !important; @@ -248,6 +248,7 @@ } #footer { + padding-left: 1rem; margin-top: 30px; } From 9887f43761db03e6acd19e8d2632017b7a7b8cdb Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 2 Feb 2025 15:43:45 +1100 Subject: [PATCH 27/43] Fixes for the elegant design --- resources/views/pdf-designs/elegant.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/views/pdf-designs/elegant.html b/resources/views/pdf-designs/elegant.html index 933499ece1..ee91dfd498 100644 --- a/resources/views/pdf-designs/elegant.html +++ b/resources/views/pdf-designs/elegant.html @@ -188,7 +188,6 @@ grid-template-columns: 2fr 1fr; padding-top: 0.5rem; padding-left: 1rem; - margin-right: .75rem; gap: 80px; page-break-inside:auto; overflow: visible !important; @@ -212,11 +211,12 @@ #table-totals>.totals-table-right-side>*> :nth-child(2) { text-align: right; + margin-right: 1rem; } #table-totals > * > :last-child { text-align: right; - padding-right: 0.5rem; + margin-right: .75rem; } [data-ref="total_table-footer"] { @@ -225,6 +225,7 @@ #footer { margin-top: 30px; + padding-left: 1rem; } /** Markdown-specific styles. **/ From 007243a0cab68de2c9d2ac0ff4cef5989178dd91 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 2 Feb 2025 16:22:19 +1100 Subject: [PATCH 28/43] Hipster design fixes --- resources/views/pdf-designs/hipster.html | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/resources/views/pdf-designs/hipster.html b/resources/views/pdf-designs/hipster.html index 623b2a9845..9711cbb9d1 100644 --- a/resources/views/pdf-designs/hipster.html +++ b/resources/views/pdf-designs/hipster.html @@ -37,7 +37,7 @@ .header-wrapper { display: grid; grid-template-columns: 0.5fr 1.5fr; - gap: 20px; + gap: 0px; line-height: var(--line-height); } @@ -63,6 +63,7 @@ .header-wrapper .header-left-side-wrapper #company-address { display: flex; flex-direction: column; + padding-right: 1rem; } .header-wrapper .header-right-side-wrapper { @@ -75,7 +76,7 @@ grid-template-areas: "a b c"; grid-auto-columns: minmax(0, 1fr); grid-auto-flow: column; - justify-content: end; + align-items: start; } .header-wrapper .header-right-side-wrapper #client-details { @@ -83,6 +84,8 @@ flex-direction: column; margin-top: 0.8rem; grid-area: a; + justify-self: left !important; + align-self: start !important; } .header-wrapper .header-right-side-wrapper #shipping-details { @@ -99,8 +102,11 @@ } .header-wrapper .header-right-side-wrapper-right { - float:left; grid-area: c; + justify-self: end; + align-self: start; + justify-content: end; + float:right; } .shipping-text-label { From dbe92f23a2c4da483ff86be53fa755166c19ba3b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 2 Feb 2025 16:31:56 +1100 Subject: [PATCH 29/43] Fixes for playful design --- resources/views/pdf-designs/playful.html | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/resources/views/pdf-designs/playful.html b/resources/views/pdf-designs/playful.html index d81874365d..79fd8e1bb8 100644 --- a/resources/views/pdf-designs/playful.html +++ b/resources/views/pdf-designs/playful.html @@ -30,7 +30,6 @@ margin-top: 1rem; } - p { margin: 0; padding: 0; @@ -47,6 +46,7 @@ background-color: var(--primary-color); padding: 0.5rem; border-radius: 10px; + align-self: flex-start; } #entity-details p { @@ -229,10 +229,6 @@ #table-totals>.totals-table-right-side>*> :nth-child(2) { text-align: right; - } - - #table-totals > * > :last-child { - text-align: right; padding-right: 1rem; } From a494898394cbd3b299622021d777592c71949909 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 2 Feb 2025 22:32:51 +1100 Subject: [PATCH 30/43] Adjustments for designs --- app/Services/Pdf/PdfService.php | 2 +- resources/views/pdf-designs/playful.html | 1 + tests/Pdf/PdfServiceTest.php | 2 -- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index b37c47559c..3546c308ca 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -92,7 +92,7 @@ class PdfService try { $html = $this->getHtml(); - nlog($html); + // nlog($html); $pdf = $this->resolvePdfEngine($html); $numbered_pdf = $this->pageNumbering($pdf, $this->company); diff --git a/resources/views/pdf-designs/playful.html b/resources/views/pdf-designs/playful.html index 79fd8e1bb8..3a8ec5c8c3 100644 --- a/resources/views/pdf-designs/playful.html +++ b/resources/views/pdf-designs/playful.html @@ -83,6 +83,7 @@ grid-auto-columns: minmax(0, 5fr); grid-auto-flow: column; justify-content: space-between; + margin-left:1rem; } diff --git a/tests/Pdf/PdfServiceTest.php b/tests/Pdf/PdfServiceTest.php index 05d5313f03..e7a1bfcf31 100644 --- a/tests/Pdf/PdfServiceTest.php +++ b/tests/Pdf/PdfServiceTest.php @@ -45,7 +45,6 @@ class PdfServiceTest extends TestCase \App\Models\Design::where('is_custom',false)->cursor()->each(function ($design){ - $this->invoice->design_id = $design->id; $this->invoice->save(); $this->invoice = $this->invoice->fresh(); @@ -61,7 +60,6 @@ class PdfServiceTest extends TestCase }); - \App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) { From 2835d29b0b108109720fcd7602db1a18604d8a31 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 4 Feb 2025 15:15:51 +1100 Subject: [PATCH 31/43] Ensure delivery notes have left and right radius classes applied --- app/Services/Pdf/PdfBuilder.php | 86 +++++++++++++++++++++++---- app/Utils/Traits/Pdf/PdfMaker.php | 2 +- resources/views/pdf-designs/bold.html | 5 -- 3 files changed, 75 insertions(+), 18 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 2cb64047dc..e955b343b9 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -748,6 +748,11 @@ class PdfBuilder return []; } + + //2025-01-28 Regular Table Body + $_type = Str::startsWith($type, '$') ? ltrim($type, '$') : $type; + $column_visibility = $this->getColumnVisibility($this->service->config->entity->line_items, $_type); + if ($type == PdfService::DELIVERY_NOTE) { $product_customs = [false, false, false, false]; @@ -762,27 +767,70 @@ class PdfBuilder foreach ($items as $row) { $element = ['element' => 'tr', 'elements' => []]; - $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.product_key'], 'properties' => ['data-ref' => 'delivery_note_table.product_key-td']]; - $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.notes'], 'properties' => ['data-ref' => 'delivery_note_table.notes-td']]; - $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.quantity'], 'properties' => ['data-ref' => 'delivery_note_table.quantity-td']]; + $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.product_key'], 'properties' => ['data-ref' => 'delivery_note_table.product_key-td','visi' => $this->visibilityCheck($column_visibility, 'product_key')]]; + $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.notes'], 'properties' => ['data-ref' => 'delivery_note_table.notes-td','visi' => $this->visibilityCheck($column_visibility, 'notes')]]; + $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.quantity'], 'properties' => ['data-ref' => 'delivery_note_table.quantity-td','visi' => $this->visibilityCheck($column_visibility, 'quantity')]]; for ($i = 0; $i < count($product_customs); $i++) { if ($product_customs[$i]) { - $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.delivery_note' . ($i + 1)], 'properties' => ['data-ref' => 'delivery_note_table.product' . ($i + 1) . '-td']]; + $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.delivery_note' . ($i + 1)], 'properties' => ['data-ref' => 'delivery_note_table.product' . ($i + 1) . '-td','visi' => $this->visibilityCheck($column_visibility, 'product' . ($i + 1))]]; } } + $visible_elements = array_filter($element['elements'], function ($el) { + if (isset($el['properties']['visi']) && $el['properties']['visi']) { + return true; + } + return false; + }); + + if (!empty($visible_elements)) { + $first_visible = array_key_first($visible_elements); + $last_visible = array_key_last($visible_elements); + + // Add class to first visible cell + if (!isset($element['elements'][$first_visible]['properties']['class'])) { //@phpstan-ignore-line + $element['elements'][$first_visible]['properties']['class'] = 'left-radius'; + } else { + $element['elements'][$first_visible]['properties']['class'] .= ' left-radius'; + } + + // Add class to last visible cell + if (!isset($element['elements'][$last_visible]['properties']['class'])) { + $element['elements'][$last_visible]['properties']['class'] = 'right-radius'; + } else { + $element['elements'][$last_visible]['properties']['class'] .= ' right-radius'; + } + } + + // Then, filter the elements array + $element['elements'] = array_map(function ($el) { + if (isset($el['properties']['visi'])) { + if ($el['properties']['visi'] === false) { + $el['properties']['style'] = 'display: none;'; + } + unset($el['properties']['visi']); + } + return $el; + }, $element['elements']); + $elements[] = $element; } + + + + + + + + + + + return $elements; } - - - //2025-01-28 Regular Table Body - $_type = Str::startsWith($type, '$') ? ltrim($type, '$') : $type; - $table_type = "{$_type}_columns"; //Handle custom quote columns @@ -790,9 +838,6 @@ class PdfBuilder $table_type = "product_quote_columns"; } - $_type = Str::startsWith($type, '$') ? ltrim($type, '$') : $type; - - $column_visibility = $this->getColumnVisibility($this->service->config->entity->line_items, $_type); foreach ($items as $row) { $element = ['element' => 'tr', 'elements' => []]; @@ -1836,6 +1881,23 @@ class PdfBuilder } } + $first_visible = array_key_first($thead); + $last_visible = array_key_last($thead); + + // Add class to first visible cell + if (!isset($thead[$first_visible]['properties']['class'])) { //@phpstan-ignore-line + $thead[$first_visible]['properties']['class'] = 'left-radius'; + } else { + $thead[$first_visible]['properties']['class'] .= ' left-radius'; + } + + // Add class to last visible cell + if (!isset($thead[$last_visible]['properties']['class'])) { + $thead[$last_visible]['properties']['class'] = 'right-radius'; + } else { + $thead[$last_visible]['properties']['class'] .= ' right-radius'; + } + return [ ['element' => 'thead', 'elements' => $thead], ['element' => 'tbody', 'elements' => $this->buildTableBody(PdfService::DELIVERY_NOTE)], diff --git a/app/Utils/Traits/Pdf/PdfMaker.php b/app/Utils/Traits/Pdf/PdfMaker.php index 744a20d531..6224fc0dd4 100644 --- a/app/Utils/Traits/Pdf/PdfMaker.php +++ b/app/Utils/Traits/Pdf/PdfMaker.php @@ -91,7 +91,7 @@ trait PdfMaker } $html = str_ireplace(['file:/', 'iframe', '', '/etc/'], '', $html); - +nlog($html); $generated = $pdf ->setHtml($html) ->generate(); diff --git a/resources/views/pdf-designs/bold.html b/resources/views/pdf-designs/bold.html index 846da450b4..18e684b91d 100644 --- a/resources/views/pdf-designs/bold.html +++ b/resources/views/pdf-designs/bold.html @@ -316,11 +316,6 @@ overflow-wrap: break-word; } - .left-radius { - padding-left: 1rem; - text-align: right; - } - .right-radius { text-align: right; } From 5067b8e758113c4c7830a2c5731e8e9d2b8dbf49 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 4 Feb 2025 15:40:01 +1100 Subject: [PATCH 32/43] padding fixes for playful --- app/Utils/Traits/Pdf/PdfMaker.php | 2 +- resources/views/pdf-designs/bold.html | 4 ++-- resources/views/pdf-designs/playful.html | 9 +++++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/Utils/Traits/Pdf/PdfMaker.php b/app/Utils/Traits/Pdf/PdfMaker.php index 6224fc0dd4..ab4ed8051a 100644 --- a/app/Utils/Traits/Pdf/PdfMaker.php +++ b/app/Utils/Traits/Pdf/PdfMaker.php @@ -91,7 +91,7 @@ trait PdfMaker } $html = str_ireplace(['file:/', 'iframe', '', '/etc/'], '', $html); -nlog($html); +// nlog($html); $generated = $pdf ->setHtml($html) ->generate(); diff --git a/resources/views/pdf-designs/bold.html b/resources/views/pdf-designs/bold.html index 18e684b91d..56bca2f171 100644 --- a/resources/views/pdf-designs/bold.html +++ b/resources/views/pdf-designs/bold.html @@ -91,7 +91,7 @@ display: flex; flex-direction: column; line-height: var(--line-height) !important; - padding-left: 1.5rem; + padding-left: 1rem; } #shipping-details { @@ -235,7 +235,7 @@ .entity-label { text-transform: uppercase; color: var(--primary-color); - padding-left: 1.5rem; + padding-left: 1rem; font-size: 1.5rem; } diff --git a/resources/views/pdf-designs/playful.html b/resources/views/pdf-designs/playful.html index 3a8ec5c8c3..ed178bef50 100644 --- a/resources/views/pdf-designs/playful.html +++ b/resources/views/pdf-designs/playful.html @@ -76,7 +76,7 @@ .contacts-wrapper { display: grid; - gap: 20px; + gap: 0px; padding: 1rem 0rem 0rem 1rem; grid-template-columns: 1fr 1fr auto; grid-template-areas: "a b c"; @@ -137,14 +137,19 @@ } - .contact-wrapper-left-side, + .contact-wrapper-left-side{ + border-bottom: 1px solid var(--primary-color); + } + .contact-wrapper-right-side { border-bottom: 1px solid var(--primary-color); + margin-left:20px; } .shipping-wrapper-right-side { border-bottom: 1px solid var(--primary-color); display: $show_shipping_address_block; + margin-left:20px; } [data-ref="table"] { From 83d6767f7a106620d3c4cceb139911c2191e8b10 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 4 Feb 2025 16:14:47 +1100 Subject: [PATCH 33/43] Improve efficiency of PDF generation --- app/Services/Pdf/PdfBuilder.php | 163 ++++++++++++-------------------- 1 file changed, 60 insertions(+), 103 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index e955b343b9..89460561d5 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -97,11 +97,11 @@ class PdfBuilder if ($el && $el->childElementCount === 0) { $el->parentNode->removeChild($el); // This removes the element completely + continue; } } - $xpath = new \DOMXPath($this->document); $elements = $xpath->query('//*[@data-state="encoded-html"]'); @@ -141,11 +141,10 @@ class PdfBuilder } + unset($temp); } - - return $this; } @@ -727,6 +726,50 @@ class PdfBuilder ]; } + private function parseVisibleElements(array $element): array + { + + $visible_elements = array_filter($element['elements'], function ($el) { + if (isset($el['properties']['visi']) && $el['properties']['visi']) { + return true; + } + return false; + }); + + if (!empty($visible_elements)) { + $first_visible = array_key_first($visible_elements); + $last_visible = array_key_last($visible_elements); + + // Add class to first visible cell + if (!isset($element['elements'][$first_visible]['properties']['class'])) { //@phpstan-ignore-line + $element['elements'][$first_visible]['properties']['class'] = 'left-radius'; + } else { + $element['elements'][$first_visible]['properties']['class'] .= ' left-radius'; + } + + // Add class to last visible cell + if (!isset($element['elements'][$last_visible]['properties']['class'])) { + $element['elements'][$last_visible]['properties']['class'] = 'right-radius'; + } else { + $element['elements'][$last_visible]['properties']['class'] .= ' right-radius'; + } + } + + // Then, filter the elements array + $element['elements'] = array_map(function ($el) { + if (isset($el['properties']['visi'])) { + if ($el['properties']['visi'] === false) { + $el['properties']['style'] = 'display: none;'; + } + unset($el['properties']['visi']); + } + return $el; + }, $element['elements']); + + return $element; + + } + /** * Generate the structure of table body. () @@ -777,57 +820,11 @@ class PdfBuilder } } - $visible_elements = array_filter($element['elements'], function ($el) { - if (isset($el['properties']['visi']) && $el['properties']['visi']) { - return true; - } - return false; - }); - - if (!empty($visible_elements)) { - $first_visible = array_key_first($visible_elements); - $last_visible = array_key_last($visible_elements); - - // Add class to first visible cell - if (!isset($element['elements'][$first_visible]['properties']['class'])) { //@phpstan-ignore-line - $element['elements'][$first_visible]['properties']['class'] = 'left-radius'; - } else { - $element['elements'][$first_visible]['properties']['class'] .= ' left-radius'; - } - - // Add class to last visible cell - if (!isset($element['elements'][$last_visible]['properties']['class'])) { - $element['elements'][$last_visible]['properties']['class'] = 'right-radius'; - } else { - $element['elements'][$last_visible]['properties']['class'] .= ' right-radius'; - } - } - - // Then, filter the elements array - $element['elements'] = array_map(function ($el) { - if (isset($el['properties']['visi'])) { - if ($el['properties']['visi'] === false) { - $el['properties']['style'] = 'display: none;'; - } - unset($el['properties']['visi']); - } - return $el; - }, $element['elements']); + $element = $this->parseVisibleElements($element); $elements[] = $element; } - - - - - - - - - - - return $elements; } @@ -890,50 +887,14 @@ class PdfBuilder } elseif ($cell == '$task.tax_rate3') { $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'task_table-task.tax3-td', 'visi' => $this->visibilityCheck($column_visibility, $cell)]]; } elseif ($cell == '$product.unit_cost' || $cell == '$task.rate') { - $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['style' => 'white-space: nowrap;', 'data-ref' => "{$_type}_table-" . substr($cell, 1) . '-td', 'style' => $this->visibilityCheck($column_visibility, $cell)]]; + $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['style' => 'white-space: nowrap;', 'data-ref' => "{$_type}_table-" . substr($cell, 1) . '-td', 'visi' => $this->visibilityCheck($column_visibility, $cell)]]; } else { $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => "{$_type}_table-" . substr($cell, 1) . '-td', 'visi' => $this->visibilityCheck($column_visibility, $cell)]]; } } } - $visible_elements = array_filter($element['elements'], function ($el) { - if (isset($el['properties']['visi']) && $el['properties']['visi']) { - return true; - } - return false; - }); - - if (!empty($visible_elements)) { - $first_visible = array_key_first($visible_elements); - $last_visible = array_key_last($visible_elements); - - // Add class to first visible cell - if (!isset($element['elements'][$first_visible]['properties']['class'])) { //@phpstan-ignore-line - $element['elements'][$first_visible]['properties']['class'] = 'left-radius'; - } else { - $element['elements'][$first_visible]['properties']['class'] .= ' left-radius'; - } - - // Add class to last visible cell - if (!isset($element['elements'][$last_visible]['properties']['class'])) { - $element['elements'][$last_visible]['properties']['class'] = 'right-radius'; - } else { - $element['elements'][$last_visible]['properties']['class'] .= ' right-radius'; - } - } - - // Then, filter the elements array - $element['elements'] = array_map(function ($el) { - if (isset($el['properties']['visi'])) { - if ($el['properties']['visi'] === false) { - $el['properties']['style'] = 'display: none;'; - } - unset($el['properties']['visi']); - } - return $el; - }, $element['elements']); - + $element = $this->parseVisibleElements($element); $elements[] = $element; } @@ -978,10 +939,6 @@ class PdfBuilder $data[$key][$table_type.'.item'] = $item->item ?? $item->product_key; $data[$key][$table_type.'.service'] = $item->service ?? $item->product_key; - // $data[$key][$table_type.'.product_key'] = is_null(optional($item)->product_key) ? $item->item : $item->product_key; - // $data[$key][$table_type.'.item'] = is_null(optional($item)->item) ? $item->product_key : $item->item; - // $data[$key][$table_type.'.service'] = is_null(optional($item)->service) ? $item->product_key : $item->service; - $currentDateTime = null; if (isset($this->service->config->entity->next_send_date)) { $currentDateTime = Carbon::parse($this->service->config->entity->next_send_date); @@ -2140,22 +2097,22 @@ class PdfBuilder return $this; } - public function updateVariable(string $element, string $variable, string $value) - { - $element = $this->document->getElementById($element); + // public function updateVariable(string $element, string $variable, string $value) + // { + // $element = $this->document->getElementById($element); - $original = $element->nodeValue; + // $original = $element->nodeValue; - $element->nodeValue = ''; + // $element->nodeValue = ''; - $replaced = strtr($original, [$variable => $value]); + // $replaced = strtr($original, [$variable => $value]); - $element->appendChild( - $this->document->createTextNode($replaced) - ); + // $element->appendChild( + // $this->document->createTextNode($replaced) + // ); - return $element; - } + // return $element; + // } public function getEmptyElements(): self { From b731f3b3f744aedf831f6ef9348d857971bba233 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 4 Feb 2025 16:32:20 +1100 Subject: [PATCH 34/43] Clean up and documentation for new PDF generation: --- app/Services/Pdf/PdfBuilder.php | 197 +++++++++++++++++++------------- 1 file changed, 120 insertions(+), 77 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 89460561d5..0bf089e671 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -80,7 +80,15 @@ class PdfBuilder return $this; } - + + /** + * removeEmptyElements + * + * Removes any empty elements from the DomDocument, this improves the vertical spacing of the PDF + * This also decodes any encoded HTML elements. + * + * @return self + */ private function removeEmptyElements(): self { @@ -97,11 +105,11 @@ class PdfBuilder if ($el && $el->childElementCount === 0) { $el->parentNode->removeChild($el); // This removes the element completely - continue; } } + // Decode any HTML based elements. $xpath = new \DOMXPath($this->document); $elements = $xpath->query('//*[@data-state="encoded-html"]'); @@ -117,31 +125,26 @@ class PdfBuilder // Add UTF-8 wrapper and div container $wrappedHtml = '
' . $html . '
'; - // Load the HTML, suppressing any parsing warnings @$temp->loadHTML($wrappedHtml, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); - - // Import the div's contents $imported = $this->document->importNode($temp->getElementsByTagName('div')->item(0), true); - // Clear existing content - more efficient $element->textContent = ''; - // Get the first div's content $divContent = $temp->getElementsByTagName('div')->item(0); if ($divContent) { - // Import all nodes from the temporary div + foreach ($divContent->childNodes as $child) { $imported = $this->document->importNode($child, true); $element->appendChild($imported); } } else { - // Fallback - import the entire content if no div found + $imported = $this->document->importNode($temp->documentElement, true); $element->appendChild($imported); } - unset($temp); + unset($temp); //releases memory immediately rather than at the end of the function } @@ -181,8 +184,15 @@ class PdfBuilder return $this; } - - private function parseTwigElements() + + /** + * parseTwigElements + * + * Parses any ninja tags in the template and processes them via TWIG. + * + * @return self + */ + private function parseTwigElements(): self { $replacements = []; @@ -216,7 +226,13 @@ class PdfBuilder return $this; } - + + /** + * setDocument + * + * @param mixed $document + * @return self + */ public function setDocument($document): self { $this->document = $document; @@ -240,14 +256,30 @@ class PdfBuilder return $this; } - + + /** + * mergeSections + * + * Merges the sections into the sections array. + * + * @param array $section + * @return self + */ private function mergeSections(array $section): self { $this->sections = array_merge($this->sections, $section); return $this; } - + + /** + * setSections + * + * Sets the sections array. + * + * @param mixed $sections + * @return self + */ public function setSections($sections): self { $this->sections = $sections; @@ -373,29 +405,26 @@ class PdfBuilder } /** - * Parent method for building payments table within statement. + * Parent method for building credits table within for statements. * * @return array */ public function statementCreditTable(): array { - if (is_null($this->service->options['credits'])) { - return []; - } - - if (\array_key_exists('show_credits_table', $this->service->options) && $this->service->options['show_credits_table'] === false) { + if (is_null($this->service->options['credits']) || (\array_key_exists('show_credits_table', $this->service->options) && $this->service->options['show_credits_table'] === false)) { return []; } $tbody = []; foreach ($this->service->options['credits'] as $credit) { + $element = ['element' => 'tr', 'elements' => []]; - $element['elements'][] = ['element' => 'td', 'content' => $credit->number]; - $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($credit->date, $this->service->config->client->date_format(), $this->service->config->locale) ?: ' ']; - $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($credit->amount) ?: ' ']; - $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($credit->balance) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $credit->number]; + $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($credit->date, $this->service->config->client->date_format(), $this->service->config->locale) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($credit->amount) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($credit->balance) ?: ' ']; $tbody[] = $element; } @@ -408,9 +437,8 @@ class PdfBuilder } /** - * Parent method for building invoice table totals - * for statements. - * + * Parent method for building credits table totals for statements. + * * @return array */ public function statementCreditTableTotals(): array @@ -434,17 +462,12 @@ class PdfBuilder */ public function statementPaymentTable(): array { - if (is_null($this->service->options['payments'])) { - return []; - } - - if (\array_key_exists('show_payments_table', $this->service->options) && $this->service->options['show_payments_table'] === false) { + if (is_null($this->service->options['payments']) || (\array_key_exists('show_payments_table', $this->service->options) && $this->service->options['show_payments_table'] === false)) { return []; } $tbody = []; - //24-03-2022 show payments per invoice foreach ($this->service->options['invoices'] as $invoice) { foreach ($invoice->payments as $payment) { if ($payment->is_deleted) { @@ -482,10 +505,10 @@ class PdfBuilder } $element = ['element' => 'tr', 'elements' => []]; - $element['elements'][] = ['element' => 'td', 'content' => $invoice->number]; - $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($refund_date, $this->service->config->date_format, $this->service->config->locale) ?: ' ']; - $element['elements'][] = ['element' => 'td', 'content' => ctrans('texts.refund')]; - $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($payment->pivot->refunded) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $invoice->number]; + $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($refund_date, $this->service->config->date_format, $this->service->config->locale) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => ctrans('texts.refund')]; + $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($payment->pivot->refunded) ?: ' ']; $tbody[] = $element; @@ -502,18 +525,14 @@ class PdfBuilder } /** - * Generates the statement payments table + * Generates the payments table totals for statements. * * @return array * */ public function statementPaymentTableTotals(): array { - if (is_null($this->service->options['payments']) || !$this->service->options['payments']->first()) { - return []; - } - - if (\array_key_exists('show_payments_table', $this->service->options) && $this->service->options['show_payments_table'] === false) { + if (is_null($this->service->options['payments']) || !$this->service->options['payments']->first() || (\array_key_exists('show_payments_table', $this->service->options) && $this->service->options['show_payments_table'] === false)) { return []; } @@ -523,15 +542,16 @@ class PdfBuilder ['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), $this->service->config->formatMoney($this->payment_amount_total))], ]; } - + + /** + * Generates the unapplied payments table totals for statements. + * + * @return array + */ public function statementUnappliedPaymentTableTotals(): array { - if (is_null($this->service->options['unapplied']) || !$this->service->options['unapplied']->first()) { - return []; - } - - if (\array_key_exists('show_payments_table', $this->service->options) && $this->service->options['show_payments_table'] === false) { + if (is_null($this->service->options['unapplied']) || !$this->service->options['unapplied']->first() || (\array_key_exists('show_payments_table', $this->service->options) && $this->service->options['show_payments_table'] === false)) { return []; } @@ -545,18 +565,14 @@ class PdfBuilder /** - * Generates the statement unapplied payments table + * Generates the unapplied payments table for statements. * * @return array * */ public function statementUnappliedPaymentTable(): array { - if (is_null($this->service->options['unapplied']) || !$this->service->options['unapplied']->first()) { - return []; - } - - if (\array_key_exists('show_payments_table', $this->service->options) && $this->service->options['show_payments_table'] === false) { + if (is_null($this->service->options['unapplied']) || !$this->service->options['unapplied']->first() || (\array_key_exists('show_payments_table', $this->service->options) && $this->service->options['show_payments_table'] === false)) { return []; } @@ -571,10 +587,10 @@ class PdfBuilder $element = ['element' => 'tr', 'elements' => []]; - $element['elements'][] = ['element' => 'td', 'content' => $unapplied_payment->number]; - $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($unapplied_payment->date, $this->service->config->date_format, $this->service->config->locale) ?: ' ']; - $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($unapplied_payment->amount) ?: ' ']; - $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($unapplied_payment->amount - $unapplied_payment->applied) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $unapplied_payment->number]; + $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($unapplied_payment->date, $this->service->config->date_format, $this->service->config->locale) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($unapplied_payment->amount) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($unapplied_payment->amount - $unapplied_payment->applied) ?: ' ']; $tbody[] = $element; @@ -589,7 +605,7 @@ class PdfBuilder } /** - * Generates the statement aging table + * Generates the aging table for statements. * * @return array * @@ -711,11 +727,11 @@ class PdfBuilder foreach ($this->service->options['invoices'] as $invoice) { $element = ['element' => 'tr', 'elements' => []]; - $element['elements'][] = ['element' => 'td', 'content' => $invoice->number]; - $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->date, $date_format, $this->service->config->locale) ?: ' ']; - $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->due_date, $date_format, $this->service->config->locale) ?: ' ']; - $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($invoice->amount) ?: ' ']; - $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($invoice->balance) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $invoice->number]; + $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->date, $date_format, $this->service->config->locale) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->due_date, $date_format, $this->service->config->locale) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($invoice->amount) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($invoice->balance) ?: ' ']; $tbody[] = $element; } @@ -725,7 +741,14 @@ class PdfBuilder ['element' => 'tbody', 'elements' => $tbody], ]; } - + + /** + * Filters the visible elements for a table row and also + * assigned the left and right radius classes to the first and last cells + * + * @param array $element + * @return array + */ private function parseVisibleElements(array $element): array { @@ -791,9 +814,8 @@ class PdfBuilder return []; } - - //2025-01-28 Regular Table Body $_type = Str::startsWith($type, '$') ? ltrim($type, '$') : $type; + $column_visibility = $this->getColumnVisibility($this->service->config->entity->line_items, $_type); if ($type == PdfService::DELIVERY_NOTE) { @@ -835,7 +857,6 @@ class PdfBuilder $table_type = "product_quote_columns"; } - foreach ($items as $row) { $element = ['element' => 'tr', 'elements' => []]; //checks if we have custom columns in the options array with key $product/$task - looks like unused functionality @@ -1013,7 +1034,15 @@ class PdfBuilder return $data; } - + + /** + * Filters the visible columns for a table row. + * + * @param array $items + * @param string $type_id + * + * @return array + */ private function getColumnVisibility(array $items, string $type_id): array { @@ -1147,7 +1176,14 @@ class PdfBuilder return $elements; } - + + /** + * visibilityCheck + * + * @param array $column_visibility + * @param string $column + * @return bool + */ private function visibilityCheck(array $column_visibility, string $column): bool { if(!$this->service->config->settings->hide_empty_columns_on_pdf){ @@ -1716,9 +1752,11 @@ class PdfBuilder /** - * Generates the client delivery - * details array - * + * Generates the client delivery details array + * + * We also override some variables here to ensure they are + * appropriate for the delivery note. + * * @return array * */ @@ -1783,7 +1821,12 @@ class PdfBuilder return $elements; } - + + /** + * Generates the shipping details section + * + * @return array + */ public function shippingDetails(): array { $elements = []; @@ -1811,7 +1854,7 @@ class PdfBuilder */ public function deliveryNoteTable(): array { - /* Static array of delivery note columns*/ + $thead = [ ['element' => 'th', 'content' => '$item_label', 'properties' => ['data-ref' => 'delivery_note-item_label']], ['element' => 'th', 'content' => '$description_label', 'properties' => ['data-ref' => 'delivery_note-description_label']], From 7fd08e02fe8ca44bcae40280fe7c6ac55d522e8c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 4 Feb 2025 17:14:55 +1100 Subject: [PATCH 35/43] Formatting designs --- resources/views/pdf-designs/business.html | 6 - tests/Pdf/PdfServiceTest.php | 273 ++++++++++++++-------- 2 files changed, 179 insertions(+), 100 deletions(-) diff --git a/resources/views/pdf-designs/business.html b/resources/views/pdf-designs/business.html index ed0d574b76..dc6ef04a82 100644 --- a/resources/views/pdf-designs/business.html +++ b/resources/views/pdf-designs/business.html @@ -99,7 +99,6 @@ flex-direction: column; line-height: var(--line-height); vertical-align: top; - margin-left: 1rem; } #client-details > p:nth-child(2) { @@ -196,7 +195,6 @@ gap: 80px; padding-top: 0.5rem; padding-bottom: 0.8rem; - padding-left: 0.7rem; /*page-break-inside:auto; this may cause weird breaking*/ overflow: visible !important; } @@ -228,10 +226,6 @@ padding-right: 1rem; } - [data-ref="total_table-footer"] { - padding-left: 0.8rem - } - #footer { margin-top: 30px; } diff --git a/tests/Pdf/PdfServiceTest.php b/tests/Pdf/PdfServiceTest.php index e7a1bfcf31..bf612e17fd 100644 --- a/tests/Pdf/PdfServiceTest.php +++ b/tests/Pdf/PdfServiceTest.php @@ -12,10 +12,15 @@ namespace Tests\Pdf; -use App\Services\Pdf\PdfConfiguration; -use App\Services\Pdf\PdfService; -use Tests\MockAccountData; use Tests\TestCase; +use App\Models\Client; +use App\Models\Company; +use App\Models\Invoice; +use Tests\MockAccountData; +use App\Models\ClientContact; +use App\Services\Pdf\PdfService; +use App\DataMapper\CompanySettings; +use App\Services\Pdf\PdfConfiguration; /** * @@ -29,11 +34,182 @@ class PdfServiceTest extends TestCase private string $min_pdf_variables = '{"client_details":["$client.name","$client.vat_number","$client.address1","$client.city_state_postal","$client.country"],"vendor_details":["$vendor.name","$vendor.vat_number","$vendor.address1","$vendor.city_state_postal","$vendor.country"],"purchase_order_details":["$purchase_order.number","$purchase_order.date","$purchase_order.total"],"company_details":["$company.name","$company.address1","$company.city_state_postal"],"company_address":["$company.name","$company.website"],"invoice_details":["$invoice.number","$invoice.date","$invoice.due_date","$invoice.balance"],"quote_details":["$quote.number","$quote.date","$quote.valid_until"],"credit_details":["$credit.date","$credit.number","$credit.balance"],"product_columns":["$product.item","$product.description","$product.line_total"],"product_quote_columns":["$product.item","$product.description","$product.unit_cost","$product.quantity","$product.discount","$product.tax","$product.line_total"],"task_columns":["$task.description","$task.rate","$task.line_total"],"total_columns":["$total","$total_taxes","$outstanding"],"statement_invoice_columns":["$invoice.number","$invoice.date","$due_date","$total","$balance"],"statement_payment_columns":["$invoice.number","$payment.date","$method","$statement_amount"],"statement_credit_columns":["$credit.number","$credit.date","$total","$credit.balance"],"statement_details":["$statement_date","$balance"],"delivery_note_columns":["$product.item","$product.description","$product.quantity"],"statement_unapplied_columns":["$payment.number","$payment.date","$payment.amount","$payment.payment_balance"]}'; + private string $fake_email; + protected function setUp(): void { parent::setUp(); $this->makeTestData(); + + $this->fake_email = $this->faker->email(); + + } + + public function testMaxInvoiceFields() + { + $settings = CompanySettings::defaults(); + $settings->pdf_variables = json_decode($this->max_pdf_variables); + $settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png'; + $settings->website = 'www.invoiceninja.com'; + $settings->name = 'Invoice Ninja'; + $settings->address1 = 'Address 1'; + $settings->address2 = 'Address 2'; + $settings->city = 'City'; + $settings->state = 'State'; + $settings->postal_code = 'Postal Code'; + $settings->phone = '555-343-2323'; + $settings->email = $this->fake_email; + $settings->country_id = '840'; + $settings->vat_number = 'vat number'; + $settings->id_number = 'id number'; + $settings->use_credits_payment = 'always'; + $settings->timezone_id = '1'; + $settings->entity_send_time = 0; + + $company = Company::factory()->create([ + 'account_id' => $this->account->id, + 'settings' => $settings + ]); + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id + ]); + + $contact = ClientContact::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'client_id' => $client->id, + 'is_primary' => true, + 'first_name' => 'John', + 'last_name' => 'Doe', + 'email' => 'john@doe.com', + 'phone' => '1234567890', + 'send_email' => true, + ]); + + $invoice = Invoice::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'client_id' => $client->id, + 'status_id' => Invoice::STATUS_DRAFT, + ]); + + $invoice = $invoice->calc()->getInvoice(); + $invoice = $invoice->service()->createInvitations()->markSent()->save(); + $invoice = $invoice->fresh(); + + + $this->assertGreaterThan(0, $invoice->invitations()->count()); + + \App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) use($invoice) { + + $invoice->design_id = $design->id; + $invoice->save(); + $invoice = $invoice->fresh(); + + $service = (new PdfService($invoice->invitations()->first()))->boot(); + $pdf = $service->getPdf(); + + $this->assertNotNull($pdf); + + \Illuminate\Support\Facades\Storage::put('/pdf/max_fields_' . $design->name.'.pdf', $pdf); + + }); + + } + + public function testMinInvoiceFields() + { + + $settings = CompanySettings::defaults(); + $settings->pdf_variables = json_decode($this->min_pdf_variables); + $settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png'; + $settings->website = 'www.invoiceninja.com'; + $settings->name = 'Invoice Ninja'; + $settings->address1 = 'Address 1'; + $settings->address2 = 'Address 2'; + $settings->city = 'City'; + $settings->state = 'State'; + $settings->postal_code = 'Postal Code'; + $settings->phone = '555-343-2323'; + $settings->email = $this->fake_email; + $settings->country_id = '840'; + $settings->vat_number = 'vat number'; + $settings->id_number = 'id number'; + $settings->use_credits_payment = 'always'; + $settings->timezone_id = '1'; + $settings->entity_send_time = 0; + + $company = Company::factory()->create([ + 'account_id' => $this->account->id, + 'settings' => $settings + ]); + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id + ]); + + $contact = ClientContact::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'client_id' => $client->id, + 'is_primary' => true, + 'first_name' => 'John', + 'last_name' => 'Doe', + 'email' => 'john@doe.com', + 'phone' => '1234567890', + 'send_email' => true, + ]); + + $invoice = Invoice::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'client_id' => $client->id, + 'status_id' => Invoice::STATUS_DRAFT, + ]); + + $invoice = $invoice->calc()->getInvoice(); + $invoice = $invoice->service()->createInvitations()->markSent()->save(); + + \App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) use ($invoice) { + + $invoice->design_id = $design->id; + $invoice->save(); + $invoice = $invoice->fresh(); + + $service = (new PdfService($invoice->invitations->first()))->boot(); + $pdf = $service->getPdf(); + + $this->assertNotNull($pdf); + + \Illuminate\Support\Facades\Storage::put('/pdf/min_fields_' . $design->name.'.pdf', $pdf); + + }); + + } + + + public function testStatementPdfGeneration() + { + + $pdf = $this->client->service()->statement([ + 'client_id' => $this->client->hashed_id, + 'start_date' => '2000-01-01', + 'end_date' => '2023-01-01', + 'show_aging_table' => true, + 'show_payments_table' => true, + 'status' => 'all' + ]); + + + $this->assertNotNull($pdf); + + \Illuminate\Support\Facades\Storage::put('/pdf/statement.pdf', $pdf); + + } public function testMultiDesignGeneration() @@ -80,97 +256,6 @@ class PdfServiceTest extends TestCase } - public function testMaxInvoiceFields() - { - $max_settings = json_decode($this->max_pdf_variables); - - $settings = $this->company->settings; - $settings->pdf_variables = $max_settings; - - $this->company->settings = $settings; - $this->company->save(); - - $this->invoice->company->settings->pdf_variables = $max_settings; - - \App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) use ($max_settings) { - - - $this->invoice->design_id = $design->id; - $this->invoice->client->settings->pdf_variables = $max_settings; - $this->invoice->push(); - $this->invoice = $this->invoice->fresh(); - - $invitation = $this->invoice->invitations->first(); - $invitation->setRelation('company', $this->company); - - $service = (new PdfService($invitation))->boot(); - $pdf = $service->getPdf(); - - $this->assertNotNull($pdf); - - \Illuminate\Support\Facades\Storage::put('/pdf/max_fields_' . $design->name.'.pdf', $pdf); - - }); - - - } - - public function testMinInvoiceFields() - { - $min_settings = json_decode($this->min_pdf_variables); - - $settings = $this->company->settings; - $settings->pdf_variables = $min_settings; - - $this->company->settings = $settings; - $this->company->save(); - - $this->invoice->company->settings->pdf_variables = $min_settings; - - \App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) use ($min_settings) { - - - $this->invoice->design_id = $design->id; - $this->invoice->client->settings = $min_settings; - $this->invoice->push(); - $this->invoice = $this->invoice->fresh(); - - $invitation = $this->invoice->invitations->first(); - $invitation = $invitation->fresh(); - - $service = (new PdfService($invitation))->boot(); - $pdf = $service->getPdf(); - - $this->assertNotNull($pdf); - - \Illuminate\Support\Facades\Storage::put('/pdf/min_fields_' . $design->name.'.pdf', $pdf); - - }); - - - } - - - public function testStatementPdfGeneration() - { - - $pdf = $this->client->service()->statement([ - 'client_id' => $this->client->hashed_id, - 'start_date' => '2000-01-01', - 'end_date' => '2023-01-01', - 'show_aging_table' => true, - 'show_payments_table' => true, - 'status' => 'all' - ]); - - - $this->assertNotNull($pdf); - - \Illuminate\Support\Facades\Storage::put('/pdf/statement.pdf', $pdf); - - - } - public function testPdfGeneration() { From c381441a96522b7225f5a1c4755a51d39d5ef52a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 4 Feb 2025 17:22:35 +1100 Subject: [PATCH 36/43] Formatting designs --- resources/views/pdf-designs/calm.html | 7 ++----- resources/views/pdf-designs/clean.html | 8 ++++++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/resources/views/pdf-designs/calm.html b/resources/views/pdf-designs/calm.html index bb283c4979..a80a294857 100644 --- a/resources/views/pdf-designs/calm.html +++ b/resources/views/pdf-designs/calm.html @@ -42,7 +42,6 @@ .header-wrapper { display: grid; margin-top: 2rem; - gap: 20px; grid-template-columns: 2fr 1fr 1fr; grid-template-areas: "a b c"; grid-auto-columns: minmax(0, 5fr); @@ -53,7 +52,6 @@ .header-wrapper2 { display: grid; margin-top: 2rem; - gap: 20px; grid-template-columns: 2fr 2fr auto; grid-template-areas: "a b c"; grid-auto-columns: minmax(0, 5fr); @@ -62,7 +60,6 @@ } .company-logo { - /* max-width: 65%;*/ max-width: $company_logo_size; } @@ -87,12 +84,14 @@ display: flex; flex-direction: column; line-height: var(--line-height); + margin-left: 20px; } .header-wrapper #company-details { display: flex; flex-direction: column; line-height: var(--line-height); + margin-left: 20px; } .header-wrapper #entity-details { @@ -184,7 +183,6 @@ display: grid; grid-template-columns: 1.5fr 1fr; padding-top: .5rem; - padding-left: 1rem; gap: 80px; page-break-inside: auto; overflow: visible !important; @@ -227,7 +225,6 @@ } [data-ref="total_table-footer"] { - padding-left: 1rem; padding-right: 1rem; } diff --git a/resources/views/pdf-designs/clean.html b/resources/views/pdf-designs/clean.html index a9ad0d6f01..43be53fa46 100644 --- a/resources/views/pdf-designs/clean.html +++ b/resources/views/pdf-designs/clean.html @@ -104,6 +104,8 @@ text-align: left; margin-right: 20px; line-height: var(--line-height) !important; + justify-self: start; + align-self: start; } #entity-details>tr, @@ -118,6 +120,8 @@ flex-direction: column; line-height: var(--line-height); padding-right: 30px; + justify-self: center; + align-self: start; } #client-details> :first-child { @@ -128,6 +132,8 @@ display: $show_shipping_address; flex-direction: column; line-height: var(--line-height); + justify-self: end; + align-self: start; } [data-ref="table"] { @@ -183,7 +189,6 @@ grid-template-columns: 2fr 1fr; padding-top: 0rem; padding-right: 1rem; - padding-left: 1rem; gap: 80px; page-break-inside: auto; overflow: visible !important; @@ -216,7 +221,6 @@ #footer { margin-top: 10px; - margin-left: 1rem; } /** Markdown-specific styles. **/ From 2266b69c221dfcea92ee04be75a434362d9ca543 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 4 Feb 2025 17:40:25 +1100 Subject: [PATCH 37/43] Formatting designs --- resources/views/pdf-designs/creative.html | 5 -- resources/views/pdf-designs/elegant.html | 57 +++++++++++------------ resources/views/pdf-designs/hipster.html | 6 +-- resources/views/pdf-designs/modern.html | 2 +- resources/views/pdf-designs/plain.html | 3 +- resources/views/pdf-designs/playful.html | 4 +- 6 files changed, 32 insertions(+), 45 deletions(-) diff --git a/resources/views/pdf-designs/creative.html b/resources/views/pdf-designs/creative.html index e89798f826..31c7116ec8 100644 --- a/resources/views/pdf-designs/creative.html +++ b/resources/views/pdf-designs/creative.html @@ -207,8 +207,6 @@ grid-template-columns: 2fr 1fr; padding-top: 1rem; margin-right: .75rem; - padding-left: 0.5rem; - gap: 80px; page-break-inside:auto; overflow: visible !important; } @@ -234,7 +232,6 @@ } #table-totals > * > * { - padding-left: 0.5rem; } #table-totals > * > :last-child { @@ -243,12 +240,10 @@ } [data-ref="total_table-footer"] { - padding-left: 0.5rem; padding-right:0.8rem; } #footer { - padding-left: 1rem; margin-top: 30px; } diff --git a/resources/views/pdf-designs/elegant.html b/resources/views/pdf-designs/elegant.html index ee91dfd498..470d35ca70 100644 --- a/resources/views/pdf-designs/elegant.html +++ b/resources/views/pdf-designs/elegant.html @@ -133,8 +133,7 @@ [data-ref="table"] { margin-top: 3rem; - margin-bottom: 5 - px; + margin-bottom: 5px; min-width: 100%; table-layout: fixed; overflow-wrap: break-word; @@ -187,7 +186,6 @@ display: grid; grid-template-columns: 2fr 1fr; padding-top: 0.5rem; - padding-left: 1rem; gap: 80px; page-break-inside:auto; overflow: visible !important; @@ -220,12 +218,10 @@ } [data-ref="total_table-footer"] { - padding-left: 1rem } #footer { margin-top: 30px; - padding-left: 1rem; } /** Markdown-specific styles. **/ @@ -269,6 +265,7 @@ overflow-wrap: break-word; min-width: 100px !important; } + .stamp { transform: rotate(12deg); color: #555; @@ -362,44 +359,44 @@
-
- -
-
-
-
-
+
+ +
+
+
+
+

$to_label

-
-
+
+

$shipping_label

-
-
+
+

$from_label

-
+

$details_label

-
-
-
-
-
-
-
-
-
-
-
-
-
$status_logo
+
+
+
+
+
+
+
+
+
+
+
+
+
$status_logo
diff --git a/resources/views/pdf-designs/hipster.html b/resources/views/pdf-designs/hipster.html index 9711cbb9d1..cd676f8d63 100644 --- a/resources/views/pdf-designs/hipster.html +++ b/resources/views/pdf-designs/hipster.html @@ -224,7 +224,6 @@ grid-template-columns: 2fr 1fr; padding-top: 0.5rem; margin-right: 0rem; - gap: 80px; page-break-inside:auto; overflow: visible !important; } @@ -322,10 +321,7 @@ width: 13%; } - - - - [data-ref="task_table-task.line_total-th"], + [data-ref="task_table-task.line_total-th"], [data-ref="task_table-task.line_total-td"] { text-align: right; width: 13%; diff --git a/resources/views/pdf-designs/modern.html b/resources/views/pdf-designs/modern.html index b2fc774524..eaf9be8013 100644 --- a/resources/views/pdf-designs/modern.html +++ b/resources/views/pdf-designs/modern.html @@ -228,7 +228,7 @@ display: grid; grid-template-columns: 2fr 1fr; padding-top: 0.5rem; - padding-left: 2.5rem; + padding-left: 2rem; padding-right: 3rem; gap: 80px; } diff --git a/resources/views/pdf-designs/plain.html b/resources/views/pdf-designs/plain.html index 07b0bdc81d..2517cd8aa2 100644 --- a/resources/views/pdf-designs/plain.html +++ b/resources/views/pdf-designs/plain.html @@ -147,7 +147,6 @@ display: grid; grid-template-columns: 2fr 1fr; padding-top: .5rem; - padding-left: .5rem; margin-right: .75rem; gap: 80px; page-break-inside: auto; @@ -187,7 +186,7 @@ } [data-ref="total_table-footer"] { - padding-left: 1rem + padding-left: 0rem; } #footer { diff --git a/resources/views/pdf-designs/playful.html b/resources/views/pdf-designs/playful.html index ed178bef50..977dc1bdcc 100644 --- a/resources/views/pdf-designs/playful.html +++ b/resources/views/pdf-designs/playful.html @@ -116,7 +116,7 @@ .contacts-wrapper .company-info { margin-top: 1rem; - padding: 1rem; + padding-top: 1rem; border-top: 1px solid var(--primary-color); display: grid; grid-template-columns: 1fr 1fr; @@ -125,7 +125,7 @@ .contacts-wrapper #client-details { margin-top: 1rem; - padding: 1rem; + padding-top: 1rem; border-top: 1px solid var(--primary-color); } From fb293c0a1495be1241627dcd727a58a35624294e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 5 Feb 2025 11:24:20 +1100 Subject: [PATCH 38/43] Fixes for designs --- app/Http/Controllers/SetupController.php | 2 +- resources/views/pdf-designs/calm.html | 17 ++++++-- resources/views/pdf-designs/clean.html | 2 +- resources/views/pdf-designs/plain.html | 49 +----------------------- resources/views/pdf-designs/tech.html | 47 +---------------------- tests/Pdf/PdfServiceTest.php | 2 + 6 files changed, 20 insertions(+), 99 deletions(-) diff --git a/app/Http/Controllers/SetupController.php b/app/Http/Controllers/SetupController.php index 2feaa7073b..28b6730510 100644 --- a/app/Http/Controllers/SetupController.php +++ b/app/Http/Controllers/SetupController.php @@ -78,7 +78,7 @@ class SetupController extends Controller $db_database = $request->input('db_database'); $db_username = $request->input('db_username'); $db_password = $request->input('db_password'); - $mail_port = $request->input('mail_port'); + $mail_port = $request->input('mail_port',0); $encryption = $request->input('encryption'); $mail_host = $request->input('mail_host'); $mail_username = $request->input('mail_username'); diff --git a/resources/views/pdf-designs/calm.html b/resources/views/pdf-designs/calm.html index a80a294857..fbd855aadb 100644 --- a/resources/views/pdf-designs/calm.html +++ b/resources/views/pdf-designs/calm.html @@ -56,7 +56,13 @@ grid-template-areas: "a b c"; grid-auto-columns: minmax(0, 5fr); grid-auto-flow: column; - justify-content: left; + justify-content: space-between; + + } + + .header-wrapper2 .entity-container { + justify-self: end; + align-self: start; } .company-logo { @@ -95,7 +101,6 @@ } .header-wrapper #entity-details { - padding-right: 0.5rem; text-align: left; line-height: var(--line-height); width: 100%; @@ -112,6 +117,10 @@ background-color: #e6e6e6; } + .entity-label { + padding-left: 1rem; + } + #entity-details { text-align: left; width: 100%; @@ -120,7 +129,7 @@ #entity-details th { font-weight: normal; line-height: 1.5rem; - padding-right: 2rem; + padding-left: 1rem; } #client-details { @@ -404,7 +413,7 @@
-
+

$entity_label

diff --git a/resources/views/pdf-designs/clean.html b/resources/views/pdf-designs/clean.html index 43be53fa46..dde2c19325 100644 --- a/resources/views/pdf-designs/clean.html +++ b/resources/views/pdf-designs/clean.html @@ -93,7 +93,7 @@ .client-and-entity-wrapper { padding: 1rem; display: grid; - grid-template-columns: 1fr auto 1fr; + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: start; border-top: 1px solid #d8d8d8; border-bottom: 1px solid #d8d8d8; diff --git a/resources/views/pdf-designs/plain.html b/resources/views/pdf-designs/plain.html index 2517cd8aa2..fe24621331 100644 --- a/resources/views/pdf-designs/plain.html +++ b/resources/views/pdf-designs/plain.html @@ -73,7 +73,7 @@ line-height: var(--line-height) !important; white-space: nowrap; border: 0px solid #000; - float:right; + float:right; } #entity-details>tr, @@ -111,7 +111,6 @@ min-width: 100%; table-layout: fixed; overflow-wrap: break-word; - margin-bottom: 5px; } .task-time-details { @@ -146,7 +145,6 @@ margin-top: 0rem; display: grid; grid-template-columns: 2fr 1fr; - padding-top: .5rem; margin-right: .75rem; gap: 80px; page-break-inside: auto; @@ -241,54 +239,11 @@ [data-ref='product_table-product.description-td'], [data-ref='task_table-task.description-th']{ - min-width: 100px !important; + min-width: 150px !important; overflow-wrap: break-word; } - [data-ref='product_table-product.item-th'], - [data-ref='product_table-product.unit_cost-th'], - [data-ref='task_table-task.service-th'], - [data-ref='task_table-task.cost-th']{ - width: 12%; - } - - [data-ref='product_table-product.quantity-th'], - [data-ref='task_table-task.hours-th'], - [data-ref='task_table-task.quantity-th'], - [data-ref='product_table-product.product1-th'], - [data-ref='product_table-product.product2-th'], - [data-ref='product_table-product.product3-th'], - [data-ref='product_table-product.product4-th'] { - width: 10%; - } - - [data-ref='product_table-product.tax1-th'] { - width: 6%; - } - - [data-ref='product_table-product.line_total-th'], - [data-ref='product_table-product.line_total-td'], - [data-ref='task_table-task.line_total-th'], - [data-ref='task_table-task.line_total-td'] { - width: 12%; - text-align: right; - } - - [data-ref='task_table-task.discount-th'], - [data-ref='task_table-task.task1-th'], - [data-ref='task_table-task.task2-th'], - [data-ref='task_table-task.task3-th'], - [data-ref='task_table-task.task4-th'] { - width: 10%; - } - - - - [data-ref='task_table-task.tax1-th'] { - width: 10%; - } - .stamp { transform: rotate(12deg); color: #555; diff --git a/resources/views/pdf-designs/tech.html b/resources/views/pdf-designs/tech.html index 8b2343c561..91b523abb2 100644 --- a/resources/views/pdf-designs/tech.html +++ b/resources/views/pdf-designs/tech.html @@ -279,7 +279,7 @@ [data-ref='product_table-product.description-td'], [data-ref='task_table-task.description-td']{ - min-width: 100px !important; + min-width: 150px !important; overflow-wrap: break-word; } @@ -288,51 +288,6 @@ width: 10%; } - [data-ref='product_table-product.unit_cost-th'], - [data-ref='task_table-task.cost-th'] { - width: 13%; - } - - [data-ref='product_table-product.quantity-th'], - [data-ref='task_table-task.hours-th'], - [data-ref='product_table-product.product1-th'], - [data-ref='product_table-product.product2-th'], - [data-ref='product_table-product.product3-th'], - [data-ref='product_table-product.product4-th'] { - width: 8%; - } - - [data-ref='product_table-product.tax1-th'] { - width: 6%; - } - - [data-ref='product_table-product.line_total-th'], - [data-ref='product_table-product.line_total-td'] { - width: 13%; - text-align: right; - } - - [data-ref='task_table-task.discount-th'], - [data-ref='task_table-task.quantity-th'], - [data-ref='task_table-task.task1-th'], - [data-ref='task_table-task.task2-th'], - [data-ref='task_table-task.task3-th'], - [data-ref='task_table-task.task4-th'] { - width: 10%; - } - - - [data-ref='task_table-task.tax1-th'] { - width: 10%; - } - - [data-ref='task_table-task.line_total-th'], - [data-ref='task_table-task.line_total-td'] { - width: 13%; - text-align: right !important; - } - - .stamp { transform: rotate(12deg); color: #555; diff --git a/tests/Pdf/PdfServiceTest.php b/tests/Pdf/PdfServiceTest.php index bf612e17fd..be422adc08 100644 --- a/tests/Pdf/PdfServiceTest.php +++ b/tests/Pdf/PdfServiceTest.php @@ -66,6 +66,7 @@ class PdfServiceTest extends TestCase $settings->use_credits_payment = 'always'; $settings->timezone_id = '1'; $settings->entity_send_time = 0; + $settings->hide_empty_columns_on_pdf = true; $company = Company::factory()->create([ 'account_id' => $this->account->id, @@ -141,6 +142,7 @@ class PdfServiceTest extends TestCase $settings->use_credits_payment = 'always'; $settings->timezone_id = '1'; $settings->entity_send_time = 0; + $settings->hide_empty_columns_on_pdf = true; $company = Company::factory()->create([ 'account_id' => $this->account->id, From d4c349ecae03dcd7956fbc07204843c18e74ebaa Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 5 Feb 2025 14:03:11 +1100 Subject: [PATCH 39/43] Design fixes --- app/Services/Pdf/PdfBuilder.php | 4 + resources/views/pdf-designs/bold.html | 138 +-- resources/views/pdf-designs/business.html | 188 ++-- resources/views/pdf-designs/calm.html | 14 +- resources/views/pdf-designs/clean.html | 19 +- resources/views/pdf-designs/creative.html | 231 +++-- resources/views/pdf-designs/elegant.html | 215 ++-- resources/views/pdf-designs/hipster.html | 286 +++-- resources/views/pdf-designs/modern.html | 975 +++++++++--------- resources/views/pdf-designs/plain.html | 18 +- resources/views/pdf-designs/playful.html | 207 ++-- resources/views/pdf-designs/tech.html | 234 +++-- resources/views/pdf-designs/tidy_receipt.html | 3 +- tests/Pdf/PdfServiceTest.php | 312 +++++- 14 files changed, 1534 insertions(+), 1310 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 0bf089e671..5f06144efc 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -1601,6 +1601,10 @@ class PdfBuilder */ public function taskTable(): array { + + if($this->service->config->entity instanceof \App\Models\PurchaseOrder) + return []; + $task_items = collect($this->service->config->entity->line_items)->filter(function ($item) { return $item->type_id == 2; }); diff --git a/resources/views/pdf-designs/bold.html b/resources/views/pdf-designs/bold.html index 56bca2f171..acb15a9615 100644 --- a/resources/views/pdf-designs/bold.html +++ b/resources/views/pdf-designs/bold.html @@ -15,10 +15,12 @@ zoom: 80%; margin: 0; padding: 0; - + } - table tr td, table tr, th { + table tr td, + table tr, + th { font-size: $font_size !important; } @@ -38,11 +40,11 @@ padding: 0; } - #spacer-table > * > tr > td { + #spacer-table>*>tr>td { padding: 0; } - #spacer-table{ + #spacer-table { width: 100%; } @@ -59,7 +61,8 @@ } - #header, #header-spacer { + #header, + #header-spacer { height: 160px; padding-top: 2rem; padding-bottom: 2rem; @@ -67,6 +70,7 @@ padding-right: 1rem; margin-bottom: 1rem; } + .company-logo { max-width: $company_logo_size; object-fit: contain; @@ -87,7 +91,15 @@ } #client-details { - padding-right:1rem; + padding-right: 1rem; + display: flex; + flex-direction: column; + line-height: var(--line-height) !important; + padding-left: 1rem; + } + + #vendor-details { + padding-right: 1rem; display: flex; flex-direction: column; line-height: var(--line-height) !important; @@ -100,16 +112,16 @@ line-height: var(--line-height) !important; } - #client-details > :first-child { + #client-details> :first-child { font-weight: bold; } .client-entity-wrapper { display: grid; grid-template-columns: 2fr 1fr; - + } - + .client-wrapper-left-side { display: flex; } @@ -125,7 +137,7 @@ color: white !important; } - #entity-details > tr, + #entity-details>tr, #entity-details th { font-weight: normal; line-height: var(--line-height) !important; @@ -140,8 +152,8 @@ margin-bottom: 0px; } - [data-ref="table"]:last-child{ - margin-bottom:0; + [data-ref="table"]:last-child { + margin-bottom: 0; } .task-time-details { @@ -150,11 +162,11 @@ color: grey; } - [data-ref="table"] > thead { + [data-ref="table"]>thead { text-align: left; } - [data-ref="table"] > thead > tr > th { + [data-ref="table"]>thead>tr>th { padding-top: 1.5rem; padding-bottom: 1.5rem; padding-left: 0; @@ -163,7 +175,7 @@ } - [data-ref="table"] > tbody > tr > td { + [data-ref="table"]>tbody>tr>td { padding-top: 1.5rem; padding-bottom: 1.5rem; padding-left: 0; @@ -175,21 +187,21 @@ } th.right-radius { - text-align:right !important; + text-align: right !important; padding-right: 1rem !important; } - + td.right-radius { text-align: right !important; padding-right: 1rem !important; } - td.left-radius{ + td.left-radius { font-weight: bold; padding-left: 1rem !important; } - [data-ref="table"] > tbody > tr:nth-child(odd) { + [data-ref="table"]>tbody>tr:nth-child(odd) { background-color: #ebebeb; } @@ -207,27 +219,27 @@ gap: 80px; } - #table-totals .totals-table-right-side > * { + #table-totals .totals-table-right-side>* { display: grid; grid-template-columns: 1fr 1fr; } - #table-totals > .totals-table-right-side > * > :nth-child(1) { + #table-totals>.totals-table-right-side>*> :nth-child(1) { text-align: $dir_text_align; margin-top: .75rem; } - #table-totals > .totals-table-right-side > * > :not([hidden]) ~ :not([hidden]) { + #table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse))); margin-bottom: calc(.75rem * var(--tw-space-y-reverse)); } - #table-totals > .totals-table-right-side > * > :nth-child(2) { + #table-totals>.totals-table-right-side>*> :nth-child(2) { text-align: right; } - #table-totals > * > :last-child { + #table-totals>*> :last-child { text-align: right; padding-right: 1.5rem; } @@ -251,7 +263,8 @@ color: white; } - #footer, #footer-spacer { + #footer, + #footer-spacer { height: 160px; padding: 1rem 1rem; margin-top: 1rem; @@ -261,7 +274,7 @@ padding-top: 0.5rem } - [data-ref="footer_content"]{ + [data-ref="footer_content"] { padding-right: 1rem; margin-right: 1rem; } @@ -296,24 +309,24 @@ white-space: nowrap; } - #statement-invoice-table-totals > p { + #statement-invoice-table-totals>p { margin-right: 2rem; margin-top: 1rem; } [data-ref='product_table-product.description-th'], [data-ref='product_table-product.description-td'] { - width:30%; - overflow-wrap: break-word; - padding-right: 1rem !important; + width: 30%; + overflow-wrap: break-word; + padding-right: 1rem !important; } [data-ref='product_table-product.tax1-th'], [data-ref='product_table-product.tax1-td'], [data-ref='product_table-product.discount-th'], [data-ref='product_table-product.discount-td'] { - width:9%; - overflow-wrap: break-word; + width: 9%; + overflow-wrap: break-word; } .right-radius { @@ -321,7 +334,7 @@ } .stamp { - transform: rotate(12deg); + transform: rotate(12deg); color: #555; font-size: 3rem; font-weight: 700; @@ -332,24 +345,24 @@ border-radius: 1rem; font-family: 'Courier'; mix-blend-mode: multiply; - z-index:200 !important; - position: fixed; + z-index: 200 !important; + position: fixed; text-align: center; } .is-paid { - color: #D23; - border: 1rem double #D23; + color: #D23; + border: 1rem double #D23; transform: rotate(-5deg); font-size: 6rem; font-family: "Open sans", Helvetica, Arial, sans-serif; border-radius: 0; padding: 0.5rem; opacity: 0.2; - z-index:200 !important; - position: fixed; + z-index: 200 !important; + position: fixed; display: $show_paid_stamp; - } + } .project-header { font-size: 1.2em; @@ -360,14 +373,12 @@ margin-right: 0; font-weight: bold; color: #505050; - } - - .pqrcode { - } - - #qr-bill{ - width:100% !important; + + .pqrcode {} + + #qr-bill { + width: 100% !important; } /** Useful snippets, uncomment to enable. **/ @@ -402,28 +413,28 @@
- +
- - - + + + - - - + + +
-
-
+
+
-
+

$entity_label

-
+
@@ -433,7 +444,8 @@
-

$entity_label

+

+ $entity_label

@@ -456,11 +468,11 @@
- -
+ +
@@ -471,4 +483,4 @@ $entity_images

$entity_footer

-
+
\ No newline at end of file diff --git a/resources/views/pdf-designs/business.html b/resources/views/pdf-designs/business.html index dc6ef04a82..41f086a7b3 100644 --- a/resources/views/pdf-designs/business.html +++ b/resources/views/pdf-designs/business.html @@ -1,6 +1,6 @@ - - - - - - - - + + + + + + + - - - - - - - +
+
+

$entity_issued_to_label

+
+
+
+
+
-
 
-
-
-
-
- +
+
 
+
+
+
+
+ +
+
+
-
-
-
-
-

$entity_issued_to_label

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
$status_logo
- -
- -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
$status_logo
+
+ + + + + + + + + + @@ -428,4 +436,4 @@ $entity_images +
\ No newline at end of file diff --git a/resources/views/pdf-designs/calm.html b/resources/views/pdf-designs/calm.html index fbd855aadb..3e18ca39fb 100644 --- a/resources/views/pdf-designs/calm.html +++ b/resources/views/pdf-designs/calm.html @@ -120,7 +120,7 @@ .entity-label { padding-left: 1rem; } - + #entity-details { text-align: left; width: 100%; @@ -174,7 +174,7 @@ td.left-radius { padding-left: 1rem !important; } - + th.right-radius { text-align: right !important; } @@ -209,7 +209,7 @@ text-align: left; margin-top: .25rem; padding-left: 7px; - + } #table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) { @@ -358,10 +358,10 @@ .pqrcode {} - #qr-bill{ - width:100% !important; + #qr-bill { + width: 100% !important; } - + /** Useful snippets, uncomment to enable. **/ /** Hide company logo **/ @@ -451,6 +451,6 @@ \ No newline at end of file diff --git a/resources/views/pdf-designs/clean.html b/resources/views/pdf-designs/clean.html index dde2c19325..287674b2df 100644 --- a/resources/views/pdf-designs/clean.html +++ b/resources/views/pdf-designs/clean.html @@ -40,8 +40,8 @@ padding: 0; } - #qr-bill{ - width:100% !important; + #qr-bill { + width: 100% !important; } .header-container { @@ -49,7 +49,7 @@ grid-template-columns: 1fr auto 1fr; align-items: start; gap: 1rem; - width:100%; + width: 100%; } .company-logo-container { @@ -75,7 +75,7 @@ #company-address { justify-self: end; - align-self: start; + align-self: start; display: flex; flex-direction: column; line-height: var(--line-height); @@ -166,7 +166,7 @@ padding: 1rem 1rem; } - th.right-radius { + th.right-radius { padding-right: 1rem; text-align: right; } @@ -175,10 +175,10 @@ text-align: right; } - [data-ref='product_table-product.item-td']{ + [data-ref='product_table-product.item-td'] { color: var(--primary-color); } - + [data-ref="table"]>tbody>tr:nth-child(odd) { background-color: #f5f5f5; } @@ -262,7 +262,8 @@ bottom: 0; } - [data-ref='product_table-product.description-td'], td { + [data-ref='product_table-product.description-td'], + td { min-width: 100%; max-width: 300px; overflow-wrap: break-word; @@ -383,7 +384,7 @@
- +
diff --git a/resources/views/pdf-designs/creative.html b/resources/views/pdf-designs/creative.html index 31c7116ec8..a83e45c807 100644 --- a/resources/views/pdf-designs/creative.html +++ b/resources/views/pdf-designs/creative.html @@ -9,7 +9,7 @@ html { width: 210mm; - height: 200mm; + height: 200mm; } body { @@ -21,7 +21,9 @@ } - table tr td, table tr, th { + table tr td, + table tr, + th { font-size: $font_size !important; } @@ -40,7 +42,7 @@ .header-wrapper { display: grid; - grid-template-rows:0.5fr; + grid-template-rows: 0.5fr; grid-template-columns: auto auto auto auto auto; grid-template-areas: "a b c d e"; grid-auto-columns: minmax(0, 1fr); @@ -51,12 +53,12 @@ } .company-logo { - max-width: $company_logo_size; - float:right; + max-width: $company_logo_size; + float: right; } .logo-wrapper { - grid-area: e; + grid-area: e; justify-self: end; align-self: start; } @@ -67,8 +69,8 @@ text-align: right; } - #entity-details p { - margin-top: 5px; + #entity-details p { + margin-top: 5px; } .header-wrapper #client-details, @@ -84,24 +86,24 @@ font-weight: bold; } - .header-wrapper #client-details > *:first-child { + .header-wrapper #client-details>*:first-child { font-weight: bold; } - .header-wrapper .company-info-wrapper > * { + .header-wrapper .company-info-wrapper>* { margin-bottom: 1rem; grid-row-end: 4; } .entity-label-wrapper { - display: flex; - align-items: center; - width: 100%; - overflow: hidden; + display: flex; + align-items: center; + width: 100%; + overflow: hidden; box-sizing: border-box; justify-content: space-between; - margin-top:1rem; - margin-bottom:1rem; + margin-top: 1rem; + margin-bottom: 1rem; } @@ -110,27 +112,28 @@ } .entity-label h4 { - margin-top:0rem; - margin-bottom:0rem; + margin-top: 0rem; + margin-bottom: 0rem; } + .entity-details-wrapper { - flex-shrink: 0; + flex-shrink: 0; } - .entity-label-wrapper .entity-label > * { + .entity-label-wrapper .entity-label>* { font-size: 3rem; - + } - .entity-label-wrapper .entity-label > *:first-child { + .entity-label-wrapper .entity-label>*:first-child { text-transform: uppercase; white-space: nowrap; } - .entity-label-wrapper .entity-label > *:last-child { + .entity-label-wrapper .entity-label>*:last-child { color: var(--primary-color); font-style: italic; - + } #shipping-details { @@ -140,7 +143,7 @@ margin-left: 1rem; } - .entity-label-wrapper #entity-details > tr, + .entity-label-wrapper #entity-details>tr, .entity-label-wrapper #entity-details th { font-weight: normal; line-height: var(--line-height); @@ -148,7 +151,7 @@ } [data-ref="table"] { - margin-bottom: 5px; + margin-bottom: 5px; min-width: 100%; table-layout: fixed; overflow-wrap: break-word; @@ -172,15 +175,15 @@ overflow-wrap: break-word; } - [data-ref='product_table-product.item-td']{ + [data-ref='product_table-product.item-td'] { color: var(--primary-color); } - - [data-ref="table"] > thead { + + [data-ref="table"]>thead { text-align: left; } - [data-ref="table"] > thead > tr > th { + [data-ref="table"]>thead>tr>th { font-size: 1.1rem; padding: 1rem; } @@ -193,11 +196,11 @@ text-align: right !important; } - [data-ref="table"] > tbody > tr > td { + [data-ref="table"]>tbody>tr>td { padding: 1rem; } - [data-ref="table"] > tbody > tr:nth-child(odd) { + [data-ref="table"]>tbody>tr:nth-child(odd) { background-color: #e8e8e8; } @@ -207,7 +210,7 @@ grid-template-columns: 2fr 1fr; padding-top: 1rem; margin-right: .75rem; - page-break-inside:auto; + page-break-inside: auto; overflow: visible !important; } @@ -221,7 +224,7 @@ margin-top: .75rem; } - #table-totals>.totals-table-right-side> * > :not([hidden]) ~ :not([hidden]) { + #table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse))); margin-bottom: calc(.75rem * var(--tw-space-y-reverse)); @@ -231,16 +234,15 @@ text-align: right; } - #table-totals > * > * { - } + #table-totals>*>* {} - #table-totals > * > :last-child { + #table-totals>*> :last-child { text-align: right; padding-right: 1rem; } [data-ref="total_table-footer"] { - padding-right:0.8rem; + padding-right: 0.8rem; } #footer { @@ -269,23 +271,26 @@ .repeating-footer-space { height: 10px; } + .repeating-header { position: fixed; top: 0; } + .repeating-footer { position: fixed; bottom: 0; } - - [data-element='product_table-product.description-td'], td { - min-width:100%; + + [data-element='product_table-product.description-td'], + td { + min-width: 100%; max-width: 300px; - overflow-wrap: break-word; + overflow-wrap: break-word; } .stamp { - transform: rotate(12deg); + transform: rotate(12deg); color: #555; font-size: 3rem; font-weight: 700; @@ -296,25 +301,25 @@ border-radius: 1rem; font-family: 'Courier'; mix-blend-mode: multiply; - z-index:200 !important; - position: fixed; + z-index: 200 !important; + position: fixed; text-align: center; } .is-paid { - color: #D23; - border: 1rem double #D23; + color: #D23; + border: 1rem double #D23; transform: rotate(-5deg); font-size: 6rem; font-family: "Open sans", Helvetica, Arial, sans-serif; border-radius: 0; padding: 0.5rem; opacity: 0.2; - z-index:200 !important; - position: fixed; + z-index: 200 !important; + position: fixed; display: $show_paid_stamp; - } + } .project-header { font-size: 1.2em; @@ -325,14 +330,12 @@ margin-right: 0; font-weight: bold; color: #505050; - } - - .pqrcode { - } - - #qr-bill{ - width:100% !important; + + .pqrcode {} + + #qr-bill { + width: 100% !important; } /** Useful snippets, uncomment to enable. **/ @@ -366,66 +369,62 @@ - - - - - - - - + + + + + + + - - - - - - - +
-
 
-
-
-
-
-
-
+
+
 
+
+
+
+
+
+
-
-
-
-
-
- -
-
-
-
-

$entity_label

-

#$entity_number

+
+
+
+
+
+ +
-
-
+
+
+

$entity_label

+

#$entity_number

+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
$status_logo
-
-
- -
+
+
+
+
+
+
+
+
+
+
+
$status_logo
+
+ + + + + + + + + + @@ -434,6 +433,6 @@ $entity_images +
\ No newline at end of file diff --git a/resources/views/pdf-designs/elegant.html b/resources/views/pdf-designs/elegant.html index 470d35ca70..27de4e21f8 100644 --- a/resources/views/pdf-designs/elegant.html +++ b/resources/views/pdf-designs/elegant.html @@ -9,8 +9,8 @@ html { width: 210mm; - height: 200mm; - } + height: 200mm; + } body { -webkit-font-smoothing: antialiased; @@ -20,7 +20,9 @@ zoom: 80%; } - table tr td, table tr, th { + table tr td, + table tr, + th { font-size: $font_size !important; } @@ -35,7 +37,7 @@ } .company-logo { -/* max-width: 55%;*/ + /* max-width: 55%;*/ max-width: $company_logo_size; margin-left: auto; margin-right: auto; @@ -47,15 +49,13 @@ border-bottom: 4px solid; } - .company-logo-wrapper { - - } + .company-logo-wrapper {} .right-radius { padding-right: 1rem; - text-align:right; + text-align: right; } - + .client-entity-wrapper { width: 100%; margin-top: 1rem; @@ -74,19 +74,19 @@ table-layout: fixed; } - #entity-details p { + #entity-details p { margin-right: 0px; - margin-top: 0px; + margin-top: 0px; white-space: nowrap; line-height: var(--line-height) !important; } - + .client-entity-wrapper .wrapper-info-text { display: block; font-size: 1.5rem; font-weight: normal; } - + .client-entity-wrapper .shipping-info-text { display: block; font-size: 1.5rem; @@ -94,10 +94,11 @@ display: $show_shipping_address; } - .wrapper-right-side { - } + .wrapper-right-side {} - .text-with-client { margin-right: 1px; } + .text-with-client { + margin-right: 1px; + } .client-entity-wrapper .wrapper-left-side #client-details, .client-entity-wrapper .wrapper-left-side #company-details, @@ -124,10 +125,10 @@ line-height: var(--line-height) !important; } - .client-entity-wrapper #entity-details > tr, + .client-entity-wrapper #entity-details>tr, .client-entity-wrapper #entity-details th { font-weight: normal; - padding-right:8px; + padding-right: 8px; line-height: var(--line-height) !important; } @@ -145,11 +146,11 @@ color: grey; } - [data-ref="table"] > thead { + [data-ref="table"]>thead { text-align: left; } - [data-ref="table"] > thead > tr > th { + [data-ref="table"]>thead>tr>th { font-size: 1.1rem; padding-bottom: 1.5rem; padding-left: 1rem; @@ -157,22 +158,22 @@ font-weight: bold; } - [data-ref="table"] > thead > tr > th:last-child { + [data-ref="table"]>thead>tr>th:last-child { text-align: right; padding-right: 1rem; } - [data-ref="table"] > tbody > tr > td { + [data-ref="table"]>tbody>tr>td { border-bottom: 1pt solid; padding: 1rem; } - [data-ref="table"] > tbody > tr:first-child > td { + [data-ref="table"]>tbody>tr:first-child>td { border-top: 1pt solid !important; padding: 1rem; } - [data-ref="table"] > tbody > tr > td:last-child { + [data-ref="table"]>tbody>tr>td:last-child { text-align: right; padding-right: 1rem; } @@ -187,7 +188,7 @@ grid-template-columns: 2fr 1fr; padding-top: 0.5rem; gap: 80px; - page-break-inside:auto; + page-break-inside: auto; overflow: visible !important; } @@ -201,7 +202,7 @@ margin-top: .75rem; } - #table-totals>.totals-table-right-side> * > :not([hidden]) ~ :not([hidden]) { + #table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse))); margin-bottom: calc(.75rem * var(--tw-space-y-reverse)); @@ -212,13 +213,12 @@ margin-right: 1rem; } - #table-totals > * > :last-child { + #table-totals>*> :last-child { text-align: right; margin-right: .75rem; } - [data-ref="total_table-footer"] { - } + [data-ref="total_table-footer"] {} #footer { margin-top: 30px; @@ -246,19 +246,22 @@ .repeating-footer-space { height: 10px; } + .repeating-header { position: fixed; top: 0; } + .repeating-footer { position: fixed; bottom: 0; } - [data-ref='product_table-product.description-td'], td { - min-width:100%; + [data-ref='product_table-product.description-td'], + td { + min-width: 100%; max-width: 300px; - overflow-wrap: break-word; + overflow-wrap: break-word; } [data-ref='task_table-task.description-th'] { @@ -267,7 +270,7 @@ } .stamp { - transform: rotate(12deg); + transform: rotate(12deg); color: #555; font-size: 3rem; font-weight: 700; @@ -278,25 +281,25 @@ border-radius: 1rem; font-family: 'Courier'; mix-blend-mode: multiply; - z-index:200 !important; - position: fixed; + z-index: 200 !important; + position: fixed; text-align: center; } .is-paid { - color: #D23; - border: 1rem double #D23; + color: #D23; + border: 1rem double #D23; transform: rotate(-5deg); font-size: 6rem; font-family: "Open sans", Helvetica, Arial, sans-serif; border-radius: 0; padding: 0.5rem; opacity: 0.2; - z-index:200 !important; - position: fixed; + z-index: 200 !important; + position: fixed; display: $show_paid_stamp; - } + } .project-header { font-size: 1.2em; @@ -307,14 +310,12 @@ margin-right: 0; font-weight: bold; color: #505050; - } - - .pqrcode { - } - #qr-bill{ - width:100% !important; + .pqrcode {} + + #qr-bill { + width: 100% !important; } /** Useful snippets, uncomment to enable. **/ @@ -348,66 +349,66 @@ - - - - - - - - + + + + + + + - - - - - - - + + + + + + + +
-
 
-
-
-
- -
-
-
-
-
-

$to_label

-
-
-
-
-

$shipping_label

-
-
-
-

$from_label

-
-
-
-
-

$details_label

-
+
+
 
+
+
+
+
+
+
+
+
+

$to_label

+
+
+
+
+

$shipping_label

+
+
+
+

$from_label

+
+
+
+
+

$details_label

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
$status_logo
- -
-
-
-
-
-
-
-
-
-
-
-
$status_logo
- -
- -
+ +
@@ -416,8 +417,6 @@ $entity_images - +

$entity_footer

+
\ No newline at end of file diff --git a/resources/views/pdf-designs/hipster.html b/resources/views/pdf-designs/hipster.html index cd676f8d63..f903f4522b 100644 --- a/resources/views/pdf-designs/hipster.html +++ b/resources/views/pdf-designs/hipster.html @@ -4,12 +4,12 @@ :root { --primary-color: $primary_color; --secondary-color: $secondary_color; - --line-height: 1.6; + --line-height: 1.6; } html { width: 210mm; - height: 200mm; + height: 200mm; } body { @@ -20,7 +20,9 @@ zoom: 80%; } - table tr td, table tr, th { + table tr td, + table tr, + th { font-size: $font_size !important; } @@ -55,7 +57,7 @@ padding-left: 1rem; } - .header-wrapper .header-left-side-wrapper > * { + .header-wrapper .header-left-side-wrapper>* { margin-bottom: 0.8rem; } @@ -98,15 +100,15 @@ .header-wrapper .header-right-side-wrapper-left-shipping { padding-left: 1rem; border-left: 1px solid #303030; - float:left; + float: left; } .header-wrapper .header-right-side-wrapper-right { grid-area: c; justify-self: end; - align-self: start; + align-self: start; justify-content: end; - float:right; + float: right; } .shipping-text-label { @@ -123,7 +125,7 @@ .company-logo { max-width: $company_logo_size; - float:right; + float: right; object-fit: contain; } @@ -133,7 +135,7 @@ margin: 2rem 0; } - .entity-details-wrapper > * { + .entity-details-wrapper>* { margin-right: 1.5rem; direction: $dir; } @@ -153,10 +155,8 @@ content: ":"; } - .entity-details-wrapper - [data-element='entity-details-wrapper-invoice-number-label'], - .entity-details-wrapper - [data-element='entity-details-wrapper-amount-due'] { + .entity-details-wrapper [data-element='entity-details-wrapper-invoice-number-label'], + .entity-details-wrapper [data-element='entity-details-wrapper-amount-due'] { color: var(--primary-color); font-weight: bold; } @@ -166,12 +166,12 @@ } td.left-radius { - text-align:left !important; + text-align: left !important; padding-left: 1rem !important; } th.left-radius { - text-align:left !important; + text-align: left !important; padding-left: 0.5rem !important; } @@ -189,25 +189,25 @@ color: grey; } - [data-ref="table"] > thead { + [data-ref="table"]>thead { text-align: left; text-transform: uppercase; font-weight: bold; } - [data-ref="table"] > thead > tr > th { + [data-ref="table"]>thead>tr>th { font-size: 1.1rem; padding-bottom: 1.5rem; padding-left: 1rem; border-left: 1px solid; } - [data-ref="table"] > thead > tr > th:nth-last-child(2) { + [data-ref="table"]>thead>tr>th:nth-last-child(2) { text-align: right; padding-right: 1rem; } - [data-ref="table"] > tbody > tr > td { + [data-ref="table"]>tbody>tr>td { padding-left: 1rem; padding-top: 1rem; padding-bottom: 1rem; @@ -217,14 +217,14 @@ .right-radius { text-align: right; } - + #table-totals { margin-top: 1rem; display: grid; grid-template-columns: 2fr 1fr; padding-top: 0.5rem; margin-right: 0rem; - page-break-inside:auto; + page-break-inside: auto; overflow: visible !important; } @@ -238,7 +238,7 @@ margin-top: .75rem; } - #table-totals>.totals-table-right-side> * > :not([hidden]) ~ :not([hidden]) { + #table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse))); margin-bottom: calc(.75rem * var(--tw-space-y-reverse)); @@ -248,7 +248,7 @@ text-align: right; } - #table-totals > * > :last-child { + #table-totals>*> :last-child { text-align: right; padding-right: 0rem; } @@ -265,26 +265,27 @@ [data-ref='task_table-task.service-th'], [data-ref='product_table-product.item-th'] { - padding-left:1rem !important; - width:14%; - } + padding-left: 1rem !important; + width: 14%; + } [data-ref='task_table-task.description-th'], [data-ref='product_table-product.description-th'], [data-ref='product_table-product.description-td'], [data-ref="task_table-task.description-td"] { - min-width:100px !important;; - overflow-wrap: break-word; + min-width: 100px !important; + ; + overflow-wrap: break-word; } - + [data-ref="product_table-product.unit_cost-td"], [data-ref="product_table-product.unit_cost-th"], [data-ref='task_table-task.cost-th'], - [data-ref='task_table-task.cost-td']{ - text-align: center !important; + [data-ref='task_table-task.cost-td'] { + text-align: center !important; width: 10%; - padding-left:0 !important; - padding-right:0 !important; + padding-left: 0 !important; + padding-right: 0 !important; } [data-ref="product_table-product.quantity-th"], @@ -292,9 +293,9 @@ [data-ref='task_table-task.hours-th'], [data-ref='task_table-task.hours-td'] { width: 10%; - text-align: center !important; - padding-left:0 !important; - padding-right:0 !important; + text-align: center !important; + padding-left: 0 !important; + padding-right: 0 !important; } [data-ref="task_table-task.discount-th"], @@ -311,8 +312,8 @@ [data-ref="product_table-product.tax1-td"] { text-align: center !important; width: 9%; - padding-left:0 !important; - padding-right:0 !important; + padding-left: 0 !important; + padding-right: 0 !important; } [data-ref="product_table-product.line_total-th"], @@ -327,15 +328,15 @@ width: 13%; } - [data-ref="totals_table-outstanding-label"] { - font-weight:bold; - font-size:120%; + [data-ref="totals_table-outstanding-label"] { + font-weight: bold; + font-size: 120%; } - [data-ref="totals_table-outstanding"] { - color: var(--primary-color); - font-weight:bold; - font-size:120%; + [data-ref="totals_table-outstanding"] { + color: var(--primary-color); + font-weight: bold; + font-size: 120%; } [data-ref="statement-totals"] { @@ -354,17 +355,19 @@ .repeating-footer-space { height: 0; } + .repeating-header { position: fixed; top: 0; } + .repeating-footer { position: fixed; bottom: 0; } .stamp { - transform: rotate(12deg); + transform: rotate(12deg); color: #555; font-size: 3rem; font-weight: 700; @@ -375,24 +378,24 @@ border-radius: 1rem; font-family: 'Courier'; mix-blend-mode: multiply; - z-index:200 !important; - position: fixed; + z-index: 200 !important; + position: fixed; text-align: center; } .is-paid { - color: #D23; - border: 1rem double #D23; + color: #D23; + border: 1rem double #D23; transform: rotate(-5deg); font-size: 6rem; font-family: "Open sans", Helvetica, Arial, sans-serif; border-radius: 0; padding: 0.5rem; opacity: 0.2; - z-index:200 !important; - position: fixed; + z-index: 200 !important; + position: fixed; display: $show_paid_stamp; - } + } .project-header { font-size: 1.2em; @@ -403,13 +406,12 @@ margin-right: 0; font-weight: bold; color: #505050; - } - - .pqrcode { - } - #qr-bill{ - width:100% !important; + + .pqrcode {} + + #qr-bill { + width: 100% !important; } /** Useful snippets, uncomment to enable. **/ @@ -443,91 +445,84 @@ - - - - - - - - + + + + + + + - - - - - - - +

$entity_label

+
+
+ + $entity_number_label + + $entity_number +
+
+ $date_label + $date +
+
+ $payment_due_label + $payment_due +
+
+ $amount_due_label + $amount_due +
+
+
-
 
-
-
-
-
-

$from_label:

-
-
-
-
-
-

$to_label:

-
-
-
-
-

$shipping_label:

-
+
+
 
+
+
+
+
+

$from_label:

+
+
+
+
+
+

$to_label:

+
+
+
+
+

$shipping_label:

+
+
+
+ +
+
-
- -
-
- -

$entity_label

-
-
- - $entity_number_label - - $entity_number -
-
- $date_label - $date -
-
- $payment_due_label - $payment_due -
-
- $amount_due_label - $amount_due -
-
-
-
-
-
-
-
-
-
-
-
-
-
$status_logo
- -
- -
+
+
+
+
+
+
+
+
+
+
+
$status_logo
+
+ + + + + + + + + + @@ -536,8 +531,7 @@ $entity_images +
\ No newline at end of file diff --git a/resources/views/pdf-designs/modern.html b/resources/views/pdf-designs/modern.html index eaf9be8013..0c3ea06f85 100644 --- a/resources/views/pdf-designs/modern.html +++ b/resources/views/pdf-designs/modern.html @@ -1,502 +1,503 @@
- - - - - - - - - + + + + + + + +
-
-
-
- -
-
-
-
-
-
+ + + + + + + + + - - - - - - - -
+
+
+
+ +
+
+
+
+
+
-
+
-
+
-
-
+
+
-
-
-
-
-
-
-
-
$status_logo
-
- -
+
+
+
+
+
+
+
+
$status_logo
+
+ +
$entity_images \ No newline at end of file diff --git a/resources/views/pdf-designs/plain.html b/resources/views/pdf-designs/plain.html index fe24621331..712d1281a1 100644 --- a/resources/views/pdf-designs/plain.html +++ b/resources/views/pdf-designs/plain.html @@ -73,7 +73,7 @@ line-height: var(--line-height) !important; white-space: nowrap; border: 0px solid #000; - float:right; + float: right; } #entity-details>tr, @@ -103,7 +103,7 @@ white-space: nowrap; border: 0px solid #000; margin-left: auto; - margin-right:auto; + margin-right: auto; } [data-ref="table"] { @@ -238,12 +238,12 @@ } [data-ref='product_table-product.description-td'], - [data-ref='task_table-task.description-th']{ + [data-ref='task_table-task.description-th'] { min-width: 150px !important; overflow-wrap: break-word; } - + .stamp { transform: rotate(12deg); color: #555; @@ -295,12 +295,12 @@ .pqrcode {} - - #qr-bill{ - width:100% !important; + + #qr-bill { + width: 100% !important; } - - + + /** Useful snippets, uncomment to enable. **/ diff --git a/resources/views/pdf-designs/playful.html b/resources/views/pdf-designs/playful.html index 977dc1bdcc..d8209716bc 100644 --- a/resources/views/pdf-designs/playful.html +++ b/resources/views/pdf-designs/playful.html @@ -15,13 +15,15 @@ zoom: 80%; } - table tr td, table tr, th { + table tr td, + table tr, + th { font-size: $font_size !important; } html { width: 210mm; - height: 200mm; + height: 200mm; } @page { @@ -49,8 +51,8 @@ align-self: flex-start; } - #entity-details p { - margin-right: 20px; + #entity-details p { + margin-right: 20px; white-space: nowrap; --tw-space-y-reverse: 0; margin-top: calc(.5rem * calc(1 - var(--tw-space-y-reverse))); @@ -64,13 +66,13 @@ line-height: 1.2; } - .header-wrapper #entity-details > tr, + .header-wrapper #entity-details>tr, .header-wrapper #entity-details th { font-weight: normal; } .company-logo { -/* max-width: 65%;*/ + /* max-width: 65%;*/ max-width: $company_logo_size; } @@ -83,7 +85,7 @@ grid-auto-columns: minmax(0, 5fr); grid-auto-flow: column; justify-content: space-between; - margin-left:1rem; + margin-left: 1rem; } @@ -137,19 +139,19 @@ } - .contact-wrapper-left-side{ + .contact-wrapper-left-side { border-bottom: 1px solid var(--primary-color); } - + .contact-wrapper-right-side { border-bottom: 1px solid var(--primary-color); - margin-left:20px; + margin-left: 20px; } .shipping-wrapper-right-side { border-bottom: 1px solid var(--primary-color); display: $show_shipping_address_block; - margin-left:20px; + margin-left: 20px; } [data-ref="table"] { @@ -168,11 +170,11 @@ color: grey; } - [data-ref="table"] > thead { + [data-ref="table"]>thead { text-align: left; } - [data-ref="table"] > thead > tr > th { + [data-ref="table"]>thead>tr>th { font-size: 1.2rem; padding: 1rem; background: var(--primary-color); @@ -192,17 +194,17 @@ text-align: right; } - [data-ref="table"] > tbody > tr > td { + [data-ref="table"]>tbody>tr>td { background-color: #F7F7F7; border-bottom: 1px solid var(--primary-color); padding: 1rem; } - [data-ref="table"] > tbody > tr > td.left-radius { + [data-ref="table"]>tbody>tr>td.left-radius { color: var(--primary-color); } - [data-ref="table"] > tbody > tr > td.right-radius { + [data-ref="table"]>tbody>tr>td.right-radius { text-align: right; } @@ -227,7 +229,7 @@ margin-top: .75rem; } - #table-totals>.totals-table-right-side> * > :not([hidden]) ~ :not([hidden]) { + #table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse))); margin-bottom: calc(.75rem * var(--tw-space-y-reverse)); @@ -247,7 +249,7 @@ margin-left: -10px; } - #header > * { + #header>* { padding: 10px; } @@ -276,9 +278,9 @@ display: grid; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; margin-left: -10px; - } + } - #footer-colors > * { + #footer-colors>* { padding: 10px; } @@ -296,28 +298,31 @@ .repeating-header, .repeating-header-space { height: 20px; - page-break-inside:avoid; - page-break-before:avoid; - page-break-after:avoid; + page-break-inside: avoid; + page-break-before: avoid; + page-break-after: avoid; } .repeating-footer, .repeating-footer-space { height: 20px; } + .repeating-header { position: fixed; top: 0; } + .repeating-footer { position: fixed; bottom: 0; } - [data-element='product_table-product.description-td'], td { - min-width:100%; + [data-element='product_table-product.description-td'], + td { + min-width: 100%; max-width: 300px; - overflow-wrap: break-word; + overflow-wrap: break-word; } [data-ref="shipping_address-label"] { @@ -325,7 +330,7 @@ } .stamp { - transform: rotate(12deg); + transform: rotate(12deg); color: #555; font-size: 3rem; font-weight: 700; @@ -336,25 +341,25 @@ border-radius: 1rem; font-family: 'Courier'; mix-blend-mode: multiply; - z-index:200 !important; - position: fixed; + z-index: 200 !important; + position: fixed; text-align: center; } .is-paid { - color: #D23; - border: 1rem double #D23; + color: #D23; + border: 1rem double #D23; transform: rotate(-5deg); font-size: 6rem; font-family: "Open sans", Helvetica, Arial, sans-serif; border-radius: 0; padding: 0.5rem; opacity: 0.2; - z-index:200 !important; - position: fixed; + z-index: 200 !important; + position: fixed; display: $show_paid_stamp; - } + } .project-header { font-size: 1.2em; @@ -365,16 +370,14 @@ margin-right: 0; font-weight: bold; color: #505050; - } - - .pqrcode { - } - #qr-bill{ - width:100% !important; + .pqrcode {} + + #qr-bill { + width: 100% !important; } - + /** Useful snippets, uncomment to enable. **/ /** Hide company logo **/ @@ -405,67 +408,67 @@ /** To find out selectors on your own: https://invoiceninja.github.io/docs/custom-fields/#snippets **/
- - - - - - - - - + + + + + + + +
-
 
-
-
-
- -
-
-
-
-
-
-
-

$from_label:

-
-
-
+ + + + + + + + + - - - - - - - -
+
 
+
+
+
+ +
+
+
+
- -
-

$to_label:

-
-
-
+
+
+

$from_label:

+
+
+
+
+
+
+

$to_label:

+
+
+
-
-

$shipping_label:

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
$status_logo
-
- -
+
+

$shipping_label:

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
$status_logo
+
+ +
+
+ + + + + + + + + + @@ -459,8 +459,6 @@ $entity_images - +

$entity_footer

+ \ No newline at end of file diff --git a/resources/views/pdf-designs/tidy_receipt.html b/resources/views/pdf-designs/tidy_receipt.html index f54ea77d34..06d5a1f4e7 100644 --- a/resources/views/pdf-designs/tidy_receipt.html +++ b/resources/views/pdf-designs/tidy_receipt.html @@ -97,7 +97,8 @@
-

$receipt_label {%if payments|length == 1%}#$number{% endif %}

+

$receipt_label {%if payments|length == + 1%}#$number{% endif %}

diff --git a/tests/Pdf/PdfServiceTest.php b/tests/Pdf/PdfServiceTest.php index be422adc08..2f059859e2 100644 --- a/tests/Pdf/PdfServiceTest.php +++ b/tests/Pdf/PdfServiceTest.php @@ -14,12 +14,15 @@ namespace Tests\Pdf; use Tests\TestCase; use App\Models\Client; +use App\Models\Vendor; use App\Models\Company; use App\Models\Invoice; use Tests\MockAccountData; use App\Models\ClientContact; +use App\Models\VendorContact; use App\Services\Pdf\PdfService; use App\DataMapper\CompanySettings; +use App\Models\PurchaseOrder; use App\Services\Pdf\PdfConfiguration; /** @@ -46,32 +49,13 @@ class PdfServiceTest extends TestCase } - public function testMaxInvoiceFields() + private function stubInvoice($settings, array $company_props = []) { - $settings = CompanySettings::defaults(); - $settings->pdf_variables = json_decode($this->max_pdf_variables); - $settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png'; - $settings->website = 'www.invoiceninja.com'; - $settings->name = 'Invoice Ninja'; - $settings->address1 = 'Address 1'; - $settings->address2 = 'Address 2'; - $settings->city = 'City'; - $settings->state = 'State'; - $settings->postal_code = 'Postal Code'; - $settings->phone = '555-343-2323'; - $settings->email = $this->fake_email; - $settings->country_id = '840'; - $settings->vat_number = 'vat number'; - $settings->id_number = 'id number'; - $settings->use_credits_payment = 'always'; - $settings->timezone_id = '1'; - $settings->entity_send_time = 0; - $settings->hide_empty_columns_on_pdf = true; - - $company = Company::factory()->create([ + + $company = Company::factory()->create(array_merge([ 'account_id' => $this->account->id, 'settings' => $settings - ]); + ], $company_props)); $client = Client::factory()->create([ 'user_id' => $this->user->id, @@ -101,6 +85,256 @@ class PdfServiceTest extends TestCase $invoice = $invoice->service()->createInvitations()->markSent()->save(); $invoice = $invoice->fresh(); + return $invoice; + } + + private function stubPurchaseOrder($settings, array $company_props = []) + { + + $company = Company::factory()->create(array_merge([ + 'account_id' => $this->account->id, + 'settings' => $settings + ], $company_props)); + + $vendor = Vendor::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id + ]); + + $contact = VendorContact::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'vendor_id' => $vendor->id, + 'is_primary' => true, + 'first_name' => 'John', + 'last_name' => 'Doe', + 'email' => 'john@doe.com', + 'phone' => '1234567890', + 'send_email' => true, + ]); + + $po = PurchaseOrder::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'vendor_id' => $vendor->id, + 'status_id' => PurchaseOrder::STATUS_DRAFT, + ]); + + $po = $po->calc()->getInvoice(); + $po = $po->service()->createInvitations()->markSent()->save(); + $po = $po->fresh(); + + return $po; + + } + + public function testPurchaseOrderGeneration() + { + + $settings = CompanySettings::defaults(); + $settings->pdf_variables = json_decode($this->max_pdf_variables); + $settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png'; + $settings->website = 'www.invoiceninja.com'; + $settings->name = 'Invoice Ninja'; + $settings->address1 = 'Address 1'; + $settings->address2 = 'Address 2'; + $settings->city = 'City'; + $settings->state = 'State'; + $settings->postal_code = 'Postal Code'; + $settings->phone = '555-343-2323'; + $settings->email = $this->fake_email; + $settings->country_id = '840'; + $settings->vat_number = 'vat number'; + $settings->id_number = 'id number'; + $settings->use_credits_payment = 'always'; + $settings->timezone_id = '1'; + $settings->entity_send_time = 0; + $settings->hide_empty_columns_on_pdf = true; + + $po = $this->stubPurchaseOrder($settings, ['markdown_enabled' => true]); + + $items = $po->line_items; + + $first_item = $items[0]; + + $first_item->notes = $this->faker->paragraphs(2, true); + + $items[] = $first_item; + + $new_item = $items[0]; + $new_item->notes = '**Bold** _Italic_ [Link](https://www.google.com) + + this + + and that + + is something to think about'; + + $items[] = $new_item; + + $po->line_items = $items; + $po->calc()->getPurchaseOrder(); + + + $this->assertGreaterThan(0, $po->invitations()->count()); + + \App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) use($po) { + + $po->design_id = $design->id; + $po->save(); + $po = $po->fresh(); + + $service = (new PdfService($po->invitations()->first(), 'purchase_order'))->boot(); + $pdf = $service->getPdf(); + + $this->assertNotNull($pdf); + + \Illuminate\Support\Facades\Storage::put('/pdf/po_' . $design->name.'.pdf', $pdf); + + }); + + } + + public function testMarkdownEnabled() + { + + $settings = CompanySettings::defaults(); + $settings->pdf_variables = json_decode($this->max_pdf_variables); + $settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png'; + $settings->website = 'www.invoiceninja.com'; + $settings->name = 'Invoice Ninja'; + $settings->address1 = 'Address 1'; + $settings->address2 = 'Address 2'; + $settings->city = 'City'; + $settings->state = 'State'; + $settings->postal_code = 'Postal Code'; + $settings->phone = '555-343-2323'; + $settings->email = $this->fake_email; + $settings->country_id = '840'; + $settings->vat_number = 'vat number'; + $settings->id_number = 'id number'; + $settings->use_credits_payment = 'always'; + $settings->timezone_id = '1'; + $settings->entity_send_time = 0; + $settings->hide_empty_columns_on_pdf = true; + + $invoice = $this->stubInvoice($settings, ['markdown_enabled' => true]); + + $items = $invoice->line_items; + + $first_item = $items[0]; + + $first_item->notes = $this->faker->paragraphs(2, true); + + $items[] = $first_item; + + $new_item = $items[0]; + $new_item->notes = '**Bold** _Italic_ [Link](https://www.google.com) + + this + + and that + + is something to think about'; + + $items[] = $new_item; + + $invoice->line_items = $items; + $invoice->calc()->getInvoice(); + + + $this->assertGreaterThan(0, $invoice->invitations()->count()); + + \App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) use($invoice) { + + $invoice->design_id = $design->id; + $invoice->save(); + $invoice = $invoice->fresh(); + + $service = (new PdfService($invoice->invitations()->first()))->boot(); + $pdf = $service->getPdf(); + + $this->assertNotNull($pdf); + + \Illuminate\Support\Facades\Storage::put('/pdf/markdown_' . $design->name.'.pdf', $pdf); + + }); + + } + + + + public function testLargeDescriptionField() + { + + $settings = CompanySettings::defaults(); + $settings->pdf_variables = json_decode($this->max_pdf_variables); + $settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png'; + $settings->website = 'www.invoiceninja.com'; + $settings->name = 'Invoice Ninja'; + $settings->address1 = 'Address 1'; + $settings->address2 = 'Address 2'; + $settings->city = 'City'; + $settings->state = 'State'; + $settings->postal_code = 'Postal Code'; + $settings->phone = '555-343-2323'; + $settings->email = $this->fake_email; + $settings->country_id = '840'; + $settings->vat_number = 'vat number'; + $settings->id_number = 'id number'; + $settings->use_credits_payment = 'always'; + $settings->timezone_id = '1'; + $settings->entity_send_time = 0; + $settings->hide_empty_columns_on_pdf = true; + + $invoice = $this->stubInvoice($settings); + + $items = $invoice->line_items; + + $items[0]->notes = $this->faker->text(500); + + $invoice->line_items = $items; + $invoice->save(); + + $this->assertGreaterThan(0, $invoice->invitations()->count()); + + \App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) use($invoice) { + + $invoice->design_id = $design->id; + $invoice->save(); + $invoice = $invoice->fresh(); + + $service = (new PdfService($invoice->invitations()->first()))->boot(); + $pdf = $service->getPdf(); + + $this->assertNotNull($pdf); + + \Illuminate\Support\Facades\Storage::put('/pdf/desc_' . $design->name.'.pdf', $pdf); + + }); + + } + + + + public function testMaxInvoiceFields() + { + + $settings = CompanySettings::defaults(); + $settings->pdf_variables = json_decode($this->max_pdf_variables); + $settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png'; + $settings->website = 'www.invoiceninja.com'; + $settings->name = 'Invoice Ninja'; + $settings->address1 = 'Address 1'; + $settings->address2 = 'Address 2'; + $settings->city = 'City'; + $settings->state = 'State'; + $settings->postal_code = 'Postal Code'; + $settings->phone = '555-343-2323'; + $settings->email = $this->fake_email; + $settings->country_id = '840'; + $settings->vat_number = 'vat number'; + $settings->id_number = 'id number'; + $settings->use_credits_payment = 'always'; + $settings->timezone_id = '1'; + $settings->entity_send_time = 0; + $settings->hide_empty_columns_on_pdf = true; + + $invoice = $this->stubInvoice($settings); $this->assertGreaterThan(0, $invoice->invitations()->count()); @@ -144,37 +378,7 @@ class PdfServiceTest extends TestCase $settings->entity_send_time = 0; $settings->hide_empty_columns_on_pdf = true; - $company = Company::factory()->create([ - 'account_id' => $this->account->id, - 'settings' => $settings - ]); - - $client = Client::factory()->create([ - 'user_id' => $this->user->id, - 'company_id' => $company->id - ]); - - $contact = ClientContact::factory()->create([ - 'user_id' => $this->user->id, - 'company_id' => $company->id, - 'client_id' => $client->id, - 'is_primary' => true, - 'first_name' => 'John', - 'last_name' => 'Doe', - 'email' => 'john@doe.com', - 'phone' => '1234567890', - 'send_email' => true, - ]); - - $invoice = Invoice::factory()->create([ - 'user_id' => $this->user->id, - 'company_id' => $company->id, - 'client_id' => $client->id, - 'status_id' => Invoice::STATUS_DRAFT, - ]); - - $invoice = $invoice->calc()->getInvoice(); - $invoice = $invoice->service()->createInvitations()->markSent()->save(); + $invoice = $this->stubInvoice($settings); \App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) use ($invoice) { From 84723d65d655328ce03324f3680b285487fdb39d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 5 Feb 2025 14:45:16 +1100 Subject: [PATCH 40/43] uPdate designs --- app/Services/Pdf/PdfService.php | 6 +++--- app/Utils/VendorHtmlEngine.php | 9 ++++++++- resources/views/pdf-designs/bold.html | 4 ++-- resources/views/pdf-designs/modern.html | 1 - resources/views/pdf-designs/playful.html | 6 ++++++ 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index 3546c308ca..d506aa7811 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -143,9 +143,9 @@ class PdfService $this->config = (new PdfConfiguration($this))->init(); - $this->html_variables = $this->config->client ? - (new HtmlEngine($this->invitation))->generateLabelsAndValues() : - (new VendorHtmlEngine($this->invitation))->generateLabelsAndValues(); + $this->html_variables = ($this->invitation instanceof \App\Models\PurchaseOrderInvitation) ? + (new VendorHtmlEngine($this->invitation))->generateLabelsAndValues() : + (new HtmlEngine($this->invitation))->generateLabelsAndValues(); $this->designer = (new PdfDesigner($this))->build(); diff --git a/app/Utils/VendorHtmlEngine.php b/app/Utils/VendorHtmlEngine.php index 8b1db39425..634494ea8c 100644 --- a/app/Utils/VendorHtmlEngine.php +++ b/app/Utils/VendorHtmlEngine.php @@ -136,6 +136,10 @@ class VendorHtmlEngine $data['$line_tax_values'] = ['value' => $this->lineTaxValues(), 'label' => ctrans('texts.taxes')]; $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->company->date_format(), $this->vendor->locale()) ?: ' ', 'label' => ctrans('texts.date')]; + $data['$show_shipping_address'] = ['value' => strlen($this->entity->client->shipping_address1 ?? '') > 0 && $this->settings->show_shipping_address ? 'flex' : 'none', 'label' => '']; + $data['$show_shipping_address_block'] = ['value' => strlen($this->entity->client->shipping_address1 ?? '') > 0 && $this->settings->show_shipping_address ? 'block' : 'none', 'label' => '']; + $data['$show_shipping_address_visibility'] = ['value' => strlen($this->entity->client->shipping_address1 ?? '') > 0 && $this->settings->show_shipping_address ? 1 : 0, 'label' => '']; + $data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->company->date_format(), $this->vendor->locale()) ?: ' ', 'label' => ctrans('texts.due_date')]; $data['$partial_due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->company->date_format(), $this->vendor->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')]; @@ -154,7 +158,7 @@ class VendorHtmlEngine $data['$entity'] = ['value' => '', 'label' => ctrans('texts.purchase_order')]; $data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.number')]; $data['$number_short'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.purchase_order_number_short')]; - $data['$entity.terms'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->terms ?? ''), $this->company) ?: '', 'label' => ctrans('texts.invoice_terms')]; + $data['$entity.terms'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->terms ?? ''), $this->company) ?: '', 'label' => ctrans('texts.purchase_order_terms')]; $data['$terms'] = &$data['$entity.terms']; $data['$view_link'] = ['value' => $this->buildViewButton($this->invitation->getLink(), ctrans('texts.view_purchase_order')), 'label' => ctrans('texts.view_purchase_order')]; $data['$viewLink'] = &$data['$view_link']; @@ -428,6 +432,9 @@ class VendorHtmlEngine $data['$payments'] = ['value' => '', 'label' => ctrans('texts.payments')]; + $data['$shipping'] = ['value' => '', 'label' => ctrans('texts.ship_to')]; + $data['$ship_to'] = &$data['$shipping']; + if ($this->entity->client()->exists()) { $data['$client1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client1', $this->entity->client->custom_value1, $this->entity->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client1')]; $data['$client2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client2', $this->entity->client->custom_value2, $this->entity->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client2')]; diff --git a/resources/views/pdf-designs/bold.html b/resources/views/pdf-designs/bold.html index acb15a9615..a959f48963 100644 --- a/resources/views/pdf-designs/bold.html +++ b/resources/views/pdf-designs/bold.html @@ -64,8 +64,8 @@ #header, #header-spacer { height: 160px; - padding-top: 2rem; - padding-bottom: 2rem; + padding-top: 1rem; + padding-bottom: 1rem; padding-left: 1rem; padding-right: 1rem; margin-bottom: 1rem; diff --git a/resources/views/pdf-designs/modern.html b/resources/views/pdf-designs/modern.html index 0c3ea06f85..7504314b77 100644 --- a/resources/views/pdf-designs/modern.html +++ b/resources/views/pdf-designs/modern.html @@ -222,7 +222,6 @@ #company-address>*, #company-details>* { - margin-bottom: 0.5rem; } #table-totals { diff --git a/resources/views/pdf-designs/playful.html b/resources/views/pdf-designs/playful.html index d8209716bc..bd894bdeb1 100644 --- a/resources/views/pdf-designs/playful.html +++ b/resources/views/pdf-designs/playful.html @@ -131,6 +131,12 @@ border-top: 1px solid var(--primary-color); } + .contacts-wrapper #vendor-details { + margin-top: 1rem; + padding-top: 1rem; + border-top: 1px solid var(--primary-color); + } + .contacts-wrapper #shipping-details { margin-top: 1rem; padding: 1rem; From dc7d1500350d8f868fab9fba781bc9a7586ea5db Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 5 Feb 2025 15:03:52 +1100 Subject: [PATCH 41/43] Fixes for creative --- resources/views/pdf-designs/creative.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/views/pdf-designs/creative.html b/resources/views/pdf-designs/creative.html index a83e45c807..c655777c86 100644 --- a/resources/views/pdf-designs/creative.html +++ b/resources/views/pdf-designs/creative.html @@ -96,12 +96,12 @@ } .entity-label-wrapper { - display: flex; - align-items: center; + /* display: flex; */ + display: grid; + grid-template-columns: 2fr 1fr; width: 100%; - overflow: hidden; + word-break: break-all; box-sizing: border-box; - justify-content: space-between; margin-top: 1rem; margin-bottom: 1rem; From 82d4a4d8e7750b5f7da520847163b28166ba22de Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 5 Feb 2025 15:23:29 +1100 Subject: [PATCH 42/43] Updates for clean design --- resources/views/pdf-designs/clean.html | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/resources/views/pdf-designs/clean.html b/resources/views/pdf-designs/clean.html index 287674b2df..2668c2ae59 100644 --- a/resources/views/pdf-designs/clean.html +++ b/resources/views/pdf-designs/clean.html @@ -46,7 +46,7 @@ .header-container { display: grid; - grid-template-columns: 1fr auto 1fr; + grid-template-columns: minmax(0, 1.5fr) auto minmax(0, 1fr); align-items: start; gap: 1rem; width: 100%; @@ -91,9 +91,10 @@ } .client-and-entity-wrapper { - padding: 1rem; + padding-top: 1rem; + padding-bottom: 1rem; display: grid; - grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); + grid-template-columns: minmax(0, 2fr) auto minmax(0, 1fr); align-items: start; border-top: 1px solid #d8d8d8; border-bottom: 1px solid #d8d8d8; @@ -106,6 +107,7 @@ line-height: var(--line-height) !important; justify-self: start; align-self: start; + padding-left: 1rem; } #entity-details>tr, @@ -119,7 +121,6 @@ display: flex; flex-direction: column; line-height: var(--line-height); - padding-right: 30px; justify-self: center; align-self: start; } @@ -134,6 +135,7 @@ line-height: var(--line-height); justify-self: end; align-self: start; + padding-left: 1rem; } [data-ref="table"] { From 8730c3635815dce011b53e503220847fb5b66956 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 5 Feb 2025 15:48:49 +1100 Subject: [PATCH 43/43] Minor cleanup --- app/Jobs/Cron/RecurringInvoicesCron.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/Jobs/Cron/RecurringInvoicesCron.php b/app/Jobs/Cron/RecurringInvoicesCron.php index c99e12d9a1..1b0fef8638 100644 --- a/app/Jobs/Cron/RecurringInvoicesCron.php +++ b/app/Jobs/Cron/RecurringInvoicesCron.php @@ -55,8 +55,8 @@ class RecurringInvoicesCron ->whereNull('deleted_at') ->where('next_send_date', '<=', now()->toDateTimeString()) ->whereHas('client', function ($query) { - $query->where('is_deleted', 0) - ->where('deleted_at', null); + $query->where('is_deleted', false) + ->whereNull('deleted_at'); }) ->whereHas('company', function ($query) { $query->where('is_disabled', 0) @@ -97,8 +97,9 @@ class RecurringInvoicesCron ->whereNotNull('next_send_date') ->where('next_send_date', '<=', now()->toDateTimeString()) ->whereHas('client', function ($query) { - $query->where('is_deleted', 0) - ->where('deleted_at', null); + $query->where('is_deleted', false) + ->whereNull('deleted_at'); + }) ->whereHas('company', function ($query) { $query->where('is_disabled', 0)