Merge pull request #7018 from turbo124/v5-stable

v5.3.34
This commit is contained in:
David Bomba 2021-12-08 09:06:39 +11:00 committed by GitHub
commit b2ee7cce38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
111 changed files with 409259 additions and 407498 deletions

View File

@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
operating-system: ['ubuntu-18.04', 'ubuntu-20.04']
php-versions: ['7.4','8.0']
php-versions: ['7.4','8.0','8.1']
phpunit-versions: ['latest']
env:
@ -106,7 +106,8 @@ jobs:
vendor/bin/phpunit --testdox
env:
DB_PORT: ${{ job.services.mysql.ports[3306] }}
PHP_CS_FIXER_IGNORE_ENV: true
- name: Run php-cs-fixer
run: |
vendor/bin/php-cs-fixer fix
PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix

View File

@ -1 +1 @@
5.3.33
5.3.34

View File

@ -46,6 +46,13 @@ class CloneQuoteToInvoiceFactory
$invoice->date = now()->format('Y-m-d');
$invoice->balance = 0;
$invoice->deleted_at = null;
$invoice->next_send_date = null;
$invoice->reminder1_sent = null;
$invoice->reminder2_sent = null;
$invoice->reminder3_sent = null;
$invoice->reminder_last_sent = null;
$invoice->last_sent_date = null;
$invoice->last_viewed = null;
return $invoice;
}

View File

@ -36,7 +36,7 @@ class ExpenseFilters extends QueryFilters
}
return $this->builder->where(function ($query) use ($filter) {
$query->where('expenses.name', 'like', '%'.$filter.'%')
$query->where('expenses.public_notes', 'like', '%'.$filter.'%')
->orWhere('expenses.id_number', 'like', '%'.$filter.'%')
->orWhere('expenses.custom_value1', 'like', '%'.$filter.'%')
->orWhere('expenses.custom_value2', 'like', '%'.$filter.'%')
@ -94,7 +94,10 @@ class ExpenseFilters extends QueryFilters
{
$sort_col = explode('|', $sort);
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
if(is_array($sort_col) && in_array($sort_col[1], ['asc', 'desc']) && in_array($sort_col[0], ['public_notes', 'date', 'id_number', 'custom_value1', 'custom_value2', 'custom_value3', 'custom_value4']))
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
return $this->builder;
}
/**

View File

@ -60,7 +60,14 @@ class ContactForgotPasswordController extends Controller
{
$account_id = $request->has('account_id') ? $request->get('account_id') : 1;
$account = Account::find($account_id);
$company = $account->companies->first();
if($request->has('company_key'))
$company = Company::where('company_key', $request->input('company_key'))->first();
else
$company = $account->companies->first();
if(!$account)
$account = Account::first();
return $this->render('auth.passwords.request', [
'title' => 'Client Password Reset',
@ -90,7 +97,9 @@ class ContactForgotPasswordController extends Controller
// $user = MultiDB::hasContact($request->input('email'));
$company = Company::where('company_key', $request->input('company_key'))->first();
$contact = MultiDB::findContact(['company_id' => $company->id, 'email' => $request->input('email')]);
//$contact = MultiDB::findContact(['company_id' => $company->id, 'email' => $request->input('email')]);
nlog(['company_id' => $company->id, 'email' => $request->input('email')]);
$contact = ClientContact::where(['company_id' => $company->id, 'email' => $request->input('email')])->first();
$response = false;

View File

@ -42,16 +42,15 @@ class ContactLoginController extends Controller
if($request->has('company_key')){
MultiDB::findAndSetDbByCompanyKey($request->input('company_key'));
$company = Company::where('company_key', $request->input('company_key'))->first();
}
if (!$company && strpos($request->getHost(), 'invoicing.co') !== false) {
if($company){
$account = $company->account;
}
elseif (!$company && strpos($request->getHost(), 'invoicing.co') !== false) {
$subdomain = explode('.', $request->getHost())[0];
MultiDB::findAndSetDbByDomain(['subdomain' => $subdomain]);
$company = Company::where('subdomain', $subdomain)->first();
} elseif(Ninja::isHosted()){
@ -107,7 +106,7 @@ class ContactLoginController extends Controller
public function authenticated(Request $request, ClientContact $client)
{
Auth::guard('contact')->login($client, true);
Auth::guard('contact')->loginUsingId($client->id, true);
event(new ContactLoggedIn($client, $client->company, Ninja::eventVars()));

View File

@ -43,7 +43,7 @@ class ContactRegisterController extends Controller
$client = $this->getClient($request->all());
$client_contact = $this->getClientContact($request->all(), $client);
Auth::guard('contact')->login($client_contact, true);
Auth::guard('contact')->loginUsingId($client_contact->id, true);
return redirect()->route('client.dashboard');
}

View File

@ -255,7 +255,7 @@ class BaseController extends Controller
$query->where('expenses.user_id', $user->id)->orWhere('expenses.assigned_user_id', $user->id);
},
'company.groups' => function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('documents');
$query->whereNotNull('updated_at')->with('documents');
// if(!$user->isAdmin())
// $query->where('group_settings.user_id', $user->id);
@ -275,7 +275,7 @@ class BaseController extends Controller
},
'company.payment_terms'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at);
$query->whereNotNull('updated_at');
if(!$user->isAdmin())
$query->where('payment_terms.user_id', $user->id);
@ -346,7 +346,6 @@ class BaseController extends Controller
},
'company.subscriptions'=> function ($query) use($updated_at, $user) {
// $query->where('updated_at', '>=', $updated_at);
$query->whereNotNull('updated_at');
if(!$user->isAdmin())

View File

@ -25,7 +25,7 @@ class ContactHashLoginController extends Controller
*/
public function login(string $contact_key)
{
if(request()->has('subscription') && $request->subscription == 'true') {
if(request()->has('subscription') && request()->subscription == 'true') {
$recurring_invoice = RecurringInvoice::where('client_id', auth()->guard('contact')->client->id)
->whereNotNull('subscription_id')

View File

@ -15,6 +15,7 @@ namespace App\Http\Controllers\ClientPortal;
use App\Http\Controllers\Controller;
use App\Http\Requests\ClientPortal\Documents\ShowDocumentRequest;
use App\Http\Requests\Document\DownloadMultipleDocumentsRequest;
use App\Libraries\MultiDB;
use App\Models\Document;
use App\Utils\TempFile;
use App\Utils\Traits\MakesHash;
@ -55,6 +56,8 @@ class DocumentController extends Controller
public function publicDownload(string $document_hash)
{
MultiDB::documentFindAndSetDb($document_hash);
$document = Document::where('hash', $document_hash)->firstOrFail();
$headers = [];

View File

@ -71,6 +71,8 @@ class InvitationController extends Controller
if(!in_array($entity, ['invoice', 'credit', 'quote', 'recurring_invoice']))
return response()->json(['message' => 'Invalid resource request']);
$is_silent = 'false';
$key = $entity.'_id';
$entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
@ -92,7 +94,7 @@ class InvitationController extends Controller
$client_contact->email = Str::random(15) . "@example.com"; $client_contact->save();
if (request()->has('client_hash') && request()->input('client_hash') == $invitation->contact->client->client_hash) {
auth()->guard('contact')->login($client_contact, true);
auth()->guard('contact')->loginUsingId($client_contact->id, true);
} elseif ((bool) $invitation->contact->client->getSetting('enable_client_portal_password') !== false) {
$this->middleware('auth:contact');
@ -100,7 +102,7 @@ class InvitationController extends Controller
} else {
nlog("else - default - login contact");
auth()->guard('contact')->login($client_contact, true);
auth()->guard('contact')->loginUsingId($client_contact->id, true);
}
@ -111,8 +113,16 @@ class InvitationController extends Controller
$this->fireEntityViewedEvent($invitation, $entity);
}
else{
$is_silent = 'true';
return redirect()->route('client.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->{$key}), 'silent' => $is_silent]);
}
return redirect()->route('client.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->{$key})]);
}
private function fireEntityViewedEvent($invitation, $entity_string)
@ -191,7 +201,7 @@ class InvitationController extends Controller
if($payment->client_id != $contact->client_id)
abort(403, 'You are not authorized to view this resource');
auth()->guard('contact')->login($contact, true);
auth()->guard('contact')->loginUsingId($contact->id, true);
return redirect()->route('client.payments.show', $payment->hashed_id);
@ -203,7 +213,7 @@ class InvitationController extends Controller
->with('contact.client')
->firstOrFail();
auth()->guard('contact')->login($invitation->contact, true);
auth()->guard('contact')->loginUsingId($invitation->contact->id, true);
$invoice = $invitation->invoice;

View File

@ -50,14 +50,17 @@ class NinjaPlanController extends Controller
nlog("Ninja Plan Controller - Found and set Client Contact");
Auth::guard('contact')->login($client_contact,true);
Auth::guard('contact')->loginUsingId($client_contact->id,true);
/* Current paid users get pushed straight to subscription overview page*/
if($account->isPaidHostedClient())
return redirect('/client/dashboard');
// /* Current paid users get pushed straight to subscription overview page*/
// if($account->isPaidHostedClient())
// return redirect('/client/dashboard');
// /* Users that are not paid get pushed to a custom purchase page */
// return $this->render('subscriptions.ninja_plan', ['settings' => $client_contact->company->settings]);
return $this->plan();
/* Users that are not paid get pushed to a custom purchase page */
return $this->render('subscriptions.ninja_plan', ['settings' => $client_contact->company->settings]);
}
return redirect()->route('client.catchall');
@ -68,6 +71,7 @@ class NinjaPlanController extends Controller
{
//harvest the current plan
$data = [];
$data['late_invoice'] = false;
if(MultiDB::findAndSetDbByAccountKey(Auth::guard('contact')->user()->client->custom_value2))
{
@ -137,8 +141,7 @@ class NinjaPlanController extends Controller
}
else
return redirect()->route('client.catchall');
return redirect('/client/dashboard');
}
}

View File

@ -27,7 +27,7 @@ class SwitchCompanyController extends Controller
->where('id', $this->transformKeys($contact))
->first();
auth()->guard('contact')->login($client_contact, true);
auth()->guard('contact')->loginUsingId($client_contact->id, true);
return redirect('/client/dashboard');
}

View File

@ -107,7 +107,7 @@ class LicenseController extends BaseController
'message' => trans('texts.invalid_white_label_license'),
'errors' => new stdClass,
];
$account = auth()->user()->account;
$account->plan_term = Account::PLAN_TERM_YEARLY;
$account->plan_paid = null;
$account->plan_expires = null;
@ -116,7 +116,7 @@ class LicenseController extends BaseController
return response()->json($error, 400);
} else {
$account = auth()->user()->company()->account;
$account = auth()->user()->account;
$account->plan_term = Account::PLAN_TERM_YEARLY;
$account->plan_paid = $data;
@ -151,7 +151,7 @@ class LicenseController extends BaseController
private function checkLicense()
{
$account = auth()->user()->company()->account;
$account = auth()->user()->account;
if($account->plan == 'white_label' && Carbon::parse($account->plan_expires)->lt(now())){
$account->plan = null;

View File

@ -285,7 +285,7 @@ class BillingPortalPurchase extends Component
*/
protected function getPaymentMethods(ClientContact $contact): self
{
Auth::guard('contact')->login($contact, true);
Auth::guard('contact')->loginUsingId($contact->id, true);
$this->contact = $contact;

View File

@ -74,7 +74,7 @@ class DocumentsTable extends Component
break;
case 'expenses':
$this->query = $this->expenses();
// $this->query = $this->expenses();
break;
case 'invoices':

View File

@ -43,6 +43,7 @@ class InvoicesTable extends Component
$local_status = [];
$query = Invoice::query()
->with('client.gateway_tokens','company','client.contacts')
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
->where('company_id', $this->company->id)
->where('is_deleted', false);
@ -82,6 +83,7 @@ class InvoicesTable extends Component
return render('components.livewire.invoices-table', [
'invoices' => $query,
'gateway_available' => !empty(auth()->user()->client->service()->getPaymentMethods(0)),
]);
}
}

View File

@ -38,6 +38,7 @@ class QuotesTable extends Component
public function render()
{
$query = Quote::query()
->with('client.gateway_tokens','company','client.contacts')
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc');
if (count($this->status) > 0) {
@ -48,6 +49,7 @@ class QuotesTable extends Component
->where('company_id', $this->company->id)
->where('client_id', auth('contact')->user()->client->id)
->where('status_id', '<>', Quote::STATUS_DRAFT)
->where('is_deleted', 0)
->withTrashed()
->paginate($this->per_page);

View File

@ -60,7 +60,7 @@ class RequiredClientInfo extends Component
'contact_first_name' => 'first_name',
'contact_last_name' => 'last_name',
// 'contact_email' => 'email',
'contact_email' => 'email',
// 'contact_phone' => 'phone',
];

View File

@ -29,7 +29,7 @@ class CheckClientExistence
public function handle(Request $request, Closure $next)
{
$multiple_contacts = ClientContact::query()
->with('company','client')
->with('client.gateway_tokens')
->where('email', auth('contact')->user()->email)
->whereNotNull('email')
->where('email', '<>', '')
@ -52,7 +52,7 @@ class CheckClientExistence
}
if (count($multiple_contacts) == 1) {
Auth::guard('contact')->login($multiple_contacts[0], true);
Auth::guard('contact')->loginUsingId($multiple_contacts[0]->id, true);
}
session()->put('multiple_contacts', $multiple_contacts);

View File

@ -52,7 +52,7 @@ class ContactKeyLogin
if(empty($client_contact->email))
$client_contact->email = Str::random(15) . "@example.com"; $client_contact->save();
auth()->guard('contact')->login($client_contact, true);
auth()->guard('contact')->loginUsingId($client_contact->id, true);
if ($request->query('redirect') && !empty($request->query('redirect'))) {
return redirect()->to($request->query('redirect'));
@ -70,7 +70,7 @@ class ContactKeyLogin
if(empty($client_contact->email))
$client_contact->email = Str::random(6) . "@example.com"; $client_contact->save();
auth()->guard('contact')->login($client_contact, true);
auth()->guard('contact')->loginUsingId($client_contact->id, true);
if ($request->query('next')) {
return redirect()->to($request->query('next'));
@ -86,7 +86,7 @@ class ContactKeyLogin
$client_contact->email = Str::random(6) . "@example.com"; $client_contact->save();
}
auth()->guard('contact')->login($client_contact, true);
auth()->guard('contact')->loginUsingId($client_contact->id, true);
if ($request->query('next')) {
return redirect($request->query('next'));
@ -104,7 +104,7 @@ class ContactKeyLogin
if(empty($primary_contact->email))
$primary_contact->email = Str::random(6) . "@example.com"; $primary_contact->save();
auth()->guard('contact')->login($primary_contact, true);
auth()->guard('contact')->loginUsingId($primary_contact->id, true);
return redirect()->to('client/dashboard');
}
}
@ -116,7 +116,7 @@ class ContactKeyLogin
if(empty($primary_contact->email))
$primary_contact->email = Str::random(6) . "@example.com"; $primary_contact->save();
auth()->guard('contact')->login($primary_contact, true);
auth()->guard('contact')->loginUsingId($primary_contact->id, true);
return redirect()->to('client/dashboard');
}

View File

@ -19,10 +19,11 @@ class ContactRegister
*/
public function handle($request, Closure $next)
{
$domain_name = $request->getHost();
if (strpos($request->getHost(), 'invoicing.co') !== false)
if (strpos($domain_name, 'invoicing.co') !== false)
{
$subdomain = explode('.', $request->getHost())[0];
$subdomain = explode('.', $domain_name)[0];
$query = [
'subdomain' => $subdomain,
@ -86,6 +87,6 @@ class ContactRegister
return $next($request);
}
abort(404, 'ContactRegister Middlware');
abort(404, 'ContactRegister Middleware');
}
}

View File

@ -51,7 +51,7 @@ class ContactTokenAuth
}
//stateless, don't remember the contact.
auth()->guard('contact')->login($client_contact, false);
auth()->guard('contact')->loginUsingId($client_contact->id, false);
event(new ContactLoggedIn($client_contact, $client_contact->company, Ninja::eventVars()));
} else {

View File

@ -38,10 +38,11 @@ class SetDomainNameDb
if(!config('ninja.db.multi_db_enabled'))
return $next($request);
$domain_name = $request->getHost();
if (strpos($request->getHost(), 'invoicing.co') !== false)
if (strpos($domain_name, 'invoicing.co') !== false)
{
$subdomain = explode('.', $request->getHost())[0];
$subdomain = explode('.', $domain_name)[0];
$query = [
'subdomain' => $subdomain,
@ -49,7 +50,7 @@ class SetDomainNameDb
];
if($company = MultiDB::findAndSetDbByDomain($query)){
$request->request->add(['account_id' => $company->account_id]);
$request->request->add(['account_id' => $company->account_id, 'company_key' => $company->company_key]);
}
else
{
@ -71,7 +72,7 @@ class SetDomainNameDb
];
if($company = MultiDB::findAndSetDbByDomain($query)){
$request->request->add(['account_id' => $company->account_id]);
$request->request->add(['account_id' => $company->account_id, 'company_key' => $company->company_key]);
}
else
{
@ -86,8 +87,6 @@ class SetDomainNameDb
}
// config(['app.url' => $request->getSchemeAndHttpHost()]);
return $next($request);
}
}

View File

@ -17,6 +17,7 @@ class ShowPlanSwitchRequest extends FormRequest
*/
public function authorize()
{
return (bool)$this->recurring_invoice->subscription->allow_plan_changes;
}

View File

@ -81,6 +81,9 @@ class StoreCompanyRequest extends Request
}
}
if(array_key_exists('portal_domain', $input))
$input['portal_domain'] = strtolower($input['portal_domain']);
$this->replace($input);
}
}

