1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-23 09:51:35 +02: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:
David Bomba 2023-10-12 16:30:36 +11:00
commit 03f5fb35e6
2 changed files with 196 additions and 114 deletions

View File

@ -12,29 +12,33 @@
namespace App\Services\Client; namespace App\Services\Client;
use App\Factory\InvoiceFactory; use App\Utils\Number;
use App\Factory\InvoiceInvitationFactory;
use App\Factory\InvoiceItemFactory;
use App\Models\Client; use App\Models\Client;
use App\Models\Credit;
use App\Models\Design; use App\Models\Design;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Payment; 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\HtmlEngine;
use App\Utils\Number;
use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\Pdf\PdfMaker as PdfMakerTrait;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB; use App\Factory\InvoiceFactory;
use App\Models\Credit;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use App\Utils\PhantomJS\Phantom;
use App\Utils\HostedPDF\NinjaPdf;
use Illuminate\Support\Facades\DB;
use App\Factory\InvoiceItemFactory;
use App\Services\PdfMaker\PdfMaker;
use App\Factory\InvoiceInvitationFactory;
use Illuminate\Database\Eloquent\Builder;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\Pdf\PdfMaker as PdfMakerTrait;
class Statement class Statement
{ {
use PdfMakerTrait; use PdfMakerTrait;
use MakesHash; use MakesHash;
use MakesDates;
/** /**
* @var Invoice|Payment|null * @var Invoice|Payment|null
*/ */
@ -54,6 +58,20 @@ class Statement
$html = new HtmlEngine($this->getInvitation()); $html = new HtmlEngine($this->getInvitation());
$variables = [];
if($this->client->getSetting('statement_design_id') != '') {
$variables['values']['$start_date'] = $this->translateDate($this->options['start_date'], $this->client->date_format(), $this->client->locale());
$variables['values']['$end_date'] = $this->translateDate($this->options['end_date'], $this->client->date_format(), $this->client->locale());
$variables['labels']['$start_date_label'] = ctrans('texts.start_date');
$variables['labels']['$end_date_label'] = ctrans('texts.end_date');
return $this->templateStatement($variables);
}
$variables = $html->generateLabelsAndValues();
if ($this->getDesign()->is_custom) { if ($this->getDesign()->is_custom) {
$this->options['custom_partials'] = \json_decode(\json_encode($this->getDesign()->design), true); $this->options['custom_partials'] = \json_decode(\json_encode($this->getDesign()->design), true);
@ -62,8 +80,6 @@ class Statement
$template = new PdfMakerDesign(strtolower($this->getDesign()->name), $this->options); $template = new PdfMakerDesign(strtolower($this->getDesign()->name), $this->options);
} }
$variables = $html->generateLabelsAndValues();
$state = [ $state = [
'template' => $template->elements([ 'template' => $template->elements([
'client' => $this->client, 'client' => $this->client,
@ -71,9 +87,9 @@ class Statement
'pdf_variables' => (array) $this->entity->company->settings->pdf_variables, 'pdf_variables' => (array) $this->entity->company->settings->pdf_variables,
'$product' => $this->getDesign()->design->product, '$product' => $this->getDesign()->design->product,
'variables' => $variables, 'variables' => $variables,
'invoices' => $this->getInvoices(), 'invoices' => $this->getInvoices()->cursor(),
'payments' => $this->getPayments(), 'payments' => $this->getPayments()->cursor(),
'credits' => $this->getCredits(), 'credits' => $this->getCredits()->cursor(),
'aging' => $this->getAging(), 'aging' => $this->getAging(),
], \App\Services\PdfMaker\Design::STATEMENT), ], \App\Services\PdfMaker\Design::STATEMENT),
'variables' => $variables, 'variables' => $variables,
@ -81,44 +97,65 @@ class Statement
'client' => $this->client, 'client' => $this->client,
'entity' => $this->entity, 'entity' => $this->entity,
'variables' => $variables, 'variables' => $variables,
'invoices' => $this->getInvoices(), 'invoices' => $this->getInvoices()->cursor(),
'payments' => $this->getPayments(), 'payments' => $this->getPayments()->cursor(),
'credits' => $this->getCredits(), 'credits' => $this->getCredits()->cursor(),
'aging' => $this->getAging(), 'aging' => $this->getAging(),
], ],
'process_markdown' => $this->entity->client->company->markdown_enabled, 'process_markdown' => $this->entity->client->company->markdown_enabled,
]; ];
if($this->options['template'] ?? false){
$template = Design::where('id', $this->decodePrimaryKey($this->options['template']))
->where('company_id', $this->client->company_id)
->first();
$ts = $template->service()->build([ $maker = new PdfMaker($state);
'client' => $this->client,
'entity' => $this->entity,
'variables' => $variables,
'invoices' => $this->getInvoices(),
'payments' => $this->getPayments(),
'credits' => $this->getCredits(),
'aging' => $this->getAging(),
]);
$html = $ts->getHtml(); $maker
->design($template)
->build();
$pdf = null;
$html = $maker->getCompiledHTML(true);
if ($this->rollback) {
\DB::connection(config('database.default'))->rollBack();
} }
else {
$maker = new PdfMaker($state); $pdf = $this->convertToPdf($html);
$maker $maker = null;
->design($template) $state = null;
->build();
$pdf = null; return $pdf;
$html = $maker->getCompiledHTML(true); }
}
private function templateStatement($variables)
{
if(isset($this->options['template']))
$statement_design_id = $this->options['template'];
else
$statement_design_id = $this->client->getSetting('statement_design_id');
$template = Design::where('id', $this->decodePrimaryKey($statement_design_id))
->where('company_id', $this->client->company_id)
->first();
$ts = $template->service()->build([
'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() : [],
]);
$html = $ts->getHtml();
return $this->convertToPdf($html);
}
private function convertToPdf(string $html): mixed
{
$pdf = false;
try { try {
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
@ -132,16 +169,9 @@ class Statement
nlog(print_r($e->getMessage(), 1)); nlog(print_r($e->getMessage(), 1));
} }
if ($this->rollback) {
\DB::connection(config('database.default'))->rollBack();
}
$maker = null;
$state = null;
return $pdf; return $pdf;
} }
/** /**
* Setup correct entity instance. * Setup correct entity instance.
* *
@ -253,9 +283,9 @@ class Statement
/** /**
* The collection of invoices for the statement. * The collection of invoices for the statement.
* *
* @return Invoice[]|\Illuminate\Support\LazyCollection * @return Builder
*/ */
public function getInvoices(): \Illuminate\Support\LazyCollection public function getInvoices(): Builder
{ {
return Invoice::withTrashed() return Invoice::withTrashed()
->with('payments.type') ->with('payments.type')
@ -265,8 +295,7 @@ class Statement
->whereIn('status_id', $this->invoiceStatuses()) ->whereIn('status_id', $this->invoiceStatuses())
->whereBetween('date', [Carbon::parse($this->options['start_date']), Carbon::parse($this->options['end_date'])]) ->whereBetween('date', [Carbon::parse($this->options['start_date']), Carbon::parse($this->options['end_date'])])
->orderBy('due_date', 'ASC') ->orderBy('due_date', 'ASC')
->orderBy('date', 'ASC') ->orderBy('date', 'ASC');
->cursor();
} }
private function invoiceStatuses() :array private function invoiceStatuses() :array
@ -287,7 +316,6 @@ class Statement
case 'unpaid': case 'unpaid':
return [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]; return [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL];
default: default:
return [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID]; return [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID];
@ -297,9 +325,9 @@ class Statement
/** /**
* The collection of payments for the statement. * The collection of payments for the statement.
* *
* @return Payment[]|\Illuminate\Support\LazyCollection * @return Builder
*/ */
protected function getPayments(): \Illuminate\Support\LazyCollection protected function getPayments(): Builder
{ {
return Payment::withTrashed() return Payment::withTrashed()
->with('client.country', 'invoices') ->with('client.country', 'invoices')
@ -308,19 +336,18 @@ class Statement
->where('client_id', $this->client->id) ->where('client_id', $this->client->id)
->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED]) ->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
->whereBetween('date', [Carbon::parse($this->options['start_date']), Carbon::parse($this->options['end_date'])]) ->whereBetween('date', [Carbon::parse($this->options['start_date']), Carbon::parse($this->options['end_date'])])
->orderBy('date', 'ASC') ->orderBy('date', 'ASC');
->cursor();
} }
/** /**
* The collection of credits for the statement. * The collection of credits for the statement.
* *
* @return Credit[]|\Illuminate\Support\LazyCollection * @return Builder
*/ */
protected function getCredits(): \Illuminate\Support\LazyCollection protected function getCredits(): Builder
{ {
return Credit::withTrashed() return Credit::withTrashed()
->with('client.country', 'invoices') ->with('client.country','invoice')
->where('is_deleted', false) ->where('is_deleted', false)
->where('company_id', $this->client->company_id) ->where('company_id', $this->client->company_id)
->where('client_id', $this->client->id) ->where('client_id', $this->client->id)
@ -330,8 +357,7 @@ class Statement
$query->whereDate('due_date', '>=', $this->options['end_date']) $query->whereDate('due_date', '>=', $this->options['end_date'])
->orWhereNull('due_date'); ->orWhereNull('due_date');
}) })
->orderBy('date', 'ASC') ->orderBy('date', 'ASC');
->cursor();
} }
/** /**
@ -370,7 +396,7 @@ class Statement
* @param mixed $range * @param mixed $range
* @return string * @return string
*/ */
private function getAgingAmount($range) private function getAgingAmount($range): string
{ {
$ranges = $this->calculateDateRanges($range); $ranges = $this->calculateDateRanges($range);

View File

@ -71,10 +71,13 @@ class TemplateService
$this->document->validateOnParse = true; $this->document->validateOnParse = true;
$loader = new \Twig\Loader\FilesystemLoader(storage_path()); $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(); $string_extension = new \Twig\Extension\StringLoaderExtension();
$this->twig->addExtension($string_extension); $this->twig->addExtension($string_extension);
$this->twig->addExtension(new IntlExtension()); $this->twig->addExtension(new IntlExtension());
$this->twig->addExtension(new \Twig\Extension\DebugExtension());
$function = new \Twig\TwigFunction('img', function ($string, $style = '') { $function = new \Twig\TwigFunction('img', function ($string, $style = '') {
return '<img src="'.$string.'" style="'.$style.'"></img>'; return '<img src="'.$string.'" style="'.$style.'"></img>';
@ -124,6 +127,11 @@ class TemplateService
return $this; return $this;
} }
/**
* Returns the HTML as string
*
* @return string
*/
public function getHtml(): string public function getHtml(): string
{ {
return $this->compiled_html; return $this->compiled_html;
@ -172,6 +180,22 @@ class TemplateService
nlog($e->getMessage()); nlog($e->getMessage());
throw ($e); throw ($e);
} }
catch(\Twig\Error\Error $e) {
nlog("error = " .$e->getMessage());
throw ($e);
}
catch(\Twig\Error\RuntimeError $e) {
nlog("runtime = " .$e->getMessage());
throw ($e);
}
catch(\Twig\Error\LoaderError $e) {
nlog("loader = " . $e->getMessage());
throw ($e);
}
catch(\Twig\Error\SecurityError $e) {
nlog("security = " . $e->getMessage());
throw ($e);
}
$template = $template->render($this->data); $template = $template->render($this->data);
@ -284,10 +308,11 @@ class TemplateService
$processed = []; $processed = [];
if(in_array($key, ['tasks','projects']) || !$value->first() ) if(in_array($key, ['tasks','projects','aging']) || !$value->first() )
return $processed; return $processed;
match ($key) { match ($key) {
'variables' => $processed = $value->first() ?? [],
'invoices' => $processed = (new HtmlEngine($value->first()->invitations()->first()))->generateLabelsAndValues() ?? [], 'invoices' => $processed = (new HtmlEngine($value->first()->invitations()->first()))->generateLabelsAndValues() ?? [],
'quotes' => $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() ?? [], 'credits' => $processed = (new HtmlEngine($value->first()->invitations()->first()))->generateLabelsAndValues() ?? [],
@ -295,6 +320,8 @@ class TemplateService
'tasks' => $processed = [], 'tasks' => $processed = [],
'projects' => $processed = [], 'projects' => $processed = [],
'purchase_orders' => (new VendorHtmlEngine($value->first()->invitations()->first()))->generateLabelsAndValues() ?? [], 'purchase_orders' => (new VendorHtmlEngine($value->first()->invitations()->first()))->generateLabelsAndValues() ?? [],
'aging' => $processed = [],
default => $processed = [],
}; };
return $processed; return $processed;
@ -317,6 +344,8 @@ class TemplateService
'tasks' => $processed = $this->processTasks($value), 'tasks' => $processed = $this->processTasks($value),
'projects' => $processed = $this->processProjects($value), 'projects' => $processed = $this->processProjects($value),
'purchase_orders' => $processed = $this->processPurchaseOrders($value), 'purchase_orders' => $processed = $this->processPurchaseOrders($value),
'aging' => $processed = $value,
default => $processed = [],
}; };
return $processed; return $processed;
@ -333,7 +362,6 @@ class TemplateService
if($invoice->payments ?? false) { if($invoice->payments ?? false) {
$payments = $invoice->payments->map(function ($payment) { $payments = $invoice->payments->map(function ($payment) {
// nlog(microtime(true));
return $this->transformPayment($payment); return $this->transformPayment($payment);
})->toArray(); })->toArray();
} }
@ -428,35 +456,6 @@ class TemplateService
})->toArray(); })->toArray();
} }
public function processInvoicesBak($invoices): array
{
$it = new InvoiceTransformer();
$it->setDefaultIncludes(['client','payments', 'credits']);
$manager = new Manager();
$manager->parseIncludes(['client','payments','payments.type','credits']);
$resource = new \League\Fractal\Resource\Collection($invoices, $it, null);
$invoices = $manager->createData($resource)->toArray();
foreach($invoices['data'] as $key => $invoice)
{
$invoices['data'][$key]['client'] = $invoice['client']['data'] ?? [];
$invoices['data'][$key]['client']['contacts'] = $invoice['client']['data']['contacts']['data'] ?? [];
$invoices['data'][$key]['payments'] = $invoice['payments']['data'] ?? [];
$invoices['data'][$key]['credits'] = $invoice['credits']['data'] ?? [];
if($invoice['payments']['data'] ?? false) {
foreach($invoice['payments']['data'] as $keyx => $payment) {
$invoices['data'][$key]['payments'][$keyx]['paymentables'] = $payment['paymentables']['data'] ?? [];
$invoices['data'][$key]['payments'][$keyx]['type'] = $payment['type']['data'] ?? [];
}
}
}
return $invoices['data'];
}
private function transformPayment(Payment $payment): array private function transformPayment(Payment $payment): array
{ {
@ -530,7 +529,7 @@ class TemplateService
'refund_activity' => $this->getPaymentRefundActivity($payment), 'refund_activity' => $this->getPaymentRefundActivity($payment),
]; ];
nlog($this->getPaymentRefundActivity($payment)); nlog($data);
return $data; return $data;
@ -583,8 +582,6 @@ class TemplateService
} }
public function processQuotes($quotes): array public function processQuotes($quotes): array
{ {
$it = new QuoteTransformer(); $it = new QuoteTransformer();
@ -614,24 +611,75 @@ class TemplateService
*/ */
public function processCredits($credits): array public function processCredits($credits): array
{ {
$it = new CreditTransformer(); $credits = collect($credits)
$it->setDefaultIncludes(['client']); ->map(function ($credit){
$manager = new Manager();
$resource = new \League\Fractal\Resource\Collection($credits, $it, Credit::class);
$resources = $manager->createData($resource)->toArray();
foreach($resources['data'] as $key => $resource) { 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(),
];
$resources['data'][$key]['client'] = $resource['client']['data'] ?? []; });
$resources['data'][$key]['client']['contacts'] = $resource['client']['data']['contacts']['data'] ?? [];
}
return $resources['data'];
return $credits->toArray();
} }
/** /**
* Pushes payments through the appropriate transformer * Pushes payments through the appropriate transformer
* *
@ -641,7 +689,7 @@ class TemplateService
public function processPayments($payments): array public function processPayments($payments): array
{ {
$payments = $payments->map(function ($payment) { $payments = collect($payments)->map(function ($payment) {
return $this->transformPayment($payment); return $this->transformPayment($payment);
})->toArray(); })->toArray();
@ -709,4 +757,12 @@ class TemplateService
{ {
return $this->company; return $this->company;
} }
public function overrideVariables($variables): self
{
$this->variables = $variables;
return $this;
}
} }