mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-10-31 15:57:33 -04:00 
			
		
		
		
	Merge branch 'support_for_custom_statement_designs' of https://github.com/turbo124/invoiceninja into support_for_custom_statement_designs
This commit is contained in:
		
						commit
						8c2f94a1c0
					
				| @ -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'); | ||||
| 
 | ||||
|  | ||||
| @ -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(); | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
| @ -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', | ||||
|  | ||||
| @ -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; | ||||
|     } | ||||
|          | ||||
|  | ||||
| @ -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', | ||||
|         ]; | ||||
| 
 | ||||
| } | ||||
|         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; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user