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/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/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 7e39fd4d34..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 { @@ -145,99 +146,65 @@ class PreviewController extends BaseController public function show(ShowPreviewRequest $request) { + if ($request->input('design.is_template')) { return $this->template(); } - if (request()->has('entity') && - request()->has('entity_id') && - ! empty(request()->input('entity')) && - ! empty(request()->input('entity_id'))) { + 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'))); + $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(); } - $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(); - 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], + ]); + + $ps->boot() + ->designer + ->buildFromPartials($request->design['design']); + + $ps->builder + ->build(); + + if ($request->query('html') == 'true') { + return $ps->getHtml(); } - $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->getPdf(); return response()->streamDownload(function () use ($pdf) { echo $pdf; @@ -338,200 +305,85 @@ 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) { 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, + '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' => $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], - ] - ]; + $ps->builder + ->build(); - $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/Http/Controllers/PreviewPurchaseOrderController.php b/app/Http/Controllers/PreviewPurchaseOrderController.php index 86f218292e..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 @@ -87,18 +85,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,63 +106,36 @@ 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()); + $invitation = $entity_obj->invitations()->first(); - $design_namespace = 'App\Services\PdfMaker\Designs\\'.request()->design['name']; + $ps = new PdfService($invitation, 'product', [ + 'client' => $entity_obj->client ?? false, + 'vendor' => $entity_obj->vendor ?? false, + $request->input('entity')."s" => [$entity_obj], + ]); - $design_class = new $design_namespace(); + $ps->boot() + ->designer + ->buildFromPartials($request->design['design']); - $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->builder + ->build(); - $design = new Design(request()->design['name']); - $maker = new PdfMaker($state); - - $maker - ->design($design) - ->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()); - - 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 +208,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)) { + 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); - - $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', + ]); - 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; + } } 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/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; } 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) 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/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/Repositories/ActivityRepository.php b/app/Repositories/ActivityRepository.php index 79c0e43974..a4cab0c8ab 100644 --- a/app/Repositories/ActivityRepository.php +++ b/app/Repositories/ActivityRepository.php @@ -11,23 +11,21 @@ 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\PurchaseOrder; -use App\Models\Quote; -use App\Models\RecurringInvoice; -use App\Models\User; -use App\Services\PdfMaker\Design as PdfDesignModel; -use App\Services\PdfMaker\Design as PdfMakerDesign; -use App\Services\PdfMaker\PdfMaker as PdfMakerService; +use App\Models\Activity; use App\Utils\HtmlEngine; +use App\Models\CompanyToken; +use App\Models\PurchaseOrder; use App\Utils\Traits\MakesHash; -use App\Utils\Traits\MakesInvoiceHtml; use App\Utils\VendorHtmlEngine; +use App\Models\RecurringInvoice; +use App\Services\Pdf\PdfService; +use App\Utils\Traits\MakesInvoiceHtml; /** * Class for activity repository. @@ -111,7 +109,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 +130,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 +175,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/Client/Statement.php b/app/Services/Client/Statement.php index 9fe51761bf..ff6ca627de 100644 --- a/app/Services/Client/Statement.php +++ b/app/Services/Client/Statement.php @@ -12,29 +12,27 @@ namespace App\Services\Client; -use App\Factory\InvoiceFactory; -use App\Factory\InvoiceInvitationFactory; -use App\Factory\InvoiceItemFactory; +use App\Utils\Number; use App\Models\Client; 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 Illuminate\Support\Carbon; +use App\Factory\InvoiceFactory; +use App\Utils\Traits\MakesHash; use App\Utils\PhantomJS\Phantom; use App\Utils\Traits\MakesDates; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\Pdf\PdfMaker as PdfMakerTrait; +use App\Utils\HostedPDF\NinjaPdf; +use App\Utils\Traits\Pdf\PdfMaker; +use App\Factory\InvoiceItemFactory; +use App\Factory\InvoiceInvitationFactory; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Support\Carbon; class Statement { - use PdfMakerTrait; + use PdfMaker; use MakesHash; use MakesDates; @@ -89,52 +87,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'; - $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), - 'variables' => $variables, - 'options' => [ - ], - 'process_markdown' => $this->entity->client->company->markdown_enabled, + $options = [ + // 'client' => $this->entity->client, + // 'entity' => $this->entity, + // 'pdf_variables' => (array) $this->entity->company->settings->pdf_variables, + // '$product' => $this->getDesign()->design->product, + // 'variables' => $variables, + 'invoices' => $this->getInvoices()->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->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 +198,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 +214,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 +519,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/Invoice/GenerateDeliveryNote.php b/app/Services/Invoice/GenerateDeliveryNote.php index 0c1cf2dc38..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,62 +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); - $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)); - } + $ps = new \App\Services\Pdf\PdfService($invitation, 'delivery_note'); - $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; + return $ps->boot()->getPdf(); } } 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/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 48e37b1a08..5f06144efc 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 { @@ -101,7 +109,7 @@ class PdfBuilder } - + // Decode any HTML based elements. $xpath = new \DOMXPath($this->document); $elements = $xpath->query('//*[@data-state="encoded-html"]'); @@ -117,35 +125,29 @@ 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); //releases memory immediately rather than at the end of the function } - - return $this; } @@ -182,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 = []; @@ -217,7 +226,13 @@ class PdfBuilder return $this; } - + + /** + * setDocument + * + * @param mixed $document + * @return self + */ public function setDocument($document): self { $this->document = $document; @@ -241,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; @@ -296,6 +327,23 @@ class PdfBuilder $this->genericSectionBuilder(); $this->mergeSections([ + 'client-details' => [ + 'id' => 'client-details', + 'elements' => $this->clientDetails(), + ], + 'vendor-details' => [ //this block pads the grid for client / vendor / entity details + 'id' => 'vendor-details', + 'elements' => [ + ['element' => 'tr', 'properties' => ['data-ref' => 'statement-labelx'], 'elements' => [ + ['element' => 'th', 'properties' => [], 'content' => ""], + ['element' => 'th', 'properties' => [], 'content' => '

'], + ]], + ], + ], + 'entity-details' => [ + 'id' => 'entity-details', + 'elements' => $this->statementDetails(), + ], 'statement-invoice-table' => [ 'id' => 'statement-invoice-table', 'elements' => $this->statementInvoiceTable(), @@ -320,8 +368,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' => [ @@ -357,49 +405,49 @@ 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; } return [ - ['element' => 'thead', 'elements' => $this->buildTableHeader('statement_invoice')], + ['element' => 'thead', 'elements' => $this->buildTableHeader('statement_credit')], ['element' => 'tbody', 'elements' => $tbody], ]; } /** - * Parent method for building invoice table totals - * for statements. - * + * Parent method for building credits table totals for statements. + * * @return array */ 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)], @@ -414,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) { @@ -462,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; @@ -482,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 []; } @@ -501,52 +540,46 @@ 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) ?: ' ')], ]; } - + + /** + * 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 []; } $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))], ]; } /** - * 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 []; } $tbody = []; + + $this->unapplied_total = 0; - //24-03-2022 show payments per invoice foreach ($this->service->options['unapplied'] as $unapplied_payment) { if ($unapplied_payment->is_deleted) { continue; @@ -554,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; @@ -572,7 +605,7 @@ class PdfBuilder } /** - * Generates the statement aging table + * Generates the aging table for statements. * * @return array * @@ -649,16 +682,6 @@ class PdfBuilder ], ]); - // if($this->service->config->entity->client) - // { - // $this->service->config->client = $this->service->config->entity->client; - // nlog("inside"); - // $this->getClientDetails(); - // $this->service->config->client = null; - // } - - // nlog($this->sections); - return $this; } @@ -682,9 +705,7 @@ class PdfBuilder ], 'footer-elements' => [ 'id' => 'footer', - 'elements' => [ - // $this->sharedFooterElements(), - ], + 'elements' => [], ], ]); @@ -701,14 +722,16 @@ class PdfBuilder { $tbody = []; + $date_format = $this->service->config->client->date_format(); + 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, $this->service->config->client->date_format(), $this->service->config->locale) ?: ' ']; - $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->due_date, $this->service->config->client->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; } @@ -718,6 +741,57 @@ 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 + { + + $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; + + } /** @@ -740,6 +814,10 @@ class PdfBuilder return []; } + $_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]; @@ -754,41 +832,36 @@ 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))]]; } } + $element = $this->parseVisibleElements($element); + $elements[] = $element; } return $elements; } - $_type = Str::startsWith($type, '$') ? ltrim($type, '$') : $type; $table_type = "{$_type}_columns"; + //Handle custom quote columns if ($_type == 'product' && $this->service->config->entity instanceof Quote && !$this->service->config->settings?->sync_invoice_quote_columns) { $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' => []]; - - if ( - array_key_exists($type, $this->service->options) && - !empty($this->service->options[$type]) && - !is_null($this->service->options[$type]) - ) { + //checks if we have custom columns in the options array with key $product/$task - looks like unused functionality + if (isset($this->service->options[$type]) && !empty($this->service->options[$type])) { + $document = new DOMDocument(); $document->loadHTML($this->service->options[$type], LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); @@ -835,50 +908,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; } @@ -912,17 +949,16 @@ class PdfBuilder } if ($table_type == '$task' && $item->type_id != 2) { - // if ($item->type_id != 4 && $item->type_id != 5) { continue; - // } } $helpers = new Helpers(); $_table_type = ltrim($table_type, '$'); // From $product -> product. - $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; + //2025-01-28 not sure how we ever got ->item and ->service.... + $data[$key][$table_type.'.product_key'] = $item->product_key ?? $item->item; + $data[$key][$table_type.'.item'] = $item->item ?? $item->product_key; + $data[$key][$table_type.'.service'] = $item->service ?? $item->product_key; $currentDateTime = null; if (isset($this->service->config->entity->next_send_date)) { @@ -930,7 +966,7 @@ class PdfBuilder } $data[$key][$table_type.'.notes'] = Helpers::processReservedKeywords($item->notes, $this->service->config->currency_entity, $currentDateTime); - $data[$key][$table_type.'.description'] = Helpers::processReservedKeywords($item->notes, $this->service->config->currency_entity, $currentDateTime); + $data[$key][$table_type.'.description'] = &$data[$key][$table_type.'.notes']; $data[$key][$table_type.".{$_table_type}1"] = strlen($item->custom_value1) >= 1 ? $helpers->formatCustomFieldValue($this->service->company->custom_fields, "{$_table_type}1", $item->custom_value1, $this->service->config->currency_entity) : ''; $data[$key][$table_type.".{$_table_type}2"] = strlen($item->custom_value2) >= 1 ? $helpers->formatCustomFieldValue($this->service->company->custom_fields, "{$_table_type}2", $item->custom_value2, $this->service->config->currency_entity) : ''; @@ -977,9 +1013,6 @@ class PdfBuilder $data[$key][$table_type.'.discount'] = ''; } - // Previously we used to check for tax_rate value, - // but that's no longer necessary. - if (isset($item->tax_rate1)) { $data[$key][$table_type.'.tax_rate1'] = $this->service->config->formatValueNoTrailingZeroes(floatval($item->tax_rate1)).'%'; $data[$key][$table_type.'.tax1'] = &$data[$key][$table_type.'.tax_rate1']; @@ -998,12 +1031,18 @@ class PdfBuilder $data[$key]['task_id'] = property_exists($item, 'task_id') ? $item->task_id : ''; } - //nlog(microtime(true) - $start); - 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 { @@ -1043,7 +1082,6 @@ class PdfBuilder return $columns; - } /** @@ -1138,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){ @@ -1218,36 +1263,6 @@ class PdfBuilder } } - /** - * Generates the javascript block for - * hiding elements which need to be hidden - * - * @return array - * - */ - public function sharedFooterElements(): array - { - // We want to show headers for statements, no exceptions. - $statements = " - document.querySelectorAll('#statement-invoice-table > thead > tr > th, #statement-payment-table > thead > tr > th, #statement-aging-table > thead > tr > th').forEach(t => { - t.hidden = false; - }); - "; - - $javascript = 'document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{if(""!==t.innerText){let e=t.getAttribute("data-ref").slice(0,-3);document.querySelector(`th[data-ref="${e}-th"]`).removeAttribute("hidden")}}),document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{let e=t.getAttribute("data-ref").slice(0,-3);(e=document.querySelector(`th[data-ref="${e}-th"]`)).hasAttribute("hidden")&&""==t.innerText&&t.setAttribute("hidden","true")})},!1);'; - - // Previously we've been decoding the HTML on the backend and XML parsing isn't good options because it requires, - // strict & valid HTML to even output/decode. Decoding is now done on the frontend with this piece of Javascript. - - $html_decode = 'document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll(`[data-state="encoded-html"]`).forEach(e=>e.innerHTML=e.innerText)},!1);'; - - return ['element' => 'div', 'elements' => [ - ['element' => 'script', 'content' => $statements], - ['element' => 'script', 'content' => $javascript], - ['element' => 'script', 'content' => $html_decode], - ]]; - } - /** * Generates the totals table for * the product type entities @@ -1373,14 +1388,10 @@ 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; - } - + return false; } @@ -1402,11 +1413,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' => [ @@ -1465,8 +1476,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') { @@ -1478,8 +1489,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')) { @@ -1488,28 +1499,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' => ''], ]]; @@ -1590,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; }); @@ -1699,11 +1714,6 @@ class PdfBuilder { $variables = $this->service->config->pdf_variables['invoice_details']; - // $_v = $this->service->html_variables; - - // $_v['labels']['$invoice.date_label'] = ctrans('text.date'); - // $this->service->html_variables = $_v; - $variables = array_filter($variables, function ($m) { return !in_array($m, ['$invoice.balance_due', '$invoice.total']); }); @@ -1746,9 +1756,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 * */ @@ -1760,19 +1772,31 @@ 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'; + $this->service->html_variables['labels']['$entity_issued_to_label'] = ''; + $this->service->html_variables['labels']['$entity_number_label'] = ctrans('texts.delivery_note'); + $this->service->html_variables['values']['$entity'] = ctrans('texts.delivery_note'); + $this->service->html_variables['labels']['$entity_label'] = ctrans('texts.delivery_note'); + $this->service->html_variables['labels']['$invoice.number_label'] = ctrans('texts.delivery_note'); + $this->service->html_variables['labels']['$payment_due_label'] = ''; + $this->service->html_variables['values']['$payment_due'] = ''; + $this->service->html_variables['labels']['$amount_due_label'] = ''; + $this->service->html_variables['values']['$balance_due'] = ''; + $this->service->html_variables['values']['$amount_due'] = ''; + $this->service->html_variables['labels']['$amount_due_label'] = ''; + $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']], ['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], ]; + 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']]; } @@ -1801,26 +1825,26 @@ class PdfBuilder return $elements; } - + + /** + * Generates the shipping details section + * + * @return array + */ public function shippingDetails(): array { $elements = []; - if (!$this->service->config->client) { + if (!$this->service->config->client || $this->service->document_type == PdfService::DELIVERY_NOTE) { return $elements; } $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' => 'div', 'content' => optional($this->service->config->client->shipping_country)->name, 'show_empty' => false], + ['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']], ]; return $elements; @@ -1834,7 +1858,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']], @@ -1861,6 +1885,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)], @@ -1899,7 +1940,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; @@ -1993,7 +2034,15 @@ class PdfBuilder return $element; } - + + /** + * isMarkdown + * + * Checks if the given content is most likely markdown + * + * @param string $content + * @return bool + */ private function isMarkdown(string $content): bool { $content = str_ireplace('
', "\n", $content); @@ -2006,7 +2055,8 @@ class PdfBuilder '/\*\*.*?\*\*/', // Bold '/\*.*?\*/', // Italic '/__.*?__/', // Bold - '/_.*?_/', // Italic + // '/_.*?_/', // Italic + '/(?/m', // Blockquotes '/^\s*```/m', // Code blocks @@ -2025,30 +2075,25 @@ class PdfBuilder public function createElementContent($element, $children): self { - foreach ($children as $child) { - + foreach ($children as $child) { + if (isset($child['is_empty']) && $child['is_empty'] === true) { + continue; + } + $contains_html = false; $child['content'] = $child['content'] ?? ''; - if ($this->service->company->markdown_enabled && $this->isMarkdown($child['content']) && $child['element'] !== 'script') { + if ($this->service->company->markdown_enabled && $this->isMarkdown($child['content'])) { $child['content'] = str_ireplace('
', "\r", $child['content']); $child['content'] = $this->commonmark->convert($child['content']); //@phpstan-ignore-line } - if (isset($child['is_empty']) && $child['is_empty'] === true) { - continue; - } - $contains_html = str_contains($child['content'], '<') && str_contains($child['content'], '>'); - if ($contains_html) { - - // If the element contains the HTML, we gonna display it as is. Backend is going to - // encode it for us, preventing any errors on the processing stage. - // Later, we decode this using Javascript so it looks like it's normal HTML being injected. - // To get all elements that need frontend decoding, we use 'data-state' property. + // Encode any HTML elements now so that DOMDocument doesn't throw any errors, + // Later we can decode specific elements. $_child = $this->document->createElement($child['element'], ''); $_child->setAttribute('data-state', 'encoded-html'); @@ -2056,9 +2101,6 @@ class PdfBuilder } else { - // .. in case string doesn't contain any HTML, we'll just return - // raw $content - $_child = $this->document->createElement($child['element'], htmlspecialchars($child['content'])); } @@ -2077,14 +2119,18 @@ class PdfBuilder return $this; } - + + /** + * updateVariables + * + * @return void + */ public function updateVariables() { $html = strtr($this->getCompiledHTML(), $this->service->html_variables['labels']); $html = strtr($html, $this->service->html_variables['values']); - //old block @$this->document->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); //new block @@ -2098,49 +2144,60 @@ class PdfBuilder return $this; } - public function updateVariable(string $element, string $variable, string $value) + // public function updateVariable(string $element, string $variable, string $value) + // { + // $element = $this->document->getElementById($element); + + // $original = $element->nodeValue; + + // $element->nodeValue = ''; + + // $replaced = strtr($original, [$variable => $value]); + + // $element->appendChild( + // $this->document->createTextNode($replaced) + // ); + + // return $element; + // } + + public function getEmptyElements(): self { - $element = $this->document->getElementById($element); + foreach ($this->sections as $key => $element) { + if (isset($element['elements'])) { + $this->sections[$key] = $this->getEmptyChildren($element); + } + } - $original = $element->nodeValue; + return $this; + } - $element->nodeValue = ''; + public function getEmptyChildren(array $element): array + { + foreach ($element['elements'] as $key => &$child) { + if ($this->isChildEmpty($child)) { + $child['is_empty'] = true; + } - $replaced = strtr($original, [$variable => $value]); - - $element->appendChild( - $this->document->createTextNode($replaced) - ); + if (isset($child['elements'])) { + $child = $this->getEmptyChildren($child); + } + } return $element; } - public function getEmptyElements(): self + private function isChildEmpty(array $child): bool { - foreach ($this->sections as $element) { - if (isset($element['elements'])) { - $this->getEmptyChildrens($element['elements'], $this->service->html_variables); - } + if (!isset($child['content']) && isset($child['show_empty']) && $child['show_empty'] === false) { + return true; } - return $this; - } - - public function getEmptyChildrens(array $children) - { - 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; - } - } - - if (isset($child['elements'])) { - $this->getEmptyChildrens($child['elements']); - } + 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 $this; + return false; } } 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; diff --git a/app/Services/Pdf/PdfMock.php b/app/Services/Pdf/PdfMock.php index 05cc7f0021..fd8437b769 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']); + 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($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; } diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index 3b23761149..d506aa7811 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -143,10 +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/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index c7e1a5abe8..64bfe19b48 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; @@ -327,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], ]; @@ -344,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; @@ -405,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/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..f7f1ec2f26 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); - } - -} +} \ No newline at end of file diff --git a/app/Utils/Traits/Pdf/PdfMaker.php b/app/Utils/Traits/Pdf/PdfMaker.php index 744a20d531..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); $generated = $pdf ->setHtml($html) ->generate(); 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/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/resources/views/pdf-designs/bold.html b/resources/views/pdf-designs/bold.html index 6d3a6f826a..1f2ff0938b 100644 --- a/resources/views/pdf-designs/bold.html +++ b/resources/views/pdf-designs/bold.html @@ -15,17 +15,17 @@ zoom: 80%; margin: 0; padding: 0; - + } - table tr td, table tr, th { + table tr td, + table tr, + th { font-size: $font_size !important; } html { - /* width: 210mm; */ - /* height: 200mm; */ margin: 0; padding: 0; } @@ -40,11 +40,11 @@ padding: 0; } - #spacer-table > * > tr > td { + #spacer-table>*>tr>td { padding: 0; } - #spacer-table{ + #spacer-table { width: 100%; } @@ -61,14 +61,16 @@ } - #header, #header-spacer { + #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; } + .company-logo { max-width: $company_logo_size; object-fit: contain; @@ -89,11 +91,19 @@ } #client-details { - padding-right:1rem; + padding-right: 1rem; display: flex; flex-direction: column; line-height: var(--line-height) !important; - padding-left: 1.5rem; + padding-left: 1rem; + } + + #vendor-details { + padding-right: 1rem; + display: flex; + flex-direction: column; + line-height: var(--line-height) !important; + padding-left: 1rem; } #shipping-details { @@ -102,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; } @@ -127,7 +137,7 @@ color: white !important; } - #entity-details > tr, + #entity-details>tr, #entity-details th { font-weight: normal; line-height: var(--line-height) !important; @@ -142,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 { @@ -152,36 +162,46 @@ color: grey; } - [data-ref="table"] > thead { + [data-ref="table"]>thead { text-align: left; } - [data-ref="table"] > thead > tr > th { - padding: 1.5rem; + [data-ref="table"]>thead>tr>th { + 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-top: 1.5rem; + padding-bottom: 1.5rem; + padding-left: 0; + padding-right: 0; } - [data-ref="table"] > tbody > tr > td { - padding: 1.5rem; + th.left-radius { + padding-left: 1rem !important; } 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; } @@ -199,43 +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 - > * - [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 { + #table-totals>*> :last-child { text-align: right; padding-right: 1.5rem; } @@ -243,7 +247,7 @@ .entity-label { text-transform: uppercase; color: var(--primary-color); - padding-left: 1.5rem; + padding-left: 1rem; font-size: 1.5rem; } @@ -259,7 +263,8 @@ color: white; } - #footer, #footer-spacer { + #footer, + #footer-spacer { height: 160px; padding: 1rem 1rem; margin-top: 1rem; @@ -269,7 +274,7 @@ padding-top: 0.5rem } - [data-ref="footer_content"]{ + [data-ref="footer_content"] { padding-right: 1rem; margin-right: 1rem; } @@ -304,23 +309,28 @@ 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'] { - width:30%; - overflow-wrap: break-word; + [data-ref='product_table-product.description-th'], + [data-ref='product_table-product.description-td'] { + width: 30%; + overflow-wrap: break-word; + padding-right: 1rem !important; } - .left-radius { - padding-left: 1rem; - text-align: right; + [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; } .stamp { - transform: rotate(12deg); + transform: rotate(12deg); color: #555; font-size: 3rem; font-weight: 700; @@ -331,24 +341,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; @@ -359,14 +369,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. **/ @@ -401,69 +409,66 @@
- +
- - - + + + - - + - +
-
-
+
+
-
-
-
-

$entity_label

-
-
+
+
+
+
+

$entity_label

+
+
+
+
+

&

+
+
-
-

&

-
+ +
+

+ $entity_label

+
+
+
-
-

$entity_label

-
-
-
-
-
- -
- -
- -
- -
-
- -
-
-
-
-
-
-
$status_logo
-
+
+
+
+
+
+
+
+
+
+
+
$status_logo
+ + - - - - - + + + + +
@@ -474,4 +479,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 0bfc39c494..41f086a7b3 100644 --- a/resources/views/pdf-designs/business.html +++ b/resources/views/pdf-designs/business.html @@ -1,6 +1,6 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + +
-
 
-
-
-
- -
-
-
-
-

$entity_issued_to_label:

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

$entity_issued_to_label

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
$status_logo
+
+
+ +
@@ -443,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 49f089a2cd..3e18ca39fb 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,16 +52,20 @@ .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); grid-auto-flow: column; - justify-content: left; + justify-content: space-between; + + } + + .header-wrapper2 .entity-container { + justify-self: end; + align-self: start; } .company-logo { - /* max-width: 65%;*/ max-width: $company_logo_size; } @@ -87,16 +90,17 @@ 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 { - padding-right: 0.5rem; text-align: left; line-height: var(--line-height); width: 100%; @@ -113,6 +117,10 @@ background-color: #e6e6e6; } + .entity-label { + padding-left: 1rem; + } + #entity-details { text-align: left; width: 100%; @@ -121,7 +129,7 @@ #entity-details th { font-weight: normal; line-height: 1.5rem; - padding-right: 2rem; + padding-left: 1rem; } #client-details { @@ -166,7 +174,7 @@ td.left-radius { padding-left: 1rem !important; } - + th.right-radius { text-align: right !important; } @@ -201,6 +209,7 @@ text-align: left; margin-top: .25rem; padding-left: 7px; + } #table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) { @@ -211,7 +220,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'], @@ -225,7 +234,6 @@ } [data-ref="total_table-footer"] { - padding-left: 1rem; padding-right: 1rem; } @@ -350,10 +358,10 @@ .pqrcode {} - #qr-bill{ - width:100% !important; + #qr-bill { + width: 100% !important; } - + /** Useful snippets, uncomment to enable. **/ /** Hide company logo **/ @@ -405,7 +413,7 @@
-
+

$entity_label

@@ -443,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 1bba0cb3bd..2668c2ae59 100644 --- a/resources/views/pdf-designs/clean.html +++ b/resources/views/pdf-designs/clean.html @@ -40,19 +40,21 @@ padding: 0; } - #qr-bill{ - width:100% !important; + #qr-bill { + width: 100% !important; } .header-container { display: grid; - grid-template-columns: repeat(3, 1fr); - justify-content: space-between; - width:100%; + grid-template-columns: minmax(0, 1.5fr) auto minmax(0, 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); @@ -85,9 +91,11 @@ } .client-and-entity-wrapper { - padding: 1rem; + padding-top: 1rem; + padding-bottom: 1rem; display: grid; - grid-template-columns: 1fr 1fr 1fr; + grid-template-columns: minmax(0, 2fr) auto minmax(0, 1fr); + align-items: start; border-top: 1px solid #d8d8d8; border-bottom: 1px solid #d8d8d8; } @@ -97,6 +105,9 @@ text-align: left; margin-right: 20px; line-height: var(--line-height) !important; + justify-self: start; + align-self: start; + padding-left: 1rem; } #entity-details>tr, @@ -110,7 +121,8 @@ display: flex; flex-direction: column; line-height: var(--line-height); - padding-right: 30px; + justify-self: center; + align-self: start; } #client-details> :first-child { @@ -121,6 +133,9 @@ display: $show_shipping_address; flex-direction: column; line-height: var(--line-height); + justify-self: end; + align-self: start; + padding-left: 1rem; } [data-ref="table"] { @@ -153,7 +168,7 @@ padding: 1rem 1rem; } - th.right-radius { + th.right-radius { padding-right: 1rem; text-align: right; } @@ -162,10 +177,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; } @@ -176,9 +191,8 @@ grid-template-columns: 2fr 1fr; padding-top: 0rem; padding-right: 1rem; - padding-left: 1rem; gap: 80px; - page-break-inside: avoid; + page-break-inside: auto; overflow: visible !important; } @@ -202,15 +216,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; @@ -218,7 +223,6 @@ #footer { margin-top: 10px; - margin-left: 1rem; } /** Markdown-specific styles. **/ @@ -260,7 +264,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; @@ -363,7 +368,7 @@

$entity_label

-
+
@@ -381,10 +386,15 @@
-
$status_logo
+
+ + +
$status_logo
+ + diff --git a/resources/views/pdf-designs/creative.html b/resources/views/pdf-designs/creative.html index 590aea888b..c655777c86 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,35 +42,35 @@ .header-wrapper { display: grid; - grid-template-rows:0.5fr; - grid-template-columns: auto auto auto auto; + 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); grid-auto-flow: column; - justify-content:left; - gap: 20px; + align-items: start; + gap: 0px; line-height: var(--line-height); } .company-logo { - max-width: $company_logo_size; - float:right; + max-width: $company_logo_size; + float: right; } - ,logo-wrapper { - grid-area: e; - align-content: right; - border:1px solid #000; + .logo-wrapper { + grid-area: e; + justify-self: end; + align-self: start; } #entity-details { width: 100%; white-space: nowrap; - margin-right: 3rem; + text-align: right; } - #entity-details p { - margin-top: 5px; + #entity-details p { + margin-top: 5px; } .header-wrapper #client-details, @@ -84,51 +86,72 @@ 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; */ display: grid; - grid-template-columns: 3fr 1fr; + grid-template-columns: 2fr 1fr; + width: 100%; + word-break: break-all; + box-sizing: border-box; margin-top: 1rem; + margin-bottom: 1rem; + } - .entity-label-wrapper .entity-label > * { + .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 { + .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; - } - .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>tr, .entity-label-wrapper #entity-details th { font-weight: normal; + line-height: var(--line-height); + padding-left: 1rem; } [data-ref="table"] { - margin-bottom: 5px; + margin-bottom: 5px; min-width: 100%; table-layout: fixed; overflow-wrap: break-word; @@ -136,7 +159,7 @@ [data-ref="table"]:not(:empty) { border-top: 5px solid var(--primary-color); - margin-top: 3rem; + margin-top: 1rem; } .task-time-details { @@ -152,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; } @@ -173,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; } @@ -187,8 +210,7 @@ grid-template-columns: 2fr 1fr; padding-top: 1rem; margin-right: .75rem; - gap: 80px; - page-break-inside:auto; + page-break-inside: auto; overflow: visible !important; } @@ -202,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)); @@ -212,25 +234,15 @@ text-align: right; } - #table-totals - > * - [data-element='product-table-balance-due'] { - font-weight: bold; - color: var(--primary-color); - } + #table-totals>*>* {} - #table-totals > * > * { - padding-left: 0.5rem; - } - - #table-totals > * > :last-child { + #table-totals>*> :last-child { text-align: right; padding-right: 1rem; } [data-ref="total_table-footer"] { - padding-left: 0.5rem; - padding-right:0.8rem; + padding-right: 0.8rem; } #footer { @@ -259,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; @@ -286,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; @@ -315,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. **/ @@ -356,66 +369,62 @@ - - - - - - - - + + + + + + + - - - - - - - +
+
+

$entity_label

+

#$entity_number

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

- $entity_label  - #$entity_number -

-
-
+
+
+
+
+
+ +
-
-
-
-
-
-
-
-
-
-
-
-
-
$status_logo
-
-
- -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
$status_logo
+ + + + + + + + + + + @@ -424,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 7ab065da70..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,7 +94,10 @@ display: $show_shipping_address; } - .wrapper-right-side { + .wrapper-right-side {} + + .text-with-client { + margin-right: 1px; } .client-entity-wrapper .wrapper-left-side #client-details, @@ -122,17 +125,16 @@ 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; } [data-ref="table"] { margin-top: 3rem; - margin-bottom: 5 - px; + margin-bottom: 5px; min-width: 100%; table-layout: fixed; overflow-wrap: break-word; @@ -144,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; @@ -156,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; } @@ -185,10 +187,8 @@ display: grid; grid-template-columns: 2fr 1fr; padding-top: 0.5rem; - padding-left: 1rem; - margin-right: .75rem; gap: 80px; - page-break-inside:auto; + page-break-inside: auto; overflow: visible !important; } @@ -202,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)); @@ -210,31 +210,15 @@ #table-totals>.totals-table-right-side>*> :nth-child(2) { text-align: right; + margin-right: 1rem; } - #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 { + #table-totals>*> :last-child { text-align: right; - padding-right: 0.5rem; + margin-right: .75rem; } - [data-ref="total_table-footer"] { - padding-left: 1rem - } + [data-ref="total_table-footer"] {} #footer { margin-top: 30px; @@ -262,27 +246,31 @@ .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'] { overflow-wrap: break-word; min-width: 100px !important; } + .stamp { - transform: rotate(12deg); + transform: rotate(12deg); color: #555; font-size: 3rem; font-weight: 700; @@ -293,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; @@ -322,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. **/ @@ -363,66 +349,66 @@ - - - - - - - - + + + + + + + - - - - - - - + + + + + + + +
-
 
-
-
-
- -
-
-
-
-
-

$to_label

-
-
-
-
-

$shipping_label

-
-
-
-

$from_label

-
-
-
-
-

$details_label

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

$to_label

+
+
+
+
+

$shipping_label

+
+
+
+

$from_label

+
+
+
+
+

$details_label

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
$status_logo
- -
-
-
-
-
-
-
-
-
-
-
-
$status_logo
- -
- -
+ +
@@ -431,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 e49b73fb28..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; } @@ -37,7 +39,7 @@ .header-wrapper { display: grid; grid-template-columns: 0.5fr 1.5fr; - gap: 20px; + gap: 0px; line-height: var(--line-height); } @@ -55,7 +57,7 @@ padding-left: 1rem; } - .header-wrapper .header-left-side-wrapper > * { + .header-wrapper .header-left-side-wrapper>* { margin-bottom: 0.8rem; } @@ -63,6 +65,7 @@ .header-wrapper .header-left-side-wrapper #company-address { display: flex; flex-direction: column; + padding-right: 1rem; } .header-wrapper .header-right-side-wrapper { @@ -75,20 +78,37 @@ grid-template-areas: "a b c"; grid-auto-columns: minmax(0, 1fr); grid-auto-flow: column; - justify-content:left; - + align-items: start; } .header-wrapper .header-right-side-wrapper #client-details { display: flex; 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 { - 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; + float: left; + } + + .header-wrapper .header-right-side-wrapper-right { + grid-area: c; + justify-self: end; + align-self: start; + justify-content: end; + float: right; } .shipping-text-label { @@ -105,6 +125,8 @@ .company-logo { max-width: $company_logo_size; + float: right; + object-fit: contain; } .entity-label { @@ -113,7 +135,7 @@ margin: 2rem 0; } - .entity-details-wrapper > * { + .entity-details-wrapper>* { margin-right: 1.5rem; direction: $dir; } @@ -129,10 +151,12 @@ text-transform: uppercase; } - .entity-details-wrapper - [data-element='entity-details-wrapper-invoice-number-label'], - .entity-details-wrapper - [data-element='entity-details-wrapper-amount-due'] { + .entity-property-label:not(:empty)::after { + content: ":"; + } + + .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; } @@ -142,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; } @@ -165,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; @@ -193,15 +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: 1rem; - gap: 80px; - page-break-inside:auto; + margin-right: 0rem; + page-break-inside: auto; overflow: visible !important; } @@ -215,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)); @@ -225,22 +248,7 @@ 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 { + #table-totals>*> :last-child { text-align: right; padding-right: 0rem; } @@ -257,29 +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; - overflow-wrap: break-word; - } - - [data-ref='task_table-task.description-th'] { - 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"], @@ -287,19 +293,27 @@ [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"], + [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; + padding-left: 0 !important; + padding-right: 0 !important; } [data-ref="product_table-product.line_total-th"], @@ -308,24 +322,21 @@ 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%; } - [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"] { @@ -344,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; @@ -365,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; @@ -393,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. **/ @@ -433,90 +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
+ + + + + + + + + + + @@ -525,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 307b50cdc7..7504314b77 100644 --- a/resources/views/pdf-designs/modern.html +++ b/resources/views/pdf-designs/modern.html @@ -1,488 +1,502 @@
- - - - - - - - - + + + + + + + +
-
-
-
- -
-
-
-
-
-
+ + + + + + + + + - - - - - - - -
+
+
+
+ +
+
+
+
+
+
-
+
-
+
-
-
+
+
-
-
-
-
-
-
-
-
$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 0e52daf18a..712d1281a1 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"] { @@ -109,7 +111,6 @@ min-width: 100%; table-layout: fixed; overflow-wrap: break-word; - margin-bottom: 5px; } .task-time-details { @@ -144,8 +145,6 @@ margin-top: 0rem; display: grid; grid-template-columns: 2fr 1fr; - padding-top: .5rem; - padding-left: .5rem; margin-right: .75rem; gap: 80px; page-break-inside: auto; @@ -185,7 +184,7 @@ } [data-ref="total_table-footer"] { - padding-left: 1rem + padding-left: 0rem; } #footer { @@ -239,54 +238,11 @@ } [data-ref='product_table-product.description-td'], - [data-ref='task_table-task.description-th']{ - min-width: 100px !important; + [data-ref='task_table-task.description-th'] { + 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); @@ -339,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 a731537270..bd894bdeb1 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 { @@ -30,7 +32,6 @@ margin-top: 1rem; } - p { margin: 0; padding: 0; @@ -47,10 +48,11 @@ background-color: var(--primary-color); padding: 0.5rem; border-radius: 10px; + 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,25 +66,26 @@ 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; } .contacts-wrapper { display: grid; - gap: 20px; - padding: 1rem 0rem 0rem 2rem; + gap: 0px; + padding: 1rem 0rem 0rem 1rem; grid-template-columns: 1fr 1fr auto; grid-template-areas: "a b c"; grid-auto-columns: minmax(0, 5fr); grid-auto-flow: column; justify-content: space-between; + margin-left: 1rem; } @@ -115,7 +118,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; @@ -124,25 +127,37 @@ .contacts-wrapper #client-details { margin-top: 1rem; - padding: 1rem; + padding-top: 1rem; + 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; + padding-right: 0rem; border-top: 1px solid var(--primary-color); } - .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"] { @@ -161,11 +176,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); @@ -185,17 +200,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; } @@ -220,7 +235,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)); @@ -228,25 +243,6 @@ #table-totals>.totals-table-right-side>*> :nth-child(2) { 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; } @@ -259,7 +255,7 @@ margin-left: -10px; } - #header > * { + #header>* { padding: 10px; } @@ -288,9 +284,9 @@ display: grid; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; margin-left: -10px; - } + } - #footer-colors > * { + #footer-colors>* { padding: 10px; } @@ -308,28 +304,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"] { @@ -337,7 +336,7 @@ } .stamp { - transform: rotate(12deg); + transform: rotate(12deg); color: #555; font-size: 3rem; font-weight: 700; @@ -348,25 +347,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; @@ -377,16 +376,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 **/ @@ -417,67 +414,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
+
+ +
+
+ + + + + + + + + + @@ -504,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/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('