Fixes for rounding

This commit is contained in:
David Bomba 2023-11-14 10:57:02 +11:00
parent d7362c6bbe
commit ea3c5236c6
3 changed files with 159 additions and 50 deletions

View File

@ -633,9 +633,7 @@ class PdfBuilder
$element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'product_table-product.tax2-td']]; $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'product_table-product.tax2-td']];
} elseif ($cell == '$product.tax_rate3') { } elseif ($cell == '$product.tax_rate3') {
$element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'product_table-product.tax3-td']]; $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'product_table-product.tax3-td']];
} } elseif ($cell == '$task.discount' && !$this->service->company->enable_product_discount) {
elseif ($cell == '$task.discount' && !$this->service->company->enable_product_discount) {
$element['elements'][] = ['element' => 'td', 'content' => $row['$task.discount'], 'properties' => ['data-ref' => 'task_table-task.discount-td', 'style' => 'display: none;']]; $element['elements'][] = ['element' => 'td', 'content' => $row['$task.discount'], 'properties' => ['data-ref' => 'task_table-task.discount-td', 'style' => 'display: none;']];
} elseif ($cell == '$task.tax_rate1') { } elseif ($cell == '$task.tax_rate1') {
$element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'task_table-task.tax1-td']]; $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'task_table-task.tax1-td']];
@ -643,10 +641,7 @@ class PdfBuilder
$element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'task_table-task.tax2-td']]; $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'task_table-task.tax2-td']];
} elseif ($cell == '$task.tax_rate3') { } elseif ($cell == '$task.tax_rate3') {
$element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'task_table-task.tax3-td']]; $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'task_table-task.tax3-td']];
} } elseif ($cell == '$product.unit_cost' || $cell == '$task.rate') {
elseif ($cell == '$product.unit_cost' || $cell == '$task.rate') {
$element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['style' => 'white-space: nowrap;', 'data-ref' => "{$_type}_table-" . substr($cell, 1) . '-td']]; $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['style' => 'white-space: nowrap;', 'data-ref' => "{$_type}_table-" . substr($cell, 1) . '-td']];
} else { } else {
$element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => "{$_type}_table-" . substr($cell, 1) . '-td']]; $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => "{$_type}_table-" . substr($cell, 1) . '-td']];
@ -712,9 +707,9 @@ class PdfBuilder
$data[$key][$table_type.".{$_table_type}4"] = strlen($item->custom_value4) >= 1 ? $helpers->formatCustomFieldValue($this->service->company->custom_fields, "{$_table_type}4", $item->custom_value4, $this->service->config->currency_entity) : ''; $data[$key][$table_type.".{$_table_type}4"] = strlen($item->custom_value4) >= 1 ? $helpers->formatCustomFieldValue($this->service->company->custom_fields, "{$_table_type}4", $item->custom_value4, $this->service->config->currency_entity) : '';
if ($item->quantity > 0 || $item->cost > 0) { if ($item->quantity > 0 || $item->cost > 0) {
$data[$key][$table_type.'.quantity'] = $item->quantity; $data[$key][$table_type.'.quantity'] = $this->service->config->formatValueNoTrailingZeroes($item->quantity);
$data[$key][$table_type.'.unit_cost'] = $this->service->config->formatMoney($item->cost); $data[$key][$table_type.'.unit_cost'] = $this->service->config->formatMoneyNoRounding($item->cost);
$data[$key][$table_type.'.cost'] = $this->service->config->formatMoney($item->cost); $data[$key][$table_type.'.cost'] = $this->service->config->formatMoney($item->cost);
@ -820,9 +815,7 @@ class PdfBuilder
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax2-th", 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]]; $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax2-th", 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]];
} elseif ($column == '$product.tax_rate3') { } elseif ($column == '$product.tax_rate3') {
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax3-th", 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]]; $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax3-th", 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]];
} } elseif ($column == '$task.discount' && !$this->service->company->enable_product_discount) {
elseif ($column == '$task.discount' && !$this->service->company->enable_product_discount) {
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'style' => 'display: none;']]; $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'style' => 'display: none;']];
} elseif ($column == '$task.tax_rate1') { } elseif ($column == '$task.tax_rate1') {
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-task.tax1-th", 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]]; $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-task.tax1-th", 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]];
@ -830,9 +823,7 @@ class PdfBuilder
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-task.tax2-th", 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]]; $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-task.tax2-th", 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]];
} elseif ($column == '$task.tax_rate3') { } elseif ($column == '$task.tax_rate3') {
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-task.tax3-th", 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]]; $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-task.tax3-th", 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]];
} } else {
else {
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]]; $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]];
} }
} }

