mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-11-04 01:27:31 -05:00 
			
		
		
		
	Merge branch 'v5-develop' of https://github.com/invoiceninja/invoiceninja into feature-inbound-email-expenses
This commit is contained in:
		
						commit
						3fc35aefe7
					
				@ -68,4 +68,7 @@ MICROSOFT_REDIRECT_URI=
 | 
			
		||||
 | 
			
		||||
APPLE_CLIENT_ID=
 | 
			
		||||
APPLE_CLIENT_SECRET=
 | 
			
		||||
APPLE_REDIRECT_URI=
 | 
			
		||||
APPLE_REDIRECT_URI=
 | 
			
		||||
 | 
			
		||||
NORDIGEN_SECRET_ID=
 | 
			
		||||
NORDIGEN_SECRET_KEY=
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/phpunit.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/phpunit.yml
									
									
									
									
										vendored
									
									
								
							@ -130,4 +130,4 @@ jobs:
 | 
			
		||||
        DB_PORT: ${{ job.services.mysql.ports[3306] }}
 | 
			
		||||
        PHP_CS_FIXER_IGNORE_ENV: true
 | 
			
		||||
        CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
 | 
			
		||||
        CI_NODE_INDEX: ${{ matrix.ci_node_index }}
 | 
			
		||||
        CI_NODE_INDEX: ${{ matrix.ci_node_index }}
 | 
			
		||||
 | 
			
		||||
@ -36,7 +36,8 @@ We offer a $30 per year white-label license to remove the Invoice Ninja branding
 | 
			
		||||
### Desktop Apps
 | 
			
		||||
