Merge pull request #10527 from turbo124/v5-develop

v5.11.24
This commit is contained in:
David Bomba 2025-01-14 18:36:31 +11:00 committed by GitHub
commit 4ff745de82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 488 additions and 473 deletions

View File

@ -1 +1 @@
5.11.23
5.11.24

View File

@ -172,6 +172,7 @@ class SwissQrGenerator
->setPrintable(false)
->getPaymentPart();
// return $html;
return htmlspecialchars($html);
} catch (\Exception $e) {

View File

@ -106,14 +106,14 @@ class InvoiceController extends Controller
break;
}
usleep(200000);
usleep(300000);
}
$invitation = false;
match($data['entity_type'] ?? 'invoice') {
'invoice' => $invitation = InvoiceInvitation::withTrashed()->find($data['invitation_id']),
'invoice' => $invitation = InvoiceInvitation::withTrashed()->find($data['invitation_id']), //@todo - sometimes this is false!!
'quote' => $invitation = QuoteInvitation::withTrashed()->find($data['invitation_id']),
'credit' => $invitation = CreditInvitation::withTrashed()->find($data['invitation_id']),
'recurring_invoice' => $invitation = RecurringInvoiceInvitation::withTrashed()->find($data['invitation_id']),

View File

@ -195,7 +195,10 @@ class UpdateCompanyRequest extends Request
if (Ninja::isHosted()) {
foreach ($this->protected_input as $protected_var) {
$settings[$protected_var] = str_replace("script", "", $settings[$protected_var]);
if(isset($settings[$protected_var])) {
$settings[$protected_var] = str_replace("script", "", $settings[$protected_var]);
}
}
}

View File

@ -13,7 +13,6 @@ namespace App\Http\Requests\RecurringInvoice;
use App\Http\Requests\Request;
use App\Http\ValidationRules\Project\ValidProjectForClient;
use App\Http\ValidationRules\Recurring\UniqueRecurringInvoiceNumberRule;
use App\Models\Client;
use App\Models\RecurringInvoice;
use App\Utils\Traits\CleanLineItems;
@ -68,7 +67,7 @@ class StoreRecurringInvoiceRequest extends Request
$rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())];
$rules['number'] = new UniqueRecurringInvoiceNumberRule($this->all());
$rules['number'] = ['bail', 'nullable', \Illuminate\Validation\Rule::unique('recurring_invoices')->where('company_id', $user->company()->id)];
$rules['tax_rate1'] = 'bail|sometimes|numeric';
$rules['tax_rate2'] = 'bail|sometimes|numeric';

View File

@ -12,7 +12,6 @@
namespace App\Http\Requests\RecurringQuote;
use App\Http\Requests\Request;
use App\Http\ValidationRules\Recurring\UniqueRecurringQuoteNumberRule;
use App\Models\Client;
use App\Models\RecurringQuote;
use App\Utils\Traits\CleanLineItems;
@ -63,7 +62,7 @@ class StoreRecurringQuoteRequest extends Request
$rules['frequency_id'] = 'required|integer|digits_between:1,12';
$rules['number'] = new UniqueRecurringQuoteNumberRule($this->all());
$rules['number'] = ['bail', 'nullable', \Illuminate\Validation\Rule::unique('recurring_quotes')->where('company_id', $user->company()->id)];
return $rules;
}

View File

@ -1,67 +0,0 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\ValidationRules\Recurring;
use App\Models\RecurringInvoice;
use Illuminate\Contracts\Validation\Rule;
/**
* Class UniqueRecurringInvoiceNumberRule.
*/
class UniqueRecurringInvoiceNumberRule implements Rule
{
public $input;
public function __construct($input)
{
$this->input = $input;
}
/**
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
return $this->checkIfInvoiceNumberUnique(); //if it exists, return false!
}
/**
* @return string
*/
public function message()
{
return ctrans('texts.recurring_invoice_number_taken', ['number' => $this->input['number']]);
}
/**
* @return bool
*/
private function checkIfInvoiceNumberUnique(): bool
{
if (empty($this->input['number'])) {
return true;
}
$invoice = RecurringInvoice::query()->where('client_id', $this->input['client_id'])
->where('number', $this->input['number'])
->withTrashed()
->exists();
if ($invoice) {
return false;
}
return true;
}
}

View File

@ -1,67 +0,0 @@
<?php
/**
* Quote Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Quote Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\ValidationRules\Recurring;
use App\Models\RecurringQuote;
use Illuminate\Contracts\Validation\Rule;
/**
* Class UniqueRecurringQuoteNumberRule.
*/
class UniqueRecurringQuoteNumberRule implements Rule
{
public $input;
public function __construct($input)
{
$this->input = $input;
}
/**
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
return $this->checkIfQuoteNumberUnique(); //if it exists, return false!
}
/**
* @return string
*/
public function message()
{
return ctrans('texts.recurring_quote_number_taken', ['number' => $this->input['number']]);
}
/**
* @return bool
*/
private function checkIfQuoteNumberUnique(): bool
{
if (empty($this->input['number'])) {
return true;
}
$invoice = RecurringQuote::query()->where('client_id', $this->input['client_id'])
->where('number', $this->input['number'])
->withTrashed()
->exists();
if ($invoice) {
return false;
}
return true;
}
}

View File

