mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-11-04 07:57:33 -05:00 
			
		
		
		
	Merge branch 'v5-develop' of https://github.com/invoiceninja/invoiceninja into feature-inbound-email-expenses
This commit is contained in:
		
						commit
						13dae4f524
					
				
							
								
								
									
										2
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							@ -8,7 +8,7 @@ assignees: ''
 | 
				
			|||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<!-- Before posting please check our "Troubleshooting" category in the docs: 
 | 
					<!-- Before posting please check our "Troubleshooting" category in the docs: 
 | 
				
			||||||
https://invoiceninja.github.io/docs/self-host-troubleshooting/ -->
 | 
					https://invoiceninja.github.io/en/self-host-troubleshooting/ -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Setup
 | 
					## Setup
 | 
				
			||||||
- Version: <!-- i.e. v4.5.25 / v5.0.30 -->
 | 
					- Version: <!-- i.e. v4.5.25 / v5.0.30 -->
 | 
				
			||||||
 | 
				
			|||||||
@ -51,6 +51,8 @@ All Pro and Enterprise features from the hosted app are included in the open-sou
 | 
				
			|||||||
* [Docker File](https://hub.docker.com/r/invoiceninja/invoiceninja/)
 | 
					* [Docker File](https://hub.docker.com/r/invoiceninja/invoiceninja/)
 | 
				
			||||||
* [Cloudron](https://www.cloudron.io/store/com.invoiceninja.cloudronapp2.html)
 | 
					* [Cloudron](https://www.cloudron.io/store/com.invoiceninja.cloudronapp2.html)
 | 
				
			||||||
* [Softaculous](https://www.softaculous.com/apps/ecommerce/Invoice_Ninja)
 | 
					* [Softaculous](https://www.softaculous.com/apps/ecommerce/Invoice_Ninja)
 | 
				
			||||||
 | 
					* [Elestio](https://elest.io/open-source/invoiceninja)
 | 
				
			||||||
 | 
					* [YunoHost](https://apps.yunohost.org/app/invoiceninja5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Recommended Providers
 | 
					### Recommended Providers
 | 
				
			||||||
* [Stripe](https://stripe.com/)
 | 
					* [Stripe](https://stripe.com/)
 | 
				
			||||||
 | 
				
			|||||||
@ -1 +1 @@
 | 
				
			|||||||
5.10.16
 | 
					5.10.26
 | 
				
			||||||
							
								
								
									
										50
									
								
								app/Casts/QuickbooksSettingsCast.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								app/Casts/QuickbooksSettingsCast.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Casts;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\DataMapper\QuickbooksSettings;
 | 
				
			||||||
 | 
					use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class QuickbooksSettingsCast implements CastsAttributes
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function get($model, string $key, $value, array $attributes)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $data = json_decode($value, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(!is_array($data))
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $qb = new QuickbooksSettings();
 | 
				
			||||||
 | 
					        $qb->accessTokenKey =  $data['accessTokenKey'];
 | 
				
			||||||
 | 
					        $qb->refresh_token =  $data['refresh_token'];
 | 
				
			||||||
 | 
					        $qb->realmID =  $data['realmID'];
 | 
				
			||||||
 | 
					        $qb->accessTokenExpiresAt =  $data['accessTokenExpiresAt'];
 | 
				
			||||||
 | 
					        $qb->refreshTokenExpiresAt =  $data['refreshTokenExpiresAt'];
 | 
				
			||||||
 | 
					        $qb->settings = $data['settings'] ?? [];
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return $qb;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function set($model, string $key, $value, array $attributes)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            $key => json_encode([
 | 
				
			||||||
 | 
					                'accessTokenKey' => $value->accessTokenKey,
 | 
				
			||||||
 | 
					                'refresh_token' => $value->refresh_token,
 | 
				
			||||||
 | 
					                'realmID' => $value->realmID,
 | 
				
			||||||
 | 
					                'accessTokenExpiresAt' => $value->accessTokenExpiresAt,
 | 
				
			||||||
 | 
					                'refreshTokenExpiresAt' => $value->refreshTokenExpiresAt,
 | 
				
			||||||
 | 
					                'settings' => $value->settings,
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -15,7 +15,6 @@ use App\Jobs\Cron\AutoBillCron;
 | 
				
			|||||||
use App\Jobs\Cron\RecurringExpensesCron;
 | 
					use App\Jobs\Cron\RecurringExpensesCron;
 | 
				
			||||||
use App\Jobs\Cron\RecurringInvoicesCron;
 | 
					use App\Jobs\Cron\RecurringInvoicesCron;
 | 
				
			||||||
use App\Jobs\Cron\SubscriptionCron;
 | 
					use App\Jobs\Cron\SubscriptionCron;
 | 
				
			||||||
use App\Jobs\Cron\UpdateCalculatedFields;
 | 
					 | 
				
			||||||
use App\Jobs\Invoice\InvoiceCheckLateWebhook;
 | 
					use App\Jobs\Invoice\InvoiceCheckLateWebhook;
 | 
				
			||||||
use App\Jobs\Ninja\AdjustEmailQuota;
 | 
					use App\Jobs\Ninja\AdjustEmailQuota;
 | 
				
			||||||
use App\Jobs\Ninja\BankTransactionSync;
 | 
					use App\Jobs\Ninja\BankTransactionSync;
 | 
				
			||||||
@ -33,6 +32,7 @@ use App\Jobs\Util\SchedulerCheck;
 | 
				
			|||||||
use App\Jobs\Util\UpdateExchangeRates;
 | 
					use App\Jobs\Util\UpdateExchangeRates;
 | 
				
			||||||
use App\Jobs\Util\VersionCheck;
 | 
					use App\Jobs\Util\VersionCheck;
 | 
				
			||||||
use App\Models\Account;
 | 
					use App\Models\Account;
 | 
				
			||||||
 | 
					use App\PaymentDrivers\Rotessa\Jobs\TransactionReport;
 | 
				
			||||||
use App\Utils\Ninja;
 | 
					use App\Utils\Ninja;
 | 
				
			||||||
use Illuminate\Console\Scheduling\Schedule;
 | 
					use Illuminate\Console\Scheduling\Schedule;
 | 
				
			||||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
 | 
					use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
 | 
				
			||||||
@ -65,11 +65,11 @@ class Kernel extends ConsoleKernel
 | 
				
			|||||||
        /* Checks for scheduled tasks */
 | 
					        /* Checks for scheduled tasks */
 | 
				
			||||||
        $schedule->job(new TaskScheduler())->hourlyAt(10)->withoutOverlapping()->name('task-scheduler-job')->onOneServer();
 | 
					        $schedule->job(new TaskScheduler())->hourlyAt(10)->withoutOverlapping()->name('task-scheduler-job')->onOneServer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /* Stale Invoice Cleanup*/
 | 
					        /* Checks Rotessa Transactions */
 | 
				
			||||||
        $schedule->job(new CleanStaleInvoiceOrder())->hourlyAt(30)->withoutOverlapping()->name('stale-invoice-job')->onOneServer();
 | 
					        $schedule->job(new TransactionReport())->dailyAt('01:48')->withoutOverlapping()->name('rotessa-transaction-report')->onOneServer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /* Stale Invoice Cleanup*/
 | 
					        /* Stale Invoice Cleanup*/
 | 
				
			||||||
        $schedule->job(new UpdateCalculatedFields())->hourlyAt(40)->withoutOverlapping()->name('update-calculated-fields-job')->onOneServer();
 | 
					        $schedule->job(new CleanStaleInvoiceOrder())->hourlyAt(30)->withoutOverlapping()->name('stale-invoice-job')->onOneServer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /* Checks for large companies and marked them as is_large */
 | 
					        /* Checks for large companies and marked them as is_large */
 | 
				
			||||||
        $schedule->job(new CompanySizeCheck())->dailyAt('23:20')->withoutOverlapping()->name('company-size-job')->onOneServer();
 | 
					        $schedule->job(new CompanySizeCheck())->dailyAt('23:20')->withoutOverlapping()->name('company-size-job')->onOneServer();
 | 
				
			||||||
 | 
				
			|||||||
@ -516,9 +516,10 @@ class CompanySettings extends BaseSettings
 | 
				
			|||||||
    public $quote_late_fee_amount1 = 0;
 | 
					    public $quote_late_fee_amount1 = 0;
 | 
				
			||||||
    public $quote_late_fee_percent1 = 0;
 | 
					    public $quote_late_fee_percent1 = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public string $payment_flow = 'default'; //smooth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static $casts = [
 | 
					    public static $casts = [
 | 
				
			||||||
 | 
					        'payment_flow'                       => 'string',
 | 
				
			||||||
        'enable_quote_reminder1'             => 'bool',
 | 
					        'enable_quote_reminder1'             => 'bool',
 | 
				
			||||||
        'quote_num_days_reminder1'           => 'int',
 | 
					        'quote_num_days_reminder1'           => 'int',
 | 
				
			||||||
        'quote_schedule_reminder1'           => 'string',
 | 
					        'quote_schedule_reminder1'           => 'string',
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										59
									
								
								app/DataMapper/QuickbooksSettings.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								app/DataMapper/QuickbooksSettings.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\DataMapper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Illuminate\Contracts\Database\Eloquent\Castable;
 | 
				
			||||||
 | 
					use App\Casts\QuickbooksSettingsCast;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * QuickbooksSettings.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class QuickbooksSettings implements Castable
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public string $accessTokenKey;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public string $refresh_token;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public string $realmID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public int $accessTokenExpiresAt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public int $refreshTokenExpiresAt;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /** 
 | 
				
			||||||
 | 
					     * entity client,invoice,quote,purchase_order,vendor,payment
 | 
				
			||||||
 | 
					     * sync true/false
 | 
				
			||||||
 | 
					     * update_record true/false
 | 
				
			||||||
 | 
					     * direction push/pull/birectional
 | 
				
			||||||
 | 
					     * */
 | 
				
			||||||
 | 
					    public array $settings = [
 | 
				
			||||||
 | 
					        'client' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
 | 
				
			||||||
 | 
					        'vendor' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
 | 
				
			||||||
 | 
					        'invoice' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
 | 
				
			||||||
 | 
					        'quote' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
 | 
				
			||||||
 | 
					        'purchase_order' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
 | 
				
			||||||
 | 
					        'product' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
 | 
				
			||||||
 | 
					        'payment' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the name of the caster class to use when casting from / to this cast target.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  array<string, mixed>  $arguments
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static function castUsing(array $arguments): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return QuickbooksSettingsCast::class;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -17,6 +17,7 @@ use App\Models\Invoice;
 | 
				
			|||||||
use App\Models\Product;
 | 
					use App\Models\Product;
 | 
				
			||||||
use App\DataProviders\USStates;
 | 
					use App\DataProviders\USStates;
 | 
				
			||||||
use App\DataMapper\Tax\ZipTax\Response;
 | 
					use App\DataMapper\Tax\ZipTax\Response;
 | 
				
			||||||
 | 
					use App\Models\RecurringInvoice;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BaseRule implements RuleInterface
 | 
					class BaseRule implements RuleInterface
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -132,7 +133,7 @@ class BaseRule implements RuleInterface
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public function shouldCalcTax(): bool
 | 
					    public function shouldCalcTax(): bool
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->should_calc_tax;
 | 
					        return $this->should_calc_tax && $this->checkIfInvoiceLocked();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Initializes the tax rule for the entity.
 | 
					     * Initializes the tax rule for the entity.
 | 
				
			||||||
@ -215,7 +216,7 @@ class BaseRule implements RuleInterface
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            $this->invoice->tax_data = $tax_data;
 | 
					            $this->invoice->tax_data = $tax_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if(\DB::transactionLevel() == 0) {
 | 
					            if(\DB::transactionLevel() == 0 && isset($this->invoice->id)) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                try {
 | 
					                try {
 | 
				
			||||||
                    $this->invoice->saveQuietly();
 | 
					                    $this->invoice->saveQuietly();
 | 
				
			||||||
@ -400,4 +401,40 @@ class BaseRule implements RuleInterface
 | 
				
			|||||||
        return ! in_array($iso_3166_2, array_merge($this->eu_country_codes, array_keys($this->region_codes)));
 | 
					        return ! in_array($iso_3166_2, array_merge($this->eu_country_codes, array_keys($this->region_codes)));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private function checkIfInvoiceLocked(): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $lock_invoices = $this->client->getSetting('lock_invoices');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if($this->invoice instanceof RecurringInvoice) {
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch ($lock_invoices) {
 | 
				
			||||||
 | 
					            case 'off':
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            case 'when_sent':
 | 
				
			||||||
 | 
					                if ($this->invoice->status_id == Invoice::STATUS_SENT) {
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case 'when_paid':
 | 
				
			||||||
 | 
					                if ($this->invoice->status_id == Invoice::STATUS_PAID) {
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                //if now is greater than the end of month the invoice was dated - do not modify
 | 
				
			||||||
 | 
					            case 'end_of_month':
 | 
				
			||||||
 | 
					                if(\Carbon\Carbon::parse($this->invoice->date)->setTimezone($this->invoice->company->timezone()->name)->endOfMonth()->lte(now())) {
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -43,6 +43,8 @@ class Rule extends BaseRule implements RuleInterface
 | 
				
			|||||||
    public float $reduced_tax_rate = 0;
 | 
					    public float $reduced_tax_rate = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public string $tax_name1 = 'MwSt.';
 | 
					    public string $tax_name1 = 'MwSt.';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private string $tax_name;
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Initializes the rules and builds any required data.
 | 
					     * Initializes the rules and builds any required data.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -50,6 +52,7 @@ class Rule extends BaseRule implements RuleInterface
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public function init(): self
 | 
					    public function init(): self
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        $this->tax_name = $this->tax_name1;
 | 
				
			||||||
        $this->calculateRates();
 | 
					        $this->calculateRates();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this;
 | 
					        return $this;
 | 
				
			||||||
@ -91,6 +94,7 @@ class Rule extends BaseRule implements RuleInterface
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public function reverseTax($item): self
 | 
					    public function reverseTax($item): self
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        $this->tax_name1 = $this->tax_name;
 | 
				
			||||||
        $this->tax_rate1 = 0;
 | 
					        $this->tax_rate1 = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this;
 | 
					        return $this;
 | 
				
			||||||
@ -103,6 +107,8 @@ class Rule extends BaseRule implements RuleInterface
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public function taxReduced($item): self
 | 
					    public function taxReduced($item): self
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->tax_name1 = $this->tax_name;
 | 
				
			||||||
        $this->tax_rate1 = $this->reduced_tax_rate;
 | 
					        $this->tax_rate1 = $this->reduced_tax_rate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this;
 | 
					        return $this;
 | 
				
			||||||
@ -115,6 +121,8 @@ class Rule extends BaseRule implements RuleInterface
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public function zeroRated($item): self
 | 
					    public function zeroRated($item): self
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->tax_name1 = $this->tax_name;
 | 
				
			||||||
        $this->tax_rate1 = 0;
 | 
					        $this->tax_rate1 = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this;
 | 
					        return $this;
 | 
				
			||||||
@ -142,6 +150,7 @@ class Rule extends BaseRule implements RuleInterface
 | 
				
			|||||||
    public function taxDigital($item): self
 | 
					    public function taxDigital($item): self
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->tax_name1 = $this->tax_name;
 | 
				
			||||||
        $this->tax_rate1 = $this->tax_rate;
 | 
					        $this->tax_rate1 = $this->tax_rate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this;
 | 
					        return $this;
 | 
				
			||||||
@ -155,6 +164,7 @@ class Rule extends BaseRule implements RuleInterface
 | 
				
			|||||||
    public function taxService($item): self
 | 
					    public function taxService($item): self
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->tax_name1 = $this->tax_name;
 | 
				
			||||||
        $this->tax_rate1 = $this->tax_rate;
 | 
					        $this->tax_rate1 = $this->tax_rate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this;
 | 
					        return $this;
 | 
				
			||||||
@ -168,6 +178,7 @@ class Rule extends BaseRule implements RuleInterface
 | 
				
			|||||||
    public function taxShipping($item): self
 | 
					    public function taxShipping($item): self
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->tax_name1 = $this->tax_name;
 | 
				
			||||||
        $this->tax_rate1 = $this->tax_rate;
 | 
					        $this->tax_rate1 = $this->tax_rate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this;
 | 
					        return $this;
 | 
				
			||||||
@ -181,6 +192,7 @@ class Rule extends BaseRule implements RuleInterface
 | 
				
			|||||||
    public function taxPhysical($item): self
 | 
					    public function taxPhysical($item): self
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->tax_name1 = $this->tax_name;
 | 
				
			||||||
        $this->tax_rate1 = $this->tax_rate;
 | 
					        $this->tax_rate1 = $this->tax_rate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this;
 | 
					        return $this;
 | 
				
			||||||
@ -229,8 +241,7 @@ class Rule extends BaseRule implements RuleInterface
 | 
				
			|||||||
            // nlog("tax exempt");
 | 
					            // nlog("tax exempt");
 | 
				
			||||||
            $this->tax_rate = 0;
 | 
					            $this->tax_rate = 0;
 | 
				
			||||||
            $this->reduced_tax_rate = 0;
 | 
					            $this->reduced_tax_rate = 0;
 | 
				
			||||||
        } elseif($this->client_subregion != $this->client->company->tax_data->seller_subregion && in_array($this->client_subregion, $this->eu_country_codes) && $this->client->vat_number && $this->eu_business_tax_exempt) {
 | 
					        } elseif($this->client_subregion != $this->client->company->tax_data->seller_subregion && in_array($this->client_subregion, $this->eu_country_codes) && $this->client->vat_number && $this->client->has_valid_vat_number && $this->eu_business_tax_exempt) {
 | 
				
			||||||
            // elseif($this->client_subregion != $this->client->company->tax_data->seller_subregion && in_array($this->client_subregion, $this->eu_country_codes) && $this->client->has_valid_vat_number && $this->eu_business_tax_exempt)
 | 
					 | 
				
			||||||
            // nlog("euro zone and tax exempt");
 | 
					            // nlog("euro zone and tax exempt");
 | 
				
			||||||
            $this->tax_rate = 0;
 | 
					            $this->tax_rate = 0;
 | 
				
			||||||
            $this->reduced_tax_rate = 0;
 | 
					            $this->reduced_tax_rate = 0;
 | 
				
			||||||
@ -240,8 +251,8 @@ class Rule extends BaseRule implements RuleInterface
 | 
				
			|||||||
            $this->reduced_tax_rate = 0;
 | 
					            $this->reduced_tax_rate = 0;
 | 
				
			||||||
        } elseif(!in_array($this->client_subregion, $this->eu_country_codes)) {
 | 
					        } elseif(!in_array($this->client_subregion, $this->eu_country_codes)) {
 | 
				
			||||||
            $this->defaultForeign();
 | 
					            $this->defaultForeign();
 | 
				
			||||||
        } elseif(in_array($this->client_subregion, $this->eu_country_codes) && !$this->client->vat_number) { //eu country / no valid vat
 | 
					        } elseif(in_array($this->client_subregion, $this->eu_country_codes) && ((strlen($this->client->vat_number ?? '') == 1) || !$this->client->has_valid_vat_number)) { //eu country / no valid vat
 | 
				
			||||||
            if(($this->client->company->tax_data->seller_subregion != $this->client_subregion) && $this->client->company->tax_data->regions->EU->has_sales_above_threshold) {
 | 
					            if($this->client->company->tax_data->seller_subregion != $this->client_subregion) {
 | 
				
			||||||
                // nlog("eu zone with sales above threshold");
 | 
					                // nlog("eu zone with sales above threshold");
 | 
				
			||||||
                $this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->tax_rate ?? 0;
 | 
					                $this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->tax_rate ?? 0;
 | 
				
			||||||
                $this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->reduced_tax_rate ?? 0;
 | 
					                $this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->reduced_tax_rate ?? 0;
 | 
				
			||||||
 | 
				
			|||||||
@ -48,8 +48,7 @@ class TaxModel
 | 
				
			|||||||
    public function migrate(): self
 | 
					    public function migrate(): self
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if($this->version == 'alpha')
 | 
					        if($this->version == 'alpha') {
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            $this->regions->EU->subregions->PL = new \stdClass();
 | 
					            $this->regions->EU->subregions->PL = new \stdClass();
 | 
				
			||||||
            $this->regions->EU->subregions->PL->tax_rate = 23;
 | 
					            $this->regions->EU->subregions->PL->tax_rate = 23;
 | 
				
			||||||
            $this->regions->EU->subregions->PL->tax_name = 'VAT';
 | 
					            $this->regions->EU->subregions->PL->tax_name = 'VAT';
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										68
									
								
								app/DataProviders/CAProvinces.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								app/DataProviders/CAProvinces.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\DataProviders;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class CAProvinces
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The provinces and territories of Canada
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @var array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected static $provinces = [
 | 
				
			||||||
 | 
					        'AB' => 'Alberta',
 | 
				
			||||||
 | 
					        'BC' => 'British Columbia',
 | 
				
			||||||
 | 
					        'MB' => 'Manitoba',
 | 
				
			||||||
 | 
					        'NB' => 'New Brunswick',
 | 
				
			||||||
 | 
					        'NL' => 'Newfoundland And Labrador',
 | 
				
			||||||
 | 
					        'NS' => 'Nova Scotia',
 | 
				
			||||||
 | 
					        'ON' => 'Ontario',
 | 
				
			||||||
 | 
					        'PE' => 'Prince Edward Island',
 | 
				
			||||||
 | 
					        'QC' => 'Quebec',
 | 
				
			||||||
 | 
					        'SK' => 'Saskatchewan',
 | 
				
			||||||
 | 
					        'NT' => 'Northwest Territories',
 | 
				
			||||||
 | 
					        'NU' => 'Nunavut',
 | 
				
			||||||
 | 
					        'YT' => 'Yukon'
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the name of the province or territory for a given abbreviation.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  string  $abbreviation
 | 
				
			||||||
 | 
					     * @return string
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static function getName($abbreviation)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return self::$provinces[$abbreviation];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get all provinces and territories.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static function get()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return self::$provinces;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the abbreviation for a given province or territory name.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  string  $name
 | 
				
			||||||
 | 
					     * @return string
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static function getAbbreviation($name)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return array_search(ucwords($name), self::$provinces);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -451,6 +451,7 @@ class BaseExport
 | 
				
			|||||||
        'project' => 'task.project_id',
 | 
					        'project' => 'task.project_id',
 | 
				
			||||||
        'billable' => 'task.billable',
 | 
					        'billable' => 'task.billable',
 | 
				
			||||||
        'item_notes' => 'task.item_notes',
 | 
					        'item_notes' => 'task.item_notes',
 | 
				
			||||||
 | 
					        'time_log' => 'task.time_log',
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected array $forced_client_fields = [
 | 
					    protected array $forced_client_fields = [
 | 
				
			||||||
@ -1258,7 +1259,7 @@ class BaseExport
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        $date_range = $this->input['date_range'];
 | 
					        $date_range = $this->input['date_range'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (array_key_exists('date_key', $this->input) && strlen($this->input['date_key']) > 1 && ($table_name && $this->columnExists($table_name, $this->input['date_key']))) {
 | 
					        if (array_key_exists('date_key', $this->input) && strlen($this->input['date_key'] ?? '') > 1 && ($table_name && $this->columnExists($table_name, $this->input['date_key']))) {
 | 
				
			||||||
            $this->date_key = $this->input['date_key'];
 | 
					            $this->date_key = $this->input['date_key'];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -269,8 +269,7 @@ class ExpenseExport extends BaseExport
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if($expense->uses_inclusive_taxes) {
 | 
					            if($expense->uses_inclusive_taxes) {
 | 
				
			||||||
                $entity['expense.net_amount'] = round($expense->amount, $precision) - $total_tax_amount;
 | 
					                $entity['expense.net_amount'] = round($expense->amount, $precision) - $total_tax_amount;
 | 
				
			||||||
            }
 | 
					            } else {
 | 
				
			||||||
            else {
 | 
					 | 
				
			||||||
                $entity['expense.net_amount'] = round($expense->amount, $precision);
 | 
					                $entity['expense.net_amount'] = round($expense->amount, $precision);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -229,10 +229,6 @@ class InvoiceItemExport extends BaseExport
 | 
				
			|||||||
        //     $entity['currency'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code;
 | 
					        //     $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('tax_category', $entity)) {
 | 
					        // if(array_key_exists('tax_category', $entity)) {
 | 
				
			||||||
        //     $entity['tax_category'] = $invoice->taxTypeString($entity['tax_category']);
 | 
					        //     $entity['tax_category'] = $invoice->taxTypeString($entity['tax_category']);
 | 
				
			||||||
        // }
 | 
					        // }
 | 
				
			||||||
 | 
				
			|||||||
@ -213,10 +213,6 @@ class PurchaseOrderItemExport extends BaseExport
 | 
				
			|||||||
        //     $entity['currency'] = $purchase_order->vendor->currency() ? $purchase_order->vendor->currency()->code : $purchase_order->company->currency()->code;
 | 
					        //     $entity['currency'] = $purchase_order->vendor->currency() ? $purchase_order->vendor->currency()->code : $purchase_order->company->currency()->code;
 | 
				
			||||||
        // }
 | 
					        // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // if(array_key_exists('type', $entity)) {
 | 
					 | 
				
			||||||
        //     $entity['type'] = $purchase_order->typeIdString($entity['type']);
 | 
					 | 
				
			||||||
        // }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // if(array_key_exists('tax_category', $entity)) {
 | 
					        // if(array_key_exists('tax_category', $entity)) {
 | 
				
			||||||
        //     $entity['tax_category'] = $purchase_order->taxTypeString($entity['tax_category']);
 | 
					        //     $entity['tax_category'] = $purchase_order->taxTypeString($entity['tax_category']);
 | 
				
			||||||
        // }
 | 
					        // }
 | 
				
			||||||
 | 
				
			|||||||
@ -29,7 +29,7 @@ class TaskExport extends BaseExport
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    private $entity_transformer;
 | 
					    private $entity_transformer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public string $date_key = 'created_at';
 | 
					    public string $date_key = 'calculated_start_date';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private string $date_format = 'Y-m-d';
 | 
					    private string $date_format = 'Y-m-d';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -156,7 +156,7 @@ class TaskExport extends BaseExport
 | 
				
			|||||||
                $entity[$key] = $transformed_entity[$parts[1]];
 | 
					                $entity[$key] = $transformed_entity[$parts[1]];
 | 
				
			||||||
            } elseif (array_key_exists($key, $transformed_entity)) {
 | 
					            } elseif (array_key_exists($key, $transformed_entity)) {
 | 
				
			||||||
                $entity[$key] = $transformed_entity[$key];
 | 
					                $entity[$key] = $transformed_entity[$key];
 | 
				
			||||||
            } elseif (in_array($key, ['task.start_date', 'task.end_date', 'task.duration', 'task.billable', 'task.item_notes'])) {
 | 
					            } elseif (in_array($key, ['task.start_date', 'task.end_date', 'task.duration', 'task.billable', 'task.item_notes', 'task.time_log'])) {
 | 
				
			||||||
                $entity[$key] = '';
 | 
					                $entity[$key] = '';
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                $entity[$key] = $this->decorator->transform($key, $task);
 | 
					                $entity[$key] = $this->decorator->transform($key, $task);
 | 
				
			||||||
@ -207,6 +207,9 @@ class TaskExport extends BaseExport
 | 
				
			|||||||
                $seconds = $task->calcDuration();
 | 
					                $seconds = $task->calcDuration();
 | 
				
			||||||
                $entity['task.duration'] = $seconds;
 | 
					                $entity['task.duration'] = $seconds;
 | 
				
			||||||
                $entity['task.duration_words'] =  $seconds > 86400 ? CarbonInterval::seconds($seconds)->locale($this->company->locale())->cascade()->forHumans() : now()->startOfDay()->addSeconds($seconds)->format('H:i:s');
 | 
					                $entity['task.duration_words'] =  $seconds > 86400 ? CarbonInterval::seconds($seconds)->locale($this->company->locale())->cascade()->forHumans() : now()->startOfDay()->addSeconds($seconds)->format('H:i:s');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                $entity['task.time_log'] = (isset($item[1]) && $item[1] != 0) ? $item[1] - $item[0] : ctrans('texts.is_running');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (in_array('task.billable', $this->input['report_keys']) || in_array('billable', $this->input['report_keys'])) {
 | 
					            if (in_array('task.billable', $this->input['report_keys']) || in_array('billable', $this->input['report_keys'])) {
 | 
				
			||||||
 | 
				
			|||||||
@ -98,7 +98,14 @@ class CreditFilters extends QueryFilters
 | 
				
			|||||||
                                ->orWhere('last_name', 'like', '%'.$filter.'%')
 | 
					                                ->orWhere('last_name', 'like', '%'.$filter.'%')
 | 
				
			||||||
                                ->orWhere('email', 'like', '%'.$filter.'%');
 | 
					                                ->orWhere('email', 'like', '%'.$filter.'%');
 | 
				
			||||||
                          })
 | 
					                          })
 | 
				
			||||||
                          ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
 | 
					                                                    ->orWhereRaw("
 | 
				
			||||||
 | 
					                            JSON_UNQUOTE(JSON_EXTRACT(
 | 
				
			||||||
 | 
					                                JSON_ARRAY(
 | 
				
			||||||
 | 
					                                    JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')), 
 | 
				
			||||||
 | 
					                                    JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].product_key'))
 | 
				
			||||||
 | 
					                                ), '$[*]')
 | 
				
			||||||
 | 
					                            ) LIKE ?", ['%'.$filter.'%']);
 | 
				
			||||||
 | 
					            //   ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -158,6 +158,19 @@ class ExpenseFilters extends QueryFilters
 | 
				
			|||||||
        return $this->builder;
 | 
					        return $this->builder;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function categories(string $categories = ''): Builder
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $categories_exploded = explode(",", $categories);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(empty($categories) || count(array_filter($categories_exploded)) == 0) {
 | 
				
			||||||
 | 
					            return $this->builder;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $categories_keys = $this->transformKeys($categories_exploded);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $this->builder->whereIn('category_id', $categories_keys);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function number(string $number = ''): Builder
 | 
					    public function number(string $number = ''): Builder
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (strlen($number) == 0) {
 | 
					        if (strlen($number) == 0) {
 | 
				
			||||||
@ -205,6 +218,11 @@ class ExpenseFilters extends QueryFilters
 | 
				
			|||||||
                    ->whereColumn('expense_categories.id', 'expenses.category_id'), $sort_col[1]);
 | 
					                    ->whereColumn('expense_categories.id', 'expenses.category_id'), $sort_col[1]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($sort_col[0] == 'payment_date' && in_array($sort_col[1], ['asc', 'desc'])) {
 | 
				
			||||||
 | 
					            return $this->builder
 | 
				
			||||||
 | 
					                    ->orderByRaw('ISNULL(payment_date), payment_date '. $sort_col[1]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if($sort_col[0] == 'number') {
 | 
					        if($sort_col[0] == 'number') {
 | 
				
			||||||
            return $this->builder->orderByRaw("REGEXP_REPLACE(number,'[^0-9]+','')+0 " . $dir);
 | 
					            return $this->builder->orderByRaw("REGEXP_REPLACE(number,'[^0-9]+','')+0 " . $dir);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -125,7 +125,14 @@ class InvoiceFilters extends QueryFilters
 | 
				
			|||||||
                                ->orWhere('last_name', 'like', '%'.$filter.'%')
 | 
					                                ->orWhere('last_name', 'like', '%'.$filter.'%')
 | 
				
			||||||
                                ->orWhere('email', 'like', '%'.$filter.'%');
 | 
					                                ->orWhere('email', 'like', '%'.$filter.'%');
 | 
				
			||||||
                          })
 | 
					                          })
 | 
				
			||||||
                          ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
 | 
					                          ->orWhereRaw("
 | 
				
			||||||
 | 
					                            JSON_UNQUOTE(JSON_EXTRACT(
 | 
				
			||||||
 | 
					                                JSON_ARRAY(
 | 
				
			||||||
 | 
					                                    JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')), 
 | 
				
			||||||
 | 
					                                    JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].product_key'))
 | 
				
			||||||
 | 
					                                ), '$[*]')
 | 
				
			||||||
 | 
					                            ) LIKE ?", ['%'.$filter.'%']);
 | 
				
			||||||
 | 
					            //   ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -60,7 +60,7 @@ class ProjectFilters extends QueryFilters
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        $sort_col = explode('|', $sort);
 | 
					        $sort_col = explode('|', $sort);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!is_array($sort_col) || count($sort_col) != 2) {
 | 
					        if (!is_array($sort_col) || count($sort_col) != 2 || !in_array($sort_col[0], \Illuminate\Support\Facades\Schema::getColumnListing('projects'))) {
 | 
				
			||||||
            return $this->builder;
 | 
					            return $this->builder;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -96,7 +96,14 @@ class PurchaseOrderFilters extends QueryFilters
 | 
				
			|||||||
                ->orWhere('custom_value4', 'like', '%'.$filter.'%')
 | 
					                ->orWhere('custom_value4', 'like', '%'.$filter.'%')
 | 
				
			||||||
                ->orWhereHas('vendor', function ($q) use ($filter) {
 | 
					                ->orWhereHas('vendor', function ($q) use ($filter) {
 | 
				
			||||||
                    $q->where('name', 'like', '%'.$filter.'%');
 | 
					                    $q->where('name', 'like', '%'.$filter.'%');
 | 
				
			||||||
                });
 | 
					                })
 | 
				
			||||||
 | 
					                ->orWhereRaw("
 | 
				
			||||||
 | 
					                JSON_UNQUOTE(JSON_EXTRACT(
 | 
				
			||||||
 | 
					                    JSON_ARRAY(
 | 
				
			||||||
 | 
					                        JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')), 
 | 
				
			||||||
 | 
					                        JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].product_key'))
 | 
				
			||||||
 | 
					                    ), '$[*]')
 | 
				
			||||||
 | 
					                ) LIKE ?", ['%'.$filter.'%']);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -46,7 +46,14 @@ class QuoteFilters extends QueryFilters
 | 
				
			|||||||
                        ->orWhere('last_name', 'like', '%'.$filter.'%')
 | 
					                        ->orWhere('last_name', 'like', '%'.$filter.'%')
 | 
				
			||||||
                        ->orWhere('email', 'like', '%'.$filter.'%');
 | 
					                        ->orWhere('email', 'like', '%'.$filter.'%');
 | 
				
			||||||
                  })
 | 
					                  })
 | 
				
			||||||
                  ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
 | 
					                                            ->orWhereRaw("
 | 
				
			||||||
 | 
					                            JSON_UNQUOTE(JSON_EXTRACT(
 | 
				
			||||||
 | 
					                                JSON_ARRAY(
 | 
				
			||||||
 | 
					                                    JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')), 
 | 
				
			||||||
 | 
					                                    JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].product_key'))
 | 
				
			||||||
 | 
					                                ), '$[*]')
 | 
				
			||||||
 | 
					                            ) LIKE ?", ['%'.$filter.'%']);
 | 
				
			||||||
 | 
					            //   ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -49,7 +49,14 @@ class RecurringInvoiceFilters extends QueryFilters
 | 
				
			|||||||
                        ->orWhere('last_name', 'like', '%'.$filter.'%')
 | 
					                        ->orWhere('last_name', 'like', '%'.$filter.'%')
 | 
				
			||||||
                        ->orWhere('email', 'like', '%'.$filter.'%');
 | 
					                        ->orWhere('email', 'like', '%'.$filter.'%');
 | 
				
			||||||
                  })
 | 
					                  })
 | 
				
			||||||
                  ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
 | 
					                    ->orWhereRaw("
 | 
				
			||||||
 | 
					                            JSON_UNQUOTE(JSON_EXTRACT(
 | 
				
			||||||
 | 
					                                JSON_ARRAY(
 | 
				
			||||||
 | 
					                                    JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')), 
 | 
				
			||||||
 | 
					                                    JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].product_key'))
 | 
				
			||||||
 | 
					                                ), '$[*]')
 | 
				
			||||||
 | 
					                            ) LIKE ?", ['%'.$filter.'%']);
 | 
				
			||||||
 | 
					            //->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -85,6 +85,10 @@ class TaskFilters extends QueryFilters
 | 
				
			|||||||
            $this->builder->whereNull('invoice_id');
 | 
					            $this->builder->whereNull('invoice_id');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (in_array('is_running', $status_parameters)) {
 | 
				
			||||||
 | 
					            $this->builder->where('is_running', true);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this->builder;
 | 
					        return $this->builder;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -99,7 +99,7 @@ class TransactionTransformer implements BankRevenueInterface
 | 
				
			|||||||
        } elseif (array_key_exists('internalTransactionId', $transaction)) {
 | 
					        } elseif (array_key_exists('internalTransactionId', $transaction)) {
 | 
				
			||||||
            $transactionId = $transaction["internalTransactionId"];
 | 
					            $transactionId = $transaction["internalTransactionId"];
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            nlog(`Invalid Input for nordigen transaction transformer: ` . $transaction);
 | 
					            nlog('Invalid Input for nordigen transaction transformer: ' . $transaction);
 | 
				
			||||||
            throw new \Exception('invalid dataset: missing transactionId - Please report this error to the developer');
 | 
					            throw new \Exception('invalid dataset: missing transactionId - Please report this error to the developer');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,7 @@ use App\Models\Quote;
 | 
				
			|||||||
use App\Utils\Number;
 | 
					use App\Utils\Number;
 | 
				
			||||||
use App\Models\Client;
 | 
					use App\Models\Client;
 | 
				
			||||||
use App\Models\Credit;
 | 
					use App\Models\Credit;
 | 
				
			||||||
 | 
					use App\Models\Vendor;
 | 
				
			||||||
use App\Models\Invoice;
 | 
					use App\Models\Invoice;
 | 
				
			||||||
use App\Models\PurchaseOrder;
 | 
					use App\Models\PurchaseOrder;
 | 
				
			||||||
use App\Models\RecurringQuote;
 | 
					use App\Models\RecurringQuote;
 | 
				
			||||||
@ -121,7 +122,7 @@ class InvoiceItemSum
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private $tax_collection;
 | 
					    private $tax_collection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private ?Client $client;
 | 
					    private Client | Vendor $client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private bool $calc_tax = false;
 | 
					    private bool $calc_tax = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -132,10 +133,10 @@ class InvoiceItemSum
 | 
				
			|||||||
        $this->tax_collection = collect([]);
 | 
					        $this->tax_collection = collect([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->invoice = $invoice;
 | 
					        $this->invoice = $invoice;
 | 
				
			||||||
 | 
					        $this->client = $invoice->client ?? $invoice->vendor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ($this->invoice->client) {
 | 
					        if ($this->invoice->client) {
 | 
				
			||||||
            $this->currency = $this->invoice->client->currency();
 | 
					            $this->currency = $this->invoice->client->currency();
 | 
				
			||||||
            $this->client = $this->invoice->client;
 | 
					 | 
				
			||||||
            $this->shouldCalculateTax();
 | 
					            $this->shouldCalculateTax();
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            $this->currency = $this->invoice->vendor->currency();
 | 
					            $this->currency = $this->invoice->vendor->currency();
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,7 @@ use App\Models\Quote;
 | 
				
			|||||||
use App\Utils\Number;
 | 
					use App\Utils\Number;
 | 
				
			||||||
use App\Models\Client;
 | 
					use App\Models\Client;
 | 
				
			||||||
use App\Models\Credit;
 | 
					use App\Models\Credit;
 | 
				
			||||||
 | 
					use App\Models\Vendor;
 | 
				
			||||||
use App\Models\Invoice;
 | 
					use App\Models\Invoice;
 | 
				
			||||||
use App\Models\PurchaseOrder;
 | 
					use App\Models\PurchaseOrder;
 | 
				
			||||||
use App\Models\RecurringQuote;
 | 
					use App\Models\RecurringQuote;
 | 
				
			||||||
@ -110,7 +111,7 @@ class InvoiceItemSumInclusive
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private bool $calc_tax = false;
 | 
					    private bool $calc_tax = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private ?Client $client;
 | 
					    private Client | Vendor $client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private RuleInterface $rule;
 | 
					    private RuleInterface $rule;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -119,10 +120,10 @@ class InvoiceItemSumInclusive
 | 
				
			|||||||
        $this->tax_collection = collect([]);
 | 
					        $this->tax_collection = collect([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->invoice = $invoice;
 | 
					        $this->invoice = $invoice;
 | 
				
			||||||
 | 
					        $this->client = $invoice->client ?? $invoice->vendor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ($this->invoice->client) {
 | 
					        if ($this->invoice->client) {
 | 
				
			||||||
            $this->currency = $this->invoice->client->currency();
 | 
					            $this->currency = $this->invoice->client->currency();
 | 
				
			||||||
            $this->client = $this->invoice->client;
 | 
					 | 
				
			||||||
            $this->shouldCalculateTax();
 | 
					            $this->shouldCalculateTax();
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            $this->currency = $this->invoice->vendor->currency();
 | 
					            $this->currency = $this->invoice->vendor->currency();
 | 
				
			||||||
 | 
				
			|||||||
@ -11,12 +11,14 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Helpers\Invoice;
 | 
					namespace App\Helpers\Invoice;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Models\Client;
 | 
				
			||||||
use App\Models\Credit;
 | 
					use App\Models\Credit;
 | 
				
			||||||
use App\Models\Invoice;
 | 
					use App\Models\Invoice;
 | 
				
			||||||
use App\Models\PurchaseOrder;
 | 
					use App\Models\PurchaseOrder;
 | 
				
			||||||
use App\Models\Quote;
 | 
					use App\Models\Quote;
 | 
				
			||||||
use App\Models\RecurringInvoice;
 | 
					use App\Models\RecurringInvoice;
 | 
				
			||||||
use App\Models\RecurringQuote;
 | 
					use App\Models\RecurringQuote;
 | 
				
			||||||
 | 
					use App\Models\Vendor;
 | 
				
			||||||
use App\Utils\Number;
 | 
					use App\Utils\Number;
 | 
				
			||||||
use App\Utils\Traits\NumberFormatter;
 | 
					use App\Utils\Traits\NumberFormatter;
 | 
				
			||||||
use Illuminate\Support\Collection;
 | 
					use Illuminate\Support\Collection;
 | 
				
			||||||
@ -50,6 +52,8 @@ class InvoiceSum
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private $precision;
 | 
					    private $precision;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private Client | Vendor $client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public InvoiceItemSum $invoice_items;
 | 
					    public InvoiceItemSum $invoice_items;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private $rappen_rounding = false;
 | 
					    private $rappen_rounding = false;
 | 
				
			||||||
@ -60,18 +64,15 @@ class InvoiceSum
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public function __construct($invoice)
 | 
					    public function __construct($invoice)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->invoice = $invoice;
 | 
					        $this->invoice = $invoice;
 | 
				
			||||||
 | 
					        $this->client = $invoice->client ?? $invoice->vendor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ($this->invoice->client) {
 | 
					        $this->precision = $this->client->currency()->precision;
 | 
				
			||||||
            $this->precision = $this->invoice->client->currency()->precision;
 | 
					        $this->rappen_rounding = $this->client->getSetting('enable_rappen_rounding');
 | 
				
			||||||
            $this->rappen_rounding = $this->invoice->client->getSetting('enable_rappen_rounding');
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            $this->precision = $this->invoice->vendor->currency()->precision;
 | 
					 | 
				
			||||||
            $this->rappen_rounding = $this->invoice->vendor->getSetting('enable_rappen_rounding');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->tax_map = new Collection();
 | 
					        $this->tax_map = new Collection();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function build()
 | 
					    public function build()
 | 
				
			||||||
@ -131,7 +132,7 @@ class InvoiceSum
 | 
				
			|||||||
            $tax += $this->getSurchargeTaxTotalForKey($this->invoice->tax_name1, $this->invoice->tax_rate1);
 | 
					            $tax += $this->getSurchargeTaxTotalForKey($this->invoice->tax_name1, $this->invoice->tax_rate1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            $this->total_taxes += $tax;
 | 
					            $this->total_taxes += $tax;
 | 
				
			||||||
            $this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate1), $this->invoice->client).'%', 'total' => $tax];
 | 
					            $this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate1), $this->client).'%', 'total' => $tax];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (is_string($this->invoice->tax_name2) && strlen($this->invoice->tax_name2) >= 2) {
 | 
					        if (is_string($this->invoice->tax_name2) && strlen($this->invoice->tax_name2) >= 2) {
 | 
				
			||||||
@ -139,7 +140,7 @@ class InvoiceSum
 | 
				
			|||||||
            $tax += $this->getSurchargeTaxTotalForKey($this->invoice->tax_name2, $this->invoice->tax_rate2);
 | 
					            $tax += $this->getSurchargeTaxTotalForKey($this->invoice->tax_name2, $this->invoice->tax_rate2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            $this->total_taxes += $tax;
 | 
					            $this->total_taxes += $tax;
 | 
				
			||||||
            $this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate2), $this->invoice->client).'%', 'total' => $tax];
 | 
					            $this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate2), $this->client).'%', 'total' => $tax];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (is_string($this->invoice->tax_name3) && strlen($this->invoice->tax_name3) >= 2) {
 | 
					        if (is_string($this->invoice->tax_name3) && strlen($this->invoice->tax_name3) >= 2) {
 | 
				
			||||||
@ -147,7 +148,7 @@ class InvoiceSum
 | 
				
			|||||||
            $tax += $this->getSurchargeTaxTotalForKey($this->invoice->tax_name3, $this->invoice->tax_rate3);
 | 
					            $tax += $this->getSurchargeTaxTotalForKey($this->invoice->tax_name3, $this->invoice->tax_rate3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            $this->total_taxes += $tax;
 | 
					            $this->total_taxes += $tax;
 | 
				
			||||||
            $this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate3), $this->invoice->client).'%', 'total' => $tax];
 | 
					            $this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate3), $this->client).'%', 'total' => $tax];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this;
 | 
					        return $this;
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,9 @@ namespace App\Helpers\Invoice;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use App\Models\Quote;
 | 
					use App\Models\Quote;
 | 
				
			||||||
use App\Utils\Number;
 | 
					use App\Utils\Number;
 | 
				
			||||||
 | 
					use App\Models\Client;
 | 
				
			||||||
use App\Models\Credit;
 | 
					use App\Models\Credit;
 | 
				
			||||||
 | 
					use App\Models\Vendor;
 | 
				
			||||||
use App\Models\Invoice;
 | 
					use App\Models\Invoice;
 | 
				
			||||||
use App\Models\PurchaseOrder;
 | 
					use App\Models\PurchaseOrder;
 | 
				
			||||||
use App\Models\RecurringQuote;
 | 
					use App\Models\RecurringQuote;
 | 
				
			||||||
@ -50,6 +52,8 @@ class InvoiceSumInclusive
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private $rappen_rounding = false;
 | 
					    private $rappen_rounding = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private Client | Vendor $client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public InvoiceItemSumInclusive $invoice_items;
 | 
					    public InvoiceItemSumInclusive $invoice_items;
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Constructs the object with Invoice and Settings object.
 | 
					     * Constructs the object with Invoice and Settings object.
 | 
				
			||||||
@ -59,14 +63,10 @@ class InvoiceSumInclusive
 | 
				
			|||||||
    public function __construct($invoice)
 | 
					    public function __construct($invoice)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->invoice = $invoice;
 | 
					        $this->invoice = $invoice;
 | 
				
			||||||
 | 
					        $this->client = $invoice->client ?? $invoice->vendor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ($this->invoice->client) {
 | 
					        $this->precision = $this->client->currency()->precision;
 | 
				
			||||||
            $this->precision = $this->invoice->client->currency()->precision;
 | 
					        $this->rappen_rounding = $this->client->getSetting('enable_rappen_rounding');
 | 
				
			||||||
            $this->rappen_rounding = $this->invoice->client->getSetting('enable_rappen_rounding');
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            $this->precision = $this->invoice->vendor->currency()->precision;
 | 
					 | 
				
			||||||
            $this->rappen_rounding = $this->invoice->vendor->getSetting('enable_rappen_rounding');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->tax_map = new Collection();
 | 
					        $this->tax_map = new Collection();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -158,19 +158,19 @@ class InvoiceSumInclusive
 | 
				
			|||||||
            $tax = $this->calcInclusiveLineTax($this->invoice->tax_rate1, $amount);
 | 
					            $tax = $this->calcInclusiveLineTax($this->invoice->tax_rate1, $amount);
 | 
				
			||||||
            $this->total_taxes += $tax;
 | 
					            $this->total_taxes += $tax;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            $this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate1), $this->invoice->client).'%', 'total' => $tax];
 | 
					            $this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate1), $this->client).'%', 'total' => $tax];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (is_string($this->invoice->tax_name2) && strlen($this->invoice->tax_name2) > 1) {
 | 
					        if (is_string($this->invoice->tax_name2) && strlen($this->invoice->tax_name2) > 1) {
 | 
				
			||||||
            $tax = $this->calcInclusiveLineTax($this->invoice->tax_rate2, $amount);
 | 
					            $tax = $this->calcInclusiveLineTax($this->invoice->tax_rate2, $amount);
 | 
				
			||||||
            $this->total_taxes += $tax;
 | 
					            $this->total_taxes += $tax;
 | 
				
			||||||
            $this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate2), $this->invoice->client).'%', 'total' => $tax];
 | 
					            $this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate2), $this->client).'%', 'total' => $tax];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (is_string($this->invoice->tax_name3) && strlen($this->invoice->tax_name3) > 1) {
 | 
					        if (is_string($this->invoice->tax_name3) && strlen($this->invoice->tax_name3) > 1) {
 | 
				
			||||||
            $tax = $this->calcInclusiveLineTax($this->invoice->tax_rate3, $amount);
 | 
					            $tax = $this->calcInclusiveLineTax($this->invoice->tax_rate3, $amount);
 | 
				
			||||||
            $this->total_taxes += $tax;
 | 
					            $this->total_taxes += $tax;
 | 
				
			||||||
            $this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate3), $this->invoice->client).'%', 'total' => $tax];
 | 
					            $this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate3), $this->client).'%', 'total' => $tax];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this;
 | 
					        return $this;
 | 
				
			||||||
 | 
				
			|||||||
@ -22,5 +22,5 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
function ctrans(string $string, $replace = [], $locale = null): string
 | 
					function ctrans(string $string, $replace = [], $locale = null): string
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    return trans($string, $replace, $locale);
 | 
					    return html_entity_decode(trans($string, $replace, $locale));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -254,17 +254,20 @@ class ActivityController extends BaseController
 | 
				
			|||||||
                $activity->client_id = $entity->client_id;
 | 
					                $activity->client_id = $entity->client_id;
 | 
				
			||||||
                $activity->project_id = $entity->project_id;
 | 
					                $activity->project_id = $entity->project_id;
 | 
				
			||||||
                $activity->vendor_id = $entity->vendor_id;
 | 
					                $activity->vendor_id = $entity->vendor_id;
 | 
				
			||||||
 | 
					                // no break
 | 
				
			||||||
            case Task::class:
 | 
					            case Task::class:
 | 
				
			||||||
                $activity->task_id = $entity->id;
 | 
					                $activity->task_id = $entity->id;
 | 
				
			||||||
                $activity->expense_id = $entity->id;
 | 
					                $activity->expense_id = $entity->id;
 | 
				
			||||||
                $activity->client_id = $entity->client_id;
 | 
					                $activity->client_id = $entity->client_id;
 | 
				
			||||||
                $activity->project_id = $entity->project_id;
 | 
					                $activity->project_id = $entity->project_id;
 | 
				
			||||||
                $activity->vendor_id = $entity->vendor_id;
 | 
					                $activity->vendor_id = $entity->vendor_id;
 | 
				
			||||||
 | 
					                // no break
 | 
				
			||||||
            case Payment::class:
 | 
					            case Payment::class:
 | 
				
			||||||
                $activity->payment_id = $entity->id;
 | 
					                $activity->payment_id = $entity->id;
 | 
				
			||||||
                $activity->expense_id = $entity->id;
 | 
					                $activity->expense_id = $entity->id;
 | 
				
			||||||
                $activity->client_id = $entity->client_id;
 | 
					                $activity->client_id = $entity->client_id;
 | 
				
			||||||
                $activity->project_id = $entity->project_id;
 | 
					                $activity->project_id = $entity->project_id;
 | 
				
			||||||
 | 
					                // no break
 | 
				
			||||||
            default:
 | 
					            default:
 | 
				
			||||||
                # code...
 | 
					                # code...
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
 | 
				
			|||||||
@ -41,8 +41,9 @@ class ContactLoginController extends Controller
 | 
				
			|||||||
        $company = false;
 | 
					        $company = false;
 | 
				
			||||||
        $account = false;
 | 
					        $account = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if($request->query('intended'))
 | 
					        if($request->query('intended')) {
 | 
				
			||||||
            $request->session()->put('url.intended', $request->query('intended'));
 | 
					            $request->session()->put('url.intended', $request->query('intended'));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ($request->session()->has('company_key')) {
 | 
					        if ($request->session()->has('company_key')) {
 | 
				
			||||||
            MultiDB::findAndSetDbByCompanyKey($request->session()->get('company_key'));
 | 
					            MultiDB::findAndSetDbByCompanyKey($request->session()->get('company_key'));
 | 
				
			||||||
@ -142,8 +143,9 @@ class ContactLoginController extends Controller
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        $this->setRedirectPath();
 | 
					        $this->setRedirectPath();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if($intended)
 | 
					        if($intended) {
 | 
				
			||||||
            $this->redirectTo = $intended;
 | 
					            $this->redirectTo = $intended;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $request->wantsJson()
 | 
					        return $request->wantsJson()
 | 
				
			||||||
                    ? new JsonResponse([], 204)
 | 
					                    ? new JsonResponse([], 204)
 | 
				
			||||||
 | 
				
			|||||||
@ -20,7 +20,6 @@ use Illuminate\Http\Request;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
class BrevoController extends BaseController
 | 
					class BrevoController extends BaseController
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public function __construct()
 | 
					    public function __construct()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -62,6 +62,7 @@ class InvoiceController extends Controller
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        $invitation = $invoice->invitations()->where('client_contact_id', auth()->guard('contact')->user()->id)->first();
 | 
					        $invitation = $invoice->invitations()->where('client_contact_id', auth()->guard('contact')->user()->id)->first();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // @phpstan-ignore-next-line
 | 
				
			||||||
        if ($invitation && auth()->guard('contact') && ! session()->get('is_silent') && ! $invitation->viewed_date) {
 | 
					        if ($invitation && auth()->guard('contact') && ! session()->get('is_silent') && ! $invitation->viewed_date) {
 | 
				
			||||||
            $invitation->markViewed();
 | 
					            $invitation->markViewed();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -77,13 +78,17 @@ class InvoiceController extends Controller
 | 
				
			|||||||
            'key' => $invitation ? $invitation->key : false,
 | 
					            'key' => $invitation ? $invitation->key : false,
 | 
				
			||||||
            'hash' => $hash,
 | 
					            'hash' => $hash,
 | 
				
			||||||
            'variables' => $variables,
 | 
					            'variables' => $variables,
 | 
				
			||||||
 | 
					            'invoices' => [$invoice->hashed_id],
 | 
				
			||||||
 | 
					            'db' => $invoice->company->db,
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ($request->query('mode') === 'fullscreen') {
 | 
					        if ($request->query('mode') === 'fullscreen') {
 | 
				
			||||||
            return render('invoices.show-fullscreen', $data);
 | 
					            return render('invoices.show-fullscreen', $data);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this->render('invoices.show', $data);
 | 
					        return auth()->guard('contact')->user()->client->getSetting('payment_flow') == 'default' ? $this->render('invoices.show', $data) : $this->render('invoices.show_smooth', $data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // return $this->render('invoices.show_smooth', $data);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function showBlob($hash)
 | 
					    public function showBlob($hash)
 | 
				
			||||||
@ -235,9 +240,12 @@ class InvoiceController extends Controller
 | 
				
			|||||||
            'hashed_ids' => $invoices->pluck('hashed_id'),
 | 
					            'hashed_ids' => $invoices->pluck('hashed_id'),
 | 
				
			||||||
            'total' =>  $total,
 | 
					            'total' =>  $total,
 | 
				
			||||||
            'variables' => $variables,
 | 
					            'variables' => $variables,
 | 
				
			||||||
 | 
					            'invitation' => $invitation,
 | 
				
			||||||
 | 
					            'db' => $invitation->company->db,
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this->render('invoices.payment', $data);
 | 
					        // return $this->render('invoices.payment', $data);
 | 
				
			||||||
 | 
					        return auth()->guard('contact')->user()->client->getSetting('payment_flow') === 'default' ? $this->render('invoices.payment', $data) : $this->render('invoices.show_smooth_multi', $data);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
@ -88,6 +88,8 @@ class NinjaPlanController extends Controller
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        $trial_started = "Trial Started @ ".now()->format('Y-m-d H:i:s');
 | 
					        $trial_started = "Trial Started @ ".now()->format('Y-m-d H:i:s');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        auth()->guard('contact')->user()->fill($request->only(['first_name','last_name']))->save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $client = auth()->guard('contact')->user()->client;
 | 
					        $client = auth()->guard('contact')->user()->client;
 | 
				
			||||||
        $client->private_notes = $trial_started;
 | 
					        $client->private_notes = $trial_started;
 | 
				
			||||||
        $client->fill($request->all());
 | 
					        $client->fill($request->all());
 | 
				
			||||||
 | 
				
			|||||||
@ -567,9 +567,9 @@ class CompanyGatewayController extends BaseController
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //Throttle here
 | 
					        //Throttle here
 | 
				
			||||||
        if (Cache::has("throttle_polling:import_customers:{$company_gateway->company->company_key}:{$company_gateway->hashed_id}")) {
 | 
					        // if (Cache::has("throttle_polling:import_customers:{$company_gateway->company->company_key}:{$company_gateway->hashed_id}")) {
 | 
				
			||||||
            return response()->json(['message' => 'Please wait whilst your previous attempts complete.'], 200);
 | 
					        //     return response()->json(['message' => 'Please wait whilst your previous attempts complete.'], 200);
 | 
				
			||||||
        }
 | 
					        // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dispatch(function () use ($company_gateway) {
 | 
					        dispatch(function () use ($company_gateway) {
 | 
				
			||||||
            MultiDB::setDb($company_gateway->company->db);
 | 
					            MultiDB::setDb($company_gateway->company->db);
 | 
				
			||||||
 | 
				
			|||||||
@ -51,9 +51,9 @@ class EmailHistoryController extends BaseController
 | 
				
			|||||||
        /** @var \App\Models\User $user */
 | 
					        /** @var \App\Models\User $user */
 | 
				
			||||||
        $user = auth()->user();
 | 
					        $user = auth()->user();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
        $data = SystemLog::where('company_id', $user->company()->id)
 | 
					        $data = SystemLog::where('company_id', $user->company()->id)
 | 
				
			||||||
        ->where('category_id', SystemLog::CATEGORY_MAIL)
 | 
					        ->where('category_id', SystemLog::CATEGORY_MAIL)
 | 
				
			||||||
 | 
					        ->whereJsonContains('log->history->entity', $request->entity)
 | 
				
			||||||
        ->whereJsonContains('log->history->entity_id', $this->encodePrimaryKey($request->entity_id))
 | 
					        ->whereJsonContains('log->history->entity_id', $this->encodePrimaryKey($request->entity_id))
 | 
				
			||||||
        ->orderBy('id', 'DESC')
 | 
					        ->orderBy('id', 'DESC')
 | 
				
			||||||
        ->cursor()
 | 
					        ->cursor()
 | 
				
			||||||
 | 
				
			|||||||
@ -584,20 +584,14 @@ class ExpenseController extends BaseController
 | 
				
			|||||||
        return $this->itemResponse($expense->fresh());
 | 
					        return $this->itemResponse($expense->fresh());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function edocument(EDocumentRequest $request): string
 | 
					    public function edocument(EDocumentRequest $request)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if ($request->hasFile("documents"))
 | 
					        $user = auth()->user();
 | 
				
			||||||
            try {
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return (new ImportEDocument($request->file("documents")->get(), $request->file("documents")->getClientOriginalName(), $request->file("documents")->getMimeType()))->handle();
 | 
					        foreach ($request->file("documents") as $file) {
 | 
				
			||||||
 | 
					            ImportEDocument::dispatch($file->get(), $file->getClientOriginalName(), $request->file("documents")->getMimeType(), $user->company());
 | 
				
			||||||
            } catch (\Exception $e) {
 | 
					 | 
				
			||||||
                if ($e->getCode() == 409)
 | 
					 | 
				
			||||||
                    return $e->getMessage();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                throw $e;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return "No file found";
 | 
					        return response()->json(['message' => 'Processing....'], 200);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -59,9 +59,9 @@ class ExportController extends BaseController
 | 
				
			|||||||
        /** @var \App\Models\User $user */
 | 
					        /** @var \App\Models\User $user */
 | 
				
			||||||
        $user = auth()->user();
 | 
					        $user = auth()->user();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $hash = Str::uuid();
 | 
					        $hash = Str::uuid()->toString();
 | 
				
			||||||
        $url = \Illuminate\Support\Facades\URL::temporarySignedRoute('protected_download', now()->addHour(), ['hash' => $hash]);
 | 
					        $url = \Illuminate\Support\Facades\URL::temporarySignedRoute('protected_download', now()->addHour(), ['hash' => $hash]);
 | 
				
			||||||
        Cache::put($hash, $url, now()->addHour());
 | 
					        Cache::put($hash, $url, 3600);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        CompanyExport::dispatch($user->getCompany(), $user, $hash);
 | 
					        CompanyExport::dispatch($user->getCompany(), $user, $hash);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										134
									
								
								app/Http/Controllers/ImportQuickbooksController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								app/Http/Controllers/ImportQuickbooksController.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,134 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Http\Controllers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Http\Requests\Quickbooks\AuthorizedQuickbooksRequest;
 | 
				
			||||||
 | 
					use App\Libraries\MultiDB;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Cache;
 | 
				
			||||||
 | 
					use App\Http\Requests\Quickbooks\AuthQuickbooksRequest;
 | 
				
			||||||
 | 
					use App\Services\Quickbooks\QuickbooksService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ImportQuickbooksController extends BaseController
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    // private array $import_entities = [
 | 
				
			||||||
 | 
					    //     'client' => 'Customer',
 | 
				
			||||||
 | 
					    //     'invoice' => 'Invoice',
 | 
				
			||||||
 | 
					    //     'product' => 'Item',
 | 
				
			||||||
 | 
					    //     'payment' => 'Payment'
 | 
				
			||||||
 | 
					    // ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function onAuthorized(AuthorizedQuickbooksRequest $request)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']);
 | 
				
			||||||
 | 
					        $company = $request->getCompany();
 | 
				
			||||||
 | 
					        $qb = new QuickbooksService($company);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $realm = $request->query('realmId');
 | 
				
			||||||
 | 
					        $access_token_object = $qb->sdk()->accessTokenFromCode($request->query('code'), $realm);
 | 
				
			||||||
 | 
					        $qb->sdk()->saveOAuthToken($access_token_object);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return redirect(config('ninja.react_url'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Determine if the user is authorized to make this request.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function authorizeQuickbooks(AuthQuickbooksRequest $request, string $token)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']);
 | 
				
			||||||
 | 
					        $company = $request->getCompany();
 | 
				
			||||||
 | 
					        $qb = new QuickbooksService($company);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $authorizationUrl = $qb->sdk()->getAuthorizationUrl();
 | 
				
			||||||
 | 
					        $state = $qb->sdk()->getState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Cache::put($state, $token, 190);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return redirect()->to($authorizationUrl);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function preimport(string $type, string $hash)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // // Check for authorization otherwise
 | 
				
			||||||
 | 
					        // // Create a reference
 | 
				
			||||||
 | 
					        // $data = [
 | 
				
			||||||
 | 
					        //     'hash' => $hash,
 | 
				
			||||||
 | 
					        //     'type' => $type
 | 
				
			||||||
 | 
					        // ];
 | 
				
			||||||
 | 
					        // $this->getData($data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getData($data)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // $entity = $this->import_entities[$data['type']];
 | 
				
			||||||
 | 
					        // $cache_name = "{$data['hash']}-{$data['type']}";
 | 
				
			||||||
 | 
					        // // TODO: Get or put cache  or DB?
 | 
				
			||||||
 | 
					        // if(! Cache::has($cache_name)) {
 | 
				
			||||||
 | 
					        //     $contents = call_user_func([$this->service, "fetch{$entity}s"]);
 | 
				
			||||||
 | 
					        //     if($contents->isEmpty()) {
 | 
				
			||||||
 | 
					        //         return;
 | 
				
			||||||
 | 
					        //     }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //     Cache::put($cache_name, base64_encode($contents->toJson()), 600);
 | 
				
			||||||
 | 
					        // }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @OA\Post(
 | 
				
			||||||
 | 
					     *      path="/api/v1/import_json",
 | 
				
			||||||
 | 
					     *      operationId="getImportJson",
 | 
				
			||||||
 | 
					     *      tags={"import"},
 | 
				
			||||||
 | 
					     *      summary="Import data from the system",
 | 
				
			||||||
 | 
					     *      description="Import data from the system",
 | 
				
			||||||
 | 
					     *      @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
 | 
				
			||||||
 | 
					     *      @OA\Response(
 | 
				
			||||||
 | 
					     *          response=200,
 | 
				
			||||||
 | 
					     *          description="success",
 | 
				
			||||||
 | 
					     *          @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\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 import(Request $request)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // $hash = Str::random(32);
 | 
				
			||||||
 | 
					        // foreach($request->input('import_types') as $type) {
 | 
				
			||||||
 | 
					        //     $this->preimport($type, $hash);
 | 
				
			||||||
 | 
					        // }
 | 
				
			||||||
 | 
					        // /** @var \App\Models\User $user */
 | 
				
			||||||
 | 
					        // // $user = auth()->user() ?? Auth::loginUsingId(60);
 | 
				
			||||||
 | 
					        // $data = ['import_types' => $request->input('import_types') ] + compact('hash');
 | 
				
			||||||
 | 
					        // if (Ninja::isHosted()) {
 | 
				
			||||||
 | 
					        //     QuickbooksIngest::dispatch($data, $user->company());
 | 
				
			||||||
 | 
					        // } else {
 | 
				
			||||||
 | 
					        //     QuickbooksIngest::dispatch($data, $user->company());
 | 
				
			||||||
 | 
					        // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // return response()->json(['message' => 'Processing'], 200);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -22,7 +22,6 @@ use Illuminate\Support\Str;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class OneTimeTokenController extends BaseController
 | 
					class OneTimeTokenController extends BaseController
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public function __construct()
 | 
					    public function __construct()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        parent::__construct();
 | 
					        parent::__construct();
 | 
				
			||||||
 | 
				
			|||||||
@ -24,7 +24,6 @@ use Illuminate\Http\Request;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
class PostMarkController extends BaseController
 | 
					class PostMarkController extends BaseController
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public function __construct()
 | 
					    public function __construct()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -297,6 +297,8 @@ class PreviewController extends BaseController
 | 
				
			|||||||
                ->setTemplate($design_object)
 | 
					                ->setTemplate($design_object)
 | 
				
			||||||
                ->mock();
 | 
					                ->mock();
 | 
				
			||||||
        } catch(SyntaxError $e) {
 | 
					        } catch(SyntaxError $e) {
 | 
				
			||||||
 | 
					        } catch(\Exception $e) {
 | 
				
			||||||
 | 
					            return response()->json(['message' => 'invalid data access', 'errors' => ['design.design.body' => $e->getMessage()]], 422);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (request()->query('html') == 'true') {
 | 
					        if (request()->query('html') == 'true') {
 | 
				
			||||||
 | 
				
			|||||||
@ -181,8 +181,9 @@ class SelfUpdateController extends BaseController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public function checkVersion()
 | 
					    public function checkVersion()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if(Ninja::isHosted())
 | 
					        if(Ninja::isHosted()) {
 | 
				
			||||||
            return '5.10.SaaS';
 | 
					            return '5.10.SaaS';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return trim(file_get_contents(config('ninja.version_url')));
 | 
					        return trim(file_get_contents(config('ninja.version_url')));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -11,23 +11,25 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Http\Controllers\VendorPortal;
 | 
					namespace App\Http\Controllers\VendorPortal;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use App\Events\Misc\InvitationWasViewed;
 | 
					use App\Utils\Ninja;
 | 
				
			||||||
use App\Events\PurchaseOrder\PurchaseOrderWasAccepted;
 | 
					use App\Models\Webhook;
 | 
				
			||||||
use App\Events\PurchaseOrder\PurchaseOrderWasViewed;
 | 
					use Illuminate\View\View;
 | 
				
			||||||
 | 
					use App\Models\PurchaseOrder;
 | 
				
			||||||
 | 
					use App\Utils\Traits\MakesHash;
 | 
				
			||||||
 | 
					use App\Utils\Traits\MakesDates;
 | 
				
			||||||
 | 
					use App\Jobs\Entity\CreateRawPdf;
 | 
				
			||||||
 | 
					use App\Jobs\Util\WebhookHandler;
 | 
				
			||||||
use App\Http\Controllers\Controller;
 | 
					use App\Http\Controllers\Controller;
 | 
				
			||||||
use App\Http\Requests\VendorPortal\PurchaseOrders\ProcessPurchaseOrdersInBulkRequest;
 | 
					use App\Jobs\Invoice\InjectSignature;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Cache;
 | 
				
			||||||
 | 
					use Illuminate\Contracts\View\Factory;
 | 
				
			||||||
 | 
					use App\Models\PurchaseOrderInvitation;
 | 
				
			||||||
 | 
					use App\Events\Misc\InvitationWasViewed;
 | 
				
			||||||
 | 
					use App\Events\PurchaseOrder\PurchaseOrderWasViewed;
 | 
				
			||||||
 | 
					use App\Events\PurchaseOrder\PurchaseOrderWasAccepted;
 | 
				
			||||||
use App\Http\Requests\VendorPortal\PurchaseOrders\ShowPurchaseOrderRequest;
 | 
					use App\Http\Requests\VendorPortal\PurchaseOrders\ShowPurchaseOrderRequest;
 | 
				
			||||||
use App\Http\Requests\VendorPortal\PurchaseOrders\ShowPurchaseOrdersRequest;
 | 
					use App\Http\Requests\VendorPortal\PurchaseOrders\ShowPurchaseOrdersRequest;
 | 
				
			||||||
use App\Jobs\Entity\CreateRawPdf;
 | 
					use App\Http\Requests\VendorPortal\PurchaseOrders\ProcessPurchaseOrdersInBulkRequest;
 | 
				
			||||||
use App\Jobs\Invoice\InjectSignature;
 | 
					 | 
				
			||||||
use App\Models\PurchaseOrder;
 | 
					 | 
				
			||||||
use App\Models\PurchaseOrderInvitation;
 | 
					 | 
				
			||||||
use App\Utils\Ninja;
 | 
					 | 
				
			||||||
use App\Utils\Traits\MakesDates;
 | 
					 | 
				
			||||||
use App\Utils\Traits\MakesHash;
 | 
					 | 
				
			||||||
use Illuminate\Contracts\View\Factory;
 | 
					 | 
				
			||||||
use Illuminate\Support\Facades\Cache;
 | 
					 | 
				
			||||||
use Illuminate\View\View;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PurchaseOrderController extends Controller
 | 
					class PurchaseOrderController extends Controller
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -187,6 +189,9 @@ class PurchaseOrderController extends Controller
 | 
				
			|||||||
                            }
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            event(new PurchaseOrderWasAccepted($purchase_order, auth()->guard('vendor')->user(), $purchase_order->company, Ninja::eventVars()));
 | 
					                            event(new PurchaseOrderWasAccepted($purchase_order, auth()->guard('vendor')->user(), $purchase_order->company, Ninja::eventVars()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            WebhookHandler::dispatch(Webhook::EVENT_ACCEPTED_PURCHASE_ORDER, $purchase_order, $purchase_order->company, 'vendor')->delay(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        });
 | 
					                        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ($purchase_count_query->count() == 1) {
 | 
					        if ($purchase_count_query->count() == 1) {
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,7 @@ namespace App\Http\Controllers\VendorPortal;
 | 
				
			|||||||
use App\Http\Controllers\Controller;
 | 
					use App\Http\Controllers\Controller;
 | 
				
			||||||
use App\Models\VendorContact;
 | 
					use App\Models\VendorContact;
 | 
				
			||||||
use App\Utils\Traits\MakesHash;
 | 
					use App\Utils\Traits\MakesHash;
 | 
				
			||||||
use App\Utils\TranslationHelper;
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class VendorContactController extends Controller
 | 
					class VendorContactController extends Controller
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -58,14 +58,14 @@ class VendorContactController extends Controller
 | 
				
			|||||||
            'settings' => $vendor_contact->vendor->company->settings,
 | 
					            'settings' => $vendor_contact->vendor->company->settings,
 | 
				
			||||||
            'company' => $vendor_contact->vendor->company,
 | 
					            'company' => $vendor_contact->vendor->company,
 | 
				
			||||||
            'sidebar' => $this->sidebarMenu(),
 | 
					            'sidebar' => $this->sidebarMenu(),
 | 
				
			||||||
            'countries' => TranslationHelper::getCountries(),
 | 
					            'countries' => app('countries'),
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function update(VendorContact $vendor_contact)
 | 
					    public function update(Request $request, VendorContact $vendor_contact)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $vendor_contact->fill(request()->all());
 | 
					        $vendor_contact->fill($request->all());
 | 
				
			||||||
        $vendor_contact->vendor->fill(request()->all());
 | 
					        $vendor_contact->vendor->fill($request->all());
 | 
				
			||||||
        $vendor_contact->push();
 | 
					        $vendor_contact->push();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return back()->withSuccess(ctrans('texts.profile_updated_successfully'));
 | 
					        return back()->withSuccess(ctrans('texts.profile_updated_successfully'));
 | 
				
			||||||
@ -76,16 +76,10 @@ class VendorContactController extends Controller
 | 
				
			|||||||
        $enabled_modules = auth()->guard('vendor')->user()->company->enabled_modules;
 | 
					        $enabled_modules = auth()->guard('vendor')->user()->company->enabled_modules;
 | 
				
			||||||
        $data = [];
 | 
					        $data = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // TODO: Enable dashboard once it's completed.
 | 
					 | 
				
			||||||
        // $this->settings->enable_client_portal_dashboard
 | 
					 | 
				
			||||||
        // $data[] = [ 'title' => ctrans('texts.dashboard'), 'url' => 'client.dashboard', 'icon' => 'activity'];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (self::MODULE_PURCHASE_ORDERS & $enabled_modules) {
 | 
					        if (self::MODULE_PURCHASE_ORDERS & $enabled_modules) {
 | 
				
			||||||
            $data[] = ['title' => ctrans('texts.purchase_orders'), 'url' => 'vendor.purchase_orders.index', 'icon' => 'file-text'];
 | 
					            $data[] = ['title' => ctrans('texts.purchase_orders'), 'url' => 'vendor.purchase_orders.index', 'icon' => 'file-text'];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // $data[] = ['title' => ctrans('texts.documents'), 'url' => 'client.documents.index', 'icon' => 'download'];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return $data;
 | 
					        return $data;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -79,7 +79,7 @@ class ContactRegister
 | 
				
			|||||||
        // As a fallback for self-hosted, it will use default company in the system
 | 
					        // As a fallback for self-hosted, it will use default company in the system
 | 
				
			||||||
        // if key isn't provided in the url.
 | 
					        // if key isn't provided in the url.
 | 
				
			||||||
        if (! $request->route()->parameter('company_key') && Ninja::isSelfHost()) {
 | 
					        if (! $request->route()->parameter('company_key') && Ninja::isSelfHost()) {
 | 
				
			||||||
            $company = Account::query()->first()->default_company;
 | 
					            $company = Account::query()->first()->default_company ?? Account::query()->first()->companies->first();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (! $company->client_can_register) {
 | 
					            if (! $company->client_can_register) {
 | 
				
			||||||
                abort(400, 'Registration disabled');
 | 
					                abort(400, 'Registration disabled');
 | 
				
			||||||
 | 
				
			|||||||
@ -4,15 +4,15 @@ namespace App\Http\Middleware;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use Closure;
 | 
					use Closure;
 | 
				
			||||||
use Illuminate\Cache\RateLimiter;
 | 
					use Illuminate\Cache\RateLimiter;
 | 
				
			||||||
 | 
					use Illuminate\Contracts\Redis\Factory as Redis;
 | 
				
			||||||
use Illuminate\Redis\Limiters\DurationLimiter;
 | 
					use Illuminate\Redis\Limiters\DurationLimiter;
 | 
				
			||||||
use Illuminate\Routing\Middleware\ThrottleRequests;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ThrottleRequestsWithPredis extends ThrottleRequests
 | 
					class ThrottleRequestsWithPredis extends \Illuminate\Routing\Middleware\ThrottleRequests
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The Redis factory implementation.
 | 
					     * The Redis factory implementation.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @var \Illuminate\Redis\Connections\Connection
 | 
					     * @var \Illuminate\Contracts\Redis\Factory
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected $redis;
 | 
					    protected $redis;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -32,14 +32,14 @@ class ThrottleRequestsWithPredis extends ThrottleRequests
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Create a new request throttler.
 | 
					     * Create a new request throttler.
 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param  \Illuminate\Cache\RateLimiter  $limiter
 | 
					 | 
				
			||||||
     * @return void
 | 
					     * @return void
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function __construct(RateLimiter $limiter)
 | 
					
 | 
				
			||||||
 | 
					    /** @phpstan-ignore-next-line */
 | 
				
			||||||
 | 
					    public function __construct(RateLimiter $limiter, Redis $redis)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        parent::__construct($limiter);
 | 
					        parent::__construct($limiter);
 | 
				
			||||||
 | 
					        /** @phpstan-ignore-next-line */
 | 
				
			||||||
        $this->redis = \Illuminate\Support\Facades\Redis::connection('sentinel-cache');
 | 
					        $this->redis = \Illuminate\Support\Facades\Redis::connection('sentinel-cache');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -56,7 +56,7 @@ class ThrottleRequestsWithPredis extends ThrottleRequests
 | 
				
			|||||||
    protected function handleRequest($request, Closure $next, array $limits)
 | 
					    protected function handleRequest($request, Closure $next, array $limits)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        foreach ($limits as $limit) {
 | 
					        foreach ($limits as $limit) {
 | 
				
			||||||
            if ($this->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decayMinutes)) {
 | 
					            if ($this->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decaySeconds)) {
 | 
				
			||||||
                throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback);
 | 
					                throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -79,16 +79,16 @@ class ThrottleRequestsWithPredis extends ThrottleRequests
 | 
				
			|||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param  string  $key
 | 
					     * @param  string  $key
 | 
				
			||||||
     * @param  int  $maxAttempts
 | 
					     * @param  int  $maxAttempts
 | 
				
			||||||
     * @param  int  $decayMinutes
 | 
					     * @param  int  $decaySeconds
 | 
				
			||||||
     * @return mixed
 | 
					     * @return mixed
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected function tooManyAttempts($key, $maxAttempts, $decayMinutes)
 | 
					    protected function tooManyAttempts($key, $maxAttempts, $decaySeconds)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $limiter = new DurationLimiter(
 | 
					        $limiter = new DurationLimiter(
 | 
				
			||||||
            $this->redis,
 | 
					            $this->getRedisConnection(),
 | 
				
			||||||
            $key,
 | 
					            $key,
 | 
				
			||||||
            $maxAttempts,
 | 
					            $maxAttempts,
 | 
				
			||||||
            $decayMinutes * 60
 | 
					            $decaySeconds
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return tap(! $limiter->acquire(), function () use ($key, $limiter) {
 | 
					        return tap(! $limiter->acquire(), function () use ($key, $limiter) {
 | 
				
			||||||
@ -121,4 +121,13 @@ class ThrottleRequestsWithPredis extends ThrottleRequests
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->decaysAt[$key] - $this->currentTime();
 | 
					        return $this->decaysAt[$key] - $this->currentTime();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the Redis connection that should be used for throttling.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected function getRedisConnection()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->redis;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -68,8 +68,9 @@ class StoreNoteRequest extends Request
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public function getEntity()
 | 
					    public function getEntity()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if(!$this->entity)
 | 
					        if(!$this->entity) {
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $class = "\\App\\Models\\".ucfirst(Str::camel(rtrim($this->entity, 's')));
 | 
					        $class = "\\App\\Models\\".ucfirst(Str::camel(rtrim($this->entity, 's')));
 | 
				
			||||||
        return $class::withTrashed()->find(is_string($this->entity_id) ? $this->decodePrimaryKey($this->entity_id) : $this->entity_id);
 | 
					        return $class::withTrashed()->find(is_string($this->entity_id) ? $this->decodePrimaryKey($this->entity_id) : $this->entity_id);
 | 
				
			||||||
 | 
				
			|||||||
@ -48,7 +48,8 @@ class StoreBankTransactionRuleRequest extends Request
 | 
				
			|||||||
            'rules.*.value' => 'bail|required|nullable',
 | 
					            'rules.*.value' => 'bail|required|nullable',
 | 
				
			||||||
            'auto_convert' => 'bail|sometimes|bool',
 | 
					            'auto_convert' => 'bail|sometimes|bool',
 | 
				
			||||||
            'matches_on_all' => 'bail|sometimes|bool',
 | 
					            'matches_on_all' => 'bail|sometimes|bool',
 | 
				
			||||||
            'applies_to' => 'bail|sometimes|string',
 | 
					            'applies_to' => 'bail|sometimes|string|in:CREDIT,DEBIT',
 | 
				
			||||||
 | 
					            'on_credit_match' => 'bail|sometimes|in:create_payment,link_payment'
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $rules['category_id'] = 'bail|sometimes|nullable|exists:expense_categories,id,company_id,'.$user->company()->id.',is_deleted,0';
 | 
					        $rules['category_id'] = 'bail|sometimes|nullable|exists:expense_categories,id,company_id,'.$user->company()->id.',is_deleted,0';
 | 
				
			||||||
 | 
				
			|||||||
@ -66,8 +66,7 @@ class ShowCalculatedFieldRequest extends Request
 | 
				
			|||||||
            $input['end_date'] = now()->format('Y-m-d');
 | 
					            $input['end_date'] = now()->format('Y-m-d');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(isset($input['period']) && $input['period'] == 'previous')
 | 
					        if(isset($input['period']) && $input['period'] == 'previous') {
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            $dates = $this->calculatePreviousPeriodStartAndEndDates($input, $user->company());
 | 
					            $dates = $this->calculatePreviousPeriodStartAndEndDates($input, $user->company());
 | 
				
			||||||
            $input['start_date'] = $dates[0];
 | 
					            $input['start_date'] = $dates[0];
 | 
				
			||||||
            $input['end_date'] = $dates[1];
 | 
					            $input['end_date'] = $dates[1];
 | 
				
			||||||
 | 
				
			|||||||
@ -25,9 +25,9 @@ class EDocumentRequest extends Request
 | 
				
			|||||||
        $rules = [];
 | 
					        $rules = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ($this->file('documents') && is_array($this->file('documents'))) {
 | 
					        if ($this->file('documents') && is_array($this->file('documents'))) {
 | 
				
			||||||
            $rules['documents.*'] = $this->fileValidation();
 | 
					            $rules['documents.*'] = 'required|file|max:1000000|mimes:xml';
 | 
				
			||||||
        } elseif ($this->file('documents')) {
 | 
					        } elseif ($this->file('documents')) {
 | 
				
			||||||
            $rules['documents'] = $this->fileValidation();
 | 
					            $rules['documents'] = 'required|file|max:1000000|mimes:xml';
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return $rules;
 | 
					        return $rules;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -40,10 +40,11 @@ class BulkInvoiceRequest extends Request
 | 
				
			|||||||
        /** @var \App\Models\User $user */
 | 
					        /** @var \App\Models\User $user */
 | 
				
			||||||
        $user = auth()->user();
 | 
					        $user = auth()->user();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(\Illuminate\Support\Facades\Cache::has($this->ip()."|".$this->input('action', 0)."|".json_encode($this->input('ids', ''))."|".$user->company()->company_key))
 | 
					        if(\Illuminate\Support\Facades\Cache::has($this->ip()."|".$this->input('action', 0)."|".$user->company()->company_key)) {
 | 
				
			||||||
            throw new DuplicatePaymentException('Duplicate request.', 429);
 | 
					            throw new DuplicatePaymentException('Duplicate request.', 429);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        \Illuminate\Support\Facades\Cache::put(($this->ip()."|".$this->input('action', 0)."|".json_encode($this->input('ids', ''))."|".$user->company()->company_key), true, 1);
 | 
					        \Illuminate\Support\Facades\Cache::put(($this->ip()."|".$this->input('action', 0)."|".$user->company()->company_key), true, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -93,6 +93,12 @@ class StoreInvoiceRequest extends Request
 | 
				
			|||||||
        /** @var \App\Models\User $user */
 | 
					        /** @var \App\Models\User $user */
 | 
				
			||||||
        $user = auth()->user();
 | 
					        $user = auth()->user();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(\Illuminate\Support\Facades\Cache::has($this->ip()."|INVOICE|".$this->input('client_id', '')."|".$user->company()->company_key)) {
 | 
				
			||||||
 | 
					            usleep(200000);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        \Illuminate\Support\Facades\Cache::put($this->ip()."|INVOICE|".$this->input('client_id', '')."|".$user->company()->company_key,1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $input = $this->all();
 | 
					        $input = $this->all();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $input = $this->decodePrimaryKeys($input);
 | 
					        $input = $this->decodePrimaryKeys($input);
 | 
				
			||||||
 | 
				
			|||||||
@ -80,8 +80,9 @@ class StorePaymentRequest extends Request
 | 
				
			|||||||
        /** @var \App\Models\User $user */
 | 
					        /** @var \App\Models\User $user */
 | 
				
			||||||
        $user = auth()->user();
 | 
					        $user = auth()->user();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(\Illuminate\Support\Facades\Cache::has($this->ip()."|".$this->input('amount', 0)."|".$this->input('client_id', '')."|".$user->company()->company_key))
 | 
					        if(\Illuminate\Support\Facades\Cache::has($this->ip()."|".$this->input('amount', 0)."|".$this->input('client_id', '')."|".$user->company()->company_key)) {
 | 
				
			||||||
            throw new DuplicatePaymentException('Duplicate request.', 429);
 | 
					            throw new DuplicatePaymentException('Duplicate request.', 429);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        \Illuminate\Support\Facades\Cache::put(($this->ip()."|".$this->input('amount', 0)."|".$this->input('client_id', '')."|".$user->company()->company_key), true, 1);
 | 
					        \Illuminate\Support\Facades\Cache::put(($this->ip()."|".$this->input('amount', 0)."|".$this->input('client_id', '')."|".$user->company()->company_key), true, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										69
									
								
								app/Http/Requests/Quickbooks/AuthQuickbooksRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								app/Http/Requests/Quickbooks/AuthQuickbooksRequest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,69 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Http\Requests\Quickbooks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Models\Company;
 | 
				
			||||||
 | 
					use App\Models\User;
 | 
				
			||||||
 | 
					use Illuminate\Foundation\Http\FormRequest;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Cache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AuthQuickbooksRequest extends FormRequest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Determine if the user is authorized to make this request.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return bool
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function authorize(): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return is_array($this->getTokenContent());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the validation rules that apply to the request.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function rules(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Resolve one-time token instance.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return mixed
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function getTokenContent()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if ($this->state) {
 | 
				
			||||||
 | 
					            $this->token = $this->state;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $data = Cache::get($this->token);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $data;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getContact(): ?User
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return User::findOrFail($this->getTokenContent()['user_id']);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getCompany(): ?Company
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return Company::query()->where('company_key', $this->getTokenContent()['company_key'])->firstOrFail();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										69
									
								
								app/Http/Requests/Quickbooks/AuthorizedQuickbooksRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								app/Http/Requests/Quickbooks/AuthorizedQuickbooksRequest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,69 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Http\Requests\Quickbooks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Models\Company;
 | 
				
			||||||
 | 
					use App\Models\User;
 | 
				
			||||||
 | 
					use Illuminate\Foundation\Http\FormRequest;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Cache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AuthorizedQuickbooksRequest extends FormRequest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Determine if the user is authorized to make this request.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return bool
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function authorize(): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return is_array($this->getTokenContent());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the validation rules that apply to the request.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function rules(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'code' => 'required|string',
 | 
				
			||||||
 | 
					            'state' => 'required|string',
 | 
				
			||||||
 | 
					            'realmId' => 'required|string',
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Resolve one-time token instance.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return mixed
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function getTokenContent()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $token = Cache::get($this->state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $data = Cache::get($token);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $data;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getContact()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return User::findOrFail($this->getTokenContent()['user_id']);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getCompany()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return Company::where('company_key', $this->getTokenContent()['company_key'])->firstOrFail();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -17,7 +17,6 @@ use Illuminate\Auth\Access\AuthorizationException;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class GenericReportRequest extends Request
 | 
					class GenericReportRequest extends Request
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Determine if the user is authorized to make this request.
 | 
					     * Determine if the user is authorized to make this request.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
 | 
				
			|||||||
@ -133,7 +133,7 @@ class UpdateTaskRequest extends Request
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(!isset($input['time_log']) || empty($input['time_log']) || $input['time_log'] == '{}') {
 | 
					        if(!isset($input['time_log']) || empty($input['time_log']) || $input['time_log'] == '{}' || $input['time_log'] == '[""]') {
 | 
				
			||||||
            $input['time_log'] = json_encode([]);
 | 
					            $input['time_log'] = json_encode([]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,6 @@ use App\Http\Requests\Request;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class DisconnectUserMailerRequest extends Request
 | 
					class DisconnectUserMailerRequest extends Request
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Determine if the user is authorized to make this request.
 | 
					     * Determine if the user is authorized to make this request.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
 | 
				
			|||||||
@ -77,8 +77,7 @@ class UpdateUserRequest extends Request
 | 
				
			|||||||
            unset($input['oauth_user_token']);
 | 
					            unset($input['oauth_user_token']);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(isset($input['password']) && is_string($input['password']))
 | 
					        if(isset($input['password']) && is_string($input['password'])) {
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            $input['password'] = trim($input['password']);
 | 
					            $input['password'] = trim($input['password']);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -31,12 +31,8 @@ class TwigLint implements ValidationRule
 | 
				
			|||||||
        try {
 | 
					        try {
 | 
				
			||||||
            $twig->parse($twig->tokenize(new \Twig\Source(preg_replace('/<!--.*?-->/s', '', $value), '')));
 | 
					            $twig->parse($twig->tokenize(new \Twig\Source(preg_replace('/<!--.*?-->/s', '', $value), '')));
 | 
				
			||||||
        } catch (\Twig\Error\SyntaxError $e) {
 | 
					        } catch (\Twig\Error\SyntaxError $e) {
 | 
				
			||||||
            // echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
 | 
					 | 
				
			||||||
            nlog($e->getMessage());
 | 
					 | 
				
			||||||
            $fail($e->getMessage());
 | 
					            $fail($e->getMessage());
 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -34,8 +34,7 @@ class ValidClientScheme implements ValidationRule, ValidatorAwareRule
 | 
				
			|||||||
    public function validate(string $attribute, mixed $value, Closure $fail): void
 | 
					    public function validate(string $attribute, mixed $value, Closure $fail): void
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(isset($value['Invoice']))
 | 
					        if(isset($value['Invoice'])) {
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            $r = new EInvoice();
 | 
					            $r = new EInvoice();
 | 
				
			||||||
            $errors = $r->validateRequest($value['Invoice'], ClientLevel::class);
 | 
					            $errors = $r->validateRequest($value['Invoice'], ClientLevel::class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -24,7 +24,6 @@ use Illuminate\Contracts\Validation\ValidatorAwareRule;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
class ValidCompanyScheme implements ValidationRule, ValidatorAwareRule
 | 
					class ValidCompanyScheme implements ValidationRule, ValidatorAwareRule
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The validator instance.
 | 
					     * The validator instance.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -35,8 +34,7 @@ class ValidCompanyScheme implements ValidationRule, ValidatorAwareRule
 | 
				
			|||||||
    public function validate(string $attribute, mixed $value, Closure $fail): void
 | 
					    public function validate(string $attribute, mixed $value, Closure $fail): void
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(isset($value['Invoice']))
 | 
					        if(isset($value['Invoice'])) {
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            $r = new EInvoice();
 | 
					            $r = new EInvoice();
 | 
				
			||||||
            $errors = $r->validateRequest($value['Invoice'], CompanyLevel::class);
 | 
					            $errors = $r->validateRequest($value['Invoice'], CompanyLevel::class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Http\ViewComposers\Components\Rotessa;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\DataProviders\CAProvinces;
 | 
				
			||||||
 | 
					use App\DataProviders\USStates;
 | 
				
			||||||
 | 
					use Illuminate\View\Component;
 | 
				
			||||||
 | 
					use App\Models\ClientContact;
 | 
				
			||||||
 | 
					use Illuminate\Support\Arr;
 | 
				
			||||||
 | 
					use Illuminate\View\View;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AmericanBankInfo Component
 | 
				
			||||||
 | 
					class AccountComponent extends Component
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private $fields = [
 | 
				
			||||||
 | 
					        'bank_account_type',
 | 
				
			||||||
 | 
					        'routing_number',
 | 
				
			||||||
 | 
					        'institution_number',
 | 
				
			||||||
 | 
					        'transit_number',
 | 
				
			||||||
 | 
					        'bank_name',
 | 
				
			||||||
 | 
					        'country',
 | 
				
			||||||
 | 
					        'account_number'
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private $defaults =  [
 | 
				
			||||||
 | 
					        'bank_account_type' => null,
 | 
				
			||||||
 | 
					        'routing_number' => null,
 | 
				
			||||||
 | 
					        'institution_number' => null,
 | 
				
			||||||
 | 
					        'transit_number' => null,
 | 
				
			||||||
 | 
					        'bank_name' => null,
 | 
				
			||||||
 | 
					        'account_number' => null,
 | 
				
			||||||
 | 
					        'country' => 'US',
 | 
				
			||||||
 | 
					        "authorization_type" => 'Online'
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function __construct(public array $account)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->attributes = $this->newAttributeBag(Arr::only($this->account, $this->fields));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function render()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return render('gateways.rotessa.components.account', $this->attributes->getAttributes() + $this->defaults);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Http\ViewComposers\Components\Rotessa;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\DataProviders\CAProvinces;
 | 
				
			||||||
 | 
					use App\DataProviders\USStates;
 | 
				
			||||||
 | 
					use Illuminate\View\Component;
 | 
				
			||||||
 | 
					use App\Models\ClientContact;
 | 
				
			||||||
 | 
					use Illuminate\Support\Arr;
 | 
				
			||||||
 | 
					use Illuminate\View\View;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Address Component
 | 
				
			||||||
 | 
					class AddressComponent extends Component
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private $fields = [
 | 
				
			||||||
 | 
					        'address_1',
 | 
				
			||||||
 | 
					        'address_2',
 | 
				
			||||||
 | 
					        'city',
 | 
				
			||||||
 | 
					        'postal_code',
 | 
				
			||||||
 | 
					        'province_code',
 | 
				
			||||||
 | 
					        'country'
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private $defaults = [
 | 
				
			||||||
 | 
					        'country' => 'US'
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function __construct(public array $address)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if(strlen($this->address['state']) > 2) {
 | 
				
			||||||
 | 
					            $this->address['state'] = $this->address['country'] == 'US' ? array_search($this->address['state'], USStates::$states) : CAProvinces::getAbbreviation($this->address['state']);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->attributes = $this->newAttributeBag(
 | 
				
			||||||
 | 
					            Arr::only(
 | 
				
			||||||
 | 
					                Arr::mapWithKeys($this->address, function ($item, $key) {
 | 
				
			||||||
 | 
					                    return in_array($key, ['address1','address2','state']) ? [ (['address1' => 'address_1','address2' => 'address_2','state' => 'province_code'])[$key] => $item ] : [ $key => $item ];
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					                $this->fields
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function render()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return render('gateways.rotessa.components.address', $this->attributes->getAttributes() + $this->defaults);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Http\ViewComposers\Components\Rotessa;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\DataProviders\CAProvinces;
 | 
				
			||||||
 | 
					use App\DataProviders\USStates;
 | 
				
			||||||
 | 
					use Illuminate\View\Component;
 | 
				
			||||||
 | 
					use App\Models\ClientContact;
 | 
				
			||||||
 | 
					use Illuminate\Support\Arr;
 | 
				
			||||||
 | 
					use Illuminate\View\View;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Contact Component
 | 
				
			||||||
 | 
					class ContactComponent extends Component
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(ClientContact $contact)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $contact = collect($contact->client->contacts->firstWhere('is_primary', 1)->toArray())->merge([
 | 
				
			||||||
 | 
					            'home_phone' => $contact->client->phone,
 | 
				
			||||||
 | 
					            'custom_identifier' => $contact->client->client_hash,
 | 
				
			||||||
 | 
					            'name' => $contact->client->name,
 | 
				
			||||||
 | 
					            'id' => null,
 | 
				
			||||||
 | 
					        ])->all();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->attributes = $this->newAttributeBag(Arr::only($contact, $this->fields));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private $fields = [
 | 
				
			||||||
 | 
					        'name',
 | 
				
			||||||
 | 
					        'email',
 | 
				
			||||||
 | 
					        'home_phone',
 | 
				
			||||||
 | 
					        'phone',
 | 
				
			||||||
 | 
					        'custom_identifier',
 | 
				
			||||||
 | 
					        'customer_type' ,
 | 
				
			||||||
 | 
					        'id'
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private $defaults = [
 | 
				
			||||||
 | 
					        'customer_type' => "Business",
 | 
				
			||||||
 | 
					        'custom_identifier' => null,
 | 
				
			||||||
 | 
					        'customer_id' => null
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function render()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return render('gateways.rotessa.components.contact', $this->attributes->getAttributes() + $this->defaults);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -88,11 +88,11 @@ class PortalComposer
 | 
				
			|||||||
        $data['sidebar'] = $this->sidebarMenu();
 | 
					        $data['sidebar'] = $this->sidebarMenu();
 | 
				
			||||||
        $data['header'] = [];
 | 
					        $data['header'] = [];
 | 
				
			||||||
        $data['footer'] = [];
 | 
					        $data['footer'] = [];
 | 
				
			||||||
        $data['countries'] = TranslationHelper::getCountries();
 | 
					        $data['countries'] = app('countries');
 | 
				
			||||||
        $data['company'] = auth()->guard('contact')->user()->company;
 | 
					        $data['company'] = auth()->guard('contact')->user()->company;
 | 
				
			||||||
        $data['client'] = auth()->guard('contact')->user()->client;
 | 
					        $data['client'] = auth()->guard('contact')->user()->client;
 | 
				
			||||||
        $data['settings'] = $this->settings;
 | 
					        $data['settings'] = $this->settings;
 | 
				
			||||||
        $data['currencies'] = TranslationHelper::getCurrencies();
 | 
					        $data['currencies'] = app('currencies');
 | 
				
			||||||
        $data['contact'] = auth()->guard('contact')->user();
 | 
					        $data['contact'] = auth()->guard('contact')->user();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $data['multiple_contacts'] = session()->get('multiple_contacts') ?: collect();
 | 
					        $data['multiple_contacts'] = session()->get('multiple_contacts') ?: collect();
 | 
				
			||||||
@ -136,11 +136,11 @@ class PortalComposer
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        $data[] = ['title' => ctrans('texts.statement'), 'url' => 'client.statement', 'icon' => 'activity'];
 | 
					        $data[] = ['title' => ctrans('texts.statement'), 'url' => 'client.statement', 'icon' => 'activity'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (Ninja::isHosted() && auth()->guard('contact')->user()->company->id == config('ninja.ninja_default_company_id')) {
 | 
					        // if (Ninja::isHosted() && auth()->guard('contact')->user()->company->id == config('ninja.ninja_default_company_id')) {
 | 
				
			||||||
        $data[] = ['title' => ctrans('texts.plan'), 'url' => 'client.plan', 'icon' => 'credit-card'];
 | 
					        $data[] = ['title' => ctrans('texts.plan'), 'url' => 'client.plan', 'icon' => 'credit-card'];
 | 
				
			||||||
        } else {
 | 
					        // } else {
 | 
				
			||||||
        $data[] = ['title' => ctrans('texts.subscriptions'), 'url' => 'client.subscriptions.index', 'icon' => 'calendar'];
 | 
					        $data[] = ['title' => ctrans('texts.subscriptions'), 'url' => 'client.subscriptions.index', 'icon' => 'calendar'];
 | 
				
			||||||
        }
 | 
					        // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (auth()->guard('contact')->user()->client->getSetting('client_initiated_payments')) {
 | 
					        if (auth()->guard('contact')->user()->client->getSetting('client_initiated_payments')) {
 | 
				
			||||||
            $data[] = ['title' => ctrans('texts.pre_payment'), 'url' => 'client.pre_payments.index', 'icon' => 'dollar-sign'];
 | 
					            $data[] = ['title' => ctrans('texts.pre_payment'), 'url' => 'client.pre_payments.index', 'icon' => 'dollar-sign'];
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										24
									
								
								app/Http/ViewComposers/RotessaComposer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/Http/ViewComposers/RotessaComposer.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\View;
 | 
				
			||||||
 | 
					use App\DataProviders\CAProvinces;
 | 
				
			||||||
 | 
					use App\DataProviders\USStates;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					View::composer(['*.rotessa.components.address','*.rotessa.components.banks.US.bank','*.rotessa.components.dropdowns.country.US'], function ($view) {
 | 
				
			||||||
 | 
					    $states = USStates::get();
 | 
				
			||||||
 | 
					    $view->with('states', $states);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					View::composer(['*.rotessa.components.address','*.rotessa.components.banks.CA.bank','*.rotessa.components.dropdowns.country.CA'], function ($view) {
 | 
				
			||||||
 | 
					    $provinces = CAProvinces::get();
 | 
				
			||||||
 | 
					    $view->with('provinces', $provinces);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										253
									
								
								app/Import/Providers/Quickbooks.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										253
									
								
								app/Import/Providers/Quickbooks.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,253 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Import\Providers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Models\Invoice;
 | 
				
			||||||
 | 
					use App\Factory\ClientFactory;
 | 
				
			||||||
 | 
					use App\Factory\InvoiceFactory;
 | 
				
			||||||
 | 
					use App\Factory\PaymentFactory;
 | 
				
			||||||
 | 
					use App\Factory\ProductFactory;
 | 
				
			||||||
 | 
					use App\Import\ImportException;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Cache;
 | 
				
			||||||
 | 
					use App\Repositories\ClientRepository;
 | 
				
			||||||
 | 
					use App\Repositories\InvoiceRepository;
 | 
				
			||||||
 | 
					use App\Repositories\PaymentRepository;
 | 
				
			||||||
 | 
					use App\Repositories\ProductRepository;
 | 
				
			||||||
 | 
					use App\Http\Requests\Client\StoreClientRequest;
 | 
				
			||||||
 | 
					use App\Http\Requests\Invoice\StoreInvoiceRequest;
 | 
				
			||||||
 | 
					use App\Http\Requests\Payment\StorePaymentRequest;
 | 
				
			||||||
 | 
					use App\Http\Requests\Product\StoreProductRequest;
 | 
				
			||||||
 | 
					use App\Import\Transformer\Quickbooks\ClientTransformer;
 | 
				
			||||||
 | 
					use App\Import\Transformer\Quickbooks\InvoiceTransformer;
 | 
				
			||||||
 | 
					use App\Import\Transformer\Quickbooks\PaymentTransformer;
 | 
				
			||||||
 | 
					use App\Import\Transformer\Quickbooks\ProductTransformer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Quickbooks extends BaseImport
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public array $entity_count = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function import(string $entity)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            in_array($entity, [
 | 
				
			||||||
 | 
					                'client',
 | 
				
			||||||
 | 
					                'invoice',
 | 
				
			||||||
 | 
					                'product',
 | 
				
			||||||
 | 
					                'payment',
 | 
				
			||||||
 | 
					                // 'vendor',
 | 
				
			||||||
 | 
					                // 'expense',
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            $this->{$entity}();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //collate any errors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // $this->finalizeImport();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function client()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $entity_type = 'client';
 | 
				
			||||||
 | 
					        $data = $this->getData($entity_type);
 | 
				
			||||||
 | 
					        if (empty($data)) {
 | 
				
			||||||
 | 
					            $this->entity_count['clients'] = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->request_name = StoreClientRequest::class;
 | 
				
			||||||
 | 
					        $this->repository_name = ClientRepository::class;
 | 
				
			||||||
 | 
					        $this->factory_name = ClientFactory::class;
 | 
				
			||||||
 | 
					        $this->repository = app()->make($this->repository_name);
 | 
				
			||||||
 | 
					        $this->repository->import_mode = true;
 | 
				
			||||||
 | 
					        $this->transformer = new ClientTransformer($this->company);
 | 
				
			||||||
 | 
					        $client_count = $this->ingest($data, $entity_type);
 | 
				
			||||||
 | 
					        $this->entity_count['clients'] = $client_count;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function product()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $entity_type = 'product';
 | 
				
			||||||
 | 
					        $data = $this->getData($entity_type);
 | 
				
			||||||
 | 
					        if (empty($data)) {
 | 
				
			||||||
 | 
					            $this->entity_count['products'] = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->request_name = StoreProductRequest::class;
 | 
				
			||||||
 | 
					        $this->repository_name = ProductRepository::class;
 | 
				
			||||||
 | 
					        $this->factory_name = ProductFactory::class;
 | 
				
			||||||
 | 
					        $this->repository = app()->make($this->repository_name);
 | 
				
			||||||
 | 
					        $this->repository->import_mode = true;
 | 
				
			||||||
 | 
					        $this->transformer = new ProductTransformer($this->company);
 | 
				
			||||||
 | 
					        $count = $this->ingest($data, $entity_type);
 | 
				
			||||||
 | 
					        $this->entity_count['products'] = $count;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getData($type)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // get the data from cache? file? or api ?
 | 
				
			||||||
 | 
					        return json_decode(base64_decode(Cache::get("{$this->hash}-{$type}")), true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function payment()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $entity_type = 'payment';
 | 
				
			||||||
 | 
					        $data = $this->getData($entity_type);
 | 
				
			||||||
 | 
					        if (empty($data)) {
 | 
				
			||||||
 | 
					            $this->entity_count['payments'] = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->request_name = StorePaymentRequest::class;
 | 
				
			||||||
 | 
					        $this->repository_name = PaymentRepository::class;
 | 
				
			||||||
 | 
					        $this->factory_name = PaymentFactory::class;
 | 
				
			||||||
 | 
					        $this->repository = app()->make($this->repository_name);
 | 
				
			||||||
 | 
					        $this->repository->import_mode = true;
 | 
				
			||||||
 | 
					        $this->transformer = new PaymentTransformer($this->company);
 | 
				
			||||||
 | 
					        $count = $this->ingest($data, $entity_type);
 | 
				
			||||||
 | 
					        $this->entity_count['payments'] = $count;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function invoice()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        //make sure we update and create products
 | 
				
			||||||
 | 
					        $initial_update_products_value = $this->company->update_products;
 | 
				
			||||||
 | 
					        $this->company->update_products = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->company->save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $entity_type = 'invoice';
 | 
				
			||||||
 | 
					        $data = $this->getData($entity_type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (empty($data)) {
 | 
				
			||||||
 | 
					            $this->entity_count['invoices'] = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->request_name = StoreInvoiceRequest::class;
 | 
				
			||||||
 | 
					        $this->repository_name = InvoiceRepository::class;
 | 
				
			||||||
 | 
					        $this->factory_name = InvoiceFactory::class;
 | 
				
			||||||
 | 
					        $this->repository = app()->make($this->repository_name);
 | 
				
			||||||
 | 
					        $this->repository->import_mode = true;
 | 
				
			||||||
 | 
					        $this->transformer = new InvoiceTransformer($this->company);
 | 
				
			||||||
 | 
					        $invoice_count = $this->ingestInvoices($data, '');
 | 
				
			||||||
 | 
					        $this->entity_count['invoices'] = $invoice_count;
 | 
				
			||||||
 | 
					        $this->company->update_products = $initial_update_products_value;
 | 
				
			||||||
 | 
					        $this->company->save();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function ingestInvoices($invoices, $invoice_number_key)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $count = 0;
 | 
				
			||||||
 | 
					        $invoice_transformer = $this->transformer;
 | 
				
			||||||
 | 
					        /** @var ClientRepository $client_repository */
 | 
				
			||||||
 | 
					        $client_repository = app()->make(ClientRepository::class);
 | 
				
			||||||
 | 
					        $client_repository->import_mode = true;
 | 
				
			||||||
 | 
					        $invoice_repository = new InvoiceRepository();
 | 
				
			||||||
 | 
					        $invoice_repository->import_mode = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        foreach ($invoices as $raw_invoice) {
 | 
				
			||||||
 | 
					            if(!is_array($raw_invoice)) {
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                $invoice_data = $invoice_transformer->transform($raw_invoice);
 | 
				
			||||||
 | 
					                $invoice_data['user_id'] = $this->company->owner()->id;
 | 
				
			||||||
 | 
					                $invoice_data['line_items'] = (array) $invoice_data['line_items'];
 | 
				
			||||||
 | 
					                $invoice_data['line_items'] = $this->cleanItems(
 | 
				
			||||||
 | 
					                    $invoice_data['line_items'] ?? []
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (
 | 
				
			||||||
 | 
					                    empty($invoice_data['client_id']) &&
 | 
				
			||||||
 | 
					                    ! empty($invoice_data['client'])
 | 
				
			||||||
 | 
					                ) {
 | 
				
			||||||
 | 
					                    $client_data = $invoice_data['client'];
 | 
				
			||||||
 | 
					                    $client_data['user_id'] = $this->getUserIDForRecord(
 | 
				
			||||||
 | 
					                        $invoice_data
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    $client_repository->save(
 | 
				
			||||||
 | 
					                        $client_data,
 | 
				
			||||||
 | 
					                        $client = ClientFactory::create(
 | 
				
			||||||
 | 
					                            $this->company->id,
 | 
				
			||||||
 | 
					                            $client_data['user_id']
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    $invoice_data['client_id'] = $client->id;
 | 
				
			||||||
 | 
					                    unset($invoice_data['client']);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                $validator = $this->request_name::runFormRequest($invoice_data);
 | 
				
			||||||
 | 
					                if ($validator->fails()) {
 | 
				
			||||||
 | 
					                    $this->error_array['invoice'][] = [
 | 
				
			||||||
 | 
					                        'invoice' => $invoice_data,
 | 
				
			||||||
 | 
					                        'error' => $validator->errors()->all(),
 | 
				
			||||||
 | 
					                    ];
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    if(!Invoice::where('number', $invoice_data['number'])->first()) {
 | 
				
			||||||
 | 
					                        $invoice = InvoiceFactory::create(
 | 
				
			||||||
 | 
					                            $this->company->id,
 | 
				
			||||||
 | 
					                            $this->company->owner()->id
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                        $invoice->mergeFillable(['partial','amount','balance','line_items']);
 | 
				
			||||||
 | 
					                        if (! empty($invoice_data['status_id'])) {
 | 
				
			||||||
 | 
					                            $invoice->status_id = $invoice_data['status_id'];
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        $saveable_invoice_data = $invoice_data;
 | 
				
			||||||
 | 
					                        if(array_key_exists('payments', $saveable_invoice_data)) {
 | 
				
			||||||
 | 
					                            unset($saveable_invoice_data['payments']);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        $invoice->fill($saveable_invoice_data);
 | 
				
			||||||
 | 
					                        $invoice->save();
 | 
				
			||||||
 | 
					                        $count++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    // $this->actionInvoiceStatus(
 | 
				
			||||||
 | 
					                    //     $invoice,
 | 
				
			||||||
 | 
					                    //     $invoice_data,
 | 
				
			||||||
 | 
					                    //     $invoice_repository
 | 
				
			||||||
 | 
					                    // );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } catch (\Exception $ex) {
 | 
				
			||||||
 | 
					                if (\DB::connection(config('database.default'))->transactionLevel() > 0) {
 | 
				
			||||||
 | 
					                    \DB::connection(config('database.default'))->rollBack();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if ($ex instanceof ImportException) {
 | 
				
			||||||
 | 
					                    $message = $ex->getMessage();
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    report($ex);
 | 
				
			||||||
 | 
					                    $message = 'Unknown error ';
 | 
				
			||||||
 | 
					                    nlog($ex->getMessage());
 | 
				
			||||||
 | 
					                    nlog($raw_invoice);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                $this->error_array['invoice'][] = [
 | 
				
			||||||
 | 
					                    'invoice' => $raw_invoice,
 | 
				
			||||||
 | 
					                    'error' => $message,
 | 
				
			||||||
 | 
					                ];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $count;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -244,8 +244,7 @@ class Wave extends BaseImport implements ImportInterface
 | 
				
			|||||||
                if (empty($expense_data['vendor_id'])) {
 | 
					                if (empty($expense_data['vendor_id'])) {
 | 
				
			||||||
                    $vendor_data['user_id'] = $this->getUserIDForRecord($expense_data);
 | 
					                    $vendor_data['user_id'] = $this->getUserIDForRecord($expense_data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if(isset($raw_expense['Vendor Name']) || isset($raw_expense['Vendor']))
 | 
					                    if(isset($raw_expense['Vendor Name']) || isset($raw_expense['Vendor'])) {
 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        $vendor_repository->save(
 | 
					                        $vendor_repository->save(
 | 
				
			||||||
                            ['name' => isset($raw_expense['Vendor Name']) ? $raw_expense['Vendor Name'] : isset($raw_expense['Vendor'])],
 | 
					                            ['name' => isset($raw_expense['Vendor Name']) ? $raw_expense['Vendor Name'] : isset($raw_expense['Vendor'])],
 | 
				
			||||||
                            $vendor = VendorFactory::create(
 | 
					                            $vendor = VendorFactory::create(
 | 
				
			||||||
 | 
				
			|||||||
@ -119,8 +119,9 @@ class BaseTransformer
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        $code = array_key_exists($key, $data) ? $data[$key] : false;
 | 
					        $code = array_key_exists($key, $data) ? $data[$key] : false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(!$code)
 | 
					        if(!$code) {
 | 
				
			||||||
            return $this->company->settings->currency_id;
 | 
					            return $this->company->settings->currency_id;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /** @var \Illuminate\Support\Collection<\App\Models\Currency> */
 | 
					        /** @var \Illuminate\Support\Collection<\App\Models\Currency> */
 | 
				
			||||||
        $currencies = app('currencies');
 | 
					        $currencies = app('currencies');
 | 
				
			||||||
 | 
				
			|||||||
@ -38,12 +38,13 @@ class ExpenseTransformer extends BaseTransformer
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        $tax_rate = $total_tax > 0 ? round(($total_tax / $amount) * 100, 3) : 0;
 | 
					        $tax_rate = $total_tax > 0 ? round(($total_tax / $amount) * 100, 3) : 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(isset($data['Notes / Memo']) && strlen($data['Notes / Memo']) > 1)
 | 
					        if(isset($data['Notes / Memo']) && strlen($data['Notes / Memo']) > 1) {
 | 
				
			||||||
            $public_notes = $data['Notes / Memo'];
 | 
					            $public_notes = $data['Notes / Memo'];
 | 
				
			||||||
        elseif (isset($data['Transaction Description']) && strlen($data['Transaction Description']) > 1)
 | 
					        } elseif (isset($data['Transaction Description']) && strlen($data['Transaction Description']) > 1) {
 | 
				
			||||||
            $public_notes = $data['Transaction Description'];
 | 
					            $public_notes = $data['Transaction Description'];
 | 
				
			||||||
        else
 | 
					        } else {
 | 
				
			||||||
            $public_notes = '';
 | 
					            $public_notes = '';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $transformed = [
 | 
					        $transformed = [
 | 
				
			||||||
 | 
				
			|||||||
@ -695,7 +695,7 @@ class CompanyExport implements ShouldQueue
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        $url = Cache::get($this->hash);
 | 
					        $url = Cache::get($this->hash);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Cache::put($this->hash, $storage_path, now()->addHour());
 | 
					        Cache::put($this->hash, $storage_path, 3600);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        App::forgetInstance('translator');
 | 
					        App::forgetInstance('translator');
 | 
				
			||||||
        $t = app('translator');
 | 
					        $t = app('translator');
 | 
				
			||||||
 | 
				
			|||||||
@ -114,8 +114,7 @@ class RecurringExpensesCron
 | 
				
			|||||||
            $exchange_rate = new CurrencyApi();
 | 
					            $exchange_rate = new CurrencyApi();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            $expense->exchange_rate = $exchange_rate->exchangeRate($expense->currency_id, (int)$expense->company->settings->currency_id, Carbon::parse($expense->date));
 | 
					            $expense->exchange_rate = $exchange_rate->exchangeRate($expense->currency_id, (int)$expense->company->settings->currency_id, Carbon::parse($expense->date));
 | 
				
			||||||
        }
 | 
					        } else {
 | 
				
			||||||
        else {
 | 
					 | 
				
			||||||
            $expense->exchange_rate = 1;
 | 
					            $expense->exchange_rate = 1;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -48,12 +48,12 @@ class RecurringInvoicesCron
 | 
				
			|||||||
        Auth::logout();
 | 
					        Auth::logout();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (! config('ninja.db.multi_db_enabled')) {
 | 
					        if (! config('ninja.db.multi_db_enabled')) {
 | 
				
			||||||
            $recurring_invoices = RecurringInvoice::query()->where('recurring_invoices.status_id', RecurringInvoice::STATUS_ACTIVE)
 | 
					            $recurring_invoices = RecurringInvoice::query()->where('status_id', RecurringInvoice::STATUS_ACTIVE)
 | 
				
			||||||
                                                        ->where('recurring_invoices.is_deleted', false)
 | 
					                                                        ->where('is_deleted', false)
 | 
				
			||||||
                                                        ->where('recurring_invoices.remaining_cycles', '!=', '0')
 | 
					                                                        ->where('remaining_cycles', '!=', '0')
 | 
				
			||||||
                                                        ->whereNotNull('recurring_invoices.next_send_date')
 | 
					                                                        ->whereNotNull('next_send_date')
 | 
				
			||||||
                                                        ->whereNull('recurring_invoices.deleted_at')
 | 
					                                                        ->whereNull('deleted_at')
 | 
				
			||||||
                                                        ->where('recurring_invoices.next_send_date', '<=', now()->toDateTimeString())
 | 
					                                                        ->where('next_send_date', '<=', now()->toDateTimeString())
 | 
				
			||||||
                                                        ->whereHas('client', function ($query) {
 | 
					                                                        ->whereHas('client', function ($query) {
 | 
				
			||||||
                                                            $query->where('is_deleted', 0)
 | 
					                                                            $query->where('is_deleted', 0)
 | 
				
			||||||
                                                                   ->where('deleted_at', null);
 | 
					                                                                   ->where('deleted_at', null);
 | 
				
			||||||
@ -87,27 +87,18 @@ class RecurringInvoicesCron
 | 
				
			|||||||
            foreach (MultiDB::$dbs as $db) {
 | 
					            foreach (MultiDB::$dbs as $db) {
 | 
				
			||||||
                MultiDB::setDB($db);
 | 
					                MultiDB::setDB($db);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                $recurring_invoices = RecurringInvoice::query()->where('recurring_invoices.status_id', RecurringInvoice::STATUS_ACTIVE)
 | 
					                $recurring_invoices = RecurringInvoice::query()->where('status_id', RecurringInvoice::STATUS_ACTIVE)
 | 
				
			||||||
                                                        ->where('recurring_invoices.is_deleted', false)
 | 
					                                                        ->where('is_deleted', false)
 | 
				
			||||||
                                                        ->where('recurring_invoices.remaining_cycles', '!=', '0')
 | 
					                                                        ->where('remaining_cycles', '!=', '0')
 | 
				
			||||||
                                                        ->whereNull('recurring_invoices.deleted_at')
 | 
					                                                        ->whereNull('deleted_at')
 | 
				
			||||||
                                                        ->whereNotNull('recurring_invoices.next_send_date')
 | 
					                                                        ->whereNotNull('next_send_date')
 | 
				
			||||||
                                                        ->where('recurring_invoices.next_send_date', '<=', now()->toDateTimeString())
 | 
					                                                        ->where('next_send_date', '<=', now()->toDateTimeString())
 | 
				
			||||||
                                                        // ->whereHas('client', function ($query) {
 | 
					                                                        ->whereHas('client', function ($query) {
 | 
				
			||||||
                                                        //     $query->where('is_deleted', 0)
 | 
					                                                            $query->where('is_deleted', 0)
 | 
				
			||||||
                                                        //            ->where('deleted_at', null);
 | 
					                                                                   ->where('deleted_at', null);
 | 
				
			||||||
                                                        // })
 | 
					 | 
				
			||||||
                                                        // ->whereHas('company', function ($query) {
 | 
					 | 
				
			||||||
                                                        //     $query->where('is_disabled', 0);
 | 
					 | 
				
			||||||
                                                        // })
 | 
					 | 
				
			||||||
                                                        ->leftJoin('clients', function ($join) {
 | 
					 | 
				
			||||||
                                                            $join->on('recurring_invoices.client_id', '=', 'clients.id')
 | 
					 | 
				
			||||||
                                                                ->where('clients.is_deleted', 0)
 | 
					 | 
				
			||||||
                                                                ->whereNull('clients.deleted_at');
 | 
					 | 
				
			||||||
                                                        })
 | 
					                                                        })
 | 
				
			||||||
                                                        ->leftJoin('companies', function ($join) {
 | 
					                                                        ->whereHas('company', function ($query) {
 | 
				
			||||||
                                                            $join->on('recurring_invoices.company_id', '=', 'companies.id')
 | 
					                                                            $query->where('is_disabled', 0);
 | 
				
			||||||
                                                                ->where('companies.is_disabled', 0);
 | 
					 | 
				
			||||||
                                                        })
 | 
					                                                        })
 | 
				
			||||||
                                                        ->with('company')
 | 
					                                                        ->with('company')
 | 
				
			||||||
                                                        ->cursor();
 | 
					                                                        ->cursor();
 | 
				
			||||||
 | 
				
			|||||||
@ -1,105 +0,0 @@
 | 
				
			|||||||
<?php
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Invoice Ninja (https://invoiceninja.com).
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @license https://www.elastic.co/licensing/elastic-license
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace App\Jobs\Cron;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use App\Libraries\MultiDB;
 | 
					 | 
				
			||||||
use App\Models\Project;
 | 
					 | 
				
			||||||
use Illuminate\Foundation\Bus\Dispatchable;
 | 
					 | 
				
			||||||
use Illuminate\Support\Facades\Auth;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class UpdateCalculatedFields
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    use Dispatchable;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Create a new job instance.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return void
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function __construct()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Execute the job.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return void
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function handle(): void
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        nlog("Updating calculated fields");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Auth::logout();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (! config('ninja.db.multi_db_enabled')) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Project::query()->with('tasks')->whereHas('tasks', function ($query) {
 | 
					 | 
				
			||||||
                $query->where('updated_at', '>', now()->subHours(2));
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
                ->cursor()
 | 
					 | 
				
			||||||
                ->each(function ($project) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    $project->current_hours = $this->calculateDuration($project);
 | 
					 | 
				
			||||||
                    $project->save();
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            //multiDB environment, need to
 | 
					 | 
				
			||||||
            foreach (MultiDB::$dbs as $db) {
 | 
					 | 
				
			||||||
                MultiDB::setDB($db);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Project::query()->with('tasks')->whereHas('tasks', function ($query) {
 | 
					 | 
				
			||||||
                    $query->where('updated_at', '>', now()->subHours(2));
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                ->cursor()
 | 
					 | 
				
			||||||
                ->each(function ($project) {
 | 
					 | 
				
			||||||
                    $project->current_hours = $this->calculateDuration($project);
 | 
					 | 
				
			||||||
                    $project->save();
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                //Clean password resets table
 | 
					 | 
				
			||||||
                \DB::connection($db)->table('password_resets')->where('created_at', '<', now()->subHour())->delete();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private function calculateDuration($project): int
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        $duration = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $project->tasks->each(function ($task) use (&$duration) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if(is_iterable(json_decode($task->time_log))) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                foreach(json_decode($task->time_log) as $log) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if(!is_array($log))
 | 
					 | 
				
			||||||
                        continue;
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    $start_time = $log[0];
 | 
					 | 
				
			||||||
                    $end_time = $log[1] == 0 ? time() : $log[1];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    $duration += $end_time - $start_time;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return (int) round(($duration / 60 / 60), 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -11,7 +11,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Jobs\EDocument;
 | 
					namespace App\Jobs\EDocument;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use App\Services\EDocument\Standards\RoEInvoice;
 | 
					 | 
				
			||||||
use App\Utils\Ninja;
 | 
					use App\Utils\Ninja;
 | 
				
			||||||
use App\Models\Quote;
 | 
					use App\Models\Quote;
 | 
				
			||||||
use App\Models\Credit;
 | 
					use App\Models\Credit;
 | 
				
			||||||
@ -23,10 +22,12 @@ use Illuminate\Queue\SerializesModels;
 | 
				
			|||||||
use Illuminate\Queue\InteractsWithQueue;
 | 
					use Illuminate\Queue\InteractsWithQueue;
 | 
				
			||||||
use Illuminate\Contracts\Queue\ShouldQueue;
 | 
					use Illuminate\Contracts\Queue\ShouldQueue;
 | 
				
			||||||
use Illuminate\Foundation\Bus\Dispatchable;
 | 
					use Illuminate\Foundation\Bus\Dispatchable;
 | 
				
			||||||
 | 
					use App\Services\EDocument\Standards\Peppol;
 | 
				
			||||||
use horstoeko\zugferd\ZugferdDocumentBuilder;
 | 
					use horstoeko\zugferd\ZugferdDocumentBuilder;
 | 
				
			||||||
 | 
					use App\Services\EDocument\Standards\FatturaPA;
 | 
				
			||||||
 | 
					use App\Services\EDocument\Standards\RoEInvoice;
 | 
				
			||||||
use App\Services\EDocument\Standards\OrderXDocument;
 | 
					use App\Services\EDocument\Standards\OrderXDocument;
 | 
				
			||||||
use App\Services\EDocument\Standards\FacturaEInvoice;
 | 
					use App\Services\EDocument\Standards\FacturaEInvoice;
 | 
				
			||||||
use App\Services\EDocument\Standards\FatturaPA;
 | 
					 | 
				
			||||||
use App\Services\EDocument\Standards\ZugferdEDokument;
 | 
					use App\Services\EDocument\Standards\ZugferdEDokument;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CreateEDocument implements ShouldQueue
 | 
					class CreateEDocument implements ShouldQueue
 | 
				
			||||||
@ -68,6 +69,8 @@ class CreateEDocument implements ShouldQueue
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if ($this->document instanceof Invoice) {
 | 
					        if ($this->document instanceof Invoice) {
 | 
				
			||||||
            switch ($e_document_type) {
 | 
					            switch ($e_document_type) {
 | 
				
			||||||
 | 
					                case "PEPPOL":
 | 
				
			||||||
 | 
					                    return (new Peppol($this->document))->toXml();
 | 
				
			||||||
                case "FACT1":
 | 
					                case "FACT1":
 | 
				
			||||||
                    return (new RoEInvoice($this->document))->generateXml();
 | 
					                    return (new RoEInvoice($this->document))->generateXml();
 | 
				
			||||||
                case "FatturaPA":
 | 
					                case "FatturaPA":
 | 
				
			||||||
 | 
				
			|||||||
@ -15,11 +15,14 @@ use App\Models\Expense;
 | 
				
			|||||||
use App\Services\EDocument\Imports\ParseEDocument;
 | 
					use App\Services\EDocument\Imports\ParseEDocument;
 | 
				
			||||||
use App\Utils\TempFile;
 | 
					use App\Utils\TempFile;
 | 
				
			||||||
use Exception;
 | 
					use Exception;
 | 
				
			||||||
 | 
					use App\Models\Company;
 | 
				
			||||||
use Illuminate\Bus\Queueable;
 | 
					use Illuminate\Bus\Queueable;
 | 
				
			||||||
use Illuminate\Queue\SerializesModels;
 | 
					use Illuminate\Queue\SerializesModels;
 | 
				
			||||||
use Illuminate\Queue\InteractsWithQueue;
 | 
					use Illuminate\Queue\InteractsWithQueue;
 | 
				
			||||||
use Illuminate\Contracts\Queue\ShouldQueue;
 | 
					use Illuminate\Contracts\Queue\ShouldQueue;
 | 
				
			||||||
use Illuminate\Foundation\Bus\Dispatchable;
 | 
					use Illuminate\Foundation\Bus\Dispatchable;
 | 
				
			||||||
 | 
					use Illuminate\Queue\Middleware\WithoutOverlapping;
 | 
				
			||||||
 | 
					use App\Services\EDocument\Imports\ZugferdEDocument;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ImportEDocument implements ShouldQueue
 | 
					class ImportEDocument implements ShouldQueue
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -28,9 +31,9 @@ class ImportEDocument implements ShouldQueue
 | 
				
			|||||||
    use Queueable;
 | 
					    use Queueable;
 | 
				
			||||||
    use SerializesModels;
 | 
					    use SerializesModels;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public $deleteWhenMissingModels = true;
 | 
					    public $tries = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function __construct(private readonly string $file_content, private string $file_name, private string $file_mime_type)
 | 
					    public function __construct(private readonly string $file_content, private string $file_name, private string $file_mime_type, private Company $company)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -46,7 +49,21 @@ class ImportEDocument implements ShouldQueue
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        $file = TempFile::UploadedFileFromRaw($this->file_content, $this->file_name, $this->file_mime_type);
 | 
					        $file = TempFile::UploadedFileFromRaw($this->file_content, $this->file_name, $this->file_mime_type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return (new ParseEDocument($file))->run();
 | 
					        return (new ParseEDocument($file, $this->company))->run();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function middleware()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [new WithoutOverlapping($this->company->company_key)];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function failed($exception = null)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if ($exception) {
 | 
				
			||||||
 | 
					            nlog("EXCEPTION:: ImportEDocument:: " . $exception->getMessage());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        config(['queue.failed.driver' => null]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										48
									
								
								app/Jobs/Import/QuickbooksIngest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								app/Jobs/Import/QuickbooksIngest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Jobs\Import;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Libraries\MultiDB;
 | 
				
			||||||
 | 
					use Illuminate\Bus\Queueable;
 | 
				
			||||||
 | 
					use App\Import\Providers\Quickbooks;
 | 
				
			||||||
 | 
					use Illuminate\Queue\SerializesModels;
 | 
				
			||||||
 | 
					use Illuminate\Queue\InteractsWithQueue;
 | 
				
			||||||
 | 
					use Illuminate\Contracts\Queue\ShouldQueue;
 | 
				
			||||||
 | 
					use Illuminate\Foundation\Bus\Dispatchable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class QuickbooksIngest implements ShouldQueue
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    use Dispatchable;
 | 
				
			||||||
 | 
					    use InteractsWithQueue;
 | 
				
			||||||
 | 
					    use Queueable;
 | 
				
			||||||
 | 
					    use SerializesModels;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected $engine;
 | 
				
			||||||
 | 
					    protected $request;
 | 
				
			||||||
 | 
					    protected $company;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Create a new job instance.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function __construct(array $request, $company)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->company = $company;
 | 
				
			||||||
 | 
					        $this->request = $request;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Execute the job.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function handle(): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        MultiDB::setDb($this->company->db);
 | 
				
			||||||
 | 
					        set_time_limit(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $engine = new Quickbooks(['import_type' => 'client', 'hash' => $this->request['hash'] ], $this->company);
 | 
				
			||||||
 | 
					        foreach ($this->request['import_types'] as $entity) {
 | 
				
			||||||
 | 
					            $engine->import($entity);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $engine->finalizeImport();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -254,7 +254,7 @@ class NinjaMailerJob implements ShouldQueue
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private function incrementEmailCounter(): void
 | 
					    private function incrementEmailCounter(): void
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if(in_array($this->mailer, ['default','mailgun','postmark'])) {
 | 
					        if(in_array($this->nmo->settings->email_sending_method, ['default','mailgun','postmark'])) {
 | 
				
			||||||
            Cache::increment("email_quota".$this->company->account->key);
 | 
					            Cache::increment("email_quota".$this->company->account->key);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -298,7 +298,7 @@ class NinjaMailerJob implements ShouldQueue
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        /** Force free/trials onto specific mail driver */
 | 
					        /** Force free/trials onto specific mail driver */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if($this->mailer == 'default' && $this->company->account->isNewHostedAccount()) {
 | 
					        if($this->nmo->settings->email_sending_method == 'default' && $this->company->account->isNewHostedAccount()) {
 | 
				
			||||||
            $this->mailer = 'mailgun';
 | 
					            $this->mailer = 'mailgun';
 | 
				
			||||||
            $this->setHostedMailgunMailer();
 | 
					            $this->setHostedMailgunMailer();
 | 
				
			||||||
            return $this;
 | 
					            return $this;
 | 
				
			||||||
 | 
				
			|||||||
@ -94,7 +94,9 @@ class ProcessMailgunWebhook implements ShouldQueue
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        MultiDB::findAndSetDbByCompanyKey($this->request['event-data']['tags'][0]);
 | 
					        MultiDB::findAndSetDbByCompanyKey($this->request['event-data']['tags'][0]);
 | 
				
			||||||
        $company = Company::query()->where('company_key', $this->request['event-data']['tags'][0])->first();
 | 
					
 | 
				
			||||||
 | 
					        /** @var \App\Models\Company $company */
 | 
				
			||||||
 | 
					        $company = Company::where('company_key', $this->request['event-data']['tags'][0])->first();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ($company && $this->request['event-data']['event'] == 'complained' && config('ninja.notification.slack')) {
 | 
					        if ($company && $this->request['event-data']['event'] == 'complained' && config('ninja.notification.slack')) {
 | 
				
			||||||
            $company->notification(new EmailSpamNotification($company))->ninja();
 | 
					            $company->notification(new EmailSpamNotification($company))->ninja();
 | 
				
			||||||
@ -195,7 +197,7 @@ class ProcessMailgunWebhook implements ShouldQueue
 | 
				
			|||||||
            'date' => \Carbon\Carbon::parse($this->request['event-data']['timestamp'])->format('Y-m-d H:i:s') ?? '',
 | 
					            'date' => \Carbon\Carbon::parse($this->request['event-data']['timestamp'])->format('Y-m-d H:i:s') ?? '',
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if($sl) {
 | 
					        if($sl instanceof SystemLog) {
 | 
				
			||||||
            $data = $sl->log;
 | 
					            $data = $sl->log;
 | 
				
			||||||
            $data['history']['events'][] = $event;
 | 
					            $data['history']['events'][] = $event;
 | 
				
			||||||
            $this->updateSystemLog($sl, $data);
 | 
					            $this->updateSystemLog($sl, $data);
 | 
				
			||||||
 | 
				
			|||||||
@ -79,7 +79,7 @@ class AdjustEmailQuota implements ShouldQueue
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        /** Use redis pipelines to execute bulk deletes efficiently */
 | 
					        /** Use redis pipelines to execute bulk deletes efficiently */
 | 
				
			||||||
        $redis = Redis::connection('sentinel-cache');
 | 
					        $redis = Redis::connection('sentinel-cache');
 | 
				
			||||||
        $prefix =  config('cache.prefix'). ":email_quota*";
 | 
					        $prefix =  config('cache.prefix'). "email_quota*";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $keys = $redis->keys($prefix);
 | 
					        $keys = $redis->keys($prefix);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -92,7 +92,7 @@ class AdjustEmailQuota implements ShouldQueue
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        $keys = null;
 | 
					        $keys = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $prefix =  config('cache.prefix'). ":throttle_notified*";
 | 
					        $prefix =  config('cache.prefix'). "throttle_notified*";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $keys = $redis->keys($prefix);
 | 
					        $keys = $redis->keys($prefix);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -58,10 +58,6 @@ class EmailPayment implements ShouldQueue
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public function handle()
 | 
					    public function handle()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if ($this->company->is_disabled || (!$this->contact?->email ?? false)) {
 | 
					 | 
				
			||||||
            nlog("company disabled - or - contact email not found");
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        MultiDB::setDb($this->company->db);
 | 
					        MultiDB::setDb($this->company->db);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -71,6 +67,11 @@ class EmailPayment implements ShouldQueue
 | 
				
			|||||||
            $this->contact = $this->payment->client->contacts()->orderBy('is_primary', 'desc')->first();
 | 
					            $this->contact = $this->payment->client->contacts()->orderBy('is_primary', 'desc')->first();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($this->company->is_disabled || (!$this->contact?->email ?? false)) {
 | 
				
			||||||
 | 
					            nlog("company disabled - or - contact email not found");
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->contact->load('client');
 | 
					        $this->contact->load('client');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $email_builder = (new PaymentEmailEngine($this->payment, $this->contact))->build();
 | 
					        $email_builder = (new PaymentEmailEngine($this->payment, $this->contact))->build();
 | 
				
			||||||
 | 
				
			|||||||
@ -176,8 +176,8 @@ class SendRecurring implements ShouldQueue
 | 
				
			|||||||
    private function createRecurringInvitations($invoice): Invoice
 | 
					    private function createRecurringInvitations($invoice): Invoice
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if ($this->recurring_invoice->invitations->count() == 0) {
 | 
					        if ($this->recurring_invoice->invitations->count() == 0) {
 | 
				
			||||||
            $this->recurring_invoice->service()->createInvitations()->save();
 | 
					            $this->recurring_invoice = $this->recurring_invoice->service()->createInvitations()->save();
 | 
				
			||||||
            $this->recurring_invoice = $this->recurring_invoice->fresh();
 | 
					            // $this->recurring_invoice = $this->recurring_invoice->fresh();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->recurring_invoice->invitations->each(function ($recurring_invitation) use ($invoice) {
 | 
					        $this->recurring_invoice->invitations->each(function ($recurring_invitation) use ($invoice) {
 | 
				
			||||||
 | 
				
			|||||||
@ -96,6 +96,8 @@ class CleanStaleInvoiceOrder implements ShouldQueue
 | 
				
			|||||||
                    $invoice->service()->removeUnpaidGatewayFees();
 | 
					                    $invoice->service()->removeUnpaidGatewayFees();
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            \DB::connection($db)->table('password_resets')->where('created_at', '<', now()->subHours(12))->delete();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -48,8 +48,7 @@ class TaskAssigned implements ShouldQueue
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        $company_user = $this->task->assignedCompanyUser();
 | 
					        $company_user = $this->task->assignedCompanyUser();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(($company_user instanceof CompanyUser) && $this->findEntityAssignedNotification($company_user, 'task'))
 | 
					        if(($company_user instanceof CompanyUser) && $this->findEntityAssignedNotification($company_user, 'task')) {
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            $mo = new EmailObject();
 | 
					            $mo = new EmailObject();
 | 
				
			||||||
            $mo->subject = ctrans('texts.task_assigned_subject', ['task' => $this->task->number, 'date' => now()->setTimeZone($this->task->company->timezone()->name)->format($this->task->company->date_format()) ]);
 | 
					            $mo->subject = ctrans('texts.task_assigned_subject', ['task' => $this->task->number, 'date' => now()->setTimeZone($this->task->company->timezone()->name)->format($this->task->company->date_format()) ]);
 | 
				
			||||||
            $mo->body = ctrans('texts.task_assigned_body', ['task' => $this->task->number, 'description' => $this->task->description ?? '', 'client' => $this->task->client ? $this->task->client->present()->name() : ' ']);
 | 
					            $mo->body = ctrans('texts.task_assigned_body', ['task' => $this->task->number, 'description' => $this->task->description ?? '', 'client' => $this->task->client ? $this->task->client->present()->name() : ' ']);
 | 
				
			||||||
 | 
				
			|||||||
@ -61,10 +61,10 @@ class QuoteReminderJob implements ShouldQueue
 | 
				
			|||||||
            nrlog("Sending quote reminders on ".now()->format('Y-m-d h:i:s'));
 | 
					            nrlog("Sending quote reminders on ".now()->format('Y-m-d h:i:s'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Quote::query()
 | 
					            Quote::query()
 | 
				
			||||||
                 ->where('quotes.is_deleted', 0)
 | 
					                 ->where('is_deleted', 0)
 | 
				
			||||||
                 ->whereIn('quotes.status_id', [Invoice::STATUS_SENT])
 | 
					                 ->whereIn('status_id', [Invoice::STATUS_SENT])
 | 
				
			||||||
                 ->whereNull('quotes.deleted_at')
 | 
					                 ->whereNull('deleted_at')
 | 
				
			||||||
                 ->where('quotes.next_send_date', '<=', now()->toDateTimeString())
 | 
					                 ->where('next_send_date', '<=', now()->toDateTimeString())
 | 
				
			||||||
                 ->whereHas('client', function ($query) {
 | 
					                 ->whereHas('client', function ($query) {
 | 
				
			||||||
                     $query->where('is_deleted', 0)
 | 
					                     $query->where('is_deleted', 0)
 | 
				
			||||||
                           ->where('deleted_at', null);
 | 
					                           ->where('deleted_at', null);
 | 
				
			||||||
@ -88,10 +88,10 @@ class QuoteReminderJob implements ShouldQueue
 | 
				
			|||||||
                nrlog("Sending quote reminders on db {$db} ".now()->format('Y-m-d h:i:s'));
 | 
					                nrlog("Sending quote reminders on db {$db} ".now()->format('Y-m-d h:i:s'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                Quote::query()
 | 
					                Quote::query()
 | 
				
			||||||
                     ->where('quotes.is_deleted', 0)
 | 
					                     ->where('is_deleted', 0)
 | 
				
			||||||
                     ->whereIn('quotes.status_id', [Invoice::STATUS_SENT])
 | 
					                     ->whereIn('status_id', [Invoice::STATUS_SENT])
 | 
				
			||||||
                     ->whereNull('quotes.deleted_at')
 | 
					                     ->whereNull('deleted_at')
 | 
				
			||||||
                     ->where('quotes.next_send_date', '<=', now()->toDateTimeString())
 | 
					                     ->where('next_send_date', '<=', now()->toDateTimeString())
 | 
				
			||||||
                     ->whereHas('client', function ($query) {
 | 
					                     ->whereHas('client', function ($query) {
 | 
				
			||||||
                         $query->where('is_deleted', 0)
 | 
					                         $query->where('is_deleted', 0)
 | 
				
			||||||
                               ->where('deleted_at', null);
 | 
					                               ->where('deleted_at', null);
 | 
				
			||||||
 | 
				
			|||||||
@ -59,7 +59,6 @@ class Register extends Component
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public function register(array $data)
 | 
					    public function register(array $data)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        $service = new ClientRegisterService(
 | 
					        $service = new ClientRegisterService(
 | 
				
			||||||
            company: $this->subscription->company,
 | 
					            company: $this->subscription->company,
 | 
				
			||||||
            additional: $this->additional_fields,
 | 
					            additional: $this->additional_fields,
 | 
				
			||||||
 | 
				
			|||||||
@ -349,20 +349,24 @@ class BillingPortalPurchase extends Component
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ((int)$this->price == 0) {
 | 
					        if ((int)$this->price == 0) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            $this->steps['payment_required'] = false;
 | 
					            $this->steps['payment_required'] = false;
 | 
				
			||||||
 | 
					            $this->steps['fetched_payment_methods'] = false;
 | 
				
			||||||
 | 
					            $this->heading_text = ctrans('texts.payment_methods');
 | 
				
			||||||
 | 
					            return $this;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            // $this->steps['fetched_payment_methods'] = true;
 | 
					            // $this->steps['fetched_payment_methods'] = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->methods = $contact->client->service()->getPaymentMethods($this->price);
 | 
					        $this->methods = $contact->client->service()->getPaymentMethods($this->price);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        foreach($this->methods as $method){
 | 
					        $method_values = array_column($this->methods, 'is_paypal');
 | 
				
			||||||
 | 
					        $is_paypal = in_array('1', $method_values);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if($method['is_paypal'] == '1' && !$this->steps['check_rff']){
 | 
					        if($is_paypal && !$this->steps['check_rff']) {
 | 
				
			||||||
            $this->rff();
 | 
					            $this->rff();
 | 
				
			||||||
                break;
 | 
					        } elseif(!$this->steps['check_rff']) {
 | 
				
			||||||
            }
 | 
					            $this->steps['fetched_payment_methods'] = true;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->heading_text = ctrans('texts.payment_methods');
 | 
					        $this->heading_text = ctrans('texts.payment_methods');
 | 
				
			||||||
 | 
				
			|||||||
@ -484,7 +484,7 @@ class BillingPortalPurchasev2 extends Component
 | 
				
			|||||||
            $this->methods = [];
 | 
					            $this->methods = [];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ($this->contact && $this->float_amount_total >= 1) {
 | 
					        if ($this->contact && $this->float_amount_total >= 0) {
 | 
				
			||||||
            $this->methods = $this->contact->client->service()->getPaymentMethods($this->float_amount_total);
 | 
					            $this->methods = $this->contact->client->service()->getPaymentMethods($this->float_amount_total);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -515,8 +515,7 @@ class BillingPortalPurchasev2 extends Component
 | 
				
			|||||||
            strlen($this->contact_email ?? '') == 0 ||
 | 
					            strlen($this->contact_email ?? '') == 0 ||
 | 
				
			||||||
            strlen($this->client_city ?? '') == 0 ||
 | 
					            strlen($this->client_city ?? '') == 0 ||
 | 
				
			||||||
            strlen($this->client_postal_code ?? '') == 0
 | 
					            strlen($this->client_postal_code ?? '') == 0
 | 
				
			||||||
        )
 | 
					        ) {
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            $this->check_rff = true;
 | 
					            $this->check_rff = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										287
									
								
								app/Livewire/Flow2/InvoicePay.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								app/Livewire/Flow2/InvoicePay.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,287 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Livewire\Flow2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Libraries\MultiDB;
 | 
				
			||||||
 | 
					use App\Models\CompanyGateway;
 | 
				
			||||||
 | 
					use App\Models\Invoice;
 | 
				
			||||||
 | 
					use App\Utils\Number;
 | 
				
			||||||
 | 
					use App\Utils\Traits\MakesDates;
 | 
				
			||||||
 | 
					use App\Utils\Traits\MakesHash;
 | 
				
			||||||
 | 
					use App\Utils\Traits\WithSecureContext;
 | 
				
			||||||
 | 
					use Livewire\Attributes\Computed;
 | 
				
			||||||
 | 
					use Livewire\Attributes\On;
 | 
				
			||||||
 | 
					use Livewire\Component;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InvoicePay extends Component
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    use MakesDates;
 | 
				
			||||||
 | 
					    use MakesHash;
 | 
				
			||||||
 | 
					    use WithSecureContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private $mappings = [
 | 
				
			||||||
 | 
					        'client_name' => 'name',
 | 
				
			||||||
 | 
					        'client_website' => 'website',
 | 
				
			||||||
 | 
					        'client_phone' => 'phone',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        'client_address_line_1' => 'address1',
 | 
				
			||||||
 | 
					        'client_address_line_2' => 'address2',
 | 
				
			||||||
 | 
					        'client_city' => 'city',
 | 
				
			||||||
 | 
					        'client_state' => 'state',
 | 
				
			||||||
 | 
					        'client_postal_code' => 'postal_code',
 | 
				
			||||||
 | 
					        'client_country_id' => 'country_id',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        'client_shipping_address_line_1' => 'shipping_address1',
 | 
				
			||||||
 | 
					        'client_shipping_address_line_2' => 'shipping_address2',
 | 
				
			||||||
 | 
					        'client_shipping_city' => 'shipping_city',
 | 
				
			||||||
 | 
					        'client_shipping_state' => 'shipping_state',
 | 
				
			||||||
 | 
					        'client_shipping_postal_code' => 'shipping_postal_code',
 | 
				
			||||||
 | 
					        'client_shipping_country_id' => 'shipping_country_id',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        'client_custom_value1' => 'custom_value1',
 | 
				
			||||||
 | 
					        'client_custom_value2' => 'custom_value2',
 | 
				
			||||||
 | 
					        'client_custom_value3' => 'custom_value3',
 | 
				
			||||||
 | 
					        'client_custom_value4' => 'custom_value4',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        'contact_first_name' => 'first_name',
 | 
				
			||||||
 | 
					        'contact_last_name' => 'last_name',
 | 
				
			||||||
 | 
					        'contact_email' => 'email',
 | 
				
			||||||
 | 
					        // 'contact_phone' => 'phone',
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $client_address_array = [
 | 
				
			||||||
 | 
					        'address1',
 | 
				
			||||||
 | 
					        'address2',
 | 
				
			||||||
 | 
					        'city',
 | 
				
			||||||
 | 
					        'state',
 | 
				
			||||||
 | 
					        'postal_code',
 | 
				
			||||||
 | 
					        'country_id',
 | 
				
			||||||
 | 
					        'shipping_address1',
 | 
				
			||||||
 | 
					        'shipping_address2',
 | 
				
			||||||
 | 
					        'shipping_city',
 | 
				
			||||||
 | 
					        'shipping_state',
 | 
				
			||||||
 | 
					        'shipping_postal_code',
 | 
				
			||||||
 | 
					        'shipping_country_id',
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $invitation_id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $invoices;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $variables;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $db;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $settings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $terms_accepted = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $signature_accepted = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $payment_method_accepted = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $under_over_payment = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $required_fields = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[On('update.context')]
 | 
				
			||||||
 | 
					    public function handleContext(string $property, $value): self
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->setContext(property: $property, value: $value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[On('terms-accepted')]
 | 
				
			||||||
 | 
					    public function termsAccepted()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        nlog("Terms accepted");
 | 
				
			||||||
 | 
					        // $this->invite = \App\Models\InvoiceInvitation::withTrashed()->find($this->invitation_id)->withoutRelations();
 | 
				
			||||||
 | 
					        $this->terms_accepted = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[On('signature-captured')]
 | 
				
			||||||
 | 
					    public function signatureCaptured($base64)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        nlog("signature captured");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->signature_accepted = true;
 | 
				
			||||||
 | 
					        $invite = \App\Models\InvoiceInvitation::withTrashed()->find($this->invitation_id);
 | 
				
			||||||
 | 
					        $invite->signature_base64 = $base64;
 | 
				
			||||||
 | 
					        $invite->signature_date = now()->addSeconds($invite->contact->client->timezone_offset());
 | 
				
			||||||
 | 
					        $this->setContext('signature', $base64); // $this->context['signature'] = $base64;
 | 
				
			||||||
 | 
					        $invite->save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[On('payable-amount')]
 | 
				
			||||||
 | 
					    public function payableAmount($payable_amount)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // $this->setContext('payable_invoices.0.amount', Number::parseFloat($payable_amount)); // $this->context['payable_invoices'][0]['amount'] = Number::parseFloat($payable_amount); //TODO DB: check parseFloat()
 | 
				
			||||||
 | 
					        $this->under_over_payment = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[On('payment-method-selected')]
 | 
				
			||||||
 | 
					    public function paymentMethodSelected($company_gateway_id, $gateway_type_id, $amount)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->setContext('company_gateway_id', $company_gateway_id);
 | 
				
			||||||
 | 
					        $this->setContext('gateway_type_id', $gateway_type_id);
 | 
				
			||||||
 | 
					        $this->setContext('amount', $amount);
 | 
				
			||||||
 | 
					        $this->setContext('pre_payment', false);
 | 
				
			||||||
 | 
					        $this->setContext('is_recurring', false);
 | 
				
			||||||
 | 
					        $this->setContext('invitation_id', $this->invitation_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->payment_method_accepted = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $company_gateway = CompanyGateway::query()->find($company_gateway_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->checkRequiredFields($company_gateway);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[On('required-fields')]
 | 
				
			||||||
 | 
					    public function requiredFieldsFilled()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->required_fields = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private function checkRequiredFields(CompanyGateway $company_gateway)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $fields = $company_gateway->driver()->getClientRequiredFields();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->setContext('fields', $fields); // $this->context['fields'] = $fields;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($company_gateway->always_show_required_fields) {
 | 
				
			||||||
 | 
					            return $this->required_fields = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $contact = $this->getContext()['contact'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        foreach ($fields as $index => $field) {
 | 
				
			||||||
 | 
					            $_field = $this->mappings[$field['name']];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (\Illuminate\Support\Str::startsWith($field['name'], 'client_')) {
 | 
				
			||||||
 | 
					                if (
 | 
				
			||||||
 | 
					                    empty($contact->client->{$_field})
 | 
				
			||||||
 | 
					                    || is_null($contact->client->{$_field})
 | 
				
			||||||
 | 
					                ) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return $this->required_fields = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (\Illuminate\Support\Str::startsWith($field['name'], 'contact_')) {
 | 
				
			||||||
 | 
					                if (empty($contact->{$_field}) || is_null($contact->{$_field}) || str_contains($contact->{$_field}, '@example.com')) {
 | 
				
			||||||
 | 
					                    return $this->required_fields = true;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $this->required_fields = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[Computed()]
 | 
				
			||||||
 | 
					    public function component(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (!$this->terms_accepted) {
 | 
				
			||||||
 | 
					            return Terms::class;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!$this->signature_accepted) {
 | 
				
			||||||
 | 
					            return Signature::class;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($this->under_over_payment) {
 | 
				
			||||||
 | 
					            return UnderOverPayment::class;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!$this->payment_method_accepted) {
 | 
				
			||||||
 | 
					            return PaymentMethod::class;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($this->required_fields) {
 | 
				
			||||||
 | 
					            return RequiredFields::class;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ProcessPayment::class;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[Computed()]
 | 
				
			||||||
 | 
					    public function componentUniqueId(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return "purchase-" . md5(microtime());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function mount()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->resetContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MultiDB::setDb($this->db);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // @phpstan-ignore-next-line
 | 
				
			||||||
 | 
					        $invite = \App\Models\InvoiceInvitation::with('contact.client', 'company')->withTrashed()->find($this->invitation_id);
 | 
				
			||||||
 | 
					        $client = $invite->contact->client;
 | 
				
			||||||
 | 
					        $settings = $client->getMergedSettings();
 | 
				
			||||||
 | 
					        $this->setContext('contact', $invite->contact); // $this->context['contact'] = $invite->contact;
 | 
				
			||||||
 | 
					        $this->setContext('settings', $settings); // $this->context['settings'] = $settings;
 | 
				
			||||||
 | 
					        $this->setContext('db', $this->db); // $this->context['db'] = $this->db;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        nlog($this->invoices);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(is_array($this->invoices)) {
 | 
				
			||||||
 | 
					            $this->invoices = Invoice::find($this->transformKeys($this->invoices));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $invoices = $this->invoices->filter(function ($i) {
 | 
				
			||||||
 | 
					            $i = $i->service()
 | 
				
			||||||
 | 
					                ->markSent()
 | 
				
			||||||
 | 
					                ->removeUnpaidGatewayFees()
 | 
				
			||||||
 | 
					                ->save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return $i->isPayable();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //under-over / payment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //required fields
 | 
				
			||||||
 | 
					        $this->terms_accepted = !$settings->show_accept_invoice_terms;
 | 
				
			||||||
 | 
					        $this->signature_accepted = !$settings->require_invoice_signature;
 | 
				
			||||||
 | 
					        $this->under_over_payment = $settings->client_portal_allow_over_payment || $settings->client_portal_allow_under_payment;
 | 
				
			||||||
 | 
					        $this->required_fields = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->setContext('variables', $this->variables); // $this->context['variables'] = $this->variables;
 | 
				
			||||||
 | 
					        $this->setContext('invoices', $invoices); // $this->context['invoices'] = $invoices;
 | 
				
			||||||
 | 
					        $this->setContext('settings', $settings); // $this->context['settings'] = $settings;
 | 
				
			||||||
 | 
					        $this->setContext('invitation', $invite); // $this->context['invitation'] = $invite;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $payable_invoices = $invoices->map(function ($i) {
 | 
				
			||||||
 | 
					            /** @var \App\Models\Invoice $i */
 | 
				
			||||||
 | 
					            return [
 | 
				
			||||||
 | 
					                'invoice_id' => $i->hashed_id,
 | 
				
			||||||
 | 
					                'amount' => $i->partial > 0 ? $i->partial : $i->balance,
 | 
				
			||||||
 | 
					                'formatted_amount' => Number::formatValue($i->partial > 0 ? $i->partial : $i->balance, $i->client->currency()),
 | 
				
			||||||
 | 
					                'number' => $i->number,
 | 
				
			||||||
 | 
					                'date' => $i->translateDate($i->date, $i->client->date_format(), $i->client->locale())
 | 
				
			||||||
 | 
					            ];
 | 
				
			||||||
 | 
					        })->toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->setContext('payable_invoices', $payable_invoices);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return render('flow2.invoice-pay');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										47
									
								
								app/Livewire/Flow2/InvoiceSummary.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								app/Livewire/Flow2/InvoiceSummary.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Livewire\Flow2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Utils\Traits\WithSecureContext;
 | 
				
			||||||
 | 
					use Livewire\Attributes\On;
 | 
				
			||||||
 | 
					use Livewire\Component;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InvoiceSummary extends Component
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    use WithSecureContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $invoices;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function mount()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        //@TODO for a single invoice - show all details, for multi-invoices, only show the summaries
 | 
				
			||||||
 | 
					        $this->invoices = $this->getContext()['invoices']; // $this->context['invitation']->invoice;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[On(self::CONTEXT_UPDATE)]
 | 
				
			||||||
 | 
					    public function onContextUpdate(): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // refactor logic for updating the price for eg if it changes with under/over pay
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->invoices = $this->getContext()['invoices'];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return render('flow2.invoices-summary', [
 | 
				
			||||||
 | 
					            'invoice' => $this->invoices,
 | 
				
			||||||
 | 
					            'client' => $this->invoices->first()->client,
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										78
									
								
								app/Livewire/Flow2/PaymentMethod.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								app/Livewire/Flow2/PaymentMethod.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Livewire\Flow2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Utils\Traits\WithSecureContext;
 | 
				
			||||||
 | 
					use Livewire\Component;
 | 
				
			||||||
 | 
					use App\Libraries\MultiDB;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PaymentMethod extends Component
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    use WithSecureContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $invoice;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $variables;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $methods = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $isLoading = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $amount = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function placeholder()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return <<<'HTML'
 | 
				
			||||||
 | 
					        <div  class="flex items-center justify-center min-h-screen">
 | 
				
			||||||
 | 
					        <svg class="animate-spin h-10 w-10 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
 | 
				
			||||||
 | 
					            <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
 | 
				
			||||||
 | 
					            <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
 | 
				
			||||||
 | 
					        </svg>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        HTML;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function handleSelect(string $company_gateway_id, string $gateway_type_id, string $amount)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->isLoading = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->dispatch(
 | 
				
			||||||
 | 
					            event: 'payment-method-selected',
 | 
				
			||||||
 | 
					            company_gateway_id: $company_gateway_id,
 | 
				
			||||||
 | 
					            gateway_type_id: $gateway_type_id,
 | 
				
			||||||
 | 
					            amount: $amount,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function mount()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->variables = $this->getContext()['variables'];
 | 
				
			||||||
 | 
					        $this->amount = array_sum(array_column($this->getContext()['payable_invoices'], 'amount'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MultiDB::setDb($this->getContext()['db']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->methods = $this->getContext()['invitation']->contact->client->service()->getPaymentMethods($this->amount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (count($this->methods) == 1) {
 | 
				
			||||||
 | 
					            $this->dispatch('singlePaymentMethodFound', company_gateway_id: $this->methods[0]['company_gateway_id'], gateway_type_id: $this->methods[0]['gateway_type_id'], amount: $this->amount);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            $this->isLoading = false;
 | 
				
			||||||
 | 
					            $this->dispatch('loadingCompleted');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return render('flow2.payment-method', ['methods' => $this->methods]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										86
									
								
								app/Livewire/Flow2/ProcessPayment.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								app/Livewire/Flow2/ProcessPayment.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,86 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Livewire\Flow2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Exceptions\PaymentFailed;
 | 
				
			||||||
 | 
					use App\Utils\Traits\WithSecureContext;
 | 
				
			||||||
 | 
					use Livewire\Component;
 | 
				
			||||||
 | 
					use App\Libraries\MultiDB;
 | 
				
			||||||
 | 
					use App\Models\CompanyGateway;
 | 
				
			||||||
 | 
					use App\Models\InvoiceInvitation;
 | 
				
			||||||
 | 
					use App\Services\ClientPortal\LivewireInstantPayment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProcessPayment extends Component
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    use WithSecureContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ?string $payment_view;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private array $payment_data_payload = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $isLoading = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function mount()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MultiDB::setDb($this->getContext()['db']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $invitation = InvoiceInvitation::find($this->getContext()['invitation_id']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $data = [
 | 
				
			||||||
 | 
					            'company_gateway_id' => $this->getContext()['company_gateway_id'],
 | 
				
			||||||
 | 
					            'payment_method_id' => $this->getContext()['gateway_type_id'],
 | 
				
			||||||
 | 
					            'payable_invoices' => $this->getContext()['payable_invoices'],
 | 
				
			||||||
 | 
					            'signature' => isset($this->getContext()['signature']) ? $this->getContext()['signature'] : false,
 | 
				
			||||||
 | 
					            'signature_ip' => isset($this->getContext()['signature_ip']) ? $this->getContext()['signature_ip'] : false,
 | 
				
			||||||
 | 
					            'pre_payment' => false,
 | 
				
			||||||
 | 
					            'frequency_id' => false,
 | 
				
			||||||
 | 
					            'remaining_cycles' => false,
 | 
				
			||||||
 | 
					            'is_recurring' => false,
 | 
				
			||||||
 | 
					            // 'hash' => false,
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $responder_data = (new LivewireInstantPayment($data))->run();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $company_gateway = CompanyGateway::find($this->getContext()['company_gateway_id']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!$responder_data['success']) {
 | 
				
			||||||
 | 
					            throw new PaymentFailed($responder_data['error'], 400);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $driver = $company_gateway
 | 
				
			||||||
 | 
					            ->driver($invitation->contact->client)
 | 
				
			||||||
 | 
					            ->setPaymentMethod($data['payment_method_id'])
 | 
				
			||||||
 | 
					            ->setPaymentHash($responder_data['payload']['ph']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->payment_data_payload = $driver->processPaymentViewData($responder_data['payload']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->payment_view = $driver->livewirePaymentView(
 | 
				
			||||||
 | 
					            $this->payment_data_payload,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->isLoading = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function render(): \Illuminate\Contracts\View\Factory|string|\Illuminate\View\View
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if ($this->isLoading) {
 | 
				
			||||||
 | 
					            return <<<'HTML'
 | 
				
			||||||
 | 
					            <template></template>
 | 
				
			||||||
 | 
					        HTML;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return render($this->payment_view, $this->payment_data_payload);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										136
									
								
								app/Livewire/Flow2/RequiredFields.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								app/Livewire/Flow2/RequiredFields.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,136 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Livewire\Flow2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Libraries\MultiDB;
 | 
				
			||||||
 | 
					use App\Models\CompanyGateway;
 | 
				
			||||||
 | 
					use App\Services\Client\RFFService;
 | 
				
			||||||
 | 
					use App\Utils\Traits\WithSecureContext;
 | 
				
			||||||
 | 
					use Livewire\Component;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RequiredFields extends Component
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    use WithSecureContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public ?CompanyGateway $company_gateway;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public ?string $client_name;
 | 
				
			||||||
 | 
					    public ?string $contact_first_name;
 | 
				
			||||||
 | 
					    public ?string $contact_last_name;
 | 
				
			||||||
 | 
					    public ?string $contact_email;
 | 
				
			||||||
 | 
					    public ?string $client_phone;
 | 
				
			||||||
 | 
					    public ?string $client_address_line_1;
 | 
				
			||||||
 | 
					    public ?string $client_city;
 | 
				
			||||||
 | 
					    public ?string $client_state;
 | 
				
			||||||
 | 
					    public ?int $client_country_id;
 | 
				
			||||||
 | 
					    public ?string $client_postal_code;
 | 
				
			||||||
 | 
					    public ?string $client_shipping_address_line_1;
 | 
				
			||||||
 | 
					    public ?string $client_shipping_city;
 | 
				
			||||||
 | 
					    public ?string $client_shipping_state;
 | 
				
			||||||
 | 
					    public ?string $client_shipping_postal_code;
 | 
				
			||||||
 | 
					    public ?int $client_shipping_country_id;
 | 
				
			||||||
 | 
					    public ?string $client_custom_value1;
 | 
				
			||||||
 | 
					    public ?string $client_custom_value2;
 | 
				
			||||||
 | 
					    public ?string $client_custom_value3;
 | 
				
			||||||
 | 
					    public ?string $client_custom_value4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @var array<int, string> */
 | 
				
			||||||
 | 
					    public array $fields = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public bool $is_loading = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public array $errors = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function mount(): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        MultiDB::setDB(
 | 
				
			||||||
 | 
					            $this->getContext()['db'],
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->fields = $this->getContext()['fields'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->company_gateway = CompanyGateway::withTrashed()
 | 
				
			||||||
 | 
					            ->with('company')
 | 
				
			||||||
 | 
					            ->find($this->getContext()['company_gateway_id']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $contact = auth()->user();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->client_name = $contact->client->name;
 | 
				
			||||||
 | 
					        $this->contact_first_name = $contact->first_name;
 | 
				
			||||||
 | 
					        $this->contact_last_name = $contact->last_name;
 | 
				
			||||||
 | 
					        $this->contact_email = $contact->email;
 | 
				
			||||||
 | 
					        $this->client_phone = $contact->client->phone;
 | 
				
			||||||
 | 
					        $this->client_address_line_1 = $contact->client->address1;
 | 
				
			||||||
 | 
					        $this->client_city = $contact->client->city;
 | 
				
			||||||
 | 
					        $this->client_state = $contact->client->state;
 | 
				
			||||||
 | 
					        $this->client_country_id = $contact->client->country_id;
 | 
				
			||||||
 | 
					        $this->client_postal_code = $contact->client->postal_code;
 | 
				
			||||||
 | 
					        $this->client_shipping_address_line_1 = $contact->client->shipping_address1;
 | 
				
			||||||
 | 
					        $this->client_shipping_city = $contact->client->shipping_city;
 | 
				
			||||||
 | 
					        $this->client_shipping_state = $contact->client->shipping_state;
 | 
				
			||||||
 | 
					        $this->client_shipping_postal_code = $contact->client->shipping_postal_code;
 | 
				
			||||||
 | 
					        $this->client_shipping_country_id = $contact->client->shipping_country_id;
 | 
				
			||||||
 | 
					        $this->client_custom_value1 = $contact->client->custom_value1;
 | 
				
			||||||
 | 
					        $this->client_custom_value2 = $contact->client->custom_value2;
 | 
				
			||||||
 | 
					        $this->client_custom_value3 = $contact->client->custom_value3;
 | 
				
			||||||
 | 
					        $this->client_custom_value4 = $contact->client->custom_value4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $rff = new RFFService(
 | 
				
			||||||
 | 
					            fields: $this->getContext()['fields'],
 | 
				
			||||||
 | 
					            database: $this->getContext()['db'],
 | 
				
			||||||
 | 
					            company_gateway_id: $this->company_gateway->id,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /** @var \App\Models\ClientContact $contact */
 | 
				
			||||||
 | 
					        $rff->check($contact);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($rff->unfilled_fields === 0) {
 | 
				
			||||||
 | 
					            $this->dispatch('required-fields');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($rff->unfilled_fields > 0) {
 | 
				
			||||||
 | 
					            $this->is_loading = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function handleSubmit(array $data)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->errors = [];
 | 
				
			||||||
 | 
					        $this->is_loading = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $rff = new RFFService(
 | 
				
			||||||
 | 
					            fields: $this->fields,
 | 
				
			||||||
 | 
					            database: $this->getContext()['db'],
 | 
				
			||||||
 | 
					            company_gateway_id: $this->company_gateway->id,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $contact = auth()->user();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /** @var \App\Models\ClientContact $contact */
 | 
				
			||||||
 | 
					        $errors = $rff->handleSubmit($data, $contact, return_errors: true, callback: function () {
 | 
				
			||||||
 | 
					            $this->dispatch('required-fields');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (is_array($errors) && count($errors)) {
 | 
				
			||||||
 | 
					            $this->errors = $errors;
 | 
				
			||||||
 | 
					            $this->is_loading = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return render('flow2.required-fields', [
 | 
				
			||||||
 | 
					            'contact' => $this->getContext()['contact'],
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								app/Livewire/Flow2/Signature.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								app/Livewire/Flow2/Signature.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Livewire\Flow2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Livewire\Component;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Signature extends Component
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function render()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return render('components.livewire.signature');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										36
									
								
								app/Livewire/Flow2/Terms.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								app/Livewire/Flow2/Terms.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Livewire\Flow2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Utils\Traits\WithSecureContext;
 | 
				
			||||||
 | 
					use Livewire\Component;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Terms extends Component
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    use WithSecureContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $invoice;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $variables;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function mount()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->invoice = $this->getContext()['invoices']->first();
 | 
				
			||||||
 | 
					        $this->variables = $this->getContext()['variables'];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function render()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return render('components.livewire.terms');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										76
									
								
								app/Livewire/Flow2/UnderOverPayment.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								app/Livewire/Flow2/UnderOverPayment.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Invoice Ninja (https://invoiceninja.com).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @license https://www.elastic.co/licensing/elastic-license
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Livewire\Flow2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Utils\Number;
 | 
				
			||||||
 | 
					use App\Utils\Traits\WithSecureContext;
 | 
				
			||||||
 | 
					use Livewire\Component;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UnderOverPayment extends Component
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    use WithSecureContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $payableAmount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $currency;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $invoice_amount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $errors = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public $payableInvoices = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function mount()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->invoice_amount = array_sum(array_column($this->getContext()['payable_invoices'], 'amount'));
 | 
				
			||||||
 | 
					        $this->currency = $this->getContext()['invitation']->contact->client->currency();
 | 
				
			||||||
 | 
					        $this->payableInvoices = $this->getContext()['payable_invoices'];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function checkValue(array $payableInvoices)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->errors = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $settings = $this->getContext()['settings'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        foreach($payableInvoices as $key => $invoice) {
 | 
				
			||||||
 | 
					            $payableInvoices[$key]['amount'] = Number::parseFloat($invoice['formatted_amount']);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $input_amount = collect($payableInvoices)->sum('amount');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if($settings->client_portal_allow_under_payment && $settings->client_portal_under_payment_minimum != 0) {
 | 
				
			||||||
 | 
					            if($input_amount <= $settings->client_portal_under_payment_minimum) {
 | 
				
			||||||
 | 
					                // return error message under payment too low.
 | 
				
			||||||
 | 
					                $this->errors = ctrans('texts.minimum_required_payment', ['amount' => $settings->client_portal_under_payment_minimum]);
 | 
				
			||||||
 | 
					                $this->dispatch('errorMessageUpdate', errors: $this->errors);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(!$settings->client_portal_allow_over_payment && ($input_amount > $this->invoice_amount)) {
 | 
				
			||||||
 | 
					            $this->errors = ctrans('texts.over_payments_disabled');
 | 
				
			||||||
 | 
					            $this->dispatch('errorMessageUpdate', errors: $this->errors);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(!$this->errors) {
 | 
				
			||||||
 | 
					            $this->setContext('payable_invoices', $payableInvoices);
 | 
				
			||||||
 | 
					            $this->dispatch('payable-amount', payable_amount: $input_amount);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return render('flow2.under-over-payments');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -20,8 +20,6 @@ class PersonalAddress extends Component
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public $country_id;
 | 
					    public $country_id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public $countries;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public $saved;
 | 
					    public $saved;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected $rules = [
 | 
					    protected $rules = [
 | 
				
			||||||
@ -33,7 +31,7 @@ class PersonalAddress extends Component
 | 
				
			|||||||
        'country_id' => ['sometimes'],
 | 
					        'country_id' => ['sometimes'],
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function mount($countries)
 | 
					    public function mount()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->fill([
 | 
					        $this->fill([
 | 
				
			||||||
            'profile' => auth()->guard('contact')->user()->client,
 | 
					            'profile' => auth()->guard('contact')->user()->client,
 | 
				
			||||||
@ -43,8 +41,6 @@ class PersonalAddress extends Component
 | 
				
			|||||||
            'state' => auth()->guard('contact')->user()->client->state,
 | 
					            'state' => auth()->guard('contact')->user()->client->state,
 | 
				
			||||||
            'postal_code' => auth()->guard('contact')->user()->client->postal_code,
 | 
					            'postal_code' => auth()->guard('contact')->user()->client->postal_code,
 | 
				
			||||||
            'country_id' => auth()->guard('contact')->user()->client->country_id,
 | 
					            'country_id' => auth()->guard('contact')->user()->client->country_id,
 | 
				
			||||||
 | 
					 | 
				
			||||||
            'countries' => $countries,
 | 
					 | 
				
			||||||
            'saved' => ctrans('texts.save'),
 | 
					            'saved' => ctrans('texts.save'),
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
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