diff --git a/app/Jobs/Entity/CreateRawPdf.php b/app/Jobs/Entity/CreateRawPdf.php index 1be6cd24e114..4f1619587e9a 100644 --- a/app/Jobs/Entity/CreateRawPdf.php +++ b/app/Jobs/Entity/CreateRawPdf.php @@ -23,6 +23,7 @@ use App\Models\QuoteInvitation; use App\Utils\Traits\MakesHash; use App\Models\CreditInvitation; use App\Models\RecurringInvoice; +use App\Services\Pdf\PdfService; use App\Utils\PhantomJS\Phantom; use App\Models\InvoiceInvitation; use App\Utils\HostedPDF\NinjaPdf; @@ -89,6 +90,17 @@ class CreateRawPdf implements ShouldQueue public function handle() { + /** Testing this override to improve PDF generation performance */ + $ps = new PdfService($this->invitation, 'product', [ + 'client' => $this->entity->client, + "{$this->entity_string}s" => [$this->entity], + ]); + + $pdf = $ps->boot()->getPdf(); + nlog("pdf timer = ". $ps->execution_time); + + + /* Forget the singleton*/ App::forgetInstance('translator'); diff --git a/app/Services/Client/Statement.php b/app/Services/Client/Statement.php index 4cca2b8bb770..7bd42e14cef9 100644 --- a/app/Services/Client/Statement.php +++ b/app/Services/Client/Statement.php @@ -144,8 +144,8 @@ class Statement 'variables' => collect([$variables]), 'invoices' => $this->getInvoices()->get(), 'payments' => $this->options['show_payments_table'] ? $this->getPayments()->get() : collect([]), - 'credits' => $this->options['show_credits_table'] ? $this->getCredits()->get() : [], - 'aging' => $this->options['show_aging_table'] ? $this->getAging() : [], + 'credits' => $this->options['show_credits_table'] ? $this->getCredits()->get() : collect([]), + 'aging' => $this->options['show_aging_table'] ? $this->getAging() : collect([]), ]); $html = $ts->getHtml(); diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 2852345f2f7a..cc6631e5d542 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -11,13 +11,14 @@ namespace App\Services\Pdf; -use App\Models\Credit; -use App\Models\Quote; -use App\Utils\Helpers; -use App\Utils\Traits\MakesDates; use DOMDocument; -use Illuminate\Support\Carbon; +use App\Models\Quote; +use App\Models\Credit; +use App\Utils\Helpers; use Illuminate\Support\Str; +use Illuminate\Support\Carbon; +use App\Utils\Traits\MakesDates; +use App\Services\Template\TemplateService; use League\CommonMark\CommonMarkConverter; class PdfBuilder @@ -67,6 +68,7 @@ class PdfBuilder ->buildSections() ->getEmptyElements() ->updateElementProperties() + ->parseTwigElements() ->updateVariables(); return $this; @@ -104,6 +106,40 @@ class PdfBuilder return $this; } + private function parseTwigElements() + { + + $replacements = []; + $contents = $this->document->getElementsByTagName('ninja'); + + $template_service = new TemplateService(); + $data = $template_service->processData($this->service->options)->getData(); + + $twig = $template_service->twig; + + foreach ($contents as $content) { + + $template = $content->ownerDocument->saveHTML($content); + + $template = $twig->createTemplate(html_entity_decode($template)); + $template = $template->render($data); + + $f = $this->document->createDocumentFragment(); + $f->appendXML($template); + $replacements[] = $f; + + } + + foreach($contents as $key => $content) { + $content->parentNode->replaceChild($replacements[$key], $content); + } + + $contents = null; + + return $this; + + } + public function setDocument($document): self { $this->document = $document; @@ -1091,7 +1127,8 @@ class PdfBuilder } elseif (Str::startsWith($variable, '$custom_surcharge')) { $_variable = ltrim($variable, '$'); // $custom_surcharge1 -> custom_surcharge1 - $visible = intval($this->service->config->entity->{$_variable}) != 0; + // $visible = intval($this->service->config->entity->{$_variable}) != 0; + $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']], @@ -1546,12 +1583,6 @@ class PdfBuilder // Dom Traversal /////////////////////////////////////// - - public function getSectionNode(string $selector) - { - return $this->document->getElementById($selector); - } - public function updateElementProperties() :self { foreach ($this->sections as $element) { diff --git a/app/Services/Pdf/PdfMock.php b/app/Services/Pdf/PdfMock.php index c120168f2427..c7b98de46a71 100644 --- a/app/Services/Pdf/PdfMock.php +++ b/app/Services/Pdf/PdfMock.php @@ -195,7 +195,6 @@ class PdfMock { return ['values' => [ - '$client.shipping_postal_code' => '46420', '$client.billing_postal_code' => '11243', '$company.city_state_postal' => 'Beveley Hills, CA, 90210', @@ -496,6 +495,8 @@ class PdfMock '$show_shipping_address' => $this->settings->show_shipping_address ? 'flex' : 'none', '$show_shipping_address_block' => $this->settings->show_shipping_address ? 'block' : 'none', '$show_shipping_address_visibility' => $this->settings->show_shipping_address ? 'visible' : 'hidden', + '$start_date' => '31/01/2023', + '$end_date' => '31/12/2023', ], 'labels' => $this->mockTranslatedLabels(), ]; @@ -811,21 +812,23 @@ class PdfMock '$tax_label' => ctrans('texts.tax'), '$dir_label' => '', '$to_label' => ctrans('texts.to'), + '$start_date_label' => ctrans('texts.start_date'), + '$end_date_label' => ctrans('texts.end_date'), ]; } - + private function getVendorStubVariables() { return ['values' => [ - '$vendor.billing_postal_code' => '06270-5526', - '$company.postal_city_state' => '29359 New Loy, Delaware', - '$company.city_state_postal' => 'New Loy, Delaware 29359', - '$product.gross_line_total' => '', - '$purchase_order.po_number' => 'PO12345', - '$vendor.postal_city_state' => '06270-5526 Jameyhaven, West Virginia', - '$vendor.city_state_postal' => 'Jameyhaven, West Virginia 06270-5526', - '$purchase_order.due_date' => '02-12-2021', - '$vendor.billing_address1' => '589', + '$vendor.billing_postal_code' => '06270-5526', + '$company.postal_city_state' => '29359 New Loy, Delaware', + '$company.city_state_postal' => 'New Loy, Delaware 29359', + '$product.gross_line_total' => '', + '$purchase_order.po_number' => 'PO12345', + '$vendor.postal_city_state' => '06270-5526 Jameyhaven, West Virginia', + '$vendor.city_state_postal' => 'Jameyhaven, West Virginia 06270-5526', + '$purchase_order.due_date' => '02-12-2021', + '$vendor.billing_address1' => '589', '$vendor.billing_address2' => '761 Odessa Centers Suite 673', '$invoiceninja.whitelabel' => 'https://invoicing.co/images/new_logo.png', '$purchase_order.custom1' => 'Custom 1', diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index d7fde2b8cc5a..1bf803edba87 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -44,6 +44,10 @@ class PdfService public array $options; + private float $start_time; + + public float $execution_time; + const DELIVERY_NOTE = 'delivery_note'; const STATEMENT = 'statement'; const PURCHASE_ORDER = 'purchase_order'; @@ -58,10 +62,14 @@ class PdfService $this->document_type = $document_type; $this->options = $options; + + $this->start_time = microtime(true); + } public function boot(): self - { + { + $this->init(); return $this; @@ -90,6 +98,8 @@ class PdfService throw new \Exception($e->getMessage(), $e->getCode()); } + $this->execution_time = microtime(true) - $this->start_time; + return $pdf; } @@ -104,9 +114,11 @@ class PdfService $html = $this->builder->getCompiledHTML(); if (config('ninja.log_pdf_html')) { - info($html); + nlog($html); } + $this->execution_time = microtime(true) - $this->start_time; + return $html; } diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index d7b0264e928a..461e6766bbb1 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -11,32 +11,31 @@ namespace App\Services\Template; -use App\Utils\Number; use App\Models\Client; +use App\Models\Company; use App\Models\Credit; use App\Models\Design; -use App\Models\Company; use App\Models\Invoice; use App\Models\Payment; use App\Models\Project; -use App\Models\Activity; -use App\Utils\HtmlEngine; -use League\Fractal\Manager; use App\Models\PurchaseOrder; -use App\Utils\VendorHtmlEngine; -use App\Utils\PaymentHtmlEngine; -use App\Utils\Traits\MakesDates; -use App\Utils\HostedPDF\NinjaPdf; -use App\Utils\Traits\Pdf\PdfMaker; -use Twig\Extra\Intl\IntlExtension; -use App\Transformers\TaskTransformer; -use App\Transformers\QuoteTransformer; -use App\Services\Template\TemplateMock; -use App\Transformers\CreditTransformer; -use App\Transformers\InvoiceTransformer; +use App\Models\Quote; +use App\Models\RecurringInvoice; +use App\Models\Vendor; use App\Transformers\ProjectTransformer; use App\Transformers\PurchaseOrderTransformer; +use App\Transformers\QuoteTransformer; +use App\Transformers\TaskTransformer; +use App\Utils\HostedPDF\NinjaPdf; +use App\Utils\HtmlEngine; +use App\Utils\Number; +use App\Utils\PaymentHtmlEngine; +use App\Utils\Traits\MakesDates; +use App\Utils\Traits\Pdf\PdfMaker; +use App\Utils\VendorHtmlEngine; +use League\Fractal\Manager; use League\Fractal\Serializer\ArraySerializer; +use Twig\Extra\Intl\IntlExtension; class TemplateService { @@ -54,6 +53,14 @@ class TemplateService public ?Company $company; + private ?Client $client; + + private ?Vendor $vendor; + + private Invoice | Quote | Credit | PurchaseOrder | RecurringInvoice $entity; + + private Payment $payment; + public function __construct(public ?Design $template = null) { $this->template = $template; @@ -71,7 +78,7 @@ class TemplateService $this->document->validateOnParse = true; $loader = new \Twig\Loader\FilesystemLoader(storage_path()); - $this->twig = new \Twig\Environment($loader,[ + $this->twig = new \Twig\Environment($loader, [ 'debug' => true, ]); $string_extension = new \Twig\Extension\StringLoaderExtension(); @@ -84,6 +91,12 @@ class TemplateService }); $this->twig->addFunction($function); + $filter = new \Twig\TwigFilter('sum', function (array $array, string $column) { + return array_sum(array_column($array, $column)); + }); + + $this->twig->addFilter($filter); + return $this; } @@ -100,7 +113,7 @@ class TemplateService ->processData($data) ->parseNinjaBlocks() ->processVariables($data) - ->parseVariables(); + ->parseVariables(); return $this; } @@ -136,7 +149,12 @@ class TemplateService { return $this->compiled_html; } - + + /** + * Returns the PDF string + * + * @return mixed + */ public function getPdf(): mixed { @@ -149,8 +167,24 @@ class TemplateService return $pdf; } - - private function processData($data): self + + /** + * Get the parsed data + * + * @return array + */ + public function getData(): array + { + return $this->data; + } + + /** + * Process data variables + * + * @param mixed $data + * @return self + */ + public function processData($data): self { $this->data = $this->preProcessDataBlocks($data); @@ -160,7 +194,7 @@ class TemplateService /** * Parses all Ninja tags in the document - * + * * @return self */ private function parseNinjaBlocks(): self @@ -175,24 +209,19 @@ class TemplateService try { $template = $this->twig->createTemplate(html_entity_decode($template)); - } - catch(\Twig\Error\SyntaxError $e) { + } catch(\Twig\Error\SyntaxError $e) { nlog($e->getMessage()); throw ($e); - } - catch(\Twig\Error\Error $e) { + } catch(\Twig\Error\Error $e) { nlog("error = " .$e->getMessage()); throw ($e); - } - catch(\Twig\Error\RuntimeError $e) { + } catch(\Twig\Error\RuntimeError $e) { nlog("runtime = " .$e->getMessage()); throw ($e); - } - catch(\Twig\Error\LoaderError $e) { + } catch(\Twig\Error\LoaderError $e) { nlog("loader = " . $e->getMessage()); throw ($e); - } - catch(\Twig\Error\SecurityError $e) { + } catch(\Twig\Error\SecurityError $e) { nlog("security = " . $e->getMessage()); throw ($e); } @@ -218,7 +247,7 @@ class TemplateService /** * Parses all variables in the document - * + * * @return self */ private function parseVariables(): self @@ -228,8 +257,7 @@ class TemplateService foreach($this->variables as $key => $variable) { - if(isset($variable['labels']) && isset($variable['values'])) - { + if(isset($variable['labels']) && isset($variable['values'])) { $html = strtr($html, $variable['labels']); $html = strtr($html, $variable['values']); } @@ -260,8 +288,9 @@ class TemplateService */ private function compose(): self { - if(!$this->template) + if(!$this->template) { return $this; + } $html = ''; $html .= $this->template->design->includes; @@ -276,7 +305,7 @@ class TemplateService } /** - * Inject the template components + * Inject the template components * manually * * @return self @@ -308,11 +337,12 @@ class TemplateService $processed = []; - if(in_array($key, ['tasks','projects','aging']) || !$value->first() ) + if(in_array($key, ['tasks','projects','aging']) || !$value->first()) { return $processed; + } match ($key) { - 'variables' => $processed = $value->first() ?? [], + 'variables' => $processed = $value->first() ?? [], 'invoices' => $processed = (new HtmlEngine($value->first()->invitations()->first()))->generateLabelsAndValues() ?? [], 'quotes' => $processed = (new HtmlEngine($value->first()->invitations()->first()))->generateLabelsAndValues() ?? [], 'credits' => $processed = (new HtmlEngine($value->first()->invitations()->first()))->generateLabelsAndValues() ?? [], @@ -332,7 +362,7 @@ class TemplateService private function preProcessDataBlocks($data): array { - return collect($data)->map(function ($value, $key){ + return collect($data)->map(function ($value, $key) { $processed = []; @@ -356,75 +386,76 @@ class TemplateService public function processInvoices($invoices): array { $invoices = collect($invoices) - ->map(function ($invoice){ + ->map(function ($invoice) { - $payments = []; - - if($invoice->payments ?? false) { - $payments = $invoice->payments->map(function ($payment) { - return $this->transformPayment($payment); - })->toArray(); - } + $payments = []; + $this->entity = $invoice; - return [ - 'amount' => Number::formatMoney($invoice->amount, $invoice->client), - 'balance' => Number::formatMoney($invoice->balance, $invoice->client), - 'balance_raw' => $invoice->balance, - 'number' => $invoice->number ?: '', - 'discount' => $invoice->discount, - 'po_number' => $invoice->po_number ?: '', - 'date' => $this->translateDate($invoice->date, $invoice->client->date_format(), $invoice->client->locale()), - 'last_sent_date' => $this->translateDate($invoice->last_sent_date, $invoice->client->date_format(), $invoice->client->locale()), - 'next_send_date' => $this->translateDate($invoice->next_send_date, $invoice->client->date_format(), $invoice->client->locale()), - 'due_date' => $this->translateDate($invoice->due_date, $invoice->client->date_format(), $invoice->client->locale()), - 'terms' => $invoice->terms ?: '', - 'public_notes' => $invoice->public_notes ?: '', - 'private_notes' => $invoice->private_notes ?: '', - 'uses_inclusive_taxes' => (bool) $invoice->uses_inclusive_taxes, - 'tax_name1' => $invoice->tax_name1 ?? '', - 'tax_rate1' => (float) $invoice->tax_rate1, - 'tax_name2' => $invoice->tax_name2 ?? '', - 'tax_rate2' => (float) $invoice->tax_rate2, - 'tax_name3' => $invoice->tax_name3 ?? '', - 'tax_rate3' => (float) $invoice->tax_rate3, - 'total_taxes' => Number::formatMoney($invoice->total_taxes, $invoice->client), - 'total_taxes_raw' => $invoice->total_taxes, - 'is_amount_discount' => (bool) $invoice->is_amount_discount ?? false, - 'footer' => $invoice->footer ?? '', - 'partial' => $invoice->partial ?? 0, - 'partial_due_date' => $this->translateDate($invoice->partial_due_date, $invoice->client->date_format(), $invoice->client->locale()), - 'custom_value1' => (string) $invoice->custom_value1 ?: '', - 'custom_value2' => (string) $invoice->custom_value2 ?: '', - 'custom_value3' => (string) $invoice->custom_value3 ?: '', - 'custom_value4' => (string) $invoice->custom_value4 ?: '', - 'custom_surcharge1' => (float) $invoice->custom_surcharge1, - 'custom_surcharge2' => (float) $invoice->custom_surcharge2, - 'custom_surcharge3' => (float) $invoice->custom_surcharge3, - 'custom_surcharge4' => (float) $invoice->custom_surcharge4, - 'exchange_rate' => (float) $invoice->exchange_rate, - 'custom_surcharge_tax1' => (bool) $invoice->custom_surcharge_tax1, - 'custom_surcharge_tax2' => (bool) $invoice->custom_surcharge_tax2, - 'custom_surcharge_tax3' => (bool) $invoice->custom_surcharge_tax3, - 'custom_surcharge_tax4' => (bool) $invoice->custom_surcharge_tax4, - 'line_items' => $invoice->line_items ? $this->padLineItems($invoice->line_items, $invoice->client): (array) [], - 'reminder1_sent' => $this->translateDate($invoice->reminder1_sent, $invoice->client->date_format(), $invoice->client->locale()), - 'reminder2_sent' => $this->translateDate($invoice->reminder2_sent, $invoice->client->date_format(), $invoice->client->locale()), - 'reminder3_sent' => $this->translateDate($invoice->reminder3_sent, $invoice->client->date_format(), $invoice->client->locale()), - 'reminder_last_sent' => $this->translateDate($invoice->reminder_last_sent, $invoice->client->date_format(), $invoice->client->locale()), - 'paid_to_date' => Number::formatMoney($invoice->paid_to_date, $invoice->client), - 'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled, - 'client' => [ - 'name' => $invoice->client->present()->name(), - 'balance' => $invoice->client->balance, - 'payment_balance' => $invoice->client->payment_balance, - 'credit_balance' => $invoice->client->credit_balance, - ], - 'payments' => $payments, - 'total_tax_map' => $invoice->calc()->getTotalTaxMap(), - 'line_tax_map' => $invoice->calc()->getTaxMap(), - ]; + if($invoice->payments ?? false) { + $payments = $invoice->payments->map(function ($payment) { + return $this->transformPayment($payment); + })->toArray(); + } - }); + return [ + 'amount' => Number::formatMoney($invoice->amount, $invoice->client), + 'balance' => Number::formatMoney($invoice->balance, $invoice->client), + 'balance_raw' => $invoice->balance, + 'number' => $invoice->number ?: '', + 'discount' => $invoice->discount, + 'po_number' => $invoice->po_number ?: '', + 'date' => $this->translateDate($invoice->date, $invoice->client->date_format(), $invoice->client->locale()), + 'last_sent_date' => $this->translateDate($invoice->last_sent_date, $invoice->client->date_format(), $invoice->client->locale()), + 'next_send_date' => $this->translateDate($invoice->next_send_date, $invoice->client->date_format(), $invoice->client->locale()), + 'due_date' => $this->translateDate($invoice->due_date, $invoice->client->date_format(), $invoice->client->locale()), + 'terms' => $invoice->terms ?: '', + 'public_notes' => $invoice->public_notes ?: '', + 'private_notes' => $invoice->private_notes ?: '', + 'uses_inclusive_taxes' => (bool) $invoice->uses_inclusive_taxes, + 'tax_name1' => $invoice->tax_name1 ?? '', + 'tax_rate1' => (float) $invoice->tax_rate1, + 'tax_name2' => $invoice->tax_name2 ?? '', + 'tax_rate2' => (float) $invoice->tax_rate2, + 'tax_name3' => $invoice->tax_name3 ?? '', + 'tax_rate3' => (float) $invoice->tax_rate3, + 'total_taxes' => Number::formatMoney($invoice->total_taxes, $invoice->client), + 'total_taxes_raw' => $invoice->total_taxes, + 'is_amount_discount' => (bool) $invoice->is_amount_discount ?? false, + 'footer' => $invoice->footer ?? '', + 'partial' => $invoice->partial ?? 0, + 'partial_due_date' => $this->translateDate($invoice->partial_due_date, $invoice->client->date_format(), $invoice->client->locale()), + 'custom_value1' => (string) $invoice->custom_value1 ?: '', + 'custom_value2' => (string) $invoice->custom_value2 ?: '', + 'custom_value3' => (string) $invoice->custom_value3 ?: '', + 'custom_value4' => (string) $invoice->custom_value4 ?: '', + 'custom_surcharge1' => (float) $invoice->custom_surcharge1, + 'custom_surcharge2' => (float) $invoice->custom_surcharge2, + 'custom_surcharge3' => (float) $invoice->custom_surcharge3, + 'custom_surcharge4' => (float) $invoice->custom_surcharge4, + 'exchange_rate' => (float) $invoice->exchange_rate, + 'custom_surcharge_tax1' => (bool) $invoice->custom_surcharge_tax1, + 'custom_surcharge_tax2' => (bool) $invoice->custom_surcharge_tax2, + 'custom_surcharge_tax3' => (bool) $invoice->custom_surcharge_tax3, + 'custom_surcharge_tax4' => (bool) $invoice->custom_surcharge_tax4, + 'line_items' => $invoice->line_items ? $this->padLineItems($invoice->line_items, $invoice->client): (array) [], + 'reminder1_sent' => $this->translateDate($invoice->reminder1_sent, $invoice->client->date_format(), $invoice->client->locale()), + 'reminder2_sent' => $this->translateDate($invoice->reminder2_sent, $invoice->client->date_format(), $invoice->client->locale()), + 'reminder3_sent' => $this->translateDate($invoice->reminder3_sent, $invoice->client->date_format(), $invoice->client->locale()), + 'reminder_last_sent' => $this->translateDate($invoice->reminder_last_sent, $invoice->client->date_format(), $invoice->client->locale()), + 'paid_to_date' => Number::formatMoney($invoice->paid_to_date, $invoice->client), + 'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled, + 'client' => [ + 'name' => $invoice->client->present()->name(), + 'balance' => $invoice->client->balance, + 'payment_balance' => $invoice->client->payment_balance, + 'credit_balance' => $invoice->client->credit_balance, + ], + 'payments' => $payments, + 'total_tax_map' => $invoice->calc()->getTotalTaxMap(), + 'line_tax_map' => $invoice->calc()->getTaxMap(), + ]; + + }); return $invoices->toArray(); @@ -432,7 +463,7 @@ class TemplateService public function padLineItems(array $items, Client $client): array { - return collect($items)->map(function ($item) use ($client){ + return collect($items)->map(function ($item) use ($client) { $item->cost_raw = $item->cost ?? 0; $item->discount_raw = $item->discount ?? 0; @@ -443,8 +474,9 @@ class TemplateService $item->cost = Number::formatMoney($item->cost_raw, $client); - if($item->is_amount_discount) + if($item->is_amount_discount) { $item->discount = Number::formatMoney($item->discount_raw, $client); + } $item->line_total = Number::formatMoney($item->line_total_raw, $client); $item->gross_line_total = Number::formatMoney($item->gross_line_total_raw, $client); @@ -460,7 +492,9 @@ class TemplateService { $data = []; - + + $this->payment = $payment; + $credits = $payment->credits->map(function ($credit) use ($payment) { return [ 'credit' => $credit->number, @@ -559,7 +593,7 @@ class TemplateService { return collect($payment->refund_meta ?? []) - ->map(function ($refund) use($payment){ + ->map(function ($refund) use ($payment) { $date = \Carbon\Carbon::parse($refund['date'])->addSeconds($payment->client->timezone_offset()); $date = $this->translateDate($date, $payment->client->date_format(), $payment->client->locale()); @@ -568,7 +602,7 @@ class TemplateService $map = []; foreach($refund['invoices'] as $refunded_invoice) { - $invoice = Invoice::withTrashed()->find($refunded_invoice['invoice_id']); + $invoice = Invoice::withTrashed()->find($refunded_invoice['invoice_id']); $amount = Number::formatMoney($refunded_invoice['amount'], $payment->client); $notes = ctrans('texts.status_partially_refunded_amount', ['amount' => $amount]); @@ -612,67 +646,69 @@ class TemplateService public function processCredits($credits): array { $credits = collect($credits) - ->map(function ($credit){ + ->map(function ($credit) { - return [ - 'amount' => Number::formatMoney($credit->amount, $credit->client), - 'balance' => Number::formatMoney($credit->balance, $credit->client), - 'balance_raw' => $credit->balance, - 'number' => $credit->number ?: '', - 'discount' => $credit->discount, - 'po_number' => $credit->po_number ?: '', - 'date' => $this->translateDate($credit->date, $credit->client->date_format(), $credit->client->locale()), - 'last_sent_date' => $this->translateDate($credit->last_sent_date, $credit->client->date_format(), $credit->client->locale()), - 'next_send_date' => $this->translateDate($credit->next_send_date, $credit->client->date_format(), $credit->client->locale()), - 'due_date' => $this->translateDate($credit->due_date, $credit->client->date_format(), $credit->client->locale()), - 'terms' => $credit->terms ?: '', - 'public_notes' => $credit->public_notes ?: '', - 'private_notes' => $credit->private_notes ?: '', - 'uses_inclusive_taxes' => (bool) $credit->uses_inclusive_taxes, - 'tax_name1' => $credit->tax_name1 ?? '', - 'tax_rate1' => (float) $credit->tax_rate1, - 'tax_name2' => $credit->tax_name2 ?? '', - 'tax_rate2' => (float) $credit->tax_rate2, - 'tax_name3' => $credit->tax_name3 ?? '', - 'tax_rate3' => (float) $credit->tax_rate3, - 'total_taxes' => Number::formatMoney($credit->total_taxes, $credit->client), - 'total_taxes_raw' => $credit->total_taxes, - 'is_amount_discount' => (bool) $credit->is_amount_discount ?? false, - 'footer' => $credit->footer ?? '', - 'partial' => $credit->partial ?? 0, - 'partial_due_date' => $this->translateDate($credit->partial_due_date, $credit->client->date_format(), $credit->client->locale()), - 'custom_value1' => (string) $credit->custom_value1 ?: '', - 'custom_value2' => (string) $credit->custom_value2 ?: '', - 'custom_value3' => (string) $credit->custom_value3 ?: '', - 'custom_value4' => (string) $credit->custom_value4 ?: '', - 'custom_surcharge1' => (float) $credit->custom_surcharge1, - 'custom_surcharge2' => (float) $credit->custom_surcharge2, - 'custom_surcharge3' => (float) $credit->custom_surcharge3, - 'custom_surcharge4' => (float) $credit->custom_surcharge4, - 'exchange_rate' => (float) $credit->exchange_rate, - 'custom_surcharge_tax1' => (bool) $credit->custom_surcharge_tax1, - 'custom_surcharge_tax2' => (bool) $credit->custom_surcharge_tax2, - 'custom_surcharge_tax3' => (bool) $credit->custom_surcharge_tax3, - 'custom_surcharge_tax4' => (bool) $credit->custom_surcharge_tax4, - 'line_items' => $credit->line_items ? $this->padLineItems($credit->line_items, $credit->client): (array) [], - 'reminder1_sent' => $this->translateDate($credit->reminder1_sent, $credit->client->date_format(), $credit->client->locale()), - 'reminder2_sent' => $this->translateDate($credit->reminder2_sent, $credit->client->date_format(), $credit->client->locale()), - 'reminder3_sent' => $this->translateDate($credit->reminder3_sent, $credit->client->date_format(), $credit->client->locale()), - 'reminder_last_sent' => $this->translateDate($credit->reminder_last_sent, $credit->client->date_format(), $credit->client->locale()), - 'paid_to_date' => Number::formatMoney($credit->paid_to_date, $credit->client), - 'auto_bill_enabled' => (bool) $credit->auto_bill_enabled, - 'client' => [ - 'name' => $credit->client->present()->name(), - 'balance' => $credit->client->balance, - 'payment_balance' => $credit->client->payment_balance, - 'credit_balance' => $credit->client->credit_balance, - ], - 'payments' => [], - 'total_tax_map' => $credit->calc()->getTotalTaxMap(), - 'line_tax_map' => $credit->calc()->getTaxMap(), - ]; + $this->entity = $credit; + + return [ + 'amount' => Number::formatMoney($credit->amount, $credit->client), + 'balance' => Number::formatMoney($credit->balance, $credit->client), + 'balance_raw' => $credit->balance, + 'number' => $credit->number ?: '', + 'discount' => $credit->discount, + 'po_number' => $credit->po_number ?: '', + 'date' => $this->translateDate($credit->date, $credit->client->date_format(), $credit->client->locale()), + 'last_sent_date' => $this->translateDate($credit->last_sent_date, $credit->client->date_format(), $credit->client->locale()), + 'next_send_date' => $this->translateDate($credit->next_send_date, $credit->client->date_format(), $credit->client->locale()), + 'due_date' => $this->translateDate($credit->due_date, $credit->client->date_format(), $credit->client->locale()), + 'terms' => $credit->terms ?: '', + 'public_notes' => $credit->public_notes ?: '', + 'private_notes' => $credit->private_notes ?: '', + 'uses_inclusive_taxes' => (bool) $credit->uses_inclusive_taxes, + 'tax_name1' => $credit->tax_name1 ?? '', + 'tax_rate1' => (float) $credit->tax_rate1, + 'tax_name2' => $credit->tax_name2 ?? '', + 'tax_rate2' => (float) $credit->tax_rate2, + 'tax_name3' => $credit->tax_name3 ?? '', + 'tax_rate3' => (float) $credit->tax_rate3, + 'total_taxes' => Number::formatMoney($credit->total_taxes, $credit->client), + 'total_taxes_raw' => $credit->total_taxes, + 'is_amount_discount' => (bool) $credit->is_amount_discount ?? false, + 'footer' => $credit->footer ?? '', + 'partial' => $credit->partial ?? 0, + 'partial_due_date' => $this->translateDate($credit->partial_due_date, $credit->client->date_format(), $credit->client->locale()), + 'custom_value1' => (string) $credit->custom_value1 ?: '', + 'custom_value2' => (string) $credit->custom_value2 ?: '', + 'custom_value3' => (string) $credit->custom_value3 ?: '', + 'custom_value4' => (string) $credit->custom_value4 ?: '', + 'custom_surcharge1' => (float) $credit->custom_surcharge1, + 'custom_surcharge2' => (float) $credit->custom_surcharge2, + 'custom_surcharge3' => (float) $credit->custom_surcharge3, + 'custom_surcharge4' => (float) $credit->custom_surcharge4, + 'exchange_rate' => (float) $credit->exchange_rate, + 'custom_surcharge_tax1' => (bool) $credit->custom_surcharge_tax1, + 'custom_surcharge_tax2' => (bool) $credit->custom_surcharge_tax2, + 'custom_surcharge_tax3' => (bool) $credit->custom_surcharge_tax3, + 'custom_surcharge_tax4' => (bool) $credit->custom_surcharge_tax4, + 'line_items' => $credit->line_items ? $this->padLineItems($credit->line_items, $credit->client): (array) [], + 'reminder1_sent' => $this->translateDate($credit->reminder1_sent, $credit->client->date_format(), $credit->client->locale()), + 'reminder2_sent' => $this->translateDate($credit->reminder2_sent, $credit->client->date_format(), $credit->client->locale()), + 'reminder3_sent' => $this->translateDate($credit->reminder3_sent, $credit->client->date_format(), $credit->client->locale()), + 'reminder_last_sent' => $this->translateDate($credit->reminder_last_sent, $credit->client->date_format(), $credit->client->locale()), + 'paid_to_date' => Number::formatMoney($credit->paid_to_date, $credit->client), + 'auto_bill_enabled' => (bool) $credit->auto_bill_enabled, + 'client' => [ + 'name' => $credit->client->present()->name(), + 'balance' => $credit->client->balance, + 'payment_balance' => $credit->client->payment_balance, + 'credit_balance' => $credit->client->credit_balance, + ], + 'payments' => [], + 'total_tax_map' => $credit->calc()->getTotalTaxMap(), + 'line_tax_map' => $credit->calc()->getTaxMap(), + ]; - }); + }); return $credits->toArray(); @@ -764,5 +800,125 @@ class TemplateService return $this; } + + /** + * Parses and finds any stacks to replace + * + * @return self + */ + private function parseGlobalStacks(): self + { + $stacks = [ + 'entity-details', + 'client-details', + 'vendor-details', + 'company-details', + 'company-address', + 'shipping-details', + ]; -} \ No newline at end of file + collect($stacks)->filter(function ($stack) { + $this->document->getElementById($stack); + + })->each(function ($stack){ + $this->parseStack($stack); + }); + + return $this; + + } + + /** + * Injects field stacks into Template + * + * @param string $stack + * @return self + */ + private function parseStack(string $stack): self + { + match($stack){ + 'entity-details' => $this->entityDetails(), + 'client-details' => $this->clientDetails(), + 'vendor-details' => $this->vendorDetails(), + 'company-details' => $this->companyDetails(), + 'company-address' => $this->companyAddress(), + 'shipping-details' => $this->shippingDetails(), + }; + + return $this; + } + + private function companyDetails(): self + { + + collect($this->company->settings->pdf_variables['company_details']) + ->filter(function ($variable) { + return isset($this->variables['values'][$variable]) && !empty($this->variables['values'][$variable]); + }) + ->map(function ($variable) { + return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]]; + }); + + + return $this; + } + + private function companyAddress(): self + { + + $variables = $this->company->settings->pdf_variables['company_address']; + + $elements = []; + + foreach ($variables as $variable) { + $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_address-' . substr($variable, 1)]]; + } + + return $elements; + + return $this; + } + + private function shippingDetails(): self + { + + return $this; + } + + private function clientDetails(): self + { + + return $this; + } + + private function entityDetails(): self + { + + return $this; + } + + private function vendorDetails(): self + { + + + $elements = []; + + if (!$this->vendor) { + return $elements; + } + + $variables = $this->context['pdf_variables']['vendor_details']; + + foreach ($variables as $variable) { + $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'vendor_details-' . substr($variable, 1)]]; + } + + return $elements; + + + return $this; + } + + + +} diff --git a/lang/en/texts.php b/lang/en/texts.php index aa407bc9b054..68ebe6f49f11 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5163,6 +5163,8 @@ $LANG = array( 'click_or_drop_files_here' => 'Click or drop files here', 'payment_refund_receipt' => 'Payment Refund Receipt # :number', 'payment_receipt' => 'Payment Receipt # :number', + 'load_template_description' => 'The template will be applied to following:', + 'run_template' => 'Run template', ); return $LANG;