@ -515,7 +515,7 @@ class NinjaMailerJob implements ShouldQueue
private function checkValidSendingUser($user)
{
/* Always ensure the user is set on the correct account */
if ($user->account_id != $this->company->account_id) {
if (!$user ||($user->account_id != $this->company->account_id)) {
$this->nmo->settings->email_sending_method = 'default';
return $this->setMailDriver();
}

View File

@ -141,6 +141,9 @@ class InvoicePay extends Component
$company_gateway = CompanyGateway::query()->find($company_gateway_id);
if(!$company_gateway)
return $this->required_fields = false;
$this->checkRequiredFields($company_gateway);
}

View File

@ -119,6 +119,10 @@ class AuthorizeTransaction
$transactionRequestType->setOrder($order);
$transactionRequestType->addToTransactionSettings($duplicateWindowSetting);
$solution = new \net\authorize\api\contract\v1\SolutionType();
$solution->setId($this->authorize->company_gateway->getConfigField('testMode') ? 'AAA100303' : 'AAA172036');
$transactionRequestType->setSolution($solution);
$transactionRequestType->setPayment($paymentOne);
$transactionRequestType->setCurrencyCode($this->authorize->client->currency()->code);

View File

@ -92,6 +92,10 @@ class ChargePaymentProfile
$transactionRequestType->setProfile($profileToCharge);
$transactionRequestType->setCurrencyCode($this->authorize->client->currency()->code);
$solution = new \net\authorize\api\contract\v1\SolutionType();
$solution->setId($this->authorize->company_gateway->getConfigField('testMode') ? 'AAA100303' : 'AAA172036');
$transactionRequestType->setSolution($solution);
$request = new CreateTransactionRequest();
$request->setMerchantAuthentication($this->authorize->merchant_authentication);
$request->setRefId($refId);

View File

@ -83,6 +83,10 @@ class RefundTransaction
$transactionRequest->setPayment($paymentOne);
$transactionRequest->setRefTransId($payment->transaction_reference);
$solution = new \net\authorize\api\contract\v1\SolutionType();
$solution->setId($this->authorize->company_gateway->getConfigField('testMode') ? 'AAA100303' : 'AAA172036');
$transactionRequest->setSolution($solution);
$request = new CreateTransactionRequest();
$request->setMerchantAuthentication($this->authorize->merchant_authentication);
$request->setRefId($refId);

View File

@ -691,7 +691,7 @@ class Email implements ShouldQueue
private function checkValidSendingUser($user)
{
/* Always ensure the user is set on the correct account */
if ($user->account_id != $this->company->account_id) {
if (!$user || ($user->account_id != $this->company->account_id)) {
$this->email_object->settings->email_sending_method = 'default';
return $this->setMailDriver();

View File

@ -352,7 +352,7 @@ class PdfBuilder
$outstanding = $this->service->options['invoices']->sum('balance');
return [
['element' => 'p', 'content' => '$outstanding_label: ' . $this->service->config->formatMoney($outstanding)],
['element' => 'div', 'content' => '$outstanding_label: ' . $this->service->config->formatMoney($outstanding)],
];
}
@ -402,7 +402,7 @@ class PdfBuilder
$outstanding = $this->service->options['credits']->sum('balance');
return [
['element' => 'p', 'content' => '$credit.balance_label: ' . $this->service->config->formatMoney($outstanding)],
['element' => 'div', 'content' => '$credit.balance_label: ' . $this->service->config->formatMoney($outstanding)],
];
}
@ -474,7 +474,7 @@ class PdfBuilder
}
}
}
return [
['element' => 'thead', 'elements' => $this->buildTableHeader('statement_payment')],
['element' => 'tbody', 'elements' => $tbody],
@ -500,9 +500,9 @@ class PdfBuilder
$payment = $this->service->options['payments']->first();
return [
['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), $this->service->config->formatMoney($this->payment_amount_total))],
['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.payment_method'), $payment->translatedType())],
['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.payment_date'), $this->translateDate($payment->date, $this->service->config->date_format, $this->service->config->locale) ?: '&nbsp;')],
['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), $this->service->config->formatMoney($this->payment_amount_total))],
['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.payment_method'), $payment->translatedType())],
['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.payment_date'), $this->translateDate($payment->date, $this->service->config->date_format, $this->service->config->locale) ?: '&nbsp;')],
];
}
@ -520,9 +520,9 @@ class PdfBuilder
$payment = $this->service->options['unapplied']->first();
return [
['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.payment_balance'), $this->service->config->formatMoney($this->unapplied_total))],
['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.payment_method'), $payment->translatedType())],
['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.payment_date'), $this->translateDate($payment->date, $this->service->config->date_format, $this->service->config->locale) ?: '&nbsp;')],
['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.payment_balance'), $this->service->config->formatMoney($this->unapplied_total))],
['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.payment_method'), $payment->translatedType())],
['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.payment_date'), $this->translateDate($payment->date, $this->service->config->date_format, $this->service->config->locale) ?: '&nbsp;')],
];
}
@ -866,7 +866,6 @@ class PdfBuilder
$element['elements'][$last_visible]['properties']['class'] .= ' right-radius';
}
}
// Then, filter the elements array
$element['elements'] = array_map(function ($el) {
@ -1405,7 +1404,7 @@ class PdfBuilder
['element' => 'span', 'content' => strtr(str_replace(["labels", "values"], ["",""], $_variables['values']['$entity.public_notes']), $_variables)]
]],
['element' => 'div', 'content' => '', 'properties' => ['style' => 'text-align: left; display: flex; flex-direction: column; page-break-inside: auto;'], 'elements' => [
['element' => 'span', 'content' => '$entity.terms_label: ', 'properties' => ['data-ref' => 'total_table-terms-label', 'style' => "text-align: left; margin-top: 1rem; {$show_terms_label}"]],
['element' => 'span', 'content' => '$entity.terms_label: ', 'properties' => ['data-ref' => 'total_table-terms-label', 'style' => "font-weight:bold; text-align: left; margin-top: 1rem; {$show_terms_label}"]],
['element' => 'span', 'content' => strtr(str_replace("labels", "", $_variables['values']['$entity.terms']), $_variables['labels']), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']],
]],
['element' => 'img', 'properties' => ['style' => 'max-width: 50%; height: auto;', 'src' => '$contact.signature', 'id' => 'contact-signature']],
@ -1761,20 +1760,20 @@ class PdfBuilder
}
$elements = [
['element' => 'p', 'content' => ctrans('texts.delivery_note'), 'properties' => ['data-ref' => 'delivery_note-label', 'style' => 'font-weight: bold; text-transform: uppercase']],
['element' => 'p', 'content' => $this->service->config->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.name']],
['element' => 'p', 'content' => $this->service->config->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address1']],
['element' => 'p', 'content' => $this->service->config->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address2']],
['element' => 'p', 'show_empty' => false, 'elements' => [
['element' => 'div', 'content' => ctrans('texts.delivery_note'), 'properties' => ['data-ref' => 'delivery_note-label', 'style' => 'font-weight: bold; text-transform: uppercase']],
['element' => 'div', 'content' => $this->service->config->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.name']],
['element' => 'div', 'content' => $this->service->config->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address1']],
['element' => 'div', 'content' => $this->service->config->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address2']],
['element' => 'div', 'show_empty' => false, 'elements' => [
['element' => 'span', 'content' => "{$this->service->config->client->shipping_city} ", 'properties' => ['ref' => 'delivery_note-client.shipping_city']],
['element' => 'span', 'content' => "{$this->service->config->client->shipping_state} ", 'properties' => ['ref' => 'delivery_note-client.shipping_state']],
['element' => 'span', 'content' => "{$this->service->config->client->shipping_postal_code} ", 'properties' => ['ref' => 'delivery_note-client.shipping_postal_code']],
]],
['element' => 'p', 'content' => optional($this->service->config->client->shipping_country)->name, 'show_empty' => false],
['element' => 'div', 'content' => optional($this->service->config->client->shipping_country)->name, 'show_empty' => false],
];
if (!is_null($this->service->config->contact)) {
$elements[] = ['element' => 'p', 'content' => $this->service->config->contact->email, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-contact.email']];
$elements[] = ['element' => 'div', 'content' => $this->service->config->contact->email, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-contact.email']];
}
return $elements;
@ -1796,7 +1795,7 @@ class PdfBuilder
$variables = $this->service->config->pdf_variables['client_details'];
foreach ($variables as $variable) {
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'client_details-' . substr($variable, 1)]];
$elements[] = ['element' => 'div', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'client_details-' . substr($variable, 1)]];
}
return $elements;
@ -1811,16 +1810,16 @@ class PdfBuilder
}
$elements = [
['element' => 'p', 'content' => ctrans('texts.shipping_address'), 'properties' => ['data-ref' => 'shipping_address-label', 'style' => 'font-weight: bold; text-transform: uppercase']],
// ['element' => 'p', 'content' => $this->service->config->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.name']],
['element' => 'p', 'content' => $this->service->config->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address1']],
['element' => 'p', 'content' => $this->service->config->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address2']],
['element' => 'p', 'show_empty' => false, 'elements' => [
['element' => 'div', 'content' => ctrans('texts.shipping_address'), 'properties' => ['data-ref' => 'shipping_address-label', 'style' => 'font-weight: bold; text-transform: uppercase']],
// ['element' => 'div', 'content' => $this->service->config->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.name']],
['element' => 'div', 'content' => $this->service->config->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address1']],
['element' => 'div', 'content' => $this->service->config->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address2']],
['element' => 'div', 'show_empty' => false, 'elements' => [
['element' => 'span', 'content' => "{$this->service->config->client->shipping_city} ", 'properties' => ['ref' => 'shipping_address-client.shipping_city']],
['element' => 'span', 'content' => "{$this->service->config->client->shipping_state} ", 'properties' => ['ref' => 'shipping_address-client.shipping_state']],
['element' => 'span', 'content' => "{$this->service->config->client->shipping_postal_code} ", 'properties' => ['ref' => 'shipping_address-client.shipping_postal_code']],
]],
['element' => 'p', 'content' => optional($this->service->config->client->shipping_country)->name, 'show_empty' => false],
['element' => 'div', 'content' => optional($this->service->config->client->shipping_country)->name, 'show_empty' => false],
];
return $elements;
@ -1899,7 +1898,7 @@ class PdfBuilder
$elements = [];
foreach ($variables as $variable) {
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]];
$elements[] = ['element' => 'div', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]];
}
return $elements;
@ -1919,7 +1918,7 @@ class PdfBuilder
$elements = [];
foreach ($variables as $variable) {
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_address-' . substr($variable, 1)]];
$elements[] = ['element' => 'div', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_address-' . substr($variable, 1)]];
}
return $elements;
@ -1939,7 +1938,7 @@ class PdfBuilder
$variables = $this->service->config->pdf_variables['vendor_details'];
foreach ($variables as $variable) {
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'vendor_details-' . substr($variable, 1)]];
$elements[] = ['element' => 'div', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'vendor_details-' . substr($variable, 1)]];
}
return $elements;
@ -1997,23 +1996,49 @@ class PdfBuilder
public function createElementContent($element, $children): self
{
foreach ($children as $child) {
$contains_html = false;
$contains_markdown = false;
$child['content'] = $child['content'] ?? '';
if ($child['element'] !== 'script') {
if ($this->service->company->markdown_enabled && array_key_exists('content', $child)) {
$child['content'] = str_replace('<br>', "\r", ($child['content'] ?? ''));
$child['content'] = $this->commonmark->convert($child['content'] ?? ''); //@phpstan-ignore-line
}
}
$lines = explode("\n", $child['content']);
$contains_markdown = false;
if (isset($child['content'])) {
if (isset($child['is_empty']) && $child['is_empty'] === true) {
foreach ($lines as $line) {
$trimmed = ltrim($line);
if (empty($trimmed)) {
continue;
}
$contains_html = preg_match('#(?<=<)\w+(?=[^<]*?>)#', $child['content'], $m) != 0;
$first_char = substr($trimmed, 0, 1);
if (
$first_char === '#' || // Headers
$first_char === '>' || // Blockquotes
$first_char === '-' || // Lists
$first_char === '*' || // Lists/Bold
$first_char === '_' || // Italic
$first_char === '`' || // Code
$first_char === '[' || // Links
str_contains($trimmed, '**') // Bold (special case)
) {
$contains_markdown = true;
break;
}
}
if ($this->service->company->markdown_enabled && $contains_markdown && $child['element'] !== 'script') {
$child['content'] = str_ireplace('<br>', "\r", $child['content']);
$child['content'] = $this->commonmark->convert($child['content']); //@phpstan-ignore-line
}
if (isset($child['is_empty']) && $child['is_empty'] === true) {
continue;
}
$contains_html = str_contains($child['content'], '<') && str_contains($child['content'], '>');
if ($contains_html) {
// If the element contains the HTML, we gonna display it as is. Backend is going to
@ -2030,7 +2055,7 @@ class PdfBuilder
// .. in case string doesn't contain any HTML, we'll just return
// raw $content
$_child = $this->document->createElement($child['element'], isset($child['content']) ? htmlspecialchars($child['content']) : '');
$_child = $this->document->createElement($child['element'], htmlspecialchars($child['content']));
}
$element->appendChild($_child);

View File

@ -241,7 +241,7 @@ class Design extends BaseDesign
$elements = [];
foreach ($variables as $variable) {
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]];
$elements[] = ['element' => 'div', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]];
}
return $elements;
@ -254,7 +254,7 @@ class Design extends BaseDesign
$elements = [];
foreach ($variables as $variable) {
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_address-' . substr($variable, 1)]];
$elements[] = ['element' => 'div', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_address-' . substr($variable, 1)]];
}
return $elements;
@ -271,7 +271,7 @@ class Design extends BaseDesign
$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)]];
$elements[] = ['element' => 'div', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'vendor_details-' . substr($variable, 1)]];
}
return $elements;
@ -301,12 +301,12 @@ class Design extends BaseDesign
})->map(function ($variable) {
$variable = str_replace('$client.', '$client.shipping_', $variable);
return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => "client_details-shipping-" . substr($variable, 1)]];
return ['element' => 'div', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => "client_details-shipping-" . substr($variable, 1)]];
})->toArray();
$header = [];
$header[] = ['element' => 'p', 'content' => ctrans('texts.shipping_address'), 'properties' => ['data-ref' => 'shipping_address-label', 'style' => 'font-weight: bold; text-transform: uppercase']];
$header[] = ['element' => 'div', 'content' => ctrans('texts.shipping_address'), 'properties' => ['data-ref' => 'shipping_address-label', 'style' => 'font-weight: bold; text-transform: uppercase']];
return array_merge($header, $elements);
@ -322,20 +322,20 @@ class Design extends BaseDesign
if ($this->type == self::DELIVERY_NOTE) {
$elements = [
['element' => 'p', 'content' => ctrans('texts.delivery_note'), 'properties' => ['data-ref' => 'delivery_note-label', 'style' => 'font-weight: bold; text-transform: uppercase']],
['element' => 'p', 'content' => $this->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.name']],
['element' => 'p', 'content' => $this->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address1']],
['element' => 'p', 'content' => $this->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address2']],
['element' => 'p', 'show_empty' => false, 'elements' => [
['element' => 'div', 'content' => ctrans('texts.delivery_note'), 'properties' => ['data-ref' => 'delivery_note-label', 'style' => 'font-weight: bold; text-transform: uppercase']],
['element' => 'div', 'content' => $this->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.name']],
['element' => 'div', 'content' => $this->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address1']],
['element' => 'div', 'content' => $this->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address2']],
['element' => 'div', 'show_empty' => false, 'elements' => [
['element' => 'span', 'content' => "{$this->client->shipping_city} ", 'properties' => ['ref' => 'delivery_note-client.shipping_city']],
['element' => 'span', 'content' => "{$this->client->shipping_state} ", 'properties' => ['ref' => 'delivery_note-client.shipping_state']],
['element' => 'span', 'content' => "{$this->client->shipping_postal_code} ", 'properties' => ['ref' => 'delivery_note-client.shipping_postal_code']],
]],
['element' => 'p', 'content' => optional($this->client->shipping_country)->name, 'show_empty' => false],
['element' => 'div', 'content' => optional($this->client->shipping_country)->name, 'show_empty' => false],
];
if (!is_null($this->context['contact'])) {
$elements[] = ['element' => 'p', 'content' => $this->context['contact']->email, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-contact.email']];
$elements[] = ['element' => 'div', 'content' => $this->context['contact']->email, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-contact.email']];
}
return $elements;
@ -344,7 +344,7 @@ class Design extends BaseDesign
$variables = $this->context['pdf_variables']['client_details'];
foreach ($variables as $variable) {
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'client_details-' . substr($variable, 1)]];
$elements[] = ['element' => 'div', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'client_details-' . substr($variable, 1)]];
}
return $elements;
@ -553,7 +553,7 @@ class Design extends BaseDesign
$outstanding = $this->invoices->sum('balance');
return [
['element' => 'p', 'content' => '$outstanding_label: ' . Number::formatMoney($outstanding, $this->client)],
['element' => 'div', 'content' => '$outstanding_label: ' . Number::formatMoney($outstanding, $this->client)],
];
}
@ -682,7 +682,7 @@ class Design extends BaseDesign
$outstanding = $this->credits->sum('balance');
return [
['element' => 'p', 'content' => '$credit.balance_label: ' . Number::formatMoney($outstanding, $this->client)],
['element' => 'div', 'content' => '$credit.balance_label: ' . Number::formatMoney($outstanding, $this->client)],
];
}
@ -699,8 +699,8 @@ class Design extends BaseDesign
$payment = $this->payments->first();
return [
// ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), Number::formatMoney($this->payments->sum('amount'), $this->client))],
['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), Number::formatMoney($this->payment_amount_total, $this->client))],
// ['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), Number::formatMoney($this->payments->sum('amount'), $this->client))],
['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), Number::formatMoney($this->payment_amount_total, $this->client))],
];
}
@ -716,7 +716,7 @@ class Design extends BaseDesign
}
return [
['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.payment_balance_on_file'), Number::formatMoney($this->unapplied_total, $this->client))],
['element' => 'div', 'content' => \sprintf('%s: %s', ctrans('texts.payment_balance_on_file'), Number::formatMoney($this->unapplied_total, $this->client))],
];
}
@ -1127,10 +1127,10 @@ class Design extends BaseDesign
$elements = [
['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [
['element' => 'p', 'properties' => ['data-ref' => 'total_table-public_notes', 'style' => 'text-align: left;'], 'elements' => [
['element' => 'div', 'properties' => ['data-ref' => 'total_table-public_notes', 'style' => 'text-align: left;'], 'elements' => [
['element' => 'span', 'content' => strtr(str_replace(["labels", "values"], ["",""], $_variables['values']['$entity.public_notes']), $_variables)]
]],
['element' => 'p', 'content' => '', 'properties' => ['style' => 'text-align: left; display: flex; flex-direction: column; page-break-inside: auto;'], 'elements' => [
['element' => 'div', 'content' => '', 'properties' => ['style' => 'text-align: left; display: flex; flex-direction: column; page-break-inside: auto;'], 'elements' => [
['element' => 'span', 'content' => '$entity.terms_label: ', 'properties' => ['data-ref' => 'total_table-terms-label', 'style' => "font-weight: bold; text-align: left; margin-top: 1rem; {$show_terms_label}"]],
['element' => 'span', 'content' => strtr(str_replace("labels", "", $_variables['values']['$entity.terms']), $_variables['labels']), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']],
]],

View File

@ -91,22 +91,46 @@ trait PdfMakerUtilities
{
foreach ($children as $child) {
$contains_html = false;
$contains_markdown = false;
$child['content'] = $child['content'] ?? '';
if ($child['element'] !== 'script') {
if (array_key_exists('process_markdown', $this->data) && array_key_exists('content', $child) && $this->data['process_markdown']) {
$child['content'] = str_replace('<br>', "\r", ($child['content'] ?? ''));
$child['content'] = $this->commonmark->convert($child['content'] ?? ''); //@phpstan-ignore-line
}
}
$lines = explode("\n", $child['content']);
$contains_markdown = false;
if (isset($child['content'])) {
if (isset($child['is_empty']) && $child['is_empty'] === true) {
foreach ($lines as $line) {
$trimmed = ltrim($line);
if (empty($trimmed)) {
continue;
}
$contains_html = preg_match('#(?<=<)\w+(?=[^<]*?>)#', $child['content'], $m) != 0;
$first_char = substr($trimmed, 0, 1);
if (
$first_char === '#' || // Headers
$first_char === '>' || // Blockquotes
$first_char === '-' || // Lists
$first_char === '*' || // Lists/Bold
$first_char === '_' || // Italic
$first_char === '`' || // Code
$first_char === '[' || // Links
str_contains($trimmed, '**') // Bold (special case)
) {
$contains_markdown = true;
break;
}
}
if (isset($this->data['process_markdown']) && $this->data['process_markdown'] && $contains_markdown &&$child['element'] !== 'script') {
$child['content'] = str_replace('<br>', "\r", $child['content']);
$child['content'] = $this->commonmark->convert($child['content']); //@phpstan-ignore-line
}
if (isset($child['is_empty']) && $child['is_empty'] === true) {
continue;
}
$contains_html = str_contains($child['content'], '<') && str_contains($child['content'], '>');
if ($contains_html) {
// If the element contains the HTML, we gonna display it as is. Backend is going to
// encode it for us, preventing any errors on the processing stage.
@ -118,9 +142,7 @@ trait PdfMakerUtilities
$_child->nodeValue = htmlspecialchars($child['content']);
} else {
// .. in case string doesn't contain any HTML, we'll just return
// raw $content.
$_child = $this->document->createElement($child['element'], isset($child['content']) ? htmlspecialchars($child['content']) : '');
$_child = $this->document->createElement($child['element'], htmlspecialchars($child['content']));
}
$element->appendChild($_child);

View File

@ -68,7 +68,7 @@ class TemplateService
private ?Vendor $vendor = null;
private Invoice | Quote | Credit | PurchaseOrder | RecurringInvoice | Task | Project $entity;
private Invoice | Quote | Credit | PurchaseOrder | RecurringInvoice | Task | Project | Payment $entity;
private Payment $payment;
@ -1575,23 +1575,14 @@ class TemplateService
foreach ($children as $child) {
$contains_html = false;
$child['content'] = $child['content'] ?? '';
//06-11-2023 for some reason this parses content as HTML
// if ($child['element'] !== 'script') {
// if ($this->company->markdown_enabled && array_key_exists('content', $child)) {
// $child['content'] = str_replace('<br>', "\r", $child['content']);
// $child['content'] = $this->commonmark->convert($child['content'] ?? '');
// }
// }
if (isset($child['content'])) {
if (isset($child['is_empty']) && $child['is_empty'] === true) {
continue;
}
$contains_html = preg_match('#(?<=<)\w+(?=[^<]*?>)#', $child['content'], $m) != 0;
if (isset($child['is_empty']) && $child['is_empty'] === true) {
continue;
}
$contains_html = str_contains($child['content'], '<') && str_contains($child['content'], '>');
if ($contains_html) {
// If the element contains the HTML, we gonna display it as is. Backend is going to
// encode it for us, preventing any errors on the processing stage.
@ -1605,7 +1596,7 @@ class TemplateService
} else {
// .. in case string doesn't contain any HTML, we'll just return
// raw $content.
$_child = $this->document->createElement($child['element'], isset($child['content']) ? $child['content'] : '');
$_child = $this->document->createElement($child['element'], $child['content']);
}
$element->appendChild($_child);

View File

@ -46,12 +46,14 @@ trait ClientGroupSettingsSaver
unset($settings[$field]);
}
$company_settings_stub = new CompanySettings();
/*
* for clients and group settings, if a field is not set or is set to a blank value,
* we unset it from the settings object
*/
foreach ($settings as $key => $value) {
if (! isset($settings->{$key}) || empty($settings->{$key}) || (! is_object($settings->{$key}) && strlen($settings->{$key}) == 0)) {
if (! isset($settings->{$key}) || empty($settings->{$key}) || !property_exists($company_settings_stub, $key) || (! is_object($settings->{$key}) && strlen($settings->{$key}) == 0)) {
unset($settings->{$key});
}
}

View File

@ -94,7 +94,8 @@ trait PdfMaker
}
$html = str_ireplace(['file:/', 'iframe', '<embed', '&lt;embed', '&lt;object', '<object', '127.0.0.1', 'localhost', '<?xml encoding="UTF-8">'], '', $html);
// nlog($html);
$generated = $pdf
->setHtml($html)
->generate();

View File

@ -17,8 +17,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => env('APP_VERSION', '5.11.23'),
'app_tag' => env('APP_TAG', '5.11.23'),
'app_version' => env('APP_VERSION', '5.11.24'),
'app_tag' => env('APP_TAG', '5.11.24'),
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', false),

View File

@ -28,7 +28,7 @@ $lang = array(
'invoice' => 'Rechnung',
'client' => 'Kunde',
'invoice_number' => 'Rechnungsnummer',
'invoice_number_short' => 'Re-Nr.',
'invoice_number_short' => 'Rechnung ',
'po_number' => 'Bestellnummer',
'po_number_short' => 'Best.-Nr.',
'frequency_id' => 'Wie oft',
@ -1536,8 +1536,8 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'country_Kazakhstan' => 'Kasachstan',
'country_Jordan' => 'Jordanien',
'country_Kenya' => 'Kenia',
'country_Korea, Democratic People\'s Republic of' => 'Korea, Democratic People\'s Republic of',
'country_Korea, Republic of' => 'Korea, Republic of',
'country_Korea, Democratic People\'s Republic of' => 'Nordkorea',
'country_Korea, Republic of' => 'Südkorea',
'country_Kuwait' => 'Kuwait',
'country_Kyrgyzstan' => 'Kirgisistan',
'country_Lao People\'s Democratic Republic' => 'Lao People\'s Democratic Republic',
@ -1562,7 +1562,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'country_Mexico' => 'Mexiko',
'country_Monaco' => 'Monaco',
'country_Mongolia' => 'Mongolei',
'country_Moldova, Republic of' => 'Moldova, Republic of',
'country_Moldova, Republic of' => 'Moldau',
'country_Montenegro' => 'Montenegro',
'country_Montserrat' => 'Montserrat',
'country_Morocco' => 'Marokko',
@ -1625,7 +1625,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'country_Singapore' => 'Singapur',
'country_Slovakia' => 'Slowakei',
'country_Viet Nam' => 'Vietnam',
'country_Slovenia' => 'Slovenien',
'country_Slovenia' => 'Slowenien',
'country_Somalia' => 'Somalia',
'country_South Africa' => 'Südafrika',
'country_Zimbabwe' => 'Simbabwe',
@ -4035,7 +4035,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'connect_google' => 'Google-Konto verbinden',
'disconnect_google' => 'Google-Konto entfernen',
'disable_two_factor' => 'Zwei-Faktor-Authentifizierung deaktivieren',
'invoice_task_datelog' => 'In Aufgabe erfasste Daten in Rechnungen ausweisen',
'invoice_task_datelog' => 'In Aufgabe erfasstes Datum in Rechnungen ausweisen',
'invoice_task_datelog_help' => 'Zeigt Datumsdetails in den Rechnungspositionen an',
'promo_code' => 'Gutscheincode',
'recurring_invoice_issued_to' => 'Wiederkehrende Rechnung ausgestellt an',
@ -4932,7 +4932,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'oauth_mail' => 'OAuth / E-Mail',
'preferences' => 'Einstellungen',
'analytics' => 'Analytik',
'reduced_rate' => 'Reduzierte Rate',
'reduced_rate' => 'Reduzierter Preis',
'tax_all' => 'Alles besteuern',
'tax_selected' => 'Steuer ausgewählt',
'version' => 'Ausführung',
@ -4987,7 +4987,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'enable_auto_bill' => 'Automatische Bezahlung aktivieren',
'email_count_invoices' => ':count Rechnungen versenden',
'invoice_task_item_description' => 'Rechnungspositionsbeschreibung',
'invoice_task_item_description_help' => 'Element-Beschreibung zu den Rechnungs-Positionen hinzufügen',
'invoice_task_item_description_help' => 'Element-Beschreibung zu den Rechnungspositionen hinzufügen',
'next_send_time' => 'Nächster Versandzeitpunkt',
'uploaded_certificate' => 'Das Zertifikat wurde erfolgreich hochgeladen',
'certificate_set' => 'Zertifikat hochgeladen',
@ -5141,6 +5141,8 @@ Leistungsempfängers',
'nordigen_handler_error_contents_institution_invalid' => 'Die angegebene Institutions-ID ist ungültig oder nicht mehr gültig.',
'nordigen_handler_error_heading_ref_invalid' => 'Ungültige Referenz',
'nordigen_handler_error_contents_ref_invalid' => 'GoCardless hat keine gültige Referenz angegeben. Bitte führen Sie Flow erneut aus und wenden Sie sich an den Support, wenn das Problem weiterhin besteht.',
'nordigen_handler_error_heading_eua_failure' => 'EUA Fehler',
'nordigen_handler_error_contents_eua_failure' => 'Beim Erstellen der Endbenutzervereinbarung (EUA) ist ein Fehler aufgetreten:',
'nordigen_handler_error_heading_not_found' => 'Ungültige Anforderung',
'nordigen_handler_error_contents_not_found' => 'GoCardless hat keine gültige Referenz angegeben. Bitte führen Sie Flow erneut aus und wenden Sie sich an den Support, wenn das Problem weiterhin besteht.',
'nordigen_handler_error_heading_requisition_invalid_status' => 'Nicht bereit',
@ -5370,7 +5372,7 @@ Leistungsempfängers',
'peppol_plan_warning' => 'Für die Nutzung der elektronischen Rechnungsstellung über das PEPPOL-Netzwerk ist ein Enterprise-Tarif erforderlich.',
'peppol_credits_info' => 'Zum Senden und Empfangen von E-Rechnungen sind E-Credits erforderlich. Diese werden pro Dokument berechnet.',
'buy_credits' => 'E Credits kaufen',
'peppol_successfully_configured' => 'PEPPOL erfolgreich konfiguriert.',
'peppol_successfully_configured' => 'PEPPOL erfolgreich konfiguriert.',
'peppol_not_paid_message' => 'Für PEPPOL ist ein Enterprise-Tarif erforderlich. Bitte aktualisieren Sie Ihren Plan.',
'peppol_country_not_supported' => 'Das PEPPOL-Netzwerk ist für dieses Land noch nicht verfügbar.',
'peppol_disconnect' => 'Trennen Sie die Verbindung zum PEPPOL-Netzwerk',
@ -5382,24 +5384,24 @@ Leistungsempfängers',
'hidden_taxes_warning' => 'Einige Steuern sind aufgrund der aktuellen Steuereinstellungen ausgeblendet. :link',
'tax3' => 'Dritte Steuer',
'negative_payment_warning' => 'Möchten Sie wirklich eine negative Zahlung erstellen? Diese kann nicht als Gutschrift oder Zahlung verwendet werden.',
'currency_Bermudian_Dollar' => 'Bermuda-Dollar',
'currency_Central_African_CFA_Franc' => 'Zentralafrikanischer CFA-Franc',
'currency_Congolese_Franc' => 'Kongolesischer Franc',
'currency_Djiboutian_Franc' => 'Dschibuti-Franc',
'currency_Eritrean_Nakfa' => 'Eritreischer Nakfa',
'currency_Falkland_Islands_Pound' => 'Falklandinseln Pfund',
'currency_Guinean_Franc' => 'Guinea-Franc',
'currency_Iraqi_Dinar' => 'Irakischer Dinar',
'currency_Lesotho_Loti' => 'Lesothischer Loti',
'currency_Mongolian_Tugrik' => 'Mongolischer Tugrik',
'currency_Seychellois_Rupee' => 'Seychellen-Rupie',
'currency_Solomon_Islands_Dollar' => 'Salomonen-Dollar',
'currency_Somali_Shilling' => 'Somalischer Schilling',
'currency_South_Sudanese_Pound' => 'Südsudanesisches Pfund',
'currency_Sudanese_Pound' => 'Sudanesisches Pfund',
'currency_Tajikistani_Somoni' => 'Tadschikischer Somoni',
'currency_Turkmenistani_Manat' => 'Turkmenistan-Manat',
'currency_Uzbekistani_Som' => 'Usbekischer Som',
'currency_bermudian_dollar' => 'Bermuda-Dollar',
'currency_central_african_cfa_franc' => 'Zentralafrikanischer CFA-Franc',
'currency_congolese_franc' => 'Kongolesisches Franc',
'currency_djiboutian_franc' => 'Dschibuti-Franc',
'currency_eritrean_nakfa' => 'Eritreische Nakia',
'currency_falkland_islands_pound' => 'Falklandinseln Pfund',
'currency_guinean_franc' => 'Guinea-Franc',
'currency_iraqi_dinar' => 'Irakischer Dinar',
'currency_lesotho_loti' => 'Lesotho Loti',
'currency_mongolian_tugrik' => 'Mongolian Tugrik',
'currency_seychellois_rupee' => 'Seychellois Rupee',
'currency_solomon_islands_dollar' => 'Solomon Islands Dollar',
'currency_somali_shilling' => 'Somali Shilling',
'currency_south_sudanese_pound' => 'South Sudanese Pound',
'currency_sudanese_pound' => 'Sudanese Pound',
'currency_tajikistani_somoni' => 'Tajikistani Somoni',
'currency_turkmenistani_manat' => 'Turkmenistani Manat',
'currency_uzbekistani_som' => 'Uzbekistani Som',
'payment_status_changed' => 'Bitte beachten Sie, dass der Status Ihrer Zahlung aktualisiert wurde. Wir empfehlen, die Seite zu aktualisieren, um die aktuellste Version anzuzeigen.',
'credit_status_changed' => 'Bitte beachten Sie, dass der Status Ihres Guthabens aktualisiert wurde. Wir empfehlen, die Seite zu aktualisieren, um die aktuellste Version anzuzeigen.',
'credit_updated' => 'Kredit- aktualisiert',
@ -5481,15 +5483,14 @@ Leistungsempfängers',
'download_ready' => 'Ihr Download ist jetzt bereit! [:message]',
'notification_quote_reminder1_sent_subject' => 'Erste Erinnerung für Angebot :invoice wurde an :client gesendet',
'custom_reminder_sent' => 'Benutzerdefinierte Erinnerung wurde an :client gesendet',
'use_system_fonts' => 'Use System Fonts',
'use_system_fonts_help' => 'Override the standard fonts with those from the web browser',
'active_tasks' => 'Active Tasks',
'enable_public_notifications_1' => 'Hallo! Sie können jetzt Echtzeitbenachrichtigungen von Invoice Ninja erhalten!',
'enable_public_notifications_2' => 'Dies bedeutet, dass Sie mit den offiziellen Invoice Ninja-Servern verbunden werden. Wir möchten Sie daher nach Ihrer Präferenz fragen.',
'enable_public_notifications_3' => 'Bitte beachten Sie, dass Sie dieses Modal nur einmal sehen. Wenn Sie Ihre Einstellungen ändern möchten, können Sie dies im Einstellungsmenü tun.',
'use_system_fonts' => 'Verwende Systemschriftarten',
'use_system_fonts_help' => 'Überschreibe die Standardschriftarten mit denen aus dem Webbrowser',
'active_tasks' => 'Aktive Aufgaben',
'enable_notifications' => 'Aktiviere Benachrichtigungen',
'enable_public_notifications' => 'Aktiviere öffentliche Benachrichtigungen',
'navigate' => 'Navigate',
'enable_public_notifications_help' => 'Aktivieren Sie Echtzeitbenachrichtigungen von Invoice Ninja.',
'navigate' => 'Navigiere',
'calculate_taxes_warning' => 'Mit dieser Aktion werden Einzelpostensteuern aktiviert und Gesamtsteuern deaktiviert. Eventuell offene Rechnungen können mit den neuen Einstellungen neu berechnet werden!',
);
return $lang;

View File

@ -5134,6 +5134,8 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'nordigen_handler_error_contents_institution_invalid' => 'L\'identifiant d\'institution fourni n\'est pas ou n\'est plus valide.',
'nordigen_handler_error_heading_ref_invalid' => 'Référence non valide',
'nordigen_handler_error_contents_ref_invalid' => 'GoCardless n\'a pas fourni une référence valide. Veuillez relancer le processus et contacter le support si le problème persiste.',
'nordigen_handler_error_heading_eua_failure' => 'Échec du CLUF',
'nordigen_handler_error_contents_eua_failure' => 'Erreur lors de la création du CLUF :',
'nordigen_handler_error_heading_not_found' => 'Réquisition non valide',
'nordigen_handler_error_contents_not_found' => 'GoCardless n\'a pas fourni une référence valide. Veuillez relancer le processus et contacter le support si le problème persiste.',
'nordigen_handler_error_heading_requisition_invalid_status' => 'Pas prêt',
@ -5364,7 +5366,7 @@ Développe automatiquement la section des notes dans le tableau de produits pour
'peppol_plan_warning' => 'Un plan Entreprise est requis pour utiliser la facturation électronique sur le réseau PEPPOL.',
'peppol_credits_info' => 'Des crédits électroniques (Ecredits) sont nécessaires pour envoyer et recevoir des factures électroniques. Ils sont facturés par document.',
'buy_credits' => 'Acheter des Ecredits',
'peppol_successfully_configured' => 'PEPPOL a été configuré correctement',
'peppol_successfully_configured' => 'PEPPOL a été configuré correctement',
'peppol_not_paid_message' => 'Un plan Entreprise est requis pour PEPPOL. Veuillez mettre à niveau votre plan.',
'peppol_country_not_supported' => 'Le réseau PEPPOL n\'est pas encore disponible pour ce pays.',
'peppol_disconnect' => 'Se déconnecter du réseau PEPPOL',
@ -5376,24 +5378,24 @@ Développe automatiquement la section des notes dans le tableau de produits pour
'hidden_taxes_warning' => 'Certaines taxes sont masquées en raison des paramètres de taxes actuels.',
'tax3' => 'Troisième taxe',
'negative_payment_warning' => 'Êtes-vous sûr de vouloir créer un paiement négatif? Cela ne peut pas être utilisé comme crédit ou paiement.',
'currency_Bermudian_Dollar' => 'Dollar bermudien',
'currency_Central_African_CFA_Franc' => 'Franc CFA de l\'Afrique centrale',
'currency_Congolese_Franc' => 'Franc congolais',
'currency_Djiboutian_Franc' => 'Franc Djiboutien',
'currency_Eritrean_Nakfa' => 'Nakfa érythréen',
'currency_Falkland_Islands_Pound' => 'Livre des Îles Malouines',
'currency_Guinean_Franc' => 'Franc guinéen',
'currency_Iraqi_Dinar' => 'Dinar irakien',
'currency_Lesotho_Loti' => 'Loti lésothien',
'currency_Mongolian_Tugrik' => 'Tugrik mongolien',
'currency_Seychellois_Rupee' => 'Roupie seychelloise',
'currency_Solomon_Islands_Dollar' => 'Dollar des Salomon',
'currency_Somali_Shilling' => 'Shilling somalien',
'currency_South_Sudanese_Pound' => 'Livre sud-soudanaise',
'currency_Sudanese_Pound' => 'Livre soudanaise',
'currency_Tajikistani_Somoni' => 'Somoni Tadjik',
'currency_Turkmenistani_Manat' => 'Manat turkmène',
'currency_Uzbekistani_Som' => 'Sum Ouzbek',
'currency_bermudian_dollar' => 'Dollar bermudien',
'currency_central_african_cfa_franc' => 'Franc CFA centrafricain',
'currency_congolese_franc' => 'Franc congolais',
'currency_djiboutian_franc' => 'Franc Djibouti',
'currency_eritrean_nakfa' => 'Nakfa érythréen',
'currency_falkland_islands_pound' => 'Livre des Îles Malouines',
'currency_guinean_franc' => 'Franc guinéen',
'currency_iraqi_dinar' => 'Dinar irakien',
'currency_lesotho_loti' => 'Loti Lesotho',
'currency_mongolian_tugrik' => 'Tugrik mongol',
'currency_seychellois_rupee' => 'Roupie seychelloise',
'currency_solomon_islands_dollar' => 'Dollar des Salomon',
'currency_somali_shilling' => 'Shilling somalien',
'currency_south_sudanese_pound' => 'Livre sud-soudanaise',
'currency_sudanese_pound' => 'Livre soudanaise',
'currency_tajikistani_somoni' => 'Somoni Tadjik',
'currency_turkmenistani_manat' => 'Manat turkmène',
'currency_uzbekistani_som' => 'Sum Ouzbek',
'payment_status_changed' => 'Veuillez noter que le statut de votre paiement a été mis à jour. Nous vous recommandons de rafraîchir la page pour voir la version la plus récente.',
'credit_status_changed' => 'Veuillez noter que le statut de votre crédit a été mis à jour. Nous vous recommandons de rafraîchir la page pour voir la version la plus récente.',
'credit_updated' => 'Crédit mis à jour',
@ -5478,12 +5480,11 @@ Développe automatiquement la section des notes dans le tableau de produits pour
'use_system_fonts' => 'Utiliser les polices de caractères du système',
'use_system_fonts_help' => 'Remplacer les polices standard par celles du navigateur web',
'active_tasks' => 'Tâches actives',
'enable_public_notifications_1' => 'Bonjour ! Vous pouvez maintenant recevoir des notifications en temps réel d\'Invoice Ninja !',
'enable_public_notifications_2' => 'Cela signifie que vous serez connecté aux serveurs officiels d\'Invoice Ninja, nous aimerions donc connaître votre préférence.',
'enable_public_notifications_3' => 'Veuillez noter que vous ne verrez cette fenêtre modale qu\'une seule fois. Si vous souhaitez modifier vos paramètres, vous pouvez le faire dans le menu des paramètres.',
'enable_notifications' => 'Activer les notifications',
'enable_public_notifications' => 'Activer les notifications publiques',
'enable_public_notifications_help' => 'Activer les notifications en temps réel de Invoice Ninja.',
'navigate' => 'Naviguer',
'calculate_taxes_warning' => 'Cette action activera les taxes par ligne et désactivera les taxes totales. Toutes les factures ouvertes peuvent être recalculées avec les nouveaux paramètres !',
);
return $lang;

View File

@ -738,7 +738,7 @@ $lang = array(
'activity_7' => ':contact đã xem hóa đơn :invoice gửi đến :client',
'activity_8' => ':user hóa đơn lưu trữ :invoice',
'activity_9' => ':user đã xóa hóa đơn :invoice',
'activity_10' => ':user đã nhập thanh toán :payment bởi :payment _số tiền trên hóa đơn :invoice cho :client',
'activity_10' => ':user đã nhập thanh toán :payment bởi :payment số tiền trên hóa đơn :invoice cho :client',
'activity_11' => ':user cập nhật thanh toán :payment',
'activity_12' => ':user thanh toán đã lưu trữ :payment',
'activity_13' => ':user đã xóa thanh toán :payment',
@ -2132,7 +2132,7 @@ $lang = array(
'purge_data_message' => 'Cảnh báo: Thao tác này sẽ xóa vĩnh viễn dữ liệu của bạn, không thể hoàn tác.',
'contact_phone' => 'Điện thoại liên hệ',
'contact_email' => 'Email liên hệ',
'reply_to_email' => 'Trả lời Email',
'reply_to_email' => 'Địa chỉ mail',
'reply_to_email_help' => 'Chỉ định địa chỉ trả lời cho email của khách hàng.',
'bcc_email_help' => 'Bao gồm riêng địa chỉ này vào email của khách hàng.',
'import_complete' => 'Quá trình nhập của bạn đã hoàn tất thành công.',
@ -2188,7 +2188,7 @@ $lang = array(
'host' => 'Chủ nhà',
'database' => 'Cơ sở dữ liệu',
'test_connection' => 'Kiểm tra kết nối',
'from_name' => 'Từ Tên',
'from_name' => 'Người gửi',
'from_address' => 'Từ Địa chỉ',
'port' => 'Cảng',
'encryption' => 'Mã hóa',
@ -2903,7 +2903,7 @@ $lang = array(
'local_part_available' => 'Name available',
'local_part_invalid' => 'Tên không hợp lệ (chỉ chữ số, không có khoảng trắng',
'local_part_help' => 'Tùy chỉnh phần địa phương của email hỗ trợ đến của bạn, ví dụ: YOUR_NAME@support.invoiceninja.com',
'from_name_help' => 'Từ tên là người gửi có thể nhận dạng được hiển thị thay cho địa chỉ email, tức là Trung tâm hỗ trợ',
'from_name_help' => 'Người gửi là người gửi có thể nhận dạng được hiển thị thay cho địa chỉ email, tức là Trung tâm hỗ trợ',
'local_part_placeholder' => 'YOUR_NAME',
'from_name_placeholder' => 'Trung tâm hỗ trợ',
'attachments' => 'Đính kèm',
@ -3981,7 +3981,7 @@ $lang = array(
'converted_paid_to_date' => 'Đã chuyển đổi thanh toán',
'converted_credit_balance' => 'Số dư tín dụng đã chuyển đổi',
'converted_total' => 'Tổng số đã chuyển đổi',
'reply_to_name' => 'Trả lời tên',
'reply_to_name' => 'Soạn thư',
'payment_status_-2' => 'Một phần chưa áp dụng',
'color_theme' => 'Chủ đề màu sắc',
'start_migration' => 'Bắt đầu di chuyển',
@ -4527,7 +4527,7 @@ $lang = array(
'connect_email' => 'Kết nối Email',
'disconnect_email' => 'Ngắt kết nối Email',
'use_web_app_to_connect_microsoft' => 'Vui lòng sử dụng ứng dụng web để kết nối với Microsoft',
'email_provider' => 'Nhà cung cấp email',
'email_provider' => 'Email',
'connect_microsoft' => 'Kết nối Microsoft',
'disconnect_microsoft' => 'Ngắt kết nối Microsoft',
'connected_microsoft' => 'Đã kết nối thành công Microsoft',
@ -5137,6 +5137,8 @@ $lang = array(
'nordigen_handler_error_contents_institution_invalid' => 'Mã số tổ chức được cung cấp không hợp lệ hoặc không còn hợp lệ.',
'nordigen_handler_error_heading_ref_invalid' => 'Tham chiếu không hợp lệ',
'nordigen_handler_error_contents_ref_invalid' => 'GoCardless không cung cấp tham chiếu hợp lệ. Vui lòng chạy lại luồng và liên hệ với bộ phận hỗ trợ nếu sự cố này vẫn tiếp diễn.',
'nordigen_handler_error_heading_eua_failure' => 'Lỗi EUA',
'nordigen_handler_error_contents_eua_failure' => 'Đã xảy ra lỗi trong quá trình tạo Thỏa thuận kết thúc Người dùng :',
'nordigen_handler_error_heading_not_found' => 'Yêu cầu không hợp lệ',
'nordigen_handler_error_contents_not_found' => 'GoCardless không cung cấp tham chiếu hợp lệ. Vui lòng chạy lại luồng và liên hệ với bộ phận hỗ trợ nếu sự cố này vẫn tiếp diễn.',
'nordigen_handler_error_heading_requisition_invalid_status' => 'Chưa sẵn sàng',
@ -5366,7 +5368,7 @@ $lang = array(
'peppol_plan_warning' => 'Cần có gói doanh nghiệp đến sử dụng hóa đơn điện tử qua mạng PEPPOL.',
'peppol_credits_info' => 'Cần có Ecredit đến gửi và nhận hóa đơn điện tử. Chúng được tính phí theo từng chứng từ.',
'buy_credits' => 'Mua tín dụng E',
'peppol_successfully_configured' => 'PEPPOL đã được cấu hình thành công.',
'peppol_successfully_configured' => 'PEPPOL đã được cấu hình thành công.',
'peppol_not_paid_message' => 'Cần có gói Enterprise cho PEPPOL. Vui lòng nâng cấp gói của bạn.',
'peppol_country_not_supported' => 'Mạng PEPPOL hiện chưa khả dụng ở quốc gia này.',
'peppol_disconnect' => 'Ngắt kết nối khỏi mạng PEPPOL',
@ -5378,24 +5380,24 @@ $lang = array(
'hidden_taxes_warning' => 'Một số loại thuế bị ẩn Quá hạn đến Cài đặt thuế hiện hành . :link',
'tax3' => 'Thuế thứ ba',
'negative_payment_warning' => 'Bạn có chắc chắn đến Tạo một Sự chi trả giá âm ? Điều này không thể được sử dụng như một khoản tín dụng hoặc Sự chi trả .',
'currency_Bermudian_Dollar' => 'Đô la Bermuda',
'currency_Central_African_CFA_Franc' => 'Franc CFA Trung Phi',
'currency_Congolese_Franc' => 'Franc Congo',
'currency_Djiboutian_Franc' => 'Franc Djibouti',
'currency_Eritrean_Nakfa' => 'Nakfa Eritrea',
'currency_Falkland_Islands_Pound' => 'Bảng Anh Quần đảo Falkland',
'currency_Guinean_Franc' => 'Franc Guinea',
'currency_Iraqi_Dinar' => 'Dinar Iraq',
'currency_Lesotho_Loti' => 'Loti Lesotho',
'currency_Mongolian_Tugrik' => 'Tugrik Mông Cổ',
'currency_Seychellois_Rupee' => 'Rupee Seychelles',
'currency_Solomon_Islands_Dollar' => 'Đô la Quần đảo Solomon',
'currency_Somali_Shilling' => 'Shilling Somali',
'currency_South_Sudanese_Pound' => 'Bảng Nam Sudan',
'currency_Sudanese_Pound' => 'Bảng Sudan',
'currency_Tajikistani_Somoni' => 'Somoni Tajikistan',
'currency_Turkmenistani_Manat' => 'Đồng Manat Turkmenistan',
'currency_Uzbekistani_Som' => 'Som Uzbekistan',
'currency_bermudian_dollar' => 'Đô la Bermuda',
'currency_central_african_cfa_franc' => 'Franc CFA Trung Phi',
'currency_congolese_franc' => 'Franc Congo',
'currency_djiboutian_franc' => 'Franc Djibouti',
'currency_eritrean_nakfa' => 'Nakfa Eritrea',
'currency_falkland_islands_pound' => 'Bảng Anh Quần đảo Falkland',
'currency_guinean_franc' => 'Franc Guinea',
'currency_iraqi_dinar' => 'Dinar Iraq',
'currency_lesotho_loti' => 'Loti Lesotho',
'currency_mongolian_tugrik' => 'Tugrik Mông Cổ',
'currency_seychellois_rupee' => 'Rupee Seychelles',
'currency_solomon_islands_dollar' => 'Đô la Quần đảo Solomon',
'currency_somali_shilling' => 'Shilling Somali',
'currency_south_sudanese_pound' => 'Bảng Nam Sudan',
'currency_sudanese_pound' => 'Bảng Sudan',
'currency_tajikistani_somoni' => 'Somoni Tajikistan',
'currency_turkmenistani_manat' => 'Đồng Manat Turkmenistan',
'currency_uzbekistani_som' => 'Som Uzbekistan',
'payment_status_changed' => 'Xin Ghi chú rằng trạng thái Sự chi trả của bạn đã được đã cập nhật . Chúng tôi khuyên bạn nên làm mới trang đến Xem phiên bản mới nhất.',
'credit_status_changed' => 'Vui lòng Ghi chú rằng trạng thái tín dụng của bạn đã được đã cập nhật . Chúng tôi khuyên bạn nên làm mới trang đến Xem phiên bản mới nhất.',
'credit_updated' => 'đã cập nhật tín dụng',
@ -5480,12 +5482,11 @@ $lang = array(
'use_system_fonts' => 'Sử dụng Phông chữ Hệ thống',
'use_system_fonts_help' => 'Ghi đè phông chữ chuẩn bằng phông chữ từ trình duyệt web',
'active_tasks' => 'Nhiệm vụ đang hoạt động',
'enable_public_notifications_1' => 'Xin chào! Bây giờ bạn có thể nhận thông báo theo thời gian thực từ Hóa đơn Ninja!',
'enable_public_notifications_2' => 'Điều này có nghĩa là bạn sẽ được kết nối đến máy chủ Hóa đơn Ninja chính thức, vì vậy chúng tôi muốn hỏi ý kiến đến về sở thích của bạn.',
'enable_public_notifications_3' => 'Vui lòng Ghi chú , bạn sẽ chỉ nhìn thấy phương thức này một lần. Nếu bạn muốn đến đổi Cài đặt của mình, bạn có thể làm như vậy trong menu Cài đặt .',
'enable_notifications' => 'Bật thông báo',
'enable_public_notifications' => 'Bật thông báo công khai',
'enable_public_notifications_help' => 'Bật thông báo thời gian thực từ Invoice Ninja.',
'navigate' => 'Điều hướng',
'calculate_taxes_warning' => 'Hành động này sẽ kích hoạt thuế mục hàng và vô hiệu hóa thuế Tổng cộng . Mọi Hóa đơn đang mở có thể được tính toán lại bằng Cài đặt mới !',
);
return $lang;

View File

@ -58,7 +58,10 @@
#header, #header-spacer {
height: 160px;
padding: 2rem;
padding-top: 2rem;
padding-bottom: 2rem;
padding-left: 1rem;
padding-right: 1rem;
margin-bottom: 1rem;
}
.company-logo {
@ -129,7 +132,7 @@
min-width: 100%;
table-layout: fixed;
overflow-wrap: break-word;
margin-top: 0rem;
margin-top: 0.5rem;
margin-bottom: 0px;
}
@ -181,8 +184,8 @@
display: grid;
grid-template-columns: 2fr 1fr;
padding-top: .5rem;
padding-left: 3rem;
padding-right: 3rem;
padding-left: 1rem;
padding-right: 1rem;
gap: 80px;
}
@ -248,7 +251,7 @@
#footer, #footer-spacer {
height: 160px;
padding: 1rem 3rem;
padding: 1rem 1rem;
margin-top: 1rem;
}
@ -257,8 +260,8 @@
}
[data-ref="footer_content"]{
padding-right: 2rem;
margin-right: 2rem;
padding-right: 1rem;
margin-right: 1rem;
}
table {
@ -309,7 +312,10 @@
width: 10%;
}
[data-ref='product_table-product.unit_cost-th'],
[data-ref='product_table-product.unit_cost-th']{
width: 10%;
}
[data-ref='product_table-product.quantity-th'],
[data-ref='product_table-product.product1-th'],
[data-ref='product_table-product.product2-th'],
@ -323,9 +329,12 @@
}
[data-ref='product_table-product.line_total-th'],
[data-ref='product_table-product.line_total-td'] {
width: 8%;
text-align: right;
[data-ref='product_table-product.line_total-td'],
[data-ref='task_table-task.line_total-th'],
[data-ref='task_table-task.line_total-td'] {
width: 10%;
text-align: right !important;
padding-right: 1rem !important;
}
[data-ref='task_table-task.description-th'] {
@ -352,11 +361,6 @@
width: 6%;
}
[data-ref='task_table-task.line_total-th'],
[data-ref='task_table-task.line_total-td'] {
width: 8%;
text-align: right !important;
}
.left-radius {
padding-left: 1rem;

View File

@ -254,18 +254,18 @@
[data-ref="totals_table-outstanding-label"]{
background-color: var(--secondary-color);
color: white;
padding-top: 7px;
padding-bottom: 7px;
padding-right: 7px;
font-size:120%;
font-weight:bold;
padding: 1rem;
border-top-left-radius: 7px;
border-bottom-left-radius: 7px;
}
[data-ref="totals_table-outstanding"] {
background-color: var(--secondary-color);
color: white;
padding-top: 7px;
padding-bottom: 7px;
padding-right: 7px;
font-size:120%;
font-weight:bold;
padding: 1rem;
border-top-right-radius: 7px;
border-bottom-right-radius: 7px;
}

View File

@ -267,7 +267,7 @@
[data-ref='product_table-product.item-th'],
[data-ref='product_table-product.unit_cost-th'],
[data-ref='product_table-product.quantity-th'] {
width: 10%;
width: 12%;
}
[data-ref='product_table-product.tax1-th'] {
@ -289,7 +289,7 @@
[data-ref='task_table-task.discount-th'],
[data-ref='task_table-task.cost-th'],
[data-ref='task_table-task.quantity-th'] {
width: 10%;
width: 12%;
}
[data-ref='task_table-task.tax1-th'] {

View File

@ -145,6 +145,53 @@
color: grey;
}
[data-ref='product_table-product.description-th'],
[data-ref='product_table-product.description-td'] {
min-width: 150px !important;
overflow-wrap: break-word;
}
[data-ref='product_table-product.item-td']{
color: var(--primary-color);
}
[data-ref='product_table-product.item-th'],
[data-ref='product_table-product.unit_cost-th'],
[data-ref='product_table-product.quantity-th'] {
width: 12%;
}
[data-ref='product_table-product.tax1-th'] {
width: 6%;
}
[data-ref='product_table-product.line_total-th'] {
width: 10%;
}
[data-ref='task_table-task.description-th'],
[data-ref='task_table-task.description-td'] {
min-width: 100px !important;
overflow-wrap: break-word;
}
[data-ref='task_table-task.hours-th'],
[data-ref='task_table-task.service-th'],
[data-ref='task_table-task.discount-th'],
[data-ref='task_table-task.cost-th'],
[data-ref='task_table-task.quantity-th'] {
width: 12%;
}
[data-ref='task_table-task.tax1-th'] {
width: 6%;
}
[data-ref='task_table-task.line_total-th'] {
width: 10%;
}
[data-ref="table"] > thead {
text-align: left;
}

View File

@ -242,7 +242,7 @@
#table-totals > * > :last-child {
text-align: right;
padding-right: 1rem;
padding-right: 0rem;
}
#footer {
@ -255,7 +255,14 @@
margin-bottom: 0;
}
[data-ref='product_table-product.description-td'] {
[data-ref='task_table-task.service-th'],
[data-ref='product_table-product.item-th'] {
padding-left:1rem !important;
width:14%;
}
[data-ref='product_table-product.description-td'],
[data-ref="task_table-task.description-td"] {
min-width:100px;
overflow-wrap: break-word;
}
@ -266,17 +273,21 @@
}
[data-ref="product_table-product.unit_cost-td"],
[data-ref="product_table-product.unit_cost-th"]{
text-align: center;
[data-ref="product_table-product.unit_cost-th"],
[data-ref='task_table-task.cost-th'],
[data-ref='task_table-task.cost-td']{
text-align: center !important;
width: 10%;
padding-left:0 !important;
padding-left:0 !important;
padding-right:0 !important;
}
[data-ref="product_table-product.quantity-th"],
[data-ref="product_table-product.quantity-td"]{
[data-ref="product_table-product.quantity-td"],
[data-ref='task_table-task.hours-th'],
[data-ref='task_table-task.hours-td'] {
width: 10%;
text-align: center;
text-align: center !important;
padding-left:0 !important;
padding-right:0 !important;
}
@ -295,21 +306,8 @@
width: 13%;
}
[data-ref='task_table-task.hours-th'],
[data-ref='task_table-task.hours-td'] {
text-align: center;
padding-left:0 !important;
padding-right:0 !important;
width: 6%;
}
[data-ref='task_table-task.cost-th'],
[data-ref='task_table-task.cost-td'] {
text-align: center;
padding-left:0 !important;
padding-right:0 !important;
width: 8%;
}
[data-ref="task_table-task.line_total-th"],
[data-ref="task_table-task.line_total-td"] {
@ -317,7 +315,16 @@
width: 13%;
}
[data-ref="totals_table-outstanding"] { color: var(--primary-color); }
[data-ref="totals_table-outstanding-label"] {
font-weight:bold;
font-size:120%;
}
[data-ref="totals_table-outstanding"] {
color: var(--primary-color);
font-weight:bold;
font-size:120%;
}
[data-ref="statement-totals"] {
margin-top: 1rem;
@ -344,8 +351,6 @@
bottom: 0;
}
.stamp {
transform: rotate(12deg);
color: #555;
@ -487,18 +492,18 @@
>
</div>
</div>
<table id="product-table" cellspacing="0" data-ref="table"></table>
<table id="task-table" cellspacing="0" data-ref="table"></table>
<table id="delivery-note-table" cellspacing="0" data-ref="table"></table>
<table id="statement-invoice-table" cellspacing="0" data-ref="table"></table>
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div>
<table id="product-table" cellspacing="0" data-ref="table"></table>
<table id="task-table" cellspacing="0" data-ref="table"></table>
<table id="delivery-note-table" cellspacing="0" data-ref="table"></table>
<table id="statement-invoice-table" cellspacing="0" data-ref="table"></table>
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div>
</div>
</td>
</tr>

View File

@ -173,31 +173,36 @@
margin-top: 0rem;
}
.footer-content {
.footer-content {
display: flex;
gap: 10px;
width: 100%;
/* grid-template-columns: 1fr 1fr 1fr; */
width: calc(100% - 2rem);
margin: 0 1rem;
color: #fff4e9;
max-height: 140px;
justify-content: space-between;
margin-top: 0.5rem;
margin-left: 0.5rem;
}
align-items: flex-start;
}
.footer-company-details-address-wrapper {
display: flex;
gap: 0px;
margin-right: 60px;
}
/* Main footer text area */
.footer-content > div:first-child {
width: 50%;
margin-right: 2rem;
}
#company-address,
#company-details {
/* Company details/address wrapper */
.footer-company-details-address-wrapper {
width: 50%;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-top: 1rem;
}
#company-details,
#company-address {
display: flex;
flex-direction: column;
margin-top: 0.5rem;
margin-bottom: 0rem;
}
gap: 0.5rem;
}
#company-address > *,
#company-details > * {
@ -302,25 +307,7 @@
[data-ref='product_table-product.product1-th'],
[data-ref='product_table-product.product2-th'],
[data-ref='product_table-product.product3-th'],
[data-ref='product_table-product.product4-th'] {
width: 8%;
}
[data-ref='product_table-product.tax1-th'] {
width: 6%;
}
[data-ref='product_table-product.line_total-th'],
[data-ref='product_table-product.line_total-td'] {
width: 8%;
text-align: right;
}
[data-ref='task_table-task.description-th'] {
overflow-wrap: break-word;
min-width: 100px !important;
}
[data-ref='product_table-product.product4-th'],
[data-ref='task_table-task.hours-th'],
[data-ref='task_table-task.discount-th'],
[data-ref='task_table-task.cost-th'],
@ -329,7 +316,24 @@
[data-ref='task_table-task.task2-th'],
[data-ref='task_table-task.task3-th'],
[data-ref='task_table-task.task4-th'] {
width: 8%;
width: 10%;
}
[data-ref='product_table-product.tax1-th'] {
width: 6%;
}
[data-ref='product_table-product.line_total-th'],
[data-ref='product_table-product.line_total-td'],
[data-ref='task_table-task.line_total-th'],
[data-ref='task_table-task.line_total-td'] {
width: 12%;
text-align: right;
}
[data-ref='task_table-task.description-th'] {
overflow-wrap: break-word;
min-width: 100px !important;
}
[data-ref='task_table-task.service-th']{
@ -340,13 +344,6 @@
width: 6%;
}
[data-ref='task_table-task.line_total-th'],
[data-ref='task_table-task.line_total-td'] {
width: 8%;
text-align: right !important;
}
.stamp {
transform: rotate(12deg);
color: #555;
@ -483,10 +480,8 @@ $entity_images
<div id="footer">
<div class="footer-content">
<div style="width: 70%; margin-left: 2rem;">
<div class="footer-text">
<p data-ref="total_table-footer">$entity_footer</p>
</div>
<div class="footer-company-details-address-wrapper">
<div id="company-details"></div>

View File

@ -123,7 +123,7 @@
}
[data-ref="table"]>thead>tr>th {
padding: 0.5rem;
padding: 0.75rem;
background-color: #e6e6e6;
}
@ -238,25 +238,28 @@
bottom: 0;
}
[data-ref='product_table-product.description-td']{
[data-ref='product_table-product.description-td'],
[data-ref='task_table-task.description-th']{
min-width: 100px !important;
overflow-wrap: break-word;
}
[data-ref='product_table-product.item-th']{
width: 10%;
}
[data-ref='product_table-product.unit_cost-th']{
width: 10%;
[data-ref='product_table-product.item-th'],
[data-ref='product_table-product.unit_cost-th'],
[data-ref='task_table-task.service-th'],
[data-ref='task_table-task.cost-th']{
width: 12%;
}
[data-ref='product_table-product.quantity-th'],
[data-ref='task_table-task.hours-th'],
[data-ref='task_table-task.quantity-th'],
[data-ref='product_table-product.product1-th'],
[data-ref='product_table-product.product2-th'],
[data-ref='product_table-product.product3-th'],
[data-ref='product_table-product.product4-th'] {
width: 8%;
width: 10%;
}
[data-ref='product_table-product.tax1-th'] {
@ -264,43 +267,27 @@
}
[data-ref='product_table-product.line_total-th'],
[data-ref='product_table-product.line_total-td'] {
width: 8%;
[data-ref='product_table-product.line_total-td'],
[data-ref='task_table-task.line_total-th'],
[data-ref='task_table-task.line_total-td'] {
width: 12%;
text-align: right;
}
[data-ref='task_table-task.description-th'] {
overflow-wrap: break-word;
min-width: 100px !important;
}
[data-ref='task_table-task.hours-th'],
[data-ref='task_table-task.discount-th'],
[data-ref='task_table-task.cost-th'],
[data-ref='task_table-task.quantity-th'],
[data-ref='task_table-task.task1-th'],
[data-ref='task_table-task.task2-th'],
[data-ref='task_table-task.task3-th'],
[data-ref='task_table-task.task4-th'] {
width: 8%;
}
[data-ref='task_table-task.service-th']{
width: 10%;
}
[data-ref='task_table-task.tax1-th'] {
width: 10%;
}
[data-ref='task_table-task.line_total-th'],
[data-ref='task_table-task.line_total-td'] {
width: 8%;
text-align: right !important;
}
.stamp {
transform: rotate(12deg);
color: #555;

View File

@ -277,20 +277,24 @@
}
[data-ref='product_table-product.description-td']{
[data-ref='product_table-product.description-td'],
[data-ref='task_table-task.description-td']{
min-width: 100px !important;
overflow-wrap: break-word;
}
[data-ref='product_table-product.item-th']{
[data-ref='product_table-product.item-th'],
[data-ref='task_table-task.service-th']{
width: 10%;
}
[data-ref='product_table-product.unit_cost-th']{
[data-ref='product_table-product.unit_cost-th'],
[data-ref='task_table-task.cost-th'] {
width: 13%;
}
[data-ref='product_table-product.quantity-th'],
[data-ref='task_table-task.hours-th'],
[data-ref='product_table-product.product1-th'],
[data-ref='product_table-product.product2-th'],
[data-ref='product_table-product.product3-th'],
@ -308,26 +312,15 @@
text-align: right;
}
[data-ref='task_table-task.description-th'] {
overflow-wrap: break-word;
min-width: 100px !important;
}
[data-ref='task_table-task.hours-th'],
[data-ref='task_table-task.discount-th'],
[data-ref='task_table-task.cost-th'],
[data-ref='task_table-task.quantity-th'],
[data-ref='task_table-task.task1-th'],
[data-ref='task_table-task.task2-th'],
[data-ref='task_table-task.task3-th'],
[data-ref='task_table-task.task4-th'] {
width: 8%;
}
[data-ref='task_table-task.service-th']{
width: 10%;
}
[data-ref='task_table-task.tax1-th'] {
width: 10%;

View File

@ -61,6 +61,36 @@ class RecurringInvoiceTest extends TestCase
$this->makeTestData();
}
public function testUniqueNumber()
{
$data = [
'client_id' => $this->client->hashed_id,
'frequency_id' => 5,
'next_send_date' => now()->addMonth()->format('Y-m-d'),
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/recurring_invoices', $data);
$response->assertStatus(200);
$arr = $response->json();
$this->assertNotNull($arr['data']['number']);
$data['number'] = $arr['data']['number'];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/recurring_invoices', $data)
->assertStatus(422);
}
public function testBulkUpdatesTaxes()
{
RecurringInvoice::factory(5)->create([

View File

@ -11,6 +11,7 @@
namespace Tests\Unit;
use App\DataMapper\ClientSettings;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Validation\ValidationException;
use Tests\MockAccountData;
@ -36,6 +37,32 @@ class ClientSettingsTest extends TestCase
}
public function testBadProps()
{
$client = \App\Models\Client::factory()->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'settings' => ClientSettings::defaults(),
]);
$this->assertNotNull($client);
$settings = $client->settings;
$settings->timezone_id = '15';
$client->saveSettings($settings, $client);
$this->assertNotNull($client);
$settings->something_crazy_here = '5424234234';
$client->saveSettings($settings, $client);
$this->assertFalse(property_exists($client->settings, 'something_crazy_here'));
}
public function testClientValidSettingsWithBadProps()
{
$data = [