* [macOS](https://apps.apple.com/app/id1503970375?platform=mac)
 | 
			
		||||
* [Windows](https://microsoft.com/en-us/p/invoice-ninja/9n3f2bbcfdr6)
 | 
			
		||||
* [Linux](https://snapcraft.io/invoiceninja)
 | 
			
		||||
* [Linux - Snap](https://snapcraft.io/invoiceninja)
 | 
			
		||||
* [Linux - Flatpak](https://flathub.org/apps/com.invoiceninja.InvoiceNinja)
 | 
			
		||||
 | 
			
		||||
### Installation Options
 | 
			
		||||
* [Docker File](https://hub.docker.com/r/invoiceninja/invoiceninja/)
 | 
			
		||||
 | 
			
		||||
@ -1 +1 @@
 | 
			
		||||
5.7.59
 | 
			
		||||
5.7.63
 | 
			
		||||
@ -314,7 +314,7 @@ class CheckData extends Command
 | 
			
		||||
                $new_contact->client_id = $client->id;
 | 
			
		||||
                $new_contact->contact_key = Str::random(40);
 | 
			
		||||
                $new_contact->is_primary = true;
 | 
			
		||||
                $new_contact->save();
 | 
			
		||||
                $new_contact->saveQuietly();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -362,7 +362,7 @@ class CheckData extends Command
 | 
			
		||||
                $new_contact->vendor_id = $vendor->id;
 | 
			
		||||
                $new_contact->contact_key = Str::random(40);
 | 
			
		||||
                $new_contact->is_primary = true;
 | 
			
		||||
                $new_contact->save();
 | 
			
		||||
                $new_contact->saveQuietly();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -392,7 +392,7 @@ class CheckData extends Command
 | 
			
		||||
                $invitation->invoice_id = $invoice->id;
 | 
			
		||||
                $invitation->client_contact_id = ClientContact::whereClientId($invoice->client_id)->first()->id;
 | 
			
		||||
                $invitation->key = Str::random(config('ninja.key_length'));
 | 
			
		||||
                $invitation->save();
 | 
			
		||||
                $invitation->saveQuietly();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -583,7 +583,7 @@ class CheckData extends Command
 | 
			
		||||
                if ($this->option('paid_to_date')) {
 | 
			
		||||
                    $this->logMessage("# {$client->id} " . $client->present()->name().' - '.$client->number." Fixing {$client->paid_to_date} to {$total_paid_to_date}");
 | 
			
		||||
                    $client->paid_to_date = $total_paid_to_date;
 | 
			
		||||
                    $client->save();
 | 
			
		||||
                    $client->saveQuietly();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@ -632,7 +632,7 @@ class CheckData extends Command
 | 
			
		||||
                if ($this->option('client_balance')) {
 | 
			
		||||
                    $this->logMessage("# {$client_object->id} " . $client_object->present()->name().' - '.$client_object->number." Fixing {$client_object->balance} to " . $client['invoice_balance']);
 | 
			
		||||
                    $client_object->balance = $client['invoice_balance'];
 | 
			
		||||
                    $client_object->save();
 | 
			
		||||
                    $client_object->saveQuietly();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $this->isValid = false;
 | 
			
		||||
@ -678,7 +678,7 @@ class CheckData extends Command
 | 
			
		||||
                          $this->logMessage("# {$client->id} " . $client->present()->name().' - '.$client->number." Fixing {$client->balance} to 0");
 | 
			
		||||
 | 
			
		||||
                          $client->balance = $over_payment;
 | 
			
		||||
                          $client->save();
 | 
			
		||||
                          $client->saveQuietly();
 | 
			
		||||
                      }
 | 
			
		||||
                  }
 | 
			
		||||
              });
 | 
			
		||||
@ -732,7 +732,7 @@ class CheckData extends Command
 | 
			
		||||
                if ($this->option('client_balance')) {
 | 
			
		||||
                    $this->logMessage("# {$client->id} " . $client->present()->name().' - '.$client->number." Fixing {$client->balance} to {$invoice_balance}");
 | 
			
		||||
                    $client->balance = $invoice_balance;
 | 
			
		||||
                    $client->save();
 | 
			
		||||
                    $client->saveQuietly();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if ($ledger && (number_format($invoice_balance, 4) != number_format($ledger->balance, 4))) {
 | 
			
		||||
@ -766,7 +766,7 @@ class CheckData extends Command
 | 
			
		||||
                if ($this->option('ledger_balance')) {
 | 
			
		||||
                    $this->logMessage("# {$client->id} " . $client->present()->name().' - '.$client->number." Fixing {$client->balance} to {$invoice_balance}");
 | 
			
		||||
                    $client->balance = $invoice_balance;
 | 
			
		||||
                    $client->save();
 | 
			
		||||
                    $client->saveQuietly();
 | 
			
		||||
 | 
			
		||||
                    $ledger->adjustment = $invoice_balance;
 | 
			
		||||
                    $ledger->balance = $invoice_balance;
 | 
			
		||||
@ -884,7 +884,7 @@ class CheckData extends Command
 | 
			
		||||
        if ($this->option('fix') == 'true') {
 | 
			
		||||
            Client::query()->whereNull('country_id')->cursor()->each(function ($client) {
 | 
			
		||||
                $client->country_id = $client->company->settings->country_id;
 | 
			
		||||
                $client->save();
 | 
			
		||||
                $client->saveQuietly();
 | 
			
		||||
 | 
			
		||||
                $this->logMessage("Fixing country for # {$client->id}");
 | 
			
		||||
            });
 | 
			
		||||
@ -896,7 +896,7 @@ class CheckData extends Command
 | 
			
		||||
        if ($this->option('fix') == 'true') {
 | 
			
		||||
            Vendor::query()->whereNull('currency_id')->orWhere('currency_id', '')->cursor()->each(function ($vendor) {
 | 
			
		||||
                $vendor->currency_id = $vendor->company->settings->currency_id;
 | 
			
		||||
                $vendor->save();
 | 
			
		||||
                $vendor->saveQuietly();
 | 
			
		||||
 | 
			
		||||
                $this->logMessage("Fixing vendor currency for # {$vendor->id}");
 | 
			
		||||
            });
 | 
			
		||||
@ -919,14 +919,14 @@ class CheckData extends Command
 | 
			
		||||
 | 
			
		||||
                $invoice->balance = 0;
 | 
			
		||||
                $invoice->paid_to_date=$val;
 | 
			
		||||
                $invoice->save();
 | 
			
		||||
                $invoice->saveQuietly();
 | 
			
		||||
 | 
			
		||||
                $p = $invoice->payments->first();
 | 
			
		||||
 | 
			
		||||
                if ($p && (int)$p->amount == 0) {
 | 
			
		||||
                    $p->amount = $val;
 | 
			
		||||
                    $p->applied = $val;
 | 
			
		||||
                    $p->save();
 | 
			
		||||
                    $p->saveQuietly();
 | 
			
		||||
 | 
			
		||||
                    $pivot = $p->paymentables->first();
 | 
			
		||||
                    $pivot->amount = $val;
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@ class TypeCheck extends Command
 | 
			
		||||
        $entity_settings = $this->checkSettingType($client->settings);
 | 
			
		||||
        $entity_settings->md5 = md5(time());
 | 
			
		||||
        $client->settings = $entity_settings;
 | 
			
		||||
        $client->save();
 | 
			
		||||
        $client->saveQuietly();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function checkCompany($company)
 | 
			
		||||
@ -119,7 +119,7 @@ class TypeCheck extends Command
 | 
			
		||||
            $entity_settings = $this->checkSettingType($client->settings);
 | 
			
		||||
            $entity_settings->md5 = md5(time());
 | 
			
		||||
            $client->settings = $entity_settings;
 | 
			
		||||
            $client->save();
 | 
			
		||||
            $client->saveQuietly();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        Company::query()->cursor()->each(function ($company) {
 | 
			
		||||
 | 
			
		||||
@ -101,9 +101,12 @@ class Kernel extends ConsoleKernel
 | 
			
		||||
        /* Check ExpenseMainboxes */
 | 
			
		||||
        $schedule->job(new ExpenseMailboxJob)->everyThirtyMinutes()->withoutOverlapping()->name('expense-mailboxes-job')->onOneServer();
 | 
			
		||||
 | 
			
		||||
        /* Pulls in bank transactions from third party services */
 | 
			
		||||
        $schedule->job(new BankTransactionSync)->everyFourHours()->withoutOverlapping()->name('bank-trans-sync-job')->onOneServer();
 | 
			
		||||
 | 
			
		||||
        if (Ninja::isSelfHost()) {
 | 
			
		||||
            $schedule->call(function () {
 | 
			
		||||
                Account::whereNotNull('id')->update(['is_scheduler_running' => true]);
 | 
			
		||||
                Account::query()->whereNotNull('id')->update(['is_scheduler_running' => true]);
 | 
			
		||||
            })->everyFiveMinutes();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -111,9 +114,6 @@ class Kernel extends ConsoleKernel
 | 
			
		||||
        if (Ninja::isHosted()) {
 | 
			
		||||
            $schedule->job(new AdjustEmailQuota)->dailyAt('23:30')->withoutOverlapping();
 | 
			
		||||
 | 
			
		||||
            /* Pulls in bank transactions from third party services */
 | 
			
		||||
            $schedule->job(new BankTransactionSync)->everyFourHours()->withoutOverlapping()->name('bank-trans-sync-job')->onOneServer();
 | 
			
		||||
 | 
			
		||||
            /* Checks ACH verification status and updates state to authorize when verified */
 | 
			
		||||
            $schedule->job(new CheckACHStatus)->everySixHours()->withoutOverlapping()->name('ach-status-job')->onOneServer();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,23 @@ use Illuminate\Support\Facades\App;
 | 
			
		||||
 | 
			
		||||
class EmailTemplateDefaults
 | 
			
		||||
{
 | 
			
		||||
    public array $templates = [
 | 
			
		||||
        'email_template_invoice',
 | 
			
		||||
        'email_template_quote',
 | 
			
		||||
        'email_template_credit',
 | 
			
		||||
        'email_template_payment',
 | 
			
		||||
        'email_template_payment_partial',
 | 
			
		||||
        'email_template_statement',
 | 
			
		||||
        'email_template_reminder1',
 | 
			
		||||
        'email_template_reminder2',
 | 
			
		||||
        'email_template_reminder3',
 | 
			
		||||
        'email_template_reminder_endless',
 | 
			
		||||
        'email_template_custom1',
 | 
			
		||||
        'email_template_custom2',
 | 
			
		||||
        'email_template_custom3',
 | 
			
		||||
        'email_template_purchase_order',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public static function getDefaultTemplate($template, $locale)
 | 
			
		||||
    {
 | 
			
		||||
        App::setLocale($locale);
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,8 @@
 | 
			
		||||
 | 
			
		||||
namespace App\DataProviders;
 | 
			
		||||
 | 
			
		||||
class Domains {
 | 
			
		||||
class Domains 
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
private static array $verify_domains = [
 | 
			
		||||
        '0-00.usa.cc',
 | 
			
		||||
 | 
			
		||||
@ -181,9 +181,9 @@ class ClientExport extends BaseExport
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $entity;
 | 
			
		||||
        // return $entity;
 | 
			
		||||
 | 
			
		||||
        // return $this->decorateAdvancedFields($client, $entity);
 | 
			
		||||
        return $this->decorateAdvancedFields($client, $entity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function processMetaData(array $row, $resource): array
 | 
			
		||||
@ -221,21 +221,21 @@ class ClientExport extends BaseExport
 | 
			
		||||
            $entity['client.assigned_user'] = $client->assigned_user ? $client->user->present()->name() : '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (in_array('client.country_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['client.country_id'] = $client->country ? ctrans("texts.country_{$client->country->name}") : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('client.country_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['client.country_id'] = $client->country ? ctrans("texts.country_{$client->country->name}") : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('client.shipping_country_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['client.shipping_country_id'] = $client->shipping_country ? ctrans("texts.country_{$client->shipping_country->name}") : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('client.shipping_country_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['client.shipping_country_id'] = $client->shipping_country ? ctrans("texts.country_{$client->shipping_country->name}") : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('client.currency_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['client.currency_id'] = $client->currency() ? $client->currency()->code : $client->company->currency()->code;
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('client.currency_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['client.currency_id'] = $client->currency() ? $client->currency()->code : $client->company->currency()->code;
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('client.industry_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['industry_id'] = $client->industry ? ctrans("texts.industry_{$client->industry->name}") : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('client.industry_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['industry_id'] = $client->industry ? ctrans("texts.industry_{$client->industry->name}") : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('client.classification', $this->input['report_keys']) && isset($client->classification)) {
 | 
			
		||||
            $entity['client.classification'] = ctrans("texts.{$client->classification}") ?? '';
 | 
			
		||||
 | 
			
		||||
@ -129,8 +129,8 @@ class ContactExport extends BaseExport
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return $entity;
 | 
			
		||||
        // return $this->decorateAdvancedFields($contact->client, $entity);
 | 
			
		||||
        // return $entity;
 | 
			
		||||
        return $this->decorateAdvancedFields($contact->client, $entity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function decorateAdvancedFields(Client $client, array $entity) :array
 | 
			
		||||
@ -151,6 +151,15 @@ class ContactExport extends BaseExport
 | 
			
		||||
            $entity['industry_id'] = $client->industry ? ctrans("texts.industry_{$client->industry->name}") : '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (in_array('client.user_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['client.user_id'] = $client->user ? $client->user->present()->name() : '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (in_array('client.assigned_user_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['client.assigned_user_id'] = $client->assigned_user ? $client->assigned_user->present()->name() : '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        return $entity;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -161,29 +161,29 @@ class CreditExport extends BaseExport
 | 
			
		||||
 | 
			
		||||
    private function decorateAdvancedFields(Credit $credit, array $entity) :array
 | 
			
		||||
    {
 | 
			
		||||
        if (in_array('country_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['country'] = $credit->client->country ? ctrans("texts.country_{$credit->client->country->name}") : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('country_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['country'] = $credit->client->country ? ctrans("texts.country_{$credit->client->country->name}") : '';
 | 
			
		||||
        // }
 | 
			
		||||
        
 | 
			
		||||
        if (in_array('currency_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['currency_id'] = $credit->client->currency() ? $credit->client->currency()->code : $credit->company->currency()->code;
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('currency_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['currency_id'] = $credit->client->currency() ? $credit->client->currency()->code : $credit->company->currency()->code;
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('invoice_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['invoice'] = $credit->invoice ? $credit->invoice->number : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('invoice_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['invoice'] = $credit->invoice ? $credit->invoice->number : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('client_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['client'] = $credit->client->present()->name();
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('client_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['client'] = $credit->client->present()->name();
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('status_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['status'] = $credit->stringStatus($credit->status_id);
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('status_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['status'] = $credit->stringStatus($credit->status_id);
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if(in_array('credit.status', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['credit.status'] = $credit->stringStatus($credit->status_id);
 | 
			
		||||
        }
 | 
			
		||||
        // if(in_array('credit.status', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['credit.status'] = $credit->stringStatus($credit->status_id);
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('credit.assigned_user_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['credit.assigned_user_id'] = $credit->assigned_user ? $credit->assigned_user->present()->name(): '';
 | 
			
		||||
 | 
			
		||||
@ -126,35 +126,35 @@ class ExpenseExport extends BaseExport
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $entity;
 | 
			
		||||
        // return $this->decorateAdvancedFields($expense, $entity);
 | 
			
		||||
        // return $entity;
 | 
			
		||||
        return $this->decorateAdvancedFields($expense, $entity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function decorateAdvancedFields(Expense $expense, array $entity) :array
 | 
			
		||||
    {
 | 
			
		||||
        if (in_array('expense.currency_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['expense.currency_id'] = $expense->currency ? $expense->currency->code : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('expense.currency_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['expense.currency_id'] = $expense->currency ? $expense->currency->code : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('expense.client_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['expense.client'] = $expense->client ? $expense->client->present()->name() : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('expense.client_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['expense.client'] = $expense->client ? $expense->client->present()->name() : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('expense.invoice_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['expense.invoice_id'] = $expense->invoice ? $expense->invoice->number : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('expense.invoice_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['expense.invoice_id'] = $expense->invoice ? $expense->invoice->number : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('expense.category', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['expense.category'] = $expense->category ? $expense->category->name : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('expense.category', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['expense.category'] = $expense->category ? $expense->category->name : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('expense.vendor_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['expense.vendor'] = $expense->vendor ? $expense->vendor->name : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('expense.vendor_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['expense.vendor'] = $expense->vendor ? $expense->vendor->name : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('expense.payment_type_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['expense.payment_type_id'] = $expense->payment_type ? $expense->payment_type->name : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('expense.payment_type_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['expense.payment_type_id'] = $expense->payment_type ? $expense->payment_type->name : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('expense.project_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['expense.project_id'] = $expense->project ? $expense->project->name : '';
 | 
			
		||||
 | 
			
		||||
@ -128,32 +128,32 @@ class InvoiceExport extends BaseExport
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $entity;
 | 
			
		||||
        // return $this->decorateAdvancedFields($invoice, $entity);
 | 
			
		||||
        // return $entity;
 | 
			
		||||
        return $this->decorateAdvancedFields($invoice, $entity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function decorateAdvancedFields(Invoice $invoice, array $entity) :array
 | 
			
		||||
    {
 | 
			
		||||
        
 | 
			
		||||
        if (in_array('invoice.country_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['invoice.country_id'] = $invoice->client->country ? ctrans("texts.country_{$invoice->client->country->name}") : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('invoice.country_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['invoice.country_id'] = $invoice->client->country ? ctrans("texts.country_{$invoice->client->country->name}") : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('invoice.currency_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['invoice.currency_id'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code;
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('invoice.currency_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['invoice.currency_id'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code;
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('invoice.client_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['invoice.client_id'] = $invoice->client->present()->name();
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('invoice.client_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['invoice.client_id'] = $invoice->client->present()->name();
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('invoice.status', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['invoice.status'] = $invoice->stringStatus($invoice->status_id);
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('invoice.status', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['invoice.status'] = $invoice->stringStatus($invoice->status_id);
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('invoice.recurring_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['invoice.recurring_id'] = $invoice->recurring_invoice->number ?? '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('invoice.recurring_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['invoice.recurring_id'] = $invoice->recurring_invoice->number ?? '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('invoice.auto_bill_enabled', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['invoice.auto_bill_enabled'] = $invoice->auto_bill_enabled ? ctrans('texts.yes') : ctrans('texts.no');
 | 
			
		||||
 | 
			
		||||
@ -196,43 +196,43 @@ class InvoiceItemExport extends BaseExport
 | 
			
		||||
                // $entity[$key] = $this->resolveKey($key, $invoice, $this->invoice_transformer);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return $entity;
 | 
			
		||||
        // return $this->decorateAdvancedFields($invoice, $entity);
 | 
			
		||||
        // return $entity;
 | 
			
		||||
        return $this->decorateAdvancedFields($invoice, $entity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function decorateAdvancedFields(Invoice $invoice, array $entity) :array
 | 
			
		||||
    {
 | 
			
		||||
        if (in_array('currency_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['currency'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code;
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('currency_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['currency'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code;
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if(array_key_exists('type', $entity)) {
 | 
			
		||||
            $entity['type'] = $invoice->typeIdString($entity['type']);
 | 
			
		||||
        }
 | 
			
		||||
        // if(array_key_exists('type', $entity)) {
 | 
			
		||||
        //     $entity['type'] = $invoice->typeIdString($entity['type']);
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if(array_key_exists('tax_category', $entity)) {
 | 
			
		||||
            $entity['tax_category'] = $invoice->taxTypeString($entity['tax_category']);
 | 
			
		||||
        }
 | 
			
		||||
        // if(array_key_exists('tax_category', $entity)) {
 | 
			
		||||
        //     $entity['tax_category'] = $invoice->taxTypeString($entity['tax_category']);
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('invoice.country_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['invoice.country_id'] = $invoice->client->country ? ctrans("texts.country_{$invoice->client->country->name}") : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('invoice.country_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['invoice.country_id'] = $invoice->client->country ? ctrans("texts.country_{$invoice->client->country->name}") : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('invoice.currency_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['invoice.currency_id'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code;
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('invoice.currency_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['invoice.currency_id'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code;
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('invoice.client_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['invoice.client_id'] = $invoice->client->present()->name();
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('invoice.client_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['invoice.client_id'] = $invoice->client->present()->name();
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('invoice.status', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['invoice.status'] = $invoice->stringStatus($invoice->status_id);
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('invoice.status', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['invoice.status'] = $invoice->stringStatus($invoice->status_id);
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('invoice.recurring_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['invoice.recurring_id'] = $invoice->recurring_invoice->number ?? '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('invoice.recurring_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['invoice.recurring_id'] = $invoice->recurring_invoice->number ?? '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('invoice.assigned_user_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['invoice.assigned_user_id'] = $invoice->assigned_user ? $invoice->assigned_user->present()->name(): '';
 | 
			
		||||
 | 
			
		||||
@ -125,55 +125,55 @@ class PaymentExport extends BaseExport
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $entity;
 | 
			
		||||
        // return $this->decorateAdvancedFields($payment, $entity);
 | 
			
		||||
        // return $entity;
 | 
			
		||||
        return $this->decorateAdvancedFields($payment, $entity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function decorateAdvancedFields(Payment $payment, array $entity) :array
 | 
			
		||||
    {
 | 
			
		||||
        if (in_array('status_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['status'] = $payment->stringStatus($payment->status_id);
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('status_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['status'] = $payment->stringStatus($payment->status_id);
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('vendor_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['vendor'] = $payment->vendor()->exists() ? $payment->vendor->name : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('vendor_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['vendor'] = $payment->vendor()->exists() ? $payment->vendor->name : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('project_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['project'] = $payment->project()->exists() ? $payment->project->name : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('project_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['project'] = $payment->project()->exists() ? $payment->project->name : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('currency_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['currency'] = $payment->currency()->exists() ? $payment->currency->code : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('currency_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['currency'] = $payment->currency()->exists() ? $payment->currency->code : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('payment.currency', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['payment.currency'] = $payment->currency()->exists() ? $payment->currency->code : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('payment.currency', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['payment.currency'] = $payment->currency()->exists() ? $payment->currency->code : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('exchange_currency_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['exchange_currency'] = $payment->exchange_currency()->exists() ? $payment->exchange_currency->code : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('exchange_currency_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['exchange_currency'] = $payment->exchange_currency()->exists() ? $payment->exchange_currency->code : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('client_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['client'] = $payment->client->present()->name();
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('client_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['client'] = $payment->client->present()->name();
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('type_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['type'] = $payment->translatedType();
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('type_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['type'] = $payment->translatedType();
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('payment.method', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['payment.method'] = $payment->translatedType();
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('payment.method', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['payment.method'] = $payment->translatedType();
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('payment.status', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['payment.status'] = $payment->stringStatus($payment->status_id);
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('payment.status', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['payment.status'] = $payment->stringStatus($payment->status_id);
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('gateway_type_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['gateway'] = $payment->gateway_type ? $payment->gateway_type->name : 'Unknown Type';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('gateway_type_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['gateway'] = $payment->gateway_type ? $payment->gateway_type->name : 'Unknown Type';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('payment.assigned_user_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['payment.assigned_user_id'] = $payment->assigned_user ? $payment->assigned_user->present()->name() : '';
 | 
			
		||||
 | 
			
		||||
@ -173,8 +173,8 @@ class PurchaseOrderExport extends BaseExport
 | 
			
		||||
 | 
			
		||||
            
 | 
			
		||||
        }
 | 
			
		||||
        return $entity;
 | 
			
		||||
        // return $this->decorateAdvancedFields($purchase_order, $entity);
 | 
			
		||||
        // return $entity;
 | 
			
		||||
        return $this->decorateAdvancedFields($purchase_order, $entity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function decorateAdvancedFields(PurchaseOrder $purchase_order, array $entity) :array
 | 
			
		||||
@ -195,6 +195,15 @@ class PurchaseOrderExport extends BaseExport
 | 
			
		||||
            $entity['purchase_order.status'] = $purchase_order->stringStatus($purchase_order->status_id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (in_array('purchase_order.user_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['purchase_order.user_id'] = $purchase_order->user ? $purchase_order->user->present()->name() : '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (in_array('purchase_order.assigned_user_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['purchase_order.assigned_user_id'] = $purchase_order->assigned_user ? $purchase_order->assigned_user->present()->name() : '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        return $entity;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -206,6 +206,16 @@ class PurchaseOrderItemExport extends BaseExport
 | 
			
		||||
            $entity['status'] = $purchase_order->stringStatus($purchase_order->status_id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (in_array('purchase_order.user_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['purchase_order.user_id'] = $purchase_order->user ? $purchase_order->user->present()->name() : '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (in_array('purchase_order.assigned_user_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['purchase_order.assigned_user_id'] = $purchase_order->assigned_user ? $purchase_order->assigned_user->present()->name() : '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        return $entity;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -133,8 +133,8 @@ class QuoteExport extends BaseExport
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        return $entity;
 | 
			
		||||
        // return $this->decorateAdvancedFields($quote, $entity);
 | 
			
		||||
        // return $entity;
 | 
			
		||||
        return $this->decorateAdvancedFields($quote, $entity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function decorateAdvancedFields(Quote $quote, array $entity) :array
 | 
			
		||||
 | 
			
		||||
@ -189,22 +189,22 @@ class QuoteItemExport extends BaseExport
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $entity;
 | 
			
		||||
        // return $this->decorateAdvancedFields($quote, $entity);
 | 
			
		||||
        // return $entity;
 | 
			
		||||
        return $this->decorateAdvancedFields($quote, $entity);
 | 
			
		||||
    }
 | 
			
		||||
    private function decorateAdvancedFields(Quote $quote, array $entity) :array
 | 
			
		||||
    {
 | 
			
		||||
        if (in_array('currency_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['currency'] = $quote->client->currency() ? $quote->client->currency()->code : $quote->company->currency()->code;
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('currency_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['currency'] = $quote->client->currency() ? $quote->client->currency()->code : $quote->company->currency()->code;
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('client_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['client'] = $quote->client->present()->name();
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('client_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['client'] = $quote->client->present()->name();
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('status_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['status'] = $quote->stringStatus($quote->status_id);
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('status_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['status'] = $quote->stringStatus($quote->status_id);
 | 
			
		||||
        // }
 | 
			
		||||
        
 | 
			
		||||
        if (in_array('quote.assigned_user_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['quote.assigned_user_id'] = $quote->assigned_user ? $quote->assigned_user->present()->name(): '';
 | 
			
		||||
 | 
			
		||||
@ -109,8 +109,6 @@ class RecurringInvoiceExport extends BaseExport
 | 
			
		||||
    private function buildRow(RecurringInvoice $invoice) :array
 | 
			
		||||
    {
 | 
			
		||||
        $transformed_invoice = $this->invoice_transformer->transform($invoice);
 | 
			
		||||
        $transformed_invoice['frequency_id'] = $invoice->frequencyForKey($invoice->frequency_id); //need to inject this here because it is also a valid key
 | 
			
		||||
// nlog($transformed_invoice);
 | 
			
		||||
 | 
			
		||||
        $entity = [];
 | 
			
		||||
 | 
			
		||||
@ -131,36 +129,36 @@ class RecurringInvoiceExport extends BaseExport
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
// nlog($entity);
 | 
			
		||||
        return $entity;
 | 
			
		||||
        // return $this->decorateAdvancedFields($invoice, $entity);
 | 
			
		||||
 | 
			
		||||
        // return $entity;
 | 
			
		||||
        return $this->decorateAdvancedFields($invoice, $entity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function decorateAdvancedFields(RecurringInvoice $invoice, array $entity) :array
 | 
			
		||||
    {
 | 
			
		||||
        if (in_array('country_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['country'] = $invoice->client->country ? ctrans("texts.country_{$invoice->client->country->name}") : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('country_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['country'] = $invoice->client->country ? ctrans("texts.country_{$invoice->client->country->name}") : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('currency_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['currency'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code;
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('currency_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['currency'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code;
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('client_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['client'] = $invoice->client->present()->name();
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('client_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['client'] = $invoice->client->present()->name();
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('recurring_invoice.status', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['recurring_invoice.status'] = $invoice->stringStatus($invoice->status_id);
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('recurring_invoice.status', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['recurring_invoice.status'] = $invoice->stringStatus($invoice->status_id);
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('project_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['project'] = $invoice->project ? $invoice->project->name : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('project_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['project'] = $invoice->project ? $invoice->project->name : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('vendor_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['vendor'] = $invoice->vendor ? $invoice->vendor->name : '';
 | 
			
		||||
        }
 | 
			
		||||
        // if (in_array('vendor_id', $this->input['report_keys'])) {
 | 
			
		||||
        //     $entity['vendor'] = $invoice->vendor ? $invoice->vendor->name : '';
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (in_array('recurring_invoice.frequency_id', $this->input['report_keys']) || in_array('frequency_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['recurring_invoice.frequency_id'] = $invoice->frequencyForKey($invoice->frequency_id);
 | 
			
		||||
 | 
			
		||||
@ -197,7 +197,7 @@ class TaskExport extends BaseExport
 | 
			
		||||
                $entity['task.duration'] = $task->calcDuration();
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // $entity = $this->decorateAdvancedFields($task, $entity);
 | 
			
		||||
            $entity = $this->decorateAdvancedFields($task, $entity);
 | 
			
		||||
            
 | 
			
		||||
            $this->storage_array[] = $entity;
 | 
			
		||||
            
 | 
			
		||||
@ -218,6 +218,15 @@ class TaskExport extends BaseExport
 | 
			
		||||
            $entity['task.project_id'] = $task->project()->exists() ? $task->project->name : '';
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (in_array('task.user_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['task.user_id'] = $task->user ? $task->user->present()->name() : '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (in_array('task.assigned_user_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['task.assigned_user_id'] = $task->assigned_user ? $task->assigned_user->present()->name() : '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        return $entity;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -126,15 +126,14 @@ class VendorExport extends BaseExport
 | 
			
		||||
            } elseif (is_array($parts) && $parts[0] == 'vendor_contact' && isset($transformed_contact[$parts[1]])) {
 | 
			
		||||
                $entity[$key] = $transformed_contact[$parts[1]];
 | 
			
		||||
            } else {
 | 
			
		||||
                // nlog($key);
 | 
			
		||||
 | 
			
		||||
                $entity[$key] = $this->decorator->transform($key, $vendor);
 | 
			
		||||
 | 
			
		||||
                // $entity[$key] = $this->resolveKey($key, $vendor, $this->vendor_transformer);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $entity;
 | 
			
		||||
        // return $this->decorateAdvancedFields($vendor, $entity);
 | 
			
		||||
        // return $entity;
 | 
			
		||||
        return $this->decorateAdvancedFields($vendor, $entity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function decorateAdvancedFields(Vendor $vendor, array $entity) :array
 | 
			
		||||
@ -151,6 +150,15 @@ class VendorExport extends BaseExport
 | 
			
		||||
            $entity['vendor.classification'] = ctrans("texts.{$vendor->classification}") ?? '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (in_array('vendor.user_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['vendor.user_id'] = $vendor->user ? $vendor->user->present()->name() : '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (in_array('vendor.assigned_user_id', $this->input['report_keys'])) {
 | 
			
		||||
            $entity['vendor.assigned_user_id'] = $vendor->assigned_user ? $vendor->assigned_user->present()->name() : '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // $entity['status'] = $this->calculateStatus($vendor);
 | 
			
		||||
 | 
			
		||||
        return $entity;
 | 
			
		||||
 | 
			
		||||
@ -97,7 +97,9 @@ class BankIntegrationFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -128,13 +128,15 @@ class BankTransactionFilters extends QueryFilters
 | 
			
		||||
        if (!is_array($sort_col) || count($sort_col) != 2) {
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        if ($sort_col[0] == 'deposit') {
 | 
			
		||||
            return $this->builder->where('base_type', 'CREDIT')->orderBy('amount', $sort_col[1]);
 | 
			
		||||
            return $this->builder->where('base_type', 'CREDIT')->orderBy('amount', $dir);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($sort_col[0] == 'withdrawal') {
 | 
			
		||||
            return $this->builder->where('base_type', 'DEBIT')->orderBy('amount', $sort_col[1]);
 | 
			
		||||
            return $this->builder->where('base_type', 'DEBIT')->orderBy('amount', $dir);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($sort_col[0] == 'status') {
 | 
			
		||||
@ -145,7 +147,7 @@ class BankTransactionFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -65,7 +65,9 @@ class BankTransactionRuleFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -161,8 +161,10 @@ class ClientFilters extends QueryFilters
 | 
			
		||||
        if ($sort_col[0] == 'display_name') {
 | 
			
		||||
            $sort_col[0] = 'name';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
        
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -49,8 +49,10 @@ class CompanyGatewayFilters extends QueryFilters
 | 
			
		||||
        if (!is_array($sort_col) || count($sort_col) != 2) {
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -139,12 +139,14 @@ class CreditFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        if ($sort_col[0] == 'client_id') {
 | 
			
		||||
            return $this->builder->orderBy(\App\Models\Client::select('name')
 | 
			
		||||
                    ->whereColumn('clients.id', 'credits.client_id'), $sort_col[1]);
 | 
			
		||||
                    ->whereColumn('clients.id', 'credits.client_id'), $dir);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -51,7 +51,9 @@ class DesignFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function entities(string $entities = ''): Builder
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,7 @@
 | 
			
		||||
namespace App\Filters;
 | 
			
		||||
 | 
			
		||||
use App\Models\Company;
 | 
			
		||||
use App\Filters\QueryFilters;
 | 
			
		||||
use Illuminate\Database\Eloquent\Builder;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -63,7 +64,9 @@ class DocumentFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -47,7 +47,7 @@ class ExpenseCategoryFilters extends QueryFilters
 | 
			
		||||
        if (!is_array($sort_col) || count($sort_col) != 2) {
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        if (is_array($sort_col) && in_array($sort_col[1], ['asc', 'desc']) && in_array($sort_col[0], ['name'])) {
 | 
			
		||||
            return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -64,8 +64,10 @@ class GroupSettingFilters extends QueryFilters
 | 
			
		||||
        if (!is_array($sort_col) || count($sort_col) != 2) {
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
        
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -146,16 +146,26 @@ class InvoiceFilters extends QueryFilters
 | 
			
		||||
     */
 | 
			
		||||
    public function upcoming(): Builder
 | 
			
		||||
    {
 | 
			
		||||
        return $this->builder->whereIn('status_id', [Invoice::STATUS_PARTIAL, Invoice::STATUS_SENT])
 | 
			
		||||
                    ->whereNull('due_date')
 | 
			
		||||
 | 
			
		||||
        return $this->builder->where(function ($query) {
 | 
			
		||||
            $query->whereIn('status_id', [Invoice::STATUS_PARTIAL, Invoice::STATUS_SENT])
 | 
			
		||||
            ->where('is_deleted', 0)
 | 
			
		||||
            ->where('balance', '>', 0)
 | 
			
		||||
            ->where(function ($query) {
 | 
			
		||||
 | 
			
		||||
                $query->whereNull('due_date')
 | 
			
		||||
                    ->orWhere(function ($q) {
 | 
			
		||||
                        $q->where('due_date', '>=', now()->startOfDay()->subSecond())->where('partial', 0);
 | 
			
		||||
                    })
 | 
			
		||||
                     ->orWhere(function ($q) {
 | 
			
		||||
                         $q->where('partial_due_date', '>=', now()->startOfDay()->subSecond())->where('partial', '>', 0);
 | 
			
		||||
                     })
 | 
			
		||||
                    ->orderByRaw('ISNULL(due_date), due_date '. 'desc')
 | 
			
		||||
                    ->orderByRaw('ISNULL(partial_due_date), partial_due_date '. 'desc');
 | 
			
		||||
                    ->orWhere(function ($q) {
 | 
			
		||||
                        $q->where('partial_due_date', '>=', now()->startOfDay()->subSecond())->where('partial', '>', 0);
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
            })
 | 
			
		||||
            ->orderByRaw('ISNULL(due_date), due_date ' . 'desc')
 | 
			
		||||
            ->orderByRaw('ISNULL(partial_due_date), partial_due_date ' . 'desc');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -165,13 +175,18 @@ class InvoiceFilters extends QueryFilters
 | 
			
		||||
     */
 | 
			
		||||
    public function overdue(): Builder
 | 
			
		||||
    {
 | 
			
		||||
        return $this->builder->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
 | 
			
		||||
        return $this->builder->where(function ($query) {
 | 
			
		||||
 | 
			
		||||
            $query->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
 | 
			
		||||
                    ->where('is_deleted', 0)
 | 
			
		||||
                    ->where('balance', '>', 0)
 | 
			
		||||
                    ->where(function ($query) {
 | 
			
		||||
                        $query->where('due_date', '<', now())
 | 
			
		||||
                            ->orWhere('partial_due_date', '<', now());
 | 
			
		||||
                    })
 | 
			
		||||
                    ->orderBy('due_date', 'ASC');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -271,14 +286,16 @@ class InvoiceFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        if ($sort_col[0] == 'client_id') {
 | 
			
		||||
 | 
			
		||||
            return $this->builder->orderBy(\App\Models\Client::select('name')
 | 
			
		||||
                             ->whereColumn('clients.id', 'invoices.client_id'), $sort_col[1]);
 | 
			
		||||
                             ->whereColumn('clients.id', 'invoices.client_id'), $dir);
 | 
			
		||||
            
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -69,9 +69,11 @@ class ProjectFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (is_array($sort_col)) {
 | 
			
		||||
        if (is_array($sort_col) && in_array($sort_col[1], ['asc','desc'])) {
 | 
			
		||||
            return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -123,12 +123,14 @@ class PurchaseOrderFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        if ($sort_col[0] == 'vendor_id') {
 | 
			
		||||
            return $this->builder->orderBy(\App\Models\Vendor::select('name')
 | 
			
		||||
                    ->whereColumn('vendors.id', 'purchase_orders.vendor_id'), $sort_col[1]);
 | 
			
		||||
                    ->whereColumn('vendors.id', 'purchase_orders.vendor_id'), $dir);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -146,10 +146,12 @@ class QuoteFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        if($sort_col[0] == 'client_id') {
 | 
			
		||||
 | 
			
		||||
            return $this->builder->orderBy(\App\Models\Client::select('name')
 | 
			
		||||
                    ->whereColumn('clients.id', 'quotes.client_id'), $sort_col[1]);
 | 
			
		||||
                    ->whereColumn('clients.id', 'quotes.client_id'), $dir);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -157,7 +159,7 @@ class QuoteFilters extends QueryFilters
 | 
			
		||||
            $sort_col[0] = 'due_date';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -63,7 +63,9 @@ class RecurringExpenseFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -114,20 +114,25 @@ class RecurringInvoiceFilters extends QueryFilters
 | 
			
		||||
     */
 | 
			
		||||
    public function sort(string $sort = ''): Builder
 | 
			
		||||
    {
 | 
			
		||||
        $sort_col = explode('|', $sort);
 | 
			
		||||
 | 
			
		||||
        $sort_col = explode('|', $sort);
 | 
			
		||||
        
 | 
			
		||||
        if (!is_array($sort_col) || count($sort_col) != 2) {
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        if ($sort_col[0] == 'client_id') {
 | 
			
		||||
            return $this->builder->orderBy(\App\Models\Client::select('name')
 | 
			
		||||
                    ->whereColumn('clients.id', 'recurring_invoices.client_id'), $sort_col[1]);
 | 
			
		||||
                    ->whereColumn('clients.id', 'recurring_invoices.client_id'), $dir);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if($sort_col[0] == 'number'){
 | 
			
		||||
            return $this->builder->orderByRaw("ABS(number) {$dir}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -62,7 +62,9 @@ class RecurringQuoteFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -50,7 +50,9 @@ class SchedulerFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -50,7 +50,9 @@ class SubscriptionFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -75,7 +75,9 @@ class SystemLogFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -131,17 +131,19 @@ class TaskFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        if ($sort_col[0] == 'client_id') {
 | 
			
		||||
            return $this->builder->orderBy(\App\Models\Client::select('name')
 | 
			
		||||
                    ->whereColumn('clients.id', 'tasks.client_id'), $sort_col[1]);
 | 
			
		||||
                    ->whereColumn('clients.id', 'tasks.client_id'), $dir);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($sort_col[0] == 'user_id') {
 | 
			
		||||
            return $this->builder->orderBy(\App\Models\User::select('first_name')
 | 
			
		||||
                    ->whereColumn('users.id', 'tasks.user_id'), $sort_col[1]);
 | 
			
		||||
                    ->whereColumn('users.id', 'tasks.user_id'), $dir);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function task_status(string $value = ''): Builder
 | 
			
		||||
 | 
			
		||||
@ -50,7 +50,9 @@ class TaskStatusFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -50,7 +50,9 @@ class TaxRateFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -56,7 +56,9 @@ class TokenFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -54,7 +54,9 @@ class UserFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -64,21 +66,44 @@ class UserFilters extends QueryFilters
 | 
			
		||||
     */
 | 
			
		||||
    public function entityFilter()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->builder->whereHas('company_users', function ($q) {
 | 
			
		||||
            $q->where('company_id', '=', auth()->user()->company()->id);
 | 
			
		||||
 | 
			
		||||
        /** @var \App\Models\User $user */
 | 
			
		||||
        $user = auth()->user();
 | 
			
		||||
 | 
			
		||||
        return $this->builder->whereHas('company_users', function ($q) use ($user){
 | 
			
		||||
            $q->where('company_id', '=', $user->company()->id);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Hides owner users from the list.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Builder
 | 
			
		||||
     */
 | 
			
		||||
    public function hideOwnerUsers(): Builder
 | 
			
		||||
    {
 | 
			
		||||
        /** @var \App\Models\User $user */
 | 
			
		||||
        $user = auth()->user();
 | 
			
		||||
        
 | 
			
		||||
        return $this->builder->whereHas('company_users', function ($q) use ($user) {
 | 
			
		||||
            $q->where('company_id', '=', $user->company()->id)->where('is_owner', false);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Filters users that have been removed from the
 | 
			
		||||
     * company, but not deleted from the system.
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     * @return Builder
 | 
			
		||||
     */
 | 
			
		||||
    public function hideRemovedUsers()
 | 
			
		||||
    public function hideRemovedUsers(): Builder
 | 
			
		||||
    {
 | 
			
		||||
        return $this->builder->whereHas('company_users', function ($q) {
 | 
			
		||||
            $q->where('company_id', '=', auth()->user()->company()->id)->whereNull('deleted_at');
 | 
			
		||||
        /** @var \App\Models\User $user */
 | 
			
		||||
        $user = auth()->user();
 | 
			
		||||
 | 
			
		||||
        return $this->builder->whereHas('company_users', function ($q) use ($user) {
 | 
			
		||||
            $q->where('company_id', '=', $user->company()->id)->whereNull('deleted_at');
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -96,12 +121,21 @@ class UserFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /** @var \App\Models\User $user */
 | 
			
		||||
        $user = auth()->user();
 | 
			
		||||
 | 
			
		||||
        return $this->builder
 | 
			
		||||
            ->orWhere($this->with_property, $value)
 | 
			
		||||
            ->orderByRaw("{$this->with_property} = ? DESC", [$value])
 | 
			
		||||
            ->where('account_id', auth()->user()->account_id);
 | 
			
		||||
            ->where('account_id', $user->account_id);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
        
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns users with permissions to send emails via OAuth
 | 
			
		||||
     *
 | 
			
		||||
     * @param  string $value
 | 
			
		||||
     * @return Builder
 | 
			
		||||
     */
 | 
			
		||||
    public function sending_users(string $value = ''): Builder
 | 
			
		||||
    {
 | 
			
		||||
        if (strlen($value) == 0 || $value != 'true') {
 | 
			
		||||
 | 
			
		||||
@ -69,7 +69,9 @@ class VendorFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -50,7 +50,9 @@ class WebhookFilters extends QueryFilters
 | 
			
		||||
            return $this->builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $sort_col[1]);
 | 
			
		||||
        $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        return $this->builder->orderBy($sort_col[0], $dir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										137
									
								
								app/Helpers/Bank/Nordigen/Nordigen.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								app/Helpers/Bank/Nordigen/Nordigen.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,137 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Invoice Ninja (https://invoiceninja.com).
 | 
			
		||||
 *
 | 
			
		||||
 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
 | 
			
		||||
 *
 | 
			
		||||
 * @license https://www.elastic.co/licensing/elastic-license
 | 
			
		||||
 *
 | 
			
		||||
 * Documentation of Api-Usage: https://developer.gocardless.com/bank-account-data/overview
 | 
			
		||||
 *
 | 
			
		||||
 * Institutions: Are Banks or Payment-Providers, which manages bankaccounts.
 | 
			
		||||
 *
 | 
			
		||||
 * Accounts: Accounts are existing bank_accounts at a specific institution.
 | 
			
		||||
 *
 | 
			
		||||
 * Requisitions: Are registered/active user-flows to authenticate one or many accounts. After completition, the accoundId could be used to fetch data for this account. After the access expires, the user could create a new requisition to connect accounts again.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace App\Helpers\Bank\Nordigen;
 | 
			
		||||
 | 
			
		||||
use App\Helpers\Bank\Nordigen\Transformer\AccountTransformer;
 | 
			
		||||
use App\Helpers\Bank\Nordigen\Transformer\TransactionTransformer;
 | 
			
		||||
 | 
			
		||||
class Nordigen
 | 
			
		||||
{
 | 
			
		||||
    public bool $test_mode; // https://developer.gocardless.com/bank-account-data/sandbox
 | 
			
		||||
 | 
			
		||||
    public string $sandbox_institutionId = "SANDBOXFINANCE_SFIN0000";
 | 
			
		||||
 | 
			
		||||
    protected \Nordigen\NordigenPHP\API\NordigenClient $client;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->test_mode = config('ninja.nordigen.test_mode');
 | 
			
		||||
 | 
			
		||||
        if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))
 | 
			
		||||
            throw new \Exception('missing nordigen credentials');
 | 
			
		||||
 | 
			
		||||
        $this->client = new \Nordigen\NordigenPHP\API\NordigenClient(config('ninja.nordigen.secret_id'), config('ninja.nordigen.secret_key'));
 | 
			
		||||
 | 
			
		||||
        $this->client->createAccessToken();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // metadata-section for frontend
 | 
			
		||||
    public function getInstitutions()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->test_mode)
 | 
			
		||||
            return [$this->client->institution->getInstitution($this->sandbox_institutionId)];
 | 
			
		||||
 | 
			
		||||
        return $this->client->institution->getInstitutions();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // requisition-section
 | 
			
		||||
    public function createRequisition(string $redirect, string $initutionId, string $reference, string $userLanguage)
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->test_mode && $initutionId != $this->sandbox_institutionId)
 | 
			
		||||
            throw new \Exception('invalid institutionId while in test-mode');
 | 
			
		||||
 | 
			
		||||
        return $this->client->requisition->createRequisition($redirect, $initutionId, null, $reference, $userLanguage);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getRequisition(string $requisitionId)
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            return $this->client->requisition->getRequisition($requisitionId);
 | 
			
		||||
        } catch (\Exception $e) {
 | 
			
		||||
            if (strpos($e->getMessage(), "Invalid Requisition ID") !== false)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            throw $e;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO: return null on not found
 | 
			
		||||
    public function getAccount(string $account_id)
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $out = new \stdClass();
 | 
			
		||||
 | 
			
		||||
            $out->data = $this->client->account($account_id)->getAccountDetails()["account"];
 | 
			
		||||
            $out->metadata = $this->client->account($account_id)->getAccountMetaData();
 | 
			
		||||
            $out->balances = $this->client->account($account_id)->getAccountBalances()["balances"];
 | 
			
		||||
            $out->institution = $this->client->institution->getInstitution($out->metadata["institution_id"]);
 | 
			
		||||
 | 
			
		||||
            $it = new AccountTransformer();
 | 
			
		||||
            return $it->transform($out);
 | 
			
		||||
            
 | 
			
		||||
        } catch (\Exception $e) {
 | 
			
		||||
            if (strpos($e->getMessage(), "Invalid Account ID") !== false)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            throw $e;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * isAccountActive
 | 
			
		||||
     *
 | 
			
		||||
     * @param  string $account_id
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    public function isAccountActive(string $account_id): bool
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $account = $this->client->account($account_id)->getAccountMetaData();
 | 
			
		||||
 | 
			
		||||
            if ($account["status"] != "READY") {
 | 
			
		||||
                nlog('nordigen account was not in status ready. accountId: ' . $account_id . ' status: ' . $account["status"]);
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        } catch (\Exception $e) {
 | 
			
		||||
            if (strpos($e->getMessage(), "Invalid Account ID") !== false)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            throw $e;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * getTransactions
 | 
			
		||||
     *
 | 
			
		||||
     * @param  string $accountId
 | 
			
		||||
     * @param  string $dateFrom
 | 
			
		||||
     * @return array
 | 
			
		||||
     */
 | 
			
		||||
    public function getTransactions(string $accountId, string $dateFrom = null): array
 | 
			
		||||
    {
 | 
			
		||||
        $transactionResponse = $this->client->account($accountId)->getAccountTransactions($dateFrom);
 | 
			
		||||
 | 
			
		||||
        $it = new TransactionTransformer();
 | 
			
		||||
        return $it->transform($transactionResponse);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										121
									
								
								app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,121 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Invoice Ninja (https://invoiceninja.com).
 | 
			
		||||
 *
 | 
			
		||||
 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
 | 
			
		||||
 *
 | 
			
		||||
 * @license https://www.elastic.co/licensing/elastic-license
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace App\Helpers\Bank\Nordigen\Transformer;
 | 
			
		||||
 | 
			
		||||
use App\Helpers\Bank\AccountTransformerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
[0] => stdClass Object
 | 
			
		||||
(
 | 
			
		||||
    [data] => stdClass Object
 | 
			
		||||
        (
 | 
			
		||||
            [resourceId] => XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
 | 
			
		||||
            [iban] => DE0286055592XXXXXXXXXX
 | 
			
		||||
            [currency] => EUR
 | 
			
		||||
            [ownerName] => Max Mustermann
 | 
			
		||||
            [product] => GiroKomfort
 | 
			
		||||
            [bic] => WELADE8LXXX
 | 
			
		||||
            [usage] => PRIV
 | 
			
		||||
        )
 | 
			
		||||
    [metadata] => stdClass Object
 | 
			
		||||
        (
 | 
			
		||||
            [id] => XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
 | 
			
		||||
            [created] => 2022-12-05T18:41:53.986028Z
 | 
			
		||||
            [last_accessed] => 2023-10-29T08:35:34.003611Z
 | 
			
		||||
            [iban] => DE0286055592XXXXXXXXXX
 | 
			
		||||
            [institution_id] => STADT_KREISSPARKASSE_LEIPZIG_WELADE8LXXX
 | 
			
		||||
            [status] => READY
 | 
			
		||||
            [owner_name] => Max Mustermann
 | 
			
		||||
        )
 | 
			
		||||
    [balances] => [
 | 
			
		||||
                {
 | 
			
		||||
                    [balanceAmount]: {
 | 
			
		||||
                        [amount] => 9825.64
 | 
			
		||||
                        [currency] => EUR
 | 
			
		||||
                    },
 | 
			
		||||
                    [balanceType] => closingBooked
 | 
			
		||||
                    [referenceDate] => 2023-12-01
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    [balanceAmount[: {
 | 
			
		||||
                        [amount] => 10325.64
 | 
			
		||||
                        [currency] => EUR
 | 
			
		||||
                    },
 | 
			
		||||
                    [balanceType] => interimAvailable
 | 
			
		||||
                    [creditLimitIncluded]: true,
 | 
			
		||||
                    [referenceDate] => 2023-12-01
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
    [institution] => stdClass Object
 | 
			
		||||
        (
 | 
			
		||||
            [id] => STADT_KREISSPARKASSE_LEIPZIG_WELADE8LXXX
 | 
			
		||||
            [name] => Stadt- und Kreissparkasse Leipzig
 | 
			
		||||
            [bic] => WELADE8LXXX
 | 
			
		||||
            [transaction_total_days] => 360
 | 
			
		||||
            [countries] => [
 | 
			
		||||
                "DE"
 | 
			
		||||
            ],
 | 
			
		||||
            [logo] => https://storage.googleapis.com/gc-prd-institution_icons-production/DE/PNG/sparkasse.png
 | 
			
		||||
            [supported_payments] => {
 | 
			
		||||
                [single-payment] => [
 | 
			
		||||
                    "SCT",
 | 
			
		||||
                    "ISCT"
 | 
			
		||||
                ]
 | 
			
		||||
            },
 | 
			
		||||
            [supported_features] => [
 | 
			
		||||
                "card_accounts",
 | 
			
		||||
                "payments",
 | 
			
		||||
                "pending_transactions"
 | 
			
		||||
            ],
 | 
			
		||||
            [identification_codes] => []
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    )
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AccountTransformer implements AccountTransformerInterface
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    public function transform($nordigen_account)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        if (!property_exists($nordigen_account, 'data') || !property_exists($nordigen_account, 'metadata') || !property_exists($nordigen_account, 'balances') || !property_exists($nordigen_account, 'institution'))
 | 
			
		||||
            throw new \Exception('invalid dataset');
 | 
			
		||||
 | 
			
		||||
        $used_balance = $nordigen_account->balances[0];
 | 
			
		||||
        // prefer entry with closingBooked
 | 
			
		||||
        foreach ($nordigen_account->balances as $entry) {
 | 
			
		||||
            if ($entry["balanceType"] === 'closingBooked') { // available: closingBooked, interimAvailable
 | 
			
		||||
                $used_balance = $entry;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            'id' => $nordigen_account->metadata["id"],
 | 
			
		||||
            'account_type' => "bank",
 | 
			
		||||
            'account_name' => $nordigen_account->data["iban"],
 | 
			
		||||
            'account_status' => $nordigen_account->metadata["status"],
 | 
			
		||||
            'account_number' => '**** ' . substr($nordigen_account->data["iban"], -7),
 | 
			
		||||
            'provider_account_id' => $nordigen_account->metadata["id"],
 | 
			
		||||
            'provider_id' => $nordigen_account->institution["id"],
 | 
			
		||||
            'provider_name' => $nordigen_account->institution["name"],
 | 
			
		||||
            'nickname' => $nordigen_account->data["ownerName"] ? $nordigen_account->data["ownerName"] : '',
 | 
			
		||||
            'current_balance' => (int) $used_balance ? $used_balance["balanceAmount"]["amount"] : 0,
 | 
			
		||||
            'account_currency' => $used_balance ? $used_balance["balanceAmount"]["currency"] : '',
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										149
									
								
								app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,149 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Invoice Ninja (https://invoiceninja.com).
 | 
			
		||||
 *
 | 
			
		||||
 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
 | 
			
		||||
 *
 | 
			
		||||
 * @license https://www.elastic.co/licensing/elastic-license
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace App\Helpers\Bank\Nordigen\Transformer;
 | 
			
		||||
 | 
			
		||||
use App\Helpers\Bank\BankRevenueInterface;
 | 
			
		||||
use App\Models\BankIntegration;
 | 
			
		||||
use App\Utils\Traits\AppSetup;
 | 
			
		||||
use Illuminate\Support\Facades\Cache;
 | 
			
		||||
use Log;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
{
 | 
			
		||||
  "transactions": {
 | 
			
		||||
    "booked": [
 | 
			
		||||
      {
 | 
			
		||||
        "transactionId": "string",
 | 
			
		||||
        "debtorName": "string",
 | 
			
		||||
        "debtorAccount": {
 | 
			
		||||
          "iban": "string"
 | 
			
		||||
        },
 | 
			
		||||
        "transactionAmount": {
 | 
			
		||||
          "currency": "string",
 | 
			
		||||
          "amount": "328.18"
 | 
			
		||||
        },
 | 
			
		||||
        "bankTransactionCode": "string",
 | 
			
		||||
        "bookingDate": "date",
 | 
			
		||||
        "valueDate": "date",
 | 
			
		||||
        "remittanceInformationUnstructured": "string"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "transactionId": "string",
 | 
			
		||||
        "transactionAmount": {
 | 
			
		||||
          "currency": "string",
 | 
			
		||||
          "amount": "947.26"
 | 
			
		||||
        },
 | 
			
		||||
        "bankTransactionCode": "string",
 | 
			
		||||
        "bookingDate": "date",
 | 
			
		||||
        "valueDate": "date",
 | 
			
		||||
        "remittanceInformationUnstructured": "string"
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "pending": [
 | 
			
		||||
      {
 | 
			
		||||
        "transactionAmount": {
 | 
			
		||||
          "currency": "string",
 | 
			
		||||
          "amount": "99.20"
 | 
			
		||||
        },
 | 
			
		||||
        "valueDate": "date",
 | 
			
		||||
        "remittanceInformationUnstructured": "string"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
class TransactionTransformer implements BankRevenueInterface
 | 
			
		||||
{
 | 
			
		||||
    use AppSetup;
 | 
			
		||||
 | 
			
		||||
    public function transform($transactionResponse)
 | 
			
		||||
    {
 | 
			
		||||
        $data = [];
 | 
			
		||||
 | 
			
		||||
        if (!array_key_exists('transactions', $transactionResponse) || !array_key_exists('booked', $transactionResponse["transactions"]))
 | 
			
		||||
            throw new \Exception('invalid dataset');
 | 
			
		||||
 | 
			
		||||
        foreach ($transactionResponse["transactions"]["booked"] as $transaction) {
 | 
			
		||||
            $data[] = $this->transformTransaction($transaction);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function transformTransaction($transaction)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        if (!array_key_exists('transactionId', $transaction) || !array_key_exists('transactionAmount', $transaction))
 | 
			
		||||
            throw new \Exception('invalid dataset');
 | 
			
		||||
 | 
			
		||||
        // description could be in varios places
 | 
			
		||||
        $description = '';
 | 
			
		||||
        if (array_key_exists('remittanceInformationStructured', $transaction))
 | 
			
		||||
            $description = $transaction["remittanceInformationStructured"];
 | 
			
		||||
        else if (array_key_exists('remittanceInformationStructuredArray', $transaction))
 | 
			
		||||
            $description = implode('\n', $transaction["remittanceInformationStructuredArray"]);
 | 
			
		||||
        else if (array_key_exists('remittanceInformationUnstructured', $transaction))
 | 
			
		||||
            $description = $transaction["remittanceInformationUnstructured"];
 | 
			
		||||
        else if (array_key_exists('remittanceInformationUnstructuredArray', $transaction))
 | 
			
		||||
            $description = implode('\n', $transaction["remittanceInformationUnstructuredArray"]);
 | 
			
		||||
        else
 | 
			
		||||
            Log::warning("Missing description for the following transaction: " . json_encode($transaction));
 | 
			
		||||
 | 
			
		||||
        // participant
 | 
			
		||||
        $participant = array_key_exists('debtorAccount', $transaction) && array_key_exists('iban', $transaction["debtorAccount"]) ?
 | 
			
		||||
            $transaction['debtorAccount']['iban'] :
 | 
			
		||||
            (array_key_exists('creditorAccount', $transaction) && array_key_exists('iban', $transaction["creditorAccount"]) ?
 | 
			
		||||
                $transaction['creditorAccount']['iban'] : null);
 | 
			
		||||
        $participant_name = array_key_exists('debtorName', $transaction) ?
 | 
			
		||||
            $transaction['debtorName'] :
 | 
			
		||||
            (array_key_exists('creditorName', $transaction) ?
 | 
			
		||||
                $transaction['creditorName'] : null);
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            'transaction_id' => $transaction["transactionId"],
 | 
			
		||||
            'amount' => abs((int) $transaction["transactionAmount"]["amount"]),
 | 
			
		||||
            'currency_id' => $this->convertCurrency($transaction["transactionAmount"]["currency"]),
 | 
			
		||||
            'category_id' => null,
 | 
			
		||||
            'category_type' => array_key_exists('additionalInformation', $transaction) ? $transaction["additionalInformation"] : '',
 | 
			
		||||
            'date' => $transaction["bookingDate"],
 | 
			
		||||
            'description' => $description,
 | 
			
		||||
            'participant' => $participant,
 | 
			
		||||
            'participant_name' => $participant_name,
 | 
			
		||||
            'base_type' => (int) $transaction["transactionAmount"]["amount"] <= 0 ? 'DEBIT' : 'CREDIT',
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function convertCurrency(string $code)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        $currencies = Cache::get('currencies');
 | 
			
		||||
 | 
			
		||||
        if (!$currencies) {
 | 
			
		||||
            $this->buildCache(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $currency = $currencies->filter(function ($item) use ($code) {
 | 
			
		||||
            return $item->code == $code;
 | 
			
		||||
        })->first();
 | 
			
		||||
 | 
			
		||||
        if ($currency)
 | 
			
		||||
            return $currency->id;
 | 
			
		||||
 | 
			
		||||
        return 1;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										41
									
								
								app/Helpers/Encrypt/Secure.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								app/Helpers/Encrypt/Secure.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Invoice Ninja (https://invoiceninja.com).
 | 
			
		||||
 *
 | 
			
		||||
 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
 | 
			
		||||
 *
 | 
			
		||||
 * @license https://www.elastic.co/licensing/elastic-license
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace App\Helpers\Encrypt;
 | 
			
		||||
 | 
			
		||||
class Secure
 | 
			
		||||
{
 | 
			
		||||
    public static function encrypt(string $hash): ?string
 | 
			
		||||
    {
 | 
			
		||||
        $data = null;
 | 
			
		||||
 | 
			
		||||
        $public_key = openssl_pkey_get_public(config('ninja.encryption.public_key'));
 | 
			
		||||
 | 
			
		||||
        if (openssl_public_encrypt($hash, $encrypted, $public_key)) {
 | 
			
		||||
            $data = base64_encode($encrypted);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function decrypt(string $hash): ?string
 | 
			
		||||
    {
 | 
			
		||||
        $data = null;
 | 
			
		||||
 | 
			
		||||
        $private_key = openssl_pkey_get_private(config('ninja.encryption.private_key'));
 | 
			
		||||
 | 
			
		||||
        if (openssl_private_decrypt(base64_decode($hash), $decrypted, $private_key)) {
 | 
			
		||||
            $data = $decrypted;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $data;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -11,17 +11,18 @@
 | 
			
		||||
 | 
			
		||||
namespace App\Http\Controllers;
 | 
			
		||||
 | 
			
		||||
use App\Http\Requests\Account\CreateAccountRequest;
 | 
			
		||||
use App\Http\Requests\Account\UpdateAccountRequest;
 | 
			
		||||
use App\Jobs\Account\CreateAccount;
 | 
			
		||||
use App\Libraries\MultiDB;
 | 
			
		||||
use App\Models\Account;
 | 
			
		||||
use App\Libraries\MultiDB;
 | 
			
		||||
use App\Utils\TruthSource;
 | 
			
		||||
use App\Models\CompanyUser;
 | 
			
		||||
use Illuminate\Http\Response;
 | 
			
		||||
use App\Helpers\Encrypt\Secure;
 | 
			
		||||
use App\Jobs\Account\CreateAccount;
 | 
			
		||||
use App\Transformers\AccountTransformer;
 | 
			
		||||
use App\Transformers\CompanyUserTransformer;
 | 
			
		||||
use App\Utils\TruthSource;
 | 
			
		||||
use Illuminate\Foundation\Bus\DispatchesJobs;
 | 
			
		||||
use Illuminate\Http\Response;
 | 
			
		||||
use App\Http\Requests\Account\CreateAccountRequest;
 | 
			
		||||
use App\Http\Requests\Account\UpdateAccountRequest;
 | 
			
		||||
 | 
			
		||||
class AccountController extends BaseController
 | 
			
		||||
{
 | 
			
		||||
@ -65,6 +66,33 @@ class AccountController extends BaseController
 | 
			
		||||
     */
 | 
			
		||||
    public function store(CreateAccountRequest $request)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        if($request->has('cf-turnstile-response') && config('ninja.cloudflare.turnstile.secret')) {
 | 
			
		||||
            $r = \Illuminate\Support\Facades\Http::post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
 | 
			
		||||
                'secret' => config('ninja.cloudflare.turnstile.secret'),
 | 
			
		||||
                'response' => $request->input('cf-turnstile-response'),
 | 
			
		||||
                'remoteip' => $request->getClientIp(),
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            if($r->successful()){
 | 
			
		||||
 | 
			
		||||
                if($r->json()['success'] === true) {
 | 
			
		||||
                    // Captcha passed
 | 
			
		||||
                } else {
 | 
			
		||||
                    return response()->json(['message' => 'Captcha Failed'], 400);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if($request->has('hash') && config('ninja.cloudflare.turnstile.secret')) { //@todo once all platforms are implemented, we disable access to the rest of this route without a success response.
 | 
			
		||||
            
 | 
			
		||||
            if(Secure::decrypt($request->input('hash')) !== $request->input('email')) {
 | 
			
		||||
                return response()->json(['message' => 'Invalid Signup Payload'], 400);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $account = (new CreateAccount($request->all(), $request->getClientIp()))->handle();
 | 
			
		||||
        if (! ($account instanceof Account)) {
 | 
			
		||||
            return $account;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										308
									
								
								app/Http/Controllers/Bank/NordigenController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										308
									
								
								app/Http/Controllers/Bank/NordigenController.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,308 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Invoice Ninja (https://invoiceninja.com).
 | 
			
		||||
 *
 | 
			
		||||
 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
 | 
			
		||||
 *
 | 
			
		||||
 * @license https://www.elastic.co/licensing/elastic-license
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace App\Http\Controllers\Bank;
 | 
			
		||||
 | 
			
		||||
use App\Helpers\Bank\Nordigen\Nordigen;
 | 
			
		||||
use App\Http\Controllers\BaseController;
 | 
			
		||||
use App\Http\Requests\Nordigen\ConfirmNordigenBankIntegrationRequest;
 | 
			
		||||
use App\Http\Requests\Nordigen\ConnectNordigenBankIntegrationRequest;
 | 
			
		||||
use App\Jobs\Bank\ProcessBankTransactionsNordigen;
 | 
			
		||||
use App\Models\BankIntegration;
 | 
			
		||||
use App\Utils\Ninja;
 | 
			
		||||
use Cache;
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
use Nordigen\NordigenPHP\Exceptions\NordigenExceptions\NordigenException;
 | 
			
		||||
 | 
			
		||||
class NordigenController extends BaseController
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * VIEW: Connect Nordigen Bank Integration
 | 
			
		||||
     * @param ConnectNordigenBankIntegrationRequest $request
 | 
			
		||||
     */
 | 
			
		||||
    public function connect(ConnectNordigenBankIntegrationRequest $request)
 | 
			
		||||
    {
 | 
			
		||||
        $data = $request->all();
 | 
			
		||||
 | 
			
		||||
        /** @var array $context */
 | 
			
		||||
        $context = $request->getTokenContent();
 | 
			
		||||
        $company = $request->getCompany();
 | 
			
		||||
        $lang = $company->locale();
 | 
			
		||||
        $context["lang"] = $lang;
 | 
			
		||||
 | 
			
		||||
        if (!$context)
 | 
			
		||||
            return view('bank.nordigen.handler', [
 | 
			
		||||
                'lang' => $lang,
 | 
			
		||||
                'failed_reason' => "token-invalid",
 | 
			
		||||
                "redirectUrl" => config("ninja.app_url") . "?action=nordigen_connect&status=failed&reason=token-invalid",
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
        $context["redirect"] = $data["redirect"];
 | 
			
		||||
        if ($context["context"] != "nordigen" || array_key_exists("requisitionId", $context))
 | 
			
		||||
            return view('bank.nordigen.handler', [
 | 
			
		||||
                'lang' => $lang,
 | 
			
		||||
                'failed_reason' => "token-invalid",
 | 
			
		||||
                "redirectUrl" => ($context["redirect"]) . "?action=nordigen_connect&status=failed&reason=token-invalid",
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
        $company = $request->getCompany();
 | 
			
		||||
        $account = $company->account;
 | 
			
		||||
 | 
			
		||||
        if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))
 | 
			
		||||
            return view('bank.nordigen.handler', [
 | 
			
		||||
                'lang' => $lang,
 | 
			
		||||
                'company' => $company,
 | 
			
		||||
                'account' => $company->account,
 | 
			
		||||
                'failed_reason' => "account-config-invalid",
 | 
			
		||||
                "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=account-config-invalid",
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
        if (!(Ninja::isSelfHost() || (Ninja::isHosted() && $account->isEnterprisePaidClient())))
 | 
			
		||||
            return view('bank.nordigen.handler', [
 | 
			
		||||
                'lang' => $lang,
 | 
			
		||||
                'company' => $company,
 | 
			
		||||
                'account' => $company->account,
 | 
			
		||||
                'failed_reason' => "not-available",
 | 
			
		||||
                "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=not-available",
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
        $nordigen = new Nordigen();
 | 
			
		||||
 | 
			
		||||
        // show bank_selection_screen, when institution_id is not present
 | 
			
		||||
        if (!array_key_exists("institution_id", $data))
 | 
			
		||||
            return view('bank.nordigen.handler', [
 | 
			
		||||
                'lang' => $lang,
 | 
			
		||||
                'company' => $company,
 | 
			
		||||
                'account' => $company->account,
 | 
			
		||||
                'institutions' => $nordigen->getInstitutions(),
 | 
			
		||||
                'redirectUrl' => $context["redirect"] . "?action=nordigen_connect&status=user-aborted"
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
        // redirect to requisition flow
 | 
			
		||||
        try {
 | 
			
		||||
            $requisition = $nordigen->createRequisition(config('ninja.app_url') . '/nordigen/confirm', $data['institution_id'], $request->token, $lang);
 | 
			
		||||
        } catch (NordigenException $e) { // TODO: property_exists returns null in these cases... => why => therefore we just get unknown error everytime $responseBody is typeof GuzzleHttp\Psr7\Stream
 | 
			
		||||
            $responseBody = (string) $e->getResponse()->getBody();
 | 
			
		||||
 | 
			
		||||
            if (str_contains($responseBody, '"institution_id"')) // provided institution_id was wrong
 | 
			
		||||
                return view('bank.nordigen.handler', [
 | 
			
		||||
                    'lang' => $lang,
 | 
			
		||||
                    'company' => $company,
 | 
			
		||||
                    'account' => $company->account,
 | 
			
		||||
                    'failed_reason' => "institution-invalid",
 | 
			
		||||
                    "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=institution-invalid",
 | 
			
		||||
                ]);
 | 
			
		||||
            else if (str_contains($responseBody, '"reference"')) // this error can occur, when a reference was used double or is invalid => therefor we suggest the frontend to use another token
 | 
			
		||||
                return view('bank.nordigen.handler', [
 | 
			
		||||
                    'lang' => $lang,
 | 
			
		||||
                    'company' => $company,
 | 
			
		||||
                    'account' => $company->account,
 | 
			
		||||
                    'failed_reason' => "token-invalid",
 | 
			
		||||
                    "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=token-invalid",
 | 
			
		||||
                ]);
 | 
			
		||||
            else {
 | 
			
		||||
                nlog("Unknown Error from nordigen: " . $e);
 | 
			
		||||
                nlog($responseBody);
 | 
			
		||||
 | 
			
		||||
                return view('bank.nordigen.handler', [
 | 
			
		||||
                    'lang' => $lang,
 | 
			
		||||
                    'company' => $company,
 | 
			
		||||
                    'account' => $company->account,
 | 
			
		||||
                    'failed_reason' => "unknown",
 | 
			
		||||
                    "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=unknown",
 | 
			
		||||
                ]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // save cache
 | 
			
		||||
        $context["requisitionId"] = $requisition["id"];
 | 
			
		||||
        Cache::put($request->token, $context, 3600);
 | 
			
		||||
 | 
			
		||||
        return response()->redirectTo($requisition["link"]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * VIEW: Confirm Nordigen Bank Integration (redirect after nordigen flow)
 | 
			
		||||
     * @param ConfirmNordigenBankIntegrationRequest $request
 | 
			
		||||
     */
 | 
			
		||||
    public function confirm(ConfirmNordigenBankIntegrationRequest $request)
 | 
			
		||||
    {
 | 
			
		||||
        $data = $request->all();
 | 
			
		||||
        $company = $request->getCompany();
 | 
			
		||||
        $account = $company->account;
 | 
			
		||||
        $lang = $company->locale();
 | 
			
		||||
 | 
			
		||||
        /** @var array $context */
 | 
			
		||||
        $context = $request->getTokenContent();
 | 
			
		||||
        if (!array_key_exists('lang', $data) && $context['lang'] != 'en')
 | 
			
		||||
            return redirect()->route('nordigen.confirm', array_merge(["lang" => $context['lang']], $request->query()));
 | 
			
		||||
 | 
			
		||||
        if (!$context || $context["context"] != "nordigen" || !array_key_exists("requisitionId", $context))
 | 
			
		||||
            return view('bank.nordigen.handler', [
 | 
			
		||||
                'lang' => $lang,
 | 
			
		||||
                'failed_reason' => "ref-invalid",
 | 
			
		||||
                "redirectUrl" => ($context && array_key_exists("redirect", $context) ? $context["redirect"] : config('ninja.app_url')) . "?action=nordigen_connect&status=failed&reason=ref-invalid",
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
        if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))
 | 
			
		||||
            return view('bank.nordigen.handler', [
 | 
			
		||||
                'lang' => $lang,
 | 
			
		||||
                'company' => $company,
 | 
			
		||||
                'account' => $company->account,
 | 
			
		||||
                'failed_reason' => "account-config-invalid",
 | 
			
		||||
                "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=account-config-invalid",
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
        if (!(Ninja::isSelfHost() || (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise')))
 | 
			
		||||
            return view('bank.nordigen.handler', [
 | 
			
		||||
                'lang' => $lang,
 | 
			
		||||
                'company' => $company,
 | 
			
		||||
                'account' => $company->account,
 | 
			
		||||
                'failed_reason' => "not-available",
 | 
			
		||||
                "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=not-available",
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
        // fetch requisition
 | 
			
		||||
        $nordigen = new Nordigen();
 | 
			
		||||
        $requisition = $nordigen->getRequisition($context["requisitionId"]);
 | 
			
		||||
 | 
			
		||||
        // check validity of requisition
 | 
			
		||||
        if (!$requisition)
 | 
			
		||||
            return view('bank.nordigen.handler', [
 | 
			
		||||
                'lang' => $lang,
 | 
			
		||||
                'company' => $company,
 | 
			
		||||
                'account' => $company->account,
 | 
			
		||||
                'failed_reason' => "requisition-not-found",
 | 
			
		||||
                "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=requisition-not-found",
 | 
			
		||||
            ]);
 | 
			
		||||
        if ($requisition["status"] != "LN")
 | 
			
		||||
            return view('bank.nordigen.handler', [
 | 
			
		||||
                'lang' => $lang,
 | 
			
		||||
                'company' => $company,
 | 
			
		||||
                'account' => $company->account,
 | 
			
		||||
                'failed_reason' => "requisition-invalid-status",
 | 
			
		||||
                "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=requisition-invalid-status&status=" . $requisition["status"],
 | 
			
		||||
            ]);
 | 
			
		||||
        if (sizeof($requisition["accounts"]) == 0)
 | 
			
		||||
            return view('bank.nordigen.handler', [
 | 
			
		||||
                'lang' => $lang,
 | 
			
		||||
                'company' => $company,
 | 
			
		||||
                'account' => $company->account,
 | 
			
		||||
                'failed_reason' => "requisition-no-accounts",
 | 
			
		||||
                "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=requisition-no-accounts",
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
        // connect new accounts
 | 
			
		||||
        $bank_integration_ids = [];
 | 
			
		||||
        foreach ($requisition["accounts"] as $nordigenAccountId) {
 | 
			
		||||
 | 
			
		||||
            $nordigen_account = $nordigen->getAccount($nordigenAccountId);
 | 
			
		||||
 | 
			
		||||
            $existing_bank_integration = BankIntegration::withTrashed()->where('nordigen_account_id', $nordigen_account['id'])->where('company_id', $company->id)->first();
 | 
			
		||||
 | 
			
		||||
            if (!$existing_bank_integration) {
 | 
			
		||||
 | 
			
		||||
                $bank_integration = new BankIntegration();
 | 
			
		||||
                $bank_integration->integration_type = BankIntegration::INTEGRATION_TYPE_NORDIGEN;
 | 
			
		||||
                $bank_integration->company_id = $company->id;
 | 
			
		||||
                $bank_integration->account_id = $company->account_id;
 | 
			
		||||
                $bank_integration->user_id = $company->owner()->id;
 | 
			
		||||
                $bank_integration->nordigen_account_id = $nordigen_account['id'];
 | 
			
		||||
                $bank_integration->bank_account_type = $nordigen_account['account_type'];
 | 
			
		||||
                $bank_integration->bank_account_name = $nordigen_account['account_name'];
 | 
			
		||||
                $bank_integration->bank_account_status = $nordigen_account['account_status'];
 | 
			
		||||
                $bank_integration->bank_account_number = $nordigen_account['account_number'];
 | 
			
		||||
                $bank_integration->nordigen_institution_id = $nordigen_account['provider_id'];
 | 
			
		||||
                $bank_integration->provider_name = $nordigen_account['provider_name'];
 | 
			
		||||
                $bank_integration->nickname = $nordigen_account['nickname'];
 | 
			
		||||
                $bank_integration->balance = $nordigen_account['current_balance'];
 | 
			
		||||
                $bank_integration->currency = $nordigen_account['account_currency'];
 | 
			
		||||
                $bank_integration->disabled_upstream = false;
 | 
			
		||||
                $bank_integration->auto_sync = true;
 | 
			
		||||
                $bank_integration->from_date = now()->subDays(90); // default max-fetch interval of nordigen is 90 days
 | 
			
		||||
 | 
			
		||||
                $bank_integration->save();
 | 
			
		||||
 | 
			
		||||
                array_push($bank_integration_ids, $bank_integration->id);
 | 
			
		||||
 | 
			
		||||
            } else {
 | 
			
		||||
 | 
			
		||||
                // resetting metadata for account status
 | 
			
		||||
                $existing_bank_integration->balance = $account['current_balance'];
 | 
			
		||||
                $existing_bank_integration->bank_account_status = $account['account_status'];
 | 
			
		||||
                $existing_bank_integration->disabled_upstream = false;
 | 
			
		||||
                $existing_bank_integration->auto_sync = true;
 | 
			
		||||
                $existing_bank_integration->from_date = now()->subDays(90); // default max-fetch interval of nordigen is 90 days
 | 
			
		||||
                $existing_bank_integration->deleted_at = null;
 | 
			
		||||
 | 
			
		||||
                $existing_bank_integration->save();
 | 
			
		||||
 | 
			
		||||
                array_push($bank_integration_ids, $existing_bank_integration->id);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // perform update in background
 | 
			
		||||
        $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->each(function ($bank_integration) {
 | 
			
		||||
            ProcessBankTransactionsNordigen::dispatch($bank_integration);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // prevent rerun of this method with same ref
 | 
			
		||||
        Cache::delete($data["ref"]);
 | 
			
		||||
 | 
			
		||||
        // Successfull Response => Redirect
 | 
			
		||||
        return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=success&bank_integrations=" . implode(',', $bank_integration_ids));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Process Nordigen Institutions GETTER.
 | 
			
		||||
     *
 | 
			
		||||
     *
 | 
			
		||||
     * @OA\Post(
 | 
			
		||||
     *      path="/api/v1/nordigen/institutions",
 | 
			
		||||
     *      operationId="nordigenRefreshWebhook",
 | 
			
		||||
     *      tags={"nordigen"},
 | 
			
		||||
     *      summary="Getting available institutions from nordigen",
 | 
			
		||||
     *      description="Used to determine the available institutions for sending and creating a new connect-link",
 | 
			
		||||
     *      @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
 | 
			
		||||
     *      @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
 | 
			
		||||
     *      @OA\Parameter(ref="#/components/parameters/include"),
 | 
			
		||||
     *      @OA\Response(
 | 
			
		||||
     *          response=200,
 | 
			
		||||
     *          description="",
 | 
			
		||||
     *          @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
 | 
			
		||||
     *          @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
 | 
			
		||||
     *          @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
 | 
			
		||||
     *          @OA\JsonContent(ref="#/components/schemas/Credit"),
 | 
			
		||||
     *       ),
 | 
			
		||||
     *       @OA\Response(
 | 
			
		||||
     *          response=422,
 | 
			
		||||
     *          description="Validation error",
 | 
			
		||||
     *          @OA\JsonContent(ref="#/components/schemas/ValidationError"),
 | 
			
		||||
     *
 | 
			
		||||
     *       ),
 | 
			
		||||
     *       @OA\Response(
 | 
			
		||||
     *           response="default",
 | 
			
		||||
     *           description="Unexpected Error",
 | 
			
		||||
     *           @OA\JsonContent(ref="#/components/schemas/Error"),
 | 
			
		||||
     *       ),
 | 
			
		||||
     *     )
 | 
			
		||||
     */
 | 
			
		||||
    public function institutions(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))
 | 
			
		||||
            return response()->json(['message' => 'Not yet authenticated with Nordigen Bank Integration service'], 400);
 | 
			
		||||
 | 
			
		||||
        $nordigen = new Nordigen();
 | 
			
		||||
        return response()->json($nordigen->getInstitutions());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -16,7 +16,7 @@ use App\Helpers\Bank\Yodlee\Yodlee;
 | 
			
		||||
use App\Http\Controllers\BaseController;
 | 
			
		||||
use App\Http\Requests\Yodlee\YodleeAdminRequest;
 | 
			
		||||
use App\Http\Requests\Yodlee\YodleeAuthRequest;
 | 
			
		||||
use App\Jobs\Bank\ProcessBankTransactions;
 | 
			
		||||
use App\Jobs\Bank\ProcessBankTransactionsYodlee;
 | 
			
		||||
use App\Models\BankIntegration;
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
 | 
			
		||||
@ -44,7 +44,7 @@ class YodleeController extends BaseController
 | 
			
		||||
 | 
			
		||||
            $company->push();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $yodlee = new Yodlee($token);
 | 
			
		||||
 | 
			
		||||
        if ($request->has('window_closed') && $request->input("window_closed") == "true") {
 | 
			
		||||
@ -90,6 +90,7 @@ class YodleeController extends BaseController
 | 
			
		||||
                $bank_integration->balance = $account['current_balance'];
 | 
			
		||||
                $bank_integration->currency = $account['account_currency'];
 | 
			
		||||
                $bank_integration->from_date = now()->subYear();
 | 
			
		||||
 | 
			
		||||
                $bank_integration->auto_sync = true;
 | 
			
		||||
 | 
			
		||||
                $bank_integration->save();
 | 
			
		||||
@ -97,47 +98,45 @@ class YodleeController extends BaseController
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        $company->account->bank_integrations->each(function ($bank_integration) use ($company) {
 | 
			
		||||
            ProcessBankTransactions::dispatch($company->account->bank_integration_account_id, $bank_integration);
 | 
			
		||||
        $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->each(function ($bank_integration) use ($company) { // TODO: filter to yodlee only
 | 
			
		||||
            ProcessBankTransactionsYodlee::dispatch($company->account->id, $bank_integration);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
        * Process Yodlee Refresh Webhook.
 | 
			
		||||
        *
 | 
			
		||||
        *
 | 
			
		||||
        * @OA\Post(
 | 
			
		||||
        *      path="/api/v1/yodlee/refresh",
 | 
			
		||||
        *      operationId="yodleeRefreshWebhook",
 | 
			
		||||
        *      tags={"yodlee"},
 | 
			
		||||
        *      summary="Processing webhooks from Yodlee",
 | 
			
		||||
        *      description="Notifies the system when a data point can be refreshed",
 | 
			
		||||
        *      @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
 | 
			
		||||
        *      @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
 | 
			
		||||
        *      @OA\Parameter(ref="#/components/parameters/include"),
 | 
			
		||||
        *      @OA\Response(
 | 
			
		||||
        *          response=200,
 | 
			
		||||
        *          description="",
 | 
			
		||||
        *          @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
 | 
			
		||||
        *          @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
 | 
			
		||||
        *          @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
 | 
			
		||||
        *          @OA\JsonContent(ref="#/components/schemas/Credit"),
 | 
			
		||||
        *       ),
 | 
			
		||||
        *       @OA\Response(
 | 
			
		||||
        *          response=422,
 | 
			
		||||
        *          description="Validation error",
 | 
			
		||||
        *          @OA\JsonContent(ref="#/components/schemas/ValidationError"),
 | 
			
		||||
        *
 | 
			
		||||
        *       ),
 | 
			
		||||
        *       @OA\Response(
 | 
			
		||||
        *           response="default",
 | 
			
		||||
        *           description="Unexpected Error",
 | 
			
		||||
        *           @OA\JsonContent(ref="#/components/schemas/Error"),
 | 
			
		||||
        *       ),
 | 
			
		||||
        *     )
 | 
			
		||||
        */
 | 
			
		||||
 | 
			
		||||
     * Process Yodlee Refresh Webhook.
 | 
			
		||||
     *
 | 
			
		||||
     *
 | 
			
		||||
     * @OA\Post(
 | 
			
		||||
     *      path="/api/v1/yodlee/refresh",
 | 
			
		||||
     *      operationId="yodleeRefreshWebhook",
 | 
			
		||||
     *      tags={"yodlee"},
 | 
			
		||||
     *      summary="Processing webhooks from Yodlee",
 | 
			
		||||
     *      description="Notifies the system when a data point can be refreshed",
 | 
			
		||||
     *      @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
 | 
			
		||||
     *      @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
 | 
			
		||||
     *      @OA\Parameter(ref="#/components/parameters/include"),
 | 
			
		||||
     *      @OA\Response(
 | 
			
		||||
     *          response=200,
 | 
			
		||||
     *          description="",
 | 
			
		||||
     *          @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
 | 
			
		||||
     *          @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
 | 
			
		||||
     *          @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
 | 
			
		||||
     *          @OA\JsonContent(ref="#/components/schemas/Credit"),
 | 
			
		||||
     *       ),
 | 
			
		||||
     *       @OA\Response(
 | 
			
		||||
     *          response=422,
 | 
			
		||||
     *          description="Validation error",
 | 
			
		||||
     *          @OA\JsonContent(ref="#/components/schemas/ValidationError"),
 | 
			
		||||
     *
 | 
			
		||||
     *       ),
 | 
			
		||||
     *       @OA\Response(
 | 
			
		||||
     *           response="default",
 | 
			
		||||
     *           description="Unexpected Error",
 | 
			
		||||
     *           @OA\JsonContent(ref="#/components/schemas/Error"),
 | 
			
		||||
     *       ),
 | 
			
		||||
     *     )
 | 
			
		||||
     */
 | 
			
		||||
    /*
 | 
			
		||||
    {
 | 
			
		||||
      "event":{
 | 
			
		||||
@ -174,12 +173,12 @@ class YodleeController extends BaseController
 | 
			
		||||
        // nlog($request->all());
 | 
			
		||||
 | 
			
		||||
        return response()->json(['message' => 'Success'], 200);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        //
 | 
			
		||||
 | 
			
		||||
        // return response()->json(['message' => 'Unauthorized'], 403);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    {
 | 
			
		||||
       "event":{
 | 
			
		||||
@ -208,12 +207,12 @@ class YodleeController extends BaseController
 | 
			
		||||
        nlog($request->all());
 | 
			
		||||
 | 
			
		||||
        return response()->json(['message' => 'Success'], 200);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        //
 | 
			
		||||
 | 
			
		||||
        // return response()->json(['message' => 'Unauthorized'], 403);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    {
 | 
			
		||||
       "event":{
 | 
			
		||||
@ -244,7 +243,7 @@ class YodleeController extends BaseController
 | 
			
		||||
        // nlog($request->all());
 | 
			
		||||
 | 
			
		||||
        return response()->json(['message' => 'Success'], 200);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        //
 | 
			
		||||
 | 
			
		||||
        // return response()->json(['message' => 'Unauthorized'], 403);
 | 
			
		||||
@ -278,7 +277,7 @@ class YodleeController extends BaseController
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        return response()->json(['message' => 'Success'], 200);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        //
 | 
			
		||||
 | 
			
		||||
        // return response()->json(['message' => 'Unauthorized'], 403);
 | 
			
		||||
@ -290,19 +289,19 @@ class YodleeController extends BaseController
 | 
			
		||||
        $user = auth()->user();
 | 
			
		||||
 | 
			
		||||
        $bank_integration = BankIntegration::query()
 | 
			
		||||
                                           ->withTrashed()
 | 
			
		||||
                                           ->where('company_id', $user->company()->id)
 | 
			
		||||
                                           ->where('account_id', $account_number)
 | 
			
		||||
                                           ->exists();
 | 
			
		||||
            ->withTrashed()
 | 
			
		||||
            ->where('company_id', $user->company()->id)
 | 
			
		||||
            ->where('account_id', $account_number)
 | 
			
		||||
            ->exists();
 | 
			
		||||
 | 
			
		||||
        if(!$bank_integration) {
 | 
			
		||||
        if (!$bank_integration) {
 | 
			
		||||
            return response()->json(['message' => 'Account does not exist.'], 400);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $yodlee = new Yodlee($user->account->bank_integration_account_id);
 | 
			
		||||
 | 
			
		||||
        $summary = $yodlee->getAccountSummary($account_number);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $transformed_summary = AccountSummary::from($summary[0]);
 | 
			
		||||
 | 
			
		||||
        return response()->json($transformed_summary, 200);
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,7 @@ namespace App\Http\Controllers;
 | 
			
		||||
use App\Factory\BankIntegrationFactory;
 | 
			
		||||
use App\Filters\BankIntegrationFilters;
 | 
			
		||||
use App\Helpers\Bank\Yodlee\Yodlee;
 | 
			
		||||
use App\Helpers\Bank\Nordigen\Nordigen;
 | 
			
		||||
use App\Http\Requests\BankIntegration\AdminBankIntegrationRequest;
 | 
			
		||||
use App\Http\Requests\BankIntegration\BulkBankIntegrationRequest;
 | 
			
		||||
use App\Http\Requests\BankIntegration\CreateBankIntegrationRequest;
 | 
			
		||||
@ -22,10 +23,14 @@ use App\Http\Requests\BankIntegration\EditBankIntegrationRequest;
 | 
			
		||||
use App\Http\Requests\BankIntegration\ShowBankIntegrationRequest;
 | 
			
		||||
use App\Http\Requests\BankIntegration\StoreBankIntegrationRequest;
 | 
			
		||||
use App\Http\Requests\BankIntegration\UpdateBankIntegrationRequest;
 | 
			
		||||
use App\Jobs\Bank\ProcessBankTransactions;
 | 
			
		||||
use App\Jobs\Bank\ProcessBankTransactionsYodlee;
 | 
			
		||||
use App\Jobs\Bank\ProcessBankTransactionsNordigen;
 | 
			
		||||
use App\Models\Account;
 | 
			
		||||
use App\Models\BankIntegration;
 | 
			
		||||
use App\Models\User;
 | 
			
		||||
use App\Repositories\BankIntegrationRepository;
 | 
			
		||||
use App\Transformers\BankIntegrationTransformer;
 | 
			
		||||
use App\Utils\Ninja;
 | 
			
		||||
use App\Utils\Traits\MakesHash;
 | 
			
		||||
use Illuminate\Http\JsonResponse;
 | 
			
		||||
use Illuminate\Http\Response;
 | 
			
		||||
@ -168,13 +173,13 @@ class BankIntegrationController extends BaseController
 | 
			
		||||
        $action = request()->input('action');
 | 
			
		||||
 | 
			
		||||
        $ids = request()->input('ids');
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
        BankIntegration::withTrashed()->whereIn('id', $this->transformKeys($ids))
 | 
			
		||||
                        ->company()
 | 
			
		||||
                        ->cursor()
 | 
			
		||||
                        ->each(function ($bank_integration, $key) use ($action) {
 | 
			
		||||
                            $this->bank_integration_repo->{$action}($bank_integration);
 | 
			
		||||
                        });
 | 
			
		||||
            ->company()
 | 
			
		||||
            ->cursor()
 | 
			
		||||
            ->each(function ($bank_integration, $key) use ($action) {
 | 
			
		||||
                $this->bank_integration_repo->{$action}($bank_integration);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        /* Need to understand which permission are required for the given bulk action ie. view / edit */
 | 
			
		||||
 | 
			
		||||
@ -189,27 +194,45 @@ class BankIntegrationController extends BaseController
 | 
			
		||||
     */
 | 
			
		||||
    public function refreshAccounts(AdminBankIntegrationRequest $request)
 | 
			
		||||
    {
 | 
			
		||||
        // As yodlee is the first integration we don't need to perform switches yet, however
 | 
			
		||||
        // if we add additional providers we can reuse this class
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        /** @var \App\Models\User $user */
 | 
			
		||||
        $user = auth()->user();
 | 
			
		||||
 | 
			
		||||
        $user_account = $user->account;
 | 
			
		||||
 | 
			
		||||
        $bank_account_id = $user_account->bank_integration_account_id;
 | 
			
		||||
        $this->refreshAccountsYodlee($user);
 | 
			
		||||
 | 
			
		||||
        if (!$bank_account_id) {
 | 
			
		||||
            return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400);
 | 
			
		||||
        }
 | 
			
		||||
        $this->refreshAccountsNordigen($user);
 | 
			
		||||
 | 
			
		||||
        $yodlee = new Yodlee($bank_account_id);
 | 
			
		||||
        if (Cache::get("throttle_polling:{$user_account->key}"))
 | 
			
		||||
            return response()->json(BankIntegration::query()->company(), 200);
 | 
			
		||||
 | 
			
		||||
        // Processing transactions for each bank account
 | 
			
		||||
        if (Ninja::isHosted() && $user->account->bank_integration_account_id)
 | 
			
		||||
            $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->each(function ($bank_integration) use ($user_account) {
 | 
			
		||||
                ProcessBankTransactionsYodlee::dispatch($user_account->id, $bank_integration);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        if (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key') && (Ninja::isSelfHost() || (Ninja::isHosted() && $user_account->isEnterprisePaidClient())))
 | 
			
		||||
            $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function ($bank_integration) {
 | 
			
		||||
                ProcessBankTransactionsNordigen::dispatch($bank_integration);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        Cache::put("throttle_polling:{$user_account->key}", true, 300);
 | 
			
		||||
 | 
			
		||||
        return response()->json(BankIntegration::query()->company(), 200);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function refreshAccountsYodlee(User $user)
 | 
			
		||||
    {
 | 
			
		||||
        if (!Ninja::isHosted() || !$user->account->bank_integration_account_id)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        $yodlee = new Yodlee($user->account->bank_integration_account_id);
 | 
			
		||||
 | 
			
		||||
        $accounts = $yodlee->getAccounts();
 | 
			
		||||
 | 
			
		||||
        foreach ($accounts as $account) {
 | 
			
		||||
            if ($bi = BankIntegration::withTrashed()->where('bank_account_id', $account['id'])->where('company_id', $user->company()->id)->first()) {
 | 
			
		||||
            if ($bi = BankIntegration::withTrashed()->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->where('bank_account_id', $account['id'])->where('company_id', $user->company()->id)->first()) {
 | 
			
		||||
                $bi->balance = $account['current_balance'];
 | 
			
		||||
                $bi->currency = $account['account_currency'];
 | 
			
		||||
                $bi->save();
 | 
			
		||||
@ -229,22 +252,35 @@ class BankIntegrationController extends BaseController
 | 
			
		||||
                $bank_integration->balance = $account['current_balance'];
 | 
			
		||||
                $bank_integration->currency = $account['account_currency'];
 | 
			
		||||
                $bank_integration->auto_sync = true;
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                $bank_integration->save();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (Cache::get("throttle_polling:{$user_account->key}")) {
 | 
			
		||||
            return response()->json(BankIntegration::query()->company(), 200);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        $user_account->bank_integrations->each(function ($bank_integration) use ($user_account) {
 | 
			
		||||
            ProcessBankTransactions::dispatch($user_account->bank_integration_account_id, $bank_integration);
 | 
			
		||||
    private function refreshAccountsNordigen(User $user)
 | 
			
		||||
    {
 | 
			
		||||
        if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        $nordigen = new Nordigen();
 | 
			
		||||
 | 
			
		||||
        BankIntegration::where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->whereNotNull('nordigen_account_id')->each(function (BankIntegration $bank_integration) use ($nordigen) {
 | 
			
		||||
            $account = $nordigen->getAccount($bank_integration->nordigen_account_id);
 | 
			
		||||
            if (!$account) {
 | 
			
		||||
                $bank_integration->disabled_upstream = true;
 | 
			
		||||
 | 
			
		||||
                $bank_integration->save();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $bank_integration->disabled_upstream = false;
 | 
			
		||||
            $bank_integration->bank_account_status = $account['account_status'];
 | 
			
		||||
            $bank_integration->balance = $account['current_balance'];
 | 
			
		||||
            $bank_integration->currency = $account['account_currency'];
 | 
			
		||||
 | 
			
		||||
            $bank_integration->save();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        Cache::put("throttle_polling:{$user_account->key}", true, 300);
 | 
			
		||||
 | 
			
		||||
        return response()->json(BankIntegration::query()->company(), 200);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -262,23 +298,30 @@ class BankIntegrationController extends BaseController
 | 
			
		||||
 | 
			
		||||
        $account = $user->account;
 | 
			
		||||
 | 
			
		||||
        $bank_account_id = $account->bank_integration_account_id;
 | 
			
		||||
        $bank_integration = BankIntegration::withTrashed()
 | 
			
		||||
                                        ->where('bank_account_id', $acc_id)
 | 
			
		||||
                                        ->orWhere('nordigen_account_id', $acc_id)
 | 
			
		||||
                                        ->company()
 | 
			
		||||
                                        ->firstOrFail();
 | 
			
		||||
 | 
			
		||||
        if (!$bank_account_id) {
 | 
			
		||||
        if ($bank_integration->integration_type == BankIntegration::INTEGRATION_TYPE_YODLEE)
 | 
			
		||||
            $this->removeAccountYodlee($account, $bank_integration);
 | 
			
		||||
 | 
			
		||||
        $this->bank_integration_repo->delete($bank_integration);
 | 
			
		||||
 | 
			
		||||
        return $this->itemResponse($bank_integration->fresh());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function removeAccountYodlee(Account $account, BankIntegration $bank_integration)
 | 
			
		||||
    {
 | 
			
		||||
        if (!$account->bank_integration_account_id) {
 | 
			
		||||
            return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $bi = BankIntegration::withTrashed()->where('bank_account_id', $acc_id)->company()->firstOrFail();
 | 
			
		||||
 | 
			
		||||
        $yodlee = new Yodlee($bank_account_id);
 | 
			
		||||
        $res = $yodlee->deleteAccount($acc_id);
 | 
			
		||||
 | 
			
		||||
        $this->bank_integration_repo->delete($bi);
 | 
			
		||||
 | 
			
		||||
        return $this->itemResponse($bi->fresh());
 | 
			
		||||
        $yodlee = new Yodlee($account->bank_integration_account_id);
 | 
			
		||||
        $yodlee->deleteAccount($bank_integration->bank_account_id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return the remote list of accounts stored on the third party provider
 | 
			
		||||
     * and update our local cache.
 | 
			
		||||
@ -288,12 +331,20 @@ class BankIntegrationController extends BaseController
 | 
			
		||||
     */
 | 
			
		||||
    public function getTransactions(AdminBankIntegrationRequest $request)
 | 
			
		||||
    {
 | 
			
		||||
        /** @var \App\Models\User $user */
 | 
			
		||||
        $user = auth()->user();
 | 
			
		||||
        /** @var \App\Models\Account $account */
 | 
			
		||||
        $account = auth()->user()->account;
 | 
			
		||||
 | 
			
		||||
        $user->account->bank_integrations->each(function ($bank_integration) use ($user) {
 | 
			
		||||
            (new ProcessBankTransactions($user->account->bank_integration_account_id, $bank_integration))->handle();
 | 
			
		||||
        });
 | 
			
		||||
        if (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise') {
 | 
			
		||||
            $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) {
 | 
			
		||||
                (new ProcessBankTransactionsYodlee($account->id, $bank_integration))->handle();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (config("ninja.nordigen.secret_id") && config("ninja.nordigen.secret_key") && (Ninja::isSelfHost() || (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise'))) {
 | 
			
		||||
            $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->cursor()->each(function ($bank_integration) {
 | 
			
		||||
                (new ProcessBankTransactionsNordigen($bank_integration))->handle();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return response()->json(['message' => 'Fetching transactions....'], 200);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -167,8 +167,8 @@ class PaymentMethodController extends Controller
 | 
			
		||||
        if (request()->query('method') == GatewayType::BACS) {
 | 
			
		||||
            return $client_contact->client->getBACSGateway();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (in_array(request()->query('method'), [GatewayType::BANK_TRANSFER, GatewayType::DIRECT_DEBIT, GatewayType::SEPA])) {
 | 
			
		||||
        
 | 
			
		||||
        if (in_array(request()->query('method'), [GatewayType::BANK_TRANSFER, GatewayType::DIRECT_DEBIT, GatewayType::SEPA, GatewayType::ACSS])) {
 | 
			
		||||
            return $client_contact->client->getBankTransferGateway();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -11,36 +11,37 @@
 | 
			
		||||
 | 
			
		||||
namespace App\Http\Controllers;
 | 
			
		||||
 | 
			
		||||
use App\Events\Credit\CreditWasCreated;
 | 
			
		||||
use App\Events\Credit\CreditWasUpdated;
 | 
			
		||||
use App\Factory\CloneCreditFactory;
 | 
			
		||||
use App\Utils\Ninja;
 | 
			
		||||
use App\Models\Client;
 | 
			
		||||
use App\Models\Credit;
 | 
			
		||||
use App\Models\Account;
 | 
			
		||||
use App\Models\Invoice;
 | 
			
		||||
use App\Models\Webhook;
 | 
			
		||||
use Illuminate\Http\Response;
 | 
			
		||||
use App\Factory\CreditFactory;
 | 
			
		||||
use App\Filters\CreditFilters;
 | 
			
		||||
use App\Http\Requests\Credit\ActionCreditRequest;
 | 
			
		||||
use App\Jobs\Credit\ZipCredits;
 | 
			
		||||
use App\Utils\Traits\MakesHash;
 | 
			
		||||
use App\Jobs\Entity\EmailEntity;
 | 
			
		||||
use App\Factory\CloneCreditFactory;
 | 
			
		||||
use App\Services\PdfMaker\PdfMerge;
 | 
			
		||||
use Illuminate\Support\Facades\App;
 | 
			
		||||
use App\Utils\Traits\SavesDocuments;
 | 
			
		||||
use App\Repositories\CreditRepository;
 | 
			
		||||
use App\Events\Credit\CreditWasCreated;
 | 
			
		||||
use App\Events\Credit\CreditWasUpdated;
 | 
			
		||||
use App\Transformers\CreditTransformer;
 | 
			
		||||
use Illuminate\Support\Facades\Storage;
 | 
			
		||||
use App\Services\Template\TemplateAction;
 | 
			
		||||
use App\Http\Requests\Credit\BulkCreditRequest;
 | 
			
		||||
use App\Http\Requests\Credit\CreateCreditRequest;
 | 
			
		||||
use App\Http\Requests\Credit\DestroyCreditRequest;
 | 
			
		||||
use App\Http\Requests\Credit\EditCreditRequest;
 | 
			
		||||
use App\Http\Requests\Credit\ShowCreditRequest;
 | 
			
		||||
use App\Http\Requests\Credit\StoreCreditRequest;
 | 
			
		||||
use App\Http\Requests\Credit\ActionCreditRequest;
 | 
			
		||||
use App\Http\Requests\Credit\CreateCreditRequest;
 | 
			
		||||
use App\Http\Requests\Credit\UpdateCreditRequest;
 | 
			
		||||
use App\Http\Requests\Credit\UploadCreditRequest;
 | 
			
		||||
use App\Jobs\Credit\ZipCredits;
 | 
			
		||||
use App\Jobs\Entity\EmailEntity;
 | 
			
		||||
use App\Models\Account;
 | 
			
		||||
use App\Models\Client;
 | 
			
		||||
use App\Models\Credit;
 | 
			
		||||
use App\Models\Invoice;
 | 
			
		||||
use App\Repositories\CreditRepository;
 | 
			
		||||
use App\Services\PdfMaker\PdfMerge;
 | 
			
		||||
use App\Services\Template\TemplateAction;
 | 
			
		||||
use App\Transformers\CreditTransformer;
 | 
			
		||||
use App\Utils\Ninja;
 | 
			
		||||
use App\Utils\Traits\MakesHash;
 | 
			
		||||
use App\Utils\Traits\SavesDocuments;
 | 
			
		||||
use Illuminate\Http\Response;
 | 
			
		||||
use Illuminate\Support\Facades\App;
 | 
			
		||||
use Illuminate\Support\Facades\Storage;
 | 
			
		||||
use App\Http\Requests\Credit\DestroyCreditRequest;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class CreditController.
 | 
			
		||||
@ -153,6 +154,7 @@ class CreditController extends BaseController
 | 
			
		||||
        $user = auth()->user();
 | 
			
		||||
        
 | 
			
		||||
        $credit = CreditFactory::create($user->company()->id, $user->id);
 | 
			
		||||
        $credit->date = now()->addSeconds($user->company()->utc_offset())->format('Y-m-d');
 | 
			
		||||
 | 
			
		||||
        return $this->itemResponse($credit);
 | 
			
		||||
    }
 | 
			
		||||
@ -638,23 +640,14 @@ class CreditController extends BaseController
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case 'email':
 | 
			
		||||
 | 
			
		||||
                $credit->invitations->load('contact.client.country', 'credit.client.country', 'credit.company')->each(function ($invitation) use ($credit) {
 | 
			
		||||
                    EmailEntity::dispatch($invitation, $credit->company, 'credit');
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                if (! $bulk) {
 | 
			
		||||
                    return response()->json(['message'=>'email sent'], 200);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case 'send_email':
 | 
			
		||||
 | 
			
		||||
                $credit->invitations->load('contact.client.country', 'credit.client.country', 'credit.company')->each(function ($invitation) use ($credit) {
 | 
			
		||||
                    EmailEntity::dispatch($invitation, $credit->company, 'credit');
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                $credit->sendEvent(Webhook::EVENT_SENT_CREDIT, "client");
 | 
			
		||||
 | 
			
		||||
                if (! $bulk) {
 | 
			
		||||
                    return response()->json(['message'=>'email sent'], 200);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -11,25 +11,26 @@
 | 
			
		||||
 | 
			
		||||
namespace App\Http\Controllers;
 | 
			
		||||
 | 
			
		||||
use App\Events\Credit\CreditWasEmailed;
 | 
			
		||||
use App\Events\Quote\QuoteWasEmailed;
 | 
			
		||||
use App\Http\Requests\Email\SendEmailRequest;
 | 
			
		||||
use App\Jobs\PurchaseOrder\PurchaseOrderEmail;
 | 
			
		||||
use App\Utils\Ninja;
 | 
			
		||||
use App\Models\Quote;
 | 
			
		||||
use App\Models\Credit;
 | 
			
		||||
use App\Models\Invoice;
 | 
			
		||||
use App\Models\Webhook;
 | 
			
		||||
use App\Models\PurchaseOrder;
 | 
			
		||||
use App\Models\Quote;
 | 
			
		||||
use App\Models\RecurringInvoice;
 | 
			
		||||
use App\Services\Email\Email;
 | 
			
		||||
use App\Utils\Traits\MakesHash;
 | 
			
		||||
use App\Models\RecurringInvoice;
 | 
			
		||||
use App\Services\Email\EmailObject;
 | 
			
		||||
use App\Events\Quote\QuoteWasEmailed;
 | 
			
		||||
use App\Transformers\QuoteTransformer;
 | 
			
		||||
use Illuminate\Mail\Mailables\Address;
 | 
			
		||||
use App\Events\Credit\CreditWasEmailed;
 | 
			
		||||
use App\Transformers\CreditTransformer;
 | 
			
		||||
use App\Transformers\InvoiceTransformer;
 | 
			
		||||
use App\Http\Requests\Email\SendEmailRequest;
 | 
			
		||||
use App\Jobs\PurchaseOrder\PurchaseOrderEmail;
 | 
			
		||||
use App\Transformers\PurchaseOrderTransformer;
 | 
			
		||||
use App\Transformers\QuoteTransformer;
 | 
			
		||||
use App\Transformers\RecurringInvoiceTransformer;
 | 
			
		||||
use App\Utils\Ninja;
 | 
			
		||||
use App\Utils\Traits\MakesHash;
 | 
			
		||||
use Illuminate\Mail\Mailables\Address;
 | 
			
		||||
 | 
			
		||||
class EmailController extends BaseController
 | 
			
		||||
{
 | 
			
		||||
@ -100,6 +101,7 @@ class EmailController extends BaseController
 | 
			
		||||
 | 
			
		||||
            if ($entity_obj->invitations->count() >= 1) {
 | 
			
		||||
                $entity_obj->entityEmailEvent($entity_obj->invitations->first(), 'invoice', $template);
 | 
			
		||||
                $entity_obj->sendEvent(Webhook::EVENT_SENT_INVOICE, "client");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -109,6 +111,8 @@ class EmailController extends BaseController
 | 
			
		||||
 | 
			
		||||
            if ($entity_obj->invitations->count() >= 1) {
 | 
			
		||||
                event(new QuoteWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null), 'quote'));
 | 
			
		||||
                $entity_obj->sendEvent(Webhook::EVENT_SENT_QUOTE, "client");
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -118,6 +122,7 @@ class EmailController extends BaseController
 | 
			
		||||
 | 
			
		||||
            if ($entity_obj->invitations->count() >= 1) {
 | 
			
		||||
                event(new CreditWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null), 'credit'));
 | 
			
		||||
                $entity_obj->sendEvent(Webhook::EVENT_SENT_CREDIT, "client");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -143,7 +148,8 @@ class EmailController extends BaseController
 | 
			
		||||
        $data['template'] = $template;
 | 
			
		||||
        
 | 
			
		||||
        PurchaseOrderEmail::dispatch($entity_obj, $entity_obj->company, $data);
 | 
			
		||||
        
 | 
			
		||||
        $entity_obj->sendEvent(Webhook::EVENT_SENT_PURCHASE_ORDER, "vendor");
 | 
			
		||||
 | 
			
		||||
        return $this->itemResponse($entity_obj);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -166,6 +166,7 @@ class InvoiceController extends BaseController
 | 
			
		||||
        /** @var \App\Models\User $user */
 | 
			
		||||
        $user = auth()->user();
 | 
			
		||||
        $invoice = InvoiceFactory::create($user->company()->id, $user->id);
 | 
			
		||||
        $invoice->date = now()->addSeconds($user->company()->utc_offset())->format('Y-m-d');
 | 
			
		||||
 | 
			
		||||
        return $this->itemResponse($invoice);
 | 
			
		||||
    }
 | 
			
		||||
@ -538,8 +539,6 @@ class InvoiceController extends BaseController
 | 
			
		||||
                return (new \App\Jobs\Entity\CreateRawPdf($invoice->invitations->first()))->handle();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            return response()->streamDownload(function () use ($paths) {
 | 
			
		||||
                echo $merge = (new PdfMerge($paths->toArray()))->run();
 | 
			
		||||
            }, 'print.pdf', ['Content-Type' => 'application/pdf']);
 | 
			
		||||
 | 
			
		||||
@ -155,6 +155,7 @@ class PaymentController extends BaseController
 | 
			
		||||
        $user = auth()->user();
 | 
			
		||||
 | 
			
		||||
        $payment = PaymentFactory::create($user->company()->id, $user->id);
 | 
			
		||||
        $payment->date = now()->addSeconds($user->company()->utc_offset())->format('Y-m-d');
 | 
			
		||||
 | 
			
		||||
        return $this->itemResponse($payment);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -144,6 +144,7 @@ class PurchaseOrderController extends BaseController
 | 
			
		||||
        $user = auth()->user();
 | 
			
		||||
 | 
			
		||||
        $purchase_order = PurchaseOrderFactory::create($user->company()->id, $user->id);
 | 
			
		||||
        $purchase_order->date = now()->addSeconds($user->company()->utc_offset())->format('Y-m-d');
 | 
			
		||||
 | 
			
		||||
        return $this->itemResponse($purchase_order);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -168,6 +168,7 @@ class QuoteController extends BaseController
 | 
			
		||||
        $user = auth()->user();
 | 
			
		||||
 | 
			
		||||
        $quote = QuoteFactory::create($user->company()->id, $user->id);
 | 
			
		||||
        $quote->date = now()->addSeconds($user->company()->utc_offset())->format('Y-m-d');
 | 
			
		||||
 | 
			
		||||
        return $this->itemResponse($quote);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -30,6 +30,12 @@ class SubdomainController extends BaseController
 | 
			
		||||
            return response()->json(['message' => ctrans('texts.subdomain_is_not_available')], 401);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if (!preg_match('/^[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$/', request()->input('subdomain'))) {
 | 
			
		||||
            return response()->json(['message' => ctrans('texts.subdomain_is_not_available')], 401);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        return response()->json(['message' => 'Domain available'], 200);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,12 @@ use Twilio\Rest\Client;
 | 
			
		||||
 | 
			
		||||
class TwilioController extends BaseController
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    private array $invalid_codes = [
 | 
			
		||||
        '+21',
 | 
			
		||||
        '+17152567760',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct();
 | 
			
		||||
@ -36,8 +42,16 @@ class TwilioController extends BaseController
 | 
			
		||||
        /** @var \App\Models\User $user */
 | 
			
		||||
        $user = auth()->user();
 | 
			
		||||
 | 
			
		||||
        if(!$user->email_verified_at) {
 | 
			
		||||
            return response()->json(['message' => 'Please verify your email address before verifying your phone number'], 400);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $account = $user->company()->account;
 | 
			
		||||
 | 
			
		||||
        if(!$this->checkPhoneValidity($request->phone)) {
 | 
			
		||||
            return response()->json(['message' => 'This phone number is not supported'], 400);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (MultiDB::hasPhoneNumber($request->phone)) {
 | 
			
		||||
            return response()->json(['message' => 'This phone number has already been verified with another account'], 400);
 | 
			
		||||
        }
 | 
			
		||||
@ -65,6 +79,19 @@ class TwilioController extends BaseController
 | 
			
		||||
        return response()->json(['message' => 'Code sent.'], 200);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function checkPhoneValidity($phone)
 | 
			
		||||
    {
 | 
			
		||||
        foreach($this->invalid_codes as $code){
 | 
			
		||||
 | 
			
		||||
            if(stripos($phone, $code) !== false) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show the form for creating a new resource.
 | 
			
		||||
     *
 | 
			
		||||
@ -117,12 +144,24 @@ class TwilioController extends BaseController
 | 
			
		||||
     */
 | 
			
		||||
    public function generate2faResetCode(Generate2faRequest $request)
 | 
			
		||||
    {
 | 
			
		||||
        nlog($request->all());
 | 
			
		||||
        nlog($request->headers());
 | 
			
		||||
 | 
			
		||||
        $user = User::where('email', $request->email)->first();
 | 
			
		||||
 | 
			
		||||
        if (!$user) {
 | 
			
		||||
            return response()->json(['message' => 'Unable to retrieve user.'], 400);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!$user->email_verified_at) {
 | 
			
		||||
            return response()->json(['message' => 'Please verify your email address before verifying your phone number'], 400);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if(!$user->first_name || !$user->last_name) {
 | 
			
		||||
            return response()->json(['message' => 'Please update your first and/or last name in the User Details before verifying your number.'], 400);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!$user->phone || $user->phone == '') {
 | 
			
		||||
            return response()->json(['message' => 'User found, but no valid phone number on file, please contact support.'], 400);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -196,7 +196,7 @@ class UserController extends BaseController
 | 
			
		||||
     */
 | 
			
		||||
    public function destroy(DestroyUserRequest $request, User $user)
 | 
			
		||||
    {
 | 
			
		||||
        if ($user->isOwner()) {
 | 
			
		||||
        if ($user->hasOwnerFlag()) {
 | 
			
		||||
            return response()->json(['message', 'Cannot detach owner.'], 401);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -55,6 +55,8 @@ class PdfSlot extends Component
 | 
			
		||||
 | 
			
		||||
    public $is_quote = false;
 | 
			
		||||
 | 
			
		||||
    private $entity_calc;
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
    {
 | 
			
		||||
        MultiDB::setDb($this->db);
 | 
			
		||||
@ -123,6 +125,7 @@ class PdfSlot extends Component
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        $this->entity_type = $this->resolveEntityType();
 | 
			
		||||
        $this->entity_calc = $this->entity->calc();
 | 
			
		||||
 | 
			
		||||
        $this->settings = $this->entity->client ? $this->entity->client->getMergedSettings() : $this->entity->company->settings;
 | 
			
		||||
 | 
			
		||||
@ -149,6 +152,8 @@ class PdfSlot extends Component
 | 
			
		||||
            'services' => $this->getServices(),
 | 
			
		||||
            'amount' => Number::formatMoney($this->entity->amount, $this->entity->client ?: $this->entity->vendor),
 | 
			
		||||
            'balance' => Number::formatMoney($this->entity->balance, $this->entity->client ?: $this->entity->vendor),
 | 
			
		||||
            'discount' => $this->entity_calc->getTotalDiscount() > 0 ? Number::formatMoney($this->entity_calc->getTotalDiscount(), $this->entity->client ?: $this->entity->vendor) : false,
 | 
			
		||||
            'taxes' => $this->entity_calc->getTotalTaxes() > 0 ? Number::formatMoney($this->entity_calc->getTotalTaxes(), $this->entity->client ?: $this->entity->vendor) : false,
 | 
			
		||||
            'company_details' => $this->getCompanyDetails(),
 | 
			
		||||
            'company_address' => $this->getCompanyAddress(),
 | 
			
		||||
            'entity_details' => $this->getEntityDetails(),
 | 
			
		||||
@ -198,20 +203,20 @@ class PdfSlot extends Component
 | 
			
		||||
 | 
			
		||||
        if($this->entity_type == 'invoice' || $this->entity_type == 'recurring_invoice') {
 | 
			
		||||
            foreach($this->settings->pdf_variables->invoice_details as $variable) {
 | 
			
		||||
                $entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-5 w-36 block entity-field'>{$variable}</p></div>";
 | 
			
		||||
                $entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='ml-5 w-36 block entity-field'>{$variable}</p></div>";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } elseif($this->entity_type == 'quote') {
 | 
			
		||||
            foreach($this->settings->pdf_variables->quote_details ?? [] as $variable) {
 | 
			
		||||
                $entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-5 w-36 block entity-field'>{$variable}</p></div>";
 | 
			
		||||
                $entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='ml-5 w-36 block entity-field'>{$variable}</p></div>";
 | 
			
		||||
            }
 | 
			
		||||
        } elseif($this->entity_type == 'credit') {
 | 
			
		||||
            foreach($this->settings->pdf_variables->credit_details ?? [] as $variable) {
 | 
			
		||||
                $entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-5 w-36 block entity-field'>{$variable}</p></div>";
 | 
			
		||||
                $entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='ml-5 w-36 block entity-field'>{$variable}</p></div>";
 | 
			
		||||
            }
 | 
			
		||||
        } elseif($this->entity_type == 'purchase_order') {
 | 
			
		||||
            foreach($this->settings->pdf_variables->purchase_order_details ?? [] as $variable) {
 | 
			
		||||
                $entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-5 w-36 block entity-field'>{$variable}</p></div>";
 | 
			
		||||
                $entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='ml-5 w-36 block entity-field'>{$variable}</p></div>";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -305,6 +310,7 @@ class PdfSlot extends Component
 | 
			
		||||
            return 'purchase_order';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,10 @@ class BulkBankIntegrationRequest extends Request
 | 
			
		||||
     */
 | 
			
		||||
    public function authorize() : bool
 | 
			
		||||
    {
 | 
			
		||||
        return auth()->user()->isAdmin();
 | 
			
		||||
        /** @var \App\Models\User $user */
 | 
			
		||||
        $user = auth()->user();
 | 
			
		||||
 | 
			
		||||
        return $user->isAdmin();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function rules()
 | 
			
		||||
 | 
			
		||||
@ -61,7 +61,7 @@ class UpdateCompanyRequest extends Request
 | 
			
		||||
        // $rules['client_registration_fields'] = 'array';
 | 
			
		||||
 | 
			
		||||
        if (isset($input['portal_mode']) && ($input['portal_mode'] == 'domain' || $input['portal_mode'] == 'iframe')) {
 | 
			
		||||
            $rules['portal_domain'] = 'sometimes|url';
 | 
			
		||||
            $rules['portal_domain'] = 'bail|nullable|sometimes|url';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (Ninja::isHosted()) {
 | 
			
		||||
@ -118,7 +118,7 @@ class UpdateCompanyRequest extends Request
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isset($settings['email_style_custom'])) {
 | 
			
		||||
            $settings['email_style_custom'] = str_replace(['{{', '}}'], ['', ''], $settings['email_style_custom']);
 | 
			
		||||
            $settings['email_style_custom'] = str_replace(['{!!', '!!}', '{{', '}}', '@if(', '@endif', '@isset', '@unless', '@auth', '@empty', '@guest', '@env', '@section', '@switch', '@foreach', '@while', '@include', '@each', '@once', '@push', '@use', '@forelse', '@verbatim', '<?php', '@php', '@for'], '', $settings['email_style_custom']);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!$account->isFreeHostedClient()) {
 | 
			
		||||
 | 
			
		||||
@ -60,7 +60,7 @@ class UpdateCreditRequest extends Request
 | 
			
		||||
            $rules['file'] = $this->file_validation;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $rules['number'] = ['bail', 'sometimes', Rule::unique('credits')->where('company_id', $user->company()->id)->ignore($this->credit->id)];
 | 
			
		||||
        $rules['number'] = ['bail', 'sometimes', 'nullable', Rule::unique('credits')->where('company_id', $user->company()->id)->ignore($this->credit->id)];
 | 
			
		||||
        
 | 
			
		||||
        $rules['client_id'] = ['bail', 'sometimes',Rule::in([$this->credit->client_id])];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -11,17 +11,37 @@
 | 
			
		||||
 | 
			
		||||
namespace App\Http\Requests\Email;
 | 
			
		||||
 | 
			
		||||
use App\Http\Requests\Request;
 | 
			
		||||
use App\Utils\Ninja;
 | 
			
		||||
use App\Utils\Traits\MakesHash;
 | 
			
		||||
use Illuminate\Auth\Access\AuthorizationException;
 | 
			
		||||
use Illuminate\Support\Str;
 | 
			
		||||
use App\Http\Requests\Request;
 | 
			
		||||
use App\Utils\Traits\MakesHash;
 | 
			
		||||
use Illuminate\Validation\Rule;
 | 
			
		||||
use Illuminate\Auth\Access\AuthorizationException;
 | 
			
		||||
 | 
			
		||||
class SendEmailRequest extends Request
 | 
			
		||||
{
 | 
			
		||||
    use MakesHash;
 | 
			
		||||
 | 
			
		||||
    private string $entity_plural = '';
 | 
			
		||||
    private string $error_message = '';
 | 
			
		||||
 | 
			
		||||
    public array $templates = [
 | 
			
		||||
        'email_template_invoice',
 | 
			
		||||
        'email_template_quote',
 | 
			
		||||
        'email_template_credit',
 | 
			
		||||
        'email_template_payment',
 | 
			
		||||
        'email_template_payment_partial',
 | 
			
		||||
        'email_template_statement',
 | 
			
		||||
        'email_template_reminder1',
 | 
			
		||||
        'email_template_reminder2',
 | 
			
		||||
        'email_template_reminder3',
 | 
			
		||||
        'email_template_reminder_endless',
 | 
			
		||||
        'email_template_custom1',
 | 
			
		||||
        'email_template_custom2',
 | 
			
		||||
        'email_template_custom3',
 | 
			
		||||
        'email_template_purchase_order',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determine if the user is authorized to make this request.
 | 
			
		||||
     *
 | 
			
		||||
@ -39,14 +59,16 @@ class SendEmailRequest extends Request
 | 
			
		||||
     */
 | 
			
		||||
    public function rules()
 | 
			
		||||
    {
 | 
			
		||||
        /** @var \App\Models\User $user */
 | 
			
		||||
        $user = auth()->user();
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            'template' => 'bail|required',
 | 
			
		||||
            'entity' => 'bail|required',
 | 
			
		||||
            'entity_id' => 'bail|required',
 | 
			
		||||
            'template' => 'bail|required|in:'.implode(',', $this->templates),
 | 
			
		||||
            'entity' => 'bail|required|in:App\Models\Invoice,App\Models\Quote,App\Models\Credit,App\Models\RecurringInvoice,App\Models\PurchaseOrder,App\Models\Payment',
 | 
			
		||||
            'entity_id' => ['bail', 'required', Rule::exists($this->entity_plural, 'id')->where('company_id', $user->company()->id)],
 | 
			
		||||
            'cc_email.*' => 'bail|sometimes|email',
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function prepareForValidation()
 | 
			
		||||
@ -70,6 +92,8 @@ class SendEmailRequest extends Request
 | 
			
		||||
            $input['entity_id'] = $this->decodePrimaryKey($input['entity_id']);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $this->entity_plural = Str::plural($input['entity']) ?? '';
 | 
			
		||||
 | 
			
		||||
        if (isset($input['entity'])) {
 | 
			
		||||
            $input['entity'] = "App\Models\\".ucfirst(Str::camel($input['entity']));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -37,7 +37,17 @@ class ImportRequest extends Request
 | 
			
		||||
            'column_map' => 'required_with:hash|array',
 | 
			
		||||
            'skip_header' => 'required_with:hash|boolean',
 | 
			
		||||
            'files.*' => 'file|mimes:csv,txt',
 | 
			
		||||
            'bank_integration_id' => 'bail|required_if:column_map,bank_transaction|min:2'
 | 
			
		||||
            'bank_integration_id' => 'bail|required_with:column_map.bank_transaction|min:2'
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function prepareForValidation()
 | 
			
		||||
    {
 | 
			
		||||
        $input = $this->all();
 | 
			
		||||
 | 
			
		||||
        if(!isset($input['column_map']['bank_transaction']) && array_key_exists('bank_integration_id',$input)) 
 | 
			
		||||
            unset($input['bank_integration_id']);
 | 
			
		||||
 | 
			
		||||
        $this->replace($input);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -59,9 +59,8 @@ class UpdateInvoiceRequest extends Request
 | 
			
		||||
 | 
			
		||||
        $rules['id'] = new LockedInvoiceRule($this->invoice);
 | 
			
		||||
 | 
			
		||||
        $rules['number'] = ['bail', 'sometimes', Rule::unique('invoices')->where('company_id', $user->company()->id)->ignore($this->invoice->id)];
 | 
			
		||||
        $rules['number'] = ['bail', 'sometimes', 'nullable', Rule::unique('invoices')->where('company_id', $user->company()->id)->ignore($this->invoice->id)];
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $rules['is_amount_discount'] = ['boolean'];
 | 
			
		||||
        $rules['client_id'] = ['bail', 'sometimes', Rule::in([$this->invoice->client_id])];
 | 
			
		||||
        $rules['line_items'] = 'array';
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,58 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Invoice Ninja (https://invoiceninja.com).
 | 
			
		||||
 *
 | 
			
		||||
 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
 | 
			
		||||
 *
 | 
			
		||||
 * @license https://www.elastic.co/licensing/elastic-license
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace App\Http\Requests\Nordigen;
 | 
			
		||||
 | 
			
		||||
use App\Http\Requests\Request;
 | 
			
		||||
use App\Libraries\MultiDB;
 | 
			
		||||
use App\Models\Company;
 | 
			
		||||
use Cache;
 | 
			
		||||
 | 
			
		||||
class ConfirmNordigenBankIntegrationRequest extends Request
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Determine if the user is authorized to make this request.
 | 
			
		||||
     *
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    public function authorize()
 | 
			
		||||
    {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the validation rules that apply to the request.
 | 
			
		||||
     *
 | 
			
		||||
     * @return array
 | 
			
		||||
     */
 | 
			
		||||
    public function rules()
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            'ref' => 'required|string', // nordigen redirects only with the ref-property
 | 
			
		||||
            'lang' => 'string',
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
    public function getTokenContent()
 | 
			
		||||
    {
 | 
			
		||||
        $input = $this->all();
 | 
			
		||||
 | 
			
		||||
        $data = Cache::get($input['ref']);
 | 
			
		||||
 | 
			
		||||
        return $data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getCompany()
 | 
			
		||||
    {
 | 
			
		||||
        MultiDB::findAndSetDbByCompanyKey($this->getTokenContent()['company_key']);
 | 
			
		||||
 | 
			
		||||
        return Company::where('company_key', $this->getTokenContent()['company_key'])->firstOrFail();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,70 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Invoice Ninja (https://invoiceninja.com).
 | 
			
		||||
 *
 | 
			
		||||
 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
 | 
			
		||||
 *
 | 
			
		||||
 * @license https://www.elastic.co/licensing/elastic-license
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace App\Http\Requests\Nordigen;
 | 
			
		||||
 | 
			
		||||
use App\Http\Requests\Request;
 | 
			
		||||
use App\Libraries\MultiDB;
 | 
			
		||||
use App\Models\Company;
 | 
			
		||||
use Cache;
 | 
			
		||||
 | 
			
		||||
class ConnectNordigenBankIntegrationRequest extends Request
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Determine if the user is authorized to make this request.
 | 
			
		||||
     *
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    public function authorize()
 | 
			
		||||
    {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the validation rules that apply to the request.
 | 
			
		||||
     *
 | 
			
		||||
     * @return array
 | 
			
		||||
     */
 | 
			
		||||
    public function rules()
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function prepareForValidation()
 | 
			
		||||
    {
 | 
			
		||||
        $input = $this->all();
 | 
			
		||||
 | 
			
		||||
        $context = $this->getTokenContent();
 | 
			
		||||
 | 
			
		||||
        $input["redirect"] = isset($context["is_react"]) && $context['is_react'] ? config('ninja.react_url') . "/#/settings/bank_accounts" : config('ninja.app_url');
 | 
			
		||||
 | 
			
		||||
        $this->replace($input);
 | 
			
		||||
       
 | 
			
		||||
    }
 | 
			
		||||
    public function getTokenContent()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->state) {
 | 
			
		||||
            $this->token = $this->state;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $data = Cache::get($this->token);
 | 
			
		||||
 | 
			
		||||
        return $data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getCompany()
 | 
			
		||||
    {
 | 
			
		||||
        MultiDB::findAndSetDbByCompanyKey($this->getTokenContent()['company_key']);
 | 
			
		||||
 | 
			
		||||
        return Company::where('company_key', $this->getTokenContent()['company_key'])->firstOrFail();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -23,7 +23,10 @@ class StoreProductRequest extends Request
 | 
			
		||||
     */
 | 
			
		||||
    public function authorize() : bool
 | 
			
		||||
    {
 | 
			
		||||
        return auth()->user()->can('create', Product::class);
 | 
			
		||||
        /** @var \App\Models\User $user */
 | 
			
		||||
        $user = auth()->user();
 | 
			
		||||
 | 
			
		||||
        return $user->can('create', Product::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function rules()
 | 
			
		||||
@ -54,7 +57,7 @@ class StoreProductRequest extends Request
 | 
			
		||||
    {
 | 
			
		||||
        $input = $this->all();
 | 
			
		||||
 | 
			
		||||
        if (! isset($input['quantity']) || $input['quantity'] < 1) {
 | 
			
		||||
        if (! isset($input['quantity'])) {
 | 
			
		||||
            $input['quantity'] = 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -56,7 +56,7 @@ class UpdateProductRequest extends Request
 | 
			
		||||
    {
 | 
			
		||||
        $input = $this->all();
 | 
			
		||||
 | 
			
		||||
        if (! isset($input['quantity']) || $input['quantity'] < 1) {
 | 
			
		||||
        if (! isset($input['quantity'])) {
 | 
			
		||||
            $input['quantity'] = 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -48,7 +48,7 @@ class UpdatePurchaseOrderRequest extends Request
 | 
			
		||||
 | 
			
		||||
        $rules = [];
 | 
			
		||||
 | 
			
		||||
        $rules['number'] = ['bail', 'sometimes', Rule::unique('purchase_orders')->where('company_id', $user->company()->id)->ignore($this->purchase_order->id)];        
 | 
			
		||||
        $rules['number'] = ['bail', 'sometimes', 'nullable', Rule::unique('purchase_orders')->where('company_id', $user->company()->id)->ignore($this->purchase_order->id)];        
 | 
			
		||||
        $rules['vendor_id'] = ['bail', 'sometimes', Rule::in([$this->purchase_order->vendor_id])];
 | 
			
		||||
 | 
			
		||||
        $rules['line_items'] = 'array';
 | 
			
		||||
 | 
			
		||||
@ -55,7 +55,7 @@ class UpdateQuoteRequest extends Request
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        $rules['number'] = ['bail', 'sometimes', Rule::unique('quotes')->where('company_id', $user->company()->id)->ignore($this->quote->id)];
 | 
			
		||||
        $rules['number'] = ['bail', 'sometimes', 'nullable', Rule::unique('quotes')->where('company_id', $user->company()->id)->ignore($this->quote->id)];
 | 
			
		||||
 | 
			
		||||
        $rules['client_id'] = ['bail', 'sometimes', Rule::in([$this->quote->client_id])];
 | 
			
		||||
 | 
			
		||||
@ -73,6 +73,8 @@ class UpdateQuoteRequest extends Request
 | 
			
		||||
 | 
			
		||||
        $input = $this->decodePrimaryKeys($input);
 | 
			
		||||
 | 
			
		||||
        $input['id'] = $this->quote->id;
 | 
			
		||||
 | 
			
		||||
        if (isset($input['line_items'])) {
 | 
			
		||||
            $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
 | 
			
		||||
        }
 | 
			
		||||
@ -85,7 +87,6 @@ class UpdateQuoteRequest extends Request
 | 
			
		||||
            $input['exchange_rate'] = 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $input['id'] = $this->quote->id;
 | 
			
		||||
 | 
			
		||||
        $this->replace($input);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -42,14 +42,14 @@ class StoreSchedulerRequest extends Request
 | 
			
		||||
            'template' => 'bail|required|string',
 | 
			
		||||
            'parameters' => 'bail|array',
 | 
			
		||||
            'parameters.clients' => ['bail','sometimes', 'array', new ValidClientIds()],
 | 
			
		||||
            'parameters.date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,all_time,custom',
 | 
			
		||||
            'parameters.date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,all_time,custom,all',
 | 
			
		||||
            'parameters.start_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom'],
 | 
			
		||||
            'parameters.end_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom', 'after_or_equal:parameters.start_date'],
 | 
			
		||||
            'parameters.entity' => ['bail', 'sometimes', 'string', 'in:invoice,credit,quote,purchase_order'],
 | 
			
		||||
            'parameters.entity_id' => ['bail', 'sometimes', 'string'],
 | 
			
		||||
            'parameters.report_name' => ['bail','sometimes', 'string', 'required_if:template,email_report','in:ar_detailed,ar_summary,client_balance,tax_summary,profitloss,client_sales,user_sales,product_sales,clients,client_contacts,credits,documents,expenses,invoices,invoice_items,quotes,quote_items,recurring_invoices,payments,products,tasks'],
 | 
			
		||||
            'parameters.report_name' => ['bail','sometimes', 'string', 'required_if:template,email_report','in:ar_detailed,ar_summary,client_balance,tax_summary,profitloss,client_sales,user_sales,product_sales,activity,client,contact,client_contact,credit,document,expense,invoice,invoice_item,quote,quote_item,recurring_invoice,payment,product,task'],
 | 
			
		||||
            'parameters.date_key' => ['bail','sometimes', 'string'],
 | 
			
		||||
            'parameters.status' => ['bail','sometimes', 'string', 'in:all,draft,paid,unpaid,overdue'],
 | 
			
		||||
            'parameters.status' => ['bail','sometimes', 'string'],
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        return $rules;
 | 
			
		||||
@ -67,6 +67,18 @@ class StoreSchedulerRequest extends Request
 | 
			
		||||
            $input['frequency_id'] = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(isset($input['parameters']) && !isset($input['parameters']['clients'])) {
 | 
			
		||||
            $input['parameters']['clients'] = [];
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if(isset($input['parameters']['status'])) {
 | 
			
		||||
 | 
			
		||||
            $input['parameters']['status'] = collect(explode(",", $input['parameters']['status']))
 | 
			
		||||
                                                    ->filter(function ($status) {
 | 
			
		||||
                                                        return in_array($status, ['all','draft','paid','unpaid','overdue']);
 | 
			
		||||
                                                    })->implode(",") ?? '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->replace($input);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -39,13 +39,14 @@ class UpdateSchedulerRequest extends Request
 | 
			
		||||
            'template' => 'bail|required|string',
 | 
			
		||||
            'parameters' => 'bail|array',
 | 
			
		||||
            'parameters.clients' => ['bail','sometimes', 'array', new ValidClientIds()],
 | 
			
		||||
            'parameters.date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,all_time,custom',
 | 
			
		||||
            'parameters.date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,all_time,custom,all',
 | 
			
		||||
            'parameters.start_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom'],
 | 
			
		||||
            'parameters.end_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom', 'after_or_equal:parameters.start_date'],
 | 
			
		||||
            'parameters.entity' => ['bail', 'sometimes', 'string', 'in:invoice,credit,quote,purchase_order'],
 | 
			
		||||
            'parameters.entity_id' => ['bail', 'sometimes', 'string'],
 | 
			
		||||
            'parameters.report_name' => ['bail','sometimes', 'string', 'required_if:template,email_report', 'in:ar_detailed,ar_summary,client_balance,tax_summary,profitloss,client_sales,user_sales,product_sales,clients,client_contacts,credits,documents,expenses,invoices,invoice_items,quotes,quote_items,recurring_invoices,payments,products,tasks'],
 | 
			
		||||
            'parameters.report_name' => ['bail','sometimes', 'string', 'required_if:template,email_report','in:ar_detailed,ar_summary,client_balance,tax_summary,profitloss,client_sales,user_sales,product_sales,activity,client,contact,client_contact,credit,document,expense,invoice,invoice_item,quote,quote_item,recurring_invoice,payment,product,task'],
 | 
			
		||||
            'parameters.date_key' => ['bail','sometimes', 'string'],
 | 
			
		||||
            'parameters.status' => ['bail','sometimes', 'string'],
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        return $rules;
 | 
			
		||||
@ -63,8 +64,21 @@ class UpdateSchedulerRequest extends Request
 | 
			
		||||
            $input['frequency_id'] = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(isset($input['parameters']) && !isset($input['parameters']['clients'])) {
 | 
			
		||||
            $input['parameters']['clients'] = [];
 | 
			
		||||
        }
 | 
			
		||||
                
 | 
			
		||||
        if(isset($input['parameters']['status'])) {
 | 
			
		||||
 | 
			
		||||
            $input['parameters']['status'] = collect(explode(",", $input['parameters']['status']))
 | 
			
		||||
                                                    ->filter(function ($status) {
 | 
			
		||||
                                                        return in_array($status, ['all','draft','paid','unpaid','overdue']);
 | 
			
		||||
                                                    })->implode(",") ?? '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->replace($input);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@ -11,14 +11,19 @@
 | 
			
		||||
 | 
			
		||||
namespace App\Http\ValidationRules\Account;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Contracts\Validation\Rule;
 | 
			
		||||
use Closure;
 | 
			
		||||
use Illuminate\Contracts\Validation\ValidationRule;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class BlackListRule.
 | 
			
		||||
 */
 | 
			
		||||
class BlackListRule implements Rule
 | 
			
		||||
class BlackListRule implements ValidationRule
 | 
			
		||||
{
 | 
			
		||||
    /** Bad domains +/- dispoable email domains */
 | 
			
		||||
    private array $blacklist = [
 | 
			
		||||
        'secure-coinspot.com',
 | 
			
		||||
        'casasotombo.com',
 | 
			
		||||
        'otpku.com',
 | 
			
		||||
        'ckptr.com',
 | 
			
		||||
        'pretreer.com',
 | 
			
		||||
        'candassociates.com',
 | 
			
		||||
@ -57,6 +62,8 @@ class BlackListRule implements Rule
 | 
			
		||||
        '10dk.email',
 | 
			
		||||
        '10mail.com',
 | 
			
		||||
        '10mail.org',
 | 
			
		||||
        '10mail.tk',
 | 
			
		||||
        '10minmail.de',
 | 
			
		||||
        '10minut.com.pl',
 | 
			
		||||
        '10minut.xyz',
 | 
			
		||||
        '10minutemail.be',
 | 
			
		||||
@ -75,6 +82,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        '10minutemailbox.com',
 | 
			
		||||
        '10minutemails.in',
 | 
			
		||||
        '10minutenemail.de',
 | 
			
		||||
        '10minutenmail.xyz',
 | 
			
		||||
        '10minutesmail.com',
 | 
			
		||||
        '10minutesmail.fr',
 | 
			
		||||
        '10minutmail.pl',
 | 
			
		||||
@ -268,6 +276,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'affinitywe.us',
 | 
			
		||||
        'affluentwe.us',
 | 
			
		||||
        'affordablewe.us',
 | 
			
		||||
        'afia.pro',
 | 
			
		||||
        'afrobacon.com',
 | 
			
		||||
        'afterhourswe.us',
 | 
			
		||||
        'agedmail.com',
 | 
			
		||||
@ -287,6 +296,8 @@ class BlackListRule implements Rule
 | 
			
		||||
        'akapost.com',
 | 
			
		||||
        'akerd.com',
 | 
			
		||||
        'akgq701.com',
 | 
			
		||||
        'akmail.in',
 | 
			
		||||
        'akugu.com',
 | 
			
		||||
        'al-qaeda.us',
 | 
			
		||||
        'albionwe.us',
 | 
			
		||||
        'alchemywe.us',
 | 
			
		||||
@ -294,6 +305,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'aliaswe.us',
 | 
			
		||||
        'alienware13.com',
 | 
			
		||||
        'aligamel.com',
 | 
			
		||||
        'alina-schiesser.ch',
 | 
			
		||||
        'alisongamel.com',
 | 
			
		||||
        'alivance.com',
 | 
			
		||||
        'alivewe.us',
 | 
			
		||||
@ -376,6 +388,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'antispam.de',
 | 
			
		||||
        'antispam24.de',
 | 
			
		||||
        'antispammail.de',
 | 
			
		||||
        'any.pink',
 | 
			
		||||
        'anyalias.com',
 | 
			
		||||
        'aoeuhtns.com',
 | 
			
		||||
        'apfelkorps.de',
 | 
			
		||||
@ -440,10 +453,12 @@ class BlackListRule implements Rule
 | 
			
		||||
        'badoop.com',
 | 
			
		||||
        'badpotato.tk',
 | 
			
		||||
        'balaket.com',
 | 
			
		||||
        'bangban.uk',
 | 
			
		||||
        'banit.club',
 | 
			
		||||
        'banit.me',
 | 
			
		||||
        'bank-opros1.ru',
 | 
			
		||||
        'bareed.ws',
 | 
			
		||||
        'barooko.com',
 | 
			
		||||
        'barryogorman.com',
 | 
			
		||||
        'bartdevos.be',
 | 
			
		||||
        'basscode.org',
 | 
			
		||||
@ -451,11 +466,15 @@ class BlackListRule implements Rule
 | 
			
		||||
        'bazaaboom.com',
 | 
			
		||||
        'bbbbyyzz.info',
 | 
			
		||||
        'bbhost.us',
 | 
			
		||||
        'bbitf.com',
 | 
			
		||||
        'bbitj.com',
 | 
			
		||||
        'bbitq.com',
 | 
			
		||||
        'bcaoo.com',
 | 
			
		||||
        'bcast.ws',
 | 
			
		||||
        'bcb.ro',
 | 
			
		||||
        'bccto.me',
 | 
			
		||||
        'bdmuzic.pw',
 | 
			
		||||
        'beaconmessenger.com',
 | 
			
		||||
        'bearsarefuzzy.com',
 | 
			
		||||
        'beddly.com',
 | 
			
		||||
        'beefmilk.com',
 | 
			
		||||
@ -477,6 +496,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'betr.co',
 | 
			
		||||
        'bgtmail.com',
 | 
			
		||||
        'bgx.ro',
 | 
			
		||||
        'bheps.com',
 | 
			
		||||
        'bidourlnks.com',
 | 
			
		||||
        'big1.us',
 | 
			
		||||
        'bigprofessor.so',
 | 
			
		||||
@ -520,9 +540,11 @@ class BlackListRule implements Rule
 | 
			
		||||
        'bouncr.com',
 | 
			
		||||
        'boxformail.in',
 | 
			
		||||
        'boximail.com',
 | 
			
		||||
        'boxmail.lol',
 | 
			
		||||
        'boxomail.live',
 | 
			
		||||
        'boxtemp.com.br',
 | 
			
		||||
        'bptfp.net',
 | 
			
		||||
        'brand-app.biz',
 | 
			
		||||
        'brandallday.net',
 | 
			
		||||
        'brasx.org',
 | 
			
		||||
        'breakthru.com',
 | 
			
		||||
@ -543,8 +565,10 @@ class BlackListRule implements Rule
 | 
			
		||||
        'budaya-tionghoa.com',
 | 
			
		||||
        'budayationghoa.com',
 | 
			
		||||
        'buffemail.com',
 | 
			
		||||
        'bugfoo.com',
 | 
			
		||||
        'bugmenever.com',
 | 
			
		||||
        'bugmenot.com',
 | 
			
		||||
        'bukhariansiddur.com',
 | 
			
		||||
        'bulrushpress.com',
 | 
			
		||||
        'bum.net',
 | 
			
		||||
        'bumpymail.com',
 | 
			
		||||
@ -582,6 +606,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'caseedu.tk',
 | 
			
		||||
        'cashflow35.com',
 | 
			
		||||
        'casualdx.com',
 | 
			
		||||
        'catgroup.uk',
 | 
			
		||||
        'cavi.mx',
 | 
			
		||||
        'cbair.com',
 | 
			
		||||
        'cbes.net',
 | 
			
		||||
@ -605,6 +630,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'cheaphub.net',
 | 
			
		||||
        'cheatmail.de',
 | 
			
		||||
        'chenbot.email',
 | 
			
		||||
        'chewydonut.com',
 | 
			
		||||
        'chibakenma.ml',
 | 
			
		||||
        'chickenkiller.com',
 | 
			
		||||
        'chielo.com',
 | 
			
		||||
@ -621,6 +647,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'chong-mail.org',
 | 
			
		||||
        'chumpstakingdumps.com',
 | 
			
		||||
        'cigar-auctions.com',
 | 
			
		||||
        'civikli.com',
 | 
			
		||||
        'civx.org',
 | 
			
		||||
        'ckaazaza.tk',
 | 
			
		||||
        'ckiso.com',
 | 
			
		||||
@ -637,6 +664,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'clonemoi.tk',
 | 
			
		||||
        'cloud-mail.top',
 | 
			
		||||
        'cloudns.cx',
 | 
			
		||||
        'clout.wiki',
 | 
			
		||||
        'clrmail.com',
 | 
			
		||||
        'cmail.club',
 | 
			
		||||
        'cmail.com',
 | 
			
		||||
@ -678,6 +706,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'crazymailing.com',
 | 
			
		||||
        'cream.pink',
 | 
			
		||||
        'crepeau12.com',
 | 
			
		||||
        'cringemonster.com',
 | 
			
		||||
        'cross-law.ga',
 | 
			
		||||
        'cross-law.gq',
 | 
			
		||||
        'crossmailjet.com',
 | 
			
		||||
@ -733,6 +762,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'daymailonline.com',
 | 
			
		||||
        'dayrep.com',
 | 
			
		||||
        'dbunker.com',
 | 
			
		||||
        'dcctb.com',
 | 
			
		||||
        'dcemail.com',
 | 
			
		||||
        'ddcrew.com',
 | 
			
		||||
        'de-a.org',
 | 
			
		||||
@ -769,6 +799,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'dev-null.ga',
 | 
			
		||||
        'dev-null.gq',
 | 
			
		||||
        'dev-null.ml',
 | 
			
		||||
        'developermail.com',
 | 
			
		||||
        'devnullmail.com',
 | 
			
		||||
        'deyom.com',
 | 
			
		||||
        'dharmatel.net',
 | 
			
		||||
@ -800,6 +831,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'discardmail.com',
 | 
			
		||||
        'discardmail.de',
 | 
			
		||||
        'discos4.com',
 | 
			
		||||
        'dishcatfish.com',
 | 
			
		||||
        'disign-concept.eu',
 | 
			
		||||
        'disign-revelation.com',
 | 
			
		||||
        'dispo.in',
 | 
			
		||||
@ -851,10 +883,12 @@ class BlackListRule implements Rule
 | 
			
		||||
        'domforfb8.tk',
 | 
			
		||||
        'domforfb9.tk',
 | 
			
		||||
        'domozmail.com',
 | 
			
		||||
        'donebyngle.com',
 | 
			
		||||
        'donemail.ru',
 | 
			
		||||
        'dongqing365.com',
 | 
			
		||||
        'dontreg.com',
 | 
			
		||||
        'dontsendmespam.de',
 | 
			
		||||
        'doojazz.com',
 | 
			
		||||
        'doquier.tk',
 | 
			
		||||
        'dotman.de',
 | 
			
		||||
        'dotmsg.com',
 | 
			
		||||
@ -869,13 +903,17 @@ class BlackListRule implements Rule
 | 
			
		||||
        'dred.ru',
 | 
			
		||||
        'drevo.si',
 | 
			
		||||
        'drivetagdev.com',
 | 
			
		||||
        'drmail.in',
 | 
			
		||||
        'droolingfanboy.de',
 | 
			
		||||
        'dropcake.de',
 | 
			
		||||
        'dropjar.com',
 | 
			
		||||
        'droplar.com',
 | 
			
		||||
        'dropmail.me',
 | 
			
		||||
        'dropsin.net',
 | 
			
		||||
        'drowblock.com',
 | 
			
		||||
        'dsgvo.party',
 | 
			
		||||
        'dsgvo.ru',
 | 
			
		||||
        'dshfjdafd.cloud',
 | 
			
		||||
        'dsiay.com',
 | 
			
		||||
        'dspwebservices.com',
 | 
			
		||||
        'duam.net',
 | 
			
		||||
@ -944,6 +982,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'emailage.ml',
 | 
			
		||||
        'emailage.tk',
 | 
			
		||||
        'emailate.com',
 | 
			
		||||
        'emailbin.net',
 | 
			
		||||
        'emailcu.icu',
 | 
			
		||||
        'emaildienst.de',
 | 
			
		||||
        'emaildrop.io',
 | 
			
		||||
@ -1017,8 +1056,11 @@ class BlackListRule implements Rule
 | 
			
		||||
        'eposta.buzz',
 | 
			
		||||
        'eposta.work',
 | 
			
		||||
        'eqiluxspam.ga',
 | 
			
		||||
        'ereplyzy.com',
 | 
			
		||||
        'ericjohnson.ml',
 | 
			
		||||
        'eripo.net',
 | 
			
		||||
        'ero-tube.org',
 | 
			
		||||
        'esadverse.com',
 | 
			
		||||
        'esbano-ru.ru',
 | 
			
		||||
        'esc.la',
 | 
			
		||||
        'escapehatchapp.com',
 | 
			
		||||
@ -1043,16 +1085,19 @@ class BlackListRule implements Rule
 | 
			
		||||
        'evopo.com',
 | 
			
		||||
        'evyush.com',
 | 
			
		||||
        'exdonuts.com',
 | 
			
		||||
        'exelica.com',
 | 
			
		||||
        'existiert.net',
 | 
			
		||||
        'exitstageleft.net',
 | 
			
		||||
        'explodemail.com',
 | 
			
		||||
        'express.net.ua',
 | 
			
		||||
        'extracurricularsociety.com',
 | 
			
		||||
        'extremail.ru',
 | 
			
		||||
        'eyepaste.com',
 | 
			
		||||
        'ez.lv',
 | 
			
		||||
        'ezehe.com',
 | 
			
		||||
        'ezfill.com',
 | 
			
		||||
        'ezstest.com',
 | 
			
		||||
        'ezztt.com',
 | 
			
		||||
        'f4k.es',
 | 
			
		||||
        'f5.si',
 | 
			
		||||
        'facebook-email.cf',
 | 
			
		||||
@ -1108,12 +1153,14 @@ class BlackListRule implements Rule
 | 
			
		||||
        'fbma.tk',
 | 
			
		||||
        'fddns.ml',
 | 
			
		||||
        'fdfdsfds.com',
 | 
			
		||||
        'femailtor.com',
 | 
			
		||||
        'fer-gabon.org',
 | 
			
		||||
        'fermaxxi.ru',
 | 
			
		||||
        'fettometern.com',
 | 
			
		||||
        'fexbox.org',
 | 
			
		||||
        'fexbox.ru',
 | 
			
		||||
        'fexpost.com',
 | 
			
		||||
        'fextemp.com',
 | 
			
		||||
        'ficken.de',
 | 
			
		||||
        'fictionsite.com',
 | 
			
		||||
        'fightallspam.com',
 | 
			
		||||
@ -1127,6 +1174,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'filzmail.com',
 | 
			
		||||
        'findemail.info',
 | 
			
		||||
        'findu.pl',
 | 
			
		||||
        'finews.biz',
 | 
			
		||||
        'fir.hk',
 | 
			
		||||
        'firemailbox.club',
 | 
			
		||||
        'fitnesrezink.ru',
 | 
			
		||||
@ -1135,12 +1183,14 @@ class BlackListRule implements Rule
 | 
			
		||||
        'fizmail.com',
 | 
			
		||||
        'fleckens.hu',
 | 
			
		||||
        'flemail.ru',
 | 
			
		||||
        'fliegender.fish',
 | 
			
		||||
        'flowu.com',
 | 
			
		||||
        'flu.cc',
 | 
			
		||||
        'fluidsoft.us',
 | 
			
		||||
        'flurred.com',
 | 
			
		||||
        'fly-ts.de',
 | 
			
		||||
        'flyinggeek.net',
 | 
			
		||||
        'flymail.tk',
 | 
			
		||||
        'flyspam.com',
 | 
			
		||||
        'foobarbot.net',
 | 
			
		||||
        'footard.com',
 | 
			
		||||
@ -1158,6 +1208,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'fosil.pro',
 | 
			
		||||
        'foxja.com',
 | 
			
		||||
        'foxtrotter.info',
 | 
			
		||||
        'fr.cr',
 | 
			
		||||
        'fr.nf',
 | 
			
		||||
        'fr33mail.info',
 | 
			
		||||
        'fragolina2.tk',
 | 
			
		||||
@ -1204,11 +1255,15 @@ class BlackListRule implements Rule
 | 
			
		||||
        'fuirio.com',
 | 
			
		||||
        'fukaru.com',
 | 
			
		||||
        'fukurou.ch',
 | 
			
		||||
        'fullangle.org',
 | 
			
		||||
        'fulvie.com',
 | 
			
		||||
        'fun64.com',
 | 
			
		||||
        'funnycodesnippets.com',
 | 
			
		||||
        'funnymail.de',
 | 
			
		||||
        'furzauflunge.de',
 | 
			
		||||
        'futuramind.com',
 | 
			
		||||
        'fuwa.be',
 | 
			
		||||
        'fuwa.li',
 | 
			
		||||
        'fuwamofu.com',
 | 
			
		||||
        'fuwari.be',
 | 
			
		||||
        'fux0ringduh.com',
 | 
			
		||||
@ -1291,6 +1346,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'giveh2o.info',
 | 
			
		||||
        'givememail.club',
 | 
			
		||||
        'givmail.com',
 | 
			
		||||
        'gixenmixen.com',
 | 
			
		||||
        'glitch.sx',
 | 
			
		||||
        'globaltouron.com',
 | 
			
		||||
        'glubex.com',
 | 
			
		||||
@ -1304,6 +1360,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'gnctr-calgary.com',
 | 
			
		||||
        'go2usa.info',
 | 
			
		||||
        'go2vpn.net',
 | 
			
		||||
        'goatmail.uk',
 | 
			
		||||
        'goemailgo.com',
 | 
			
		||||
        'golemico.com',
 | 
			
		||||
        'gomail.in',
 | 
			
		||||
@ -1363,6 +1420,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'guerrillamail.net',
 | 
			
		||||
        'guerrillamail.org',
 | 
			
		||||
        'guerrillamailblock.com',
 | 
			
		||||
        'gufum.com',
 | 
			
		||||
        'gustr.com',
 | 
			
		||||
        'gxemail.men',
 | 
			
		||||
        'gynzi.co.uk',
 | 
			
		||||
@ -1389,6 +1447,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'haltospam.com',
 | 
			
		||||
        'hamham.uk',
 | 
			
		||||
        'hangxomcuatoilatotoro.ml',
 | 
			
		||||
        'happy2023year.com',
 | 
			
		||||
        'happydomik.ru',
 | 
			
		||||
        'harakirimail.com',
 | 
			
		||||
        'haribu.com',
 | 
			
		||||
@ -1467,6 +1526,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'huskion.net',
 | 
			
		||||
        'hvastudiesucces.nl',
 | 
			
		||||
        'hwsye.net',
 | 
			
		||||
        'hypenated-domain.com',
 | 
			
		||||
        'i2pmail.org',
 | 
			
		||||
        'i6.cloudns.cc',
 | 
			
		||||
        'iaoss.com',
 | 
			
		||||
@ -1476,6 +1536,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'ichigo.me',
 | 
			
		||||
        'icx.in',
 | 
			
		||||
        'icx.ro',
 | 
			
		||||
        'icznn.com',
 | 
			
		||||
        'idx4.com',
 | 
			
		||||
        'idxue.com',
 | 
			
		||||
        'ieatspam.eu',
 | 
			
		||||
@ -1498,9 +1559,12 @@ class BlackListRule implements Rule
 | 
			
		||||
        'imgof.com',
 | 
			
		||||
        'imgv.de',
 | 
			
		||||
        'immo-gerance.info',
 | 
			
		||||
        'imperialcnk.com',
 | 
			
		||||
        'imstations.com',
 | 
			
		||||
        'imul.info',
 | 
			
		||||
        'in-ulm.de',
 | 
			
		||||
        'in2reach.com',
 | 
			
		||||
        'inactivemachine.com',
 | 
			
		||||
        'inbax.tk',
 | 
			
		||||
        'inbound.plus',
 | 
			
		||||
        'inbox.si',
 | 
			
		||||
@ -1530,6 +1594,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'ineec.net',
 | 
			
		||||
        'infocom.zp.ua',
 | 
			
		||||
        'inggo.org',
 | 
			
		||||
        'inkiny.com',
 | 
			
		||||
        'inkomail.com',
 | 
			
		||||
        'inmynetwork.tk',
 | 
			
		||||
        'inoutmail.de',
 | 
			
		||||
@ -1540,12 +1605,16 @@ class BlackListRule implements Rule
 | 
			
		||||
        'insanumingeniumhomebrew.com',
 | 
			
		||||
        'insorg-mail.info',
 | 
			
		||||
        'instaddr.ch',
 | 
			
		||||
        'instaddr.uk',
 | 
			
		||||
        'instaddr.win',
 | 
			
		||||
        'instance-email.com',
 | 
			
		||||
        'instant-mail.de',
 | 
			
		||||
        'instantblingmail.info',
 | 
			
		||||
        'instantemailaddress.com',
 | 
			
		||||
        'instantmail.fr',
 | 
			
		||||
        'instmail.uk',
 | 
			
		||||
        'internet-v-stavropole.ru',
 | 
			
		||||
        'internetkeno.com',
 | 
			
		||||
        'internetoftags.com',
 | 
			
		||||
        'interstats.org',
 | 
			
		||||
        'intersteller.com',
 | 
			
		||||
@ -1577,6 +1646,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'italy-mail.com',
 | 
			
		||||
        'itcompu.com',
 | 
			
		||||
        'itfast.net',
 | 
			
		||||
        'itsjiff.com',
 | 
			
		||||
        'itunesgiftcodegenerator.com',
 | 
			
		||||
        'iubridge.com',
 | 
			
		||||
        'iuemail.men',
 | 
			
		||||
@ -1585,6 +1655,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'ixx.io',
 | 
			
		||||
        'j-p.us',
 | 
			
		||||
        'jafps.com',
 | 
			
		||||
        'jaga.email',
 | 
			
		||||
        'jajxz.com',
 | 
			
		||||
        'janproz.com',
 | 
			
		||||
        'jaqis.com',
 | 
			
		||||
@ -1599,11 +1670,15 @@ class BlackListRule implements Rule
 | 
			
		||||
        'jetable.net',
 | 
			
		||||
        'jetable.org',
 | 
			
		||||
        'jetable.pp.ua',
 | 
			
		||||
        'ji5.de',
 | 
			
		||||
        'ji6.de',
 | 
			
		||||
        'ji7.de',
 | 
			
		||||
        'jiooq.com',
 | 
			
		||||
        'jmail.ovh',
 | 
			
		||||
        'jmail.ro',
 | 
			
		||||
        'jnxjn.com',
 | 
			
		||||
        'jobbikszimpatizans.hu',
 | 
			
		||||
        'jobbrett.com',
 | 
			
		||||
        'jobposts.net',
 | 
			
		||||
        'jobs-to-be-done.net',
 | 
			
		||||
        'joelpet.com',
 | 
			
		||||
@ -1660,6 +1735,8 @@ class BlackListRule implements Rule
 | 
			
		||||
        'killmail.com',
 | 
			
		||||
        'killmail.net',
 | 
			
		||||
        'kimsdisk.com',
 | 
			
		||||
        'kinda.email',
 | 
			
		||||
        'kindamail.com',
 | 
			
		||||
        'kingsq.ga',
 | 
			
		||||
        'kino-100.ru',
 | 
			
		||||
        'kiois.com',
 | 
			
		||||
@ -1668,16 +1745,20 @@ class BlackListRule implements Rule
 | 
			
		||||
        'kitnastar.com',
 | 
			
		||||
        'kjkszpjcompany.com',
 | 
			
		||||
        'kkmail.be',
 | 
			
		||||
        'kkoup.com',
 | 
			
		||||
        'kksm.be',
 | 
			
		||||
        'klassmaster.com',
 | 
			
		||||
        'klassmaster.net',
 | 
			
		||||
        'klick-tipp.us',
 | 
			
		||||
        'klipschx12.com',
 | 
			
		||||
        'kloap.com',
 | 
			
		||||
        'klovenode.com',
 | 
			
		||||
        'kludgemush.com',
 | 
			
		||||
        'klzlk.com',
 | 
			
		||||
        'kmail.li',
 | 
			
		||||
        'kmail.live',
 | 
			
		||||
        'kmhow.com',
 | 
			
		||||
        'knickerbockerban.de',
 | 
			
		||||
        'knol-power.nl',
 | 
			
		||||
        'kobrandly.com',
 | 
			
		||||
        'kommunity.biz',
 | 
			
		||||
@ -1711,6 +1792,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'kwilco.net',
 | 
			
		||||
        'kyal.pl',
 | 
			
		||||
        'kyois.com',
 | 
			
		||||
        'kzccv.com',
 | 
			
		||||
        'l-c-a.us',
 | 
			
		||||
        'l33r.eu',
 | 
			
		||||
        'l6factors.com',
 | 
			
		||||
@ -1725,9 +1807,12 @@ class BlackListRule implements Rule
 | 
			
		||||
        'lak.pp.ua',
 | 
			
		||||
        'lakelivingstonrealestate.com',
 | 
			
		||||
        'lakqs.com',
 | 
			
		||||
        'lamasticots.com',
 | 
			
		||||
        'lambsauce.de',
 | 
			
		||||
        'landmail.co',
 | 
			
		||||
        'laoeq.com',
 | 
			
		||||
        'larisia.com',
 | 
			
		||||
        'larland.com',
 | 
			
		||||
        'last-chance.pro',
 | 
			
		||||
        'lastmail.co',
 | 
			
		||||
        'lastmail.com',
 | 
			
		||||
@ -1757,6 +1842,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'ligsb.com',
 | 
			
		||||
        'lillemap.net',
 | 
			
		||||
        'lilo.me',
 | 
			
		||||
        'lilspam.com',
 | 
			
		||||
        'lindenbaumjapan.com',
 | 
			
		||||
        'link2mail.net',
 | 
			
		||||
        'linkedintuts2016.pw',
 | 
			
		||||
@ -1802,6 +1888,8 @@ class BlackListRule implements Rule
 | 
			
		||||
        'lukop.dk',
 | 
			
		||||
        'luv2.us',
 | 
			
		||||
        'lyfestylecreditsolutions.com',
 | 
			
		||||
        'lyft.live',
 | 
			
		||||
        'lyricspad.net',
 | 
			
		||||
        'lzoaq.com',
 | 
			
		||||
        'm21.cc',
 | 
			
		||||
        'm4ilweb.info',
 | 
			
		||||
@ -1847,6 +1935,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'mailapp.top',
 | 
			
		||||
        'mailback.com',
 | 
			
		||||
        'mailbidon.com',
 | 
			
		||||
        'mailbiscuit.com',
 | 
			
		||||
        'mailbiz.biz',
 | 
			
		||||
        'mailblocks.com',
 | 
			
		||||
        'mailbox.in.ua',
 | 
			
		||||
@ -1876,6 +1965,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'mailed.ro',
 | 
			
		||||
        'maileimer.de',
 | 
			
		||||
        'maileme101.com',
 | 
			
		||||
        'mailers.edu.pl',
 | 
			
		||||
        'mailexpire.com',
 | 
			
		||||
        'mailf5.com',
 | 
			
		||||
        'mailfa.tk',
 | 
			
		||||
@ -1943,6 +2033,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'mailonaut.com',
 | 
			
		||||
        'mailorc.com',
 | 
			
		||||
        'mailorg.org',
 | 
			
		||||
        'mailosaur.net',
 | 
			
		||||
        'mailox.fun',
 | 
			
		||||
        'mailpick.biz',
 | 
			
		||||
        'mailpluss.com',
 | 
			
		||||
@ -2006,18 +2097,22 @@ class BlackListRule implements Rule
 | 
			
		||||
        'mcache.net',
 | 
			
		||||
        'mciek.com',
 | 
			
		||||
        'mdhc.tk',
 | 
			
		||||
        'mdz.email',
 | 
			
		||||
        'meantinc.com',
 | 
			
		||||
        'mebelnu.info',
 | 
			
		||||
        'mechanicalresumes.com',
 | 
			
		||||
        'medkabinet-uzi.ru',
 | 
			
		||||
        'meepsheep.eu',
 | 
			
		||||
        'meidecn.com',
 | 
			
		||||
        'meinspamschutz.de',
 | 
			
		||||
        'meltedbrownies.com',
 | 
			
		||||
        'meltmail.com',
 | 
			
		||||
        'memsg.site',
 | 
			
		||||
        'mentonit.net',
 | 
			
		||||
        'mepost.pw',
 | 
			
		||||
        'merepost.com',
 | 
			
		||||
        'merry.pink',
 | 
			
		||||
        'meruado.uk',
 | 
			
		||||
        'messagebeamer.de',
 | 
			
		||||
        'messwiththebestdielikethe.rest',
 | 
			
		||||
        'metadownload.org',
 | 
			
		||||
@ -2043,6 +2138,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'migumail.com',
 | 
			
		||||
        'mihep.com',
 | 
			
		||||
        'mijnhva.nl',
 | 
			
		||||
        'minimail.gq',
 | 
			
		||||
        'ministry-of-silly-walks.de',
 | 
			
		||||
        'minsmail.com',
 | 
			
		||||
        'mintemail.com',
 | 
			
		||||
@ -2054,6 +2150,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'mjukglass.nu',
 | 
			
		||||
        'mkpfilm.com',
 | 
			
		||||
        'ml8.ca',
 | 
			
		||||
        'mliok.com',
 | 
			
		||||
        'mm.my',
 | 
			
		||||
        'mm5.se',
 | 
			
		||||
        'mnode.me',
 | 
			
		||||
@ -2109,6 +2206,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'mucincanon.com',
 | 
			
		||||
        'muehlacker.tk',
 | 
			
		||||
        'muell.icu',
 | 
			
		||||
        'muell.io',
 | 
			
		||||
        'muell.monster',
 | 
			
		||||
        'muell.xyz',
 | 
			
		||||
        'muellemail.com',
 | 
			
		||||
@ -2128,16 +2226,19 @@ class BlackListRule implements Rule
 | 
			
		||||
        'mycleaninbox.net',
 | 
			
		||||
        'mycorneroftheinter.net',
 | 
			
		||||
        'myde.ml',
 | 
			
		||||
        'mydefipet.live',
 | 
			
		||||
        'mydemo.equipment',
 | 
			
		||||
        'myecho.es',
 | 
			
		||||
        'myemailboxy.com',
 | 
			
		||||
        'mygeoweb.info',
 | 
			
		||||
        'myindohome.services',
 | 
			
		||||
        'myinfoinc.com',
 | 
			
		||||
        'myinterserver.ml',
 | 
			
		||||
        'mykickassideas.com',
 | 
			
		||||
        'mymail-in.net',
 | 
			
		||||
        'mymail90.com',
 | 
			
		||||
        'mymailoasis.com',
 | 
			
		||||
        'mymaily.lol',
 | 
			
		||||
        'mynetstore.de',
 | 
			
		||||
        'myopang.com',
 | 
			
		||||
        'mypacks.net',
 | 
			
		||||
@ -2171,10 +2272,12 @@ class BlackListRule implements Rule
 | 
			
		||||
        'naslazhdai.ru',
 | 
			
		||||
        'nationalgardeningclub.com',
 | 
			
		||||
        'nawmin.info',
 | 
			
		||||
        'naymedia.com',
 | 
			
		||||
        'nbzmr.com',
 | 
			
		||||
        'negated.com',
 | 
			
		||||
        'neko2.net',
 | 
			
		||||
        'nekochan.fr',
 | 
			
		||||
        'nekosan.uk',
 | 
			
		||||
        'neomailbox.com',
 | 
			
		||||
        'neotlozhniy-zaim.ru',
 | 
			
		||||
        'nepwk.com',
 | 
			
		||||
@ -2263,6 +2366,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'nwytg.com',
 | 
			
		||||
        'nwytg.net',
 | 
			
		||||
        'ny7.me',
 | 
			
		||||
        'nyasan.com',
 | 
			
		||||
        'nypato.com',
 | 
			
		||||
        'nyrmusic.com',
 | 
			
		||||
        'o2stk.org',
 | 
			
		||||
@ -2280,6 +2384,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'oepia.com',
 | 
			
		||||
        'oerpub.org',
 | 
			
		||||
        'offshore-proxies.net',
 | 
			
		||||
        'ofisher.net',
 | 
			
		||||
        'ohaaa.de',
 | 
			
		||||
        'ohi.tw',
 | 
			
		||||
        'oida.icu',
 | 
			
		||||
@ -2305,6 +2410,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'onlatedotcom.info',
 | 
			
		||||
        'online.ms',
 | 
			
		||||
        'onlineidea.info',
 | 
			
		||||
        'onlyapp.net',
 | 
			
		||||
        'onqin.com',
 | 
			
		||||
        'ontyne.biz',
 | 
			
		||||
        'oohioo.com',
 | 
			
		||||
@ -2330,6 +2436,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'ourklips.com',
 | 
			
		||||
        'ourpreviewdomain.com',
 | 
			
		||||
        'outlawspam.com',
 | 
			
		||||
        'outlook.edu.pl',
 | 
			
		||||
        'outmail.win',
 | 
			
		||||
        'ovomail.co',
 | 
			
		||||
        'ovpn.to',
 | 
			
		||||
@ -2337,6 +2444,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'owlpic.com',
 | 
			
		||||
        'ownsyou.de',
 | 
			
		||||
        'oxopoha.com',
 | 
			
		||||
        'ozatvn.com',
 | 
			
		||||
        'ozyl.de',
 | 
			
		||||
        'p-banlis.ru',
 | 
			
		||||
        'p33.org',
 | 
			
		||||
@ -2347,6 +2455,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'pagamenti.tk',
 | 
			
		||||
        'paharpurmim.ga',
 | 
			
		||||
        'pakadebu.ga',
 | 
			
		||||
        'pamaweb.com',
 | 
			
		||||
        'pancakemail.com',
 | 
			
		||||
        'papierkorb.me',
 | 
			
		||||
        'paplease.com',
 | 
			
		||||
@ -2384,6 +2493,8 @@ class BlackListRule implements Rule
 | 
			
		||||
        'pisls.com',
 | 
			
		||||
        'pitaniezdorovie.ru',
 | 
			
		||||
        'pivo-bar.ru',
 | 
			
		||||
        'pixiil.com',
 | 
			
		||||
        'pizzajunk.com',
 | 
			
		||||
        'pjjkp.com',
 | 
			
		||||
        'placebomail10.com',
 | 
			
		||||
        'pleasenoham.org',
 | 
			
		||||
@ -2435,6 +2546,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'prin.be',
 | 
			
		||||
        'privacy.net',
 | 
			
		||||
        'privatdemail.net',
 | 
			
		||||
        'privmail.edu.pl',
 | 
			
		||||
        'privy-mail.com',
 | 
			
		||||
        'privy-mail.de',
 | 
			
		||||
        'privymail.de',
 | 
			
		||||
@ -2454,6 +2566,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'prtz.eu',
 | 
			
		||||
        'psh.me',
 | 
			
		||||
        'psles.com',
 | 
			
		||||
        'psnator.com',
 | 
			
		||||
        'psoxs.com',
 | 
			
		||||
        'puglieisi.com',
 | 
			
		||||
        'puji.pro',
 | 
			
		||||
@ -2464,11 +2577,13 @@ class BlackListRule implements Rule
 | 
			
		||||
        'put2.net',
 | 
			
		||||
        'puttanamaiala.tk',
 | 
			
		||||
        'putthisinyourspamdatabase.com',
 | 
			
		||||
        'pwpwa.com',
 | 
			
		||||
        'pwrby.com',
 | 
			
		||||
        'qasti.com',
 | 
			
		||||
        'qbfree.us',
 | 
			
		||||
        'qc.to',
 | 
			
		||||
        'qibl.at',
 | 
			
		||||
        'qiott.com',
 | 
			
		||||
        'qipmail.net',
 | 
			
		||||
        'qiq.us',
 | 
			
		||||
        'qisdo.com',
 | 
			
		||||
@ -2485,6 +2600,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'quickinbox.com',
 | 
			
		||||
        'quickmail.nl',
 | 
			
		||||
        'quicksend.ch',
 | 
			
		||||
        'quipas.com',
 | 
			
		||||
        'ququb.com',
 | 
			
		||||
        'qvy.me',
 | 
			
		||||
        'qwickmail.com',
 | 
			
		||||
@ -2496,6 +2612,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'raetp9.com',
 | 
			
		||||
        'rainbowly.ml',
 | 
			
		||||
        'raketenmann.de',
 | 
			
		||||
        'ramenmail.de',
 | 
			
		||||
        'rancidhome.net',
 | 
			
		||||
        'randomail.io',
 | 
			
		||||
        'randomail.net',
 | 
			
		||||
@ -2512,6 +2629,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        're-gister.com',
 | 
			
		||||
        'reality-concept.club',
 | 
			
		||||
        'reallymymail.com',
 | 
			
		||||
        'realquickemail.com',
 | 
			
		||||
        'realtyalerts.ca',
 | 
			
		||||
        'rebates.stream',
 | 
			
		||||
        'receiveee.com',
 | 
			
		||||
@ -2542,6 +2660,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'rippb.com',
 | 
			
		||||
        'risingsuntouch.com',
 | 
			
		||||
        'riski.cf',
 | 
			
		||||
        'risu.be',
 | 
			
		||||
        'rklips.com',
 | 
			
		||||
        'rkomo.com',
 | 
			
		||||
        'rm2rf.com',
 | 
			
		||||
@ -2578,6 +2697,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        's33db0x.com',
 | 
			
		||||
        'sabrestlouis.com',
 | 
			
		||||
        'sackboii.com',
 | 
			
		||||
        'saeoil.com',
 | 
			
		||||
        'safaat.cf',
 | 
			
		||||
        'safermail.info',
 | 
			
		||||
        'safersignup.de',
 | 
			
		||||
@ -2630,10 +2750,13 @@ class BlackListRule implements Rule
 | 
			
		||||
        'sexforswingers.com',
 | 
			
		||||
        'sexical.com',
 | 
			
		||||
        'sexyalwasmi.top',
 | 
			
		||||
        'sfolkar.com',
 | 
			
		||||
        'shadap.org',
 | 
			
		||||
        'shalar.net',
 | 
			
		||||
        'sharedmailbox.org',
 | 
			
		||||
        'sharkfaces.com',
 | 
			
		||||
        'sharklasers.com',
 | 
			
		||||
        'shchiba.uk',
 | 
			
		||||
        'sheryli.com',
 | 
			
		||||
        'shhmail.com',
 | 
			
		||||
        'shhuut.org',
 | 
			
		||||
@ -2662,6 +2785,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'sify.com',
 | 
			
		||||
        'sika3.com',
 | 
			
		||||
        'sikux.com',
 | 
			
		||||
        'silenceofthespam.com',
 | 
			
		||||
        'siliwangi.ga',
 | 
			
		||||
        'silvercoin.life',
 | 
			
		||||
        'sim-simka.ru',
 | 
			
		||||
@ -2681,6 +2805,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'skrx.tk',
 | 
			
		||||
        'sky-inbox.com',
 | 
			
		||||
        'sky-ts.de',
 | 
			
		||||
        'skygazerhub.com',
 | 
			
		||||
        'skyrt.de',
 | 
			
		||||
        'slapsfromlastnight.com',
 | 
			
		||||
        'slaskpost.se',
 | 
			
		||||
@ -2698,6 +2823,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'smapfree24.eu',
 | 
			
		||||
        'smapfree24.info',
 | 
			
		||||
        'smapfree24.org',
 | 
			
		||||
        'smartnator.com',
 | 
			
		||||
        'smarttalent.pw',
 | 
			
		||||
        'smashmail.de',
 | 
			
		||||
        'smellfear.com',
 | 
			
		||||
@ -2705,12 +2831,14 @@ class BlackListRule implements Rule
 | 
			
		||||
        'smellypotato.tk',
 | 
			
		||||
        'smtp99.com',
 | 
			
		||||
        'smwg.info',
 | 
			
		||||
        'snakebutt.com',
 | 
			
		||||
        'snakemail.com',
 | 
			
		||||
        'snapwet.com',
 | 
			
		||||
        'sneakmail.de',
 | 
			
		||||
        'snece.com',
 | 
			
		||||
        'social-mailer.tk',
 | 
			
		||||
        'socialfurry.org',
 | 
			
		||||
        'sociallymediocre.com',
 | 
			
		||||
        'sofia.re',
 | 
			
		||||
        'sofimail.com',
 | 
			
		||||
        'sofort-mail.de',
 | 
			
		||||
@ -2731,6 +2859,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'soodmail.com',
 | 
			
		||||
        'soodomail.com',
 | 
			
		||||
        'soodonims.com',
 | 
			
		||||
        'soombo.com',
 | 
			
		||||
        'soon.it',
 | 
			
		||||
        'spacebazzar.ru',
 | 
			
		||||
        'spam-be-gone.com',
 | 
			
		||||
@ -2763,6 +2892,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'spamday.com',
 | 
			
		||||
        'spamdecoy.net',
 | 
			
		||||
        'spamex.com',
 | 
			
		||||
        'spamfellas.com',
 | 
			
		||||
        'spamfighter.cf',
 | 
			
		||||
        'spamfighter.ga',
 | 
			
		||||
        'spamfighter.gq',
 | 
			
		||||
@ -2791,6 +2921,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'spamobox.com',
 | 
			
		||||
        'spamoff.de',
 | 
			
		||||
        'spamsalad.in',
 | 
			
		||||
        'spamsandwich.com',
 | 
			
		||||
        'spamslicer.com',
 | 
			
		||||
        'spamsphere.com',
 | 
			
		||||
        'spamspot.com',
 | 
			
		||||
@ -2810,11 +2941,13 @@ class BlackListRule implements Rule
 | 
			
		||||
        'spikio.com',
 | 
			
		||||
        'spindl-e.com',
 | 
			
		||||
        'spoofmail.de',
 | 
			
		||||
        'sportrid.com',
 | 
			
		||||
        'spr.io',
 | 
			
		||||
        'spritzzone.de',
 | 
			
		||||
        'spruzme.com',
 | 
			
		||||
        'spybox.de',
 | 
			
		||||
        'spymail.com',
 | 
			
		||||
        'spymail.one',
 | 
			
		||||
        'squizzy.de',
 | 
			
		||||
        'squizzy.net',
 | 
			
		||||
        'sroff.com',
 | 
			
		||||
@ -2852,10 +2985,12 @@ class BlackListRule implements Rule
 | 
			
		||||
        'submic.com',
 | 
			
		||||
        'suburbanthug.com',
 | 
			
		||||
        'suckmyd.com',
 | 
			
		||||
        'sudern.de',
 | 
			
		||||
        'sueshaw.com',
 | 
			
		||||
        'suexamplesb.com',
 | 
			
		||||
        'suioe.com',
 | 
			
		||||
        'super-auswahl.de',
 | 
			
		||||
        'superblohey.com',
 | 
			
		||||
        'supergreatmail.com',
 | 
			
		||||
        'supermailer.jp',
 | 
			
		||||
        'superplatyna.com',
 | 
			
		||||
@ -2872,6 +3007,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'sweetxxx.de',
 | 
			
		||||
        'swift-mail.net',
 | 
			
		||||
        'swift10minutemail.com',
 | 
			
		||||
        'syinxun.com',
 | 
			
		||||
        'sylvannet.com',
 | 
			
		||||
        'symphonyresume.com',
 | 
			
		||||
        'syosetu.gq',
 | 
			
		||||
@ -2891,6 +3027,8 @@ class BlackListRule implements Rule
 | 
			
		||||
        'tastrg.com',
 | 
			
		||||
        'taukah.com',
 | 
			
		||||
        'tb-on-line.net',
 | 
			
		||||
        'tcwlm.com',
 | 
			
		||||
        'tcwlx.com',
 | 
			
		||||
        'tdtda.com',
 | 
			
		||||
        'tech69.com',
 | 
			
		||||
        'techblast.ch',
 | 
			
		||||
@ -2901,6 +3039,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'teewars.org',
 | 
			
		||||
        'tefl.ro',
 | 
			
		||||
        'telecomix.pl',
 | 
			
		||||
        'teleg.eu',
 | 
			
		||||
        'teleworm.com',
 | 
			
		||||
        'teleworm.us',
 | 
			
		||||
        'tellos.xyz',
 | 
			
		||||
@ -2965,6 +3104,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'thecloudindex.com',
 | 
			
		||||
        'thediamants.org',
 | 
			
		||||
        'thedirhq.info',
 | 
			
		||||
        'theeyeoftruth.com',
 | 
			
		||||
        'thejoker5.com',
 | 
			
		||||
        'thelightningmail.net',
 | 
			
		||||
        'thelimestones.com',
 | 
			
		||||
@ -2974,6 +3114,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'thereddoors.online',
 | 
			
		||||
        'theroyalweb.club',
 | 
			
		||||
        'thescrappermovie.com',
 | 
			
		||||
        'thespamfather.com',
 | 
			
		||||
        'theteastory.info',
 | 
			
		||||
        'thex.ro',
 | 
			
		||||
        'thichanthit.com',
 | 
			
		||||
@ -3010,9 +3151,13 @@ class BlackListRule implements Rule
 | 
			
		||||
        'tkitc.de',
 | 
			
		||||
        'tlpn.org',
 | 
			
		||||
        'tmail.com',
 | 
			
		||||
        'tmail.io',
 | 
			
		||||
        'tmail.ws',
 | 
			
		||||
        'tmail3.com',
 | 
			
		||||
        'tmail9.com',
 | 
			
		||||
        'tmailinator.com',
 | 
			
		||||
        'tmails.net',
 | 
			
		||||
        'tmmbt.net',
 | 
			
		||||
        'tmpbox.net',
 | 
			
		||||
        'tmpemails.com',
 | 
			
		||||
        'tmpeml.com',
 | 
			
		||||
@ -3020,6 +3165,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'tmpjr.me',
 | 
			
		||||
        'tmpmail.net',
 | 
			
		||||
        'tmpmail.org',
 | 
			
		||||
        'tmpx.sa.com',
 | 
			
		||||
        'toddsbighug.com',
 | 
			
		||||
        'tofeat.com',
 | 
			
		||||
        'toiea.com',
 | 
			
		||||
@ -3049,6 +3195,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'totoan.info',
 | 
			
		||||
        'tourcc.com',
 | 
			
		||||
        'tp-qa-mail.com',
 | 
			
		||||
        'tpwlb.com',
 | 
			
		||||
        'tqoai.com',
 | 
			
		||||
        'tqosi.com',
 | 
			
		||||
        'tradermail.info',
 | 
			
		||||
@ -3097,6 +3244,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'trialmail.de',
 | 
			
		||||
        'trickmail.net',
 | 
			
		||||
        'trillianpro.com',
 | 
			
		||||
        'triots.com',
 | 
			
		||||
        'trixtrux1.ru',
 | 
			
		||||
        'trollproject.com',
 | 
			
		||||
        'tropicalbass.info',
 | 
			
		||||
@ -3112,6 +3260,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'turoid.com',
 | 
			
		||||
        'turual.com',
 | 
			
		||||
        'turuma.com',
 | 
			
		||||
        'tutuapp.bid',
 | 
			
		||||
        'tvchd.com',
 | 
			
		||||
        'tverya.com',
 | 
			
		||||
        'twinmail.de',
 | 
			
		||||
@ -3150,6 +3299,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'unit7lahaina.com',
 | 
			
		||||
        'unmail.ru',
 | 
			
		||||
        'uooos.com',
 | 
			
		||||
        'uorak.com',
 | 
			
		||||
        'upliftnow.com',
 | 
			
		||||
        'uplipht.com',
 | 
			
		||||
        'uploadnolimit.com',
 | 
			
		||||
@ -3268,6 +3418,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'watchever.biz',
 | 
			
		||||
        'watchfull.net',
 | 
			
		||||
        'watchironman3onlinefreefullmovie.com',
 | 
			
		||||
        'waterisgone.com',
 | 
			
		||||
        'wazabi.club',
 | 
			
		||||
        'wbdev.tech',
 | 
			
		||||
        'wbml.net',
 | 
			
		||||
@ -3307,6 +3458,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'wegwrfmail.de',
 | 
			
		||||
        'wegwrfmail.net',
 | 
			
		||||
        'wegwrfmail.org',
 | 
			
		||||
        'weizixu.com',
 | 
			
		||||
        'wekawa.com',
 | 
			
		||||
        'welikecookies.com',
 | 
			
		||||
        'wellsfargocomcardholders.com',
 | 
			
		||||
@ -3316,6 +3468,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'wfgdfhj.tk',
 | 
			
		||||
        'wg0.com',
 | 
			
		||||
        'wh4f.org',
 | 
			
		||||
        'whaaaaaaaaaat.com',
 | 
			
		||||
        'whatiaas.com',
 | 
			
		||||
        'whatifanalytics.com',
 | 
			
		||||
        'whatpaas.com',
 | 
			
		||||
@ -3327,6 +3480,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'wickmail.net',
 | 
			
		||||
        'widaryanto.info',
 | 
			
		||||
        'widget.gg',
 | 
			
		||||
        'wiemei.com',
 | 
			
		||||
        'wierie.tk',
 | 
			
		||||
        'wifimaple.com',
 | 
			
		||||
        'wifioak.com',
 | 
			
		||||
@ -3355,6 +3509,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'wudet.men',
 | 
			
		||||
        'wuespdj.xyz',
 | 
			
		||||
        'wupics.com',
 | 
			
		||||
        'wuuvo.com',
 | 
			
		||||
        'wuzup.net',
 | 
			
		||||
        'wuzupmail.net',
 | 
			
		||||
        'wwjmp.com',
 | 
			
		||||
@ -3466,6 +3621,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'zebins.eu',
 | 
			
		||||
        'zehnminuten.de',
 | 
			
		||||
        'zehnminutenmail.de',
 | 
			
		||||
        'zemzar.net',
 | 
			
		||||
        'zepp.dk',
 | 
			
		||||
        'zetmail.com',
 | 
			
		||||
        'zfymail.com',
 | 
			
		||||
@ -3476,6 +3632,7 @@ class BlackListRule implements Rule
 | 
			
		||||
        'zhorachu.com',
 | 
			
		||||
        'zik.dj',
 | 
			
		||||
        'zipcad.com',
 | 
			
		||||
        'zipcatfish.com',
 | 
			
		||||
        'zipo1.gq',
 | 
			
		||||
        'zippymail.info',
 | 
			
		||||
        'zipsendtest.com',
 | 
			
		||||
@ -3497,27 +3654,13 @@ class BlackListRule implements Rule
 | 
			
		||||
        'zzz.com',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param string $attribute
 | 
			
		||||
     * @param mixed $value
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    public function passes($attribute, $value): bool
 | 
			
		||||
    public function validate(string $attribute, mixed $value, Closure $fail): void
 | 
			
		||||
    {
 | 
			
		||||
        $parts = explode("@", $value);
 | 
			
		||||
 | 
			
		||||
        if (is_array($parts)) {
 | 
			
		||||
            return ! in_array($parts[1], $this->blacklist);
 | 
			
		||||
        } else {
 | 
			
		||||
            return true;
 | 
			
		||||
        if (is_array($parts) && in_array($parts[1], $this->blacklist)) {
 | 
			
		||||
            $fail('This domain is blacklisted, if you think this is in error, please email contact@invoiceninja.com');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    public function message(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'This domain is blacklisted, if you think this is in error, please email contact@invoiceninja.com';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -11,32 +11,26 @@
 | 
			
		||||
 | 
			
		||||
namespace App\Http\ValidationRules\Account;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Contracts\Validation\Rule;
 | 
			
		||||
use Closure;
 | 
			
		||||
use Illuminate\Contracts\Validation\ValidationRule;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class EmailBlackListRule.
 | 
			
		||||
 */
 | 
			
		||||
class EmailBlackListRule implements Rule
 | 
			
		||||
class EmailBlackListRule implements ValidationRule
 | 
			
		||||
{
 | 
			
		||||
    public array $blacklist = [
 | 
			
		||||
 | 
			
		||||
        'noddy@invoiceninja.com',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param string $attribute
 | 
			
		||||
     * @param mixed $value
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    public function passes($attribute, $value)
 | 
			
		||||
 | 
			
		||||
    public function validate(string $attribute, mixed $value, Closure $fail): void
 | 
			
		||||
    {
 | 
			
		||||
        return ! in_array($value, $this->blacklist);
 | 
			
		||||
 | 
			
		||||
        if (in_array($value, $this->blacklist)) {
 | 
			
		||||
            $fail('This email address is blacklisted, if you think this is in error, please email contact@invoiceninja.com');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    public function message()
 | 
			
		||||
    {
 | 
			
		||||
        return 'This email address is blacklisted, if you think this is in error, please email contact@invoiceninja.com';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -58,7 +58,7 @@ class MatchBankTransactions implements ShouldQueue
 | 
			
		||||
    private $categories;
 | 
			
		||||
 | 
			
		||||
    private float $available_balance = 0;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    private float $applied_amount = 0;
 | 
			
		||||
 | 
			
		||||
    private array $attachable_invoices = [];
 | 
			
		||||
@ -94,7 +94,6 @@ class MatchBankTransactions implements ShouldQueue
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $bank_categories = Cache::get('bank_categories');
 | 
			
		||||
        
 | 
			
		||||
        if (!$bank_categories && $yodlee) {
 | 
			
		||||
            $_categories = $yodlee->getTransactionCategories();
 | 
			
		||||
            $this->categories = collect($_categories->transactionCategory);
 | 
			
		||||
@ -135,11 +134,11 @@ class MatchBankTransactions implements ShouldQueue
 | 
			
		||||
        return $collection->toArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function checkPayable($invoices) :bool
 | 
			
		||||
    private function checkPayable($invoices): bool
 | 
			
		||||
    {
 | 
			
		||||
        foreach ($invoices as $invoice) {
 | 
			
		||||
            $invoice->service()->markSent();
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            if (!$invoice->isPayable()) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
@ -158,12 +157,12 @@ class MatchBankTransactions implements ShouldQueue
 | 
			
		||||
 | 
			
		||||
        $_expenses = explode(",", $input['expense_id']);
 | 
			
		||||
 | 
			
		||||
        foreach($_expenses as $_expense) {
 | 
			
		||||
                    
 | 
			
		||||
        foreach ($_expenses as $_expense) {
 | 
			
		||||
 | 
			
		||||
            $expense = Expense::withTrashed()
 | 
			
		||||
                             ->where('id', $this->decodePrimaryKey($_expense))
 | 
			
		||||
                             ->where('company_id', $this->bt->company_id)
 | 
			
		||||
                             ->first();
 | 
			
		||||
                ->where('id', $this->decodePrimaryKey($_expense))
 | 
			
		||||
                ->where('company_id', $this->bt->company_id)
 | 
			
		||||
                ->first();
 | 
			
		||||
 | 
			
		||||
            if ($expense && !$expense->transaction_id) {
 | 
			
		||||
                $expense->transaction_id = $this->bt->id;
 | 
			
		||||
@ -178,7 +177,7 @@ class MatchBankTransactions implements ShouldQueue
 | 
			
		||||
                $this->bts->push($this->bt->id);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -202,7 +201,7 @@ class MatchBankTransactions implements ShouldQueue
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $payment = Payment::withTrashed()->find($input['payment_id']);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if ($payment && !$payment->transaction_id) {
 | 
			
		||||
            $payment->transaction_id = $this->bt->id;
 | 
			
		||||
            $payment->saveQuietly();
 | 
			
		||||
@ -218,7 +217,7 @@ class MatchBankTransactions implements ShouldQueue
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function matchInvoicePayment($input) :self
 | 
			
		||||
    private function matchInvoicePayment($input): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->bt = BankTransaction::withTrashed()->find($input['id']);
 | 
			
		||||
 | 
			
		||||
@ -227,10 +226,10 @@ class MatchBankTransactions implements ShouldQueue
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $_invoices = Invoice::query()
 | 
			
		||||
                            ->withTrashed()
 | 
			
		||||
                            ->where('company_id', $this->bt->company_id)
 | 
			
		||||
                            ->whereIn('id', $this->getInvoices($input['invoice_ids']));
 | 
			
		||||
        
 | 
			
		||||
            ->withTrashed()
 | 
			
		||||
            ->where('company_id', $this->bt->company_id)
 | 
			
		||||
            ->whereIn('id', $this->getInvoices($input['invoice_ids']));
 | 
			
		||||
 | 
			
		||||
        $amount = $this->bt->amount;
 | 
			
		||||
 | 
			
		||||
        if ($_invoices && $this->checkPayable($_invoices)) {
 | 
			
		||||
@ -242,7 +241,7 @@ class MatchBankTransactions implements ShouldQueue
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function matchExpense($input) :self
 | 
			
		||||
    private function matchExpense($input): self
 | 
			
		||||
    {
 | 
			
		||||
        //if there is a category id, pull it from Yodlee and insert - or just reuse!!
 | 
			
		||||
        $this->bt = BankTransaction::query()->withTrashed()->find($input['id']);
 | 
			
		||||
@ -274,7 +273,7 @@ class MatchBankTransactions implements ShouldQueue
 | 
			
		||||
        if (array_key_exists('vendor_id', $input)) {
 | 
			
		||||
            $this->bt->vendor_id = $input['vendor_id'];
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $this->bt->status_id = BankTransaction::STATUS_CONVERTED;
 | 
			
		||||
        $this->bt->save();
 | 
			
		||||
 | 
			
		||||
@ -283,7 +282,7 @@ class MatchBankTransactions implements ShouldQueue
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function createPayment($invoices, float $amount) :void
 | 
			
		||||
    private function createPayment($invoices, float $amount): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->available_balance = $amount;
 | 
			
		||||
 | 
			
		||||
@ -320,7 +319,7 @@ class MatchBankTransactions implements ShouldQueue
 | 
			
		||||
        if (!$this->invoice) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        /* Create Payment */
 | 
			
		||||
        $payment = PaymentFactory::create($this->invoice->company_id, $this->invoice->user_id);
 | 
			
		||||
 | 
			
		||||
@ -333,7 +332,7 @@ class MatchBankTransactions implements ShouldQueue
 | 
			
		||||
        $payment->currency_id = $this->bt->currency_id;
 | 
			
		||||
        $payment->is_manual = false;
 | 
			
		||||
        $payment->date = $this->bt->date ? Carbon::parse($this->bt->date) : now();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        /* Bank Transfer! */
 | 
			
		||||
        $payment_type_id = 1;
 | 
			
		||||
@ -341,7 +340,7 @@ class MatchBankTransactions implements ShouldQueue
 | 
			
		||||
        $payment->saveQuietly();
 | 
			
		||||
 | 
			
		||||
        $payment->service()->applyNumber()->save();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if ($payment->client->getSetting('send_email_on_mark_paid')) {
 | 
			
		||||
            $payment->service()->sendEmail();
 | 
			
		||||
        }
 | 
			
		||||
@ -360,24 +359,24 @@ class MatchBankTransactions implements ShouldQueue
 | 
			
		||||
        $this->invoice->next_send_date = null;
 | 
			
		||||
 | 
			
		||||
        $this->invoice
 | 
			
		||||
                ->service()
 | 
			
		||||
                ->applyNumber()
 | 
			
		||||
                ->deletePdf()
 | 
			
		||||
                ->save();
 | 
			
		||||
            ->service()
 | 
			
		||||
            ->applyNumber()
 | 
			
		||||
            ->deletePdf()
 | 
			
		||||
            ->save();
 | 
			
		||||
 | 
			
		||||
        $payment->ledger()
 | 
			
		||||
                ->updatePaymentBalance($amount * -1);
 | 
			
		||||
            ->updatePaymentBalance($amount * -1);
 | 
			
		||||
 | 
			
		||||
        $this->invoice
 | 
			
		||||
             ->client
 | 
			
		||||
             ->service()
 | 
			
		||||
             ->updateBalanceAndPaidToDate($this->applied_amount*-1, $amount)
 | 
			
		||||
             ->save();
 | 
			
		||||
            ->client
 | 
			
		||||
            ->service()
 | 
			
		||||
            ->updateBalanceAndPaidToDate($this->applied_amount * -1, $amount)
 | 
			
		||||
            ->save();
 | 
			
		||||
 | 
			
		||||
        $this->invoice = $this->invoice
 | 
			
		||||
                             ->service()
 | 
			
		||||
                             ->workFlow()
 | 
			
		||||
                             ->save();
 | 
			
		||||
            ->service()
 | 
			
		||||
            ->workFlow()
 | 
			
		||||
            ->save();
 | 
			
		||||
 | 
			
		||||
        /* Update Invoice balance */
 | 
			
		||||
        event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
 | 
			
		||||
@ -389,13 +388,13 @@ class MatchBankTransactions implements ShouldQueue
 | 
			
		||||
        $this->bt->save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function resolveCategory($input) :?int
 | 
			
		||||
    private function resolveCategory($input): ?int
 | 
			
		||||
    {
 | 
			
		||||
        if (array_key_exists('ninja_category_id', $input) && (int)$input['ninja_category_id'] > 1) {
 | 
			
		||||
        if (array_key_exists('ninja_category_id', $input) && (int) $input['ninja_category_id'] > 1) {
 | 
			
		||||
            $this->bt->ninja_category_id = $input['ninja_category_id'];
 | 
			
		||||
            $this->bt->save();
 | 
			
		||||
 | 
			
		||||
            return (int)$input['ninja_category_id'];
 | 
			
		||||
            return (int) $input['ninja_category_id'];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $category = $this->categories->firstWhere('highLevelCategoryId', $this->bt->category_id);
 | 
			
		||||
@ -414,7 +413,7 @@ class MatchBankTransactions implements ShouldQueue
 | 
			
		||||
 | 
			
		||||
            return $ec->id;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										177
									
								
								app/Jobs/Bank/ProcessBankTransactionsNordigen.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								app/Jobs/Bank/ProcessBankTransactionsNordigen.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,177 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Credit Ninja (https://invoiceninja.com).
 | 
			
		||||
 *
 | 
			
		||||
 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright Copyright (c) 2022. Credit Ninja LLC (https://invoiceninja.com)
 | 
			
		||||
 *
 | 
			
		||||
 * @license https://www.elastic.co/licensing/elastic-license
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace App\Jobs\Bank;
 | 
			
		||||
 | 
			
		||||
use App\Helpers\Bank\Nordigen\Nordigen;
 | 
			
		||||
use App\Libraries\MultiDB;
 | 
			
		||||
use App\Models\BankIntegration;
 | 
			
		||||
use App\Models\BankTransaction;
 | 
			
		||||
use App\Models\Company;
 | 
			
		||||
use App\Notifications\Ninja\GenericNinjaAdminNotification;
 | 
			
		||||
use App\Services\Bank\BankMatchingService;
 | 
			
		||||
use Illuminate\Bus\Queueable;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldQueue;
 | 
			
		||||
use Illuminate\Foundation\Bus\Dispatchable;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
 | 
			
		||||
class ProcessBankTransactionsNordigen implements ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 | 
			
		||||
 | 
			
		||||
    private BankIntegration $bank_integration;
 | 
			
		||||
 | 
			
		||||
    private ?string $from_date;
 | 
			
		||||
 | 
			
		||||
    public Company $company;
 | 
			
		||||
    public Nordigen $nordigen;
 | 
			
		||||
    public $nordigen_account;
 | 
			
		||||
    private bool $stop_loop = false;
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a new job instance.
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct(BankIntegration $bank_integration)
 | 
			
		||||
    {
 | 
			
		||||
        $this->bank_integration = $bank_integration;
 | 
			
		||||
        $this->from_date = $bank_integration->from_date ?: now()->subDays(90);
 | 
			
		||||
        $this->company = $this->bank_integration->company;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute the job.
 | 
			
		||||
     *
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function handle()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->bank_integration->integration_type != BankIntegration::INTEGRATION_TYPE_NORDIGEN)
 | 
			
		||||
            throw new \Exception("Invalid BankIntegration Type");
 | 
			
		||||
 | 
			
		||||
        if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))
 | 
			
		||||
            throw new \Exception("Missing credentials for bank_integration service nordigen");
 | 
			
		||||
 | 
			
		||||
        $this->nordigen = new Nordigen();
 | 
			
		||||
 | 
			
		||||
        set_time_limit(0);
 | 
			
		||||
 | 
			
		||||
        nlog("Nordigen: Processing transactions for account: {$this->bank_integration->account->key}");
 | 
			
		||||
 | 
			
		||||
        // UPDATE ACCOUNT
 | 
			
		||||
        try {
 | 
			
		||||
            $this->updateAccount();
 | 
			
		||||
        } catch (\Exception $e) {
 | 
			
		||||
            nlog("Nordigen: {$this->bank_integration->nordigen_account_id} - exited abnormally => " . $e->getMessage());
 | 
			
		||||
 | 
			
		||||
            $content = [
 | 
			
		||||
                "Processing transactions for account: {$this->bank_integration->nordigen_account_id} failed",
 | 
			
		||||
                "Exception Details => ",
 | 
			
		||||
                $e->getMessage(),
 | 
			
		||||
            ];
 | 
			
		||||
 | 
			
		||||
            $this->bank_integration->company->notification(new GenericNinjaAdminNotification($content))->ninja();
 | 
			
		||||
 | 
			
		||||
            throw $e;
 | 
			
		||||
        }
 | 
			
		||||
        if (!$this->nordigen_account)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // UPDATE TRANSACTIONS
 | 
			
		||||
        try {
 | 
			
		||||
            $this->processTransactions();
 | 
			
		||||
        } catch (\Exception $e) {
 | 
			
		||||
            nlog("Nordigen: {$this->bank_integration->nordigen_account_id} - exited abnormally => " . $e->getMessage());
 | 
			
		||||
 | 
			
		||||
            $content = [
 | 
			
		||||
                "Processing transactions for account: {$this->bank_integration->nordigen_account_id} failed",
 | 
			
		||||
                "Exception Details => ",
 | 
			
		||||
                $e->getMessage(),
 | 
			
		||||
            ];
 | 
			
		||||
 | 
			
		||||
            $this->bank_integration->company->notification(new GenericNinjaAdminNotification($content))->ninja();
 | 
			
		||||
 | 
			
		||||
            throw $e;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Perform Matching
 | 
			
		||||
        BankMatchingService::dispatch($this->company->id, $this->company->db);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function updateAccount()
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->nordigen->isAccountActive($this->bank_integration->nordigen_account_id)) {
 | 
			
		||||
            $this->bank_integration->disabled_upstream = true;
 | 
			
		||||
            $this->bank_integration->save();
 | 
			
		||||
            $this->stop_loop = false;
 | 
			
		||||
            nlog("Nordigen: account inactive: " . $this->bank_integration->nordigen_account_id);
 | 
			
		||||
            // @turbo124 @todo send email for expired account
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->nordigen_account = $this->nordigen->getAccount($this->bank_integration->nordigen_account_id);
 | 
			
		||||
 | 
			
		||||
        $this->bank_integration->disabled_upstream = false;
 | 
			
		||||
        $this->bank_integration->bank_account_status = $this->nordigen_account['account_status'];
 | 
			
		||||
        $this->bank_integration->balance = $this->nordigen_account['current_balance'];
 | 
			
		||||
 | 
			
		||||
        $this->bank_integration->save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function processTransactions()
 | 
			
		||||
    {
 | 
			
		||||
        //Get transaction count object
 | 
			
		||||
        $transactions = $this->nordigen->getTransactions($this->bank_integration->nordigen_account_id, $this->from_date);
 | 
			
		||||
 | 
			
		||||
        //if no transactions, update the from_date and move on
 | 
			
		||||
        if (count($transactions) == 0) {
 | 
			
		||||
 | 
			
		||||
            $this->bank_integration->from_date = now()->subDays(5);
 | 
			
		||||
            $this->bank_integration->disabled_upstream = false;
 | 
			
		||||
            $this->bank_integration->save();
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //Harvest the company
 | 
			
		||||
 | 
			
		||||
        MultiDB::setDb($this->company->db);
 | 
			
		||||
 | 
			
		||||
        /*Get the user */
 | 
			
		||||
        $user_id = $this->company->owner()->id;
 | 
			
		||||
 | 
			
		||||
        /* Unguard the model to perform batch inserts */
 | 
			
		||||
        BankTransaction::unguard();
 | 
			
		||||
 | 
			
		||||
        $now = now();
 | 
			
		||||
 | 
			
		||||
        foreach ($transactions as $transaction) {
 | 
			
		||||
 | 
			
		||||
            if (BankTransaction::where('transaction_id', $transaction['transaction_id'])->where('company_id', $this->company->id)->where('bank_integration_id', $this->bank_integration->id)->withTrashed()->exists())
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            //this should be much faster to insert than using ::create()
 | 
			
		||||
            \DB::table('bank_transactions')->insert(
 | 
			
		||||
                array_merge($transaction, [
 | 
			
		||||
                    'company_id' => $this->company->id,
 | 
			
		||||
                    'user_id' => $user_id,
 | 
			
		||||
                    'bank_integration_id' => $this->bank_integration->id,
 | 
			
		||||
                    'created_at' => $now,
 | 
			
		||||
                    'updated_at' => $now,
 | 
			
		||||
                ])
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->bank_integration->from_date = now()->subDays(5);
 | 
			
		||||
        $this->bank_integration->save();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -14,6 +14,7 @@ namespace App\Jobs\Bank;
 | 
			
		||||
use App\Helpers\Bank\Yodlee\Transformer\AccountTransformer;
 | 
			
		||||
use App\Helpers\Bank\Yodlee\Yodlee;
 | 
			
		||||
use App\Libraries\MultiDB;
 | 
			
		||||
use App\Models\Account;
 | 
			
		||||
use App\Models\BankIntegration;
 | 
			
		||||
use App\Models\BankTransaction;
 | 
			
		||||
use App\Models\Company;
 | 
			
		||||
@ -26,7 +27,7 @@ use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
 | 
			
		||||
class ProcessBankTransactions implements ShouldQueue
 | 
			
		||||
class ProcessBankTransactionsYodlee implements ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 | 
			
		||||
 | 
			
		||||
@ -61,21 +62,24 @@ class ProcessBankTransactions implements ShouldQueue
 | 
			
		||||
     */
 | 
			
		||||
    public function handle()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->bank_integration->integration_type != BankIntegration::INTEGRATION_TYPE_YODLEE)
 | 
			
		||||
            throw new \Exception("Invalid BankIntegration Type");
 | 
			
		||||
 | 
			
		||||
        set_time_limit(0);
 | 
			
		||||
 | 
			
		||||
        //Loop through everything until we are up to date
 | 
			
		||||
        $this->from_date = $this->from_date ?: '2021-01-01';
 | 
			
		||||
 | 
			
		||||
        nlog("Processing transactions for account: {$this->bank_integration->account->key}");
 | 
			
		||||
        nlog("Yodlee: Processing transactions for account: {$this->bank_integration->account->key}");
 | 
			
		||||
 | 
			
		||||
        do {
 | 
			
		||||
            try {
 | 
			
		||||
                $this->processTransactions();
 | 
			
		||||
            } catch(\Exception $e) {
 | 
			
		||||
                nlog("{$this->bank_integration_account_id} - exited abnormally => ". $e->getMessage());
 | 
			
		||||
            } catch (\Exception $e) {
 | 
			
		||||
                nlog("Yodlee: {$this->bank_integration->bank_account_id} - exited abnormally => " . $e->getMessage());
 | 
			
		||||
 | 
			
		||||
                $content = [
 | 
			
		||||
                    "Processing transactions for account: {$this->bank_integration->account->key} failed",
 | 
			
		||||
                    "Processing transactions for account: {$this->bank_integration->bank_account_id} failed",
 | 
			
		||||
                    "Exception Details => ",
 | 
			
		||||
                    $e->getMessage(),
 | 
			
		||||
                ];
 | 
			
		||||
@ -103,21 +107,21 @@ class ProcessBankTransactions implements ShouldQueue
 | 
			
		||||
        try {
 | 
			
		||||
            $account_summary = $yodlee->getAccountSummary($this->bank_integration->bank_account_id);
 | 
			
		||||
 | 
			
		||||
            if($account_summary) {
 | 
			
		||||
            if ($account_summary) {
 | 
			
		||||
 | 
			
		||||
                $at = new AccountTransformer();
 | 
			
		||||
                $account = $at->transform($account_summary);
 | 
			
		||||
 | 
			
		||||
                if($account[0]['current_balance']) {
 | 
			
		||||
                if ($account[0]['current_balance']) {
 | 
			
		||||
                    $this->bank_integration->balance = $account[0]['current_balance'];
 | 
			
		||||
                    $this->bank_integration->currency = $account[0]['account_currency'];
 | 
			
		||||
                    $this->bank_integration->bank_account_status = $account[0]['account_status'];
 | 
			
		||||
                    $this->bank_integration->save();
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        } catch(\Exception $e) {
 | 
			
		||||
            nlog("YODLEE: unable to update account summary for {$this->bank_integration->bank_account_id} => ". $e->getMessage());
 | 
			
		||||
        } catch (\Exception $e) {
 | 
			
		||||
            nlog("YODLEE: unable to update account summary for {$this->bank_integration->bank_account_id} => " . $e->getMessage());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $data = [
 | 
			
		||||
@ -151,14 +155,14 @@ class ProcessBankTransactions implements ShouldQueue
 | 
			
		||||
 | 
			
		||||
        /*Get the user */
 | 
			
		||||
        $user_id = $this->company->owner()->id;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        /* Unguard the model to perform batch inserts */
 | 
			
		||||
        BankTransaction::unguard();
 | 
			
		||||
 | 
			
		||||
        $now = now();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        foreach ($transactions as $transaction) {
 | 
			
		||||
            if (BankTransaction::query()->where('transaction_id', $transaction['transaction_id'])->where('company_id', $this->company->id)->withTrashed()->exists()) {
 | 
			
		||||
            if (BankTransaction::query()->where('transaction_id', $transaction['transaction_id'])->where('company_id', $this->company->id)->where('bank_integration_id', $this->bank_integration->id)->withTrashed()->exists()) { 
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -189,7 +193,7 @@ class ProcessBankTransactions implements ShouldQueue
 | 
			
		||||
    {
 | 
			
		||||
        return [new WithoutOverlapping($this->bank_integration_account_id)];
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function backoff()
 | 
			
		||||
    {
 | 
			
		||||
        return [rand(10, 15), rand(30, 40), rand(60, 79), rand(160, 200), rand(3000, 5000)];
 | 
			
		||||
@ -224,7 +224,7 @@ class CompanyExport implements ShouldQueue
 | 
			
		||||
 | 
			
		||||
        $this->export_data['invoices'] = $this->company->invoices()->orderBy('number', 'DESC')->cursor()->map(function ($invoice) {
 | 
			
		||||
            $invoice = $this->transformBasicEntities($invoice);
 | 
			
		||||
            $invoice = $this->transformArrayOfKeys($invoice, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id','project_id']);
 | 
			
		||||
            $invoice = $this->transformArrayOfKeys($invoice, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']);
 | 
			
		||||
            $invoice->tax_data = '';
 | 
			
		||||
 | 
			
		||||
            return $invoice->makeVisible(['id',
 | 
			
		||||
@ -331,7 +331,8 @@ class CompanyExport implements ShouldQueue
 | 
			
		||||
            $task = $this->transformBasicEntities($task);
 | 
			
		||||
            $task = $this->transformArrayOfKeys($task, ['client_id', 'invoice_id', 'project_id', 'status_id']);
 | 
			
		||||
 | 
			
		||||
            return $task->makeVisible(['id']);
 | 
			
		||||
            return $task->makeHidden(['hash','meta'])->makeVisible(['id']);
 | 
			
		||||
// return $task->makeHidden(['hash','meta'])->makeVisible(['id']); //@release v5.7.63
 | 
			
		||||
        })->all();
 | 
			
		||||
 | 
			
		||||
        $this->export_data['task_statuses'] = $this->company->task_statuses->map(function ($status) {
 | 
			
		||||
@ -387,19 +388,19 @@ class CompanyExport implements ShouldQueue
 | 
			
		||||
        })->all();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        $this->export_data['bank_integrations'] = $this->company->bank_integrations()->orderBy('id', 'ASC')->cursor()->map(function ($bank_integration) {
 | 
			
		||||
        $this->export_data['bank_integrations'] = $this->company->bank_integrations()->withTrashed()->orderBy('id', 'ASC')->cursor()->map(function ($bank_integration) {
 | 
			
		||||
            $bank_integration = $this->transformArrayOfKeys($bank_integration, ['account_id','company_id', 'user_id']);
 | 
			
		||||
 | 
			
		||||
            return $bank_integration->makeVisible(['id','user_id','company_id','account_id']);
 | 
			
		||||
            return $bank_integration->makeVisible(['id','user_id','company_id','account_id','hashed_id']);
 | 
			
		||||
        })->all();
 | 
			
		||||
 | 
			
		||||
        $this->export_data['bank_transactions'] = $this->company->bank_transactions()->orderBy('id', 'ASC')->cursor()->map(function ($bank_transaction) {
 | 
			
		||||
            $bank_transaction = $this->transformArrayOfKeys($bank_transaction, ['company_id', 'user_id','bank_integration_id','expense_id','category_id','ninja_category_id','vendor_id']);
 | 
			
		||||
        $this->export_data['bank_transactions'] = $this->company->bank_transactions()->withTrashed()->orderBy('id', 'ASC')->cursor()->map(function ($bank_transaction) {
 | 
			
		||||
            $bank_transaction = $this->transformArrayOfKeys($bank_transaction, ['company_id', 'user_id','bank_integration_id','expense_id','ninja_category_id','vendor_id']);
 | 
			
		||||
 | 
			
		||||
            return $bank_transaction->makeVisible(['id','user_id','company_id']);
 | 
			
		||||
        })->all();
 | 
			
		||||
 | 
			
		||||
        $this->export_data['schedulers'] = $this->company->schedulers()->orderBy('id', 'ASC')->cursor()->map(function ($scheduler) {
 | 
			
		||||
        $this->export_data['schedulers'] = $this->company->schedulers()->withTrashed()->orderBy('id', 'ASC')->cursor()->map(function ($scheduler) {
 | 
			
		||||
            $scheduler = $this->transformArrayOfKeys($scheduler, ['company_id', 'user_id']);
 | 
			
		||||
 | 
			
		||||
            return $scheduler->makeVisible(['id','user_id','company_id']);
 | 
			
		||||
 | 
			
		||||
@ -63,14 +63,12 @@ use App\Utils\Ninja;
 | 
			
		||||
use App\Utils\TempFile;
 | 
			
		||||
use App\Utils\Traits\GeneratesCounter;
 | 
			
		||||
use App\Utils\Traits\MakesHash;
 | 
			
		||||
use function GuzzleHttp\json_encode;
 | 
			
		||||
use Illuminate\Bus\Queueable;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldQueue;
 | 
			
		||||
use Illuminate\Foundation\Bus\Dispatchable;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
use Illuminate\Support\Facades\App;
 | 
			
		||||
use Illuminate\Support\Facades\Cache;
 | 
			
		||||
use Illuminate\Support\Facades\Storage;
 | 
			
		||||
use Illuminate\Support\Str;
 | 
			
		||||
use JsonMachine\JsonDecoder\ExtJsonDecoder;
 | 
			
		||||
@ -142,7 +140,6 @@ class CompanyImport implements ShouldQueue
 | 
			
		||||
        'recurring_expenses',
 | 
			
		||||
        'expenses',
 | 
			
		||||
        'tasks',
 | 
			
		||||
        'payments',
 | 
			
		||||
        'company_ledger',
 | 
			
		||||
        'designs',
 | 
			
		||||
        'documents',
 | 
			
		||||
@ -569,7 +566,7 @@ class CompanyImport implements ShouldQueue
 | 
			
		||||
                ['expenses' => 'expense_id'],
 | 
			
		||||
                ['vendors' => 'vendor_id'],
 | 
			
		||||
                ['expense_categories' => 'ninja_category_id'],
 | 
			
		||||
                ['expense_categories' => 'category_id'],
 | 
			
		||||
                // ['expense_categories' => 'category_id'],
 | 
			
		||||
                ['bank_integrations' => 'bank_integration_id']
 | 
			
		||||
            ],
 | 
			
		||||
            'bank_transactions',
 | 
			
		||||
@ -1143,7 +1140,34 @@ class CompanyImport implements ShouldQueue
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $storage_url = (object)$this->getObject('storage_url', true);
 | 
			
		||||
 | 
			
		||||
            if (!Storage::exists($document->url) && is_string($storage_url)) {
 | 
			
		||||
                $url = $storage_url . $document->url;
 | 
			
		||||
 | 
			
		||||
                $file = @file_get_contents($url);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                if ($file) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        Storage::disk(config('filesystems.default'))->put($document->url, $file);
 | 
			
		||||
 | 
			
		||||
                        
 | 
			
		||||
                    } catch(\Exception $e) {
 | 
			
		||||
                        nlog($e->getMessage());
 | 
			
		||||
                        nlog("I could not upload {$document->url}");
 | 
			
		||||
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else 
 | 
			
		||||
                    continue;
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            $new_document = new Document();
 | 
			
		||||
            $new_document->disk = config('filesystems.default');
 | 
			
		||||
            $new_document->user_id = $this->transformId('users', $document->user_id);
 | 
			
		||||
            $new_document->assigned_user_id = $this->transformId('users', $document->assigned_user_id);
 | 
			
		||||
            $new_document->company_id = $this->company->id;
 | 
			
		||||
@ -1169,26 +1193,6 @@ class CompanyImport implements ShouldQueue
 | 
			
		||||
 | 
			
		||||
            $new_document->save(['timestamps' => false]);
 | 
			
		||||
 | 
			
		||||
            $storage_url = (object)$this->getObject('storage_url', true);
 | 
			
		||||
 | 
			
		||||
            if (!Storage::exists($new_document->url) && is_string($storage_url)) {
 | 
			
		||||
                $url = $storage_url . $new_document->url;
 | 
			
		||||
 | 
			
		||||
                $file = @file_get_contents($url);
 | 
			
		||||
 | 
			
		||||
                if ($file) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        Storage::disk(config('filesystems.default'))->put($new_document->url, $file);
 | 
			
		||||
 | 
			
		||||
                        $new_document->disk = config('filesystems.default');
 | 
			
		||||
                        $new_document->save();
 | 
			
		||||
                    } catch(\Exception $e) {
 | 
			
		||||
                        nlog($e->getMessage());
 | 
			
		||||
                        nlog("I could not upload {$new_document->url}");
 | 
			
		||||
                        $new_document->forceDelete();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
@ -1727,7 +1731,9 @@ class CompanyImport implements ShouldQueue
 | 
			
		||||
    */
 | 
			
		||||
    private function transformId(string $resource, ?string $old): ?int
 | 
			
		||||
    {
 | 
			
		||||
        if (empty($old)) {
 | 
			
		||||
 | 
			
		||||
        // WjnegYbwZ1 == 0 return null;
 | 
			
		||||
        if (empty($old) || $old == 'WjnegYbwZ1') {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -1736,6 +1742,7 @@ class CompanyImport implements ShouldQueue
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (! array_key_exists($resource, $this->ids)) {
 | 
			
		||||
 | 
			
		||||
            $this->sendImportMail("The Import failed due to missing data in the import file. Resource {$resource} not available.");
 | 
			
		||||
 | 
			
		||||
            throw new \Exception("Resource {$resource} not available.");
 | 
			
		||||
@ -1744,16 +1751,12 @@ class CompanyImport implements ShouldQueue
 | 
			
		||||
        if (! array_key_exists("{$old}", $this->ids[$resource])) {
 | 
			
		||||
            // nlog($this->ids[$resource]);
 | 
			
		||||
            nlog("searching for {$old} in {$resource}");
 | 
			
		||||
 | 
			
		||||
            nlog("If we are missing a user - default to the company owner");
 | 
			
		||||
            
 | 
			
		||||
            if ($resource == 'users') {
 | 
			
		||||
                return $this->company_owner->id;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $this->sendImportMail("The Import failed due to missing data in the import file. Resource {$resource} not available.");
 | 
			
		||||
            
 | 
			
		||||
            nlog($this->ids[$resource]);
 | 
			
		||||
            $this->sendImportMail("The Import failed due to missing data in the import file. Key {$old} not found in {$resource}.");
 | 
			
		||||
 | 
			
		||||
            throw new \Exception("Missing {$resource} key: {$old}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -65,7 +65,7 @@ class CreateCompany
 | 
			
		||||
        $company->settings = $settings;
 | 
			
		||||
        $company->db = config('database.default');
 | 
			
		||||
        $company->enabled_modules = config('ninja.enabled_modules');
 | 
			
		||||
        $company->subdomain = isset($this->request['subdomain']) ? $this->request['subdomain'] : '';
 | 
			
		||||
        $company->subdomain = isset($this->request['subdomain']) ? $this->request['subdomain'] : MultiDB::randomSubdomainGenerator();
 | 
			
		||||
        $company->custom_fields = new \stdClass;
 | 
			
		||||
        $company->default_password_timeout = 1800000;
 | 
			
		||||
        $company->client_registration_fields = ClientRegistrationFields::generate();
 | 
			
		||||
 | 
			
		||||
@ -11,14 +11,15 @@
 | 
			
		||||
 | 
			
		||||
namespace App\Jobs\Cron;
 | 
			
		||||
 | 
			
		||||
use App\Jobs\Entity\EmailEntity;
 | 
			
		||||
use App\Libraries\MultiDB;
 | 
			
		||||
use App\Models\Invoice;
 | 
			
		||||
use App\Models\Webhook;
 | 
			
		||||
use App\Libraries\MultiDB;
 | 
			
		||||
use Illuminate\Bus\Queueable;
 | 
			
		||||
use App\Jobs\Entity\EmailEntity;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldQueue;
 | 
			
		||||
use Illuminate\Foundation\Bus\Dispatchable;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
 | 
			
		||||
class AutoBill implements ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
@ -77,6 +78,8 @@ class AutoBill implements ShouldQueue
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                $invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client");
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -11,13 +11,14 @@
 | 
			
		||||
 | 
			
		||||
namespace App\Jobs\Invoice;
 | 
			
		||||
 | 
			
		||||
use App\Jobs\Entity\EmailEntity;
 | 
			
		||||
use App\Models\Invoice;
 | 
			
		||||
use App\Models\Webhook;
 | 
			
		||||
use Illuminate\Bus\Queueable;
 | 
			
		||||
use App\Jobs\Entity\EmailEntity;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldQueue;
 | 
			
		||||
use Illuminate\Foundation\Bus\Dispatchable;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
 | 
			
		||||
class BulkInvoiceJob implements ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
@ -50,6 +51,8 @@ class BulkInvoiceJob implements ShouldQueue
 | 
			
		||||
 | 
			
		||||
        if ($this->invoice->invitations->count() >= 1) {
 | 
			
		||||
            $this->invoice->entityEmailEvent($this->invoice->invitations->first(), 'invoice', $this->reminder_template);
 | 
			
		||||
            $this->invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client");
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -549,7 +549,7 @@ class NinjaMailerJob implements ShouldQueue
 | 
			
		||||
        /* On the hosted platform if the user has not verified their account we fail here - but still check what they are trying to send! */
 | 
			
		||||
        if (Ninja::isHosted() && $this->company->account && !$this->company->account->account_sms_verified) {
 | 
			
		||||
            if (class_exists(\Modules\Admin\Jobs\Account\EmailQuality::class)) {
 | 
			
		||||
                return (new \Modules\Admin\Jobs\Account\EmailQuality($this->nmo, $this->company))->run();
 | 
			
		||||
                (new \Modules\Admin\Jobs\Account\EmailQuality($this->nmo, $this->company))->run();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
 | 
			
		||||
@ -105,7 +105,7 @@ class PaymentFailedMailer implements ShouldQueue
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        //add client payment failures here.
 | 
			
		||||
        //
 | 
			
		||||
        
 | 
			
		||||
        if ($this->client->contacts()->whereNotNull('email')->exists() && $this->payment_hash) {
 | 
			
		||||
            $contact = $this->client->contacts()->whereNotNull('email')->first();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -11,9 +11,11 @@
 | 
			
		||||
 | 
			
		||||
namespace App\Jobs\Ninja;
 | 
			
		||||
 | 
			
		||||
use App\Jobs\Bank\ProcessBankTransactions;
 | 
			
		||||
use App\Jobs\Bank\ProcessBankTransactionsYodlee;
 | 
			
		||||
use App\Jobs\Bank\ProcessBankTransactionsNordigen;
 | 
			
		||||
use App\Libraries\MultiDB;
 | 
			
		||||
use App\Models\Account;
 | 
			
		||||
use App\Models\BankIntegration;
 | 
			
		||||
use App\Utils\Ninja;
 | 
			
		||||
use Illuminate\Bus\Queueable;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldQueue;
 | 
			
		||||
@ -43,20 +45,52 @@ class BankTransactionSync implements ShouldQueue
 | 
			
		||||
     */
 | 
			
		||||
    public function handle()
 | 
			
		||||
    {
 | 
			
		||||
        //multiDB environment, need to
 | 
			
		||||
        foreach (MultiDB::$dbs as $db) {
 | 
			
		||||
            MultiDB::setDB($db);
 | 
			
		||||
        if (config('ninja.db.multi_db_enabled')) {
 | 
			
		||||
 | 
			
		||||
            nlog("syncing transactions");
 | 
			
		||||
            foreach (MultiDB::$dbs as $db) {
 | 
			
		||||
                MultiDB::setDB($db);
 | 
			
		||||
 | 
			
		||||
            $a = Account::with('bank_integrations')->whereNotNull('bank_integration_account_id')->cursor()->each(function ($account) {
 | 
			
		||||
                // $queue = Ninja::isHosted() ? 'bank' : 'default';
 | 
			
		||||
                $this->processYodlee();
 | 
			
		||||
                $this->processNordigen();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
                if ($account->isPaid() && $account->plan == 'enterprise') {
 | 
			
		||||
                    $account->bank_integrations()->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) {
 | 
			
		||||
                        (new ProcessBankTransactions($account->bank_integration_account_id, $bank_integration))->handle();
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->processYodlee();
 | 
			
		||||
            $this->processNordigen();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        nlog("syncing transactions - done");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function processYodlee()
 | 
			
		||||
    {
 | 
			
		||||
        if (Ninja::isHosted()) {
 | 
			
		||||
            nlog("syncing transactions - yodlee");
 | 
			
		||||
 | 
			
		||||
            Account::with('bank_integrations')->whereNotNull('bank_integration_account_id')->cursor()->each(function ($account) {
 | 
			
		||||
 | 
			
		||||
                if ($account->isEnterprisePaidClient()) {
 | 
			
		||||
                    $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) {
 | 
			
		||||
                        (new ProcessBankTransactionsYodlee($account->id, $bank_integration))->handle();
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    private function processNordigen()
 | 
			
		||||
    {
 | 
			
		||||
        if (config("ninja.nordigen.secret_id") && config("ninja.nordigen.secret_key")) { 
 | 
			
		||||
            nlog("syncing transactions - nordigen");
 | 
			
		||||
 | 
			
		||||
            Account::with('bank_integrations')->cursor()->each(function ($account) {
 | 
			
		||||
 | 
			
		||||
                if ((Ninja::isSelfHost() || (Ninja::isHosted() && $account->isEnterprisePaidClient()))) {
 | 
			
		||||
                    $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->cursor()->each(function ($bank_integration) {
 | 
			
		||||
                        (new ProcessBankTransactionsNordigen($bank_integration))->handle();
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -53,11 +53,19 @@ class CompanySizeCheck implements ShouldQueue
 | 
			
		||||
 | 
			
		||||
            nlog("updating all client credit balances");
 | 
			
		||||
 | 
			
		||||
            Client::where('updated_at', '>', now()->subDay())
 | 
			
		||||
            Client::query()
 | 
			
		||||
                  ->where('updated_at', '>', now()->subDay())
 | 
			
		||||
                  ->cursor()
 | 
			
		||||
                  ->each(function ($client) {
 | 
			
		||||
                      $client->credit_balance = $client->service()->getCreditBalance();
 | 
			
		||||
                      $client->save();
 | 
			
		||||
                        
 | 
			
		||||
                        $old_credit_balance = $client->credit_balance;
 | 
			
		||||
                        $new_credit_balance = $client->service()->getCreditBalance();
 | 
			
		||||
 | 
			
		||||
                        if(floatval($old_credit_balance) !== floatval($new_credit_balance)){
 | 
			
		||||
                            $client->credit_balance = $client->service()->getCreditBalance();
 | 
			
		||||
                            $client->saveQuietly();
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                  });
 | 
			
		||||
 | 
			
		||||
            /* Ensures lower permissioned users return the correct dataset and refresh responses */
 | 
			
		||||
@ -87,11 +95,20 @@ class CompanySizeCheck implements ShouldQueue
 | 
			
		||||
 | 
			
		||||
                nlog("updating all client credit balances");
 | 
			
		||||
 | 
			
		||||
                Client::where('updated_at', '>', now()->subDay())
 | 
			
		||||
                Client::query()->where('updated_at', '>', now()->subDay())
 | 
			
		||||
                      ->cursor()
 | 
			
		||||
                      ->each(function ($client) {
 | 
			
		||||
                          $client->credit_balance = $client->service()->getCreditBalance();
 | 
			
		||||
                          $client->save();
 | 
			
		||||
                          
 | 
			
		||||
                        
 | 
			
		||||
                            $old_credit_balance = $client->credit_balance;
 | 
			
		||||
                            $new_credit_balance = $client->service()->getCreditBalance();
 | 
			
		||||
 | 
			
		||||
                            if(floatval($old_credit_balance) !== floatval($new_credit_balance)) {
 | 
			
		||||
                                $client->credit_balance = $client->service()->getCreditBalance();
 | 
			
		||||
                                $client->saveQuietly();
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                      });
 | 
			
		||||
 | 
			
		||||
                Account::where('plan', 'enterprise')
 | 
			
		||||
 | 
			
		||||
@ -215,6 +215,7 @@ class SendReminders implements ShouldQueue
 | 
			
		||||
 | 
			
		||||
                EmailEntity::dispatch($invitation, $invitation->company, $template)->delay(10);
 | 
			
		||||
                event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars(), $template));
 | 
			
		||||
                $invoice->sendEvent(Webhook::EVENT_REMIND_INVOICE, "client");
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -11,24 +11,26 @@
 | 
			
		||||
 | 
			
		||||
namespace App\Jobs\PostMark;
 | 
			
		||||
 | 
			
		||||
use App\DataMapper\Analytics\Mail\EmailBounce;
 | 
			
		||||
use App\DataMapper\Analytics\Mail\EmailSpam;
 | 
			
		||||
use App\Jobs\Util\SystemLogger;
 | 
			
		||||
use App\Models\Company;
 | 
			
		||||
use App\Models\SystemLog;
 | 
			
		||||
use App\Libraries\MultiDB;
 | 
			
		||||
use Postmark\PostmarkClient;
 | 
			
		||||
use Illuminate\Bus\Queueable;
 | 
			
		||||
use App\Jobs\Util\SystemLogger;
 | 
			
		||||
use App\Models\QuoteInvitation;
 | 
			
		||||
use App\Models\CreditInvitation;
 | 
			
		||||
use App\Models\InvoiceInvitation;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
use Turbo124\Beacon\Facades\LightLogs;
 | 
			
		||||
use App\Models\PurchaseOrderInvitation;
 | 
			
		||||
use App\Models\QuoteInvitation;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use App\Models\RecurringInvoiceInvitation;
 | 
			
		||||
use App\Models\SystemLog;
 | 
			
		||||
use App\Notifications\Ninja\EmailSpamNotification;
 | 
			
		||||
use Illuminate\Bus\Queueable;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldQueue;
 | 
			
		||||
use Illuminate\Foundation\Bus\Dispatchable;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
use Postmark\PostmarkClient;
 | 
			
		||||
use Turbo124\Beacon\Facades\LightLogs;
 | 
			
		||||
use App\DataMapper\Analytics\Mail\EmailSpam;
 | 
			
		||||
use App\DataMapper\Analytics\Mail\EmailBounce;
 | 
			
		||||
use App\Notifications\Ninja\EmailSpamNotification;
 | 
			
		||||
use App\Notifications\Ninja\EmailBounceNotification;
 | 
			
		||||
 | 
			
		||||
class ProcessPostmarkWebhook implements ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
@ -82,9 +84,14 @@ class ProcessPostmarkWebhook implements ShouldQueue
 | 
			
		||||
    public function handle()
 | 
			
		||||
    {
 | 
			
		||||
        MultiDB::findAndSetDbByCompanyKey($this->request['Tag']);
 | 
			
		||||
        $company = Company::where('company_key', $this->request['Tag'])->first();
 | 
			
		||||
        
 | 
			
		||||
        $this->invitation = $this->discoverInvitation($this->request['MessageID']);
 | 
			
		||||
 | 
			
		||||
        if ($company && $this->request['RecordType'] == 'SpamComplaint' && config('ninja.notification.slack')) {
 | 
			
		||||
            $company->notification(new EmailSpamNotification($company))->ninja();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!$this->invitation) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
@ -97,6 +104,11 @@ class ProcessPostmarkWebhook implements ShouldQueue
 | 
			
		||||
            case 'Delivery':
 | 
			
		||||
                return $this->processDelivery();
 | 
			
		||||
            case 'Bounce':
 | 
			
		||||
 | 
			
		||||
                if($this->request['Subject'] == ctrans('texts.confirmation_subject')) {
 | 
			
		||||
                    $company->notification(new EmailBounceNotification($this->request['Email']))->ninja();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return $this->processBounce();
 | 
			
		||||
            case 'SpamComplaint':
 | 
			
		||||
                return $this->processSpamComplaint();
 | 
			
		||||
@ -257,8 +269,6 @@ class ProcessPostmarkWebhook implements ShouldQueue
 | 
			
		||||
 | 
			
		||||
        (new SystemLogger($data, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_BOUNCED, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle();
 | 
			
		||||
 | 
			
		||||
        // if(config('ninja.notification.slack'))
 | 
			
		||||
        // $this->invitation->company->notification(new EmailBounceNotification($this->invitation->company->account))->ninja();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // {
 | 
			
		||||
@ -305,14 +315,8 @@ class ProcessPostmarkWebhook implements ShouldQueue
 | 
			
		||||
 | 
			
		||||
        if($sl) {
 | 
			
		||||
            $this->updateSystemLog($sl, $data);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        (new SystemLogger($data, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_SPAM_COMPLAINT, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle();
 | 
			
		||||
 | 
			
		||||
        if (config('ninja.notification.slack')) {
 | 
			
		||||
            $this->invitation->company->notification(new EmailSpamNotification($this->invitation->company->account))->ninja();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function discoverInvitation($message_id)
 | 
			
		||||
 | 
			
		||||
@ -11,24 +11,25 @@
 | 
			
		||||
 | 
			
		||||
namespace App\Jobs\RecurringInvoice;
 | 
			
		||||
 | 
			
		||||
use App\DataMapper\Analytics\SendRecurringFailure;
 | 
			
		||||
use App\Events\Invoice\InvoiceWasCreated;
 | 
			
		||||
use App\Factory\InvoiceInvitationFactory;
 | 
			
		||||
use App\Factory\RecurringInvoiceToInvoiceFactory;
 | 
			
		||||
use App\Jobs\Cron\AutoBill;
 | 
			
		||||
use App\Jobs\Entity\EmailEntity;
 | 
			
		||||
use App\Models\Invoice;
 | 
			
		||||
use App\Models\RecurringInvoice;
 | 
			
		||||
use App\Utils\Ninja;
 | 
			
		||||
use App\Utils\Traits\GeneratesCounter;
 | 
			
		||||
use App\Utils\Traits\MakesHash;
 | 
			
		||||
use Carbon\Carbon;
 | 
			
		||||
use App\Utils\Ninja;
 | 
			
		||||
use App\Models\Invoice;
 | 
			
		||||
use App\Models\Webhook;
 | 
			
		||||
use App\Jobs\Cron\AutoBill;
 | 
			
		||||
use Illuminate\Bus\Queueable;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldQueue;
 | 
			
		||||
use Illuminate\Foundation\Bus\Dispatchable;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use App\Utils\Traits\MakesHash;
 | 
			
		||||
use App\Jobs\Entity\EmailEntity;
 | 
			
		||||
use App\Models\RecurringInvoice;
 | 
			
		||||
use App\Utils\Traits\GeneratesCounter;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
use Turbo124\Beacon\Facades\LightLogs;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use App\Events\Invoice\InvoiceWasCreated;
 | 
			
		||||
use App\Factory\InvoiceInvitationFactory;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldQueue;
 | 
			
		||||
use Illuminate\Foundation\Bus\Dispatchable;
 | 
			
		||||
use App\Factory\RecurringInvoiceToInvoiceFactory;
 | 
			
		||||
use App\DataMapper\Analytics\SendRecurringFailure;
 | 
			
		||||
 | 
			
		||||
class SendRecurring implements ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
@ -117,6 +118,7 @@ class SendRecurring implements ShouldQueue
 | 
			
		||||
            //04-08-2023 edge case to support where online payment notifications are not enabled
 | 
			
		||||
            if(!$invoice->client->getSetting('client_online_payment_notification')) {
 | 
			
		||||
                $this->sendRecurringEmails($invoice);
 | 
			
		||||
                $invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client");
 | 
			
		||||
            }
 | 
			
		||||
        } elseif ($invoice->auto_bill_enabled && $invoice->client->getSetting('auto_bill_date') == 'on_due_date' && $invoice->client->getSetting('auto_email_invoice') && ($invoice->due_date && Carbon::parse($invoice->due_date)->startOfDay()->lte(now()->startOfDay()))) {
 | 
			
		||||
            nlog("attempting to autobill {$invoice->number}");
 | 
			
		||||
@ -125,10 +127,12 @@ class SendRecurring implements ShouldQueue
 | 
			
		||||
            //04-08-2023 edge case to support where online payment notifications are not enabled
 | 
			
		||||
            if(!$invoice->client->getSetting('client_online_payment_notification')) {
 | 
			
		||||
                $this->sendRecurringEmails($invoice);
 | 
			
		||||
                $invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } elseif ($invoice->client->getSetting('auto_email_invoice')) {
 | 
			
		||||
            $this->sendRecurringEmails($invoice);
 | 
			
		||||
            $invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user