View File

@ -68,8 +68,10 @@ class UpdateCompanyRequest extends Request
{
$input = $this->all();
if(Ninja::isHosted() && array_key_exists('portal_domain', $input) && strlen($input['portal_domain']) > 1)
if(Ninja::isHosted() && array_key_exists('portal_domain', $input) && strlen($input['portal_domain']) > 1){
$input['portal_domain'] = $this->addScheme($input['portal_domain']);
$input['portal_domain'] = strtolower($input['portal_domain']);
}
if (array_key_exists('settings', $input)) {
$input['settings'] = $this->filterSaveableSettings($input['settings']);

View File

@ -113,7 +113,6 @@ class PortalComposer
$data[] = ['title' => ctrans('texts.payment_methods'), 'url' => 'client.payment_methods.index', 'icon' => 'shield'];
$data[] = ['title' => ctrans('texts.documents'), 'url' => 'client.documents.index', 'icon' => 'download'];
$data[] = ['title' => ctrans('texts.subscriptions'), 'url' => 'client.subscriptions.index', 'icon' => 'calendar'];
if (auth('contact')->user()->client->getSetting('enable_client_portal_tasks')) {
$data[] = ['title' => ctrans('texts.tasks'), 'url' => 'client.tasks.index', 'icon' => 'clock'];
@ -123,6 +122,8 @@ class PortalComposer
if(Ninja::isHosted() && auth('contact')->user()->company->id == config('ninja.ninja_default_company_id'))
$data[] = ['title' => ctrans('texts.plan'), 'url' => 'client.plan', 'icon' => 'credit-card'];
else
$data[] = ['title' => ctrans('texts.subscriptions'), 'url' => 'client.subscriptions.index', 'icon' => 'calendar'];
return $data;

View File

@ -62,20 +62,30 @@ class BaseTransformer
public function getClient($client_name, $client_email) {
$clients = $this->maps['company']->clients;
$clients = $clients->where( 'id_number', $client_name );
$client_id_search = $clients->where( 'id_number', $client_name );
if ( $clients->count() >= 1 ) {
return $clients->first()->id;
if ( $client_id_search->count() >= 1 ) {
return $client_id_search->first()->id;
nlog("found via id number");
}
$client_name_search = $clients->where( 'name', $client_name );
if ( $client_name_search->count() >= 1 ) {
return $client_name_search->first()->id;
nlog("found via name");
}
if ( ! empty( $client_email ) ) {
$contacts = ClientContact::where( 'company_id', $this->maps['company']->id )
->where( 'email', $client_email );
if ( $contacts->count() >= 1 ) {
return $contacts->first()->client_id;
nlog("found via contact");
}
}
nlog("did not find client");
return null;
}

View File

@ -49,7 +49,7 @@ class InvoiceTransformer extends BaseTransformer {
'due_date' => isset( $invoice_data['invoice.due_date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['invoice.due_date'] ) ) : null,
'terms' => $this->getString( $invoice_data, 'invoice.terms' ),
'public_notes' => $this->getString( $invoice_data, 'invoice.public_notes' ),
'is_sent' => $this->getString( $invoice_data, 'invoice.is_sent' ),
// 'is_sent' => $this->getString( $invoice_data, 'invoice.is_sent' ),
'private_notes' => $this->getString( $invoice_data, 'invoice.private_notes' ),
'tax_name1' => $this->getString( $invoice_data, 'invoice.tax_name1' ),
'tax_rate1' => $this->getFloat( $invoice_data, 'invoice.tax_rate1' ),
@ -92,7 +92,7 @@ class InvoiceTransformer extends BaseTransformer {
'amount' => $this->getFloat( $invoice_data, 'invoice.amount' ),
],
];
} elseif ( isset( $transformed['amount'] ) && isset( $transformed['balance'] ) ) {
} elseif ( isset( $transformed['amount'] ) && isset( $transformed['balance'] ) && ($transformed['amount'] != $transformed['balance'])) {
$transformed['payments'] = [
[
'date' => isset( $invoice_data['payment.date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['payment.date'] ) ) : date( 'y-m-d' ),
@ -126,6 +126,8 @@ class InvoiceTransformer extends BaseTransformer {
}
$transformed['line_items'] = $line_items;
nlog($transformed);
return $transformed;
}
}

View File

@ -53,7 +53,7 @@ class AutoBill
nlog("autobill {$this->invoice->id}");
$this->invoice->service()->autoBill()->save();
$this->invoice->service()->autoBill();
}
catch(\Exception $e) {

View File

@ -115,9 +115,6 @@ class CreateEntityPdf implements ShouldQueue
/* Set customized translations _NOW_ */
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
$translate = microtime(true);
// nlog("Translate ". $translate - $start);
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom)->generate($this->invitation);
}
@ -142,9 +139,6 @@ class CreateEntityPdf implements ShouldQueue
$entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey($this->client->getSetting($entity_design_id));
// if(!$this->company->account->hasFeature(Account::FEATURE_DIFFERENT_DESIGNS))
// $entity_design_id = 2;
$design = Design::find($entity_design_id);
/* Catch all in case migration doesn't pass back a valid design */
@ -153,9 +147,6 @@ class CreateEntityPdf implements ShouldQueue
$html = new HtmlEngine($this->invitation);
$design_time = microtime(true);
// nlog("Design ". $design_time - $translate);
if ($design->is_custom) {
$options = [
'custom_partials' => json_decode(json_encode($design->design), true)
@ -167,9 +158,6 @@ class CreateEntityPdf implements ShouldQueue
$variables = $html->generateLabelsAndValues();
$labels_time = microtime(true);
// nlog("Labels ". $labels_time - $design_time);
$state = [
'template' => $template->elements([
'client' => $this->client,
@ -192,10 +180,6 @@ class CreateEntityPdf implements ShouldQueue
->design($template)
->build();
$template_time = microtime(true);
// nlog("Template Build ". $template_time - $labels_time);
$pdf = null;
try {
@ -215,10 +199,6 @@ class CreateEntityPdf implements ShouldQueue
info($maker->getCompiledHTML());
}
$pdf_time = microtime(true);
// nlog("PDF time " . $pdf_time - $template_time);
if ($pdf) {
try{

View File

@ -188,9 +188,9 @@ class CreateRawPdf implements ShouldQueue
nlog(print_r($e->getMessage(), 1));
}
// if (config('ninja.log_pdf_html')) {
if (config('ninja.log_pdf_html')) {
info($maker->getCompiledHTML());
// }
}
if ($pdf)
return $pdf;

View File

@ -112,6 +112,9 @@ class EmailEntity implements ShouldQueue
App::setLocale($this->invitation->contact->preferredLocale());
$t->replace(Ninja::transformTranslations($this->settings));
/* Mark entity sent */
$this->entity->service()->markSent()->save();
$nmo = new NinjaMailerObject;
$nmo->mailable = new TemplateEmail($this->email_entity_builder, $this->invitation->contact, $this->invitation);
$nmo->company = $this->company;
@ -124,8 +127,7 @@ class EmailEntity implements ShouldQueue
NinjaMailerJob::dispatchNow($nmo);
/* Mark entity sent */
$this->entity->service()->markSent()->save();
}
private function resolveEntityString() :string

View File

@ -332,18 +332,21 @@ class CSVImport implements ShouldQueue {
$invoice = $invoice->service()->markViewed()->save();
}
if ( $invoice->status_id === Invoice::STATUS_SENT ) {
if( $invoice->status_id === Invoice::STATUS_DRAFT ){
}
elseif ( $invoice->status_id === Invoice::STATUS_SENT ) {
$invoice = $invoice->service()->markSent()->save();
}
if ( $invoice->status_id <= Invoice::STATUS_SENT && $invoice->amount > 0 ) {
if ( $invoice->balance < $invoice->amount ) {
$invoice->status_id = Invoice::STATUS_PARTIAL;
$invoice->save();
} elseif ( $invoice->balance <= 0 ) {
elseif ( $invoice->status_id <= Invoice::STATUS_SENT && $invoice->amount > 0 ) {
if ( $invoice->balance <= 0 ) {
$invoice->status_id = Invoice::STATUS_PAID;
$invoice->save();
}
elseif ( $invoice->balance != $invoice->amount ) {
$invoice->status_id = Invoice::STATUS_PARTIAL;
$invoice->save();
}
}

View File

@ -20,8 +20,10 @@ use App\Mail\Admin\EntityNotificationMailer;
use App\Mail\Admin\PaymentFailureObject;
use App\Models\Client;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\PaymentHash;
use App\Models\User;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\Notifications\UserNotifies;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -34,7 +36,7 @@ use Illuminate\Support\Facades\Mail;
class PaymentFailedMailer implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, UserNotifies;
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, UserNotifies, MakesHash;
public ?PaymentHash $payment_hash;
@ -75,15 +77,17 @@ class PaymentFailedMailer implements ShouldQueue
$settings = $this->client->getMergedSettings();
$amount = 0;
$invoice = false;
if($this->payment_hash)
if($this->payment_hash){
$amount = array_sum(array_column($this->payment_hash->invoices(), 'amount')) + $this->payment_hash->fee_total;
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->withTrashed()->first();
}
//iterate through company_users
$this->company->company_users->each(function ($company_user) use($amount, $settings){
$this->company->company_users->each(function ($company_user) use($amount, $settings, $invoice){
//determine if this user has the right permissions
$methods = $this->findCompanyUserNotificationType($company_user, ['payment_failure','all_notifications']);
$methods = $this->findUserEntityNotificationType($invoice ?: $this->client, $company_user, ['payment_failure_user', 'payment_failure_all', 'payment_failure', 'all_notifications']);
//if mail is a method type -fire mail!!
if (($key = array_search('mail', $methods)) !== false) {

View File

@ -80,7 +80,7 @@ class PaymentFailureMailer implements ShouldQueue
$this->company->company_users->each(function ($company_user) {
//determine if this user has the right permissions
$methods = $this->findCompanyUserNotificationType($company_user, ['payment_failure','all_notifications']);
$methods = $this->findCompanyUserNotificationType($company_user, ['payment_failure_all','payment_failure', 'payment_failure_user', 'all_notifications']);
//if mail is a method type -fire mail!!
if (($key = array_search('mail', $methods)) !== false) {

View File

@ -145,7 +145,7 @@ class SendRecurring implements ShouldQueue
if ($invoice->client->getSetting('auto_bill_date') == 'on_send_date' && $invoice->auto_bill_enabled) {
nlog("attempting to autobill {$invoice->number}");
$invoice->service()->autoBill()->save();
$invoice->service()->autoBill();
}
elseif($invoice->client->getSetting('auto_bill_date') == 'on_due_date' && $invoice->auto_bill_enabled) {
@ -153,7 +153,7 @@ class SendRecurring implements ShouldQueue
if($invoice->due_date && Carbon::parse($invoice->due_date)->startOfDay()->lte(now()->startOfDay())) {
nlog("attempting to autobill {$invoice->number}");
$invoice->service()->autoBill()->save();
$invoice->service()->autoBill();
}

View File

@ -59,7 +59,12 @@ class PaymentNotification implements ShouldQueue
foreach ($payment->company->company_users as $company_user) {
$user = $company_user->user;
$methods = $this->findUserEntityNotificationType($payment, $company_user, ['payment_success', 'payment_success_all', 'all_notifications']);
$methods = $this->findUserEntityNotificationType($payment, $company_user, [
'payment_success',
'payment_success_all',
'payment_success_user',
'all_notifications']
);
if (($key = array_search('mail', $methods)) !== false) {
unset($methods[$key]);

View File

@ -69,6 +69,9 @@ class CreditEmailEngine extends BaseEmailEngine
null,
$this->client->locale()
);
$body_template .= '<div class="center">$view_button</div>';
}
if (is_array($this->template_data) && array_key_exists('subject', $this->template_data) && strlen($this->template_data['subject']) > 0) {

View File

@ -74,6 +74,9 @@ class InvoiceEmailEngine extends BaseEmailEngine
null,
$this->client->locale()
);
$body_template .= '<div class="center">$view_button</div>';
}
if (is_array($this->template_data) && array_key_exists('subject', $this->template_data) && strlen($this->template_data['subject']) > 0) {

View File

@ -44,6 +44,7 @@ class QuoteEmailEngine extends BaseEmailEngine
public function build()
{
App::forgetInstance('translator');
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
@ -59,18 +60,22 @@ class QuoteEmailEngine extends BaseEmailEngine
/* Use default translations if a custom message has not been set*/
if (iconv_strlen($body_template) == 0) {
$body_template = trans(
'texts.quote_message',
[
'quote' => $this->quote->number,
'company' => $this->quote->company->present()->name(),
'amount' => Number::formatMoney($this->quote->balance, $this->client),
'amount' => Number::formatMoney($this->quote->amount, $this->client),
],
null,
$this->client->locale()
);
$body_template .= '<div class="center">$view_button</div>';
}
if (is_array($this->template_data) && array_key_exists('subject', $this->template_data) && strlen($this->template_data['subject']) > 0) {
$subject_template = $this->template_data['subject'];
} else {
@ -99,7 +104,6 @@ class QuoteEmailEngine extends BaseEmailEngine
->setViewText(ctrans('texts.view_quote'))
->setInvitation($this->invitation);
if ($this->client->getSetting('pdf_email_attachment') !== false && $this->quote->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
if(Ninja::isHosted())

View File

@ -52,7 +52,8 @@ class TemplateEmail extends Mailable
public function build()
{
$template_name = 'email.template.'.$this->build_email->getTemplate();
$template_name = 'email.template.'.$this->build_email->getTemplate();
if ($this->build_email->getTemplate() == 'light' || $this->build_email->getTemplate() == 'dark') {
$template_name = 'email.template.client';

View File

@ -75,6 +75,7 @@ class Quote extends BaseModel
'assigned_user_id',
'exchange_rate',
'subscription_id',
'uses_inclusive_taxes',
];
protected $casts = [

View File

@ -30,6 +30,7 @@ use App\Models\GatewayType;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\Services\Subscription\SubscriptionService;
use App\Utils\Ninja;
@ -262,12 +263,18 @@ class BaseDriver extends AbstractPaymentDriver
event('eloquent.created: App\Models\Payment', $payment);
if ($this->client->getSetting('client_online_payment_notification'))
if ($this->client->getSetting('client_online_payment_notification') && in_array($status, [Payment::STATUS_COMPLETED, Payment::STATUS_PENDING
]))
$payment->service()->sendEmail();
//todo
//catch any payment failures here also and fire a subsequent failure email if necessary? note only need for delayed payment forms
//perhaps this type of functionality should be handled higher up to provide better context?
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars()));
if (property_exists($this->payment_hash->data, 'billing_context')) {
if (property_exists($this->payment_hash->data, 'billing_context') && $status == Payment::STATUS_COMPLETED) {
$billing_subscription = \App\Models\Subscription::find($this->payment_hash->data->billing_context->subscription_id);
// To access campaign hash => $this->payment_hash->data->billing_context->campaign;
@ -440,7 +447,7 @@ class BaseDriver extends AbstractPaymentDriver
$invoices->first()->invitations->each(function ($invitation) use ($nmo) {
if ($invitation->contact->email) {
if ((bool)$invitation->contact->send_email !== false && $invitation->contact->email) {
$nmo->to_user = $invitation->contact;
NinjaMailerJob::dispatch($nmo);

View File

@ -161,7 +161,8 @@ class PayPal
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_BRAINTREE,
$this->braintree->client
$this->braintree->client,
$this->braintree->client->company
);
throw new PaymentFailed($response->message, 0);

View File

@ -213,10 +213,10 @@ class CreditCard implements MethodInterface
if ($response->status == 'Declined') {
$this->checkout->unWindGatewayFees($this->checkout->payment_hash);
$this->checkout->sendFailureMail($response->response_summary);
// $this->checkout->sendFailureMail($response->response_summary);
//@todo - this will double up the checkout . com failed mails
$this->checkout->clientPaymentFailureMailer($response->status);
// $this->checkout->clientPaymentFailureMailer($response->status);
return $this->processUnsuccessfulPayment($response);
}

View File

@ -84,7 +84,8 @@ trait Utilities
public function processUnsuccessfulPayment(Payment $_payment, $throw_exception = true)
{
$this->getParent()->sendFailureMail($_payment->status . " " . optional($_payment)->response_summary);
$this->getParent()->sendFailureMail($_payment->response_summary);
// $this->getParent()->clientPaymentFailureMailer($_payment->status);
$message = [
'server_response' => $_payment,

View File

@ -160,11 +160,11 @@ class ACH implements MethodInterface
*/
public function paymentResponse(PaymentResponseRequest $request)
{
$token = ClientGatewayToken::find(
$this->decodePrimaryKey($request->source)
)->firstOrFail();
// $token = ClientGatewayToken::find(
// $this->decodePrimaryKey($request->source)
// )->firstOrFail();
$this->go_cardless->ensureMandateIsReady($token);
$this->go_cardless->ensureMandateIsReady($request->source);
try {
$payment = $this->go_cardless->gateway->payments()->create([
@ -175,7 +175,7 @@ class ACH implements MethodInterface
'payment_hash' => $this->go_cardless->payment_hash->hash,
],
'links' => [
'mandate' => $token->token,
'mandate' => $request->source,
],
],
]);
@ -201,7 +201,6 @@ class ACH implements MethodInterface
public function processPendingPayment(\GoCardlessPro\Resources\Payment $payment, array $data = [])
{
$data = [
'payment_method' => $data['token'],
'payment_type' => PaymentType::ACH,
'amount' => $this->go_cardless->payment_hash->data->amount_with_fee,
'transaction_reference' => $payment->id,

View File

@ -152,11 +152,8 @@ class DirectDebit implements MethodInterface
public function paymentResponse(PaymentResponseRequest $request)
{
$token = ClientGatewayToken::find(
$this->decodePrimaryKey($request->source)
)->firstOrFail();
$this->go_cardless->ensureMandateIsReady($token);
$this->go_cardless->ensureMandateIsReady($request->source);
try {
$payment = $this->go_cardless->gateway->payments()->create([
@ -167,14 +164,14 @@ class DirectDebit implements MethodInterface
'payment_hash' => $this->go_cardless->payment_hash->hash,
],
'links' => [
'mandate' => $token->token,
'mandate' => $request->source,
],
],
]);
if ($payment->status === 'pending_submission') {
return $this->processPendingPayment($payment, ['token' => $token->hashed_id]);
return $this->processPendingPayment($payment, ['token' => $request->source]);
}
return $this->processUnsuccessfulPayment($payment);
@ -193,7 +190,6 @@ class DirectDebit implements MethodInterface
public function processPendingPayment(\GoCardlessPro\Resources\Payment $payment, array $data = [])
{
$data = [
'payment_method' => $data['token'],
'payment_type' => PaymentType::DIRECT_DEBIT,
'amount' => $this->go_cardless->payment_hash->data->amount_with_fee,
'transaction_reference' => $payment->id,

View File

@ -160,11 +160,7 @@ class SEPA implements MethodInterface
*/
public function paymentResponse(PaymentResponseRequest $request)
{
$token = ClientGatewayToken::find(
$this->decodePrimaryKey($request->source)
)->firstOrFail();
$this->go_cardless->ensureMandateIsReady($token);
$this->go_cardless->ensureMandateIsReady($request->source);
try {
$payment = $this->go_cardless->gateway->payments()->create([
@ -175,13 +171,13 @@ class SEPA implements MethodInterface
'payment_hash' => $this->go_cardless->payment_hash->hash,
],
'links' => [
'mandate' => $token->token,
'mandate' => $request->source,
],
],
]);
if ($payment->status === 'pending_submission') {
return $this->processPendingPayment($payment, ['token' => $token->hashed_id]);
return $this->processPendingPayment($payment, ['token' => $request->source]);
}
return $this->processUnsuccessfulPayment($payment);
@ -200,7 +196,6 @@ class SEPA implements MethodInterface
public function processPendingPayment(\GoCardlessPro\Resources\Payment $payment, array $data = [])
{
$data = [
'payment_method' => $data['token'],
'payment_type' => PaymentType::SEPA,
'amount' => $this->go_cardless->payment_hash->data->amount_with_fee,
'transaction_reference' => $payment->id,

View File

@ -265,10 +265,11 @@ class GoCardlessPaymentDriver extends BaseDriver
return response()->json([], 200);
}
public function ensureMandateIsReady(ClientGatewayToken $cgt)
public function ensureMandateIsReady($token)
{
try {
$mandate = $this->gateway->mandates()->get($cgt->token);
$this->init();
$mandate = $this->gateway->mandates()->get($token);
if ($mandate->status !== 'active') {
throw new \Exception(ctrans('texts.gocardless_mandate_not_ready'));

View File

@ -86,7 +86,9 @@ class Bancontact implements MethodInterface
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
'metadata' => [
'client_id' => $this->mollie->client->hashed_id,
'hash' => $this->mollie->payment_hash->hash
'hash' => $this->mollie->payment_hash->hash,
'gateway_type_id' => GatewayType::BANCONTACT,
'payment_type_id' => PaymentType::BANCONTACT,
],
]);

View File

@ -89,7 +89,9 @@ class BankTransfer implements MethodInterface
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
'metadata' => [
'client_id' => $this->mollie->client->hashed_id,
'hash' => $this->mollie->payment_hash->hash
'hash' => $this->mollie->payment_hash->hash,
'gateway_type_id' => GatewayType::BANK_TRANSFER,
'payment_type_id' => PaymentType::MOLLIE_BANK_TRANSFER,
],
]);

View File

@ -114,7 +114,7 @@ class CreditCard
'name' => $this->mollie->client->name,
'email' => $this->mollie->client->present()->email(),
'metadata' => [
'id' => $this->mollie->client->hashed_id,
'id' => $this->mollie->client->hashed_id
],
]);

View File

@ -86,7 +86,9 @@ class IDEAL implements MethodInterface
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
'metadata' => [
'client_id' => $this->mollie->client->hashed_id,
'hash' => $this->mollie->payment_hash->hash
'hash' => $this->mollie->payment_hash->hash,
'gateway_type_id' => GatewayType::IDEAL,
'payment_type_id' => PaymentType::IDEAL,
],
]);

View File

@ -86,7 +86,9 @@ class KBC implements MethodInterface
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
'metadata' => [
'client_id' => $this->mollie->client->hashed_id,
'hash' => $this->mollie->payment_hash->hash
'hash' => $this->mollie->payment_hash->hash,
'gateway_type_id' => GatewayType::KBC,
'payment_type_id' => PaymentType::KBC,
],
]);

View File

@ -312,10 +312,31 @@ class MolliePaymentDriver extends BaseDriver
$client = $record->client;
}
else{
nlog("mollie webhook");
nlog($payment);
$client = Client::withTrashed()->find($this->decodePrimaryKey($payment->metadata->client_id));
// sometimes if the user is not returned to the site with a response from Mollie
// we may not have a payment record - in these cases we need to re-construct the payment
// record from the meta data in the payment hash.
if($payment && property_exists($payment->metadata, 'payment_hash') && $payment->metadata->payment_hash){
/* Harvest Payment Hash*/
$payment_hash = PaymentHash::where('hash', $payment->metadata->hash)->first();
$data = [
'gateway_type_id' => $payment->metadata->gateway_type_id,
'amount' => $amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total,
'payment_type' => $payment->metadata->payment_type_id,
'transaction_reference' => $payment->id,
];
$record = $this->createPayment(
$data,
$codes[$payment->status]
);
}
}
$message = [

View File

@ -136,6 +136,7 @@ class SquarePaymentDriver extends BaseDriver
$amount_money->setCurrency($this->client->currency()->code);
$body = new \Square\Models\CreatePaymentRequest($cgt->token, \Illuminate\Support\Str::random(32), $amount_money);
$body->setCustomerId($cgt->gateway_customer_reference);
/** @var ApiResponse */
$response = $this->square->getPaymentsApi()->createPayment($body);

View File

@ -56,7 +56,6 @@ class Charge
if($cgt->gateway_type_id == GatewayType::BANK_TRANSFER)
return (new ACH($this->stripe))->tokenBilling($cgt, $payment_hash);
nlog(" DB = ".$this->stripe->client->company->db);
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->withTrashed()->first();
@ -120,7 +119,6 @@ class Charge
break;
}
$this->stripe->processInternallyFailedPayment($this->stripe, $e);
SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client, $this->stripe->client->company);

View File

@ -123,7 +123,7 @@ class CreditCard
$data = [
'payment_method' => $this->stripe->payment_hash->data->server_response->payment_method,
'payment_type' => PaymentType::parseCardType(strtolower($stripe_method->card->brand)),
'payment_type' => PaymentType::parseCardType(strtolower($stripe_method->card->brand)) ?: PaymentType::CREDIT_CARD_OTHER,
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->server_response->amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
'transaction_reference' => optional($this->stripe->payment_hash->data->payment_intent->charges->data[0])->id,
'gateway_type_id' => GatewayType::CREDIT_CARD,

View File

@ -436,7 +436,7 @@ class StripePaymentDriver extends BaseDriver
//Else create a new record
$data['name'] = $this->client->present()->name();
$data['phone'] = $this->client->present()->phone();
$data['phone'] = substr($this->client->present()->phone(), 0 , 20);
if (filter_var($this->client->present()->email(), FILTER_VALIDATE_EMAIL)) {
$data['email'] = $this->client->present()->email();

View File

@ -54,6 +54,37 @@ class TaskRepository extends BaseRepository
$task->status_order = $data['status_order'];
}
/*V4 override*/
if (! empty($data['time_details'])) {
$timeLog = [];
foreach ($data['time_details'] as $detail) {
$startTime = strtotime($detail['start_datetime']);
$endTime = false;
if (! empty($detail['end_datetime'])) {
$endTime = strtotime($detail['end_datetime']);
} else {
$duration = 0;
if (! empty($detail['duration_seconds'])) {
$duration += $detail['duration_seconds'];
}
if (! empty($detail['duration_minutes'])) {
$duration += $detail['duration_minutes'] * 60;
}
if (! empty($detail['duration_hours'])) {
$duration += $detail['duration_hours'] * 60 * 60;
}
if ($duration) {
$endTime = $startTime + $duration;
}
}
$timeLog[] = [$startTime, $endTime];
if (! $endTime) {
$data['is_running'] = true;
}
}
$data['time_log'] = json_encode($timeLog);
}
if (isset($data['time_log'])) {
$time_log = json_decode($data['time_log']);
} elseif ($task->time_log) {

View File

@ -30,6 +30,7 @@ use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\Pdf\PdfMaker as PdfMakerTrait;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\LazyCollection;
class Statement
{
@ -217,7 +218,7 @@ class Statement
*
* @return Invoice[]|\Illuminate\Database\Eloquent\Collection
*/
protected function getInvoices(): Collection
protected function getInvoices(): LazyCollection
{
return Invoice::withTrashed()
->where('is_deleted', false)
@ -226,7 +227,7 @@ class Statement
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID])
->whereBetween('date', [$this->options['start_date'], $this->options['end_date']])
->orderBy('number', 'ASC')
->get();
->cursor();
}
/**
@ -234,7 +235,7 @@ class Statement
*
* @return Payment[]|\Illuminate\Database\Eloquent\Collection
*/
protected function getPayments(): Collection
protected function getPayments(): LazyCollection
{
return Payment::withTrashed()
->with('client.country','invoices')
@ -244,7 +245,7 @@ class Statement
->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
->whereBetween('date', [$this->options['start_date'], $this->options['end_date']])
->orderBy('number', 'ASC')
->get();
->cursor();
}
/**

View File

@ -36,17 +36,6 @@ class TriggeredActions extends AbstractService
public function run()
{
// if ($this->request->has('auto_bill') && $this->request->input('auto_bill') == 'true') {
// $this->credit = $this->credit->service()->autoBill()->save();
// }
// if ($this->request->has('paid') && $this->request->input('paid') == 'true') {
// $this->credit = $this->credit->service()->markPaid()->save();
// }
// if ($this->request->has('amount_paid') && is_numeric($this->request->input('amount_paid')) ) {
// $this->credit = $this->credit->service()->applyPaymentAmount($this->request->input('amount_paid'))->save();
// }
if ($this->request->has('send_email') && $this->request->input('send_email') == 'true') {
$this->sendEmail();

View File

@ -105,7 +105,7 @@ class AddGatewayFee extends AbstractService
$invoice_item->quantity = 1;
$invoice_item->cost = $gateway_fee;
if ($fees_and_limits = $this->company_gateway->getFeesAndLimits()) {
if ($fees_and_limits = $this->company_gateway->getFeesAndLimits($this->gateway_type_id)) {
$invoice_item->tax_rate1 = $fees_and_limits->fee_tax_rate1;
$invoice_item->tax_rate2 = $fees_and_limits->fee_tax_rate2;
$invoice_item->tax_rate3 = $fees_and_limits->fee_tax_rate3;

View File

@ -130,7 +130,7 @@ class AutoBillInvoice extends AbstractService
info("Auto Bill payment captured for ".$this->invoice->number);
}
return $this->invoice->fresh();
// return $this->invoice->fresh();
}
/**

View File

@ -138,7 +138,8 @@ class InvoiceService
{
// $this->invoice = (new UpdateBalance($this->invoice, $balance_adjustment, $is_draft))->run();
if ($this->invoice->is_deleted) {
if ((bool)$this->invoice->is_deleted !== false) {
nlog($this->invoice->number . " is deleted returning");
return $this;
}
@ -245,7 +246,7 @@ class InvoiceService
public function autoBill()
{
$this->invoice = (new AutoBillInvoice($this->invoice, $this->invoice->company->db))->run();
(new AutoBillInvoice($this->invoice, $this->invoice->company->db))->run();
return $this;
}
@ -483,6 +484,10 @@ class InvoiceService
if(!isset($this->invoice->exchange_rate) && $this->invoice->client->currency()->id != (int) $this->invoice->company->settings->currency_id)
$this->invoice->exchange_rate = $this->invoice->client->currency()->exchange_rate;
if($settings->counter_number_applied == 'when_saved'){
$this->invoice->service()->applyNumber()->save();
}
return $this;
}

View File

@ -37,7 +37,7 @@ class TriggeredActions extends AbstractService
public function run()
{
if ($this->request->has('auto_bill') && $this->request->input('auto_bill') == 'true') {
$this->invoice = $this->invoice->service()->autoBill()->save();
$this->invoice->service()->autoBill();
}
if ($this->request->has('paid') && $this->request->input('paid') == 'true') {

View File

@ -124,10 +124,10 @@ class QuoteService
}
if ($this->quote->client->getSetting('auto_archive_quote')) {
$quote_repo = new QuoteRepository();
$quote_repo->archive($this->quote);
}
// if ($this->quote->client->getSetting('auto_archive_quote')) {
// $quote_repo = new QuoteRepository();
// $quote_repo->archive($this->quote);
// }
return $this;
}

View File

@ -57,7 +57,7 @@ class TriggeredActions extends AbstractService
{
$reminder_template = $this->quote->calculateTemplate('quote');
//$reminder_template = 'payment';
// $reminder_template = 'email_template_quote';
$this->quote->invitations->load('contact.client.country', 'quote.client.country', 'quote.company')->each(function ($invitation) use ($reminder_template) {
EmailEntity::dispatch($invitation, $this->quote->company, $reminder_template);

View File

@ -77,8 +77,6 @@ class SubscriptionService
$recurring_invoice_repo = new RecurringInvoiceRepository();
$recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice);
// $recurring_invoice->next_send_date = now()->format('Y-m-d');
// $recurring_invoice->next_send_date = $recurring_invoice->nextSendDate();
$recurring_invoice->auto_bill = $this->subscription->auto_bill;
/* Start the recurring service */
@ -87,7 +85,6 @@ class SubscriptionService
->save();
//execute any webhooks
$context = [
'context' => 'recurring_purchase',
'recurring_invoice' => $recurring_invoice->hashed_id,
@ -95,6 +92,7 @@ class SubscriptionService
'client' => $recurring_invoice->client->hashed_id,
'subscription' => $this->subscription->hashed_id,
'contact' => auth('contact')->user()->hashed_id,
'account_key' => $recurring_invoice->client->custom_value2,
];
$response = $this->triggerWebhook($context);
@ -111,6 +109,7 @@ class SubscriptionService
'invoice' => $this->encodePrimaryKey($payment_hash->fee_invoice_id),
'client' => $invoice->client->hashed_id,
'subscription' => $this->subscription->hashed_id,
'account_key' => $invoice->client->custom_value2,
];
//execute any webhooks
@ -130,6 +129,7 @@ class SubscriptionService
'contact' => $contact->hashed_id,
'contact_email' => $contact->email,
'client' => $contact->client->hashed_id,
'account_key' => $contact->client->custom_value2,
];
$response = $this->triggerWebhook($context);
@ -180,6 +180,7 @@ class SubscriptionService
'recurring_invoice' => $recurring_invoice->hashed_id,
'client' => $recurring_invoice->client->hashed_id,
'subscription' => $this->subscription->hashed_id,
'account_key' => $recurring_invoice->client->custom_value2,
];
//execute any webhooks
@ -452,6 +453,7 @@ class SubscriptionService
'client' => $new_recurring_invoice->client->hashed_id,
'subscription' => $target_subscription->hashed_id,
'contact' => auth('contact')->user()->hashed_id,
'account_key' => $new_recurring_invoice->client->custom_value2,
];
$response = $this->triggerWebhook($context);
@ -572,6 +574,7 @@ class SubscriptionService
'client' => $recurring_invoice->client->hashed_id,
'subscription' => $this->subscription->hashed_id,
'contact' => auth('contact')->user()->hashed_id,
'account_key' => $recurring_invoice->client->custom_value2,
];
@ -768,8 +771,6 @@ class SubscriptionService
$response = false;
$body = array_merge($context, [
'company_key' => $this->subscription->company->company_key,
'account_key' => $this->subscription->company->account->key,
'db' => $this->subscription->company->db,
]);
@ -921,6 +922,7 @@ class SubscriptionService
'recurring_invoice' => $recurring_invoice->hashed_id,
'client' => $recurring_invoice->client->hashed_id,
'contact' => auth('contact')->user()->hashed_id,
'account_key' => $recurring_invoice->client->custom_value2,
];
$this->triggerWebhook($context);
@ -1043,6 +1045,7 @@ class SubscriptionService
'client' => $invoice->client->hashed_id,
'contact' => $invoice->client->primary_contact()->first() ? $invoice->client->primary_contact()->first()->hashed_id: $invoice->client->contacts->first()->hashed_id,
'invoice' => $invoice->hashed_id,
'account_key' => $invoice->client->custom_value2,
];
$response = $this->triggerWebhook($context);

View File

@ -11,6 +11,12 @@
namespace App\Utils\Traits\Notifications;
use App\Models\Client;
use App\Models\Credit;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Quote;
/**
* Class UserNotifies.
*
@ -22,53 +28,100 @@ trait UserNotifies
{
public function findUserNotificationTypes($invitation, $company_user, $entity_name, $required_permissions) :array
{
if ($company_user->company->is_disabled) {
return [];
}
$notifiable_methods = [];
$notifications = $company_user->notifications;
if ($company_user->company->is_disabled && is_array($notifications->email)) {
return [];
}
//if a user owns this record or is assigned to it, they are attached the permission for notification.
if ($invitation->{$entity_name}->user_id == $company_user->_user_id || $invitation->{$entity_name}->assigned_user_id == $company_user->user_id) {
array_push($required_permissions, 'all_user_notifications');
$required_permissions = $this->addSpecialUserPermissionForEntity($invitation->{$entity_name}, $required_permissions);
}
else{
$required_permissions = $this->removeSpecialUserPermissionForEntity($invitation->{$entity_name}, $required_permissions);
}
if (count(array_intersect($required_permissions, $notifications->email)) >= 1 || count(array_intersect(['all_user_notifications'], $notifications->email)) >= 1 || count(array_intersect(['all_notifications'],$notifications->email)) >= 1) {
if (count(array_intersect($required_permissions, $notifications->email)) >= 1) {
array_push($notifiable_methods, 'mail');
}
// if(count(array_intersect($required_permissions, $notifications->slack)) >=1)
// array_push($notifiable_methods, 'slack');
return $notifiable_methods;
}
public function findUserEntityNotificationType($entity, $company_user, $required_permissions) :array
public function findUserEntityNotificationType($entity, $company_user, array $required_permissions) :array
{
if ($company_user->company->is_disabled) {
return [];
}
$notifiable_methods = [];
$notifications = $company_user->notifications;
if (! $notifications) {
if ($company_user->company->is_disabled || ! $notifications) {
return [];
}
if ($entity->user_id == $company_user->_user_id || $entity->assigned_user_id == $company_user->user_id) {
array_push($required_permissions, 'all_user_notifications');
$required_permissions = $this->addSpecialUserPermissionForEntity($entity, $required_permissions);
}
else{
$required_permissions = $this->removeSpecialUserPermissionForEntity($entity, $required_permissions);
}
if (count(array_intersect($required_permissions, $notifications->email)) >= 1 || count(array_intersect(['all_user_notifications'], $notifications->email)) >= 1 || count(array_intersect(['all_notifications'],$notifications->email)) >= 1) {
if (count(array_intersect($required_permissions, $notifications->email)) >= 1) {
array_push($notifiable_methods, 'mail');
}
return $notifiable_methods;
}
private function addSpecialUserPermissionForEntity($entity, array $required_permissions) :array
{
array_merge($required_permissions, ["all_notifications"]);
switch ($entity) {
case ($entity instanceof Payment || $entity instanceof Client): //we pass client also as this is the proxy for Payment Failures (ie, there is no payment)
return array_merge($required_permissions, ["all_notifications","all_user_notifications","payment_failure_user","payment_success_user"]);
break;
case ($entity instanceof Invoice):
return array_merge($required_permissions, ["all_notifications","all_user_notifications","invoice_created_user","invoice_sent_user","invoice_viewed_user","invoice_late_user"]);
break;
case ($entity instanceof Quote):
return array_merge($required_permissions, ["all_notifications","all_user_notifications","quote_created_user","quote_sent_user","quote_viewed_user","quote_approved_user","quote_expired_user"]);
break;
case ($entity instanceof Credit):
return array_merge($required_permissions, ["all_notifications","all_user_notifications","credit_created_user","credit_sent_user","credit_viewed_user"]);
break;
default:
return [];
break;
}
}
private function removeSpecialUserPermissionForEntity($entity, $required_permissions)
{
array_merge($required_permissions, ["all_notifications"]);
switch ($entity) {
case ($entity instanceof Payment || $entity instanceof Client): //we pass client also as this is the proxy for Payment Failures (ie, there is no payment)
return array_diff($required_permissions, ["all_user_notifications","payment_failure_user","payment_success_user"]);
break;
case ($entity instanceof Invoice):
return array_diff($required_permissions, ["all_user_notifications","invoice_created_user","invoice_sent_user","invoice_viewed_user","invoice_late_user"]);
break;
case ($entity instanceof Quote):
return array_diff($required_permissions, ["all_user_notifications","quote_created_user","quote_sent_user","quote_viewed_user","quote_approved_user","quote_expired_user"]);
break;
case ($entity instanceof Credit):
return array_diff($required_permissions, ["all_user_notifications","credit_created_user","credit_sent_user","credit_viewed_user"]);
break;
default:
// code...
break;
}
}
public function findCompanyUserNotificationType($company_user, $required_permissions) :array
{

View File

@ -26,7 +26,7 @@
],
"type": "project",
"require": {
"php": "^7.4|^8.0",
"php": "^7.4|^8",
"ext-dom": "*",
"ext-json": "*",
"ext-libxml": "*",

715
composer.lock generated

File diff suppressed because it is too large Load Diff

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.3.33',
'app_tag' => '5.3.33',
'app_version' => '5.3.34',
'app_tag' => '5.3.34',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),

View File

@ -34,6 +34,9 @@ CREATE TABLE `accounts` (
`updated_at` timestamp(6) NULL DEFAULT NULL,
`is_scheduler_running` tinyint(1) NOT NULL DEFAULT '0',
`trial_duration` int(10) unsigned DEFAULT NULL,
`is_onboarding` tinyint(1) NOT NULL DEFAULT '0',
`onboarding` mediumtext COLLATE utf8mb4_unicode_ci,
`is_migrated` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `accounts_payment_id_index` (`payment_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
@ -66,6 +69,8 @@ CREATE TABLE `activities` (
`quote_id` int(10) unsigned DEFAULT NULL,
`subscription_id` int(10) unsigned DEFAULT NULL,
`recurring_invoice_id` int(10) unsigned DEFAULT NULL,
`recurring_expense_id` int(10) unsigned DEFAULT NULL,
`recurring_quote_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `activities_vendor_id_company_id_index` (`vendor_id`,`company_id`),
KEY `activities_project_id_company_id_index` (`project_id`,`company_id`),
@ -93,6 +98,7 @@ CREATE TABLE `backups` (
`created_at` timestamp(6) NULL DEFAULT NULL,
`updated_at` timestamp(6) NULL DEFAULT NULL,
`amount` decimal(16,4) NOT NULL,
`filename` text COLLATE utf8mb4_unicode_ci,
PRIMARY KEY (`id`),
KEY `backups_activity_id_foreign` (`activity_id`),
CONSTRAINT `backups_activity_id_foreign` FOREIGN KEY (`activity_id`) REFERENCES `activities` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
@ -378,6 +384,7 @@ CREATE TABLE `companies` (
`markdown_enabled` tinyint(1) NOT NULL DEFAULT '1',
`use_comma_as_decimal_place` tinyint(1) NOT NULL DEFAULT '0',
`report_include_drafts` tinyint(1) NOT NULL DEFAULT '0',
`client_registration_fields` mediumtext COLLATE utf8mb4_unicode_ci,
PRIMARY KEY (`id`),
UNIQUE KEY `companies_company_key_unique` (`company_key`),
KEY `companies_industry_id_foreign` (`industry_id`),
@ -1371,6 +1378,69 @@ CREATE TABLE `quotes` (
CONSTRAINT `quotes_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `recurring_expenses`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `recurring_expenses` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`created_at` timestamp(6) NULL DEFAULT NULL,
`updated_at` timestamp(6) NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
`company_id` int(10) unsigned NOT NULL,
`vendor_id` int(10) unsigned DEFAULT NULL,
`user_id` int(10) unsigned NOT NULL,
`status_id` int(10) unsigned NOT NULL,
`invoice_id` int(10) unsigned DEFAULT NULL,
`client_id` int(10) unsigned DEFAULT NULL,
`bank_id` int(10) unsigned DEFAULT NULL,
`project_id` int(10) unsigned DEFAULT NULL,
`payment_type_id` int(10) unsigned DEFAULT NULL,
`recurring_expense_id` int(10) unsigned DEFAULT NULL,
`is_deleted` tinyint(1) NOT NULL DEFAULT '0',
`uses_inclusive_taxes` tinyint(1) NOT NULL DEFAULT '1',
`tax_name1` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`tax_name2` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`tax_name3` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`date` date DEFAULT NULL,
`payment_date` date DEFAULT NULL,
`should_be_invoiced` tinyint(1) NOT NULL DEFAULT '0',
`invoice_documents` tinyint(1) NOT NULL DEFAULT '0',
`transaction_id` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`custom_value1` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`custom_value2` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`custom_value3` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`custom_value4` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`category_id` int(10) unsigned DEFAULT NULL,
`calculate_tax_by_amount` tinyint(1) NOT NULL DEFAULT '0',
`tax_amount1` decimal(20,6) DEFAULT NULL,
`tax_amount2` decimal(20,6) DEFAULT NULL,
`tax_amount3` decimal(20,6) DEFAULT NULL,
`tax_rate1` decimal(20,6) DEFAULT NULL,
`tax_rate2` decimal(20,6) DEFAULT NULL,
`tax_rate3` decimal(20,6) DEFAULT NULL,
`amount` decimal(20,6) DEFAULT NULL,
`foreign_amount` decimal(20,6) DEFAULT NULL,
`exchange_rate` decimal(20,6) NOT NULL DEFAULT '1.000000',
`assigned_user_id` int(10) unsigned DEFAULT NULL,
`number` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`invoice_currency_id` int(10) unsigned DEFAULT NULL,
`currency_id` int(10) unsigned DEFAULT NULL,
`private_notes` text COLLATE utf8mb4_unicode_ci,
`public_notes` text COLLATE utf8mb4_unicode_ci,
`transaction_reference` text COLLATE utf8mb4_unicode_ci,
`frequency_id` int(10) unsigned NOT NULL,
`last_sent_date` datetime DEFAULT NULL,
`next_send_date` datetime DEFAULT NULL,
`remaining_cycles` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `recurring_expenses_company_id_number_unique` (`company_id`,`number`),
KEY `recurring_expenses_company_id_deleted_at_index` (`company_id`,`deleted_at`),
KEY `recurring_expenses_user_id_foreign` (`user_id`),
KEY `recurring_expenses_company_id_index` (`company_id`),
CONSTRAINT `recurring_expenses_company_id_foreign` FOREIGN KEY (`company_id`) REFERENCES `companies` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `recurring_expenses_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `recurring_invoice_invitations`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
@ -1481,6 +1551,41 @@ CREATE TABLE `recurring_invoices` (
CONSTRAINT `recurring_invoices_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `recurring_quote_invitations`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `recurring_quote_invitations` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`company_id` int(10) unsigned NOT NULL,
`user_id` int(10) unsigned NOT NULL,
`client_contact_id` int(10) unsigned NOT NULL,
`recurring_quote_id` int(10) unsigned NOT NULL,
`key` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`transaction_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`message_id` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`email_error` mediumtext COLLATE utf8mb4_unicode_ci,
`signature_base64` text COLLATE utf8mb4_unicode_ci,
`signature_date` datetime DEFAULT NULL,
`sent_date` datetime DEFAULT NULL,
`viewed_date` datetime DEFAULT NULL,
`opened_date` datetime DEFAULT NULL,
`email_status` enum('delivered','bounced','spam') COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp(6) NULL DEFAULT NULL,
`updated_at` timestamp(6) NULL DEFAULT NULL,
`deleted_at` timestamp(6) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `cli_rec_q` (`client_contact_id`,`recurring_quote_id`),
KEY `recurring_quote_invitations_user_id_foreign` (`user_id`),
KEY `recurring_quote_invitations_company_id_foreign` (`company_id`),
KEY `rec_co_del_q` (`deleted_at`,`recurring_quote_id`,`company_id`),
KEY `recurring_quote_invitations_recurring_quote_id_index` (`recurring_quote_id`),
KEY `recurring_quote_invitations_key_index` (`key`),
CONSTRAINT `recurring_quote_invitations_client_contact_id_foreign` FOREIGN KEY (`client_contact_id`) REFERENCES `client_contacts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `recurring_quote_invitations_company_id_foreign` FOREIGN KEY (`company_id`) REFERENCES `companies` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `recurring_quote_invitations_recurring_quote_id_foreign` FOREIGN KEY (`recurring_quote_id`) REFERENCES `recurring_invoices` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `recurring_quote_invitations_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `recurring_quotes`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
@ -1521,13 +1626,29 @@ CREATE TABLE `recurring_quotes` (
`balance` decimal(20,6) NOT NULL DEFAULT '0.000000',
`last_viewed` datetime DEFAULT NULL,
`frequency_id` int(10) unsigned NOT NULL,
`start_date` date DEFAULT NULL,
`last_sent_date` datetime DEFAULT NULL,
`next_send_date` datetime DEFAULT NULL,
`remaining_cycles` int(10) unsigned DEFAULT NULL,
`created_at` timestamp(6) NULL DEFAULT NULL,
`updated_at` timestamp(6) NULL DEFAULT NULL,
`deleted_at` timestamp(6) NULL DEFAULT NULL,
`auto_bill` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'off',
`auto_bill_enabled` tinyint(1) NOT NULL DEFAULT '0',
`paid_to_date` decimal(20,6) NOT NULL DEFAULT '0.000000',
`custom_surcharge1` decimal(20,6) DEFAULT NULL,
`custom_surcharge2` decimal(20,6) DEFAULT NULL,
`custom_surcharge3` decimal(20,6) DEFAULT NULL,
`custom_surcharge4` decimal(20,6) DEFAULT NULL,
`custom_surcharge_tax1` tinyint(1) NOT NULL DEFAULT '0',
`custom_surcharge_tax2` tinyint(1) NOT NULL DEFAULT '0',
`custom_surcharge_tax3` tinyint(1) NOT NULL DEFAULT '0',
`custom_surcharge_tax4` tinyint(1) NOT NULL DEFAULT '0',
`due_date_days` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`exchange_rate` decimal(13,6) NOT NULL DEFAULT '1.000000',
`partial` decimal(16,4) DEFAULT NULL,
`partial_due_date` date DEFAULT NULL,
`subscription_id` int(10) unsigned DEFAULT NULL,
`uses_inclusive_taxes` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`),
KEY `recurring_quotes_company_id_deleted_at_index` (`company_id`,`deleted_at`),
KEY `recurring_quotes_user_id_foreign` (`user_id`),
@ -1973,3 +2094,25 @@ INSERT INTO `migrations` VALUES (91,'2021_08_10_034407_add_more_languages',4);
INSERT INTO `migrations` VALUES (92,'2021_08_18_220124_use_comma_as_decimal_place_companies_table',4);
INSERT INTO `migrations` VALUES (93,'2021_08_24_115919_update_designs',4);
INSERT INTO `migrations` VALUES (94,'2021_08_25_093105_report_include_drafts_in_companies_table',5);
INSERT INTO `migrations` VALUES (95,'2021_08_14_054458_square_payment_driver',6);
INSERT INTO `migrations` VALUES (96,'2021_08_23_101529_recurring_expenses_schema',6);
INSERT INTO `migrations` VALUES (97,'2021_09_05_101209_update_braintree_gateway',6);
INSERT INTO `migrations` VALUES (98,'2021_09_20_233053_set_square_test_mode_boolean',6);
INSERT INTO `migrations` VALUES (99,'2021_09_23_100629_add_currencies',6);
INSERT INTO `migrations` VALUES (100,'2021_09_24_201319_add_mollie_bank_transfer_to_payment_types',6);
INSERT INTO `migrations` VALUES (101,'2021_09_24_211504_add_kbc_to_payment_types',6);
INSERT INTO `migrations` VALUES (102,'2021_09_24_213858_add_bancontact_to_payment_types',6);
INSERT INTO `migrations` VALUES (103,'2021_09_28_154647_activate_gocardless_payment_driver',6);
INSERT INTO `migrations` VALUES (104,'2021_09_29_190258_add_required_client_registration_fields',6);
INSERT INTO `migrations` VALUES (105,'2021_10_04_134908_add_ideal_to_payment_types',6);
INSERT INTO `migrations` VALUES (106,'2021_10_06_044800_updated_bold_and_modern_designs',6);
INSERT INTO `migrations` VALUES (107,'2021_10_07_141737_razorpay',6);
INSERT INTO `migrations` VALUES (108,'2021_10_07_155410_add_hosted_page_to_payment_types',6);
INSERT INTO `migrations` VALUES (109,'2021_10_15_00000_stripe_payment_gateways',6);
INSERT INTO `migrations` VALUES (110,'2021_10_16_135200_add_direct_debit_to_payment_types',6);
INSERT INTO `migrations` VALUES (111,'2021_10_19_142200_add_gateway_type_for_direct_debit',6);
INSERT INTO `migrations` VALUES (112,'2021_10_20_005529_add_filename_to_backups_table',6);
INSERT INTO `migrations` VALUES (113,'2021_11_08_131308_onboarding',6);
INSERT INTO `migrations` VALUES (114,'2021_11_09_115919_update_designs',6);
INSERT INTO `migrations` VALUES (115,'2021_11_10_184847_add_is_migrate_column_to_accounts_table',6);
INSERT INTO `migrations` VALUES (116,'2021_11_11_163121_add_instant_bank_transfer',7);

View File

@ -44,7 +44,7 @@ class PaymentLibrariesSeeder extends Seeder
['id' => 17, 'name' => 'Pin', 'provider' => 'Pin', 'key' => '0749cb92a6b36c88bd9ff8aabd2efcab', 'fields' => '{"secretKey":"","testMode":false}'],
['id' => 18, 'name' => 'SagePay Direct', 'provider' => 'SagePay_Direct', 'key' => '4c8f4e5d0f353a122045eb9a60cc0f2d', 'fields' => '{"vendor":"","testMode":false,"referrerId":""}'],
['id' => 19, 'name' => 'SecurePay DirectPost', 'provider' => 'SecurePay_DirectPost', 'key' => '8036a5aadb2bdaafb23502da8790b6a2', 'fields' => '{"merchantId":"","transactionPassword":"","testMode":false,"enable_ach":"","enable_sofort":"","enable_apple_pay":"","enable_alipay":""}'],
['id' => 20, 'name' => 'Stripe', 'provider' => 'Stripe', 'sort_order' => 1, 'key' => 'd14dd26a37cecc30fdd65700bfb55b23', 'fields' => '{"apiKey":"", "publishableKey":""}'],
['id' => 20, 'name' => 'Stripe', 'provider' => 'Stripe', 'sort_order' => 1, 'key' => 'd14dd26a37cecc30fdd65700bfb55b23', 'fields' => '{"publishableKey":"","apiKey":""}'],
['id' => 21, 'name' => 'TargetPay Direct eBanking', 'provider' => 'TargetPay_Directebanking', 'key' => 'd14dd26a37cdcc30fdd65700bfb55b23', 'fields' => '{"subAccountId":""}'],
['id' => 22, 'name' => 'TargetPay Ideal', 'provider' => 'TargetPay_Ideal', 'key' => 'ea3b328bd72d381387281c3bd83bd97c', 'fields' => '{"subAccountId":""}'],
['id' => 23, 'name' => 'TargetPay Mr Cash', 'provider' => 'TargetPay_Mrcash', 'key' => 'a0035fc0d87c4950fb82c73e2fcb825a', 'fields' => '{"subAccountId":""}'],

View File

@ -18,7 +18,7 @@
<php>
<env name="APP_ENV" value="testing"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="CACHE_DRIVER" value="file"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="MAIL_MAILER" value="array"/>

View File

@ -6543,6 +6543,34 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--------------------------------------------------------------------------------
diacritic
Copyright (c) 2016, Agilord.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
double-conversion
icu

View File

@ -3,38 +3,38 @@ const MANIFEST = 'flutter-app-manifest';
const TEMP = 'flutter-temp-cache';
const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = {
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
"version.json": "9c7b0edc83733da56c726678aacd9fd3",
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
"favicon.ico": "51636d3a390451561744c42188ccd628",
"version.json": "f01b70a1dd8b65dd69abd55a4fe54b83",
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
"main.dart.js": "03d6faff8d468318196cd0bef8a4c9a6",
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
"favicon.ico": "51636d3a390451561744c42188ccd628",
"main.dart.js": "40a468819694baa11ae02390609712fc",
"/": "38d8084156eb614c31d736ce8c9bd255",
"assets/NOTICES": "5a96be85b952e4fcd3a6965546c85b7f",
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "015400679694f1f51047e46da0e1dc98",
"assets/AssetManifest.json": "38d9aea341601f3a5c6fa7b5a1216ea5",
"assets/fonts/MaterialIcons-Regular.otf": "4e6447691c9509f7acdbf8a931a85ca1",
"/": "c2e105029a9aed730c6480128daf2ced",
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
"assets/fonts/MaterialIcons-Regular.otf": "4e6447691c9509f7acdbf8a931a85ca1",
"assets/AssetManifest.json": "38d9aea341601f3a5c6fa7b5a1216ea5",
"assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219",
"assets/assets/images/logo_dark.png": "a233ed1d4d0f7414bf97a9a10f11fb0a",
"assets/assets/images/icon.png": "090f69e23311a4b6d851b3880ae52541",
"assets/assets/images/payment_types/paypal.png": "8e06c094c1871376dfea1da8088c29d1",
"assets/assets/images/payment_types/maestro.png": "e533b92bfb50339fdbfa79e3dfe81f08",
"assets/assets/images/payment_types/solo.png": "2030c3ccaccf5d5e87916a62f5b084d6",
"assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
"assets/assets/images/payment_types/other.png": "d936e11fa3884b8c9f1bd5c914be8629",
"assets/assets/images/payment_types/unionpay.png": "7002f52004e0ab8cc0b7450b0208ccb2",
"assets/assets/images/payment_types/discover.png": "6c0a386a00307f87db7bea366cca35f5",
"assets/assets/images/payment_types/switch.png": "4fa11c45327f5fdc20205821b2cfd9cc",
"assets/assets/images/payment_types/maestro.png": "e533b92bfb50339fdbfa79e3dfe81f08",
"assets/assets/images/payment_types/jcb.png": "07e0942d16c5592118b72e74f2f7198c",
"assets/assets/images/payment_types/carteblanche.png": "d936e11fa3884b8c9f1bd5c914be8629",
"assets/assets/images/payment_types/laser.png": "b4e6e93dd35517ac429301119ff05868",
"assets/assets/images/payment_types/paypal.png": "8e06c094c1871376dfea1da8088c29d1",
"assets/assets/images/payment_types/ach.png": "7433f0aff779dc98a649b7a2daf777cf",
"assets/assets/images/payment_types/mastercard.png": "6f6cdc29ee2e22e06b1ac029cb52ef71",
"assets/assets/images/payment_types/amex.png": "c49a4247984b3732a4af50a3390aa978",
"assets/assets/images/payment_types/switch.png": "4fa11c45327f5fdc20205821b2cfd9cc",
"assets/assets/images/payment_types/other.png": "d936e11fa3884b8c9f1bd5c914be8629",
"assets/assets/images/payment_types/visa.png": "3ddc4a4d25c946e8ad7e6998f30fd4e3",
"assets/assets/images/google_logo.png": "0f118259ce403274f407f5e982e681c3"
"assets/assets/images/payment_types/discover.png": "6c0a386a00307f87db7bea366cca35f5",
"assets/assets/images/payment_types/unionpay.png": "7002f52004e0ab8cc0b7450b0208ccb2",
"assets/assets/images/payment_types/amex.png": "c49a4247984b3732a4af50a3390aa978",
"assets/assets/images/payment_types/mastercard.png": "6f6cdc29ee2e22e06b1ac029cb52ef71",
"assets/assets/images/payment_types/laser.png": "b4e6e93dd35517ac429301119ff05868",
"assets/assets/images/payment_types/solo.png": "2030c3ccaccf5d5e87916a62f5b084d6",
"assets/assets/images/google_logo.png": "0f118259ce403274f407f5e982e681c3",
"assets/assets/images/icon.png": "090f69e23311a4b6d851b3880ae52541",
"assets/assets/images/logo_dark.png": "a233ed1d4d0f7414bf97a9a10f11fb0a",
"assets/NOTICES": "4aea723d13add566ca34288c8295b0a4",
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "015400679694f1f51047e46da0e1dc98"
};
// The application shell files that are downloaded before a service worker can

192483
public/main.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

212448
public/main.foss.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

190717
public/main.html.dart.js vendored

File diff suppressed because one or more lines are too long

212320
public/main.next.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"/livewire.js":"/livewire.js?id=21fa1dd78491a49255cd"}
{"/livewire.js":"/livewire.js?id=ece4c4ab4b746f6f1739"}

View File

@ -1 +1 @@
{"app_name":"invoiceninja_flutter","version":"5.0.67","build_number":"67"}
{"app_name":"invoiceninja_flutter","version":"5.0.68","build_number":"68"}

View File

@ -113,7 +113,7 @@
table-layout: fixed;
overflow-wrap: break-word;
margin-top: 3rem;
margin-bottom: 200px;
margin-bottom: 0px;
}
[data-ref="table"]:last-child{
@ -371,20 +371,24 @@
$entity_images
<div id="footer">
<div>
<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-totals', 'statement-payment-table-totals','statement-invoice-table-totals',
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table', 'statement-aging-table-totals', 'statement-payment-table-totals'
'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'
];
tables.forEach((tableIdentifier) => {
const el =document.getElementById(tableIdentifier);
if(el && el.childElementCount === 0)el.remove()
console.log(document.getElementById(tableIdentifier));
document.getElementById(tableIdentifier)?.childElementCount === 0
? document.getElementById(tableIdentifier).style.setProperty('display', 'none', 'important')
: '';
});
});
</script>

View File

@ -351,7 +351,7 @@ $entity_images
<div class="repeating-footer" id="footer">
<p data-ref="total_table-footer">$entity_footer</p>
</div>
<script>
// Clear up space a bit, if [product-table, tasks-table, delivery-note-table] isn't present.
@ -368,3 +368,5 @@ $entity_images
});
});
</script>
</div>

View File

@ -313,20 +313,25 @@ $entity_images
<div class="repeating-footer" id="footer">
<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'
];
tables.forEach((tableIdentifier) => {
console.log(document.getElementById(tableIdentifier));
document.getElementById(tableIdentifier)?.childElementCount === 0
? document.getElementById(tableIdentifier).style.setProperty('display', 'none', 'important')
: '';
});
});
</script>
</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',
];
tables.forEach((tableIdentifier) => {
document.getElementById(tableIdentifier).childElementCount === 0
? document.getElementById(tableIdentifier).style.display = 'none'
: '';
});
});
</script>

View File

@ -303,24 +303,29 @@
<div class="repeating-header" id="header"></div>
<div class="repeating-footer" id="footer">
<p data-ref="total_table-footer">$entity_footer</p>
</div>
$entity_images
<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',
];
<div class="repeating-footer" id="footer">
<p data-ref="total_table-footer">$entity_footer</p>
tables.forEach((tableIdentifier) => {
document.getElementById(tableIdentifier).childElementCount === 0
? document.getElementById(tableIdentifier).style.display = 'none'
: '';
});
});
</script>
<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'
];
tables.forEach((tableIdentifier) => {
console.log(document.getElementById(tableIdentifier));
document.getElementById(tableIdentifier)?.childElementCount === 0
? document.getElementById(tableIdentifier).style.setProperty('display', 'none', 'important')
: '';
});
});
</script>
</div>

View File

@ -313,24 +313,30 @@
<div class="repeating-header" id="header"></div>
<div class="repeating-footer" id="footer">
<p data-ref="total_table-footer">$entity_footer</p>
</div>
$entity_images
<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',
];
<div class="repeating-footer" id="footer">
<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'
];
tables.forEach((tableIdentifier) => {
console.log(document.getElementById(tableIdentifier));
document.getElementById(tableIdentifier)?.childElementCount === 0
? document.getElementById(tableIdentifier).style.setProperty('display', 'none', 'important')
: '';
});
});
</script>
</div>
tables.forEach((tableIdentifier) => {
document.getElementById(tableIdentifier).childElementCount === 0
? document.getElementById(tableIdentifier).style.display = 'none'
: '';
});
});
</script>

View File

@ -354,30 +354,29 @@
<div class="repeating-header" id="header"></div>
<div class="repeating-footer" id="footer">
<p data-ref="total_table-footer">$entity_footer</p>
</div>
$entity_images
<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',
];
<div class="repeating-footer" id="footer">
<p data-ref="total_table-footer">$entity_footer</p>
tables.forEach((tableIdentifier) => {
document.getElementById(tableIdentifier).childElementCount === 0
? document.getElementById(tableIdentifier).style.display = 'none'
: '';
});
<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'
];
tables.forEach((tableIdentifier) => {
console.log(document.getElementById(tableIdentifier));
document.getElementById(tableIdentifier)?.childElementCount === 0
? document.getElementById(tableIdentifier).style.setProperty('display', 'none', 'important')
: '';
});
});
</script>
</div>
// If we have elements in these tables, we can change label to "Statement" & hide entity details.
if (document.querySelectorAll('#statement-payment-table > tbody, #statement-payment-table > tbody, #statement-aging-table-totals > tbody').length > 0) {
document.querySelector('.entity-label').innerText = '$statement_label';
document.querySelector('.entity-details-wrapper').style.display = 'none';
}
});
</script>

View File

@ -348,24 +348,28 @@ $entity_images
<div id="footer">
<div class="footer-content">
<div style="width: 70%;">
<div style="width: 90%">
<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-totals', 'statement-payment-table-totals','statement-invoice-table-totals',
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table', 'statement-aging-table-totals', 'statement-payment-table-totals'
];
<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'
];
tables.forEach((tableIdentifier) => {
document.getElementById(tableIdentifier).childElementCount === 0
? document.getElementById(tableIdentifier).remove()
: '';
});
tables.forEach((tableIdentifier) => {
console.log(document.getElementById(tableIdentifier));
document.getElementById(tableIdentifier)?.childElementCount === 0
? document.getElementById(tableIdentifier).style.setProperty('display', 'none', 'important')
: '';
});
</script>
});
</script>
</div>
<div class="footer-company-details-address-wrapper">
<div id="company-details"></div>

View File

@ -288,24 +288,27 @@
<div class="repeating-header" id="header"></div>
<div class="repeating-footer" id="footer">
<p data-ref="total_table-footer">$entity_footer</p>
</div>
$entity_images
<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',
];
<div class="repeating-footer" id="footer">
<p data-ref="total_table-footer">$entity_footer</p>
tables.forEach((tableIdentifier) => {
document.getElementById(tableIdentifier).childElementCount === 0
? document.getElementById(tableIdentifier).style.display = 'none'
: '';
});
});
</script>
<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'
];
tables.forEach((tableIdentifier) => {
console.log(document.getElementById(tableIdentifier));
document.getElementById(tableIdentifier)?.childElementCount === 0
? document.getElementById(tableIdentifier).style.setProperty('display', 'none', 'important')
: '';
});
});
</script>
</div>

Some files were not shown because too many files have changed in this diff Show More