View File

@ -330,6 +330,125 @@ class PdfConfiguration
} }
} }
/**
* Formats a given value based on the clients currency.
*
* @param float $value The number to be formatted
*
* @return string The formatted value
*/
public function formatValueNoTrailingZeroes($value) :string
{
$value = floatval($value);
$thousand = $this->currency->thousand_separator;
$decimal = $this->currency->decimal_separator;
/* Country settings override client settings */
if (isset($this->country->thousand_separator) && strlen($this->country->thousand_separator) >= 1) {
$thousand = $this->country->thousand_separator;
}
if (isset($this->country->decimal_separator) && strlen($this->country->decimal_separator) >= 1) {
$decimal = $this->country->decimal_separator;
}
$precision = 10;
return rtrim(rtrim(number_format($value, $precision, $decimal, $thousand), '0'), $decimal);
}
/**
* Formats a given value based on the clients currency AND country.
*
* @param float $value The number to be formatted
* @return string The formatted value
*/
public function formatMoneyNoRounding($value) :string
{
$_value = $value;
$thousand = $this->currency->thousand_separator;
$decimal = $this->currency->decimal_separator;
$precision = $this->currency->precision;
$code = $this->currency->code;
$swapSymbol = $this->currency->swap_currency_symbol;
/* Country settings override client settings */
if (isset($this->country->thousand_separator) && strlen($this->country->thousand_separator) >= 1) {
$thousand = $this->country->thousand_separator;
}
if (isset($this->country->decimal_separator) && strlen($this->country->decimal_separator) >= 1) {
$decimal = $this->country->decimal_separator;
}
if (isset($this->country->swap_currency_symbol) && strlen($this->country->swap_currency_symbol) >= 1) {
$swapSymbol = $this->country->swap_currency_symbol;
}
/* 08-01-2022 allow increased precision for unit price*/
$v = rtrim(sprintf('%f', $value), '0');
$parts = explode('.', $v);
/* 08-02-2023 special if block to render $0.5 to $0.50*/
if ($v < 1 && strlen($v) == 3) {
$precision = 2;
} elseif ($v < 1) {
$precision = strlen($v) - strrpos($v, '.') - 1;
}
if (is_array($parts) && $parts[0] != 0) {
$precision = 2;
}
//04-04-2023 if currency = JPY override precision to 0
if($this->currency->code == 'JPY') {
$precision = 0;
}
$value = number_format($v, $precision, $decimal, $thousand);
$symbol = $this->currency->symbol;
if ($this->settings->show_currency_code === true && $this->currency->code == 'CHF') {
return "{$code} {$value}";
} elseif ($this->settings->show_currency_code === true) {
return "{$value} {$code}";
} elseif ($swapSymbol) {
return "{$value} ".trim($symbol);
} elseif ($this->settings->show_currency_code === false) {
if ($_value < 0) {
$value = substr($value, 1);
$symbol = "-{$symbol}";
}
return "{$symbol}{$value}";
} else {
return $this->formatValue($value);
}
}
/**
* Formats a given value based on the clients currency.
*
* @param float $value The number to be formatted
*
* @return string The formatted value
*/
public function formatValue($value) :string
{
$value = floatval($value);
$thousand = $this->currency->thousand_separator;
$decimal = $this->currency->decimal_separator;
$precision = $this->currency->precision;
return number_format($value, $precision, $decimal, $thousand);
}
/** /**
* date_format * date_format
* *

View File

@ -11,35 +11,31 @@
namespace App\Services\Template; namespace App\Services\Template;
use Twig\TwigFilter;
use App\Models\Quote;
use App\Utils\Number;
use Twig\Environment;
use Twig\Error\Error;
use App\Models\Client; use App\Models\Client;
use App\Models\Company;
use App\Models\Credit; use App\Models\Credit;
use App\Models\Design; use App\Models\Design;
use App\Models\Vendor;
use Twig\TwigFunction;
use App\Models\Company;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Payment; use App\Models\Payment;
use App\Models\Project; use App\Models\Project;
use App\Utils\HtmlEngine;
use Twig\Error\LoaderError;
use Twig\Error\SyntaxError;
use Twig\Error\RuntimeError;
use App\Models\PurchaseOrder; use App\Models\PurchaseOrder;
use App\Utils\VendorHtmlEngine; use App\Models\Quote;
use Twig\Sandbox\SecurityError;
use App\Models\RecurringInvoice; use App\Models\RecurringInvoice;
use App\Models\Vendor;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\HtmlEngine;
use App\Utils\Number;
use App\Utils\PaymentHtmlEngine; use App\Utils\PaymentHtmlEngine;
use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesDates;
use App\Utils\HostedPDF\NinjaPdf;
use Twig\Loader\FilesystemLoader;
use App\Utils\Traits\Pdf\PdfMaker; use App\Utils\Traits\Pdf\PdfMaker;
use Twig\Extra\Intl\IntlExtension; use App\Utils\VendorHtmlEngine;
use League\CommonMark\CommonMarkConverter; use League\CommonMark\CommonMarkConverter;
use Twig\Error\Error;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
use Twig\Extra\Intl\IntlExtension;
use Twig\Sandbox\SecurityError;
class TemplateService class TemplateService
{ {
@ -88,8 +84,9 @@ class TemplateService
$this->document = new \DOMDocument(); $this->document = new \DOMDocument();
$this->document->validateOnParse = true; $this->document->validateOnParse = true;
$loader = new FilesystemLoader(storage_path());
$this->twig = new Environment($loader, [ $loader = new \Twig\Loader\FilesystemLoader(storage_path());
$this->twig = new \Twig\Environment($loader, [
'debug' => true, 'debug' => true,
]); ]);
$string_extension = new \Twig\Extension\StringLoaderExtension(); $string_extension = new \Twig\Extension\StringLoaderExtension();
@ -97,7 +94,7 @@ class TemplateService
$this->twig->addExtension(new IntlExtension()); $this->twig->addExtension(new IntlExtension());
$this->twig->addExtension(new \Twig\Extension\DebugExtension()); $this->twig->addExtension(new \Twig\Extension\DebugExtension());
$function = new 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>';
}); });
$this->twig->addFunction($function); $this->twig->addFunction($function);
@ -236,16 +233,16 @@ class TemplateService
$template = $this->twig->createTemplate(html_entity_decode($template)); $template = $this->twig->createTemplate(html_entity_decode($template));
} catch(SyntaxError $e) { } catch(SyntaxError $e) {
nlog($e->getMessage()); nlog($e->getMessage());
continue; throw ($e);
} catch(Error $e) { } catch(Error $e) {
nlog("error = " . $e->getMessage()); nlog("error = " . $e->getMessage());
continue; throw ($e);
} catch(RuntimeError $e) { } catch(RuntimeError $e) {
nlog("runtime = " . $e->getMessage()); nlog("runtime = " . $e->getMessage());
continue; throw ($e);
} catch(LoaderError $e) { } catch(LoaderError $e) {
nlog("loader = " . $e->getMessage()); nlog("loader = " . $e->getMessage());
continue; throw ($e);
} catch(SecurityError $e) { } catch(SecurityError $e) {
nlog("security = " . $e->getMessage()); nlog("security = " . $e->getMessage());
throw ($e); throw ($e);
@ -281,7 +278,6 @@ class TemplateService
$html = $this->getHtml(); $html = $this->getHtml();
foreach($this->variables as $key => $variable) { 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['labels']);
$html = strtr($html, $variable['values']); $html = strtr($html, $variable['values']);
@ -428,6 +424,7 @@ class TemplateService
->map(function ($invoice) { ->map(function ($invoice) {
$payments = []; $payments = [];
$this->entity = $invoice;
if($invoice->payments ?? false) { if($invoice->payments ?? false) {
$payments = $invoice->payments->map(function ($payment) { $payments = $invoice->payments->map(function ($payment) {
@ -438,6 +435,8 @@ class TemplateService
return [ return [
'amount' => Number::formatMoney($invoice->amount, $invoice->client), 'amount' => Number::formatMoney($invoice->amount, $invoice->client),
'balance' => Number::formatMoney($invoice->balance, $invoice->client), 'balance' => Number::formatMoney($invoice->balance, $invoice->client),
'status_id' => $invoice->status_id,
'status' => Invoice::stringStatus($invoice->status_id),
'balance_raw' => $invoice->balance, 'balance_raw' => $invoice->balance,
'number' => $invoice->number ?: '', 'number' => $invoice->number ?: '',
'discount' => $invoice->discount, 'discount' => $invoice->discount,