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) ->setPrintable(false)
->getPaymentPart(); ->getPaymentPart();
// return $html;
return htmlspecialchars($html); return htmlspecialchars($html);
} catch (\Exception $e) { } catch (\Exception $e) {

View File

@ -106,14 +106,14 @@ class InvoiceController extends Controller
break; break;
} }
usleep(200000); usleep(300000);
} }
$invitation = false; $invitation = false;
match($data['entity_type'] ?? 'invoice') { 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']), 'quote' => $invitation = QuoteInvitation::withTrashed()->find($data['invitation_id']),
'credit' => $invitation = CreditInvitation::withTrashed()->find($data['invitation_id']), 'credit' => $invitation = CreditInvitation::withTrashed()->find($data['invitation_id']),
'recurring_invoice' => $invitation = RecurringInvoiceInvitation::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()) { if (Ninja::isHosted()) {
foreach ($this->protected_input as $protected_var) { 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\Requests\Request;
use App\Http\ValidationRules\Project\ValidProjectForClient; use App\Http\ValidationRules\Project\ValidProjectForClient;
use App\Http\ValidationRules\Recurring\UniqueRecurringInvoiceNumberRule;
use App\Models\Client; use App\Models\Client;
use App\Models\RecurringInvoice; use App\Models\RecurringInvoice;
use App\Utils\Traits\CleanLineItems; use App\Utils\Traits\CleanLineItems;
@ -68,7 +67,7 @@ class StoreRecurringInvoiceRequest extends Request
$rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())]; $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_rate1'] = 'bail|sometimes|numeric';
$rules['tax_rate2'] = 'bail|sometimes|numeric'; $rules['tax_rate2'] = 'bail|sometimes|numeric';

View File

@ -12,7 +12,6 @@
namespace App\Http\Requests\RecurringQuote; namespace App\Http\Requests\RecurringQuote;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use App\Http\ValidationRules\Recurring\UniqueRecurringQuoteNumberRule;
use App\Models\Client; use App\Models\Client;
use App\Models\RecurringQuote; use App\Models\RecurringQuote;
use App\Utils\Traits\CleanLineItems; use App\Utils\Traits\CleanLineItems;
@ -63,7 +62,7 @@ class StoreRecurringQuoteRequest extends Request
$rules['frequency_id'] = 'required|integer|digits_between:1,12'; $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; 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) private function checkValidSendingUser($user)
{ {
/* Always ensure the user is set on the correct account */ /* 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'; $this->nmo->settings->email_sending_method = 'default';
return $this->setMailDriver(); return $this->setMailDriver();
} }

View File

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

View File

@ -119,6 +119,10 @@ class AuthorizeTransaction
$transactionRequestType->setOrder($order); $transactionRequestType->setOrder($order);
$transactionRequestType->addToTransactionSettings($duplicateWindowSetting); $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->setPayment($paymentOne);
$transactionRequestType->setCurrencyCode($this->authorize->client->currency()->code); $transactionRequestType->setCurrencyCode($this->authorize->client->currency()->code);

View File

@ -92,6 +92,10 @@ class ChargePaymentProfile
$transactionRequestType->setProfile($profileToCharge); $transactionRequestType->setProfile($profileToCharge);
$transactionRequestType->setCurrencyCode($this->authorize->client->currency()->code); $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 = new CreateTransactionRequest();
$request->setMerchantAuthentication($this->authorize->merchant_authentication); $request->setMerchantAuthentication($this->authorize->merchant_authentication);
$request->setRefId($refId); $request->setRefId($refId);

View File

@ -83,6 +83,10 @@ class RefundTransaction
$transactionRequest->setPayment($paymentOne); $transactionRequest->setPayment($paymentOne);
$transactionRequest->setRefTransId($payment->transaction_reference); $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 = new CreateTransactionRequest();
$request->setMerchantAuthentication($this->authorize->merchant_authentication); $request->setMerchantAuthentication($this->authorize->merchant_authentication);
$request->setRefId($refId); $request->setRefId($refId);

View File

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

View File

@ -352,7 +352,7 @@ class PdfBuilder
$outstanding = $this->service->options['invoices']->sum('balance'); $outstanding = $this->service->options['invoices']->sum('balance');
return [ 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'); $outstanding = $this->service->options['credits']->sum('balance');
return [ 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 [ return [
['element' => 'thead', 'elements' => $this->buildTableHeader('statement_payment')], ['element' => 'thead', 'elements' => $this->buildTableHeader('statement_payment')],
['element' => 'tbody', 'elements' => $tbody], ['element' => 'tbody', 'elements' => $tbody],
@ -500,9 +500,9 @@ class PdfBuilder
$payment = $this->service->options['payments']->first(); $payment = $this->service->options['payments']->first();
return [ return [
['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), $this->service->config->formatMoney($this->payment_amount_total))], ['element' => 'div', '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' => 'div', '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_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(); $payment = $this->service->options['unapplied']->first();
return [ return [
['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.payment_balance'), $this->service->config->formatMoney($this->unapplied_total))], ['element' => 'div', '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' => 'div', '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_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'; $element['elements'][$last_visible]['properties']['class'] .= ' right-radius';
} }
} }
// Then, filter the elements array // Then, filter the elements array
$element['elements'] = array_map(function ($el) { $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' => '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' => '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' => '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']], ['element' => 'img', 'properties' => ['style' => 'max-width: 50%; height: auto;', 'src' => '$contact.signature', 'id' => 'contact-signature']],
@ -1761,20 +1760,20 @@ class PdfBuilder
} }
$elements = [ $elements = [
['element' => 'p', 'content' => ctrans('texts.delivery_note'), 'properties' => ['data-ref' => 'delivery_note-label', 'style' => 'font-weight: bold; text-transform: uppercase']], ['element' => 'div', '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' => 'div', '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' => 'div', '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' => 'div', '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', '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_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_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' => '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)) { 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; return $elements;
@ -1796,7 +1795,7 @@ class PdfBuilder
$variables = $this->service->config->pdf_variables['client_details']; $variables = $this->service->config->pdf_variables['client_details'];
foreach ($variables as $variable) { 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; return $elements;
@ -1811,16 +1810,16 @@ class PdfBuilder
} }
$elements = [ $elements = [
['element' => 'p', 'content' => ctrans('texts.shipping_address'), 'properties' => ['data-ref' => 'shipping_address-label', 'style' => 'font-weight: bold; text-transform: uppercase']], ['element' => 'div', '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' => 'div', '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' => 'div', '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' => 'div', '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', '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_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_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' => '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; return $elements;
@ -1899,7 +1898,7 @@ class PdfBuilder
$elements = []; $elements = [];
foreach ($variables as $variable) { 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; return $elements;
@ -1919,7 +1918,7 @@ class PdfBuilder
$elements = []; $elements = [];
foreach ($variables as $variable) { 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; return $elements;
@ -1939,7 +1938,7 @@ class PdfBuilder
$variables = $this->service->config->pdf_variables['vendor_details']; $variables = $this->service->config->pdf_variables['vendor_details'];
foreach ($variables as $variable) { 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; return $elements;
@ -1997,23 +1996,49 @@ class PdfBuilder
public function createElementContent($element, $children): self public function createElementContent($element, $children): self
{ {
foreach ($children as $child) { foreach ($children as $child) {
$contains_html = false; $contains_html = false;
$contains_markdown = false;
$child['content'] = $child['content'] ?? '';
if ($child['element'] !== 'script') { $lines = explode("\n", $child['content']);
if ($this->service->company->markdown_enabled && array_key_exists('content', $child)) { $contains_markdown = false;
$child['content'] = str_replace('<br>', "\r", ($child['content'] ?? ''));
$child['content'] = $this->commonmark->convert($child['content'] ?? ''); //@phpstan-ignore-line
}
}
if (isset($child['content'])) { foreach ($lines as $line) {
if (isset($child['is_empty']) && $child['is_empty'] === true) { $trimmed = ltrim($line);
if (empty($trimmed)) {
continue; 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 ($contains_html) {
// If the element contains the HTML, we gonna display it as is. Backend is going to // 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 // .. in case string doesn't contain any HTML, we'll just return
// raw $content // 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); $element->appendChild($_child);

View File

@ -241,7 +241,7 @@ class Design extends BaseDesign
$elements = []; $elements = [];
foreach ($variables as $variable) { 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; return $elements;
@ -254,7 +254,7 @@ class Design extends BaseDesign
$elements = []; $elements = [];
foreach ($variables as $variable) { 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; return $elements;
@ -271,7 +271,7 @@ class Design extends BaseDesign
$variables = $this->context['pdf_variables']['vendor_details']; $variables = $this->context['pdf_variables']['vendor_details'];
foreach ($variables as $variable) { 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; return $elements;
@ -301,12 +301,12 @@ class Design extends BaseDesign
})->map(function ($variable) { })->map(function ($variable) {
$variable = str_replace('$client.', '$client.shipping_', $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(); })->toArray();
$header = []; $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); return array_merge($header, $elements);
@ -322,20 +322,20 @@ class Design extends BaseDesign
if ($this->type == self::DELIVERY_NOTE) { if ($this->type == self::DELIVERY_NOTE) {
$elements = [ $elements = [
['element' => 'p', 'content' => ctrans('texts.delivery_note'), 'properties' => ['data-ref' => 'delivery_note-label', 'style' => 'font-weight: bold; text-transform: uppercase']], ['element' => 'div', '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' => 'div', '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' => 'div', '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' => 'div', 'content' => $this->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address2']],
['element' => 'p', 'show_empty' => false, 'elements' => [ ['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_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_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' => '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'])) { 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; return $elements;
@ -344,7 +344,7 @@ class Design extends BaseDesign
$variables = $this->context['pdf_variables']['client_details']; $variables = $this->context['pdf_variables']['client_details'];
foreach ($variables as $variable) { 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; return $elements;
@ -553,7 +553,7 @@ class Design extends BaseDesign
$outstanding = $this->invoices->sum('balance'); $outstanding = $this->invoices->sum('balance');
return [ 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'); $outstanding = $this->credits->sum('balance');
return [ 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(); $payment = $this->payments->first();
return [ return [
// ['element' => 'p', '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->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->payment_amount_total, $this->client))],
]; ];
} }
@ -716,7 +716,7 @@ class Design extends BaseDesign
} }
return [ 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 = [ $elements = [
['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], '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' => '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' => '$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' => '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) { foreach ($children as $child) {
$contains_html = false; $contains_html = false;
$contains_markdown = false;
$child['content'] = $child['content'] ?? '';
if ($child['element'] !== 'script') { $lines = explode("\n", $child['content']);
if (array_key_exists('process_markdown', $this->data) && array_key_exists('content', $child) && $this->data['process_markdown']) { $contains_markdown = false;
$child['content'] = str_replace('<br>', "\r", ($child['content'] ?? ''));
$child['content'] = $this->commonmark->convert($child['content'] ?? ''); //@phpstan-ignore-line
}
}
if (isset($child['content'])) { foreach ($lines as $line) {
if (isset($child['is_empty']) && $child['is_empty'] === true) { $trimmed = ltrim($line);
if (empty($trimmed)) {
continue; 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 ($contains_html) {
// If the element contains the HTML, we gonna display it as is. Backend is going to // 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. // encode it for us, preventing any errors on the processing stage.
@ -118,9 +142,7 @@ trait PdfMakerUtilities
$_child->nodeValue = htmlspecialchars($child['content']); $_child->nodeValue = htmlspecialchars($child['content']);
} else { } else {
// .. in case string doesn't contain any HTML, we'll just return // .. in case string doesn't contain any HTML, we'll just return
// raw $content. $_child = $this->document->createElement($child['element'], htmlspecialchars($child['content']));
$_child = $this->document->createElement($child['element'], isset($child['content']) ? htmlspecialchars($child['content']) : '');
} }
$element->appendChild($_child); $element->appendChild($_child);

View File

@ -68,7 +68,7 @@ class TemplateService
private ?Vendor $vendor = null; 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; private Payment $payment;
@ -1575,23 +1575,14 @@ class TemplateService
foreach ($children as $child) { foreach ($children as $child) {
$contains_html = false; $contains_html = false;
$child['content'] = $child['content'] ?? '';
//06-11-2023 for some reason this parses content as HTML if (isset($child['is_empty']) && $child['is_empty'] === true) {
// if ($child['element'] !== 'script') { continue;
// 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;
} }
$contains_html = str_contains($child['content'], '<') && str_contains($child['content'], '>');
if ($contains_html) { if ($contains_html) {
// If the element contains the HTML, we gonna display it as is. Backend is going to // 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. // encode it for us, preventing any errors on the processing stage.
@ -1605,7 +1596,7 @@ class TemplateService
} else { } else {
// .. in case string doesn't contain any HTML, we'll just return // .. in case string doesn't contain any HTML, we'll just return
// raw $content. // raw $content.
$_child = $this->document->createElement($child['element'], isset($child['content']) ? $child['content'] : ''); $_child = $this->document->createElement($child['element'], $child['content']);
} }
$element->appendChild($_child); $element->appendChild($_child);

View File

@ -46,12 +46,14 @@ trait ClientGroupSettingsSaver
unset($settings[$field]); 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, * 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 * we unset it from the settings object
*/ */
foreach ($settings as $key => $value) { 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}); 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); $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 $generated = $pdf
->setHtml($html) ->setHtml($html)
->generate(); ->generate();

