Merge pull request #7946 from turbo124/v5-develop

v5.5.40
This commit is contained in:
David Bomba 2022-11-16 19:50:08 +11:00 committed by GitHub
commit 9dc1c82547
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 692 additions and 64 deletions

View File

@ -18,18 +18,34 @@ Just make sure to add the `invoice-ninja` tag to your question.
Version 5 of Invoice Ninja is here! We've taken the best parts of version 4 and bolted on all of the most requested features to produce a invoicing application like no other.
The new interface has a lot more functionality so it isn't a carbon copy of v4, but once you get used to the new layout and functionality we are sure you will love it!
All Pro and Enterprise features from the hosted app are included in the open-code. We offer a $30 per year white-label license to remove the Invoice Ninja branding from client facing parts of the app.
## Referral Program
* Earn 50% of Pro & Enterprise Plans up to 4 years - [Learn more](https://www.invoiceninja.com/referral-program/)
* [Videos](https://www.youtube.com/@appinvoiceninja)
* [API Documentation](https://app.swaggerhub.com/apis/invoiceninja/invoiceninja)
* [APP Documentation](https://invoiceninja.github.io/)
* [Support Forum](https://forum.invoiceninja.com)
* [StackOverflow](https://stackoverflow.com/tags/invoice-ninja/)
## Mobile App
* [iPhone](https://apps.apple.com/us/app/invoice-ninja-v5/id1503970375#?platform=iphone)
* [Android](https://play.google.com/store/apps/details?id=com.invoiceninja.app)
* [Linux](https://github.com/invoiceninja/flutter-mobile)
## Desktop App
* [MacOS](https://apps.apple.com/app/id1503970375)
* [Windows](https://microsoft.com/en-us/p/invoice-ninja/9n3f2bbcfdr6)
* [MacOS Desktop](https://snapcraft.io/invoiceninja)
## Installation Options
* [Docker File](https://hub.docker.com/r/invoiceninja/invoiceninja/)
* [Cloudron](https://cloudron.io/store/com.invoiceninja.cloudronapp.html)
* [Softaculous](https://www.softaculous.com/apps/ecommerce/Invoice_Ninja)
## Recommended Providers
* [Stripe](https://stripe.com/)
* [Postmark](https://postmarkapp.com/)
## Development
* [API Documentation](https://app.swaggerhub.com/apis/invoiceninja/invoiceninja)
* [APP Documentation](https://invoiceninja.github.io/)
## Quick Start
@ -67,43 +83,11 @@ user: user@example.com
pass: password
```
## Contribution guide.
Code Style to follow [PSR-2](https://www.php-fig.org/psr/psr-2/) standards.
All methods names to be in CamelCase
All variables names to be in snake_case
Where practical code should be strongly typed, ie your methods must return a type ie
`public function doThis() : void`
PHP >= 7.3 allows the return type Nullable so there should be no circumstance a type cannot be return by using the following:
`public function doThat() ?:string`
To improve chances of PRs being merged please include tests to ensure your code works well and integrates with the rest of the project.
## Documentation
API documentation is hosted using Swagger and can be found [HERE](https://app.swaggerhub.com/apis/invoiceninja/invoiceninja)
Installation, Configuration and Troubleshooting documentation can be found [HERE] (https://invoiceninja.github.io)
## Credits
* [Hillel Coren](https://hillelcoren.com/)
* [David Bomba](https://github.com/turbo124)
* [All contributors](https://github.com/invoiceninja/invoiceninja/graphs/contributors)
**Special thanks to:**
* [Holger Lösken](https://github.com/codedge) - [codedge](http://codedge.de)
* [Samuel Laulhau](https://github.com/lalop) - [Lalop](http://lalop.co/)
* [Alexander Vanderveen](https://blog.technicallycomputers.ca/) - [Technically Computers](https://www.technicallycomputers.ca/)
* [Efthymios Sarmpanis](https://github.com/esarbanis)
* [Gianfranco Gasbarri](https://github.com/gincos)
* [Clemens Mol](https://github.com/clemensmol)
* [Benjamin Beganović](https://github.com/beganovich)
* [All contributors](https://github.com/invoiceninja/invoiceninja/graphs/contributors)
## Security

View File

@ -1 +1 @@
5.5.39
5.5.40

View File

@ -22,6 +22,7 @@ use App\Jobs\Ninja\CompanySizeCheck;
use App\Jobs\Ninja\QueueSize;
use App\Jobs\Ninja\SystemMaintenance;
use App\Jobs\Ninja\TaskScheduler;
use App\Jobs\Quote\QuoteCheckExpired;
use App\Jobs\Util\DiskCleanup;
use App\Jobs\Util\ReminderJob;
use App\Jobs\Util\SchedulerCheck;
@ -70,6 +71,9 @@ class Kernel extends ConsoleKernel
/* Sends recurring invoices*/
$schedule->job(new RecurringExpensesCron)->dailyAt('00:10')->withoutOverlapping();
/* Fires notifications for expired Quotes */
$schedule->job(new QuoteCheckExpired)->dailyAt('05:00')->withoutOverlapping();
/* Performs auto billing */
$schedule->job(new AutoBillCron)->dailyAt('06:00')->withoutOverlapping();

View File

@ -28,6 +28,7 @@ class BlackListRule implements Rule
'wnpop.com',
'dataservices.space',
'karenkey.com',
'sharklasers.com',
];
/**

View File

@ -87,7 +87,7 @@ class Csv extends BaseImport implements ImportInterface
foreach($data as $key => $value)
{
$data[$key]['bank.bank_integration_id'] = $this->decodePrimaryKey($this->request['bank_integration_id']);
$data[$key]['transaction.bank_integration_id'] = $this->decodePrimaryKey($this->request['bank_integration_id']);
}
}

View File

@ -31,17 +31,17 @@ class BankTransformer extends BaseTransformer
$now = now();
$transformed = [
'bank_integration_id' => $transaction['bank.bank_integration_id'],
'transaction_id' => $this->getNumber($transaction,'bank.transaction_id'),
'amount' => abs($this->getFloat($transaction, 'bank.amount')),
'currency_id' => $this->getCurrencyByCode($transaction, 'bank.currency'),
'account_type' => strlen($this->getString($transaction, 'bank.account_type')) > 1 ? $this->getString($transaction, 'bank.account_type') : 'bank',
'category_id' => $this->getNumber($transaction, 'bank.category_id') > 0 ? $this->getNumber($transaction, 'bank.category_id') : null,
'category_type' => $this->getString($transaction, 'bank.category_type'),
'date' => array_key_exists('bank.date', $transaction) ? $this->parseDate($transaction['bank.date'])
'bank_integration_id' => $transaction['transaction.bank_integration_id'],
'transaction_id' => $this->getNumber($transaction,'transaction.transaction_id'),
'amount' => abs($this->getFloat($transaction, 'transaction.amount')),
'currency_id' => $this->getCurrencyByCode($transaction, 'transaction.currency'),
'account_type' => strlen($this->getString($transaction, 'transaction.account_type')) > 1 ? $this->getString($transaction, 'transaction.account_type') : 'bank',
'category_id' => $this->getNumber($transaction, 'transaction.category_id') > 0 ? $this->getNumber($transaction, 'transaction.category_id') : null,
'category_type' => $this->getString($transaction, 'transaction.category_type'),
'date' => array_key_exists('transaction.date', $transaction) ? $this->parseDate($transaction['transaction.date'])
: now()->format('Y-m-d'),
'bank_account_id' => array_key_exists('bank.bank_account_id', $transaction) ? $transaction['bank.bank_account_id'] : 0,
'description' => array_key_exists('bank.description', $transaction) ? $transaction['bank.description'] : '',
'bank_account_id' => array_key_exists('transaction.bank_account_id', $transaction) ? $transaction['transaction.bank_account_id'] : 0,
'description' => array_key_exists('transaction.description', $transaction) ? $transaction['transaction.description'] : '',
'base_type' => $this->calculateType($transaction),
'created_at' => $now,
'updated_at' => $now,
@ -56,22 +56,22 @@ class BankTransformer extends BaseTransformer
private function calculateType($transaction)
{
if(array_key_exists('bank.base_type', $transaction) && ($transaction['bank.base_type'] == 'CREDIT') || strtolower($transaction['bank.base_type']) == 'deposit')
if(array_key_exists('transaction.base_type', $transaction) && (($transaction['transaction.base_type'] == 'CREDIT') || strtolower($transaction['transaction.base_type']) == 'deposit'))
return 'CREDIT';
if(array_key_exists('bank.base_type', $transaction) && ($transaction['bank.base_type'] == 'DEBIT') || strtolower($transaction['bank.bank_type']) == 'withdrawal')
if(array_key_exists('transaction.base_type', $transaction) && (($transaction['transaction.base_type'] == 'DEBIT') || strtolower($transaction['transaction.bank_type']) == 'withdrawal'))
return 'DEBIT';
if(array_key_exists('bank.category_id', $transaction))
if(array_key_exists('transaction.category_id', $transaction))
return 'DEBIT';
if(array_key_exists('bank.category_type', $transaction) && $transaction['bank.category_type'] == 'Income')
if(array_key_exists('transaction.category_type', $transaction) && $transaction['transaction.category_type'] == 'Income')
return 'CREDIT';
if(array_key_exists('bank.category_type', $transaction))
if(array_key_exists('transaction.category_type', $transaction))
return 'DEBIT';
if(array_key_exists('bank.amount', $transaction) && is_numeric($transaction['bank.amount']) && $transaction['bank.amount'] > 0)
if(array_key_exists('transaction.amount', $transaction) && is_numeric($transaction['transaction.amount']) && $transaction['transaction.amount'] > 0)
return 'CREDIT';
return 'DEBIT';

View File

@ -0,0 +1,112 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Jobs\Quote;
use App\Jobs\Mail\NinjaMailer;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Libraries\MultiDB;
use App\Mail\Admin\QuoteExpiredObject;
use App\Models\Quote;
use App\Repositories\BaseRepository;
use App\Utils\Traits\Notifications\UserNotifies;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class QuoteCheckExpired implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, UserNotifies;
/**
* Create a new job instance.
*/
public function __construct() {}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
if (! config('ninja.db.multi_db_enabled'))
return $this->checkForExpiredQuotes();
foreach (MultiDB::$dbs as $db) {
MultiDB::setDB($db);
$this->checkForExpiredQuotes();
}
}
private function checkForExpiredQuotes()
{
Quote::query()
->where('status_id', Quote::STATUS_SENT)
->where('is_deleted', false)
->whereNull('deleted_at')
->whereNotNull('due_date')
->whereHas('client', function ($query) {
$query->where('is_deleted', 0)
->where('deleted_at', null);
})
->whereHas('company', function ($query) {
$query->where('is_disabled', 0);
})
// ->where('due_date', '<='. now()->toDateTimeString())
->whereBetween('due_date', [now()->subDay()->startOfDay(), now()->startOfDay()->subSecond()])
->cursor()
->each(function ($quote){
$this->queueExpiredQuoteNotification($quote);
});
}
private function queueExpiredQuoteNotification(Quote $quote)
{
$nmo = new NinjaMailerObject;
$nmo->mailable = new NinjaMailer((new QuoteExpiredObject($quote, $quote->company))->build());
$nmo->company = $quote->company;
$nmo->settings = $quote->company->settings;
/* We loop through each user and determine whether they need to be notified */
foreach ($quote->company->company_users as $company_user) {
/* The User */
$user = $company_user->user;
if (! $user) {
continue;
}
/* Returns an array of notification methods */
$methods = $this->findUserNotificationTypes($quote->invitations()->first(), $company_user, 'quote', ['all_notifications', 'quote_expired', 'quote_expired_all']);
/* If one of the methods is email then we fire the EntitySentMailer */
if (($key = array_search('mail', $methods)) !== false) {
unset($methods[$key]);
$nmo->to_user = $user;
NinjaMailerJob::dispatch($nmo);
}
}
}
}

View File

@ -21,8 +21,6 @@ class InvoicePaidActivity implements ShouldQueue
{
protected $activity_repo;
public $delay = 10;
/**
* Create the event listener.
*

View File

@ -21,8 +21,6 @@ class UpdateInvoiceActivity implements ShouldQueue
{
protected $activity_repo;
public $delay = 5;
/**
* Create the event listener.
*

View File

@ -0,0 +1,103 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Mail\Admin;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Models\Quote;
use App\Utils\Ninja;
use App\Utils\Number;
use Illuminate\Support\Facades\App;
use stdClass;
class QuoteExpiredObject
{
public $quote;
public $company;
public $settings;
public function __construct(Quote $quote, Company $company)
{
$this->quote = $quote;
$this->company = $company;
}
public function build()
{
MultiDB::setDb($this->company->db);
if (! $this->quote) {
return;
}
App::forgetInstance('translator');
/* Init a new copy of the translator*/
$t = app('translator');
/* Set the locale*/
App::setLocale($this->company->getLocale());
/* Set customized translations _NOW_ */
$t->replace(Ninja::transformTranslations($this->company->settings));
$mail_obj = new stdClass;
$mail_obj->amount = $this->getAmount();
$mail_obj->subject = $this->getSubject();
$mail_obj->data = $this->getData();
$mail_obj->markdown = 'email.admin.generic';
$mail_obj->tag = $this->company->company_key;
return $mail_obj;
}
private function getAmount()
{
return Number::formatMoney($this->quote->amount, $this->quote->client);
}
private function getSubject()
{
return
ctrans(
'texts.notification_quote_expired_subject',
[
'client' => $this->quote->client->present()->name(),
'invoice' => $this->quote->number,
]
);
}
private function getData()
{
$settings = $this->quote->client->getMergedSettings();
$data = [
'title' => $this->getSubject(),
'message' => ctrans(
'texts.notification_quote_expired',
[
'amount' => $this->getAmount(),
'client' => $this->quote->client->present()->name(),
'invoice' => $this->quote->number,
]
),
'url' => $this->quote->invitations->first()->getAdminLink(),
'button' => ctrans('texts.view_quote'),
'signature' => $settings->email_signature,
'logo' => $this->company->present()->logo(),
'settings' => $settings,
'whitelabel' => $this->company->account->isPaid() ? true : false,
];
return $data;
}
}

View File

@ -124,6 +124,7 @@ class RecurringInvoice extends BaseModel
'exchange_rate',
'vendor_id',
'next_send_date_client',
'uses_inclusive_taxes',
];
protected $casts = [

View File

@ -45,6 +45,22 @@ class TriggeredActions extends AbstractService
$this->credit = $this->credit->service()->markSent()->save();
}
if($this->request->has('save_default_footer') && $this->request->input('save_default_footer') == 'true') {
$company = $this->credit->company;
$settings = $company->settings;
$settings->credit_footer = $this->credit->footer;
$company->settings = $settings;
$company->save();
}
if($this->request->has('save_default_terms') && $this->request->input('save_default_terms') == 'true') {
$company = $this->credit->company;
$settings = $company->settings;
$settings->credit_terms = $this->credit->terms;
$company->settings = $settings;
$company->save();
}
return $this->credit;
}

View File

@ -52,6 +52,22 @@ class TriggeredActions extends AbstractService
// $this->purchase_order = $this->purchase_order->service()->handleCancellation()->save();
// }
if($this->request->has('save_default_footer') && $this->request->input('save_default_footer') == 'true') {
$company = $this->purchase_order->company;
$settings = $company->settings;
$settings->purchase_order_footer = $this->purchase_order->footer;
$company->settings = $settings;
$company->save();
}
if($this->request->has('save_default_terms') && $this->request->input('save_default_terms') == 'true') {
$company = $this->purchase_order->company;
$settings = $company->settings;
$settings->purchase_order_terms = $this->purchase_order->terms;
$company->settings = $settings;
$company->save();
}
return $this->purchase_order;
}

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.5.39',
'app_tag' => '5.5.39',
'app_version' => '5.5.40',
'app_tag' => '5.5.40',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),

View File

@ -4837,7 +4837,8 @@ $LANG = array(
'enable_applying_payments_later' => 'Enable Applying Payments Later',
'line_item_tax_rates' => 'Line Item Tax Rates',
'show_tasks_in_client_portal' => 'Show Tasks in Client Portal',
'notification_quote_expired_subject' => 'Quote :invoice has expired for :client',
'notification_quote_expired' => 'The following Quote :invoice for client :client and :amount has now expired.',
);
return $LANG;

View File

@ -0,0 +1,394 @@
<style id="style">
@import url($font_url);
:root {
--primary-color: $primary_color;
--secondary-color: $secondary_color;
--line-height: 1.6;
}
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: $font_name, Helvetica, sans-serif;
font-size: "$font_size";
zoom: 80%;
}
html {
margin: 0;
padding-top: 1rem;
padding-left: 4rem;
padding-right: 4rem;
}
@page {
margin: 0 !important;
size: $page_size $page_layout;
}
p {
margin: 0;
padding: 0;
}
.header-wrapper {
display: grid;
grid-template-columns: 1fr 0.5fr;
line-height: var(--line-height);
}
.header-wrapper2 {
display: grid;
grid-template-columns: 1fr 0.5fr;
margin-top: 2rem;
min-width: 100%;
}
.company-logo {
max-width: 65%;
}
.client-and-entity-wrapper {
display: flex;
padding: 1rem;
border-top: 1px solid #d8d8d8;
border-bottom: 1px solid #d8d8d8;
}
.header-wrapper #company-address {
display: flex;
flex-direction: column;
line-height: var(--line-height);
}
.header-wrapper #entity-details {
margin-top: 0.5rem;
text-align: left;
width: 100%;
}
.header-wrapper #entity-details > tr,
.header-wrapper #entity-details th {
font-weight: normal;
padding-left: 0.9rem;
padding-top: 0.3rem;
padding-bottom: 0.3rem;
}
.header-wrapper
#entity-details
[data-element='entity-balance-due-label'],
.header-wrapper
#entity-details
[data-element='entity-balance-due'] {
background-color: #e6e6e6;
}
#client-details {
display: flex;
flex-direction: column;
line-height: var(--line-height);
}
[data-ref="table"] {
margin-top: 2rem;
min-width: 100%;
table-layout: fixed;
overflow-wrap: break-word;
}
.task-time-details {
display: block;
margin-top: 5px;
color: grey;
}
[data-ref="table"] > thead {
text-align: left;
}
[data-ref="table"] > thead > tr > th {
padding: 1rem;
background-color: #f5f5f5;
}
[data-ref="table"] > thead > tr > th:last-child {
text-align: right;
}
[data-ref="table"] > tbody > tr > td {
border-bottom: 1px solid #e6e6e6;
padding: 0.75rem;
}
[data-ref="table"] > tbody > tr > td:last-child {
text-align: right;
}
[data-ref="table"] > tbody > tr:nth-child(even) {
background-color: #f5f5f5;
}
#table-totals {
margin-top: 0.5rem;
display: grid;
grid-template-columns: 1.5fr 1fr;
padding-top: .5rem;
gap: 80px;
page-break-inside:auto;
overflow: visible !important;
font-weight: bold;
line-height: var(--line-height);
}
#table-totals .totals-table-right-side>* {
display: grid;
grid-template-columns: 1fr 1fr;
}
#table-totals>.totals-table-right-side>*> :nth-child(1) {
text-align: left;
margin-top: .25rem;
padding-left: 7px;
}
#table-totals>.totals-table-right-side> * > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(.25rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(.25rem * var(--tw-space-y-reverse));
}
#table-totals>.totals-table-right-side>*> :nth-child(2) {
text-align: right;
padding-right: 0px;
}
#entity-details {
text-align: left;
width: 100%;
}
#entity-details th {
font-weight:normal;
line-height: 1.5rem;
}
#table-totals
> *
[data-element='total-table-balance-due-label'],
#table-totals
> *
[data-element='total-table-balance-due'] {
font-weight: bold;
}
#table-totals > * > :last-child {
text-align: right;
padding-right: 1rem;
}
[data-ref="total_table-footer"] {
padding-left: 1rem;
padding-right: 1rem;
}
[data-ref="totals_table-outstanding"] {
color: var(--primary-color)
}
/** Markdown-specific styles. **/
[data-ref="table"] h3 {
font-size: 1rem;
margin-bottom: 0;
}
[data-ref="totals_table-outstanding-label"],
[data-ref="totals_table-outstanding"] {
background-color: #e6e6e6;
color: black;
padding-top: 7px;
padding-bottom: 7px;
padding-right: 7px;
}
[data-ref="statement-totals"] {
margin-top: 1rem;
text-align: right;
margin-right: .75rem;
}
[data-ref*=".line_total-td"] {
white-space: nowrap;
}
.repeating-footer,
.repeating-footer-space {
height: 150px;
}
.repeating-header {
position: fixed;
top: 0;
}
.repeating-footer {
position: fixed;
bottom: 0;
}
#header {
position: fixed;
top: 0;
}
#footer {
position: fixed;
bottom: 0;
border-top: 1px solid #000;
width: 82%;
min-height:100px;
padding-top: 0.5rem;
margin-top: 40px;
}
[data-element='product_table-product.description-td'], td {
min-width:100%;
max-width: 300px;
overflow-wrap: break-word;
}
[data-ref="total_table-public_notes"] { font-weight: normal; }
/** Useful snippets, uncomment to enable. **/
/** Hide company logo **/
/* .company-logo { display: none } */
/* Hide company details */
/* # > * { display: none } */
/* Hide company address */
/* #company-address > * { display: none } */
/* Hide terms label */
/* [data-ref="total_table-terms-label"] { display: none } */
/* Hide totals table */
/* #table-totals { display: none } */
/* Hide totals table left side */
/* #table-totals div:first-child > * { display: none !important } */
/* Hide totals table right side */
/* .totals-table-right-side { display: none } */
/** For more info, please check our docs: https://invoiceninja.github.io **/
/** To find out selectors on your own: https://invoiceninja.github.io/docs/custom-fields/#snippets **/
</style>
<table style="min-width: 100%">
<thead>
<tr>
<td>
<div class="repeating-header-space">&nbsp;</div>
</td>
</tr>
</thead>
<tbody>
<tr>
<td>
<div id="body">
<div class="header-wrapper">
<div>
<img class="company-logo" src="$company.logo" alt="$company.name logo">
</div>
<div style="float:right; width:100%;">
<div id="company-details"></div>
<div id="company-address" style="margin-top:10px;"></div>
</div>
</div>
<div class="header-wrapper2">
<div id="client-details"></div>
<div id="vendor-details"></div>
<div>
<p class="entity-label" style="font-size:32px; color:$primary_color;">$entity_label</p>
<table id="entity-details" cellspacing="0" dir="ltr"></table>
</div>
</div>
<table id="product-table" cellspacing="0" data-ref="table"></table>
<table id="task-table" cellspacing="0" data-ref="table"></table>
<table id="delivery-note-table" cellspacing="0" data-ref="table"></table>
<table id="statement-invoice-table" cellspacing="0" data-ref="table"></table>
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0"></div>
</div>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>
<div class="repeating-footer-space">&nbsp;</div>
</td>
</tr>
</tfoot>
</table>
<div class="repeating-header" id="header"></div>
<div id="footer" style="">
<div style="width: 100%;">
<p data-ref="total_table-footer">$entity_footer</p>
<script>
// Clear up space a bit, if [product-table, tasks-table, delivery-note-table] isn't present.
document.addEventListener('DOMContentLoaded', () => {
let tables = [
'product-table', 'task-table', 'delivery-note-table',
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals',
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table',
'vendor-details', 'client-details'
];
tables.forEach((tableIdentifier) => {
console.log(document.getElementById(tableIdentifier));
document.getElementById(tableIdentifier)?.childElementCount === 0
? document.getElementById(tableIdentifier).style.setProperty('display', 'none', 'important')
: '';
});
});
</script>
</div>
</div>
<script>
// Clear up space a bit, if [product-table, tasks-table, delivery-note-table] isn't present.
document.addEventListener('DOMContentLoaded', () => {
let tables = [
'product-table', 'task-table', 'delivery-note-table',
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals',
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table',
'client-details','vendor-details', 'swiss-qr'
];
tables.forEach((tableIdentifier) => {
console.log(document.getElementById(tableIdentifier));
document.getElementById(tableIdentifier)?.childElementCount === 0
? document.getElementById(tableIdentifier).style.setProperty('display', 'none', 'important')
: '';
});
});
</script>
</div>