View File

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

View File

@ -28,7 +28,7 @@ $lang = array(
'invoice' => 'Rechnung', 'invoice' => 'Rechnung',
'client' => 'Kunde', 'client' => 'Kunde',
'invoice_number' => 'Rechnungsnummer', 'invoice_number' => 'Rechnungsnummer',
'invoice_number_short' => 'Re-Nr.', 'invoice_number_short' => 'Rechnung ',
'po_number' => 'Bestellnummer', 'po_number' => 'Bestellnummer',
'po_number_short' => 'Best.-Nr.', 'po_number_short' => 'Best.-Nr.',
'frequency_id' => 'Wie oft', '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_Kazakhstan' => 'Kasachstan',
'country_Jordan' => 'Jordanien', 'country_Jordan' => 'Jordanien',
'country_Kenya' => 'Kenia', 'country_Kenya' => 'Kenia',
'country_Korea, Democratic People\'s Republic of' => 'Korea, Democratic People\'s Republic of', 'country_Korea, Democratic People\'s Republic of' => 'Nordkorea',
'country_Korea, Republic of' => 'Korea, Republic of', 'country_Korea, Republic of' => 'Südkorea',
'country_Kuwait' => 'Kuwait', 'country_Kuwait' => 'Kuwait',
'country_Kyrgyzstan' => 'Kirgisistan', 'country_Kyrgyzstan' => 'Kirgisistan',
'country_Lao People\'s Democratic Republic' => 'Lao People\'s Democratic Republic', '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_Mexico' => 'Mexiko',
'country_Monaco' => 'Monaco', 'country_Monaco' => 'Monaco',
'country_Mongolia' => 'Mongolei', 'country_Mongolia' => 'Mongolei',
'country_Moldova, Republic of' => 'Moldova, Republic of', 'country_Moldova, Republic of' => 'Moldau',
'country_Montenegro' => 'Montenegro', 'country_Montenegro' => 'Montenegro',
'country_Montserrat' => 'Montserrat', 'country_Montserrat' => 'Montserrat',
'country_Morocco' => 'Marokko', '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_Singapore' => 'Singapur',
'country_Slovakia' => 'Slowakei', 'country_Slovakia' => 'Slowakei',
'country_Viet Nam' => 'Vietnam', 'country_Viet Nam' => 'Vietnam',
'country_Slovenia' => 'Slovenien', 'country_Slovenia' => 'Slowenien',
'country_Somalia' => 'Somalia', 'country_Somalia' => 'Somalia',
'country_South Africa' => 'Südafrika', 'country_South Africa' => 'Südafrika',
'country_Zimbabwe' => 'Simbabwe', 'country_Zimbabwe' => 'Simbabwe',
@ -4035,7 +4035,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'connect_google' => 'Google-Konto verbinden', 'connect_google' => 'Google-Konto verbinden',
'disconnect_google' => 'Google-Konto entfernen', 'disconnect_google' => 'Google-Konto entfernen',
'disable_two_factor' => 'Zwei-Faktor-Authentifizierung deaktivieren', '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', 'invoice_task_datelog_help' => 'Zeigt Datumsdetails in den Rechnungspositionen an',
'promo_code' => 'Gutscheincode', 'promo_code' => 'Gutscheincode',
'recurring_invoice_issued_to' => 'Wiederkehrende Rechnung ausgestellt an', 'recurring_invoice_issued_to' => 'Wiederkehrende Rechnung ausgestellt an',
@ -4932,7 +4932,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'oauth_mail' => 'OAuth / E-Mail', 'oauth_mail' => 'OAuth / E-Mail',
'preferences' => 'Einstellungen', 'preferences' => 'Einstellungen',
'analytics' => 'Analytik', 'analytics' => 'Analytik',
'reduced_rate' => 'Reduzierte Rate', 'reduced_rate' => 'Reduzierter Preis',
'tax_all' => 'Alles besteuern', 'tax_all' => 'Alles besteuern',
'tax_selected' => 'Steuer ausgewählt', 'tax_selected' => 'Steuer ausgewählt',
'version' => 'Ausführung', 'version' => 'Ausführung',
@ -4987,7 +4987,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'enable_auto_bill' => 'Automatische Bezahlung aktivieren', 'enable_auto_bill' => 'Automatische Bezahlung aktivieren',
'email_count_invoices' => ':count Rechnungen versenden', 'email_count_invoices' => ':count Rechnungen versenden',
'invoice_task_item_description' => 'Rechnungspositionsbeschreibung', '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', 'next_send_time' => 'Nächster Versandzeitpunkt',
'uploaded_certificate' => 'Das Zertifikat wurde erfolgreich hochgeladen', 'uploaded_certificate' => 'Das Zertifikat wurde erfolgreich hochgeladen',
'certificate_set' => 'Zertifikat 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_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_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_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_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_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', '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_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.', 'peppol_credits_info' => 'Zum Senden und Empfangen von E-Rechnungen sind E-Credits erforderlich. Diese werden pro Dokument berechnet.',
'buy_credits' => 'E Credits kaufen', '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_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_country_not_supported' => 'Das PEPPOL-Netzwerk ist für dieses Land noch nicht verfügbar.',
'peppol_disconnect' => 'Trennen Sie die Verbindung zum PEPPOL-Netzwerk', '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', 'hidden_taxes_warning' => 'Einige Steuern sind aufgrund der aktuellen Steuereinstellungen ausgeblendet. :link',
'tax3' => 'Dritte Steuer', 'tax3' => 'Dritte Steuer',
'negative_payment_warning' => 'Möchten Sie wirklich eine negative Zahlung erstellen? Diese kann nicht als Gutschrift oder Zahlung verwendet werden.', '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_bermudian_dollar' => 'Bermuda-Dollar',
'currency_Central_African_CFA_Franc' => 'Zentralafrikanischer CFA-Franc', 'currency_central_african_cfa_franc' => 'Zentralafrikanischer CFA-Franc',
'currency_Congolese_Franc' => 'Kongolesischer Franc', 'currency_congolese_franc' => 'Kongolesisches Franc',
'currency_Djiboutian_Franc' => 'Dschibuti-Franc', 'currency_djiboutian_franc' => 'Dschibuti-Franc',
'currency_Eritrean_Nakfa' => 'Eritreischer Nakfa', 'currency_eritrean_nakfa' => 'Eritreische Nakia',
'currency_Falkland_Islands_Pound' => 'Falklandinseln Pfund', 'currency_falkland_islands_pound' => 'Falklandinseln Pfund',
'currency_Guinean_Franc' => 'Guinea-Franc', 'currency_guinean_franc' => 'Guinea-Franc',
'currency_Iraqi_Dinar' => 'Irakischer Dinar', 'currency_iraqi_dinar' => 'Irakischer Dinar',
'currency_Lesotho_Loti' => 'Lesothischer Loti', 'currency_lesotho_loti' => 'Lesotho Loti',
'currency_Mongolian_Tugrik' => 'Mongolischer Tugrik', 'currency_mongolian_tugrik' => 'Mongolian Tugrik',
'currency_Seychellois_Rupee' => 'Seychellen-Rupie', 'currency_seychellois_rupee' => 'Seychellois Rupee',
'currency_Solomon_Islands_Dollar' => 'Salomonen-Dollar', 'currency_solomon_islands_dollar' => 'Solomon Islands Dollar',
'currency_Somali_Shilling' => 'Somalischer Schilling', 'currency_somali_shilling' => 'Somali Shilling',
'currency_South_Sudanese_Pound' => 'Südsudanesisches Pfund', 'currency_south_sudanese_pound' => 'South Sudanese Pound',
'currency_Sudanese_Pound' => 'Sudanesisches Pfund', 'currency_sudanese_pound' => 'Sudanese Pound',
'currency_Tajikistani_Somoni' => 'Tadschikischer Somoni', 'currency_tajikistani_somoni' => 'Tajikistani Somoni',
'currency_Turkmenistani_Manat' => 'Turkmenistan-Manat', 'currency_turkmenistani_manat' => 'Turkmenistani Manat',
'currency_Uzbekistani_Som' => 'Usbekischer Som', '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.', '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_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', 'credit_updated' => 'Kredit- aktualisiert',
@ -5481,15 +5483,14 @@ Leistungsempfängers',
'download_ready' => 'Ihr Download ist jetzt bereit! [:message]', 'download_ready' => 'Ihr Download ist jetzt bereit! [:message]',
'notification_quote_reminder1_sent_subject' => 'Erste Erinnerung für Angebot :invoice wurde an :client gesendet', 'notification_quote_reminder1_sent_subject' => 'Erste Erinnerung für Angebot :invoice wurde an :client gesendet',
'custom_reminder_sent' => 'Benutzerdefinierte Erinnerung wurde an :client gesendet', 'custom_reminder_sent' => 'Benutzerdefinierte Erinnerung wurde an :client gesendet',
'use_system_fonts' => 'Use System Fonts', 'use_system_fonts' => 'Verwende Systemschriftarten',
'use_system_fonts_help' => 'Override the standard fonts with those from the web browser', 'use_system_fonts_help' => 'Überschreibe die Standardschriftarten mit denen aus dem Webbrowser',
'active_tasks' => 'Active Tasks', 'active_tasks' => 'Aktive Aufgaben',
'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.',
'enable_notifications' => 'Aktiviere Benachrichtigungen', 'enable_notifications' => 'Aktiviere Benachrichtigungen',
'enable_public_notifications' => 'Aktiviere öffentliche 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; 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_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_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_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_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_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', '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_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.', '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', '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_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_country_not_supported' => 'Le réseau PEPPOL n\'est pas encore disponible pour ce pays.',
'peppol_disconnect' => 'Se déconnecter du réseau PEPPOL', '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.', 'hidden_taxes_warning' => 'Certaines taxes sont masquées en raison des paramètres de taxes actuels.',
'tax3' => 'Troisième taxe', '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.', '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_bermudian_dollar' => 'Dollar bermudien',
'currency_Central_African_CFA_Franc' => 'Franc CFA de l\'Afrique centrale', 'currency_central_african_cfa_franc' => 'Franc CFA centrafricain',
'currency_Congolese_Franc' => 'Franc congolais', 'currency_congolese_franc' => 'Franc congolais',
'currency_Djiboutian_Franc' => 'Franc Djiboutien', 'currency_djiboutian_franc' => 'Franc Djibouti',
'currency_Eritrean_Nakfa' => 'Nakfa érythréen', 'currency_eritrean_nakfa' => 'Nakfa érythréen',
'currency_Falkland_Islands_Pound' => 'Livre des Îles Malouines', 'currency_falkland_islands_pound' => 'Livre des Îles Malouines',
'currency_Guinean_Franc' => 'Franc guinéen', 'currency_guinean_franc' => 'Franc guinéen',
'currency_Iraqi_Dinar' => 'Dinar irakien', 'currency_iraqi_dinar' => 'Dinar irakien',
'currency_Lesotho_Loti' => 'Loti lésothien', 'currency_lesotho_loti' => 'Loti Lesotho',
'currency_Mongolian_Tugrik' => 'Tugrik mongolien', 'currency_mongolian_tugrik' => 'Tugrik mongol',
'currency_Seychellois_Rupee' => 'Roupie seychelloise', 'currency_seychellois_rupee' => 'Roupie seychelloise',
'currency_Solomon_Islands_Dollar' => 'Dollar des Salomon', 'currency_solomon_islands_dollar' => 'Dollar des Salomon',
'currency_Somali_Shilling' => 'Shilling somalien', 'currency_somali_shilling' => 'Shilling somalien',
'currency_South_Sudanese_Pound' => 'Livre sud-soudanaise', 'currency_south_sudanese_pound' => 'Livre sud-soudanaise',
'currency_Sudanese_Pound' => 'Livre soudanaise', 'currency_sudanese_pound' => 'Livre soudanaise',
'currency_Tajikistani_Somoni' => 'Somoni Tadjik', 'currency_tajikistani_somoni' => 'Somoni Tadjik',
'currency_Turkmenistani_Manat' => 'Manat turkmène', 'currency_turkmenistani_manat' => 'Manat turkmène',
'currency_Uzbekistani_Som' => 'Sum Ouzbek', '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.', '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_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', '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' => 'Utiliser les polices de caractères du système',
'use_system_fonts_help' => 'Remplacer les polices standard par celles du navigateur web', 'use_system_fonts_help' => 'Remplacer les polices standard par celles du navigateur web',
'active_tasks' => 'Tâches actives', '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_notifications' => 'Activer les notifications',
'enable_public_notifications' => 'Activer les notifications publiques', 'enable_public_notifications' => 'Activer les notifications publiques',
'enable_public_notifications_help' => 'Activer les notifications en temps réel de Invoice Ninja.',
'navigate' => 'Naviguer', '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; return $lang;

View File

@ -738,7 +738,7 @@ $lang = array(
'activity_7' => ':contact đã xem hóa đơn :invoice gửi đến :client', 'activity_7' => ':contact đã xem hóa đơn :invoice gửi đến :client',
'activity_8' => ':user hóa đơn lưu trữ :invoice', 'activity_8' => ':user hóa đơn lưu trữ :invoice',
'activity_9' => ':user đã xóa hóa đơn :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_11' => ':user cập nhật thanh toán :payment',
'activity_12' => ':user thanh toán đã lưu trữ :payment', 'activity_12' => ':user thanh toán đã lưu trữ :payment',
'activity_13' => ':user đã xóa thanh toán :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.', '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_phone' => 'Điện thoại liên hệ',
'contact_email' => 'Email 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.', '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.', '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.', '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à', 'host' => 'Chủ nhà',
'database' => 'Cơ sở dữ liệu', 'database' => 'Cơ sở dữ liệu',
'test_connection' => 'Kiểm tra kết nối', '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ỉ', 'from_address' => 'Từ Địa chỉ',
'port' => 'Cảng', 'port' => 'Cảng',
'encryption' => 'Mã hóa', 'encryption' => 'Mã hóa',
@ -2903,7 +2903,7 @@ $lang = array(
'local_part_available' => 'Name available', '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_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', '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', 'local_part_placeholder' => 'YOUR_NAME',
'from_name_placeholder' => 'Trung tâm hỗ trợ', 'from_name_placeholder' => 'Trung tâm hỗ trợ',
'attachments' => 'Đính kèm', 'attachments' => 'Đính kèm',
@ -3981,7 +3981,7 @@ $lang = array(
'converted_paid_to_date' => 'Đã chuyển đổi thanh toán', 'converted_paid_to_date' => 'Đã chuyển đổi thanh toán',
'converted_credit_balance' => 'Số dư tín dụng đã chuyển đổi', 'converted_credit_balance' => 'Số dư tín dụng đã chuyển đổi',
'converted_total' => 'Tổng số đã 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', 'payment_status_-2' => 'Một phần chưa áp dụng',
'color_theme' => 'Chủ đề màu sắc', 'color_theme' => 'Chủ đề màu sắc',
'start_migration' => 'Bắt đầu di chuyển', 'start_migration' => 'Bắt đầu di chuyển',
@ -4527,7 +4527,7 @@ $lang = array(
'connect_email' => 'Kết nối Email', 'connect_email' => 'Kết nối Email',
'disconnect_email' => 'Ngắt 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', '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', 'connect_microsoft' => 'Kết nối Microsoft',
'disconnect_microsoft' => 'Ngắt 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', '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_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_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_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_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_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', '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_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ừ.', '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', '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_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_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', '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', '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', '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ả .', '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_bermudian_dollar' => 'Đô la Bermuda',
'currency_Central_African_CFA_Franc' => 'Franc CFA Trung Phi', 'currency_central_african_cfa_franc' => 'Franc CFA Trung Phi',
'currency_Congolese_Franc' => 'Franc Congo', 'currency_congolese_franc' => 'Franc Congo',
'currency_Djiboutian_Franc' => 'Franc Djibouti', 'currency_djiboutian_franc' => 'Franc Djibouti',
'currency_Eritrean_Nakfa' => 'Nakfa Eritrea', 'currency_eritrean_nakfa' => 'Nakfa Eritrea',
'currency_Falkland_Islands_Pound' => 'Bảng Anh Quần đảo Falkland', 'currency_falkland_islands_pound' => 'Bảng Anh Quần đảo Falkland',
'currency_Guinean_Franc' => 'Franc Guinea', 'currency_guinean_franc' => 'Franc Guinea',
'currency_Iraqi_Dinar' => 'Dinar Iraq', 'currency_iraqi_dinar' => 'Dinar Iraq',
'currency_Lesotho_Loti' => 'Loti Lesotho', 'currency_lesotho_loti' => 'Loti Lesotho',
'currency_Mongolian_Tugrik' => 'Tugrik Mông Cổ', 'currency_mongolian_tugrik' => 'Tugrik Mông Cổ',
'currency_Seychellois_Rupee' => 'Rupee Seychelles', 'currency_seychellois_rupee' => 'Rupee Seychelles',
'currency_Solomon_Islands_Dollar' => 'Đô la Quần đảo Solomon', 'currency_solomon_islands_dollar' => 'Đô la Quần đảo Solomon',
'currency_Somali_Shilling' => 'Shilling Somali', 'currency_somali_shilling' => 'Shilling Somali',
'currency_South_Sudanese_Pound' => 'Bảng Nam Sudan', 'currency_south_sudanese_pound' => 'Bảng Nam Sudan',
'currency_Sudanese_Pound' => 'Bảng Sudan', 'currency_sudanese_pound' => 'Bảng Sudan',
'currency_Tajikistani_Somoni' => 'Somoni Tajikistan', 'currency_tajikistani_somoni' => 'Somoni Tajikistan',
'currency_Turkmenistani_Manat' => 'Đồng Manat Turkmenistan', 'currency_turkmenistani_manat' => 'Đồng Manat Turkmenistan',
'currency_Uzbekistani_Som' => 'Som Uzbekistan', '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.', '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_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', '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' => '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', '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', '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_notifications' => 'Bật thông báo',
'enable_public_notifications' => 'Bật thông báo công khai', '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', '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; return $lang;

View File

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

View File

@ -254,18 +254,18 @@
[data-ref="totals_table-outstanding-label"]{ [data-ref="totals_table-outstanding-label"]{
background-color: var(--secondary-color); background-color: var(--secondary-color);
color: white; color: white;
padding-top: 7px; font-size:120%;
padding-bottom: 7px; font-weight:bold;
padding-right: 7px; padding: 1rem;
border-top-left-radius: 7px; border-top-left-radius: 7px;
border-bottom-left-radius: 7px; border-bottom-left-radius: 7px;
} }
[data-ref="totals_table-outstanding"] { [data-ref="totals_table-outstanding"] {
background-color: var(--secondary-color); background-color: var(--secondary-color);
color: white; color: white;
padding-top: 7px; font-size:120%;
padding-bottom: 7px; font-weight:bold;
padding-right: 7px; padding: 1rem;
border-top-right-radius: 7px; border-top-right-radius: 7px;
border-bottom-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.item-th'],
[data-ref='product_table-product.unit_cost-th'], [data-ref='product_table-product.unit_cost-th'],
[data-ref='product_table-product.quantity-th'] { [data-ref='product_table-product.quantity-th'] {
width: 10%; width: 12%;
} }
[data-ref='product_table-product.tax1-th'] { [data-ref='product_table-product.tax1-th'] {
@ -289,7 +289,7 @@
[data-ref='task_table-task.discount-th'], [data-ref='task_table-task.discount-th'],
[data-ref='task_table-task.cost-th'], [data-ref='task_table-task.cost-th'],
[data-ref='task_table-task.quantity-th'] { [data-ref='task_table-task.quantity-th'] {
width: 10%; width: 12%;
} }
[data-ref='task_table-task.tax1-th'] { [data-ref='task_table-task.tax1-th'] {

View File

@ -145,6 +145,53 @@
color: grey; 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 { [data-ref="table"] > thead {
text-align: left; text-align: left;
} }

View File

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

View File

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

View File

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

View File

@ -61,6 +61,36 @@ class RecurringInvoiceTest extends TestCase
$this->makeTestData(); $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() public function testBulkUpdatesTaxes()
{ {
RecurringInvoice::factory(5)->create([ RecurringInvoice::factory(5)->create([

View File

@ -11,6 +11,7 @@
namespace Tests\Unit; namespace Tests\Unit;
use App\DataMapper\ClientSettings;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Tests\MockAccountData; 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() public function testClientValidSettingsWithBadProps()
{ {
$data = [ $data = [